Skip to content

Commit cb08b0f

Browse files
Allow to omit collection in bulk insertions (#113)
* In bulk insertions allow to omit collection in items Same checks in item bulk insertions as in single insertions Added tests * Fixing whitespace * Update changelog * Linting fix * fix tests --------- Co-authored-by: vincentsarago <[email protected]>
1 parent 1f36485 commit cb08b0f

File tree

3 files changed

+104
-5
lines changed

3 files changed

+104
-5
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [Unreleased]
44

55
- Enable filter extension for `GET /items` requests and add `Queryables` links in `/collections` and `/collections/{collection_id}` responses ([#89](https://github.com/stac-utils/stac-fastapi-pgstac/pull/89))
6+
- Allow to omit `collection` in bulk item insertions. Same identifier checks as with single insertions ([#113](https://github.com/stac-utils/stac-fastapi-pgstac/pull/113))
67

78
## [3.0.0a4] - 2024-07-10
89

stac_fastapi/pgstac/transactions.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,7 @@
2525
logger.setLevel(logging.INFO)
2626

2727

28-
@attr.s
29-
class TransactionsClient(AsyncBaseTransactionsClient):
30-
"""Transactions extension specific CRUD operations."""
31-
28+
class ClientValidateMixIn:
3229
def _validate_id(self, id: str, settings: Settings):
3330
invalid_chars = settings.invalid_id_chars
3431
id_regex = "[" + "".join(re.escape(char) for char in invalid_chars) + "]"
@@ -67,6 +64,11 @@ def _validate_item(
6764
detail=f"Item ID from path parameter ({expected_item_id}) does not match Item ID from Item ({body_item_id})",
6865
)
6966

67+
68+
@attr.s
69+
class TransactionsClient(AsyncBaseTransactionsClient, ClientValidateMixIn):
70+
"""Transactions extension specific CRUD operations."""
71+
7072
async def create_item(
7173
self,
7274
collection_id: str,
@@ -203,11 +205,17 @@ async def delete_collection(
203205

204206

205207
@attr.s
206-
class BulkTransactionsClient(AsyncBaseBulkTransactionsClient):
208+
class BulkTransactionsClient(AsyncBaseBulkTransactionsClient, ClientValidateMixIn):
207209
"""Postgres bulk transactions."""
208210

209211
async def bulk_item_insert(self, items: Items, request: Request, **kwargs) -> str:
210212
"""Bulk item insertion using pgstac."""
213+
collection_id = request.path_params["collection_id"]
214+
215+
for item_id, item in items.items.items():
216+
self._validate_item(request, item, collection_id, item_id)
217+
item["collection"] = collection_id
218+
211219
items_to_insert = list(items.items.values())
212220

213221
async with request.app.state.get_connection(request, "w") as conn:

tests/clients/test_postgres.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,96 @@ async def test_create_bulk_items_already_exist_upsert(
407407
assert resp.text == '"Successfully upserted 2 items."'
408408

409409

410+
async def test_create_bulk_items_omit_collection(
411+
app_client, load_test_data: Callable, load_test_collection
412+
):
413+
coll = load_test_collection
414+
item = load_test_data("test_item.json")
415+
416+
items = {}
417+
for _ in range(2):
418+
_item = deepcopy(item)
419+
_item["id"] = str(uuid.uuid4())
420+
# remove collection ID here
421+
del _item["collection"]
422+
items[_item["id"]] = _item
423+
424+
payload = {"items": items, "method": "insert"}
425+
426+
resp = await app_client.post(
427+
f"/collections/{coll['id']}/bulk_items",
428+
json=payload,
429+
)
430+
assert resp.status_code == 200
431+
assert resp.text == '"Successfully added 2 items."'
432+
433+
for item_id in items.keys():
434+
resp = await app_client.get(f"/collections/{coll['id']}/items/{item_id}")
435+
assert resp.status_code == 200
436+
437+
# Try creating the same items again, but using upsert.
438+
# This should succeed.
439+
payload["method"] = "upsert"
440+
resp = await app_client.post(
441+
f"/collections/{coll['id']}/bulk_items",
442+
json=payload,
443+
)
444+
assert resp.status_code == 200
445+
assert resp.text == '"Successfully upserted 2 items."'
446+
447+
448+
async def test_create_bulk_items_collection_mismatch(
449+
app_client, load_test_data: Callable, load_test_collection
450+
):
451+
coll = load_test_collection
452+
item = load_test_data("test_item.json")
453+
454+
items = {}
455+
for _ in range(2):
456+
_item = deepcopy(item)
457+
_item["id"] = str(uuid.uuid4())
458+
_item["collection"] = "wrong-collection"
459+
items[_item["id"]] = _item
460+
461+
payload = {"items": items, "method": "insert"}
462+
463+
resp = await app_client.post(
464+
f"/collections/{coll['id']}/bulk_items",
465+
json=payload,
466+
)
467+
assert resp.status_code == 400
468+
assert (
469+
resp.json()["detail"]
470+
== "Collection ID from path parameter (test-collection) does not match Collection ID from Item (wrong-collection)"
471+
)
472+
473+
474+
async def test_create_bulk_items_id_mismatch(
475+
app_client, load_test_data: Callable, load_test_collection
476+
):
477+
coll = load_test_collection
478+
item = load_test_data("test_item.json")
479+
480+
items = {}
481+
for _ in range(2):
482+
_item = deepcopy(item)
483+
_item["id"] = str(uuid.uuid4())
484+
_item["collection"] = "wrong-collection"
485+
items[_item["id"] + "wrong"] = _item
486+
487+
payload = {"items": items, "method": "insert"}
488+
489+
resp = await app_client.post(
490+
f"/collections/{coll['id']}/bulk_items",
491+
json=payload,
492+
)
493+
assert resp.status_code == 400
494+
assert (
495+
resp.json()["detail"]
496+
== "Collection ID from path parameter (test-collection) does not match Collection ID from Item (wrong-collection)"
497+
)
498+
499+
410500
# TODO since right now puts implement upsert
411501
# test_create_collection_already_exists
412502
# test create_item_already_exists

0 commit comments

Comments
 (0)