stac-fastapi-core 6.5.1__py3-none-any.whl → 6.7.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.
@@ -0,0 +1,301 @@
1
+ """Utilities for connecting to and managing Redis connections."""
2
+
3
+ import json
4
+ import logging
5
+ from typing import List, Optional, Tuple
6
+ from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
7
+
8
+ from pydantic import field_validator
9
+ from pydantic_settings import BaseSettings
10
+ from redis import asyncio as aioredis
11
+ from redis.asyncio.sentinel import Sentinel
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class RedisSentinelSettings(BaseSettings):
17
+ """Configuration for connecting to Redis Sentinel."""
18
+
19
+ REDIS_SENTINEL_HOSTS: str = ""
20
+ REDIS_SENTINEL_PORTS: str = "26379"
21
+ REDIS_SENTINEL_MASTER_NAME: str = "master"
22
+ REDIS_DB: int = 15
23
+
24
+ REDIS_MAX_CONNECTIONS: int = 10
25
+ REDIS_RETRY_TIMEOUT: bool = True
26
+ REDIS_DECODE_RESPONSES: bool = True
27
+ REDIS_CLIENT_NAME: str = "stac-fastapi-app"
28
+ REDIS_HEALTH_CHECK_INTERVAL: int = 30
29
+ REDIS_SELF_LINK_TTL: int = 1800
30
+
31
+ @field_validator("REDIS_DB")
32
+ @classmethod
33
+ def validate_db_sentinel(cls, v: int) -> int:
34
+ """Validate REDIS_DB is not negative integer."""
35
+ if v < 0:
36
+ raise ValueError("REDIS_DB must be a positive integer")
37
+ return v
38
+
39
+ @field_validator("REDIS_MAX_CONNECTIONS")
40
+ @classmethod
41
+ def validate_max_connections_sentinel(cls, v: int) -> int:
42
+ """Validate REDIS_MAX_CONNECTIONS is at least 1."""
43
+ if v < 1:
44
+ raise ValueError("REDIS_MAX_CONNECTIONS must be at least 1")
45
+ return v
46
+
47
+ @field_validator("REDIS_HEALTH_CHECK_INTERVAL")
48
+ @classmethod
49
+ def validate_health_check_interval_sentinel(cls, v: int) -> int:
50
+ """Validate REDIS_HEALTH_CHECK_INTERVAL is not negative integer."""
51
+ if v < 0:
52
+ raise ValueError("REDIS_HEALTH_CHECK_INTERVAL must be a positive integer")
53
+ return v
54
+
55
+ @field_validator("REDIS_SELF_LINK_TTL")
56
+ @classmethod
57
+ def validate_self_link_ttl_sentinel(cls, v: int) -> int:
58
+ """Validate REDIS_SELF_LINK_TTL is not a negative integer."""
59
+ if v < 0:
60
+ raise ValueError("REDIS_SELF_LINK_TTL must be a positive integer")
61
+ return v
62
+
63
+ def get_sentinel_hosts(self) -> List[str]:
64
+ """Parse Redis Sentinel hosts from string to list."""
65
+ if not self.REDIS_SENTINEL_HOSTS:
66
+ return []
67
+
68
+ if self.REDIS_SENTINEL_HOSTS.strip().startswith("["):
69
+ return json.loads(self.REDIS_SENTINEL_HOSTS)
70
+ else:
71
+ return [
72
+ h.strip() for h in self.REDIS_SENTINEL_HOSTS.split(",") if h.strip()
73
+ ]
74
+
75
+ def get_sentinel_ports(self) -> List[int]:
76
+ """Parse Redis Sentinel ports from string to list of integers."""
77
+ if not self.REDIS_SENTINEL_PORTS:
78
+ return [26379]
79
+
80
+ if self.REDIS_SENTINEL_PORTS.strip().startswith("["):
81
+ return json.loads(self.REDIS_SENTINEL_PORTS)
82
+ else:
83
+ ports_str_list = [
84
+ p.strip() for p in self.REDIS_SENTINEL_PORTS.split(",") if p.strip()
85
+ ]
86
+ return [int(port) for port in ports_str_list]
87
+
88
+ def get_sentinel_nodes(self) -> List[Tuple[str, int]]:
89
+ """Get list of (host, port) tuples for Sentinel connection."""
90
+ hosts = self.get_sentinel_hosts()
91
+ ports = self.get_sentinel_ports()
92
+
93
+ if not hosts:
94
+ return []
95
+
96
+ if len(ports) == 1 and len(hosts) > 1:
97
+ ports = ports * len(hosts)
98
+
99
+ if len(hosts) != len(ports):
100
+ raise ValueError(
101
+ f"Mismatch between hosts ({len(hosts)}) and ports ({len(ports)})"
102
+ )
103
+
104
+ return [(str(host), int(port)) for host, port in zip(hosts, ports)]
105
+
106
+
107
+ class RedisSettings(BaseSettings):
108
+ """Configuration for connecting Redis."""
109
+
110
+ REDIS_HOST: str = ""
111
+ REDIS_PORT: int = 6379
112
+ REDIS_DB: int = 15
113
+
114
+ REDIS_MAX_CONNECTIONS: int = 10
115
+ REDIS_RETRY_TIMEOUT: bool = True
116
+ REDIS_DECODE_RESPONSES: bool = True
117
+ REDIS_CLIENT_NAME: str = "stac-fastapi-app"
118
+ REDIS_HEALTH_CHECK_INTERVAL: int = 30
119
+ REDIS_SELF_LINK_TTL: int = 1800
120
+
121
+ @field_validator("REDIS_PORT")
122
+ @classmethod
123
+ def validate_port_standalone(cls, v: int) -> int:
124
+ """Validate REDIS_PORT is not a negative integer."""
125
+ if v < 0:
126
+ raise ValueError("REDIS_PORT must be a positive integer")
127
+ return v
128
+
129
+ @field_validator("REDIS_DB")
130
+ @classmethod
131
+ def validate_db_standalone(cls, v: int) -> int:
132
+ """Validate REDIS_DB is not a negative integer."""
133
+ if v < 0:
134
+ raise ValueError("REDIS_DB must be a positive integer")
135
+ return v
136
+
137
+ @field_validator("REDIS_MAX_CONNECTIONS")
138
+ @classmethod
139
+ def validate_max_connections_standalone(cls, v: int) -> int:
140
+ """Validate REDIS_MAX_CONNECTIONS is at least 1."""
141
+ if v < 1:
142
+ raise ValueError("REDIS_MAX_CONNECTIONS must be at least 1")
143
+ return v
144
+
145
+ @field_validator("REDIS_HEALTH_CHECK_INTERVAL")
146
+ @classmethod
147
+ def validate_health_check_interval_standalone(cls, v: int) -> int:
148
+ """Validate REDIS_HEALTH_CHECK_INTERVAL is not a negative."""
149
+ if v < 0:
150
+ raise ValueError("REDIS_HEALTH_CHECK_INTERVAL must be a positive integer")
151
+ return v
152
+
153
+ @field_validator("REDIS_SELF_LINK_TTL")
154
+ @classmethod
155
+ def validate_self_link_ttl_standalone(cls, v: int) -> int:
156
+ """Validate REDIS_SELF_LINK_TTL is negative."""
157
+ if v < 0:
158
+ raise ValueError("REDIS_SELF_LINK_TTL must be a positive integer")
159
+ return v
160
+
161
+
162
+ # Configure only one Redis configuration
163
+ sentinel_settings = RedisSentinelSettings()
164
+ standalone_settings = RedisSettings()
165
+
166
+
167
+ async def connect_redis() -> Optional[aioredis.Redis]:
168
+ """Return a Redis connection Redis or Redis Sentinel."""
169
+ try:
170
+ if sentinel_settings.REDIS_SENTINEL_HOSTS:
171
+ sentinel_nodes = sentinel_settings.get_sentinel_nodes()
172
+ sentinel = Sentinel(
173
+ sentinel_nodes,
174
+ decode_responses=sentinel_settings.REDIS_DECODE_RESPONSES,
175
+ )
176
+
177
+ redis = sentinel.master_for(
178
+ service_name=sentinel_settings.REDIS_SENTINEL_MASTER_NAME,
179
+ db=sentinel_settings.REDIS_DB,
180
+ decode_responses=sentinel_settings.REDIS_DECODE_RESPONSES,
181
+ retry_on_timeout=sentinel_settings.REDIS_RETRY_TIMEOUT,
182
+ client_name=sentinel_settings.REDIS_CLIENT_NAME,
183
+ max_connections=sentinel_settings.REDIS_MAX_CONNECTIONS,
184
+ health_check_interval=sentinel_settings.REDIS_HEALTH_CHECK_INTERVAL,
185
+ )
186
+ logger.info("Connected to Redis Sentinel")
187
+
188
+ elif standalone_settings.REDIS_HOST:
189
+ pool = aioredis.ConnectionPool(
190
+ host=standalone_settings.REDIS_HOST,
191
+ port=standalone_settings.REDIS_PORT,
192
+ db=standalone_settings.REDIS_DB,
193
+ max_connections=standalone_settings.REDIS_MAX_CONNECTIONS,
194
+ decode_responses=standalone_settings.REDIS_DECODE_RESPONSES,
195
+ retry_on_timeout=standalone_settings.REDIS_RETRY_TIMEOUT,
196
+ health_check_interval=standalone_settings.REDIS_HEALTH_CHECK_INTERVAL,
197
+ )
198
+ redis = aioredis.Redis(
199
+ connection_pool=pool, client_name=standalone_settings.REDIS_CLIENT_NAME
200
+ )
201
+ logger.info("Connected to Redis")
202
+ else:
203
+ logger.warning("No Redis configuration found")
204
+ return None
205
+
206
+ return redis
207
+
208
+ except aioredis.ConnectionError as e:
209
+ logger.error(f"Redis connection error: {e}")
210
+ return None
211
+ except aioredis.AuthenticationError as e:
212
+ logger.error(f"Redis authentication error: {e}")
213
+ return None
214
+ except aioredis.TimeoutError as e:
215
+ logger.error(f"Redis timeout error: {e}")
216
+ return None
217
+ except Exception as e:
218
+ logger.error(f"Failed to connect to Redis: {e}")
219
+ return None
220
+
221
+
222
+ def get_redis_key(url: str, token: str) -> str:
223
+ """Create Redis key using URL path and token."""
224
+ parsed = urlparse(url)
225
+ return f"nav:{parsed.path}:{token}"
226
+
227
+
228
+ def build_url_with_token(base_url: str, token: str) -> str:
229
+ """Build URL with token parameter."""
230
+ parsed = urlparse(base_url)
231
+ query_params = parse_qs(parsed.query)
232
+
233
+ query_params["token"] = [token]
234
+
235
+ new_query = urlencode(query_params, doseq=True)
236
+
237
+ return urlunparse(
238
+ (
239
+ parsed.scheme,
240
+ parsed.netloc,
241
+ parsed.path,
242
+ parsed.params,
243
+ new_query,
244
+ parsed.fragment,
245
+ )
246
+ )
247
+
248
+
249
+ async def save_prev_link(
250
+ redis: aioredis.Redis, next_url: str, current_url: str, next_token: str
251
+ ) -> None:
252
+ """Save the current page as the previous link for the next URL."""
253
+ if next_url and next_token:
254
+ if sentinel_settings.REDIS_SENTINEL_HOSTS:
255
+ ttl_seconds = sentinel_settings.REDIS_SELF_LINK_TTL
256
+ elif standalone_settings.REDIS_HOST:
257
+ ttl_seconds = standalone_settings.REDIS_SELF_LINK_TTL
258
+ key = get_redis_key(next_url, next_token)
259
+ await redis.setex(key, ttl_seconds, current_url)
260
+
261
+
262
+ async def get_prev_link(
263
+ redis: aioredis.Redis, current_url: str, current_token: str
264
+ ) -> Optional[str]:
265
+ """Get the previous page link for the current token."""
266
+ if not current_url or not current_token:
267
+ return None
268
+ key = get_redis_key(current_url, current_token)
269
+ return await redis.get(key)
270
+
271
+
272
+ async def redis_pagination_links(
273
+ current_url: str, token: str, next_token: str, links: list
274
+ ) -> None:
275
+ """Handle Redis pagination."""
276
+ redis = await connect_redis()
277
+ if not redis:
278
+ logger.warning("Redis connection failed.")
279
+ return
280
+
281
+ try:
282
+ if next_token:
283
+ next_url = build_url_with_token(current_url, next_token)
284
+ await save_prev_link(redis, next_url, current_url, next_token)
285
+
286
+ if token:
287
+ prev_link = await get_prev_link(redis, current_url, token)
288
+ if prev_link:
289
+ links.insert(
290
+ 0,
291
+ {
292
+ "rel": "previous",
293
+ "type": "application/json",
294
+ "method": "GET",
295
+ "href": prev_link,
296
+ },
297
+ )
298
+ except Exception as e:
299
+ logger.warning(f"Redis pagination operation failed: {e}")
300
+ finally:
301
+ await redis.close()
@@ -1,6 +1,7 @@
1
1
  """Serializers."""
