stac-fastapi-core 6.8.1__py3-none-any.whl → 6.9.0__py3-none-any.whl
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/core.py +1 -1
- stac_fastapi/core/extensions/catalogs.py +3 -2
- stac_fastapi/core/extensions/collections_search.py +25 -21
- stac_fastapi/core/redis_utils.py +1 -1
- stac_fastapi/core/utilities.py +84 -6
- stac_fastapi/core/version.py +1 -1
- {stac_fastapi_core-6.8.1.dist-info → stac_fastapi_core-6.9.0.dist-info}/METADATA +1 -1
- {stac_fastapi_core-6.8.1.dist-info → stac_fastapi_core-6.9.0.dist-info}/RECORD +9 -9
- {stac_fastapi_core-6.8.1.dist-info → stac_fastapi_core-6.9.0.dist-info}/WHEEL +0 -0
stac_fastapi/core/core.py
CHANGED
|
@@ -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:
|
|
@@ -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",
|
|
@@ -364,9 +365,9 @@ class CatalogsExtension(ApiExtension):
|
|
|
364
365
|
await self.client.database.find_catalog(catalog_id)
|
|
365
366
|
|
|
366
367
|
# Find all collections with this catalog in parent_ids
|
|
367
|
-
query_body = {"query": {"term": {"parent_ids": catalog_id}}}
|
|
368
|
+
query_body = {"query": {"term": {"parent_ids": catalog_id}}, "size": 10000}
|
|
368
369
|
search_result = await self.client.database.client.search(
|
|
369
|
-
index=COLLECTIONS_INDEX, body=query_body
|
|
370
|
+
index=COLLECTIONS_INDEX, body=query_body
|
|
370
371
|
)
|
|
371
372
|
children = [hit["_source"] for hit in search_result["hits"]["hits"]]
|
|
372
373
|
|
|
@@ -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)
|
stac_fastapi/core/redis_utils.py
CHANGED
stac_fastapi/core/utilities.py
CHANGED
|
@@ -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:
|
stac_fastapi/core/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""library version."""
|
|
2
|
-
__version__ = "6.
|
|
2
|
+
__version__ = "6.9.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
|
|
@@ -2,26 +2,26 @@ stac_fastapi/core/__init__.py,sha256=8izV3IWRGdXmDOK1hIPQAanbWs9EI04PJCGgqG1ZGIs
|
|
|
2
2
|
stac_fastapi/core/base_database_logic.py,sha256=JL7DRcDdqeaLbSPPGcIUMs7q6I3Gm_E5XCOwFG458Io,6053
|
|
3
3
|
stac_fastapi/core/base_settings.py,sha256=R3_Sx7n5XpGMs3zAwFJD7y008WvGU_uI2xkaabm82Kg,239
|
|
4
4
|
stac_fastapi/core/basic_auth.py,sha256=RhFv3RVSHF6OaqnaaU2DO4ncJ_S5nB1q8UNpnVJJsrk,2155
|
|
5
|
-
stac_fastapi/core/core.py,sha256=
|
|
5
|
+
stac_fastapi/core/core.py,sha256=LTUF1253Bq-E9G9oaK3cPgou7Mk4iemHkS7hDVgwSow,52244
|
|
6
6
|
stac_fastapi/core/datetime_utils.py,sha256=QygF2mJFfI_zqCwmSIec3HYqrsVsn3nUcaRQx3CD7Zw,4683
|
|
7
7
|
stac_fastapi/core/queryables.py,sha256=0gKdxlmCVaIj3ODpmyIfzLChEB1nNKXPZhA3K9ApfL0,3755
|
|
8
8
|
stac_fastapi/core/rate_limit.py,sha256=Gu8dAaJReGsj1L91U6m2tflU6RahpXDRs2-AYSKoybA,1318
|
|
9
|
-
stac_fastapi/core/redis_utils.py,sha256=
|
|
9
|
+
stac_fastapi/core/redis_utils.py,sha256=xqZfXwrZ0Wei6EDereOygW_Aq6DBDykhQYD86Ws9P28,9706
|
|
10
10
|
stac_fastapi/core/route_dependencies.py,sha256=hdtuMkv-zY1vg0YxiCz1aKP0SbBcORqDGEKDGgEazW8,5482
|
|
11
11
|
stac_fastapi/core/serializers.py,sha256=l5EWZvlGjlfsZ3S4wHjWKD8sJBf83zd2dkursu18fV4,13345
|
|
12
12
|
stac_fastapi/core/session.py,sha256=aXqu4LXfVbAAsChMVXd9gAhczA2bZPne6HqPeklAwMY,474
|
|
13
|
-
stac_fastapi/core/utilities.py,sha256=
|
|
14
|
-
stac_fastapi/core/version.py,sha256=
|
|
13
|
+
stac_fastapi/core/utilities.py,sha256=B-tLc_H_v92q8ZNpzk-9nKQMKe-bVHUk64HpybGqYX0,10398
|
|
14
|
+
stac_fastapi/core/version.py,sha256=Zec8murh7xqMR7l6fB74XJcq2QdyNqRgOdhLMwBYPAI,45
|
|
15
15
|
stac_fastapi/core/extensions/__init__.py,sha256=oaK-UJDQSEISdQ8VtM0ESxpsv7Hx1HbAdmMnh6MTFD4,356
|
|
16
16
|
stac_fastapi/core/extensions/aggregation.py,sha256=v1hUHqlYuMqfQ554g3cTp16pUyRYucQxPERbHPAFtf8,1878
|
|
17
|
-
stac_fastapi/core/extensions/catalogs.py,sha256=
|
|
18
|
-
stac_fastapi/core/extensions/collections_search.py,sha256=
|
|
17
|
+
stac_fastapi/core/extensions/catalogs.py,sha256=iOMmPdY0vhGx9QkIMEaLbEaCLvGY2iyg6U07Gdpjejc,38000
|
|
18
|
+
stac_fastapi/core/extensions/collections_search.py,sha256=ZxCRQ5jyhrg6zDs037qLDzCz0T3tq3a9_lXfYQ9GweQ,14546
|
|
19
19
|
stac_fastapi/core/extensions/fields.py,sha256=NCT5XHvfaf297eDPNaIFsIzvJnbbUTpScqF0otdx0NA,1066
|
|
20
20
|
stac_fastapi/core/extensions/filter.py,sha256=-NQGME7rR_ereuDx-LAa1M5JhEXFaKiTtkH2asraYHE,2998
|
|
21
21
|
stac_fastapi/core/extensions/query.py,sha256=Xmo8pfZEZKPudZEjjozv3R0wLOP0ayjC9E67sBOXqWY,1803
|
|
22
22
|
stac_fastapi/core/models/__init__.py,sha256=sUsEB7umGZVYXjT4EHqLwm8p2wevtRBdig2Ioj2ZdVQ,631
|
|
23
23
|
stac_fastapi/core/models/links.py,sha256=5KEZKisFN34U4UuOzSQnDy0QdsUOT2VRuuY36vs-FGw,7074
|
|
24
24
|
stac_fastapi/core/models/search.py,sha256=7SgAUyzHGXBXSqB4G6cwq9FMwoAS00momb7jvBkjyow,27
|
|
25
|
-
stac_fastapi_core-6.
|
|
26
|
-
stac_fastapi_core-6.
|
|
27
|
-
stac_fastapi_core-6.
|
|
25
|
+
stac_fastapi_core-6.9.0.dist-info/METADATA,sha256=4ot6ylGlLz67tpQZ5VPxAbw2kOC1O_VCAzGXZ2dQ39U,3480
|
|
26
|
+
stac_fastapi_core-6.9.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
27
|
+
stac_fastapi_core-6.9.0.dist-info/RECORD,,
|
|
File without changes
|