Skip to content

Commit 222b5b3

Browse files
committed
use get bool env method
1 parent 2ef6813 commit 222b5b3

File tree

5 files changed

+82
-15
lines changed

5 files changed

+82
-15
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1717

1818
### Added
1919
- Added support for high-performance direct response mode for both Elasticsearch and Opensearch backends, controlled by the `ENABLE_DIRECT_RESPONSE` environment variable. When enabled (`ENABLE_DIRECT_RESPONSE=true`), endpoints return Starlette Response objects directly, bypassing FastAPI's jsonable_encoder and Pydantic serialization for significantly improved performance on large search responses. **Note:** In this mode, all FastAPI dependencies (including authentication, custom status codes, and validation) are disabled for all routes. Default is `false` for safety. A warning is logged at startup if enabled. See [issue #347](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/issues/347) and [PR #359](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/359).
20+
- Added robust tests for the `ENABLE_DIRECT_RESPONSE` environment variable, covering both Elasticsearch and OpenSearch backends. Tests gracefully handle missing backends by attempting to import both configs and skipping if neither is available. [#359](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/359)
2021

2122
### Changed
2223
- Updated test suite to use `httpx.ASGITransport(app=...)` for FastAPI app testing (removes deprecation warning). [#359](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/359)
@@ -25,6 +26,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2526
- Updated all Pydantic models to use `ConfigDict` instead of class-based `Config` for Pydantic v2 compatibility. This resolves deprecation warnings and prepares for Pydantic v3. [#359](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/359)
2627
- Migrated all Pydantic `@root_validator` validators to `@model_validator` for Pydantic v2 compatibility. [#359](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/359)
2728
- Migrated startup event handling from deprecated `@app.on_event("startup")` to FastAPI's recommended lifespan context manager. This removes deprecation warnings and ensures compatibility with future FastAPI versions. [#359](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/359)
29+
- Refactored all boolean environment variable parsing in both Elasticsearch and OpenSearch backends to use the shared `get_bool_env` utility. This ensures robust and consistent handling of environment variables such as `ES_USE_SSL`, `ES_HTTP_COMPRESS`, and `ES_VERIFY_CERTS` across both backends. [#359](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/359)
30+
2831

2932
### Fixed
3033

stac_fastapi/core/stac_fastapi/core/utilities.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,42 @@
33
This module contains functions for transforming geospatial coordinates,
44
such as converting bounding boxes to polygon representations.
55
"""
6+
import logging
7+
import os
68
from typing import Any, Dict, List, Optional, Set, Union
79

810
from stac_fastapi.types.stac import Item
911

1012
MAX_LIMIT = 10000
1113

1214

15+
def get_bool_env(name: str, default: bool = False) -> bool:
16+
"""
17+
Retrieve a boolean value from an environment variable.
18+
19+
Args:
20+
name (str): The name of the environment variable.
21+
default (bool, optional): The default value to use if the variable is not set or unrecognized. Defaults to False.
22+
23+
Returns:
24+
bool: The boolean value parsed from the environment variable.
25+
"""
26+
value = os.getenv(name, str(default).lower())
27+
true_values = ("true", "1", "yes", "y")
28+
false_values = ("false", "0", "no", "n")
29+
if value.lower() in true_values:
30+
return True
31+
elif value.lower() in false_values:
32+
return False
33+
else:
34+
logger = logging.getLogger(__name__)
35+
logger.warning(
36+
f"Environment variable '{name}' has unrecognized value '{value}'. "
37+
f"Expected one of {true_values + false_values}. Using default: {default}"
38+
)
39+
return default
40+
41+
1342
def bbox2polygon(b0: float, b1: float, b2: float, b3: float) -> List[List[List[float]]]:
1443
"""Transform a bounding box represented by its four coordinates `b0`, `b1`, `b2`, and `b3` into a polygon.
1544

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/config.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99

1010
from elasticsearch import AsyncElasticsearch, Elasticsearch # type: ignore
1111
from stac_fastapi.core.base_settings import ApiBaseSettings
12+
from stac_fastapi.core.utilities import get_bool_env
1213
from stac_fastapi.types.config import ApiSettings
1314

1415

1516
def _es_config() -> Dict[str, Any]:
1617
# Determine the scheme (http or https)
17-
use_ssl = os.getenv("ES_USE_SSL", "true").lower() == "true"
18+
use_ssl = get_bool_env("ES_USE_SSL", default=True)
1819
scheme = "https" if use_ssl else "http"
1920

2021
# Configure the hosts parameter with the correct scheme
@@ -45,7 +46,7 @@ def _es_config() -> Dict[str, Any]:
4546

4647
config["headers"] = headers
4748

48-
http_compress = os.getenv("ES_HTTP_COMPRESS", "true").lower() == "true"
49+
http_compress = get_bool_env("ES_HTTP_COMPRESS", default=True)
4950
if http_compress:
5051
config["http_compress"] = True
5152

@@ -55,7 +56,7 @@ def _es_config() -> Dict[str, Any]:
5556

5657
# Include SSL settings if using https
5758
config["ssl_version"] = ssl.TLSVersion.TLSv1_3 # type: ignore
58-
config["verify_certs"] = os.getenv("ES_VERIFY_CERTS", "true").lower() != "false" # type: ignore
59+
config["verify_certs"] = get_bool_env("ES_VERIFY_CERTS", default=True) # type: ignore
5960

6061
# Include CA Certificates if verifying certs
6162
if config["verify_certs"]:
@@ -83,9 +84,7 @@ class ElasticsearchSettings(ApiSettings, ApiBaseSettings):
8384
forbidden_fields: Set[str] = _forbidden_fields
8485
indexed_fields: Set[str] = {"datetime"}
8586
enable_response_models: bool = False
86-
enable_direct_response: bool = (
87-
os.getenv("ENABLE_DIRECT_RESPONSE", "false").lower() == "true"
88-
)
87+
enable_direct_response: bool = get_bool_env("ENABLE_DIRECT_RESPONSE", default=False)
8988

9089
@property
9190
def create_client(self):
@@ -113,9 +112,7 @@ class AsyncElasticsearchSettings(ApiSettings, ApiBaseSettings):
113112
forbidden_fields: Set[str] = _forbidden_fields
114113
indexed_fields: Set[str] = {"datetime"}
115114
enable_response_models: bool = False
116-
enable_direct_response: bool = (
117-
os.getenv("ENABLE_DIRECT_RESPONSE", "false").lower() == "true"
118-
)
115+
enable_direct_response: bool = get_bool_env("ENABLE_DIRECT_RESPONSE", default=False)
119116

120117
@property
121118
def create_client(self):

stac_fastapi/opensearch/stac_fastapi/opensearch/config.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
from opensearchpy import AsyncOpenSearch, OpenSearch
99

1010
from stac_fastapi.core.base_settings import ApiBaseSettings
11+
from stac_fastapi.core.utilities import get_bool_env
1112
from stac_fastapi.types.config import ApiSettings
1213

1314

1415
def _es_config() -> Dict[str, Any]:
1516
# Determine the scheme (http or https)
16-
use_ssl = os.getenv("ES_USE_SSL", "true").lower() == "true"
17+
use_ssl = get_bool_env("ES_USE_SSL", default=True)
1718
scheme = "https" if use_ssl else "http"
1819

1920
# Configure the hosts parameter with the correct scheme
@@ -34,7 +35,7 @@ def _es_config() -> Dict[str, Any]:
3435
"headers": {"accept": "application/json", "Content-Type": "application/json"},
3536
}
3637

37-
http_compress = os.getenv("ES_HTTP_COMPRESS", "true").lower() == "true"
38+
http_compress = get_bool_env("ES_HTTP_COMPRESS", default=True)
3839
if http_compress:
3940
config["http_compress"] = True
4041

@@ -44,7 +45,7 @@ def _es_config() -> Dict[str, Any]:
4445

4546
# Include SSL settings if using https
4647
config["ssl_version"] = ssl.PROTOCOL_SSLv23 # type: ignore
47-
config["verify_certs"] = os.getenv("ES_VERIFY_CERTS", "true").lower() != "false" # type: ignore
48+
config["verify_certs"] = get_bool_env("ES_VERIFY_CERTS", default=True) # type: ignore
4849

4950
# Include CA Certificates if verifying certs
5051
if config["verify_certs"]:
@@ -81,9 +82,7 @@ class OpensearchSettings(ApiSettings, ApiBaseSettings):
8182
forbidden_fields: Set[str] = _forbidden_fields
8283
indexed_fields: Set[str] = {"datetime"}
8384
enable_response_models: bool = False
84-
enable_direct_response: bool = (
85-
os.getenv("ENABLE_DIRECT_RESPONSE", "false").lower() == "true"
86-
)
85+
enable_direct_response: bool = get_bool_env("ENABLE_DIRECT_RESPONSE", default=False)
8786

8887
@property
8988
def create_client(self):
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import importlib
2+
3+
import pytest
4+
5+
6+
def get_settings_class():
7+
"""
8+
Try to import ElasticsearchSettings or OpenSearchSettings, whichever is available.
9+
Returns a tuple: (settings_class, config_module)
10+
"""
11+
try:
12+
config = importlib.import_module("stac_fastapi.elasticsearch.config")
13+
importlib.reload(config)
14+
return config.ElasticsearchSettings, config
15+
except ModuleNotFoundError:
16+
try:
17+
config = importlib.import_module("stac_fastapi.opensearch.config")
18+
importlib.reload(config)
19+
return config.OpensearchSettings, config
20+
except ModuleNotFoundError:
21+
pytest.skip(
22+
"Neither Elasticsearch nor OpenSearch config module is available."
23+
)
24+
25+
26+
def test_enable_direct_response_true(monkeypatch):
27+
"""Test that ENABLE_DIRECT_RESPONSE env var enables direct response config."""
28+
monkeypatch.setenv("ENABLE_DIRECT_RESPONSE", "true")
29+
settings_class, _ = get_settings_class()
30+
settings = settings_class()
31+
assert settings.enable_direct_response is True
32+
33+
34+
def test_enable_direct_response_false(monkeypatch):
35+
"""Test that ENABLE_DIRECT_RESPONSE env var disables direct response config."""
36+
monkeypatch.setenv("ENABLE_DIRECT_RESPONSE", "false")
37+
settings_class, _ = get_settings_class()
38+
settings = settings_class()
39+
assert settings.enable_direct_response is False

0 commit comments

Comments
 (0)