Skip to content

Commit 264a719

Browse files
authored
Change upsert methods to use server side upsert (#63)
1 parent 73ee1f1 commit 264a719

23 files changed

+334
-232
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.5.0
1+
3.0.0

exabel_data_sdk/client/api/data_set_api.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,28 @@ def create_data_set(self, data_set: DataSet) -> DataSet:
5959
response = self.client.create_data_set(CreateDataSetRequest(data_set=data_set.to_proto()))
6060
return DataSet.from_proto(response)
6161

62-
def update_data_set(self, data_set: DataSet, update_mask: FieldMask = None) -> DataSet:
62+
def update_data_set(
63+
self,
64+
data_set: DataSet,
65+
update_mask: FieldMask = None,
66+
allow_missing: bool = False,
67+
) -> DataSet:
6368
"""
6469
Update a data set.
6570
6671
Args:
67-
data_set: The data set to update.
68-
update_mask: The fields to update. If not specified, the update behaves as a
69-
full update, overwriting all existing fields and properties.
72+
data_set: The data set to update.
73+
update_mask: The fields to update. If not specified, the update behaves as a
74+
full update, overwriting all existing fields and properties.
75+
allow_missing: If set to true, and the resource is not found, a new resource will be
76+
created. In this situation, the "update_mask" is ignored.
7077
"""
7178
response = self.client.update_data_set(
72-
UpdateDataSetRequest(data_set=data_set.to_proto(), update_mask=update_mask)
79+
UpdateDataSetRequest(
80+
data_set=data_set.to_proto(),
81+
update_mask=update_mask,
82+
allow_missing=allow_missing,
83+
)
7384
)
7485
return DataSet.from_proto(response)
7586

exabel_data_sdk/client/api/entity_api.py

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -132,49 +132,38 @@ def create_entity(self, entity: Entity, entity_type: str) -> Entity:
132132
)
133133
return Entity.from_proto(response)
134134

135-
def update_entity(self, entity: Entity, update_mask: FieldMask = None) -> Entity:
135+
def update_entity(
136+
self, entity: Entity, update_mask: FieldMask = None, allow_missing: bool = False
137+
) -> Entity:
136138
"""
137139
Update an entity.
138140
139141
Args:
140142
entity: The entity to update.
141143
update_mask: Fields to update. If not specified, the update behaves as a full update,
142144
overwriting all existing fields and properties.
145+
allow_missing: If set to true, and the resource is not found, a new resource will be
146+
created. In this situation, the "update_mask" is ignored.
143147
"""
144148
response = self.client.update_entity(
145-
UpdateEntityRequest(entity=entity.to_proto(), update_mask=update_mask)
149+
UpdateEntityRequest(
150+
entity=entity.to_proto(),
151+
update_mask=update_mask,
152+
allow_missing=allow_missing,
153+
)
146154
)
147155
return Entity.from_proto(response)
148156

