stac-fastapi-core 4.2.0__py3-none-any.whl → 5.0.0a0__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.
@@ -1,4 +1,4 @@
1
- """Filter extension logic for es conversion."""
1
+ """Filter extension logic for conversion."""
2
2
 
3
3
  # """
4
4
  # Implements Filter Extension.
@@ -13,47 +13,55 @@
13
13
  # defines spatial operators (S_INTERSECTS, S_CONTAINS, S_WITHIN, S_DISJOINT).
14
14
  # """
15
15
 
16
- import re
17
16
  from enum import Enum
18
17
  from typing import Any, Dict
19
18
 
20
- _cql2_like_patterns = re.compile(r"\\.|[%_]|\\$")
21
- _valid_like_substitutions = {
22
- "\\\\": "\\",
23
- "\\%": "%",
24
- "\\_": "_",
25
- "%": "*",
26
- "_": "?",
19
+ DEFAULT_QUERYABLES: Dict[str, Dict[str, Any]] = {
20
+ "id": {
21
+ "description": "ID",
22
+ "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/definitions/core/allOf/2/properties/id",
23
+ },
24
+ "collection": {
25
+ "description": "Collection",
26
+ "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/definitions/core/allOf/2/then/properties/collection",
27
+ },
28
+ "geometry": {
29
+ "description": "Geometry",
30
+ "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/definitions/core/allOf/1/oneOf/0/properties/geometry",
31
+ },
32
+ "datetime": {
33
+ "description": "Acquisition Timestamp",
34
+ "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/datetime",
35
+ },
36
+ "created": {
37
+ "description": "Creation Timestamp",
38
+ "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/created",
39
+ },
40
+ "updated": {
41
+ "description": "Creation Timestamp",
42
+ "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/updated",
43
+ },
44
+ "cloud_cover": {
45
+ "description": "Cloud Cover",
46
+ "$ref": "https://stac-extensions.github.io/eo/v1.0.0/schema.json#/definitions/fields/properties/eo:cloud_cover",
47
+ },
48
+ "cloud_shadow_percentage": {
49
+ "title": "Cloud Shadow Percentage",
50
+ "description": "Cloud Shadow Percentage",
51
+ "type": "number",
52
+ "minimum": 0,
53
+ "maximum": 100,
54
+ },
55
+ "nodata_pixel_percentage": {
56
+ "title": "No Data Pixel Percentage",
57
+ "description": "No Data Pixel Percentage",
58
+ "type": "number",
59
+ "minimum": 0,
60
+ "maximum": 100,
61
+ },
27
62
  }
28
63
 
29
64
 
30
- def _replace_like_patterns(match: re.Match) -> str:
31
- pattern = match.group()
32
- try:
33
- return _valid_like_substitutions[pattern]
34
- except KeyError:
35
- raise ValueError(f"'{pattern}' is not a valid escape sequence")
36
-
37
-
38
- def cql2_like_to_es(string: str) -> str:
39
- """
40
- Convert CQL2 "LIKE" characters to Elasticsearch "wildcard" characters.
41
-
42
- Args:
43
- string (str): The string containing CQL2 wildcard characters.
44
-
45
- Returns:
46
- str: The converted string with Elasticsearch compatible wildcards.
47
-
48
- Raises:
49
- ValueError: If an invalid escape sequence is encountered.
50
- """
51
- return _cql2_like_patterns.sub(
52
- repl=_replace_like_patterns,
53
- string=string,
54
- )
55
-
56
-
57
65
  class LogicalOp(str, Enum):
58
66
  """Enumeration for logical operators used in constructing Elasticsearch queries."""
59
67
 
@@ -89,124 +97,3 @@ class SpatialOp(str, Enum):
89
97
  S_CONTAINS = "s_contains"
90
98
  S_WITHIN = "s_within"
91
99
  S_DISJOINT = "s_disjoint"
92
-
93
-
94
- def to_es_field(queryables_mapping: Dict[str, Any], field: str) -> str:
95
- """
96
- Map a given field to its corresponding Elasticsearch field according to a predefined mapping.
97
-
98
- Args:
99
- field (str): The field name from a user query or filter.
100
-
101
- Returns:
102
- str: The mapped field name suitable for Elasticsearch queries.
103
- """
104
- return queryables_mapping.get(field, field)
105
-
106
-
107
- def to_es(queryables_mapping: Dict[str, Any], query: Dict[str, Any]) -> Dict[str, Any]:
108
- """
109
- Transform a simplified CQL2 query structure to an Elasticsearch compatible query DSL.
110
-
111
- Args:
112
- query (Dict[str, Any]): The query dictionary containing 'op' and 'args'.
113
-
114
- Returns:
115
- Dict[str, Any]: The corresponding Elasticsearch query in the form of a dictionary.
116
- """
117
- if query["op"] in [LogicalOp.AND, LogicalOp.OR, LogicalOp.NOT]:
118
- bool_type = {
119
- LogicalOp.AND: "must",
120
- LogicalOp.OR: "should",
121
- LogicalOp.NOT: "must_not",
122
- }[query["op"]]
123
- return {
124
- "bool": {
125
- bool_type: [
126
- to_es(queryables_mapping, sub_query) for sub_query in query["args"]
127
- ]
128
- }
129
- }
130
-
131
- elif query["op"] in [
132
- ComparisonOp.EQ,
133
- ComparisonOp.NEQ,
134
- ComparisonOp.LT,
135
- ComparisonOp.LTE,
136
- ComparisonOp.GT,
137
- ComparisonOp.GTE,
138
- ]:
139
- range_op = {
140
- ComparisonOp.LT: "lt",
141
- ComparisonOp.LTE: "lte",
142
- ComparisonOp.GT: "gt",
143
- ComparisonOp.GTE: "gte",
144
- }
145
-
146
- field = to_es_field(queryables_mapping, query["args"][0]["property"])
147
- value = query["args"][1]
148
- if isinstance(value, dict) and "timestamp" in value:
149
- value = value["timestamp"]
150
- if query["op"] == ComparisonOp.EQ:
151
- return {"range": {field: {"gte": value, "lte": value}}}
152
- elif query["op"] == ComparisonOp.NEQ:
153
- return {
154
- "bool": {
155
- "must_not": [{"range": {field: {"gte": value, "lte": value}}}]
156
- }
157
- }
158
- else:
159
- return {"range": {field: {range_op[query["op"]]: value}}}
160
- else:
161
- if query["op"] == ComparisonOp.EQ:
162
- return {"term": {field: value}}
163
- elif query["op"] == ComparisonOp.NEQ:
164
- return {"bool": {"must_not": [{"term": {field: value}}]}}
165
- else:
166
- return {"range": {field: {range_op[query["op"]]: value}}}
167
-
168
- elif query["op"] == ComparisonOp.IS_NULL:
169
- field = to_es_field(queryables_mapping, query["args"][0]["property"])
170
- return {"bool": {"must_not": {"exists": {"field": field}}}}
171
-
172
- elif query["op"] == AdvancedComparisonOp.BETWEEN:
173
- field = to_es_field(queryables_mapping, query["args"][0]["property"])
174
- gte, lte = query["args"][1], query["args"][2]
175
- if isinstance(gte, dict) and "timestamp" in gte:
176
- gte = gte["timestamp"]
177
- if isinstance(lte, dict) and "timestamp" in lte:
178
- lte = lte["timestamp"]
179
- return {"range": {field: {"gte": gte, "lte": lte}}}
180
-
181
- elif query["op"] == AdvancedComparisonOp.IN:
182
- field = to_es_field(queryables_mapping, query["args"][0]["property"])
183
- values = query["args"][1]
184
- if not isinstance(values, list):
185
- raise ValueError(f"Arg {values} is not a list")
186
- return {"terms": {field: values}}
187
-
188
- elif query["op"] == AdvancedComparisonOp.LIKE:
189
- field = to_es_field(queryables_mapping, query["args"][0]["property"])
190
- pattern = cql2_like_to_es(query["args"][1])
191
- return {"wildcard": {field: {"value": pattern, "case_insensitive": True}}}
192
-
193
- elif query["op"] in [
194
- SpatialOp.S_INTERSECTS,
195
- SpatialOp.S_CONTAINS,
196
- SpatialOp.S_WITHIN,
197
- SpatialOp.S_DISJOINT,
198
- ]:
199
- field = to_es_field(queryables_mapping, query["args"][0]["property"])
200
- geometry = query["args"][1]
201
-
202
- relation_mapping = {
203
- SpatialOp.S_INTERSECTS: "intersects",
204
- SpatialOp.S_CONTAINS: "contains",
205
- SpatialOp.S_WITHIN: "within",
206
- SpatialOp.S_DISJOINT: "disjoint",
207
- }
208
-
209
- relation = relation_mapping[query["op"]]
210
- return {"geo_shape": {field: {"shape": geometry, "relation": relation}}}
211
-
212
- return {}
@@ -12,46 +12,6 @@ from stac_fastapi.types.stac import Item
12
12
  MAX_LIMIT = 10000
13
13
 
14
14
 
15
- def validate_refresh(value: Union[str, bool]) -> str:
16
- """
17
- Validate the `refresh` parameter value.
18
-
19
- Args:
20
- value (Union[str, bool]): The `refresh` parameter value, which can be a string or a boolean.
21
-
22
- Returns:
23
- str: The validated value of the `refresh` parameter, which can be "true", "false", or "wait_for".
24
- """
25
- logger = logging.getLogger(__name__)
26
-
27
- # Handle boolean-like values using get_bool_env
28
- if isinstance(value, bool) or value in {
29
- "true",
30
- "false",
31
- "1",
32
- "0",
33
- "yes",
34
- "no",
35
- "y",
36
- "n",
37
- }:
38
- is_true = get_bool_env("DATABASE_REFRESH", default=value)
39
- return "true" if is_true else "false"
40
-
41
- # Normalize to lowercase for case-insensitivity
42
- value = value.lower()
43
-
44
- # Handle "wait_for" explicitly
45
- if value == "wait_for":
46
- return "wait_for"
47
-
48
- # Log a warning for invalid values and default to "false"
49
- logger.warning(
50
- f"Invalid value for `refresh`: '{value}'. Expected 'true', 'false', or 'wait_for'. Defaulting to 'false'."
51
- )
52
- return "false"
53
-
54
-
55
15
  def get_bool_env(name: str, default: Union[bool, str] = False) -> bool:
56
16
  """
57
17
  Retrieve a boolean value from an environment variable.
@@ -1,2 +1,2 @@
1
1
  """library version."""
2
- __version__ = "4.2.0"
2
+ __version__ = "5.0.0a0"