2
2
 
3
3
  import abc
4
+ import logging
4
5
  from copy import deepcopy
5
6
  from typing import Any, List, Optional
6
7
 
@@ -13,6 +14,8 @@ from stac_fastapi.core.utilities import get_bool_env
13
14
  from stac_fastapi.types import stac as stac_types
14
15
  from stac_fastapi.types.links import ItemLinks, resolve_links
15
16
 
17
+ logger = logging.getLogger(__name__)
18
+
16
19
 
17
20
  @attr.s
18
21
  class Serializer(abc.ABC):
@@ -168,6 +171,9 @@ class CollectionSerializer(Serializer):
168
171
  # Avoid modifying the input dict in-place ... doing so breaks some tests
169
172
  collection = deepcopy(collection)
170
173
 
174
+ # Remove internal bbox_shape field (not part of STAC spec)
175
+ collection.pop("bbox_shape", None)
176
+
171
177
  # Set defaults
172
178
  collection_id = collection.get("id")
173
179
  collection.setdefault("type", "Collection")
@@ -10,15 +10,7 @@ from typing import Any, Dict, List, Optional, Set, Union
10
10
 
11
11
  from stac_fastapi.types.stac import Item
12
12
 
13
-
14
- def get_max_limit():
15
- """
16
- Retrieve a MAX_LIMIT value from an environment variable.
17
-
18
- Returns:
19
- int: The int value parsed from the environment variable.
20
- """
21
- return int(os.getenv("ENV_MAX_LIMIT", 10000))
13
+ MAX_LIMIT = 10000
22
14
 
