stac-fastapi-core 6.7.4__py3-none-any.whl → 6.7.5__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.
@@ -2,6 +2,7 @@
2
2
 
3
3
  from datetime import datetime, timezone
4
4
 
5
+ from stac_fastapi.core.utilities import get_bool_env
5
6
  from stac_fastapi.types.rfc3339 import rfc3339_str_to_datetime
6
7
 
7
8
 
@@ -15,27 +16,71 @@ def format_datetime_range(date_str: str) -> str:
15
16
  Returns:
16
17
  str: A string formatted as 'YYYY-MM-DDTHH:MM:SSZ/YYYY-MM-DDTHH:MM:SSZ', with '..' used if any element is None.
17
18
  """
18
-
19
- def normalize(dt):
20
- """Normalize datetime string and preserve millisecond precision."""
21
- dt = dt.strip()
22
- if not dt or dt == "..":
23
- return ".."
24
- dt_obj = rfc3339_str_to_datetime(dt)
25
- dt_utc = dt_obj.astimezone(timezone.utc)
26
- return dt_utc.isoformat(timespec="milliseconds").replace("+00:00", "Z")
27
-
28
- if not isinstance(date_str, str):
29
- return "../.."
30
-
31
- if "/" not in date_str:
32
- return f"{normalize(date_str)}/{normalize(date_str)}"
33
-
34
- try:
35
- start, end = date_str.split("/", 1)
36
- except Exception:
37
- return "../.."
38
- return f"{normalize(start)}/{normalize(end)}"
19
+ use_datetime_nanos = get_bool_env("USE_DATETIME_NANOS", default=True)
20
+
21
+ if use_datetime_nanos:
22
+ MIN_DATE_NANOS = datetime(1970, 1, 1, tzinfo=timezone.utc)
23
+ MAX_DATE_NANOS = datetime(2262, 4, 11, 23, 47, 16, 854775, tzinfo=timezone.utc)
24
+
25
+ def normalize(dt):
26
+ """Normalize datetime string and preserve nano second precision."""
27
+ dt = dt.strip()
28
+ if not dt or dt == "..":
29
+ return ".."
30
+ dt_utc = rfc3339_str_to_datetime(dt).astimezone(timezone.utc)
31
+ if dt_utc < MIN_DATE_NANOS:
32
+ dt_utc = MIN_DATE_NANOS
33
+ if dt_utc > MAX_DATE_NANOS:
34
+ dt_utc = MAX_DATE_NANOS
35
+ return dt_utc.isoformat(timespec="auto").replace("+00:00", "Z")
36
+
37
+ if not isinstance(date_str, str):
38
+ return f"{MIN_DATE_NANOS.isoformat(timespec='auto').replace('+00:00','Z')}/{MAX_DATE_NANOS.isoformat(timespec='auto').replace('+00:00','Z')}"
39
+
40
+ if "/" not in date_str:
41
+ return f"{normalize(date_str)}/{normalize(date_str)}"
42
+
43
+ try:
44
+ start, end = date_str.split("/", 1)
45
+ except Exception:
46
+ return f"{MIN_DATE_NANOS.isoformat(timespec='auto').replace('+00:00','Z')}/{MAX_DATE_NANOS.isoformat(timespec='auto').replace('+00:00','Z')}"
47
+
48
+ normalized_start = normalize(start)
49
+ normalized_end = normalize(end)
50
+
51
+ if normalized_start == "..":
52
+ normalized_start = MIN_DATE_NANOS.isoformat(timespec="auto").replace(
53
+ "+00:00", "Z"
54
+ )
55
+ if normalized_end == "..":
56
+ normalized_end = MAX_DATE_NANOS.isoformat(timespec="auto").replace(
57
+ "+00:00", "Z"
58
+ )
59
+
60
+ return f"{normalized_start}/{normalized_end}"
61
+
62
+ else:
63
+
64
+ def normalize(dt):
65
+ """Normalize datetime string and preserve millisecond precision."""
66
+ dt = dt.strip()
67
+ if not dt or dt == "..":
68
+ return ".."
69
+ dt_obj = rfc3339_str_to_datetime(dt)
70
+ dt_utc = dt_obj.astimezone(timezone.utc)
71
+ return dt_utc.isoformat(timespec="milliseconds").replace("+00:00", "Z")
72
+
73
+ if not isinstance(date_str, str):
74
+ return "../.."
75
+
76
+ if "/" not in date_str:
77
+ return f"{normalize(date_str)}/{normalize(date_str)}"
78
+
79
+ try:
80
+ start, end = date_str.split("/", 1)
81
+ except Exception:
82
+ return "../.."
83
+ return f"{normalize(start)}/{normalize(end)}"
39
84
 
40
85
 
41
86
  # Borrowed from pystac - https://github.com/stac-utils/pystac/blob/f5e4cf4a29b62e9ef675d4a4dac7977b09f53c8f/pystac/utils.py#L370-L394
@@ -2,25 +2,25 @@
2
2
 
3
3
  import json
4
4
  import logging
5
- from typing import List, Optional, Tuple
5
+ from functools import wraps
6
+ from typing import Callable, List, Optional, Tuple, cast
6
7
  from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
7
8
 
8
9
  from pydantic import Field, field_validator
9
10
  from pydantic_settings import BaseSettings
10
11
  from redis import asyncio as aioredis
11
12
  from redis.asyncio.sentinel import Sentinel
13
+ from redis.exceptions import ConnectionError as RedisConnectionError
14
+ from redis.exceptions import TimeoutError as RedisTimeoutError
15
+ from retry import retry # type: ignore
12
16
 
13
17
  logger = logging.getLogger(__name__)
14
18
 
15
19
 
16
- class RedisSentinelSettings(BaseSettings):
17
- """Configuration for connecting to Redis Sentinel."""
20
+ class RedisCommonSettings(BaseSettings):
21
+ """Common configuration for Redis Sentinel and Redis Standalone."""
18
22
 
19
- REDIS_SENTINEL_HOSTS: str = ""
20
- REDIS_SENTINEL_PORTS: str = "26379"
21
- REDIS_SENTINEL_MASTER_NAME: str = "master"
22
23
  REDIS_DB: int = 15
23
-
24
24
  REDIS_MAX_CONNECTIONS: Optional[int] = None
25
25
  REDIS_RETRY_TIMEOUT: bool = True
26
26
  REDIS_DECODE_RESPONSES: bool = True
@@ -28,9 +28,13 @@ class RedisSentinelSettings(BaseSettings):
28
28
  REDIS_HEALTH_CHECK_INTERVAL: int = Field(default=30, gt=0)
29
29
  REDIS_SELF_LINK_TTL: int = 1800
30
30
 
31
+ REDIS_QUERY_RETRIES_NUM: int = Field(default=3, gt=0)
32
+ REDIS_QUERY_INITIAL_DELAY: float = Field(default=1.0, gt=0)
33
+ REDIS_QUERY_BACKOFF: float = Field(default=2.0, gt=1)
34
+
31
35
  @field_validator("REDIS_DB")
32
36
  @classmethod
33
- def validate_db_sentinel(cls, v: int) -> int:
37
+ def validate_db(cls, v: int) -> int:
34
38
  """Validate REDIS_DB is not negative integer."""
35
39
  if v < 0:
36
40
  raise ValueError("REDIS_DB must be a positive integer")
@@ -46,12 +50,20 @@ class RedisSentinelSettings(BaseSettings):
46
50
 
47
51
  @field_validator("REDIS_SELF_LINK_TTL")
48
52
  @classmethod
49
- def validate_self_link_ttl_sentinel(cls, v: int) -> int:
50
- """Validate REDIS_SELF_LINK_TTL is not a negative integer."""
53
+ def validate_self_link_ttl(cls, v: int) -> int:
54
+ """Validate REDIS_SELF_LINK_TTL is negative."""
51
55
  if v < 0:
52
56
  raise ValueError("REDIS_SELF_LINK_TTL must be a positive integer")
53
57
  return v
54
58
 
59
+
60
+ class RedisSentinelSettings(RedisCommonSettings):
61
+ """Configuration for connecting to Redis Sentinel."""
62
+
63
+ REDIS_SENTINEL_HOSTS: str = ""
64
+ REDIS_SENTINEL_PORTS: str = "26379"
65
+ REDIS_SENTINEL_MASTER_NAME: str = "master"
66
+
55
67
  def get_sentinel_hosts(self) -> List[str]:
56
68
  """Parse Redis Sentinel hosts from string to list."""
57
69
  if not self.REDIS_SENTINEL_HOSTS:
@@ -96,19 +108,11 @@ class RedisSentinelSettings(BaseSettings):
96
108
  return [(str(host), int(port)) for host, port in zip(hosts, ports)]
97
109
 
98
110
 
99
- class RedisSettings(BaseSettings):
111
+ class RedisSettings(RedisCommonSettings):
100
112
  """Configuration for connecting Redis."""
101
113
 
102
114
  REDIS_HOST: str = ""
103
115
  REDIS_PORT: int = 6379
104
- REDIS_DB: int = 15
105
-
106
- REDIS_MAX_CONNECTIONS: Optional[int] = None
107
- REDIS_RETRY_TIMEOUT: bool = True
108
- REDIS_DECODE_RESPONSES: bool = True
109
- REDIS_CLIENT_NAME: str = "stac-fastapi-app"
110
- REDIS_HEALTH_CHECK_INTERVAL: int = Field(default=30, gt=0)
111
- REDIS_SELF_LINK_TTL: int = 1800
112
116
 
113
117
  @field_validator("REDIS_PORT")
114
118
  @classmethod
@@ -118,89 +122,93 @@ class RedisSettings(BaseSettings):
118
122
  raise ValueError("REDIS_PORT must be a positive integer")
119
123
  return v
120
124
 
121
- @field_validator("REDIS_DB")
122
- @classmethod
123
- def validate_db_standalone(cls, v: int) -> int:
124
- """Validate REDIS_DB is not a negative integer."""
125
- if v < 0:
126
- raise ValueError("REDIS_DB must be a positive integer")
127
- return v
128
-
129
- @field_validator("REDIS_MAX_CONNECTIONS", mode="before")
130
- @classmethod
131
- def validate_max_connections(cls, v):
132
- """Handle empty/None values for REDIS_MAX_CONNECTIONS."""
133
- if v in ["", "null", "Null", "NULL", "none", "None", "NONE", None]:
134
- return None
135
- return v
136
-
137
- @field_validator("REDIS_SELF_LINK_TTL")
138
- @classmethod
139
- def validate_self_link_ttl_standalone(cls, v: int) -> int:
140
- """Validate REDIS_SELF_LINK_TTL is negative."""
141
- if v < 0:
142
- raise ValueError("REDIS_SELF_LINK_TTL must be a positive integer")
143
- return v
144
-
145
125
 
146
126
  # Configure only one Redis configuration
147
127
  sentinel_settings = RedisSentinelSettings()
148
- standalone_settings = RedisSettings()
128
+ settings: RedisCommonSettings = cast(
129
+ RedisCommonSettings,
130
+ sentinel_settings if sentinel_settings.REDIS_SENTINEL_HOSTS else RedisSettings(),
131
+ )
132
+
133
+
134
+ def redis_retry(func: Callable) -> Callable:
135
+ """Retry with back-off decorator for Redis connections."""
136
+
137
+ @wraps(func)
138
+ @retry(
139
+ exceptions=(RedisConnectionError, RedisTimeoutError),
140
+ tries=settings.REDIS_QUERY_RETRIES_NUM,
141
+ delay=settings.REDIS_QUERY_INITIAL_DELAY,
142
+ backoff=settings.REDIS_QUERY_BACKOFF,
143
+ logger=logger,
144
+ )
145
+ async def wrapper(*args, **kwargs):
146
+ return await func(*args, **kwargs)
149
147
 
148
+ return wrapper
150
149
 
151
- async def connect_redis() -> Optional[aioredis.Redis]:
150
+
151
+ @redis_retry
152
+ async def _connect_redis_internal() -> Optional[aioredis.Redis]:
152
153
  """Return a Redis connection Redis or Redis Sentinel."""
153
- try:
154
- if sentinel_settings.REDIS_SENTINEL_HOSTS:
155
- sentinel_nodes = sentinel_settings.get_sentinel_nodes()
156
- sentinel = Sentinel(
157
- sentinel_nodes,
158
- decode_responses=sentinel_settings.REDIS_DECODE_RESPONSES,
159
- )
154
+ if sentinel_settings.REDIS_SENTINEL_HOSTS:
155
+ sentinel_nodes = settings.get_sentinel_nodes()
156
+ sentinel = Sentinel(
157
+ sentinel_nodes,
158
+ decode_responses=settings.REDIS_DECODE_RESPONSES,
159
+ )
160
160
 
161
- redis = sentinel.master_for(
162
- service_name=sentinel_settings.REDIS_SENTINEL_MASTER_NAME,
163
- db=sentinel_settings.REDIS_DB,
164
- decode_responses=sentinel_settings.REDIS_DECODE_RESPONSES,
165
- retry_on_timeout=sentinel_settings.REDIS_RETRY_TIMEOUT,
166
- client_name=sentinel_settings.REDIS_CLIENT_NAME,
167
- max_connections=sentinel_settings.REDIS_MAX_CONNECTIONS,
168
- health_check_interval=sentinel_settings.REDIS_HEALTH_CHECK_INTERVAL,
169
- )
170
- logger.info("Connected to Redis Sentinel")
171
-
172
- elif standalone_settings.REDIS_HOST:
173
- pool = aioredis.ConnectionPool(
174
- host=standalone_settings.REDIS_HOST,
175
- port=standalone_settings.REDIS_PORT,
176
- db=standalone_settings.REDIS_DB,
177
- max_connections=standalone_settings.REDIS_MAX_CONNECTIONS,
178
- decode_responses=standalone_settings.REDIS_DECODE_RESPONSES,
179
- retry_on_timeout=standalone_settings.REDIS_RETRY_TIMEOUT,
180
- health_check_interval=standalone_settings.REDIS_HEALTH_CHECK_INTERVAL,
181
- )
182
- redis = aioredis.Redis(
183
- connection_pool=pool, client_name=standalone_settings.REDIS_CLIENT_NAME
184
- )
185
- logger.info("Connected to Redis")
186
- else:
187
- logger.warning("No Redis configuration found")
188
- return None
161
+ redis = sentinel.master_for(
162
+ service_name=settings.REDIS_SENTINEL_MASTER_NAME,
163
+ db=settings.REDIS_DB,
164
+ decode_responses=settings.REDIS_DECODE_RESPONSES,
165
+ retry_on_timeout=settings.REDIS_RETRY_TIMEOUT,
166
+ client_name=settings.REDIS_CLIENT_NAME,
167
+ max_connections=settings.REDIS_MAX_CONNECTIONS,
168
+ health_check_interval=settings.REDIS_HEALTH_CHECK_INTERVAL,
169
+ )
170
+ logger.info("Connected to Redis Sentinel")
171
+
172
+ elif settings.REDIS_HOST:
173
+ pool = aioredis.ConnectionPool(
174
+ host=settings.REDIS_HOST,
175
+ port=settings.REDIS_PORT,
176
+ db=settings.REDIS_DB,
177
+ max_connections=settings.REDIS_MAX_CONNECTIONS,
178
+ decode_responses=settings.REDIS_DECODE_RESPONSES,
179
+ retry_on_timeout=settings.REDIS_RETRY_TIMEOUT,
180
+ health_check_interval=settings.REDIS_HEALTH_CHECK_INTERVAL,
181
+ )
182
+ redis = aioredis.Redis(
183
+ connection_pool=pool, client_name=settings.REDIS_CLIENT_NAME
184
+ )
185
+ logger.info("Connected to Redis")
186
+ else:
187
+ logger.warning("No Redis configuration found")
188
+ return None
189
+
190
+ return redis
189
191
 
190
- return redis
191
192
 
193
+ async def connect_redis() -> Optional[aioredis.Redis]:
194
+ """Handle Redis connection."""
195
+ try:
196
+ return await _connect_redis_internal()
197
+ except (
198
+ aioredis.ConnectionError,
199
+ aioredis.TimeoutError,
200
+ ) as e:
201
+ logger.error(f"Redis connection failed after retries: {e}")
192
202
  except aioredis.ConnectionError as e:
193
203
  logger.error(f"Redis connection error: {e}")
194
204
  return None
195
205
  except aioredis.AuthenticationError as e:
196
206
  logger.error(f"Redis authentication error: {e}")
197
207
  return None
198
- except aioredis.TimeoutError as e:
199
- logger.error(f"Redis timeout error: {e}")
200
- return None
201
208
  except Exception as e:
202
209
  logger.error(f"Failed to connect to Redis: {e}")
203
210
  return None
211
+ return None
204
212
 
205
213
 
206
214
  def get_redis_key(url: str, token: str) -> str:
@@ -230,19 +238,21 @@ def build_url_with_token(base_url: str, token: str) -> str:
230
238
  )
231
239
 
232
240
 
241
+ @redis_retry
233
242
  async def save_prev_link(
234
243
  redis: aioredis.Redis, next_url: str, current_url: str, next_token: str
235
244
  ) -> None:
236
245
  """Save the current page as the previous link for the next URL."""
237
246
  if next_url and next_token:
238
247
  if sentinel_settings.REDIS_SENTINEL_HOSTS:
239
- ttl_seconds = sentinel_settings.REDIS_SELF_LINK_TTL
240
- elif standalone_settings.REDIS_HOST:
241
- ttl_seconds = standalone_settings.REDIS_SELF_LINK_TTL
248
+ ttl_seconds = settings.REDIS_SELF_LINK_TTL
249
+ elif settings.REDIS_HOST:
250
+ ttl_seconds = settings.REDIS_SELF_LINK_TTL
242
251
  key = get_redis_key(next_url, next_token)
243
252
  await redis.setex(key, ttl_seconds, current_url)
244
253
 
245
254
 
255
+ @redis_retry
246
256
  async def get_prev_link(
247
257
  redis: aioredis.Redis, current_url: str, current_token: str
248
258
  ) -> Optional[str]:
@@ -1,2 +1,2 @@
1
1
  """library version."""
2
- __version__ = "6.7.4"
2
+ __version__ = "6.7.5"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stac_fastapi_core
3
- Version: 6.7.4
3
+ Version: 6.7.5
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
@@ -9,13 +9,11 @@ Classifier: Intended Audience :: Developers
9
9
  Classifier: Intended Audience :: Information Technology
10
10
  Classifier: Intended Audience :: Science/Research
11
11
  Classifier: License :: OSI Approved :: MIT License
12
- Classifier: Programming Language :: Python :: 3.9
13
- Classifier: Programming Language :: Python :: 3.10
14
12
  Classifier: Programming Language :: Python :: 3.11
15
13
  Classifier: Programming Language :: Python :: 3.12
16
14
  Classifier: Programming Language :: Python :: 3.13
17
15
  Classifier: Programming Language :: Python :: 3.14
18
- Requires-Python: >=3.9
16
+ Requires-Python: >=3.11
19
17
  Requires-Dist: attrs>=23.2.0
20
18
  Requires-Dist: fastapi~=0.109.0
21
19
  Requires-Dist: geojson-pydantic~=1.0.0
@@ -24,12 +22,14 @@ Requires-Dist: orjson~=3.11.0
24
22
  Requires-Dist: overrides~=7.4.0
25
23
  Requires-Dist: pydantic<3.0.0,>=2.4.1
26
24
  Requires-Dist: pygeofilter~=0.3.1
27
- Requires-Dist: redis==6.4.0
28
25
  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
26
+ Requires-Dist: stac-fastapi-api==6.1.1
27
+ Requires-Dist: stac-fastapi-extensions==6.1.1
28
+ Requires-Dist: stac-fastapi-types==6.1.1
32
29
  Requires-Dist: stac-pydantic~=3.3.0
30
+ Provides-Extra: redis
31
+ Requires-Dist: redis~=6.4.0; extra == 'redis'
32
+ Requires-Dist: retry~=0.9.2; extra == 'redis'
33
33
  Description-Content-Type: text/markdown
34
34
 
35
35
  # stac-fastapi-core
@@ -3,14 +3,14 @@ stac_fastapi/core/base_database_logic.py,sha256=3_XJ_j06ogQHE-Tcjkv5Vye_zNDn9OEU
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
5
  stac_fastapi/core/core.py,sha256=mmUP1da46EiON_TM2HYtxQxw8DHFg-2xEFoSg4P7UgU,50607
6
- stac_fastapi/core/datetime_utils.py,sha256=TrTgbU7AKNC-ic4a3HptfE5XAc9tHR7uJasZyhOuwnc,2633
6
+ stac_fastapi/core/datetime_utils.py,sha256=uhKZVfvsp6Y92__IB6HWn7C6VQU12DMeGuNg-78cb8Y,4534
7
7
  stac_fastapi/core/rate_limit.py,sha256=Gu8dAaJReGsj1L91U6m2tflU6RahpXDRs2-AYSKoybA,1318
8
- stac_fastapi/core/redis_utils.py,sha256=ckfuIjOUk08ofG1ql1eGXcOPG60odzSA86rXGr6h_QA,9859
8
+ stac_fastapi/core/redis_utils.py,sha256=6_lrXfZBi6vCCCibLDdwwHC3lLaXYTEmqQpxOMaCUH4,9689
9
9
  stac_fastapi/core/route_dependencies.py,sha256=hdtuMkv-zY1vg0YxiCz1aKP0SbBcORqDGEKDGgEazW8,5482
10
10
  stac_fastapi/core/serializers.py,sha256=HPA110RLZ17EnKrFf1rvVu5EwQHZto4V912Ofp_ypjA,7951
11
11
  stac_fastapi/core/session.py,sha256=aXqu4LXfVbAAsChMVXd9gAhczA2bZPne6HqPeklAwMY,474
12
12
  stac_fastapi/core/utilities.py,sha256=XR_9afK_j8wCydgoXj-CMtRyI8KqgIL3d4HZOE779dU,7807
13
- stac_fastapi/core/version.py,sha256=Ao9na2IeAbio6N4Di9fhrhw069xIK8K8gKIrHf29u_Q,45
13
+ stac_fastapi/core/version.py,sha256=WRUWk2u8pozjztw-4p6a3fAkitIN0nGhe4tLZ6P6xUk,45
14
14
  stac_fastapi/core/extensions/__init__.py,sha256=zSIAqou8jnakWPbkh4Ddcx1-oazZVBOs7U2PAakAdU0,291
15
15
  stac_fastapi/core/extensions/aggregation.py,sha256=v1hUHqlYuMqfQ554g3cTp16pUyRYucQxPERbHPAFtf8,1878
16
16
  stac_fastapi/core/extensions/collections_search.py,sha256=xpv51nffMq5a8grNSaLbv2IzeI5JH_pqcoWRbWhzn6Y,14406
@@ -20,6 +20,6 @@ stac_fastapi/core/extensions/query.py,sha256=Xmo8pfZEZKPudZEjjozv3R0wLOP0ayjC9E6
20
20
  stac_fastapi/core/models/__init__.py,sha256=g-D1DiGfmC9Bg27DW9JzkN6fAvscv75wyhyiZ6NzvIk,48
21
21
  stac_fastapi/core/models/links.py,sha256=0dWSEMt3aa7NCISlHwo11zLBeIV1LwXG3JGjrXC3dZI,6672
22
22
  stac_fastapi/core/models/search.py,sha256=7SgAUyzHGXBXSqB4G6cwq9FMwoAS00momb7jvBkjyow,27
23
- stac_fastapi_core-6.7.4.dist-info/METADATA,sha256=-mSLWe1MTLsXHLvmifAmr9vW59AxdFX7kJMrMevmkRI,3494
24
- stac_fastapi_core-6.7.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
- stac_fastapi_core-6.7.4.dist-info/RECORD,,
23
+ stac_fastapi_core-6.7.5.dist-info/METADATA,sha256=i2ek_1Z3c6aC7j9G3Atn2bHzZket_Q8Kc5peUi-wvj0,3480
24
+ stac_fastapi_core-6.7.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
+ stac_fastapi_core-6.7.5.dist-info/RECORD,,