Skip to content

Commit d94d8fe

Browse files
committed
Switch to use pydantic model for operation path.
1 parent 5929b00 commit d94d8fe

File tree

3 files changed

+168
-34
lines changed

3 files changed

+168
-34
lines changed

stac_fastapi/core/stac_fastapi/core/utilities.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ def operations_to_script(operations: List) -> Dict:
191191
source = ""
192192
for operation in operations:
193193
op_path = ElasticPath(path=operation.path)
194+
source = add_script_checks(source, operation.op, op_path)
194195

195196
if hasattr(operation, "from"):
196197
from_path = ElasticPath(path=(getattr(operation, "from")))
@@ -203,8 +204,6 @@ def operations_to_script(operations: List) -> Dict:
203204
f"{{Debug.explain('{from_path.path} does not exist');}}"
204205
)
205206

206-
source = add_script_checks(source, operation.op, op_path)
207-
208207
if operation.op in ["copy", "move"]:
209208
if op_path.index:
210209
source += (
@@ -236,6 +235,12 @@ def operations_to_script(operations: List) -> Dict:
236235
else:
237236
source += f"ctx._source.{op_path.path} = {operation.json_value};"
238237

238+
if operation.op == "test":
239+
source += (
240+
f"if (ctx._source.{op_path.location} != {operation.json_value})"
241+
f"{{Debug.explain('Test failed for: {op_path.path} | {operation.json_value} != ctx._source.{op_path.location}');}}"
242+
)
243+
239244
return {
240245
"source": source,
241246
"lang": "painless",

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/database_logic.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -896,10 +896,8 @@ async def json_patch_item(
896896
script_operations = []
897897

898898
for operation in operations:
899-
if operation.path in [
900-
"collection",
901-
"id",
902-
] and operation.op in ["add", "replace"]:
899+
if operation.path in ["collection", "id"] and operation.op in ["add", "replace"]:
900+
903901
if operation.path == "collection" and collection_id != operation.value:
904902
await self.check_collection_exists(collection_id=operation.value)
905903
new_collection_id = operation.value

stac_fastapi/tests/clients/test_elasticsearch.py

Lines changed: 159 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ async def test_update_collection(
4343
collection_data = load_test_data("test_collection.json")
4444
item_data = load_test_data("test_item.json")
4545

46-
await txn_client.create_collection(
47-
api.Collection(**collection_data), request=MockRequest
48-
)
46+
await txn_client.create_collection(api.Collection(**collection_data), request=MockRequest)
4947
await txn_client.create_item(
5048
collection_id=collection_data["id"],
5149
item=api.Item(**item_data),
@@ -54,9 +52,7 @@ async def test_update_collection(
5452
)
5553

5654
collection_data["keywords"].append("new keyword")
57-
await txn_client.update_collection(
58-
collection_data["id"], api.Collection(**collection_data), request=MockRequest
59-
)
55+
await txn_client.update_collection(collection_data["id"], api.Collection(**collection_data), request=MockRequest)
6056

6157
coll = await core_client.get_collection(collection_data["id"], request=MockRequest)
6258
assert "new keyword" in coll["keywords"]
@@ -83,9 +79,7 @@ async def test_update_collection_id(
8379
item_data = load_test_data("test_item.json")
8480
new_collection_id = "new-test-collection"
8581

86-
await txn_client.create_collection(
87-
api.Collection(**collection_data), request=MockRequest
88-
)
82+
await txn_client.create_collection(api.Collection(**collection_data), request=MockRequest)
8983
await txn_client.create_item(
9084
collection_id=collection_data["id"],
9185
item=api.Item(**item_data),
@@ -197,14 +191,10 @@ async def test_get_collection_items(app_client, ctx, core_client, txn_client):
197191

198192
@pytest.mark.asyncio
199193
async def test_create_item(ctx, core_client, txn_client):
200-
resp = await core_client.get_item(
201-
ctx.item["id"], ctx.item["collection"], request=MockRequest
202-
)
203-
assert Item(**ctx.item).model_dump(
204-
exclude={"links": ..., "properties": {"created", "updated"}}
205-
) == Item(**resp).model_dump(
206-
exclude={"links": ..., "properties": {"created", "updated"}}
207-
)
194+
resp = await core_client.get_item(ctx.item["id"], ctx.item["collection"], request=MockRequest)
195+
assert Item(**ctx.item).model_dump(exclude={"links": ..., "properties": {"created", "updated"}}) == Item(
196+
**resp
197+
).model_dump(exclude={"links": ..., "properties": {"created", "updated"}})
208198

209199

210200
@pytest.mark.asyncio
@@ -231,10 +221,157 @@ async def test_update_item(ctx, core_client, txn_client):
231221
request=MockRequest,
232222
)
233223

234-
updated_item = await core_client.get_item(
235-
item_id, collection_id, request=MockRequest
224+
updated_item = await core_client.get_item(item_id, collection_id, request=MockRequest)
225+
assert updated_item["properties"]["foo"] == "bar"
226+
227+
228+
@pytest.mark.asyncio
229+
async def test_merge_patch_item(ctx, core_client, txn_client):
230+
item = ctx.item
231+
collection_id = item["collection"]
232+
item_id = item["id"]
233+
await txn_client.merge_patch_item(
234+
collection_id=collection_id,
235+
item_id=item_id,
236+
item={"properties": {"foo": "bar", "gsd": None}},
237+
request=MockRequest,
236238
)
239+
240+
updated_item = await core_client.get_item(item_id, collection_id, request=MockRequest)
237241
assert updated_item["properties"]["foo"] == "bar"
242+
assert "gsd" not in updated_item["properties"]
243+
244+
245+
@pytest.mark.asyncio
246+
async def test_json_patch_item(ctx, core_client, txn_client):
247+
item = ctx.item
248+
collection_id = item["collection"]
249+
item_id = item["id"]
250+
operations = [
251+
{"op": "add", "path": "properties.bar", "value": "foo"},
252+
{"op": "remove", "path": "properties.instrument"},
253+
{"op": "replace", "path": "properties.width", "value": 100},
254+
{"op": "test", "path": "properties.platform", "value": "landsat-8"},
255+
{"op": "move", "path": "properties.hello", "from": "properties.height"},
256+
{"op": "copy", "path": "properties.world", "from": "properties.proj:epsg"},
257+
]
258+
259+
await txn_client.json_patch_item(
260+
collection_id=collection_id,
261+
item_id=item_id,
262+
operations=operations,
263+
request=MockRequest,
264+
)
265+
266+
updated_item = await core_client.get_item(item_id, collection_id, request=MockRequest)
267+
268+
# add foo
269+
assert updated_item["properties"]["bar"] == "foo"
270+
# remove gsd
271+
assert "instrument" not in updated_item["properties"]
272+
# replace width
273+
assert updated_item["properties"]["width"] == 100
274+
# move height
275+
assert updated_item["properties"]["hello"] == item["properties"]["height"]
276+
assert "height" not in updated_item["properties"]
277+
# copy proj:epsg
278+
assert updated_item["properties"]["world"] == item["properties"]["proj:epsg"]
279+
assert updated_item["properties"]["proj:epsg"] == item["properties"]["proj:epsg"]
280+
281+
282+
@pytest.mark.asyncio
283+
async def test_json_patch_item_test_wrong_value(ctx, core_client, txn_client):
284+
item = ctx.item
285+
collection_id = item["collection"]
286+
item_id = item["id"]
287+
operations = [
288+
{"op": "test", "path": "properties.platform", "value": "landsat-9"},
289+
]
290+
291+
with pytest.raises(ConflictError):
292+
293+
await txn_client.json_patch_item(
294+
collection_id=collection_id,
295+
item_id=item_id,
296+
operations=operations,
297+
request=MockRequest,
298+
)
299+
300+
301+
@pytest.mark.asyncio
302+
async def test_json_patch_item_replace_property_does_not_exists(ctx, core_client, txn_client):
303+
item = ctx.item
304+
collection_id = item["collection"]
305+
item_id = item["id"]
306+
operations = [
307+
{"op": "replace", "path": "properties.platforms", "value": "landsat-9"},
308+
]
309+
310+
with pytest.raises(ConflictError):
311+
312+
await txn_client.json_patch_item(
313+
collection_id=collection_id,
314+
item_id=item_id,
315+
operations=operations,
316+
request=MockRequest,
317+
)
318+
319+
320+
@pytest.mark.asyncio
321+
async def test_json_patch_item_remove_property_does_not_exists(ctx, core_client, txn_client):
322+
item = ctx.item
323+
collection_id = item["collection"]
324+
item_id = item["id"]
325+
operations = [
326+
{"op": "remove", "path": "properties.platforms"},
327+
]
328+
329+
with pytest.raises(ConflictError):
330+
331+
await txn_client.json_patch_item(
332+
collection_id=collection_id,
333+
item_id=item_id,
334+
operations=operations,
335+
request=MockRequest,
336+
)
337+
338+
339+
@pytest.mark.asyncio
340+
async def test_json_patch_item_move_property_does_not_exists(ctx, core_client, txn_client):
341+
item = ctx.item
342+
collection_id = item["collection"]
343+
item_id = item["id"]
344+
operations = [
345+
{"op": "move", "path": "properties.platformed", "from": "properties.platforms"},
346+
]
347+
348+
with pytest.raises(ConflictError):
349+
350+
await txn_client.json_patch_item(
351+
collection_id=collection_id,
352+
item_id=item_id,
353+
operations=operations,
354+
request=MockRequest,
355+
)
356+
357+
358+
@pytest.mark.asyncio
359+
async def test_json_patch_item_copy_property_does_not_exists(ctx, core_client, txn_client):
360+
item = ctx.item
361+
collection_id = item["collection"]
362+
item_id = item["id"]
363+
operations = [
364+
{"op": "copy", "path": "properties.platformed", "from": "properties.platforms"},
365+
]
366+
367+
with pytest.raises(ConflictError):
368+
369+
await txn_client.json_patch_item(
370+
collection_id=collection_id,
371+
item_id=item_id,
372+
operations=operations,
373+
request=MockRequest,
374+
)
238375

239376

240377
@pytest.mark.asyncio
@@ -259,9 +396,7 @@ async def test_update_geometry(ctx, core_client, txn_client):
259396
request=MockRequest,
260397
)
261398

262-
updated_item = await core_client.get_item(
263-
item_id, collection_id, request=MockRequest
264-
)
399+
updated_item = await core_client.get_item(item_id, collection_id, request=MockRequest)
265400
assert updated_item["geometry"]["coordinates"] == new_coordinates
266401

267402

@@ -270,9 +405,7 @@ async def test_delete_item(ctx, core_client, txn_client):
270405
await txn_client.delete_item(ctx.item["id"], ctx.item["collection"])
271406

272407
with pytest.raises(NotFoundError):
273-
await core_client.get_item(
274-
ctx.item["id"], ctx.item["collection"], request=MockRequest
275-
)
408+
await core_client.get_item(ctx.item["id"], ctx.item["collection"], request=MockRequest)
276409

277410

278411
@pytest.mark.asyncio
@@ -321,9 +454,7 @@ async def test_feature_collection_insert(
321454
async def test_landing_page_no_collection_title(ctx, core_client, txn_client, app):
322455
ctx.collection["id"] = "new_id"
323456
del ctx.collection["title"]
324-
await txn_client.create_collection(
325-
api.Collection(**ctx.collection), request=MockRequest
326-
)
457+
await txn_client.create_collection(api.Collection(**ctx.collection), request=MockRequest)
327458

328459
landing_page = await core_client.landing_page(request=MockRequest(app=app))
329460
for link in landing_page["links"]:

0 commit comments

Comments
 (0)