stac-fastapi-opensearch 4.0.0__py3-none-any.whl

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.
@@ -0,0 +1 @@
1
+ """opensearch submodule."""
@@ -0,0 +1,150 @@
1
+ """FastAPI application."""
2
+
3
+ import os
4
+ from contextlib import asynccontextmanager
5
+
6
+ from fastapi import FastAPI
7
+
8
+ from stac_fastapi.api.app import StacApi
9
+ from stac_fastapi.api.models import create_get_request_model, create_post_request_model
10
+ from stac_fastapi.core.core import (
11
+ BulkTransactionsClient,
12
+ CoreClient,
13
+ EsAsyncBaseFiltersClient,
14
+ TransactionsClient,
15
+ )
16
+ from stac_fastapi.core.extensions import QueryExtension
17
+ from stac_fastapi.core.extensions.aggregation import (
18
+ EsAggregationExtensionGetRequest,
19
+ EsAggregationExtensionPostRequest,
20
+ EsAsyncAggregationClient,
21
+ )
22
+ from stac_fastapi.core.extensions.fields import FieldsExtension
23
+ from stac_fastapi.core.rate_limit import setup_rate_limit
24
+ from stac_fastapi.core.route_dependencies import get_route_dependencies
25
+ from stac_fastapi.core.session import Session
26
+ from stac_fastapi.extensions.core import (
27
+ AggregationExtension,
28
+ FilterExtension,
29
+ FreeTextExtension,
30
+ SortExtension,
31
+ TokenPaginationExtension,
32
+ TransactionExtension,
33
+ )
34
+ from stac_fastapi.extensions.third_party import BulkTransactionExtension
35
+ from stac_fastapi.opensearch.config import OpensearchSettings
36
+ from stac_fastapi.opensearch.database_logic import (
37
+ DatabaseLogic,
38
+ create_collection_index,
39
+ create_index_templates,
40
+ )
41
+
42
+ settings = OpensearchSettings()
43
+ session = Session.create_from_settings(settings)
44
+
45
+ database_logic = DatabaseLogic()
46
+
47
+ filter_extension = FilterExtension(
48
+ client=EsAsyncBaseFiltersClient(database=database_logic)
49
+ )
50
+ filter_extension.conformance_classes.append(
51
+ "http://www.opengis.net/spec/cql2/1.0/conf/advanced-comparison-operators"
52
+ )
53
+
54
+ aggregation_extension = AggregationExtension(
55
+ client=EsAsyncAggregationClient(
56
+ database=database_logic, session=session, settings=settings
57
+ )
58
+ )
59
+ aggregation_extension.POST = EsAggregationExtensionPostRequest
60
+ aggregation_extension.GET = EsAggregationExtensionGetRequest
61
+
62
+ 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
+ ),
76
+ FieldsExtension(),
77
+ QueryExtension(),
78
+ SortExtension(),
79
+ TokenPaginationExtension(),
80
+ filter_extension,
81
+ FreeTextExtension(),
82
+ ]
83
+
84
+ extensions = [aggregation_extension] + search_extensions
85
+
86
+ database_logic.extensions = [type(ext).__name__ for ext in extensions]
87
+
88
+ post_request_model = create_post_request_model(search_extensions)
89
+
90
+ api = StacApi(
91
+ title=os.getenv("STAC_FASTAPI_TITLE", "stac-fastapi-opensearch"),
92
+ description=os.getenv("STAC_FASTAPI_DESCRIPTION", "stac-fastapi-opensearch"),
93
+ api_version=os.getenv("STAC_FASTAPI_VERSION", "4.0.0"),
94
+ settings=settings,
95
+ extensions=extensions,
96
+ client=CoreClient(
97
+ database=database_logic, session=session, post_request_model=post_request_model
98
+ ),
99
+ search_get_request_model=create_get_request_model(search_extensions),
100
+ search_post_request_model=post_request_model,
101
+ route_dependencies=get_route_dependencies(),
102
+ )
103
+
104
+
105
+ @asynccontextmanager
106
+ async def lifespan(app: FastAPI):
107
+ """Lifespan handler for FastAPI app. Initializes index templates and collections at startup."""
108
+ await create_index_templates()
109
+ await create_collection_index()
110
+ yield
111
+
112
+
113
+ app = api.app
114
+ app.router.lifespan_context = lifespan
115
+ app.root_path = os.getenv("STAC_FASTAPI_ROOT_PATH", "")
116
+ # Add rate limit
117
+ setup_rate_limit(app, rate_limit=os.getenv("STAC_FASTAPI_RATE_LIMIT"))
118
+
119
+
120
+ def run() -> None:
121
+ """Run app from command line using uvicorn if available."""
122
+ try:
123
+ import uvicorn
124
+
125
+ uvicorn.run(
126
+ "stac_fastapi.opensearch.app:app",
127
+ host=settings.app_host,
128
+ port=settings.app_port,
129
+ log_level="info",
130
+ reload=settings.reload,
131
+ )
132
+ except ImportError:
133
+ raise RuntimeError("Uvicorn must be installed in order to use command")
134
+
135
+
136
+ if __name__ == "__main__":
137
+ run()
138
+
139
+
140
+ def create_handler(app):
141
+ """Create a handler to use with AWS Lambda if mangum available."""
142
+ try:
143
+ from mangum import Mangum
144
+
145
+ return Mangum(app)
146
+ except ImportError:
147
+ return None
148
+
149
+
150
+ handler = create_handler(app)
@@ -0,0 +1,121 @@
1
+ """API configuration."""
2
+ import logging
3
+ import os
4
+ import ssl
5
+ from typing import Any, Dict, Set
6
+
7
+ import certifi
8
+ from opensearchpy import AsyncOpenSearch, OpenSearch
9
+
10
+ from stac_fastapi.core.base_settings import ApiBaseSettings
11
+ from stac_fastapi.core.utilities import get_bool_env
12
+ from stac_fastapi.types.config import ApiSettings
13
+
14
+
15
+ def _es_config() -> Dict[str, Any]:
16
+ # Determine the scheme (http or https)
17
+ use_ssl = get_bool_env("ES_USE_SSL", default=True)
18
+ scheme = "https" if use_ssl else "http"
19
+
20
+ # Configure the hosts parameter with the correct scheme
21
+ es_hosts = os.getenv(
22
+ "ES_HOST", "localhost"
23
+ ).strip() # Default to localhost if ES_HOST is not set
24
+ es_port = os.getenv("ES_PORT", "9200") # Default to 9200 if ES_PORT is not set
25
+
26
+ # Validate ES_HOST
27
+ if not es_hosts:
28
+ raise ValueError("ES_HOST environment variable is empty or invalid.")
29
+
30
+ hosts = [f"{scheme}://{host.strip()}:{es_port}" for host in es_hosts.split(",")]
31
+
32
+ # Initialize the configuration dictionary
33
+ config: Dict[str, Any] = {
34
+ "hosts": hosts,
35
+ "headers": {"accept": "application/json", "Content-Type": "application/json"},
36
+ }
37
+
38
+ http_compress = get_bool_env("ES_HTTP_COMPRESS", default=True)
39
+ if http_compress:
40
+ config["http_compress"] = True
41
+
42
+ # Explicitly exclude SSL settings when not using SSL
43
+ if not use_ssl:
44
+ return config
45
+
46
+ # Include SSL settings if using https
47
+ config["ssl_version"] = ssl.PROTOCOL_SSLv23
48
+ config["verify_certs"] = get_bool_env("ES_VERIFY_CERTS", default=True)
49
+
50
+ # Include CA Certificates if verifying certs
51
+ if config["verify_certs"]:
52
+ config["ca_certs"] = os.getenv("CURL_CA_BUNDLE", certifi.where())
53
+
54
+ # Handle authentication
55
+ if (u := os.getenv("ES_USER")) and (p := os.getenv("ES_PASS")):
56
+ config["http_auth"] = (u, p)
57
+
58
+ if api_key := os.getenv("ES_API_KEY"):
59
+ if isinstance(config["headers"], dict):
60
+ headers = {**config["headers"], "x-api-key": api_key}
61
+
62
+ else:
63
+ config["headers"] = {"x-api-key": api_key}
64
+
65
+ config["headers"] = headers
66
+
67
+ return config
68
+
69
+
70
+ _forbidden_fields: Set[str] = {"type"}
71
+
72
+
73
+ class OpensearchSettings(ApiSettings, ApiBaseSettings):
74
+ """
75
+ API settings.
76
+
77
+ Set enable_direct_response via the ENABLE_DIRECT_RESPONSE environment variable.
78
+ If enabled, all API routes use direct response for maximum performance, but ALL FastAPI dependencies (including authentication, custom status codes, and validation) are disabled.
79
+ Default is False for safety.
80
+ """
81
+
82
+ forbidden_fields: Set[str] = _forbidden_fields
83
+ indexed_fields: Set[str] = {"datetime"}
84
+ enable_response_models: bool = False
85
+ enable_direct_response: bool = get_bool_env("ENABLE_DIRECT_RESPONSE", default=False)
86
+
87
+ @property
88
+ def create_client(self):
89
+ """Create es client."""
90
+ return OpenSearch(**_es_config())
91
+
92
+
93
+ class AsyncOpensearchSettings(ApiSettings, ApiBaseSettings):
94
+ """
95
+ API settings.
96
+
97
+ Set enable_direct_response via the ENABLE_DIRECT_RESPONSE environment variable.
98
+ If enabled, all API routes use direct response for maximum performance, but ALL FastAPI dependencies (including authentication, custom status codes, and validation) are disabled.
99
+ Default is False for safety.
100
+ """
101
+
102
+ forbidden_fields: Set[str] = _forbidden_fields
103
+ indexed_fields: Set[str] = {"datetime"}
104
+ enable_response_models: bool = False
105
+ enable_direct_response: bool = get_bool_env("ENABLE_DIRECT_RESPONSE", default=False)
106
+
107
+ @property
108
+ def create_client(self):
109
+ """Create async elasticsearch client."""
110
+ return AsyncOpenSearch(**_es_config())
111
+
112
+
113
+ # Warn at import if direct response is enabled (applies to either settings class)
114
+ if (
115
+ OpensearchSettings().enable_direct_response
116
+ or AsyncOpensearchSettings().enable_direct_response
117
+ ):
118
+ logging.basicConfig(level=logging.WARNING)
119
+ logging.warning(
120
+ "ENABLE_DIRECT_RESPONSE is True: All FastAPI dependencies (including authentication) are DISABLED for all routes!"
121
+ )