stac-fastapi-core 6.8.0__tar.gz → 6.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/PKG-INFO +1 -1
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/core.py +1 -1
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/extensions/catalogs.py +60 -74
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/extensions/collections_search.py +25 -21
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/redis_utils.py +1 -1
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/utilities.py +84 -6
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/version.py +1 -1
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/.gitignore +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/README.md +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/pyproject.toml +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/pytest.ini +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/__init__.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/base_database_logic.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/base_settings.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/basic_auth.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/datetime_utils.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/extensions/__init__.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/extensions/aggregation.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/extensions/fields.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/extensions/filter.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/extensions/query.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/models/__init__.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/models/links.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/models/search.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/queryables.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/rate_limit.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/route_dependencies.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/serializers.py +0 -0
- {stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/session.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stac_fastapi_core
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.9.0
|
|
4
4
|
Summary: Core library for the Elasticsearch and Opensearch stac-fastapi backends.
|
|
5
5
|
Project-URL: Homepage, https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
|
|
6
6
|
License: MIT
|
|
@@ -315,7 +315,7 @@ class CoreClient(AsyncBaseCoreClient):
|
|
|
315
315
|
|
|
316
316
|
body_limit = None
|
|
317
317
|
try:
|
|
318
|
-
if request.method == "POST" and request.body():
|
|
318
|
+
if request.method == "POST" and await request.body():
|
|
319
319
|
body_data = await request.json()
|
|
320
320
|
body_limit = body_data.get("limit")
|
|
321
321
|
except Exception:
|
{stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/extensions/catalogs.py
RENAMED
|
@@ -56,6 +56,7 @@ class CatalogsExtension(ApiExtension):
|
|
|
56
56
|
settings: extension settings (unused for now).
|
|
57
57
|
"""
|
|
58
58
|
self.settings = settings or {}
|
|
59
|
+
self.router = APIRouter()
|
|
59
60
|
|
|
60
61
|
self.router.add_api_route(
|
|
61
62
|
path="/catalogs",
|
|
@@ -126,7 +127,7 @@ class CatalogsExtension(ApiExtension):
|
|
|
126
127
|
response_class=self.response_class,
|
|
127
128
|
status_code=204,
|
|
128
129
|
summary="Delete Catalog",
|
|
129
|
-
description="Delete a catalog.
|
|
130
|
+
description="Delete a catalog. All linked collections are unlinked and adopted by root if orphaned.",
|
|
130
131
|
tags=["Catalogs"],
|
|
131
132
|
)
|
|
132
133
|
|
|
@@ -337,22 +338,21 @@ class CatalogsExtension(ApiExtension):
|
|
|
337
338
|
status_code=404, detail=f"Catalog {catalog_id} not found"
|
|
338
339
|
)
|
|
339
340
|
|
|
340
|
-
async def delete_catalog(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
341
|
+
async def delete_catalog(self, catalog_id: str, request: Request) -> None:
|
|
342
|
+
"""Delete a catalog (The Container).
|
|
343
|
+
|
|
344
|
+
Deletes the Catalog document itself. All linked Collections are unlinked
|
|
345
|
+
and adopted by Root if they become orphans. Collection data is NEVER deleted.
|
|
346
|
+
|
|
347
|
+
Logic:
|
|
348
|
+
1. Finds all Collections linked to this Catalog.
|
|
349
|
+
2. Unlinks them (removes catalog_id from their parent_ids).
|
|
350
|
+
3. If a Collection becomes an orphan, it is adopted by Root.
|
|
351
|
+
4. PERMANENTLY DELETES the Catalog document itself.
|
|
350
352
|
|
|
351
353
|
Args:
|
|
352
354
|
catalog_id: The ID of the catalog to delete.
|
|
353
355
|
request: Request object.
|
|
354
|
-
cascade: If true, delete all collections linked to this catalog.
|
|
355
|
-
If false, only delete the catalog.
|
|
356
356
|
|
|
357
357
|
Returns:
|
|
358
358
|
None (204 No Content)
|
|
@@ -361,58 +361,42 @@ class CatalogsExtension(ApiExtension):
|
|
|
361
361
|
HTTPException: If the catalog is not found.
|
|
362
362
|
"""
|
|
363
363
|
try:
|
|
364
|
-
#
|
|
364
|
+
# Verify the catalog exists
|
|
365
365
|
await self.client.database.find_catalog(catalog_id)
|
|
366
366
|
|
|
367
|
-
#
|
|
368
|
-
|
|
369
|
-
# regardless of pagination or link truncation.
|
|
370
|
-
query_body = {"query": {"term": {"parent_ids": catalog_id}}}
|
|
367
|
+
# Find all collections with this catalog in parent_ids
|
|
368
|
+
query_body = {"query": {"term": {"parent_ids": catalog_id}}, "size": 10000}
|
|
371
369
|
search_result = await self.client.database.client.search(
|
|
372
|
-
index=COLLECTIONS_INDEX, body=query_body
|
|
370
|
+
index=COLLECTIONS_INDEX, body=query_body
|
|
373
371
|
)
|
|
374
372
|
children = [hit["_source"] for hit in search_result["hits"]["hits"]]
|
|
375
373
|
|
|
376
|
-
#
|
|
374
|
+
# Safe Unlink: Remove catalog from all children's parent_ids
|
|
375
|
+
# If a child becomes an orphan, adopt it to root
|
|
376
|
+
root_id = self.settings.get("STAC_FASTAPI_LANDING_PAGE_ID", "stac-fastapi")
|
|
377
|
+
|
|
377
378
|
for child in children:
|
|
378
379
|
child_id = child.get("id")
|
|
379
380
|
try:
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
# The collection survives and becomes a root-level collection if it has no other parents.
|
|
390
|
-
parent_ids = child.get("parent_ids", [])
|
|
391
|
-
if catalog_id in parent_ids:
|
|
392
|
-
parent_ids.remove(catalog_id)
|
|
393
|
-
child["parent_ids"] = parent_ids
|
|
394
|
-
|
|
395
|
-
# Update the collection in the database
|
|
396
|
-
# Note: Catalog links are now dynamically generated, so no need to remove them
|
|
397
|
-
await self.client.database.update_collection(
|
|
398
|
-
collection_id=child_id,
|
|
399
|
-
collection=child,
|
|
400
|
-
refresh=False,
|
|
381
|
+
parent_ids = child.get("parent_ids", [])
|
|
382
|
+
if catalog_id in parent_ids:
|
|
383
|
+
parent_ids.remove(catalog_id)
|
|
384
|
+
|
|
385
|
+
# If orphan, move to root
|
|
386
|
+
if len(parent_ids) == 0:
|
|
387
|
+
parent_ids.append(root_id)
|
|
388
|
+
logger.info(
|
|
389
|
+
f"Collection {child_id} adopted by root after catalog deletion."
|
|
401
390
|
)
|
|
402
|
-
|
|
403
|
-
# Log the result
|
|
404
|
-
if len(parent_ids) == 0:
|
|
405
|
-
logger.info(
|
|
406
|
-
f"Collection {child_id} is now a root-level orphan (no parent catalogs)"
|
|
407
|
-
)
|
|
408
|
-
else:
|
|
409
|
-
logger.info(
|
|
410
|
-
f"Removed catalog {catalog_id} from collection {child_id}; still belongs to {len(parent_ids)} other catalog(s)"
|
|
411
|
-
)
|
|
412
391
|
else:
|
|
413
|
-
logger.
|
|
414
|
-
f"
|
|
392
|
+
logger.info(
|
|
393
|
+
f"Removed catalog {catalog_id} from collection {child_id}; still belongs to {len(parent_ids)} other catalog(s)"
|
|
415
394
|
)
|
|
395
|
+
|
|
396
|
+
child["parent_ids"] = parent_ids
|
|
397
|
+
await self.client.database.update_collection(
|
|
398
|
+
collection_id=child_id, collection=child, refresh=False
|
|
399
|
+
)
|
|
416
400
|
except Exception as e:
|
|
417
401
|
error_msg = str(e)
|
|
418
402
|
if "not found" in error_msg.lower():
|
|
@@ -929,11 +913,11 @@ class CatalogsExtension(ApiExtension):
|
|
|
929
913
|
async def delete_catalog_collection(
|
|
930
914
|
self, catalog_id: str, collection_id: str, request: Request
|
|
931
915
|
) -> None:
|
|
932
|
-
"""Delete a collection from a catalog.
|
|
916
|
+
"""Delete a collection from a catalog (Unlink only).
|
|
933
917
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
collection
|
|
918
|
+
Removes the catalog from the collection's parent_ids.
|
|
919
|
+
If the collection becomes an orphan (no parents), it is adopted by the Root.
|
|
920
|
+
It NEVER deletes the collection data.
|
|
937
921
|
|
|
938
922
|
Args:
|
|
939
923
|
catalog_id: The ID of the catalog.
|
|
@@ -959,37 +943,39 @@ class CatalogsExtension(ApiExtension):
|
|
|
959
943
|
detail=f"Collection {collection_id} does not belong to catalog {catalog_id}",
|
|
960
944
|
)
|
|
961
945
|
|
|
962
|
-
#
|
|
963
|
-
|
|
964
|
-
parent_ids.remove(catalog_id)
|
|
965
|
-
collection_db["parent_ids"] = parent_ids
|
|
946
|
+
# SAFE UNLINK LOGIC
|
|
947
|
+
parent_ids.remove(catalog_id)
|
|
966
948
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
949
|
+
# Check if it is now an orphan (empty list)
|
|
950
|
+
if len(parent_ids) == 0:
|
|
951
|
+
# Fallback to Root / Landing Page
|
|
952
|
+
# You can hardcode 'root' or fetch the ID from settings
|
|
953
|
+
root_id = self.settings.get(
|
|
954
|
+
"STAC_FASTAPI_LANDING_PAGE_ID", "stac-fastapi"
|
|
971
955
|
)
|
|
972
|
-
|
|
956
|
+
parent_ids.append(root_id)
|
|
973
957
|
logger.info(
|
|
974
|
-
f"
|
|
958
|
+
f"Collection {collection_id} unlinked from {catalog_id}. Orphaned, so adopted by root ({root_id})."
|
|
975
959
|
)
|
|
976
960
|
else:
|
|
977
|
-
# If this is the only parent, delete the collection entirely
|
|
978
|
-
await self.client.database.delete_collection(
|
|
979
|
-
collection_id, refresh=True
|
|
980
|
-
)
|
|
981
961
|
logger.info(
|
|
982
|
-
f"
|
|
962
|
+
f"Removed catalog {catalog_id} from collection {collection_id}; still belongs to {len(parent_ids)} other catalog(s)"
|
|
983
963
|
)
|
|
984
964
|
|
|
965
|
+
# Update the collection in the database
|
|
966
|
+
collection_db["parent_ids"] = parent_ids
|
|
967
|
+
await self.client.database.update_collection(
|
|
968
|
+
collection_id=collection_id, collection=collection_db, refresh=True
|
|
969
|
+
)
|
|
970
|
+
|
|
985
971
|
except HTTPException:
|
|
986
972
|
raise
|
|
987
973
|
except Exception as e:
|
|
988
974
|
logger.error(
|
|
989
|
-
f"Error
|
|
975
|
+
f"Error removing collection {collection_id} from catalog {catalog_id}: {e}",
|
|
990
976
|
exc_info=True,
|
|
991
977
|
)
|
|
992
978
|
raise HTTPException(
|
|
993
979
|
status_code=500,
|
|
994
|
-
detail=f"Failed to
|
|
980
|
+
detail=f"Failed to remove collection from catalog: {str(e)}",
|
|
995
981
|
)
|
|
@@ -38,7 +38,7 @@ def build_get_collections_search_doc(original_endpoint):
|
|
|
38
38
|
query: Optional[str] = Query(
|
|
39
39
|
None,
|
|
40
40
|
description="Additional filtering expressed as a string (legacy support)",
|
|
41
|
-
|
|
41
|
+
examples=["platform=landsat AND collection_category=level2"],
|
|
42
42
|
),
|
|
43
43
|
limit: int = Query(
|
|
44
44
|
10,
|
|
@@ -83,14 +83,16 @@ def build_get_collections_search_doc(original_endpoint):
|
|
|
83
83
|
description=(
|
|
84
84
|
"Structured filter expression in CQL2 JSON or CQL2-text format"
|
|
85
85
|
),
|
|
86
|
-
|
|
86
|
+
examples=[
|
|
87
|
+
'{"op": "=", "args": [{"property": "properties.category"}, "level2"]}'
|
|
88
|
+
],
|
|
87
89
|
),
|
|
88
90
|
filter_lang: Optional[str] = Query(
|
|
89
91
|
None,
|
|
90
92
|
description=(
|
|
91
93
|
"Filter language. Must be 'cql2-json' or 'cql2-text' if specified"
|
|
92
94
|
),
|
|
93
|
-
|
|
95
|
+
examples=["cql2-json"],
|
|
94
96
|
),
|
|
95
97
|
):
|
|
96
98
|
# Delegate to original endpoint with parameters
|
|
@@ -160,24 +162,26 @@ def build_post_collections_search_doc(original_post_endpoint):
|
|
|
160
162
|
"- `sortby`: List of sort criteria objects with 'field' and 'direction' (asc/desc)\n"
|
|
161
163
|
"- `fields`: Object with 'include' and 'exclude' arrays for field selection"
|
|
162
164
|
),
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
"
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
"
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
165
|
+
examples=[
|
|
166
|
+
{
|
|
167
|
+
"q": "landsat",
|
|
168
|
+
"query": "platform=landsat AND collection_category=level2",
|
|
169
|
+
"filter": {
|
|
170
|
+
"op": "=",
|
|
171
|
+
"args": [{"property": "properties.category"}, "level2"],
|
|
172
|
+
},
|
|
173
|
+
"filter_lang": "cql2-json",
|
|
174
|
+
"limit": 10,
|
|
175
|
+
"token": "next-page-token",
|
|
176
|
+
"bbox": [-180, -90, 180, 90],
|
|
177
|
+
"datetime": "2020-01-01T00:00:00Z/2021-01-01T12:31:12Z",
|
|
178
|
+
"sortby": [{"field": "id", "direction": "asc"}],
|
|
179
|
+
"fields": {
|
|
180
|
+
"include": ["id", "title", "description"],
|
|
181
|
+
"exclude": ["properties"],
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
],
|
|
181
185
|
),
|
|
182
186
|
) -> Union[Collections, Response]:
|
|
183
187
|
return await original_post_endpoint(request, body)
|
|
@@ -6,6 +6,7 @@ such as converting bounding boxes to polygon representations.
|
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
|
+
import re
|
|
9
10
|
from typing import Any, Dict, List, Optional, Set, Union
|
|
10
11
|
|
|
11
12
|
from stac_fastapi.types.stac import Item
|
|
@@ -70,8 +71,6 @@ def bbox2polygon(b0: float, b1: float, b2: float, b3: float) -> List[List[List[f
|
|
|
70
71
|
return [[[b0, b1], [b2, b1], [b2, b3], [b0, b3], [b0, b1]]]
|
|
71
72
|
|
|
72
73
|
|
|
73
|
-
# copied from stac-fastapi-pgstac
|
|
74
|
-
# https://github.com/stac-utils/stac-fastapi-pgstac/blob/26f6d918eb933a90833f30e69e21ba3b4e8a7151/stac_fastapi/pgstac/utils.py#L10-L116
|
|
75
74
|
def filter_fields( # noqa: C901
|
|
76
75
|
item: Union[Item, Dict[str, Any]],
|
|
77
76
|
include: Optional[Set[str]] = None,
|
|
@@ -87,15 +86,60 @@ def filter_fields( # noqa: C901
|
|
|
87
86
|
if not include and not exclude:
|
|
88
87
|
return item
|
|
89
88
|
|
|
90
|
-
|
|
89
|
+
def match_pattern(pattern: str, key: str) -> bool:
|
|
90
|
+
"""Check if a key matches a wildcard pattern."""
|
|
91
|
+
regex_pattern = "^" + re.escape(pattern).replace(r"\*", ".*") + "$"
|
|
92
|
+
return bool(re.match(regex_pattern, key))
|
|
93
|
+
|
|
94
|
+
def get_matching_keys(source: Dict[str, Any], pattern: str) -> List[str]:
|
|
95
|
+
"""Get all keys that match the pattern."""
|
|
96
|
+
if not isinstance(source, dict):
|
|
97
|
+
return []
|
|
98
|
+
return [key for key in source.keys() if match_pattern(pattern, key)]
|
|
99
|
+
|
|
91
100
|
def include_fields(
|
|
92
101
|
source: Dict[str, Any], fields: Optional[Set[str]]
|
|
93
102
|
) -> Dict[str, Any]:
|
|
103
|
+
"""Include only the specified fields from the source dictionary."""
|
|
94
104
|
if not fields:
|
|
95
105
|
return source
|
|
96
106
|
|
|
107
|
+
def recursive_include(
|
|
108
|
+
source: Dict[str, Any], path_parts: List[str]
|
|
109
|
+
) -> Dict[str, Any]:
|
|
110
|
+
"""Recursively include fields matching the pattern path."""
|
|
111
|
+
if not path_parts:
|
|
112
|
+
return source
|
|
113
|
+
|
|
114
|
+
if not isinstance(source, dict):
|
|
115
|
+
return {}
|
|
116
|
+
|
|
117
|
+
current_pattern = path_parts[0]
|
|
118
|
+
remaining_parts = path_parts[1:]
|
|
119
|
+
|
|
120
|
+
matching_keys = get_matching_keys(source, current_pattern)
|
|
121
|
+
|
|
122
|
+
if not matching_keys:
|
|
123
|
+
return {}
|
|
124
|
+
|
|
125
|
+
result: Dict[str, Any] = {}
|
|
126
|
+
for key in matching_keys:
|
|
127
|
+
if remaining_parts:
|
|
128
|
+
if isinstance(source[key], dict):
|
|
129
|
+
value = recursive_include(source[key], remaining_parts)
|
|
130
|
+
if value:
|
|
131
|
+
result[key] = value
|
|
132
|
+
else:
|
|
133
|
+
result[key] = source[key]
|
|
134
|
+
|
|
135
|
+
return result
|
|
136
|
+
|
|
97
137
|
clean_item: Dict[str, Any] = {}
|
|
98
138
|
for key_path in fields or []:
|
|
139
|
+
if "*" in key_path:
|
|
140
|
+
value = recursive_include(source, key_path.split("."))
|
|
141
|
+
dict_deep_update(clean_item, value)
|
|
142
|
+
continue
|
|
99
143
|
key_path_parts = key_path.split(".")
|
|
100
144
|
key_root = key_path_parts[0]
|
|
101
145
|
if key_root in source:
|
|
@@ -125,12 +169,46 @@ def filter_fields( # noqa: C901
|
|
|
125
169
|
# The key, or root key of a multi-part key, is not present in the item,
|
|
126
170
|
# so it is ignored
|
|
127
171
|
pass
|
|
172
|
+
|
|
128
173
|
return clean_item
|
|
129
174
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
175
|
+
def exclude_fields(
|
|
176
|
+
source: Dict[str, Any],
|
|
177
|
+
fields: Optional[Set[str]],
|
|
178
|
+
) -> None:
|
|
179
|
+
"""Exclude fields from source."""
|
|
180
|
+
|
|
181
|
+
def recursive_exclude(
|
|
182
|
+
source: Dict[str, Any], path_parts: List[str], current_path: str = ""
|
|
183
|
+
) -> None:
|
|
184
|
+
"""Recursively exclude fields matching the pattern path."""
|
|
185
|
+
if not path_parts or not isinstance(source, dict):
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
current_pattern = path_parts[0]
|
|
189
|
+
remaining_parts = path_parts[1:]
|
|
190
|
+
|
|
191
|
+
matching_keys = get_matching_keys(source, current_pattern)
|
|
192
|
+
|
|
193
|
+
for key in list(matching_keys):
|
|
194
|
+
if key not in source:
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
# Build the full path for this key
|
|
198
|
+
full_path = f"{current_path}.{key}" if current_path else key
|
|
199
|
+
|
|
200
|
+
if remaining_parts:
|
|
201
|
+
if isinstance(source[key], dict):
|
|
202
|
+
recursive_exclude(source[key], remaining_parts, full_path)
|
|
203
|
+
if not source[key]:
|
|
204
|
+
del source[key]
|
|
205
|
+
else:
|
|
206
|
+
source.pop(key, None)
|
|
207
|
+
|
|
133
208
|
for key_path in fields or []:
|
|
209
|
+
if "*" in key_path:
|
|
210
|
+
recursive_exclude(source, key_path.split("."))
|
|
211
|
+
continue
|
|
134
212
|
key_path_part = key_path.split(".")
|
|
135
213
|
key_root = key_path_part[0]
|
|
136
214
|
if key_root in source:
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""library version."""
|
|
2
|
-
__version__ = "6.
|
|
2
|
+
__version__ = "6.9.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/base_database_logic.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/extensions/__init__.py
RENAMED
|
File without changes
|
{stac_fastapi_core-6.8.0 → stac_fastapi_core-6.9.0}/stac_fastapi/core/extensions/aggregation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|