Skip to content

Commit bc1c3f7

Browse files
authored
Enable transactions extensions env var (#374)
**Related Issue(s):** - #373 - #263 **Description:** - Added the `ENABLE_TRANSACTIONS_EXTENSIONS` environment variable to enable or disable the Transactions and Bulk Transactions API extensions. When set to `false`, endpoints provided by `TransactionsClient` and `BulkTransactionsClient` are not available. This allows for flexible deployment scenarios and improved API control. **PR Checklist:** - [x] Code is formatted and linted (run `pre-commit run --all-files`) - [x] Tests pass (run `make test`) - [x] Documentation has been updated to reflect changes, if applicable - [x] Changes are added to the changelog
1 parent c1d9ca8 commit bc1c3f7

File tree

6 files changed

+142
-27
lines changed

6 files changed

+142
-27
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1212
- Added configurable landing page ID `STAC_FASTAPI_LANDING_PAGE_ID` [#352](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/352)
1313
- Added support for `S_CONTAINS`, `S_WITHIN`, `S_DISJOINT` spatial filter operations [#371](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/issues/371)
1414
- Introduced the `DATABASE_REFRESH` environment variable to control 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. [#370](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/370)
15+
- Added the `ENABLE_TRANSACTIONS_EXTENSIONS` environment variable to enable or disable the Transactions and Bulk Transactions API extensions. When set to `false`, endpoints provided by `TransactionsClient` and `BulkTransactionsClient` are not available. This allows for flexible deployment scenarios and improved API control. [#374](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/374)
1516

1617
### Changed
1718

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ You can customize additional settings in your `.env` file:
117117
| `ENABLE_DIRECT_RESPONSE` | Enable direct response for maximum performance (disables all FastAPI dependencies, including authentication, custom status codes, and validation) | `false` | Optional
118118
| `RAISE_ON_BULK_ERROR` | Controls whether bulk insert operations raise exceptions on errors. If set to `true`, the operation will stop and raise an exception when an error occurs. If set to `false`, errors will be logged, and the operation will continue. **Note:** STAC Item and ItemCollection validation errors will always raise, regardless of this flag. | `false` Optional |
119119
| `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 |
120+
| `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 |
120121

121122
> [!NOTE]
122123
> The variables `ES_HOST`, `ES_PORT`, `ES_USE_SSL`, and `ES_VERIFY_CERTS` 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.

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py

+29-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""FastAPI application."""
22

3+
import logging
34
import os
45
from contextlib import asynccontextmanager
56

@@ -23,6 +24,7 @@
2324
from stac_fastapi.core.rate_limit import setup_rate_limit
2425
from stac_fastapi.core.route_dependencies import get_route_dependencies
2526
from stac_fastapi.core.session import Session
27+
from stac_fastapi.core.utilities import get_bool_env
2628
from stac_fastapi.elasticsearch.config import ElasticsearchSettings
2729
from stac_fastapi.elasticsearch.database_logic import (
2830
DatabaseLogic,
@@ -39,6 +41,12 @@
3941
)
4042
from stac_fastapi.extensions.third_party import BulkTransactionExtension
4143

44+
logging.basicConfig(level=logging.INFO)
45+
logger = logging.getLogger(__name__)
46+
47+
TRANSACTIONS_EXTENSIONS = get_bool_env("ENABLE_TRANSACTIONS_EXTENSIONS", default=True)
48+
logger.info("TRANSACTIONS_EXTENSIONS is set to %s", TRANSACTIONS_EXTENSIONS)
49+
4250
settings = ElasticsearchSettings()
4351
session = Session.create_from_settings(settings)
4452

@@ -60,19 +68,6 @@
6068
aggregation_extension.GET = EsAggregationExtensionGetRequest
6169

6270
search_extensions = [
63-
TransactionExtension(
64-
client=TransactionsClient(
65-
database=database_logic, session=session, settings=settings
66-
),
67-
settings=settings,
68-
),
69-
BulkTransactionExtension(
70-
client=BulkTransactionsClient(
71-
database=database_logic,
72-
session=session,
73-
settings=settings,
74-
)
75-
),
7671
FieldsExtension(),
7772
QueryExtension(),
7873
SortExtension(),
@@ -81,6 +76,27 @@
8176
FreeTextExtension(),
8277
]
8378

79+
if TRANSACTIONS_EXTENSIONS:
80+
search_extensions.insert(
81+
0,
82+
TransactionExtension(
83+
client=TransactionsClient(
84+
database=database_logic, session=session, settings=settings
85+
),
86+
settings=settings,
87+
),
88+
)
89+
search_extensions.insert(
90+
1,
91+
BulkTransactionExtension(
92+
client=BulkTransactionsClient(
93+
database=database_logic,
94+
session=session,
95+
settings=settings,
96+
)
97+
),
98+
)
99+
84100
extensions = [aggregation_extension] + search_extensions
85101

86102
database_logic.extensions = [type(ext).__name__ for ext in extensions]

stac_fastapi/opensearch/stac_fastapi/opensearch/app.py

+30-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""FastAPI application."""
22

3+
import logging
34
import os
45
from contextlib import asynccontextmanager
56

@@ -23,6 +24,7 @@
2324
from stac_fastapi.core.rate_limit import setup_rate_limit
2425
from stac_fastapi.core.route_dependencies import get_route_dependencies
2526
from stac_fastapi.core.session import Session
27+
from stac_fastapi.core.utilities import get_bool_env
2628
from stac_fastapi.extensions.core import (
2729
AggregationExtension,
2830
FilterExtension,
@@ -39,6 +41,12 @@
3941
create_index_templates,
4042
)
4143

44+
logging.basicConfig(level=logging.INFO)
45+
logger = logging.getLogger(__name__)
46+
47+
TRANSACTIONS_EXTENSIONS = get_bool_env("ENABLE_TRANSACTIONS_EXTENSIONS", default=True)
48+
logger.info("TRANSACTIONS_EXTENSIONS is set to %s", TRANSACTIONS_EXTENSIONS)
49+
4250
settings = OpensearchSettings()
4351
session = Session.create_from_settings(settings)
4452

@@ -60,19 +68,6 @@
6068
aggregation_extension.GET = EsAggregationExtensionGetRequest
6169

6270
search_extensions = [
63-
TransactionExtension(
64-
client=TransactionsClient(
65-
database=database_logic, session=session, settings=settings
66-
),
67-
settings=settings,
68-
),
69-
BulkTransactionExtension(
70-
client=BulkTransactionsClient(
71-
database=database_logic,
72-
session=session,
73-
settings=settings,
74-
)
75-
),
7671
FieldsExtension(),
7772
QueryExtension(),
7873
SortExtension(),
@@ -81,6 +76,28 @@
8176
FreeTextExtension(),
8277
]
8378

79+
80+
if TRANSACTIONS_EXTENSIONS:
81+
search_extensions.insert(
82+
0,
83+
TransactionExtension(
84+
client=TransactionsClient(
85+
database=database_logic, session=session, settings=settings
86+
),
87+
settings=settings,
88+
),
89+
)
90+
search_extensions.insert(
91+
1,
92+
BulkTransactionExtension(
93+
client=BulkTransactionsClient(
94+
database=database_logic,
95+
session=session,
96+
settings=settings,
97+
)
98+
),
99+
)
100+
84101
extensions = [aggregation_extension] + search_extensions
85102

86103
database_logic.extensions = [type(ext).__name__ for ext in extensions]

stac_fastapi/tests/conftest.py

+47
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
)
2828
from stac_fastapi.core.rate_limit import setup_rate_limit
2929
from stac_fastapi.core.route_dependencies import get_route_dependencies
30+
from stac_fastapi.core.utilities import get_bool_env
3031

3132
if os.getenv("BACKEND", "elasticsearch").lower() == "opensearch":
3233
from stac_fastapi.opensearch.config import AsyncOpensearchSettings as AsyncSettings
@@ -479,3 +480,49 @@ async def route_dependencies_client(route_dependencies_app):
479480
base_url="http://test-server",
480481
) as c:
481482
yield c
483+
484+
485+
def build_test_app():
486+
TRANSACTIONS_EXTENSIONS = get_bool_env(
487+
"ENABLE_TRANSACTIONS_EXTENSIONS", default=True
488+
)
489+
settings = AsyncSettings()
490+
aggregation_extension = AggregationExtension(
491+
client=EsAsyncAggregationClient(
492+
database=database, session=None, settings=settings
493+
)
494+
)
495+
aggregation_extension.POST = EsAggregationExtensionPostRequest
496+
aggregation_extension.GET = EsAggregationExtensionGetRequest
497+
search_extensions = [
498+
SortExtension(),
499+
FieldsExtension(),
500+
QueryExtension(),
501+
TokenPaginationExtension(),
502+
FilterExtension(),
503+
FreeTextExtension(),
504+
]
505+
if TRANSACTIONS_EXTENSIONS:
506+
search_extensions.insert(
507+
0,
508+
TransactionExtension(
509+
client=TransactionsClient(
510+
database=database, session=None, settings=settings
511+
),
512+
settings=settings,
513+
),
514+
)
515+
extensions = [aggregation_extension] + search_extensions
516+
post_request_model = create_post_request_model(search_extensions)
517+
return StacApi(
518+
settings=settings,
519+
client=CoreClient(
520+
database=database,
521+
session=None,
522+
extensions=extensions,
523+
post_request_model=post_request_model,
524+
),
525+
extensions=extensions,
526+
search_get_request_model=create_get_request_model(search_extensions),
527+
search_post_request_model=post_request_model,
528+
).app

stac_fastapi/tests/resources/test_collection.py

+34-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import copy
2+
import os
23
import uuid
34

45
import pytest
6+
from httpx import AsyncClient
57
from stac_pydantic import api
68

7-
from ..conftest import create_collection, delete_collections_and_items, refresh_indices
9+
from ..conftest import (
10+
build_test_app,
11+
create_collection,
12+
delete_collections_and_items,
13+
refresh_indices,
14+
)
815

916
CORE_COLLECTION_PROPS = [
1017
"id",
@@ -36,6 +43,32 @@ async def test_create_and_delete_collection(app_client, load_test_data):
3643
assert resp.status_code == 204
3744

3845

46+
@pytest.mark.asyncio
47+
async def test_create_collection_transactions_extension(load_test_data):
48+
test_collection = load_test_data("test_collection.json")
49+
test_collection["id"] = "test"
50+
51+
os.environ["ENABLE_TRANSACTIONS_EXTENSIONS"] = "false"
52+
app_disabled = build_test_app()
53+
async with AsyncClient(app=app_disabled, base_url="http://test") as client:
54+
resp = await client.post("/collections", json=test_collection)
55+
assert resp.status_code in (
56+
404,
57+
405,
58+
501,
59+
), f"Expected failure, got {resp.status_code}"
60+
61+
os.environ["ENABLE_TRANSACTIONS_EXTENSIONS"] = "true"
62+
app_enabled = build_test_app()
63+
async with AsyncClient(app=app_enabled, base_url="http://test") as client:
64+
resp = await client.post("/collections", json=test_collection)
65+
assert resp.status_code == 201
66+
resp = await client.delete(f"/collections/{test_collection['id']}")
67+
assert resp.status_code == 204
68+
69+
del os.environ["ENABLE_TRANSACTIONS_EXTENSIONS"]
70+
71+
3972
@pytest.mark.asyncio
4073
async def test_create_collection_conflict(app_client, ctx):
4174
"""Test creation of a collection which already exists"""

0 commit comments

Comments
 (0)