stac-fastapi.core 3.1.0__tar.gz → 3.2.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. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/PKG-INFO +5 -14
  2. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/README.md +5 -1
  3. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/setup.py +1 -0
  4. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/extensions/aggregation.py +36 -1
  5. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/models/links.py +8 -0
  6. stac-fastapi.core-3.2.0/stac_fastapi/core/rate_limit.py +44 -0
  7. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/version.py +1 -1
  8. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi.core.egg-info/PKG-INFO +5 -14
  9. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi.core.egg-info/SOURCES.txt +1 -0
  10. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi.core.egg-info/requires.txt +1 -0
  11. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi.core.egg-info/top_level.txt +0 -1
  12. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/setup.cfg +0 -0
  13. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/__init__.py +0 -0
  14. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/base_database_logic.py +0 -0
  15. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/base_settings.py +0 -0
  16. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/basic_auth.py +0 -0
  17. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/core.py +0 -0
  18. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/datetime_utils.py +0 -0
  19. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/extensions/__init__.py +0 -0
  20. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/extensions/fields.py +0 -0
  21. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/extensions/filter.py +0 -0
  22. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/extensions/query.py +0 -0
  23. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/models/__init__.py +0 -0
  24. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/models/search.py +0 -0
  25. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/route_dependencies.py +0 -0
  26. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/serializers.py +0 -0
  27. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/session.py +0 -0
  28. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi/core/utilities.py +0 -0
  29. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi.core.egg-info/dependency_links.txt +0 -0
  30. {stac_fastapi_core-3.1.0 → stac-fastapi.core-3.2.0}/stac_fastapi.core.egg-info/not-zip-safe +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stac-fastapi.core
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
4
  Summary: Core 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
@@ -15,19 +15,6 @@ Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Requires-Python: >=3.8
17
17
  Description-Content-Type: text/markdown
18
- Requires-Dist: fastapi-slim
19
- Requires-Dist: attrs>=23.2.0
20
- Requires-Dist: pydantic[dotenv]
21
- Requires-Dist: stac_pydantic>=3
22
- Requires-Dist: stac-fastapi.types==3.0.0
23
- Requires-Dist: stac-fastapi.api==3.0.0
24
- Requires-Dist: stac-fastapi.extensions==3.0.0
25
- Requires-Dist: orjson
26
- Requires-Dist: overrides
27
- Requires-Dist: geojson-pydantic
28
- Requires-Dist: pygeofilter==0.2.1
29
- Requires-Dist: typing_extensions==4.8.0
30
- Requires-Dist: jsonschema
31
18
 
32
19
  # stac-fastapi-elasticsearch-opensearch (sfeos)
33
20
 
