From e91060f5ef379b9ce3c5bf7da19975194fafc214 Mon Sep 17 00:00:00 2001 From: Yuri Zmytrakov Date: Mon, 8 Sep 2025 11:42:51 +0200 Subject: [PATCH] feat: Add env var ENV_MAX_LIMIT for items returned Adds env var ENV_MAX_LIMIT, which allows overriding the MAX_LIMIT value, which controls the ?limit URL parameter used in STAC API queries. --- CHANGELOG.md | 1 + README.md | 1 + .../core/stac_fastapi/core/utilities.py | 10 +++++++- .../elasticsearch/database_logic.py | 4 ++-- .../stac_fastapi/opensearch/database_logic.py | 4 ++-- stac_fastapi/tests/api/test_api.py | 24 +++++++++++++++++++ 6 files changed, 39 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77e74c57c..d39b25ab0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Simplified Patch class and updated patch script creation including adding nest creation for merge patch [#420](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/420) - Added default environment variable `STAC_ITEM_LIMIT` to SFEOS for result limiting of returned items and STAC collections [#419](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/419) +- Added the `ENV_MAX_LIMIT` environment variable to SFEOS, allowing overriding of the `MAX_LIMIT`, which controls the `?limit` parameter for returned items and STAC collections. [#434](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/434) ## [v6.2.0] - 2025-08-27 diff --git a/README.md b/README.md index a864b3d5e..b204d81a3 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,7 @@ You can customize additional settings in your `.env` file: | `DATABASE_REFRESH` | Controls whether database operations refresh the index immediately after changes. If set to `true`, changes will be immediately searchable. If set to `false`, changes may not be immediately visible but can improve performance for bulk operations. If set to `wait_for`, changes will wait for the next refresh cycle to become visible. | `false` | Optional | | `ENABLE_TRANSACTIONS_EXTENSIONS` | Enables or disables the Transactions and Bulk Transactions API extensions. If set to `false`, the POST `/collections` route and related transaction endpoints (including bulk transaction operations) will be unavailable in the API. This is useful for deployments where mutating the catalog via the API should be prevented. | `true` | Optional | | `STAC_ITEM_LIMIT` | Sets the environment variable for result limiting to SFEOS for the number of returned items and STAC collections. | `10` | Optional | +| `ENV_MAX_LIMIT` | Configures the environment variable in SFEOS to override the default `MAX_LIMIT`, which controls the limit parameter for returned items and STAC collections. | `10,000` | Optional | > [!NOTE] > The variables `ES_HOST`, `ES_PORT`, `ES_USE_SSL`, `ES_VERIFY_CERTS` and `ES_TIMEOUT` apply to both Elasticsearch and OpenSearch backends, so there is no need to rename the key names to `OS_` even if you're using OpenSearch. diff --git a/stac_fastapi/core/stac_fastapi/core/utilities.py b/stac_fastapi/core/stac_fastapi/core/utilities.py index de6536567..c54348af0 100644 --- a/stac_fastapi/core/stac_fastapi/core/utilities.py +++ b/stac_fastapi/core/stac_fastapi/core/utilities.py @@ -10,7 +10,15 @@ from stac_fastapi.types.stac import Item -MAX_LIMIT = 10000 + +def get_max_limit(): + """ + Retrieve a MAX_LIMIT value from an environment variable. + + Returns: + int: The int value parsed from the environment variable. + """ + return int(os.getenv("ENV_MAX_LIMIT", 10000)) def get_bool_env(name: str, default: Union[bool, str] = False) -> bool: diff --git a/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/database_logic.py b/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/database_logic.py index 3e07d2517..e852ed774 100644 --- a/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/database_logic.py +++ b/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/database_logic.py @@ -17,7 +17,7 @@ from stac_fastapi.core.base_database_logic import BaseDatabaseLogic from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer -from stac_fastapi.core.utilities import MAX_LIMIT, bbox2polygon +from stac_fastapi.core.utilities import bbox2polygon, get_max_limit from stac_fastapi.elasticsearch.config import AsyncElasticsearchSettings from stac_fastapi.elasticsearch.config import ( ElasticsearchSettings as SyncElasticsearchSettings, @@ -543,7 +543,7 @@ async def execute_search( index_param = ITEM_INDICES query = add_collections_to_body(collection_ids, query) - max_result_window = MAX_LIMIT + max_result_window = get_max_limit() size_limit = min(limit + 1, max_result_window) diff --git a/stac_fastapi/opensearch/stac_fastapi/opensearch/database_logic.py b/stac_fastapi/opensearch/stac_fastapi/opensearch/database_logic.py index 0e6b7b6dc..e54397bab 100644 --- a/stac_fastapi/opensearch/stac_fastapi/opensearch/database_logic.py +++ b/stac_fastapi/opensearch/stac_fastapi/opensearch/database_logic.py @@ -16,7 +16,7 @@ from stac_fastapi.core.base_database_logic import BaseDatabaseLogic from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer -from stac_fastapi.core.utilities import MAX_LIMIT, bbox2polygon +from stac_fastapi.core.utilities import bbox2polygon, get_max_limit from stac_fastapi.extensions.core.transaction.request import ( PartialCollection, PartialItem, @@ -540,7 +540,7 @@ async def execute_search( search_body["sort"] = sort if sort else DEFAULT_SORT - max_result_window = MAX_LIMIT + max_result_window = get_max_limit() size_limit = min(limit + 1, max_result_window) diff --git a/stac_fastapi/tests/api/test_api.py b/stac_fastapi/tests/api/test_api.py index c07efbd2e..b16efd4c4 100644 --- a/stac_fastapi/tests/api/test_api.py +++ b/stac_fastapi/tests/api/test_api.py @@ -1540,3 +1540,27 @@ async def test_collection_items_limit_env_variable( assert resp.status_code == 200 resp_json = resp.json() assert int(limit) == len(resp_json["features"]) + + +@pytest.mark.asyncio +async def test_search_max_item_limit( + app_client, load_test_data, txn_client, monkeypatch +): + limit = "10" + monkeypatch.setenv("ENV_MAX_LIMIT", limit) + + test_collection = load_test_data("test_collection.json") + await create_collection(txn_client, test_collection) + + item = load_test_data("test_item.json") + + for i in range(20): + test_item = item.copy() + test_item["id"] = f"test-item-collection-{i}" + await create_item(txn_client, test_item) + + resp = await app_client.get("/search", params={"limit": 20}) + + assert resp.status_code == 200 + resp_json = resp.json() + assert int(limit) == len(resp_json["features"])