stac-fastapi.core 3.0.0a3__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 (31) hide show
  1. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/PKG-INFO +5 -14
  2. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/README.md +5 -1
  3. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/setup.py +4 -3
  4. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/core.py +17 -11
  5. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/extensions/aggregation.py +39 -15
  6. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/extensions/filter.py +25 -15
  7. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/models/links.py +8 -0
  8. stac-fastapi.core-3.2.0/stac_fastapi/core/rate_limit.py +44 -0
  9. stac-fastapi.core-3.2.0/stac_fastapi/core/version.py +2 -0
  10. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi.core.egg-info/PKG-INFO +5 -14
  11. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi.core.egg-info/SOURCES.txt +1 -0
  12. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi.core.egg-info/requires.txt +4 -3
  13. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi.core.egg-info/top_level.txt +0 -1
  14. stac_fastapi_core-3.0.0a3/stac_fastapi/core/version.py +0 -2
  15. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/setup.cfg +0 -0
  16. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/__init__.py +0 -0
  17. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/base_database_logic.py +0 -0
  18. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/base_settings.py +0 -0
  19. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/basic_auth.py +0 -0
  20. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/datetime_utils.py +0 -0
  21. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/extensions/__init__.py +0 -0
  22. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/extensions/fields.py +0 -0
  23. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/extensions/query.py +0 -0
  24. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/models/__init__.py +0 -0
  25. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/models/search.py +0 -0
  26. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/route_dependencies.py +0 -0
  27. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/serializers.py +0 -0
  28. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/session.py +0 -0
  29. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi/core/utilities.py +0 -0
  30. {stac_fastapi_core-3.0.0a3 → stac-fastapi.core-3.2.0}/stac_fastapi.core.egg-info/dependency_links.txt +0 -0
  31. {stac_fastapi_core-3.0.0a3 → 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.0.0a3
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.0a4
23
- Requires-Dist: stac-fastapi.api==3.0.0a4
24
- Requires-Dist: stac-fastapi.extensions==3.0.0a4
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.
@@ -10,15 +10,16 @@ install_requires = [
10
10
  "attrs>=23.2.0",
11
11
  "pydantic[dotenv]",
12
12
  "stac_pydantic>=3",
13
- "stac-fastapi.types==3.0.0a4",
14
- "stac-fastapi.api==3.0.0a4",
15
- "stac-fastapi.extensions==3.0.0a4",
13
+ "stac-fastapi.types==3.0.0",
14
+ "stac-fastapi.api==3.0.0",
15
+ "stac-fastapi.extensions==3.0.0",
16
16
  "orjson",
17
17
  "overrides",
18
18
  "geojson-pydantic",
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(
@@ -1,6 +1,6 @@
1
1
  """Core client."""
2
+
2
3
  import logging
3
- import re
4
4
  from datetime import datetime as datetime_type
5
5
  from datetime import timezone
6
6
  from enum import Enum
@@ -279,8 +279,8 @@ class CoreClient(AsyncBaseCoreClient):
279
279
  collection_id: str,
280
280
  bbox: Optional[BBox] = None,
281
281
  datetime: Optional[DateTimeType] = None,
282
- limit: int = 10,
283
- token: str = None,
282
+ limit: Optional[int] = 10,
283
+ token: Optional[str] = None,
284
284
  **kwargs,
285
285
  ) -> stac_types.ItemCollection:
286
286
  """Read items from a specific collection in the database.
@@ -302,6 +302,8 @@ class CoreClient(AsyncBaseCoreClient):
302
302
  Exception: If any error occurs while reading the items from the database.
303
303
  """
304
304
  request: Request = kwargs["request"]
305
+ token = request.query_params.get("token")
306
+
305
307
  base_url = str(request.base_url)
306
308
 
307
309
  collection = await self.get_collection(
@@ -455,6 +457,7 @@ class CoreClient(AsyncBaseCoreClient):
455
457
  token: Optional[str] = None,
456
458
  fields: Optional[List[str]] = None,
457
459
  sortby: Optional[str] = None,
460
+ q: Optional[List[str]] = None,
458
461
  intersects: Optional[str] = None,
459
462
  filter: Optional[str] = None,
460
463
  filter_lang: Optional[str] = None,
@@ -472,6 +475,7 @@ class CoreClient(AsyncBaseCoreClient):
472
475
  token (Optional[str]): Access token to use when searching the catalog.
473
476
  fields (Optional[List[str]]): Fields to include or exclude from the results.
474
477
  sortby (Optional[str]): Sorting options for the results.
478
+ q (Optional[List[str]]): Free text query to filter the results.
475
479
  intersects (Optional[str]): GeoJSON geometry to search in.
476
480
  kwargs: Additional parameters to be passed to the API.
477
481
 
@@ -488,16 +492,9 @@ class CoreClient(AsyncBaseCoreClient):
488
492
  "limit": limit,
489
493
  "token": token,
490
494
  "query": orjson.loads(query) if query else query,
495
+ "q": q,
491
496
  }
492
497
 
493
- # this is borrowed from stac-fastapi-pgstac
494
- # Kludgy fix because using factory does not allow alias for filter-lan
495
- query_params = str(request.query_params)
496
- if filter_lang is None:
497
- match = re.search(r"filter-lang=([a-z0-9-]+)", query_params, re.IGNORECASE)
498
- if match:
499
- filter_lang = match.group(1)
500
-
501
498
  if datetime:
502
499
  base_args["datetime"] = self._format_datetime_range(datetime)
503
500
 
@@ -606,6 +603,15 @@ class CoreClient(AsyncBaseCoreClient):
606
603
  status_code=400, detail=f"Error with cql2_json filter: {e}"
607
604
  )
608
605
 
606
+ if hasattr(search_request, "q"):
607
+ free_text_queries = getattr(search_request, "q", None)
608
+ try:
609
+ search = self.database.apply_free_text_filter(search, free_text_queries)
610
+ except Exception as e:
611
+ raise HTTPException(
612
+ status_code=400, detail=f"Error with free text query: {e}"
613
+ )
614
+
609
615
  sort = None
610
616
  if search_request.sortby:
611
617
  sort = self.database.populate_sort(search_request.sortby)
@@ -1,6 +1,5 @@
1
1
  """Request model for the Aggregation extension."""
2
2
 
3
- import re
4
3
  from datetime import datetime
5
4
  from datetime import datetime as datetime_type
6
5
  from typing import Dict, List, Literal, Optional, Union
@@ -51,6 +50,7 @@ class EsAggregationExtensionGetRequest(
51
50
  centroid_geotile_grid_frequency_precision: Optional[int] = attr.ib(default=None)
52
51
  geometry_geohash_grid_frequency_precision: Optional[int] = attr.ib(default=None)
53
52
  geometry_geotile_grid_frequency_precision: Optional[int] = attr.ib(default=None)
53
+ datetime_frequency_interval: Optional[str] = attr.ib(default=None)
54
54
 
55
55
 
56
56
  class EsAggregationExtensionPostRequest(
@@ -63,6 +63,7 @@ class EsAggregationExtensionPostRequest(
63
63
  centroid_geotile_grid_frequency_precision: Optional[int] = None
64
64
  geometry_geohash_grid_frequency_precision: Optional[int] = None
65
65
  geometry_geotile_grid_frequency_precision: Optional[int] = None
66
+ datetime_frequency_interval: Optional[str] = None
66
67
 
67
68
 
68
69
  @attr.s
@@ -125,6 +126,8 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
125
126
  MAX_GEOHASH_PRECISION = 12
126
127
  MAX_GEOHEX_PRECISION = 15
127
128
  MAX_GEOTILE_PRECISION = 29
129
+ SUPPORTED_DATETIME_INTERVAL = {"day", "month", "year"}
130
+ DEFAULT_DATETIME_INTERVAL = "month"
128
131
 
129
132
  async def get_aggregations(self, collection_id: Optional[str] = None, **kwargs):
130
133
  """Get the available aggregations for a catalog or collection defined in the STAC JSON. If no aggregations, default aggregations are used."""
@@ -144,7 +147,7 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
144
147
  {
145
148
  "rel": "self",
146
149
  "type": "application/json",
147
- "href": urljoin(collection_endpoint, "aggregations"),
150
+ "href": urljoin(collection_endpoint + "/", "aggregations"),
148
151
  },
149
152
  ]
150
153
  )
@@ -183,6 +186,30 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
183
186
  else:
184
187
  return min_value
185
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
+
186
213
  @staticmethod
187
214
  def _return_date(
188
215
  interval: Optional[Union[DateTimeType, str]]
@@ -271,7 +298,7 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
271
298
  def get_filter(self, filter, filter_lang):
272
299
  """Format the filter parameter in cql2-json or cql2-text."""
273
300
  if filter_lang == "cql2-text":
274
- return orjson.loads(unquote_plus(to_cql2(parse_cql2_text(filter))))
301
+ return orjson.loads(to_cql2(parse_cql2_text(filter)))
275
302
  elif filter_lang == "cql2-json":
276
303
  if isinstance(filter, str):
277
304
  return orjson.loads(unquote_plus(filter))
@@ -320,6 +347,7 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
320
347
  centroid_geotile_grid_frequency_precision: Optional[int] = None,
321
348
  geometry_geohash_grid_frequency_precision: Optional[int] = None,
322
349
  geometry_geotile_grid_frequency_precision: Optional[int] = None,
350
+ datetime_frequency_interval: Optional[str] = None,
323
351
  **kwargs,
324
352
  ) -> Union[Dict, Exception]:
325
353
  """Get aggregations from the database."""
@@ -340,6 +368,7 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
340
368
  "centroid_geotile_grid_frequency_precision": centroid_geotile_grid_frequency_precision,
341
369
  "geometry_geohash_grid_frequency_precision": geometry_geohash_grid_frequency_precision,
342
370
  "geometry_geotile_grid_frequency_precision": geometry_geotile_grid_frequency_precision,
371
+ "datetime_frequency_interval": datetime_frequency_interval,
343
372
  }
344
373
 
345
374
  if collection_id:
@@ -351,18 +380,6 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
351
380
  if datetime:
352
381
  base_args["datetime"] = self._format_datetime_range(datetime)
353
382
 
354
- # this is borrowed from stac-fastapi-pgstac
355
- # Kludgy fix because using factory does not allow alias for filter-lang
356
- # If the value is the default, check if the request is different.
357
- query_params = str(request.query_params)
358
- if filter_lang is None:
359
- match = re.search(
360
- r"filter-lang=([a-z0-9-]+)", query_params, re.IGNORECASE
361
- )
362
- if match:
363
- filter_lang = match.group(1)
364
- else:
365
- filter_lang = "cql2-text"
366
383
  if filter:
367
384
  base_args["filter"] = self.get_filter(filter, filter_lang)
368
385
  aggregate_request = EsAggregationExtensionPostRequest(**base_args)
@@ -447,6 +464,8 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
447
464
  status_code=400,
448
465
  detail=f"Aggregation {agg_name} not supported at catalog level",
449
466
  )