23
15
 
24
16
  def get_bool_env(name: str, default: Union[bool, str] = False) -> bool:
@@ -1,2 +1,2 @@
1
1
  """library version."""
2
- __version__ = "6.5.1"
2
+ __version__ = "6.7.0"
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: stac_fastapi_core
3
+ Version: 6.7.0
4
+ Summary: Core library for the Elasticsearch and Opensearch stac-fastapi backends.
5
+ Project-URL: Homepage, https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
6
+ License: MIT
7
+ Keywords: Elasticsearch,FastAPI,Opensearch,STAC,STAC-API,stac-fastapi
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Intended Audience :: Information Technology
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Requires-Python: >=3.9
19
+ Requires-Dist: attrs>=23.2.0
20
+ Requires-Dist: fastapi~=0.109.0
21
+ Requires-Dist: geojson-pydantic~=1.0.0
22
+ Requires-Dist: jsonschema~=4.0.0
23
+ Requires-Dist: orjson~=3.11.0
24
+ Requires-Dist: overrides~=7.4.0
25
+ Requires-Dist: pydantic<3.0.0,>=2.4.1
26
+ Requires-Dist: pygeofilter~=0.3.1
27
+ Requires-Dist: redis==6.4.0
28
+ Requires-Dist: slowapi~=0.1.9
29
+ Requires-Dist: stac-fastapi-api==6.0.0
30
+ Requires-Dist: stac-fastapi-extensions==6.0.0
31
+ Requires-Dist: stac-fastapi-types==6.0.0
32
+ Requires-Dist: stac-pydantic~=3.3.0
33
+ Description-Content-Type: text/markdown
34
+
35
+ # stac-fastapi-core
36
+
37
+ <p align="left">
38
+ <img src="https://raw.githubusercontent.com/stac-utils/stac-fastapi-elasticsearch-opensearch/refs/heads/main/assets/sfeos.png" width=1000>
39
+ </p>
40
+
41
+ [![Downloads](https://static.pepy.tech/badge/stac-fastapi-core?color=blue)](https://pepy.tech/project/stac-fastapi-core)
42
+ [![GitHub contributors](https://img.shields.io/github/contributors/stac-utils/stac-fastapi-elasticsearch-opensearch?color=blue)](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/graphs/contributors)
43
+ [![GitHub stars](https://img.shields.io/github/stars/stac-utils/stac-fastapi-elasticsearch-opensearch.svg?color=blue)](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/stargazers)
44
+ [![GitHub forks](https://img.shields.io/github/forks/stac-utils/stac-fastapi-elasticsearch-opensearch.svg?color=blue)](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/network/members)
45
+ [![PyPI version](https://img.shields.io/pypi/v/stac-fastapi-elasticsearch.svg?color=blue)](https://pypi.org/project/stac-fastapi-elasticsearch/)
46
+ [![STAC](https://img.shields.io/badge/STAC-1.1.0-blue.svg)](https://github.com/radiantearth/stac-spec/tree/v1.1.0)
47
+ [![stac-fastapi](https://img.shields.io/badge/stac--fastapi-6.0.0-blue.svg)](https://github.com/stac-utils/stac-fastapi)
48
+
49
+ Core functionality for stac-fastapi. For full documentation, please see the [main README](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/blob/main/README.md).
50
+
51
+ ## Package Information
52
+
53
+ - **Package name**: stac-fastapi-core
54
+ - **Description**: Core functionality for STAC API implementations.
55
+ - **Documentation**: [https://stac-utils.github.io/stac-fastapi-elasticsearch-opensearch/](https://stac-utils.github.io/stac-fastapi-elasticsearch-opensearch/)
56
+ - **Source**: [GitHub Repository](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/)
57
+
58
+ ## Installation
59
+
60
+ ```bash
61
+ pip install stac-fastapi-core
62
+ ```
63
+
64
+ ## Quick Start
65
+
66
+ For detailed usage and examples, please refer to the [main documentation](https://stac-utils.github.io/stac-fastapi-elasticsearch-opensearch/).
@@ -1,25 +1,25 @@
1
1
  stac_fastapi/core/__init__.py,sha256=8izV3IWRGdXmDOK1hIPQAanbWs9EI04PJCGgqG1ZGIs,20
2
- stac_fastapi/core/base_database_logic.py,sha256=nhj0CZNur_SRs4GtXTER-Zjq8JPub5zINiCKbCjw0Bs,3814
2
+ stac_fastapi/core/base_database_logic.py,sha256=3_XJ_j06ogQHE-Tcjkv5Vye_zNDn9OEU9lNYU03am1k,4618
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=D_TUtcPnLKWDLnY-oPw5jsmqoA2ghLqPPs6H56c4myc,48369
5
+ stac_fastapi/core/core.py,sha256=0_H0QKQN-fkvzAidqGt8MvYfO2f_OF82pXXYs9HpQ_4,50387
6
6
  stac_fastapi/core/datetime_utils.py,sha256=TrTgbU7AKNC-ic4a3HptfE5XAc9tHR7uJasZyhOuwnc,2633
7
7
  stac_fastapi/core/rate_limit.py,sha256=Gu8dAaJReGsj1L91U6m2tflU6RahpXDRs2-AYSKoybA,1318
8
+ stac_fastapi/core/redis_utils.py,sha256=hoghKhq7eHkqttU6T_Yq6uSWPPfgFjz9HrN8nMLJqNo,10440
8
9
  stac_fastapi/core/route_dependencies.py,sha256=hdtuMkv-zY1vg0YxiCz1aKP0SbBcORqDGEKDGgEazW8,5482
9
- stac_fastapi/core/serializers.py,sha256=HU7sVSMa6w_F_qs_gdAeIFZ18GW-6t8ZHFmgI4-1uNw,7455
10
+ stac_fastapi/core/serializers.py,sha256=ZW5hPgq-mftk6zxJeZGur-1Qxn7YGc3fJYFLsd-SYwM,7619
10
11
  stac_fastapi/core/session.py,sha256=aXqu4LXfVbAAsChMVXd9gAhczA2bZPne6HqPeklAwMY,474
11
- stac_fastapi/core/utilities.py,sha256=WbspaJey_Cs-7TrBKasdqq7yjB7vjKiU01KyJM0m8_E,7506
12
- stac_fastapi/core/version.py,sha256=FuGC3fKnAmD4Wk95swJ6qCVBs5mZiShrlRKuSH-voyE,45
12
+ stac_fastapi/core/utilities.py,sha256=xXWO5oJCNDi7_C5jPYlHZD0B-DL-FN66eEUBUSW-cXw,7296
13
+ stac_fastapi/core/version.py,sha256=GMc7YzxyWeUVpr_RMVlweeoH2lCLxOWemF-FOkqKXx8,45
13
14
  stac_fastapi/core/extensions/__init__.py,sha256=zSIAqou8jnakWPbkh4Ddcx1-oazZVBOs7U2PAakAdU0,291
14
15
  stac_fastapi/core/extensions/aggregation.py,sha256=v1hUHqlYuMqfQ554g3cTp16pUyRYucQxPERbHPAFtf8,1878
15
- stac_fastapi/core/extensions/collections_search.py,sha256=q7eRBykEqNRCiTfkmM_TobqKkxA3n1zQ7dYo37juE6s,6503
16
+ stac_fastapi/core/extensions/collections_search.py,sha256=xpv51nffMq5a8grNSaLbv2IzeI5JH_pqcoWRbWhzn6Y,14406
16
17
  stac_fastapi/core/extensions/fields.py,sha256=NCT5XHvfaf297eDPNaIFsIzvJnbbUTpScqF0otdx0NA,1066
17
18
  stac_fastapi/core/extensions/filter.py,sha256=-NQGME7rR_ereuDx-LAa1M5JhEXFaKiTtkH2asraYHE,2998
18
19
  stac_fastapi/core/extensions/query.py,sha256=Xmo8pfZEZKPudZEjjozv3R0wLOP0ayjC9E67sBOXqWY,1803
19
20
  stac_fastapi/core/models/__init__.py,sha256=g-D1DiGfmC9Bg27DW9JzkN6fAvscv75wyhyiZ6NzvIk,48
20
21
  stac_fastapi/core/models/links.py,sha256=0dWSEMt3aa7NCISlHwo11zLBeIV1LwXG3JGjrXC3dZI,6672
21
22
  stac_fastapi/core/models/search.py,sha256=7SgAUyzHGXBXSqB4G6cwq9FMwoAS00momb7jvBkjyow,27
22
- stac_fastapi_core-6.5.1.dist-info/METADATA,sha256=PIBVPUY1dqhKMj_JDMOUYuyLoSa8mDoLuUpfI_sjKcU,41746
23
- stac_fastapi_core-6.5.1.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
24
- stac_fastapi_core-6.5.1.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
25
- stac_fastapi_core-6.5.1.dist-info/RECORD,,
23
+ stac_fastapi_core-6.7.0.dist-info/METADATA,sha256=K8U8uKNxI9D5JM_ZIqaOV0wi0TLPgSwK9Qv4ca2VCb4,3494
24
+ stac_fastapi_core-6.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
+ stac_fastapi_core-6.7.0.dist-info/RECORD,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.45.1)
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-