stac-fastapi.api 2.4.8__tar.gz → 2.5.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.
- stac_fastapi_api-2.5.0/PKG-INFO +39 -0
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/setup.py +8 -1
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi/api/app.py +24 -23
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi/api/middleware.py +2 -1
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi/api/models.py +5 -3
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi/api/routes.py +16 -15
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi/api/version.py +1 -1
- stac_fastapi_api-2.5.0/stac_fastapi.api.egg-info/PKG-INFO +39 -0
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi.api.egg-info/SOURCES.txt +3 -1
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi.api.egg-info/requires.txt +4 -1
- stac_fastapi_api-2.5.0/tests/test_api.py +140 -0
- stac_fastapi_api-2.5.0/tests/test_middleware.py +168 -0
- stac-fastapi.api-2.4.8/PKG-INFO +0 -18
- stac-fastapi.api-2.4.8/stac_fastapi.api.egg-info/PKG-INFO +0 -18
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/README.md +0 -0
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/setup.cfg +0 -0
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi/api/__init__.py +0 -0
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi/api/config.py +0 -0
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi/api/errors.py +0 -0
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi/api/openapi.py +0 -0
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi.api.egg-info/dependency_links.txt +0 -0
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi.api.egg-info/not-zip-safe +0 -0
- {stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi.api.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: stac-fastapi.api
|
|
3
|
+
Version: 2.5.0
|
|
4
|
+
Summary: An implementation of STAC API based on the FastAPI framework.
|
|
5
|
+
Home-page: https://github.com/stac-utils/stac-fastapi
|
|
6
|
+
Author: Arturo Engineering
|
|
7
|
+
Author-email: engineering@arturo.ai
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: STAC FastAPI COG
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Information Technology
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: attrs
|
|
22
|
+
Requires-Dist: pydantic[dotenv]<2
|
|
23
|
+
Requires-Dist: stac_pydantic==2.0.*
|
|
24
|
+
Requires-Dist: brotli_asgi
|
|
25
|
+
Requires-Dist: stac-fastapi.types
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: httpx; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
31
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
32
|
+
Requires-Dist: requests; extra == "dev"
|
|
33
|
+
Requires-Dist: pystac[validation]==1.*; extra == "dev"
|
|
34
|
+
Provides-Extra: benchmark
|
|
35
|
+
Requires-Dist: pytest-benchmark; extra == "benchmark"
|
|
36
|
+
Provides-Extra: docs
|
|
37
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
38
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
39
|
+
Requires-Dist: pdocs; extra == "docs"
|
|
@@ -7,7 +7,7 @@ with open("README.md") as f:
|
|
|
7
7
|
|
|
8
8
|
install_requires = [
|
|
9
9
|
"attrs",
|
|
10
|
-
"pydantic[dotenv]",
|
|
10
|
+
"pydantic[dotenv]<2",
|
|
11
11
|
"stac_pydantic==2.0.*",
|
|
12
12
|
"brotli_asgi",
|
|
13
13
|
"stac-fastapi.types",
|
|
@@ -23,6 +23,9 @@ extra_reqs = {
|
|
|
23
23
|
"requests",
|
|
24
24
|
"pystac[validation]==1.*",
|
|
25
25
|
],
|
|
26
|
+
"benchmark": [
|
|
27
|
+
"pytest-benchmark",
|
|
28
|
+
],
|
|
26
29
|
"docs": ["mkdocs", "mkdocs-material", "pdocs"],
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -38,6 +41,10 @@ setup(
|
|
|
38
41
|
"Intended Audience :: Information Technology",
|
|
39
42
|
"Intended Audience :: Science/Research",
|
|
40
43
|
"Programming Language :: Python :: 3.8",
|
|
44
|
+
"Programming Language :: Python :: 3.9",
|
|
45
|
+
"Programming Language :: Python :: 3.10",
|
|
46
|
+
"Programming Language :: Python :: 3.11",
|
|
47
|
+
"Programming Language :: Python :: 3.12",
|
|
41
48
|
"License :: OSI Approved :: MIT License",
|
|
42
49
|
],
|
|
43
50
|
keywords="STAC FastAPI COG",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Fastapi app creation."""
|
|
2
|
+
|
|
2
3
|
from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
|
3
4
|
|
|
4
5
|
import attr
|
|
@@ -83,10 +84,22 @@ class StacApi:
|
|
|
83
84
|
converter=update_openapi,
|
|
84
85
|
)
|
|
85
86
|
router: APIRouter = attr.ib(default=attr.Factory(APIRouter))
|
|
86
|
-
title: str = attr.ib(
|
|
87
|
-
|
|
87
|
+
title: str = attr.ib(
|
|
88
|
+
default=attr.Factory(
|
|
89
|
+
lambda self: self.settings.stac_fastapi_title, takes_self=True
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
api_version: str = attr.ib(
|
|
93
|
+
default=attr.Factory(
|
|
94
|
+
lambda self: self.settings.stac_fastapi_version, takes_self=True
|
|
95
|
+
)
|
|
96
|
+
)
|
|
88
97
|
stac_version: str = attr.ib(default=STAC_VERSION)
|
|
89
|
-
description: str = attr.ib(
|
|
98
|
+
description: str = attr.ib(
|
|
99
|
+
default=attr.Factory(
|
|
100
|
+
lambda self: self.settings.stac_fastapi_description, takes_self=True
|
|
101
|
+
)
|
|
102
|
+
)
|
|
90
103
|
search_get_request_model: Type[BaseSearchGetRequest] = attr.ib(
|
|
91
104
|
default=BaseSearchGetRequest
|
|
92
105
|
)
|
|
@@ -132,9 +145,7 @@ class StacApi:
|
|
|
132
145
|
response_model_exclude_unset=False,
|
|
133
146
|
response_model_exclude_none=True,
|
|
134
147
|
methods=["GET"],
|
|
135
|
-
endpoint=create_async_endpoint(
|
|
136
|
-
self.client.landing_page, EmptyRequest, self.response_class
|
|
137
|
-
),
|
|
148
|
+
endpoint=create_async_endpoint(self.client.landing_page, EmptyRequest),
|
|
138
149
|
)
|
|
139
150
|
|
|
140
151
|
def register_conformance_classes(self):
|
|
@@ -153,9 +164,7 @@ class StacApi:
|
|
|
153
164
|
response_model_exclude_unset=True,
|
|
154
165
|
response_model_exclude_none=True,
|
|
155
166
|
methods=["GET"],
|
|
156
|
-
endpoint=create_async_endpoint(
|
|
157
|
-
self.client.conformance, EmptyRequest, self.response_class
|
|
158
|
-
),
|
|
167
|
+
endpoint=create_async_endpoint(self.client.conformance, EmptyRequest),
|
|
159
168
|
)
|
|
160
169
|
|
|
161
170
|
def register_get_item(self):
|
|
@@ -172,9 +181,7 @@ class StacApi:
|
|
|
172
181
|
response_model_exclude_unset=True,
|
|
173
182
|
response_model_exclude_none=True,
|
|
174
183
|
methods=["GET"],
|
|
175
|
-
endpoint=create_async_endpoint(
|
|
176
|
-
self.client.get_item, ItemUri, GeoJSONResponse
|
|
177
|
-
),
|
|
184
|
+
endpoint=create_async_endpoint(self.client.get_item, ItemUri),
|
|
178
185
|
)
|
|
179
186
|
|
|
180
187
|
def register_post_search(self):
|
|
@@ -195,7 +202,7 @@ class StacApi:
|
|
|
195
202
|
response_model_exclude_none=True,
|
|
196
203
|
methods=["POST"],
|
|
197
204
|
endpoint=create_async_endpoint(
|
|
198
|
-
self.client.post_search, self.search_post_request_model
|
|
205
|
+
self.client.post_search, self.search_post_request_model
|
|
199
206
|
),
|
|
200
207
|
)
|
|
201
208
|
|
|
@@ -217,7 +224,7 @@ class StacApi:
|
|
|
217
224
|
response_model_exclude_none=True,
|
|
218
225
|
methods=["GET"],
|
|
219
226
|
endpoint=create_async_endpoint(
|
|
220
|
-
self.client.get_search, self.search_get_request_model
|
|
227
|
+
self.client.get_search, self.search_get_request_model
|
|
221
228
|
),
|
|
222
229
|
)
|
|
223
230
|
|
|
@@ -237,9 +244,7 @@ class StacApi:
|
|
|
237
244
|
response_model_exclude_unset=True,
|
|
238
245
|
response_model_exclude_none=True,
|
|
239
246
|
methods=["GET"],
|
|
240
|
-
endpoint=create_async_endpoint(
|
|
241
|
-
self.client.all_collections, EmptyRequest, self.response_class
|
|
242
|
-
),
|
|
247
|
+
endpoint=create_async_endpoint(self.client.all_collections, EmptyRequest),
|
|
243
248
|
)
|
|
244
249
|
|
|
245
250
|
def register_get_collection(self):
|
|
@@ -256,9 +261,7 @@ class StacApi:
|
|
|
256
261
|
response_model_exclude_unset=True,
|
|
257
262
|
response_model_exclude_none=True,
|
|
258
263
|
methods=["GET"],
|
|
259
|
-
endpoint=create_async_endpoint(
|
|
260
|
-
self.client.get_collection, CollectionUri, self.response_class
|
|
261
|
-
),
|
|
264
|
+
endpoint=create_async_endpoint(self.client.get_collection, CollectionUri),
|
|
262
265
|
)
|
|
263
266
|
|
|
264
267
|
def register_get_item_collection(self):
|
|
@@ -287,9 +290,7 @@ class StacApi:
|
|
|
287
290
|
response_model_exclude_unset=True,
|
|
288
291
|
response_model_exclude_none=True,
|
|
289
292
|
methods=["GET"],
|
|
290
|
-
endpoint=create_async_endpoint(
|
|
291
|
-
self.client.item_collection, request_model, GeoJSONResponse
|
|
292
|
-
),
|
|
293
|
+
endpoint=create_async_endpoint(self.client.item_collection, request_model),
|
|
293
294
|
)
|
|
294
295
|
|
|
295
296
|
def register_core(self):
|
|
@@ -10,7 +10,7 @@ from starlette.types import ASGIApp, Receive, Scope, Send
|
|
|
10
10
|
|
|
11
11
|
class CORSMiddleware(_CORSMiddleware):
|
|
12
12
|
"""Subclass of Starlette's standard CORS middleware with default values set to those
|
|
13
|
-
|
|
13
|
+
recommended by the STAC API spec.
|
|
14
14
|
|
|
15
15
|
https://github.com/radiantearth/stac-api-spec/blob/914cf8108302e2ec734340080a45aaae4859bb63/implementation.md#cors
|
|
16
16
|
"""
|
|
@@ -103,6 +103,7 @@ class ProxyHeaderMiddleware:
|
|
|
103
103
|
# ignore ports that are not valid integers
|
|
104
104
|
pass
|
|
105
105
|
else:
|
|
106
|
+
domain = self._get_header_value_by_name(scope, "x-forwarded-host", domain)
|
|
106
107
|
proto = self._get_header_value_by_name(scope, "x-forwarded-proto", proto)
|
|
107
108
|
port_str = self._get_header_value_by_name(scope, "x-forwarded-port", port)
|
|
108
109
|
try:
|
|
@@ -7,13 +7,15 @@ import attr
|
|
|
7
7
|
from fastapi import Body, Path
|
|
8
8
|
from pydantic import BaseModel, create_model
|
|
9
9
|
from pydantic.fields import UndefinedType
|
|
10
|
+
from stac_pydantic.shared import BBox
|
|
10
11
|
|
|
11
12
|
from stac_fastapi.types.extension import ApiExtension
|
|
13
|
+
from stac_fastapi.types.rfc3339 import DateTimeType
|
|
12
14
|
from stac_fastapi.types.search import (
|
|
13
15
|
APIRequest,
|
|
14
16
|
BaseSearchGetRequest,
|
|
15
17
|
BaseSearchPostRequest,
|
|
16
|
-
|
|
18
|
+
str2bbox,
|
|
17
19
|
)
|
|
18
20
|
|
|
19
21
|
|
|
@@ -124,8 +126,8 @@ class ItemCollectionUri(CollectionUri):
|
|
|
124
126
|
"""Get item collection."""
|
|
125
127
|
|
|
126
128
|
limit: int = attr.ib(default=10)
|
|
127
|
-
bbox: Optional[
|
|
128
|
-
datetime: Optional[
|
|
129
|
+
bbox: Optional[BBox] = attr.ib(default=None, converter=str2bbox)
|
|
130
|
+
datetime: Optional[DateTimeType] = attr.ib(default=None)
|
|
129
131
|
|
|
130
132
|
|
|
131
133
|
class POSTTokenPagination(BaseModel):
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Route factories."""
|
|
2
|
+
|
|
2
3
|
import functools
|
|
3
4
|
import inspect
|
|
5
|
+
import warnings
|
|
4
6
|
from typing import Any, Callable, Dict, List, Optional, Type, TypedDict, Union
|
|
5
7
|
|
|
6
8
|
from fastapi import Depends, params
|
|
@@ -8,18 +10,16 @@ from fastapi.dependencies.utils import get_parameterless_sub_dependant
|
|
|
8
10
|
from pydantic import BaseModel
|
|
9
11
|
from starlette.concurrency import run_in_threadpool
|
|
10
12
|
from starlette.requests import Request
|
|
11
|
-
from starlette.responses import
|
|
13
|
+
from starlette.responses import Response
|
|
12
14
|
from starlette.routing import BaseRoute, Match
|
|
13
15
|
from starlette.status import HTTP_204_NO_CONTENT
|
|
14
16
|
|
|
15
17
|
from stac_fastapi.api.models import APIRequest
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
def _wrap_response(resp: Any
|
|
19
|
-
if
|
|
20
|
+
def _wrap_response(resp: Any) -> Any:
|
|
21
|
+
if resp is not None:
|
|
20
22
|
return resp
|
|
21
|
-
elif resp is not None:
|
|
22
|
-
return response_class(resp)
|
|
23
23
|
else: # None is returned as 204 No Content
|
|
24
24
|
return Response(status_code=HTTP_204_NO_CONTENT)
|
|
25
25
|
|
|
@@ -37,12 +37,19 @@ def sync_to_async(func):
|
|
|
37
37
|
def create_async_endpoint(
|
|
38
38
|
func: Callable,
|
|
39
39
|
request_model: Union[Type[APIRequest], Type[BaseModel], Dict],
|
|
40
|
-
response_class: Type[Response] =
|
|
40
|
+
response_class: Optional[Type[Response]] = None,
|
|
41
41
|
):
|
|
42
42
|
"""Wrap a function in a coroutine which may be used to create a FastAPI endpoint.
|
|
43
43
|
|
|
44
44
|
Synchronous functions are executed asynchronously using a background thread.
|
|
45
45
|
"""
|
|
46
|
+
|
|
47
|
+
if response_class:
|
|
48
|
+
warnings.warns(
|
|
49
|
+
"`response_class` option is deprecated, please set the Response class directly in the endpoint.", # noqa: E501
|
|
50
|
+
DeprecationWarning,
|
|
51
|
+
)
|
|
52
|
+
|
|
46
53
|
if not inspect.iscoroutinefunction(func):
|
|
47
54
|
func = sync_to_async(func)
|
|
48
55
|
|
|
@@ -53,9 +60,7 @@ def create_async_endpoint(
|
|
|
53
60
|
request_data: request_model = Depends(), # type:ignore
|
|
54
61
|
):
|
|
55
62
|
"""Endpoint."""
|
|
56
|
-
return _wrap_response(
|
|
57
|
-
await func(request=request, **request_data.kwargs()), response_class
|
|
58
|
-
)
|
|
63
|
+
return _wrap_response(await func(request=request, **request_data.kwargs()))
|
|
59
64
|
|
|
60
65
|
elif issubclass(request_model, BaseModel):
|
|
61
66
|
|
|
@@ -64,9 +69,7 @@ def create_async_endpoint(
|
|
|
64
69
|
request_data: request_model, # type:ignore
|
|
65
70
|
):
|
|
66
71
|
"""Endpoint."""
|
|
67
|
-
return _wrap_response(
|
|
68
|
-
await func(request_data, request=request), response_class
|
|
69
|
-
)
|
|
72
|
+
return _wrap_response(await func(request_data, request=request))
|
|
70
73
|
|
|
71
74
|
else:
|
|
72
75
|
|
|
@@ -75,9 +78,7 @@ def create_async_endpoint(
|
|
|
75
78
|
request_data: Dict[str, Any], # type:ignore
|
|
76
79
|
):
|
|
77
80
|
"""Endpoint."""
|
|
78
|
-
return _wrap_response(
|
|
79
|
-
await func(request_data, request=request), response_class
|
|
80
|
-
)
|
|
81
|
+
return _wrap_response(await func(request_data, request=request))
|
|
81
82
|
|
|
82
83
|
return _endpoint
|
|
83
84
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Library version."""
|
|
2
|
-
__version__ = "2.
|
|
2
|
+
__version__ = "2.5.0"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: stac-fastapi.api
|
|
3
|
+
Version: 2.5.0
|
|
4
|
+
Summary: An implementation of STAC API based on the FastAPI framework.
|
|
5
|
+
Home-page: https://github.com/stac-utils/stac-fastapi
|
|
6
|
+
Author: Arturo Engineering
|
|
7
|
+
Author-email: engineering@arturo.ai
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: STAC FastAPI COG
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Information Technology
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: attrs
|
|
22
|
+
Requires-Dist: pydantic[dotenv]<2
|
|
23
|
+
Requires-Dist: stac_pydantic==2.0.*
|
|
24
|
+
Requires-Dist: brotli_asgi
|
|
25
|
+
Requires-Dist: stac-fastapi.types
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: httpx; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
31
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
32
|
+
Requires-Dist: requests; extra == "dev"
|
|
33
|
+
Requires-Dist: pystac[validation]==1.*; extra == "dev"
|
|
34
|
+
Provides-Extra: benchmark
|
|
35
|
+
Requires-Dist: pytest-benchmark; extra == "benchmark"
|
|
36
|
+
Provides-Extra: docs
|
|
37
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
38
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
39
|
+
Requires-Dist: pdocs; extra == "docs"
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from fastapi import Depends, HTTPException, security, status
|
|
2
|
+
from starlette.testclient import TestClient
|
|
3
|
+
|
|
4
|
+
from stac_fastapi.api.app import StacApi
|
|
5
|
+
from stac_fastapi.extensions.core import TokenPaginationExtension, TransactionExtension
|
|
6
|
+
from stac_fastapi.types import config, core
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestRouteDependencies:
|
|
10
|
+
@staticmethod
|
|
11
|
+
def _build_api(**overrides):
|
|
12
|
+
settings = config.ApiSettings()
|
|
13
|
+
return StacApi(
|
|
14
|
+
**{
|
|
15
|
+
"settings": settings,
|
|
16
|
+
"client": DummyCoreClient(),
|
|
17
|
+
"extensions": [
|
|
18
|
+
TransactionExtension(
|
|
19
|
+
client=DummyTransactionsClient(), settings=settings
|
|
20
|
+
),
|
|
21
|
+
TokenPaginationExtension(),
|
|
22
|
+
],
|
|
23
|
+
**overrides,
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def _assert_dependency_applied(api, routes):
|
|
29
|
+
with TestClient(api.app) as client:
|
|
30
|
+
for route in routes:
|
|
31
|
+
response = getattr(client, route["method"].lower())(route["path"])
|
|
32
|
+
assert (
|
|
33
|
+
response.status_code == 401
|
|
34
|
+
), "Unauthenticated requests should be rejected"
|
|
35
|
+
assert response.json() == {"detail": "Not authenticated"}
|
|
36
|
+
|
|
37
|
+
path = route["path"].format(
|
|
38
|
+
collectionId="test_collection", itemId="test_item"
|
|
39
|
+
)
|
|
40
|
+
response = client.request(
|
|
41
|
+
method=route["method"].lower(),
|
|
42
|
+
url=path,
|
|
43
|
+
auth=("bob", "dobbs"),
|
|
44
|
+
content='{"dummy": "payload"}',
|
|
45
|
+
headers={"content-type": "application/json"},
|
|
46
|
+
)
|
|
47
|
+
assert (
|
|
48
|
+
response.status_code == 200
|
|
49
|
+
), "Authenticated requests should be accepted"
|
|
50
|
+
assert response.json() == "dummy response"
|
|
51
|
+
|
|
52
|
+
def test_openapi_content_type(self):
|
|
53
|
+
api = self._build_api()
|
|
54
|
+
with TestClient(api.app) as client:
|
|
55
|
+
response = client.get(api.settings.openapi_url)
|
|
56
|
+
assert (
|
|
57
|
+
response.headers["content-type"]
|
|
58
|
+
== "application/vnd.oai.openapi+json;version=3.0"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def test_build_api_with_route_dependencies(self):
|
|
62
|
+
routes = [
|
|
63
|
+
{"path": "/collections", "method": "POST"},
|
|
64
|
+
{"path": "/collections/{collectionId}", "method": "PUT"},
|
|
65
|
+
{"path": "/collections/{collectionId}", "method": "DELETE"},
|
|
66
|
+
{"path": "/collections/{collectionId}/items", "method": "POST"},
|
|
67
|
+
{"path": "/collections/{collectionId}/items/{itemId}", "method": "PUT"},
|
|
68
|
+
{"path": "/collections/{collectionId}/items/{itemId}", "method": "DELETE"},
|
|
69
|
+
]
|
|
70
|
+
dependencies = [Depends(must_be_bob)]
|
|
71
|
+
api = self._build_api(route_dependencies=[(routes, dependencies)])
|
|
72
|
+
self._assert_dependency_applied(api, routes)
|
|
73
|
+
|
|
74
|
+
def test_add_route_dependencies_after_building_api(self):
|
|
75
|
+
routes = [
|
|
76
|
+
{"path": "/collections", "method": "POST"},
|
|
77
|
+
{"path": "/collections/{collectionId}", "method": "PUT"},
|
|
78
|
+
{"path": "/collections/{collectionId}", "method": "DELETE"},
|
|
79
|
+
{"path": "/collections/{collectionId}/items", "method": "POST"},
|
|
80
|
+
{"path": "/collections/{collectionId}/items/{itemId}", "method": "PUT"},
|
|
81
|
+
{"path": "/collections/{collectionId}/items/{itemId}", "method": "DELETE"},
|
|
82
|
+
]
|
|
83
|
+
api = self._build_api()
|
|
84
|
+
api.add_route_dependencies(scopes=routes, dependencies=[Depends(must_be_bob)])
|
|
85
|
+
self._assert_dependency_applied(api, routes)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class DummyCoreClient(core.BaseCoreClient):
|
|
89
|
+
def all_collections(self, *args, **kwargs):
|
|
90
|
+
...
|
|
91
|
+
|
|
92
|
+
def get_collection(self, *args, **kwargs):
|
|
93
|
+
...
|
|
94
|
+
|
|
95
|
+
def get_item(self, *args, **kwargs):
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
def get_search(self, *args, **kwargs):
|
|
99
|
+
...
|
|
100
|
+
|
|
101
|
+
def post_search(self, *args, **kwargs):
|
|
102
|
+
...
|
|
103
|
+
|
|
104
|
+
def item_collection(self, *args, **kwargs):
|
|
105
|
+
...
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class DummyTransactionsClient(core.BaseTransactionsClient):
|
|
109
|
+
"""Defines a pattern for implementing the STAC transaction extension."""
|
|
110
|
+
|
|
111
|
+
def create_item(self, *args, **kwargs):
|
|
112
|
+
return "dummy response"
|
|
113
|
+
|
|
114
|
+
def update_item(self, *args, **kwargs):
|
|
115
|
+
return "dummy response"
|
|
116
|
+
|
|
117
|
+
def delete_item(self, *args, **kwargs):
|
|
118
|
+
return "dummy response"
|
|
119
|
+
|
|
120
|
+
def create_collection(self, *args, **kwargs):
|
|
121
|
+
return "dummy response"
|
|
122
|
+
|
|
123
|
+
def update_collection(self, *args, **kwargs):
|
|
124
|
+
return "dummy response"
|
|
125
|
+
|
|
126
|
+
def delete_collection(self, *args, **kwargs):
|
|
127
|
+
return "dummy response"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def must_be_bob(
|
|
131
|
+
credentials: security.HTTPBasicCredentials = Depends(security.HTTPBasic()),
|
|
132
|
+
):
|
|
133
|
+
if credentials.username == "bob":
|
|
134
|
+
return True
|
|
135
|
+
|
|
136
|
+
raise HTTPException(
|
|
137
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
138
|
+
detail="You're not Bob",
|
|
139
|
+
headers={"WWW-Authenticate": "Basic"},
|
|
140
|
+
)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from unittest import mock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from starlette.applications import Starlette
|
|
5
|
+
from starlette.testclient import TestClient
|
|
6
|
+
|
|
7
|
+
from stac_fastapi.api.app import StacApi
|
|
8
|
+
from stac_fastapi.api.middleware import ProxyHeaderMiddleware
|
|
9
|
+
from stac_fastapi.types.config import ApiSettings
|
|
10
|
+
from stac_fastapi.types.core import BaseCoreClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def proxy_header_middleware() -> ProxyHeaderMiddleware:
|
|
15
|
+
app = Starlette()
|
|
16
|
+
return ProxyHeaderMiddleware(app)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def test_client() -> TestClient:
|
|
21
|
+
app = StacApi(settings=ApiSettings(), client=mock.create_autospec(BaseCoreClient))
|
|
22
|
+
with TestClient(app.app) as client:
|
|
23
|
+
yield client
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.parametrize(
|
|
27
|
+
"headers,key,expected",
|
|
28
|
+
[
|
|
29
|
+
([(b"host", b"testserver")], "host", "testserver"),
|
|
30
|
+
([(b"host", b"testserver")], "user-agent", None),
|
|
31
|
+
(
|
|
32
|
+
[(b"host", b"testserver"), (b"accept-encoding", b"gzip, deflate, br")],
|
|
33
|
+
"accept-encoding",
|
|
34
|
+
"gzip, deflate, br",
|
|
35
|
+
),
|
|
36
|
+
],
|
|
37
|
+
)
|
|
38
|
+
def test_get_header_value_by_name(
|
|
39
|
+
proxy_header_middleware: ProxyHeaderMiddleware, headers, key, expected
|
|
40
|
+
):
|
|
41
|
+
scope = {"headers": headers}
|
|
42
|
+
actual = proxy_header_middleware._get_header_value_by_name(scope, key)
|
|
43
|
+
assert actual == expected
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.parametrize(
|
|
47
|
+
"headers,key,value",
|
|
48
|
+
[
|
|
49
|
+
([(b"host", b"testserver")], "host", "another-server"),
|
|
50
|
+
([(b"host", b"testserver")], "user-agent", "agent"),
|
|
51
|
+
(
|
|
52
|
+
[(b"host", b"testserver"), (b"accept-encoding", b"gzip, deflate, br")],
|
|
53
|
+
"accept-encoding",
|
|
54
|
+
"deflate",
|
|
55
|
+
),
|
|
56
|
+
],
|
|
57
|
+
)
|
|
58
|
+
def test_replace_header_value_by_name(
|
|
59
|
+
proxy_header_middleware: ProxyHeaderMiddleware, headers, key, value
|
|
60
|
+
):
|
|
61
|
+
scope = {"headers": headers}
|
|
62
|
+
updated_headers = proxy_header_middleware._replace_header_value_by_name(
|
|
63
|
+
scope, key, value
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
header_value = proxy_header_middleware._get_header_value_by_name(
|
|
67
|
+
{"headers": updated_headers}, key
|
|
68
|
+
)
|
|
69
|
+
assert header_value == value
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@pytest.mark.parametrize(
|
|
73
|
+
"scope,expected",
|
|
74
|
+
[
|
|
75
|
+
(
|
|
76
|
+
{"scheme": "https", "server": ["testserver", 80], "headers": []},
|
|
77
|
+
("https", "testserver", 80),
|
|
78
|
+
),
|
|
79
|
+
(
|
|
80
|
+
{
|
|
81
|
+
"scheme": "http",
|
|
82
|
+
"server": ["testserver", 80],
|
|
83
|
+
"headers": [(b"host", b"testserver:81")],
|
|
84
|
+
},
|
|
85
|
+
("http", "testserver", 81),
|
|
86
|
+
),
|
|
87
|
+
(
|
|
88
|
+
{
|
|
89
|
+
"scheme": "http",
|
|
90
|
+
"server": ["testserver", 80],
|
|
91
|
+
"headers": [(b"host", b"testserver")],
|
|
92
|
+
},
|
|
93
|
+
("http", "testserver", None),
|
|
94
|
+
),
|
|
95
|
+
(
|
|
96
|
+
{
|
|
97
|
+
"scheme": "http",
|
|
98
|
+
"server": ["testserver", 80],
|
|
99
|
+
"headers": [(b"forwarded", b"proto=https;host=test:1234")],
|
|
100
|
+
},
|
|
101
|
+
("https", "test", 1234),
|
|
102
|
+
),
|
|
103
|
+
(
|
|
104
|
+
{
|
|
105
|
+
"scheme": "http",
|
|
106
|
+
"server": ["testserver", 80],
|
|
107
|
+
"headers": [(b"forwarded", b"proto=https;host=test:not-an-integer")],
|
|
108
|
+
},
|
|
109
|
+
("https", "test", 80),
|
|
110
|
+
),
|
|
111
|
+
(
|
|
112
|
+
{
|
|
113
|
+
"scheme": "http",
|
|
114
|
+
"server": ["testserver", 80],
|
|
115
|
+
"headers": [(b"x-forwarded-host", b"test")],
|
|
116
|
+
},
|
|
117
|
+
("http", "test", 80),
|
|
118
|
+
),
|
|
119
|
+
(
|
|
120
|
+
{
|
|
121
|
+
"scheme": "http",
|
|
122
|
+
"server": ["testserver", 80],
|
|
123
|
+
"headers": [(b"x-forwarded-proto", b"https")],
|
|
124
|
+
},
|
|
125
|
+
("https", "testserver", 80),
|
|
126
|
+
),
|
|
127
|
+
(
|
|
128
|
+
{
|
|
129
|
+
"scheme": "http",
|
|
130
|
+
"server": ["testserver", 80],
|
|
131
|
+
"headers": [(b"x-forwarded-port", b"1111")],
|
|
132
|
+
},
|
|
133
|
+
("http", "testserver", 1111),
|
|
134
|
+
),
|
|
135
|
+
(
|
|
136
|
+
{
|
|
137
|
+
"scheme": "http",
|
|
138
|
+
"server": ["testserver", 80],
|
|
139
|
+
"headers": [(b"x-forwarded-port", b"not-an-integer")],
|
|
140
|
+
},
|
|
141
|
+
("http", "testserver", 80),
|
|
142
|
+
),
|
|
143
|
+
(
|
|
144
|
+
{
|
|
145
|
+
"scheme": "http",
|
|
146
|
+
"server": ["testserver", 80],
|
|
147
|
+
"headers": [
|
|
148
|
+
(b"forwarded", b"proto=https;host=test:1234"),
|
|
149
|
+
(b"x-forwarded-host", b"another-test"),
|
|
150
|
+
(b"x-forwarded-port", b"1111"),
|
|
151
|
+
(b"x-forwarded-proto", b"https"),
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
("https", "test", 1234),
|
|
155
|
+
),
|
|
156
|
+
],
|
|
157
|
+
)
|
|
158
|
+
def test_get_forwarded_url_parts(
|
|
159
|
+
proxy_header_middleware: ProxyHeaderMiddleware, scope, expected
|
|
160
|
+
):
|
|
161
|
+
actual = proxy_header_middleware._get_forwarded_url_parts(scope)
|
|
162
|
+
assert actual == expected
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_cors_middleware(test_client):
|
|
166
|
+
resp = test_client.get("/_mgmt/ping", headers={"Origin": "http://netloc"})
|
|
167
|
+
assert resp.status_code == 200
|
|
168
|
+
assert resp.headers["access-control-allow-origin"] == "*"
|
stac-fastapi.api-2.4.8/PKG-INFO
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: stac-fastapi.api
|
|
3
|
-
Version: 2.4.8
|
|
4
|
-
Summary: An implementation of STAC API based on the FastAPI framework.
|
|
5
|
-
Home-page: https://github.com/stac-utils/stac-fastapi
|
|
6
|
-
Author: Arturo Engineering
|
|
7
|
-
Author-email: engineering@arturo.ai
|
|
8
|
-
License: MIT
|
|
9
|
-
Keywords: STAC FastAPI COG
|
|
10
|
-
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: Intended Audience :: Information Technology
|
|
12
|
-
Classifier: Intended Audience :: Science/Research
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
-
Requires-Python: >=3.8
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
Provides-Extra: dev
|
|
18
|
-
Provides-Extra: docs
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: stac-fastapi.api
|
|
3
|
-
Version: 2.4.8
|
|
4
|
-
Summary: An implementation of STAC API based on the FastAPI framework.
|
|
5
|
-
Home-page: https://github.com/stac-utils/stac-fastapi
|
|
6
|
-
Author: Arturo Engineering
|
|
7
|
-
Author-email: engineering@arturo.ai
|
|
8
|
-
License: MIT
|
|
9
|
-
Keywords: STAC FastAPI COG
|
|
10
|
-
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: Intended Audience :: Information Technology
|
|
12
|
-
Classifier: Intended Audience :: Science/Research
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
-
Requires-Python: >=3.8
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
Provides-Extra: dev
|
|
18
|
-
Provides-Extra: docs
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stac-fastapi.api-2.4.8 → stac_fastapi_api-2.5.0}/stac_fastapi.api.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|