467
+
468
+ if aggregate_request.filter:
450
469
  try:
451
470
  search = self.database.apply_cql2_filter(
452
471
  search, aggregate_request.filter
@@ -486,6 +505,10 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
486
505
  self.MAX_GEOTILE_PRECISION,
487
506
  )
488
507
 
508
+ datetime_frequency_interval = self.extract_date_histogram_interval(
509
+ aggregate_request.datetime_frequency_interval,
510
+ )
511
+
489
512
  try:
490
513
  db_response = await self.database.aggregate(
491
514
  collections,
@@ -496,6 +519,7 @@ class EsAsyncAggregationClient(AsyncBaseAggregationClient):
496
519
  centroid_geotile_grid_precision,
497
520
  geometry_geohash_grid_precision,
498
521
  geometry_geotile_grid_precision,
522
+ datetime_frequency_interval,
499
523
  )
500
524
  except Exception as error:
501
525
  if not isinstance(error, IndexError):
@@ -17,31 +17,41 @@ import re
17
17
  from enum import Enum
18
18
  from typing import Any, Dict
19
19
 
20
+ _cql2_like_patterns = re.compile(r"\\.|[%_]|\\$")
21
+ _valid_like_substitutions = {
22
+ "\\\\": "\\",
23
+ "\\%": "%",
24
+ "\\_": "_",
25
+ "%": "*",
26
+ "_": "?",
27
+ }
28
+
29
+
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
+
20
37
 
