sfeos-helpers 5.0.0a0__tar.gz → 6.0.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.
Files changed (30) hide show
  1. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/PKG-INFO +1 -1
  2. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/setup.py +1 -1
  3. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/sfeos_helpers.egg-info/PKG-INFO +1 -1
  4. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/sfeos_helpers.egg-info/SOURCES.txt +2 -1
  5. sfeos_helpers-6.0.0/sfeos_helpers.egg-info/requires.txt +1 -0
  6. sfeos_helpers-6.0.0/stac_fastapi/sfeos_helpers/database/utils.py +248 -0
  7. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/stac_fastapi/sfeos_helpers/filter/client.py +23 -14
  8. sfeos_helpers-6.0.0/stac_fastapi/sfeos_helpers/models/patch.py +166 -0
  9. sfeos_helpers-6.0.0/stac_fastapi/sfeos_helpers/version.py +2 -0
  10. sfeos_helpers-5.0.0a0/sfeos_helpers.egg-info/requires.txt +0 -1
  11. sfeos_helpers-5.0.0a0/stac_fastapi/sfeos_helpers/database/utils.py +0 -50
  12. sfeos_helpers-5.0.0a0/stac_fastapi/sfeos_helpers/version.py +0 -2
  13. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/README.md +0 -0
  14. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/setup.cfg +0 -0
  15. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/sfeos_helpers.egg-info/dependency_links.txt +0 -0
  16. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/sfeos_helpers.egg-info/not-zip-safe +0 -0
  17. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/sfeos_helpers.egg-info/top_level.txt +0 -0
  18. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/stac_fastapi/sfeos_helpers/aggregation/__init__.py +0 -0
  19. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/stac_fastapi/sfeos_helpers/aggregation/client.py +0 -0
  20. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/stac_fastapi/sfeos_helpers/aggregation/format.py +0 -0
  21. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/stac_fastapi/sfeos_helpers/database/__init__.py +0 -0
  22. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/stac_fastapi/sfeos_helpers/database/datetime.py +0 -0
  23. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/stac_fastapi/sfeos_helpers/database/document.py +0 -0
  24. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/stac_fastapi/sfeos_helpers/database/index.py +0 -0
  25. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/stac_fastapi/sfeos_helpers/database/mapping.py +0 -0
  26. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/stac_fastapi/sfeos_helpers/database/query.py +0 -0
  27. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/stac_fastapi/sfeos_helpers/filter/__init__.py +0 -0
  28. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/stac_fastapi/sfeos_helpers/filter/cql2.py +0 -0
  29. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/stac_fastapi/sfeos_helpers/filter/transform.py +0 -0
  30. {sfeos_helpers-5.0.0a0 → sfeos_helpers-6.0.0}/stac_fastapi/sfeos_helpers/mappings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sfeos_helpers
3
- Version: 5.0.0a0
3
+ Version: 6.0.0
4
4
  Summary: Helper library for the Elasticsearch and Opensearch stac-fastapi backends.
5
5
  Home-page: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
6
6
  License: MIT
@@ -6,7 +6,7 @@ with open("README.md") as f:
6
6
  desc = f.read()
7
7
 
8
8
  install_requires = [
9
- "stac-fastapi.core==5.0.0a0",
9
+ "stac-fastapi.core==6.0.0",
10
10
  ]
11
11
 