@@ -415,3 +402,7 @@ Available aggregations are:
415
402
  - geometry_geotile_grid_frequency ([geotile grid](https://opensearch.org/docs/latest/aggregations/bucket/geotile-grid/) on Item.geometry)
416
403
 
417
404
  Support for additional fields and new aggregations can be added in the associated `database_logic.py` file.
405
+
406
+ ## Rate Limiting
407
+
408
+ Rate limiting is an optional security feature that controls API request frequency on a remote address basis. It's enabled by setting the `STAC_FASTAPI_RATE_LIMIT` environment variable, e.g., `500/minute`. This limits each client to 500 requests per minute, helping prevent abuse and maintain API stability. Implementation examples are available in the [examples/rate_limit](examples/rate_limit) directory.
@@ -383,4 +383,8 @@ Available aggregations are:
383
383
  - geometry_geohash_grid_frequency ([geohash grid](https://opensearch.org/docs/latest/aggregations/bucket/geohash-grid/) on Item.geometry)
384
384
  - geometry_geotile_grid_frequency ([geotile grid](https://opensearch.org/docs/latest/aggregations/bucket/geotile-grid/) on Item.geometry)
385
385
 
386
- Support for additional fields and new aggregations can be added in the associated `database_logic.py` file.
386
+ Support for additional fields and new aggregations can be added in the associated `database_logic.py` file.
387
+
388
+ ## Rate Limiting
389
+
390
+ Rate limiting is an optional security feature that controls API request frequency on a remote address basis. It's enabled by setting the `STAC_FASTAPI_RATE_LIMIT` environment variable, e.g., `500/minute`. This limits each client to 500 requests per minute, helping prevent abuse and maintain API stability. Implementation examples are available in the [examples/rate_limit](examples/rate_limit) directory.
@@ -19,6 +19,7 @@ install_requires = [
19
19
  "pygeofilter==0.2.1",
20
20
  "typing_extensions==4.8.0",
21
21
  "jsonschema",
22
+ "slowapi==0.1.9",
22
23
  ]
23
24
 
24
25
  setup(
@@ -50,6 +50,7 @@ class EsAggregationExtensionGetRequest(
50
50
  centroid_geotile_grid_frequency_precision: Optional[int] = attr.ib(default=None)
51
51
  geometry_geohash_grid_frequency_precision: Optional[int] = attr.ib(default=None)
52
52
  geometry_geotile_grid_frequency_precision: Optional[int] = attr.ib(default=None)
53
+ datetime_frequency_interval: Optional[str] = attr.ib(default=None)
53
54
 
54
55
 
55
56
  class EsAggregationExtensionPostRequest(
@@ -62,6 +63,7 @@ class EsAggregationExtensionPostRequest(
62
63
  centroid_geotile_grid_frequency_precision: Optional[int] = None
63
64
  geometry_geohash_grid_frequency_precision: Optional[int] = None
64
65
  geometry_geotile_grid_frequency_precision: Optional[int] = None
66
+ datetime_frequency_interval: Optional[str] = None
65
67
 
66
68
 
67
69
  @attr.s
@@ -124,6 +126,8 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
124
126
  MAX_GEOHASH_PRECISION = 12
125
127
  MAX_GEOHEX_PRECISION = 15
126
128
  MAX_GEOTILE_PRECISION = 29
129
+ SUPPORTED_DATETIME_INTERVAL = {"day", "month", "year"}
130
+ DEFAULT_DATETIME_INTERVAL = "month"
127
131
 
128
132
  async def get_aggregations(self, collection_id: Optional[str] = None, **kwargs):
129
133
  """Get the available aggregations for a catalog or collection defined in the STAC JSON. If no aggregations, default aggregations are used."""
@@ -143,7 +147,7 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
143
147
  {
144
148
  "rel": "self",
145
149
  "type": "application/json",
146
- "href": urljoin(collection_endpoint, "aggregations"),
150
+ "href": urljoin(collection_endpoint + "/", "aggregations"),
147
151
  },
148
152
  ]
149
153
  )
@@ -182,6 +186,30 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
182
186
  else:
183
187
  return min_value
184
188
 
189
+ def extract_date_histogram_interval(self, value: Optional[str]) -> str:
190
+ """
191
+ Ensure that the interval for the date histogram is valid. If no value is provided, the default will be returned.
192
+
193
+ Args:
194
+ value: value entered by the user
195
+
196
+ Returns:
197
+ string containing the date histogram interval to use.
198
+
199
+ Raises:
200
+ HTTPException: if the supplied value is not in the supported intervals
201
+ """
202
+ if value is not None:
203
+ if value not in self.SUPPORTED_DATETIME_INTERVAL:
204
+ raise HTTPException(
205
+ status_code=400,
206
+ detail=f"Invalid datetime interval. Must be one of {self.SUPPORTED_DATETIME_INTERVAL}",
207
+ )
208
+ else:
209
+ return value
210
+ else:
211
+ return self.DEFAULT_DATETIME_INTERVAL
212
+
185
213
  @staticmethod
186
214
  def _return_date(
187
215
  interval: Optional[Union[DateTimeType, str]]
@@ -319,6 +347,7 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
319
347
  centroid_geotile_grid_frequency_precision: Optional[int] = None,
320
348
  geometry_geohash_grid_frequency_precision: Optional[int] = None,
321
349
  geometry_geotile_grid_frequency_precision: Optional[int] = None,
350
+ datetime_frequency_interval: Optional[str] = None,
322
351
  **kwargs,
323
352
  ) -> Union[Dict, Exception]:
324
353
  """Get aggregations from the database."""
@@ -339,6 +368,7 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
339
368
  "centroid_geotile_grid_frequency_precision": centroid_geotile_grid_frequency_precision,
340
369
  "geometry_geohash_grid_frequency_precision": geometry_geohash_grid_frequency_precision,
341
370
  "geometry_geotile_grid_frequency_precision": geometry_geotile_grid_frequency_precision,
371
+ "datetime_frequency_interval": datetime_frequency_interval,
342
372
  }
343
373
 
344
374
  if collection_id:
@@ -475,6 +505,10 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
475
505
  self.MAX_GEOTILE_PRECISION,
476
506
  )
477
507
 
508
+ datetime_frequency_interval = self.extract_date_histogram_interval(
509
+ aggregate_request.datetime_frequency_interval,
510
+ )
511
+
478
512
  try:
479
513
  db_response = await self.database.aggregate(
480
514
  collections,
@@ -485,6 +519,7 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
485
519
  centroid_geotile_grid_precision,
486
520
  geometry_geohash_grid_precision,
487
521
  geometry_geotile_grid_precision,
522
+ datetime_frequency_interval,
488
523
  )
489
524
  except Exception as error:
490
525
  if not isinstance(error, IndexError):
@@ -114,6 +114,14 @@ class CollectionLinks(BaseLinks):
114
114
  collection_id: str = attr.ib()
115
115
  extensions: List[str] = attr.ib(default=attr.Factory(list))
116
116
 
117
+ def link_self(self) -> Dict:
118
+ """Return the self link."""
119
+ return dict(
120
+ rel=Relations.self.value,
121
+ type=MimeTypes.json.value,
122
+ href=urljoin(self.base_url, f"collections/{self.collection_id}"),
123
+ )
124
+
117
125
  def link_parent(self) -> Dict[str, Any]:
118
126
  """Create the `parent` link."""
119
127
  return dict(rel=Relations.parent, type=MimeTypes.json.value, href=self.base_url)
@@ -0,0 +1,44 @@
1
+ """Rate limiting middleware."""
2
+
3
+ import logging
4
+ import os
5
+ from typing import Optional
6
+
7
+ from fastapi import FastAPI, Request
8
+ from slowapi import Limiter, _rate_limit_exceeded_handler
9
+ from slowapi.errors import RateLimitExceeded
10
+ from slowapi.middleware import SlowAPIMiddleware
11
+ from slowapi.util import get_remote_address
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def get_limiter(key_func=get_remote_address):
17
+ """Create and return a Limiter instance for rate limiting."""
18
+ return Limiter(key_func=key_func)
19
+
20
+
21
+ def setup_rate_limit(
22
+ app: FastAPI, rate_limit: Optional[str] = None, key_func=get_remote_address
23
+ ):
24
+ """Set up rate limiting middleware."""
25
+ RATE_LIMIT = rate_limit or os.getenv("STAC_FASTAPI_RATE_LIMIT")
26
+
27
+ if not RATE_LIMIT:
28
+ logger.info("Rate limiting is disabled")
29
+ return
30
+
31
+ logger.info(f"Setting up rate limit with RATE_LIMIT={RATE_LIMIT}")
32
+
33
+ limiter = get_limiter(key_func)
34
+ app.state.limiter = limiter
35
+ app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
36
+ app.add_middleware(SlowAPIMiddleware)
37
+
38
+ @app.middleware("http")
39
+ @limiter.limit(RATE_LIMIT)
40
+ async def rate_limit_middleware(request: Request, call_next):
41
+ response = await call_next(request)
42
+ return response
43
+
44
+ logger.info("Rate limit setup complete")
@@ -1,2 +1,2 @@
1
1
  """library version."""
2
- __version__ = "3.1.0"
2
+ __version__ = "3.2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stac-fastapi.core
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
4
  Summary: Core 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
@@ -15,19 +15,6 @@ Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Requires-Python: >=3.8
17
17
  Description-Content-Type: text/markdown
18
- Requires-Dist: fastapi-slim
19
- Requires-Dist: attrs>=23.2.0
20
- Requires-Dist: pydantic[dotenv]
21
- Requires-Dist: stac_pydantic>=3
22
- Requires-Dist: stac-fastapi.types==3.0.0
23
- Requires-Dist: stac-fastapi.api==3.0.0
24
- Requires-Dist: stac-fastapi.extensions==3.0.0
25
- Requires-Dist: orjson
26
- Requires-Dist: overrides
27
- Requires-Dist: geojson-pydantic
28
- Requires-Dist: pygeofilter==0.2.1
29
- Requires-Dist: typing_extensions==4.8.0
30
- Requires-Dist: jsonschema
31
18
 
32
19
  # stac-fastapi-elasticsearch-opensearch (sfeos)
33
20
 
@@ -415,3 +402,7 @@ Available aggregations are:
415
402
  - geometry_geotile_grid_frequency ([geotile grid](https://opensearch.org/docs/latest/aggregations/bucket/geotile-grid/) on Item.geometry)
416
403
 
417
404
  Support for additional fields and new aggregations can be added in the associated `database_logic.py` file.
405
+
406
+ ## Rate Limiting
407
+
408
+ Rate limiting is an optional security feature that controls API request frequency on a remote address basis. It's enabled by setting the `STAC_FASTAPI_RATE_LIMIT` environment variable, e.g., `500/minute`. This limits each client to 500 requests per minute, helping prevent abuse and maintain API stability. Implementation examples are available in the [examples/rate_limit](examples/rate_limit) directory.
@@ -13,6 +13,7 @@ stac_fastapi/core/base_settings.py
13
13
  stac_fastapi/core/basic_auth.py
14
14
  stac_fastapi/core/core.py
15
15
  stac_fastapi/core/datetime_utils.py
16
+ stac_fastapi/core/rate_limit.py
16
17
  stac_fastapi/core/route_dependencies.py
17
18
  stac_fastapi/core/serializers.py
18
19
  stac_fastapi/core/session.py
@@ -11,3 +11,4 @@ geojson-pydantic
11
11
  pygeofilter==0.2.1
12
12
  typing_extensions==4.8.0
13
13
  jsonschema
14
+ slowapi==0.1.9