21
38
  def cql2_like_to_es(string: str) -> str:
22
39
  """
23
- Convert CQL2 wildcard characters to Elasticsearch wildcard characters. Specifically, it converts '_' to '?' and '%' to '*', handling escape characters properly.
40
+ Convert CQL2 "LIKE" characters to Elasticsearch "wildcard" characters.
24
41
 
25
42
  Args:
26
43
  string (str): The string containing CQL2 wildcard characters.
27
44
 
28
45
  Returns:
29
46
  str: The converted string with Elasticsearch compatible wildcards.
47
+
48
+ Raises:
49
+ ValueError: If an invalid escape sequence is encountered.
30
50
  """
31
- # Translate '%' and '_' only if they are not preceded by a backslash '\'
32
- percent_pattern = r"(?<!\\)%"
33
- underscore_pattern = r"(?<!\\)_"
34
- # Remove the escape character before '%' or '_'
35
- escape_pattern = r"\\(?=[_%])"
36
-
37
- # Replace '%' with '*' for broad wildcard matching
38
- string = re.sub(percent_pattern, "*", string)
39
- # Replace '_' with '?' for single character wildcard matching
40
- string = re.sub(underscore_pattern, "?", string)
41
- # Remove the escape character used in the CQL2 format
42
- string = re.sub(escape_pattern, "", string)
43
-
44
- return string
51
+ return _cql2_like_patterns.sub(
52
+ repl=_replace_like_patterns,
53
+ string=string,
54
+ )
45
55
 
46
56
 
47
57
  class LogicalOp(str, Enum):
@@ -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")
@@ -0,0 +1,2 @@
1
+ """library version."""
2
+ __version__ = "3.2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stac-fastapi.core
3
- Version: 3.0.0a3
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.0a4
23
- Requires-Dist: stac-fastapi.api==3.0.0a4
24
- Requires-Dist: stac-fastapi.extensions==3.0.0a4
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
@@ -2,12 +2,13 @@ fastapi-slim
2
2
  attrs>=23.2.0
3
3
  pydantic[dotenv]
4
4
  stac_pydantic>=3
5
- stac-fastapi.types==3.0.0a4
6
- stac-fastapi.api==3.0.0a4
7
- stac-fastapi.extensions==3.0.0a4
5
+ stac-fastapi.types==3.0.0
6
+ stac-fastapi.api==3.0.0
7
+ stac-fastapi.extensions==3.0.0
8
8
  orjson
9
9
  overrides
10
10
  geojson-pydantic
11
11
  pygeofilter==0.2.1
12
12
  typing_extensions==4.8.0
13
13
  jsonschema
14
+ slowapi==0.1.9
@@ -1,2 +0,0 @@
1
- """library version."""
2
- __version__ = "3.0.0a3"