12
12
  setup(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sfeos-helpers
3
- Version: 5.0.0a0
3
+ Version: 6.0.0
4
4
  Summary: Helper library for the Elasticsearch and Opensearch stac-fastapi backends.
5
5
  Home-page: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
6
6
  License: MIT
@@ -22,4 +22,5 @@ stac_fastapi/sfeos_helpers/database/utils.py
22
22
  stac_fastapi/sfeos_helpers/filter/__init__.py
23
23
  stac_fastapi/sfeos_helpers/filter/client.py
24
24
  stac_fastapi/sfeos_helpers/filter/cql2.py
25
- stac_fastapi/sfeos_helpers/filter/transform.py
25
+ stac_fastapi/sfeos_helpers/filter/transform.py
26
+ stac_fastapi/sfeos_helpers/models/patch.py
@@ -0,0 +1 @@
1
+ stac-fastapi.core==6.0.0
@@ -0,0 +1,248 @@
1
+ """Utility functions for database operations in Elasticsearch/OpenSearch.
2
+
3
+ This module provides utility functions for working with database operations
4
+ in Elasticsearch/OpenSearch, such as parameter validation.
5
+ """
6
+
7
+ import logging
8
+ from typing import Dict, List, Union
9
+
10
+ from stac_fastapi.core.utilities import get_bool_env
11
+ from stac_fastapi.extensions.core.transaction.request import (
12
+ PatchAddReplaceTest,
13
+ PatchOperation,
14
+ PatchRemove,
15
+ )
16
+ from stac_fastapi.sfeos_helpers.models.patch import ElasticPath, ESCommandSet
17
+
18
+
19
+ def validate_refresh(value: Union[str, bool]) -> str:
20
+ """
21
+ Validate the `refresh` parameter value.
22
+
23
+ Args:
24
+ value (Union[str, bool]): The `refresh` parameter value, which can be a string or a boolean.
25
+
26
+ Returns:
27
+ str: The validated value of the `refresh` parameter, which can be "true", "false", or "wait_for".
28
+ """
29
+ logger = logging.getLogger(__name__)
30
+
31
+ # Handle boolean-like values using get_bool_env
32
+ if isinstance(value, bool) or value in {
33
+ "true",
34
+ "false",
35
+ "1",
36
+ "0",
37
+ "yes",
38
+ "no",
39
+ "y",
40
+ "n",
41
+ }:
42
+ is_true = get_bool_env("DATABASE_REFRESH", default=value)
43
+ return "true" if is_true else "false"
44
+
45
+ # Normalize to lowercase for case-insensitivity
46
+ value = value.lower()
47
+
48
+ # Handle "wait_for" explicitly
49
+ if value == "wait_for":
50
+ return "wait_for"
51
+
52
+ # Log a warning for invalid values and default to "false"
53
+ logger.warning(
54
+ f"Invalid value for `refresh`: '{value}'. Expected 'true', 'false', or 'wait_for'. Defaulting to 'false'."
55
+ )
56
+ return "false"
57
+
58
+
59
+ def merge_to_operations(data: Dict) -> List:
60
+ """Convert merge operation to list of RF6902 operations.
61
+
62
+ Args:
63
+ data: dictionary to convert.
64
+
65
+ Returns:
66
+ List: list of RF6902 operations.
67
+ """
68
+ operations = []
69
+
70
+ for key, value in data.copy().items():
71
+
72
+ if value is None:
73
+ operations.append(PatchRemove(op="remove", path=key))
74
+
75
+ elif isinstance(value, dict):
76
+ nested_operations = merge_to_operations(value)
77
+
78
+ for nested_operation in nested_operations:
79
+ nested_operation.path = f"{key}.{nested_operation.path}"
80
+ operations.append(nested_operation)
81
+
82
+ else:
83
+ operations.append(PatchAddReplaceTest(op="add", path=key, value=value))
84
+
85
+ return operations
86
+
87
+
88
+ def check_commands(
89
+ commands: ESCommandSet,
90
+ op: str,
91
+ path: ElasticPath,
92
+ from_path: bool = False,
93
+ ) -> None:
94
+ """Add Elasticsearch checks to operation.
95
+
96
+ Args:
97
+ commands (List[str]): current commands
98
+ op (str): the operation of script
99
+ path (Dict): path of variable to run operation on
100
+ from_path (bool): True if path is a from path
101
+
102
+ """
103
+ if path.nest:
104
+ commands.add(
105
+ f"if (!ctx._source.containsKey('{path.nest}'))"
106
+ f"{{Debug.explain('{path.nest} does not exist');}}"
107
+ )
108
+
109
+ if path.index or op in ["remove", "replace", "test"] or from_path:
110
+ commands.add(
111
+ f"if (!ctx._source{path.es_nest}.containsKey('{path.key}'))"
112
+ f"{{Debug.explain('{path.key} does not exist in {path.nest}');}}"
113
+ )
114
+
115
+ if from_path and path.index is not None:
116
+ commands.add(
117
+ f"if ((ctx._source{path.es_location} instanceof ArrayList"
118
+ f" && ctx._source{path.es_location}.size() < {path.index})"
119
+ f" || (!(ctx._source{path.es_location} instanceof ArrayList)"
120
+ f" && !ctx._source{path.es_location}.containsKey('{path.index}')))"
121
+ f"{{Debug.explain('{path.path} does not exist');}}"
122
+ )
123
+
124
+
125
+ def remove_commands(commands: ESCommandSet, path: ElasticPath) -> None:
126
+ """Remove value at path.
127
+
128
+ Args:
129
+ commands (List[str]): current commands
130
+ path (ElasticPath): Path to value to be removed
131
+
132
+ """
133
+ if path.index is not None:
134
+ commands.add(
135
+ f"def {path.variable_name} = ctx._source{path.es_location}.remove({path.index});"
136
+ )
137
+
138
+ else:
139
+ commands.add(
140
+ f"def {path.variable_name} = ctx._source{path.es_nest}.remove('{path.key}');"
141
+ )
142
+
143
+
144
+ def add_commands(
145
+ commands: ESCommandSet,
146
+ operation: PatchOperation,
147
+ path: ElasticPath,
148
+ from_path: ElasticPath,
149
+ params: Dict,
150
+ ) -> None:
151
+ """Add value at path.
152
+
153
+ Args:
154
+ commands (List[str]): current commands
155
+ operation (PatchOperation): operation to run
156
+ path (ElasticPath): path for value to be added
157
+
158
+ """
159
+ if from_path is not None:
160
+ value = (
161
+ from_path.variable_name
162
+ if operation.op == "move"
163
+ else f"ctx._source.{from_path.es_path}"
164
+ )
165
+ else:
166
+ value = f"params.{path.param_key}"
167
+ params[path.param_key] = operation.value
168
+
169
+ if path.index is not None:
170
+ commands.add(
171
+ f"if (ctx._source{path.es_location} instanceof ArrayList)"
172
+ f"{{ctx._source{path.es_location}.{'add' if operation.op in ['add', 'move'] else 'set'}({path.index}, {value})}}"
173
+ f"else{{ctx._source.{path.es_path} = {value}}}"
174
+ )
175
+
176
+ else:
177
+ commands.add(f"ctx._source.{path.es_path} = {value};")
178
+
179
+
180
+ def test_commands(
181
+ commands: ESCommandSet, operation: PatchOperation, path: ElasticPath, params: Dict
182
+ ) -> None:
183
+ """Test value at path.
184
+
185
+ Args:
186
+ commands (List[str]): current commands
187
+ operation (PatchOperation): operation to run
188
+ path (ElasticPath): path for value to be tested
189
+ """
190
+ value = f"params.{path.param_key}"
191
+ params[path.param_key] = operation.value
192
+
193
+ commands.add(
194
+ f"if (ctx._source.{path.es_path} != {value})"
195
+ f"{{Debug.explain('Test failed `{path.path}` | "
196
+ f"{operation.json_value} != ' + ctx._source.{path.es_path});}}"
197
+ )
198
+
199
+
200
+ def operations_to_script(operations: List) -> Dict:
201
+ """Convert list of operation to painless script.
202
+
203
+ Args:
204
+ operations: List of RF6902 operations.
205
+
206
+ Returns:
207
+ Dict: elasticsearch update script.
208
+ """
209
+ commands: ESCommandSet = ESCommandSet()
210
+ params: Dict = {}
211
+
212
+ for operation in operations:
213
+ path = ElasticPath(path=operation.path)
214
+ from_path = (
215
+ ElasticPath(path=operation.from_) if hasattr(operation, "from_") else None
216
+ )
217
+
218
+ check_commands(commands=commands, op=operation.op, path=path)
219
+ if from_path is not None:
220
+ check_commands(
221
+ commands=commands, op=operation.op, path=from_path, from_path=True
222
+ )
223
+
224
+ if operation.op in ["remove", "move"]:
225
+ remove_path = from_path if from_path else path
226
+ remove_commands(commands=commands, path=remove_path)
227
+
228
+ if operation.op in ["add", "replace", "copy", "move"]:
229
+ add_commands(
230
+ commands=commands,
231
+ operation=operation,
232
+ path=path,
233
+ from_path=from_path,
234
+ params=params,
235
+ )
236
+
237
+ if operation.op == "test":
238
+ test_commands(
239
+ commands=commands, operation=operation, path=path, params=params
240
+ )
241
+
242
+ source = "".join(commands)
243
+
244
+ return {
245
+ "source": source,
246
+ "lang": "painless",
247
+ "params": params,
248
+ }
@@ -1,12 +1,12 @@
1
1
  """Filter client implementation for Elasticsearch/OpenSearch."""
2
2
 
3
3
  from collections import deque
4
- from typing import Any, Dict, Optional
4
+ from typing import Any, Dict, Optional, Tuple
5
5
 
6
6
  import attr
7
7
 
8
8
  from stac_fastapi.core.base_database_logic import BaseDatabaseLogic
9
- from stac_fastapi.core.extensions.filter import DEFAULT_QUERYABLES
9
+ from stac_fastapi.core.extensions.filter import ALL_QUERYABLES, DEFAULT_QUERYABLES
10
10
  from stac_fastapi.extensions.core.filter.client import AsyncBaseFiltersClient
11
11
  from stac_fastapi.sfeos_helpers.mappings import ES_MAPPING_TYPE_TO_JSON
12
12
 
@@ -59,31 +59,31 @@ class EsAsyncBaseFiltersClient(AsyncBaseFiltersClient):
59
59
 
60
60
  mapping_data = await self.database.get_items_mapping(collection_id)
61
61
  mapping_properties = next(iter(mapping_data.values()))["mappings"]["properties"]
62
- stack = deque(mapping_properties.items())
62
+ stack: deque[Tuple[str, Dict[str, Any]]] = deque(mapping_properties.items())
63
+ enum_fields: Dict[str, Dict[str, Any]] = {}
63
64
 
64
65
  while stack:
65
- field_name, field_def = stack.popleft()
66
+ field_fqn, field_def = stack.popleft()
66
67
 
67
68
  # Iterate over nested fields
68
69
  field_properties = field_def.get("properties")
69
70
  if field_properties:
70
- # Fields in Item Properties should be exposed with their un-prefixed names,
71
- # and not require expressions to prefix them with properties,
72
- # e.g., eo:cloud_cover instead of properties.eo:cloud_cover.
73
- if field_name == "properties":
74
- stack.extend(field_properties.items())
75
- else:
76
- stack.extend(
77
- (f"{field_name}.{k}", v) for k, v in field_properties.items()
78
- )
71
+ stack.extend(
72
+ (f"{field_fqn}.{k}", v) for k, v in field_properties.items()
73
+ )
79
74
 
80
75
  # Skip non-indexed or disabled fields
81
76
  field_type = field_def.get("type")
82
77
  if not field_type or not field_def.get("enabled", True):
83
78
  continue
84
79
 
80
+ # Fields in Item Properties should be exposed with their un-prefixed names,
81
+ # and not require expressions to prefix them with properties,
82
+ # e.g., eo:cloud_cover instead of properties.eo:cloud_cover.
83
+ field_name = field_fqn.removeprefix("properties.")
84
+
85
85
  # Generate field properties
86
- field_result = DEFAULT_QUERYABLES.get(field_name, {})
86
+ field_result = ALL_QUERYABLES.get(field_name, {})
87
87
  properties[field_name] = field_result
88
88
 
89
89
  field_name_human = field_name.replace("_", " ").title()
@@ -95,4 +95,13 @@ class EsAsyncBaseFiltersClient(AsyncBaseFiltersClient):
95
95
  if field_type in {"date", "date_nanos"}:
96
96
  field_result.setdefault("format", "date-time")
97
97
 
98
+ if field_result.pop("$enum", False):
99
+ enum_fields[field_fqn] = field_result
100
+
101
+ if enum_fields:
102
+ for field_fqn, unique_values in (
103
+ await self.database.get_items_unique_values(collection_id, enum_fields)
104
+ ).items():
105
+ enum_fields[field_fqn]["enum"] = unique_values
106
+
98
107
  return queryables
@@ -0,0 +1,166 @@
1
+ """patch helpers."""
2
+
3
+ import re
4
+ from typing import Any, Dict, Optional, Union
5
+
6
+ from pydantic import BaseModel, computed_field, model_validator
7
+
8
+ regex = re.compile(r"([^.' ]*:[^.'[ ]*)\.?")
9
+ replacements = str.maketrans({"/": "", ".": "", ":": "", "[": "", "]": ""})
10
+
11
+
12
+ class ESCommandSet:
13
+ """Uses dictionary keys to behaviour of ordered set.
14
+
15
+ Yields:
16
+ str: Elasticsearch commands
17
+ """
18
+
19
+ dict_: Dict[str, None] = {}
20
+
21
+ def __init__(self):
22
+ """Initialise ESCommandSet instance."""
23
+ self.dict_ = {}
24
+
25
+ def add(self, value: str):
26
+ """Add command.
27
+
28
+ Args:
29
+ value (str): value to be added
30
+ """
31
+ self.dict_[value] = None
32
+
33
+ def remove(self, value: str):
34
+ """Remove command.
35
+
36
+ Args:
37
+ value (str): value to be removed
38
+ """
39
+ del self.dict_[value]
40
+
41
+ def __iter__(self):
42
+ """Iterate Elasticsearch commands.
43
+
44
+ Yields:
45
+ str: Elasticsearch command
46
+ """
47
+ yield from self.dict_.keys()
48
+
49
+
50
+ def to_es(string: str):
51
+ """Convert patch operation key to Elasticsearch key.
52
+
53
+ Args:
54
+ string (str): string to be converted
55
+
56
+ Returns:
57
+ _type_: converted string
58
+ """
59
+ if matches := regex.findall(string):
60
+ for match in set(matches):
61
+ string = re.sub(rf"\.?{match}", f"['{match}']", string)
62
+
63
+ return string
64
+
65
+
66
+ class ElasticPath(BaseModel):
67
+ """Converts a JSON path to an Elasticsearch path.
68
+
69
+ Args:
70
+ path (str): JSON path to be converted.
71
+
72
+ """
73
+
74
+ path: str
75
+ nest: Optional[str] = None
76
+ partition: Optional[str] = None
77
+ key: Optional[str] = None
78
+
79
+ es_path: Optional[str] = None
80
+ es_nest: Optional[str] = None
81
+ es_key: Optional[str] = None
82
+
83
+ index_: Optional[int] = None
84
+
85
+ @model_validator(mode="before")
86
+ @classmethod
87
+ def validate_model(cls, data: Any):
88
+ """Set optional fields from JSON path.
89
+
90
+ Args:
91
+ data (Any): input data
92
+ """
93
+ data["path"] = data["path"].lstrip("/").replace("/", ".")
94
+ data["nest"], data["partition"], data["key"] = data["path"].rpartition(".")
95
+
96
+ if data["key"].lstrip("-").isdigit() or data["key"] == "-":
97
+ data["index_"] = -1 if data["key"] == "-" else int(data["key"])
98
+ data["path"] = f"{data['nest']}[{data['index_']}]"
99
+ data["nest"], data["partition"], data["key"] = data["nest"].rpartition(".")
100
+
101
+ data["es_path"] = to_es(data["path"])
102
+ data["es_nest"] = f".{to_es(data['nest'])}" if data["nest"] else ""
103
+ data["es_key"] = to_es(data["key"])
104
+
105
+ return data
106
+
107
+ @computed_field # type: ignore[misc]
108
+ @property
109
+ def index(self) -> Union[int, str, None]:
110
+ """Compute location of path.
111
+
112
+ Returns:
113
+ str: path index
114
+ """
115
+ if self.index_ and self.index_ < 0:
116
+
117
+ return f"ctx._source.{self.location}.size() - {-self.index_}"
118
+
119
+ return self.index_
120
+
121
+ @computed_field # type: ignore[misc]
122
+ @property
123
+ def location(self) -> str:
124
+ """Compute location of path.
125
+
126
+ Returns:
127
+ str: path location
128
+ """
129
+ return self.nest + self.partition + self.key
130
+
131
+ @computed_field # type: ignore[misc]
132
+ @property
133
+ def es_location(self) -> str:
134
+ """Compute location of path.
135
+
136
+ Returns:
137
+ str: path location
138
+ """
139
+ if self.es_key and ":" in self.es_key:
140
+ return self.es_nest + self.es_key
141
+ return self.es_nest + self.partition + self.es_key
142
+
143
+ @computed_field # type: ignore[misc]
144
+ @property
145
+ def variable_name(self) -> str:
146
+ """Variable name for scripting.
147
+
148
+ Returns:
149
+ str: variable name
150
+ """
151
+ if self.index is not None:
152
+ return f"{self.location.replace('.','_').replace(':','_')}_{self.index}"
153
+
154
+ return (
155
+ f"{self.nest.replace('.','_').replace(':','_')}_{self.key.replace(':','_')}"
156
+ )
157
+
158
+ @computed_field # type: ignore[misc]
159
+ @property
160
+ def param_key(self) -> str:
161
+ """Param key for scripting.
162
+
163
+ Returns:
164
+ str: param key
165
+ """
166
+ return self.path.translate(replacements)
@@ -0,0 +1,2 @@
1
+ """library version."""
2
+ __version__ = "6.0.0"
@@ -1 +0,0 @@
1
- stac-fastapi.core==5.0.0a0
@@ -1,50 +0,0 @@
1
- """Utility functions for database operations in Elasticsearch/OpenSearch.
2
-
3
- This module provides utility functions for working with database operations
4
- in Elasticsearch/OpenSearch, such as parameter validation.
5
- """
6
-
7
- import logging
8
- from typing import Union
9
-
10
- from stac_fastapi.core.utilities import get_bool_env
11
-
12
-
13
- def validate_refresh(value: Union[str, bool]) -> str:
14
- """
15
- Validate the `refresh` parameter value.
16
-
17
- Args:
18
- value (Union[str, bool]): The `refresh` parameter value, which can be a string or a boolean.
19
-
20
- Returns:
21
- str: The validated value of the `refresh` parameter, which can be "true", "false", or "wait_for".
22
- """
23
- logger = logging.getLogger(__name__)
24
-
25
- # Handle boolean-like values using get_bool_env
26
- if isinstance(value, bool) or value in {
27
- "true",
28
- "false",
29
- "1",
30
- "0",
31
- "yes",
32
- "no",
33
- "y",
34
- "n",
35
- }:
36
- is_true = get_bool_env("DATABASE_REFRESH", default=value)
37
- return "true" if is_true else "false"
38
-
39
- # Normalize to lowercase for case-insensitivity
40
- value = value.lower()
41
-
42
- # Handle "wait_for" explicitly
43
- if value == "wait_for":
44
- return "wait_for"
45
-
46
- # Log a warning for invalid values and default to "false"
47
- logger.warning(
48
- f"Invalid value for `refresh`: '{value}'. Expected 'true', 'false', or 'wait_for'. Defaulting to 'false'."
49
- )
50
- return "false"
@@ -1,2 +0,0 @@
1
- """library version."""
2
- __version__ = "5.0.0a0"
File without changes
File without changes