Skip to content

Commit 481f61e

Browse files
authored
Merge pull request #177 from rhysrevans3/collection_update_endpoint
Collection update endpoint
2 parents 0cad96c + 9ed56e5 commit 481f61e

File tree

7 files changed

+160
-15
lines changed

7 files changed

+160
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1414
### Changed
1515

1616
- Elasticsearch drivers from 7.17.9 to 8.11.0 [#169](https://github.com/stac-utils/stac-fastapi-elasticsearch/pull/169)
17+
- Collection update endpoint no longer delete all sub items [#177](https://github.com/stac-utils/stac-fastapi-elasticsearch/pull/177)
1718

1819
### Fixed
1920

docker-compose.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ services:
1414
- RELOAD=true
1515
- ENVIRONMENT=local
1616
- WEB_CONCURRENCY=10
17-
- ES_HOST=172.17.0.1
17+
- ES_HOST=elasticsearch
1818
- ES_PORT=9200
1919
- ES_USE_SSL=false
2020
- ES_VERIFY_CERTS=false
@@ -32,6 +32,7 @@ services:
3232
elasticsearch:
3333
container_name: es-container
3434
image: docker.elastic.co/elasticsearch/elasticsearch:${ELASTICSEARCH_VERSION:-8.11.0}
35+
hostname: elasticsearch
3536
environment:
3637
ES_JAVA_OPTS: -Xms512m -Xmx1g
3738
volumes:

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ def _es_config() -> Dict[str, Any]:
3939
if (u := os.getenv("ES_USER")) and (p := os.getenv("ES_PASS")):
4040
config["http_auth"] = (u, p)
4141

42+
if api_key := os.getenv("ES_API_KEY"):
43+
if isinstance(config["headers"], dict):
44+
headers = {**config["headers"], "x-api-key": api_key}
45+
46+
else:
47+
config["headers"] = {"x-api-key": api_key}
48+
49+
config["headers"] = headers
50+
4251
return config
4352

4453

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/core.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -457,9 +457,9 @@ async def post_search(
457457
)
458458

459459
if search_request.query:
460-
for (field_name, expr) in search_request.query.items():
460+
for field_name, expr in search_request.query.items():
461461
field = "properties__" + field_name
462-
for (op, value) in expr.items():
462+
for op, value in expr.items():
463463
search = self.database.apply_stacql_filter(
464464
search=search, op=op, field=field, value=value
465465
)
@@ -660,8 +660,11 @@ async def update_collection(
660660
Update a collection.
661661
662662
This method updates an existing collection in the database by first finding
663-
the collection by its id, then deleting the old version, and finally creating
664-
a new version of the updated collection. The updated collection is then returned.
663+
the collection by the id given in the keyword argument `collection_id`.
664+
If no `collection_id` is given the id of the given collection object is used.
665+
If the object and keyword collection ids don't match the sub items
666+
collection id is updated else the items are left unchanged.
667+
The updated collection is then returned.
665668
666669
Args:
667670
collection: A STAC collection that needs to be updated.
@@ -673,9 +676,18 @@ async def update_collection(
673676
"""
674677
base_url = str(kwargs["request"].base_url)
675678

676-
await self.database.find_collection(collection_id=collection["id"])
677-
await self.delete_collection(collection["id"])
678-
await self.create_collection(collection, **kwargs)
679+
collection_id = kwargs["request"].query_params.get(
680+
"collection_id", collection["id"]
681+
)
682+
683+
collection_links = CollectionLinks(
684+
collection_id=collection["id"], base_url=base_url
685+
).create_links()
686+
collection["links"] = collection_links
687+
688+
await self.database.update_collection(
689+
collection_id=collection_id, collection=collection
690+
)
679691

680692
return CollectionSerializer.db_to_stac(collection, base_url)
681693

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/database_logic.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,53 @@ async def find_collection(self, collection_id: str) -> Collection:
763763

764764
return collection["_source"]
765765

766+
async def update_collection(
767+
self, collection_id: str, collection: Collection, refresh: bool = False
768+
):
769+
"""Update a collection from the database.
770+
771+
Args:
772+
self: The instance of the object calling this function.
773+
collection_id (str): The ID of the collection to be updated.
774+
collection (Collection): The Collection object to be used for the update.
775+
776+
Raises:
777+
NotFoundError: If the collection with the given `collection_id` is not
778+
found in the database.
779+
780+
Notes:
781+
This function updates the collection in the database using the specified
782+
`collection_id` and with the collection specified in the `Collection` object.
783+
If the collection is not found, a `NotFoundError` is raised.
784+
"""
785+
await self.find_collection(collection_id=collection_id)
786+
787+
if collection_id != collection["id"]:
788+
await self.create_collection(collection, refresh=refresh)
789+
790+
await self.client.reindex(
791+
body={
792+
"dest": {"index": f"{ITEMS_INDEX_PREFIX}{collection['id']}"},
793+
"source": {"index": f"{ITEMS_INDEX_PREFIX}{collection_id}"},
794+
"script": {
795+
"lang": "painless",
796+
"source": f"""ctx._id = ctx._id.replace('{collection_id}', '{collection["id"]}'); ctx._source.collection = '{collection["id"]}' ;""",
797+
},
798+
},
799+
wait_for_completion=True,
800+
refresh=refresh,
801+
)
802+
803+
await self.delete_collection(collection_id)
804+
805+
else:
806+
await self.client.index(
807+
index=COLLECTIONS_INDEX,
808+
id=collection_id,
809+
document=collection,
810+
refresh=refresh,
811+
)
812+
766813
async def delete_collection(self, collection_id: str, refresh: bool = False):
767814
"""Delete a collection from the database.
768815

stac_fastapi/elasticsearch/tests/clients/test_elasticsearch.py

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,90 @@ async def test_update_collection(
4040
txn_client,
4141
load_test_data: Callable,
4242
):
43-
data = load_test_data("test_collection.json")
43+
collection_data = load_test_data("test_collection.json")
44+
item_data = load_test_data("test_item.json")
4445

45-
await txn_client.create_collection(data, request=MockRequest)
46-
data["keywords"].append("new keyword")
47-
await txn_client.update_collection(data, request=MockRequest)
46+
await txn_client.create_collection(collection_data, request=MockRequest)
47+
await txn_client.create_item(
48+
collection_id=collection_data["id"],
49+
item=item_data,
50+
request=MockRequest,
51+
refresh=True,
52+
)
4853

49-
coll = await core_client.get_collection(data["id"], request=MockRequest)
54+
collection_data["keywords"].append("new keyword")
55+
await txn_client.update_collection(collection_data, request=MockRequest)
56+
57+
coll = await core_client.get_collection(collection_data["id"], request=MockRequest)
5058
assert "new keyword" in coll["keywords"]
5159

52-
await txn_client.delete_collection(data["id"])
60+
item = await core_client.get_item(
61+
item_id=item_data["id"],
62+
collection_id=collection_data["id"],
63+
request=MockRequest,
64+
)
65+
assert item["id"] == item_data["id"]
66+
assert item["collection"] == item_data["collection"]
67+
68+
await txn_client.delete_collection(collection_data["id"])
69+
70+
71+
@pytest.mark.asyncio
72+
async def test_update_collection_id(
73+
core_client,
74+
txn_client,
75+
load_test_data: Callable,
76+
):
77+
collection_data = load_test_data("test_collection.json")
78+
item_data = load_test_data("test_item.json")
79+
new_collection_id = "new-test-collection"
80+
81+
await txn_client.create_collection(collection_data, request=MockRequest)
82+
await txn_client.create_item(
83+
collection_id=collection_data["id"],
84+
item=item_data,
85+
request=MockRequest,
86+
refresh=True,
87+
)
88+
89+
old_collection_id = collection_data["id"]
90+
collection_data["id"] = new_collection_id
91+
92+
await txn_client.update_collection(
93+
collection=collection_data,
94+
request=MockRequest(
95+
query_params={
96+
"collection_id": old_collection_id,
97+
"limit": "10",
98+
}
99+
),
100+
refresh=True,
101+
)
102+
103+
with pytest.raises(NotFoundError):
104+
await core_client.get_collection(old_collection_id, request=MockRequest)
105+
106+
coll = await core_client.get_collection(collection_data["id"], request=MockRequest)
107+
assert coll["id"] == new_collection_id
108+
109+
with pytest.raises(NotFoundError):
110+
await core_client.get_item(
111+
item_id=item_data["id"],
112+
collection_id=old_collection_id,
113+
request=MockRequest,
114+
)
115+
116+
item = await core_client.get_item(
117+
item_id=item_data["id"],
118+
collection_id=collection_data["id"],
119+
request=MockRequest,
120+
refresh=True,
121+
)
122+
123+
assert item["id"] == item_data["id"]
124+
assert item["collection"] == new_collection_id
125+
126+
await txn_client.delete_collection(collection_data["id"])
53127

54128

55129
@pytest.mark.asyncio

stac_fastapi/elasticsearch/tests/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def __init__(self, item, collection):
3939

4040
class MockRequest:
4141
base_url = "http://test-server"
42+
query_params = {}
4243

4344
def __init__(
4445
self,
@@ -50,7 +51,7 @@ def __init__(
5051
self.method = method
5152
self.url = url
5253
self.app = app
53-
self.query_params = query_params or {}
54+
self.query_params = query_params
5455

5556

5657
class TestSettings(AsyncElasticsearchSettings):

0 commit comments

Comments
 (0)