149-
def upsert_entity(self, entity: Entity, assume_exists: bool = True) -> Entity:
157+
def upsert_entity(self, entity: Entity) -> Entity:
150158
"""
151159
Upsert an entity.
152160
153161
If the entity already exists, update it by replacement. Otherwise, create it.
154162
155163
Args:
156-
entity: The entity to upsert.
157-
assume_exists: If True, the entity is assumed to exist. Will try to to update
158-
the entity, and fall back to creating it if it does not exist,
159-
and vice versa.
160-
"""
161-
if assume_exists:
162-
try:
163-
entity = self.update_entity(entity)
164-
except RequestError as error:
165-
if error.error_type == ErrorType.NOT_FOUND:
166-
entity = self.create_entity(entity, entity.get_entity_type())
167-
else:
168-
raise error
169-
else:
170-
try:
171-
entity = self.create_entity(entity, entity.get_entity_type())
172-
except RequestError as error:
173-
if error.error_type == ErrorType.ALREADY_EXISTS:
174-
entity = self.update_entity(entity)
175-
else:
176-
raise error
177-
return entity
164+
entity: The entity to upsert.
165+
"""
166+
return self.update_entity(entity, allow_missing=True)
178167

179168
def delete_entity(self, name: str) -> None:
180169
"""
@@ -230,8 +219,7 @@ def bulk_create_entities(
230219

231220
def insert(entity: Entity) -> ResourceCreationStatus:
232221
if upsert:
233-
# Upsert entities assuming they already exist.
234-
self.upsert_entity(entity=entity, assume_exists=True)
222+
self.upsert_entity(entity=entity)
235223
return ResourceCreationStatus.UPSERTED
236224
# Optimistically insert the entity.
237225
# If the entity already exists, we'll get an ALREADY_EXISTS error from the backend,

exabel_data_sdk/client/api/relationship_api.py

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@ def create_relationship_type(self, relationship_type: RelationshipType) -> Relat
9191
return RelationshipType.from_proto(response)
9292

9393
def update_relationship_type(
94-
self, relationship_type: RelationshipType, update_mask: FieldMask = None
94+
self,
95+
relationship_type: RelationshipType,
96+
update_mask: FieldMask = None,
97+
allow_missing: bool = False,
9598
) -> RelationshipType:
9699
"""
97100
Update a relationship type.
@@ -100,10 +103,14 @@ def update_relationship_type(
100103
relationship_type: The relationship type to update.
101104
update_mask: The fields to update. If not specified, the update behaves as a
102105
full update, overwriting all existing fields and properties.
106+
allow_missing: If set to true, and the resource is not found, a new resource will
107+
be created. In this situation, the "update_mask" is ignored.
103108
"""
104109
response = self.client.update_relationship_type(
105110
UpdateRelationshipTypeRequest(
106-
relationship_type=relationship_type.to_proto(), update_mask=update_mask
111+
relationship_type=relationship_type.to_proto(),
112+
update_mask=update_mask,
113+
allow_missing=allow_missing,
107114
)
108115
)
109116
return RelationshipType.from_proto(response)
@@ -228,52 +235,37 @@ def create_relationship(self, relationship: Relationship) -> Relationship:
228235
return Relationship.from_proto(response)
229236

230237
def update_relationship(
231-
self, relationship: Relationship, update_mask: FieldMask = None
238+
self, relationship: Relationship, update_mask: FieldMask = None, allow_missing: bool = False
232239
) -> Relationship:
233240
"""
234241
Update a relationship between two entities.
235242
236243
Args:
237-
relationship: The relationship to update.
238-
update_mask: The fields to update. If not specified, the update behaves as a
239-
full update, overwriting all existing fields and properties.
244+
relationship: The relationship to update.
245+
update_mask: The fields to update. If not specified, the update behaves as a
246+
full update, overwriting all existing fields and properties.
247+
allow_missing: If set to true, and the resource is not found, a new resource will
248+
be created. In this situation, the "update_mask" is ignored.
240249
"""
241250
response = self.client.update_relationship(
242-
UpdateRelationshipRequest(relationship=relationship.to_proto(), update_mask=update_mask)
251+
UpdateRelationshipRequest(
252+
relationship=relationship.to_proto(),
253+
update_mask=update_mask,
254+
allow_missing=allow_missing,
255+
)
243256
)
244257
return Relationship.from_proto(response)
245258

246-
def upsert_relationship(
247-
self, relationship: Relationship, assume_exists: bool = True
248-
) -> Relationship:
259+
def upsert_relationship(self, relationship: Relationship) -> Relationship:
249260
"""
250261
Upsert a relationship between two entities.
251262
252263
If the relationship already exists, update it by replacement. Otherwise, create it.
253264
254265
Args:
255266
relationship: The relationship to upsert.
256-
assume_exists: If True, the relationship is assumed to exist. Will try to to update
257-
the relationship, and fall back to creating it if it does not exist,
258-
and vice versa.
259267
"""
260-
if assume_exists:
261-
try:
262-
relationship = self.update_relationship(relationship)
263-
except RequestError as error:
264-
if error.error_type == ErrorType.NOT_FOUND:
265-
relationship = self.create_relationship(relationship)
266-
else:
267-
raise error
268-
else:
269-
try:
270-
relationship = self.create_relationship(relationship)
271-
except RequestError as error:
272-
if error.error_type == ErrorType.ALREADY_EXISTS:
273-
relationship = self.update_relationship(relationship)
274-
else:
275-
raise error
276-
return relationship
268+
return self.update_relationship(relationship, allow_missing=True)
277269

278270
def delete_relationship(self, relationship_type: str, from_entity: str, to_entity: str) -> None:
279271
"""
@@ -319,8 +311,7 @@ def bulk_create_relationships(
319311

320312
def insert(relationship: Relationship) -> ResourceCreationStatus:
321313
if upsert:
322-
# Upsert relationships assuming they already exist.
323-
self.upsert_relationship(relationship=relationship, assume_exists=True)
314+
self.upsert_relationship(relationship=relationship)
324315
return ResourceCreationStatus.UPSERTED
325316
# Optimistically insert the relationship.
326317
# If the relationship already exists, we'll get an ALREADY_EXISTS error from the

exabel_data_sdk/client/api/signal_api.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,33 @@ def create_signal(self, signal: Signal, create_library_signal: bool = False) ->
7777
)
7878
return Signal.from_proto(response)
7979

80-
def update_signal(self, signal: Signal, update_mask: FieldMask = None) -> Signal:
80+
def update_signal(
81+
self,
82+
signal: Signal,
83+
update_mask: FieldMask = None,
84+
allow_missing: bool = False,
85+
create_library_signal: bool = False,
86+
) -> Signal:
8187
"""
8288
Update one signal and return it.
8389
8490
Args:
85-
signal: The signal to update.
86-
update_mask: The fields to update. If not specified, the update behaves as a
87-
full update, overwriting all existing fields and properties.
91+
signal: The signal to update.
92+
update_mask: The fields to update. If not specified, the update behaves as a
93+
full update, overwriting all existing fields and properties.
94+
allow_missing: If set to true, and the resource is not found, a new resource will be
95+
created. In this situation, the "update_mask" is ignored.
96+
create_library_signal:
97+
If allow_missing is set to true and the signal does not exist, also add
98+
it to the library.
8899
"""
89100
response = self.client.update_signal(
90-
UpdateSignalRequest(signal=signal.to_proto(), update_mask=update_mask),
101+
UpdateSignalRequest(
102+
signal=signal.to_proto(),
103+
update_mask=update_mask,
104+
allow_missing=allow_missing,
105+
create_library_signal=create_library_signal,
106+
),
91107
)
92108
return Signal.from_proto(response)
93109

exabel_data_sdk/client/api/time_series_api.py

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ def upsert_time_series(
176176
series: pd.Series,
177177
create_tag: bool = False,
178178
default_known_time: DefaultKnownTime = None,
179-
) -> bool:
179+
) -> None:
180180
"""
181181
Create or update a time series.
182182
@@ -197,20 +197,10 @@ def upsert_time_series(
197197
the Known Time for data points where a specific known time timestamp
198198
has not been given. If not provided, the Exabel API defaults to the
199199
current time (upload time) as the Known Time.
200-
201-
Returns:
202-
True if the time series already existed, or False if it is created
203200
"""
204-
try:
205-
# Optimistically assume that the time series exists, and append to it.
206-
# If it doesn't exist, we catch the error below and create the time series instead.
207-
self.append_time_series_data(name, series, default_known_time)
208-
return True
209-
except RequestError as error:
210-
if error.error_type == ErrorType.NOT_FOUND:
211-
self.create_time_series(name, series, create_tag, default_known_time)
212-
return False
213-
raise
201+
self.append_time_series_data(
202+
name, series, default_known_time, allow_missing=True, create_tag=create_tag
203+
)
214204

215205
def clear_time_series_data(self, name: str, start: pd.Timestamp, end: pd.Timestamp) -> None:
216206
"""
@@ -227,7 +217,12 @@ def clear_time_series_data(self, name: str, start: pd.Timestamp, end: pd.Timesta
227217
)
228218

229219
def append_time_series_data(
230-
self, name: str, series: pd.Series, default_known_time: DefaultKnownTime = None
220+
self,
221+
name: str,
222+
series: pd.Series,
223+
default_known_time: DefaultKnownTime = None,
224+
allow_missing: bool = False,
225+
create_tag: bool = False,
231226
) -> None:
232227
"""
233228
Append data to the given time series.
@@ -236,25 +231,36 @@ def append_time_series_data(
236231
overwritten.
237232
238233
Args:
239-
name: The resource name of the time series.
240-
series: Series with data to append.
234+
name: The resource name of the time series.
235+
series: Series with data to append.
241236
default_known_time:
242-
Specify a default known time policy. This is used to determine
243-
the Known Time for data points where a specific known time timestamp
244-
has not been given. If not provided, the Exabel API defaults to the
245-
current time (upload time) as the Known Time.
237+
Specify a default known time policy. This is used to determine
238+
the Known Time for data points where a specific known time timestamp
239+
has not been given. If not provided, the Exabel API defaults to the
240+
current time (upload time) as the Known Time.
241+
allow_missing: If set to true, and the resource is not found, a new resource will be
242+
created. In this situation, the "update_mask" is ignored.
243+
create_tag: If allow_missing is set to true and the time series does not exist, also
244+
create a tag for every entity type the signal has time series for.
246245
"""
247246
self.client.update_time_series(
248247
UpdateTimeSeriesRequest(
249248
time_series=ProtoTimeSeries(
250249
name=name, points=self._series_to_time_series_points(series)
251250
),
252251
default_known_time=default_known_time,
252+
allow_missing=allow_missing,
253+
create_tag=create_tag,
253254
),
254255
)
255256

256257
def append_time_series_data_and_return(
257-
self, name: str, series: pd.Series, default_known_time: DefaultKnownTime = None
258+
self,
259+
name: str,
260+
series: pd.Series,
261+
default_known_time: DefaultKnownTime = None,
262+
allow_missing: Optional[bool] = False,
263+
create_tag: Optional[bool] = False,
258264
) -> pd.Series:
259265
"""
260266
Append data to the given time series, and return the full series.
@@ -263,13 +269,17 @@ def append_time_series_data_and_return(
263269
overwritten.
264270
265271
Args:
266-
name: The resource name of the time series.
267-
series: Series with data to append.
272+
name: The resource name of the time series.
273+
series: Series with data to append.
268274
default_known_time:
269-
Specify a default known time policy. This is used to determine
270-
the Known Time for data points where a specific known time timestamp
271-
has not been given. If not provided, the Exabel API defaults to the
272-
current time (upload time) as the Known Time.
275+
Specify a default known time policy. This is used to determine
276+
the Known Time for data points where a specific known time timestamp
277+
has not been given. If not provided, the Exabel API defaults to the
278+
current time (upload time) as the Known Time.
279+
allow_missing: If set to true, and the resource is not found, a new resource will be
280+
created. In this situation, the "update_mask" is ignored.
281+
create_tag: If allow_missing is set to true and the time series does not exist, also
282+
create a tag for every entity type the signal has time series for.
273283
274284
Returns:
275285
A series with all data for the given time series.
@@ -282,6 +292,8 @@ def append_time_series_data_and_return(
282292
),
283293
view=TimeSeriesView(time_range=TimeRange()),
284294
default_known_time=default_known_time,
295+
allow_missing=allow_missing,
296+
create_tag=create_tag,
285297
),
286298
)
287299
return self._time_series_points_to_series(time_series.points, time_series.name)
@@ -337,10 +349,10 @@ def bulk_upsert_time_series(
337349
"""
338350

339351
def insert(ts: pd.Series) -> ResourceCreationStatus:
340-
existed = self.upsert_time_series(
352+
self.upsert_time_series(
341353
str(ts.name), ts, create_tag=create_tag, default_known_time=default_known_time
342354
)
343-
return ResourceCreationStatus.UPSERTED if existed else ResourceCreationStatus.CREATED
355+
return ResourceCreationStatus.UPSERTED
344356

345357
return bulk_insert(series, insert, threads=threads, retries=retries)
346358

0 commit comments

Comments
 (0)