stac-fastapi.api 6.1.3__tar.gz → 6.1.5__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-6.1.3 → stac_fastapi_api-6.1.5}/PKG-INFO +1 -1
- {stac_fastapi_api-6.1.3 → stac_fastapi_api-6.1.5}/stac_fastapi/api/app.py +3 -6
- {stac_fastapi_api-6.1.3 → stac_fastapi_api-6.1.5}/stac_fastapi/api/errors.py +1 -1
- {stac_fastapi_api-6.1.3 → stac_fastapi_api-6.1.5}/stac_fastapi/api/middleware.py +37 -18
- {stac_fastapi_api-6.1.3 → stac_fastapi_api-6.1.5}/stac_fastapi/api/models.py +13 -9
- {stac_fastapi_api-6.1.3 → stac_fastapi_api-6.1.5}/stac_fastapi/api/version.py +1 -1
- {stac_fastapi_api-6.1.3 → stac_fastapi_api-6.1.5}/.gitignore +0 -0
- {stac_fastapi_api-6.1.3 → stac_fastapi_api-6.1.5}/README.md +0 -0
- {stac_fastapi_api-6.1.3 → stac_fastapi_api-6.1.5}/pyproject.toml +0 -0
- {stac_fastapi_api-6.1.3 → stac_fastapi_api-6.1.5}/stac_fastapi/api/__init__.py +0 -0
- {stac_fastapi_api-6.1.3 → stac_fastapi_api-6.1.5}/stac_fastapi/api/config.py +0 -0
- {stac_fastapi_api-6.1.3 → stac_fastapi_api-6.1.5}/stac_fastapi/api/openapi.py +0 -0
- {stac_fastapi_api-6.1.3 → stac_fastapi_api-6.1.5}/stac_fastapi/api/py.typed +0 -0
- {stac_fastapi_api-6.1.3 → stac_fastapi_api-6.1.5}/stac_fastapi/api/routes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stac-fastapi-api
|
|
3
|
-
Version: 6.1.
|
|
3
|
+
Version: 6.1.5
|
|
4
4
|
Summary: An implementation of STAC API based on the FastAPI framework.
|
|
5
5
|
Project-URL: Homepage, https://github.com/stac-utils/stac-fastapi
|
|
6
6
|
Author-email: ArturoAI <engineering@arturo.ai>
|
|
@@ -8,6 +8,7 @@ import attr
|
|
|
8
8
|
from brotli_asgi import BrotliMiddleware
|
|
9
9
|
from fastapi import APIRouter, FastAPI
|
|
10
10
|
from fastapi.params import Depends
|
|
11
|
+
from pydantic import BaseModel
|
|
11
12
|
from stac_pydantic import api
|
|
12
13
|
from stac_pydantic.shared import MimeTypes
|
|
13
14
|
from stac_pydantic.version import STAC_VERSION
|
|
@@ -114,12 +115,8 @@ class StacApi:
|
|
|
114
115
|
converter=update_openapi, # type: ignore
|
|
115
116
|
)
|
|
116
117
|
router: APIRouter = attr.ib(default=attr.Factory(APIRouter))
|
|
117
|
-
search_get_request_model: Type[
|
|
118
|
-
|
|
119
|
-
)
|
|
120
|
-
search_post_request_model: Type[BaseSearchPostRequest] = attr.ib(
|
|
121
|
-
default=BaseSearchPostRequest
|
|
122
|
-
)
|
|
118
|
+
search_get_request_model: Type[APIRequest] = attr.ib(default=BaseSearchGetRequest)
|
|
119
|
+
search_post_request_model: Type[BaseModel] = attr.ib(default=BaseSearchPostRequest)
|
|
123
120
|
collections_get_request_model: Type[APIRequest] = attr.ib(default=EmptyRequest)
|
|
124
121
|
collection_get_request_model: Type[APIRequest] = attr.ib(default=CollectionUri)
|
|
125
122
|
items_get_request_model: Type[APIRequest] = attr.ib(default=ItemCollectionUri)
|
|
@@ -96,5 +96,5 @@ def add_exception_handlers(
|
|
|
96
96
|
# TODO: Argument 2 to "add_exception_handler" of "Starlette" has incompatible type
|
|
97
97
|
app.add_exception_handler(
|
|
98
98
|
RequestValidationError,
|
|
99
|
-
request_validation_exception_handler, # type: ignore
|
|
99
|
+
request_validation_exception_handler, # type: ignore [arg-type]
|
|
100
100
|
)
|
|
@@ -82,20 +82,33 @@ class ProxyHeaderMiddleware:
|
|
|
82
82
|
|
|
83
83
|
await self.app(scope, receive, send)
|
|
84
84
|
|
|
85
|
-
def _get_forwarded_url_parts(self, scope: Scope) -> Tuple[str, str,
|
|
85
|
+
def _get_forwarded_url_parts(self, scope: Scope) -> Tuple[str, str, int]:
|
|
86
86
|
proto = scope.get("scheme", "http")
|
|
87
|
-
|
|
88
|
-
if
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
# Assume default port based on protocol, can be overridden later
|
|
88
|
+
port = 443 if proto == "https" else 80
|
|
89
|
+
|
|
90
|
+
if header_host := self._get_header_value_by_name(scope, "host"):
|
|
91
91
|
header_host_parts = header_host.split(":")
|
|
92
|
+
domain = header_host_parts[0]
|
|
92
93
|
if len(header_host_parts) == 2:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
with contextlib.suppress(ValueError):
|
|
95
|
+
port = int(header_host_parts[1])
|
|
96
|
+
else:
|
|
97
|
+
# Not sure when we would not have a host header, but fallback to server info
|
|
98
|
+
domain, port = scope["server"]
|
|
99
|
+
port = int(port)
|
|
100
|
+
|
|
101
|
+
forwarded_port: Optional[str] = None
|
|
102
|
+
forwarding_occurred = any(
|
|
103
|
+
key
|
|
104
|
+
in [
|
|
105
|
+
b"forwarded",
|
|
106
|
+
b"x-forwarded-proto",
|
|
107
|
+
b"x-forwarded-host",
|
|
108
|
+
b"x-forwarded-port",
|
|
109
|
+
]
|
|
110
|
+
for key, _ in scope["headers"]
|
|
111
|
+
)
|
|
99
112
|
|
|
100
113
|
if forwarded := self._get_header_value_by_name(scope, "forwarded"):
|
|
101
114
|
for proxy in forwarded.split(","):
|
|
@@ -103,15 +116,21 @@ class ProxyHeaderMiddleware:
|
|
|
103
116
|
proto = proto_expr.group("proto")
|
|
104
117
|
if host_expr := _HOST_HEADER_REGEX.search(proxy):
|
|
105
118
|
domain = host_expr.group("host")
|
|
106
|
-
|
|
119
|
+
forwarded_port = host_expr.group("port") # None if not present
|
|
107
120
|
|
|
108
121
|
else:
|
|
109
|
-
domain = self._get_header_value_by_name(scope, "x-forwarded-host"
|
|
110
|
-
proto = self._get_header_value_by_name(scope, "x-forwarded-proto"
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
122
|
+
domain = self._get_header_value_by_name(scope, "x-forwarded-host") or domain
|
|
123
|
+
proto = self._get_header_value_by_name(scope, "x-forwarded-proto") or proto
|
|
124
|
+
forwarded_port = self._get_header_value_by_name(scope, "x-forwarded-port")
|
|
125
|
+
|
|
126
|
+
if forwarding_occurred and not forwarded_port:
|
|
127
|
+
# If forwarding occurred but no port was specified, use protocol default
|
|
128
|
+
forwarded_port = "443" if proto == "https" else "80"
|
|
129
|
+
|
|
130
|
+
if forwarded_port:
|
|
131
|
+
# ignore ports that are not valid integers
|
|
132
|
+
with contextlib.suppress(ValueError):
|
|
133
|
+
port = int(forwarded_port)
|
|
115
134
|
|
|
116
135
|
return (proto, domain, port)
|
|
117
136
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Api request/response models."""
|
|
2
2
|
|
|
3
|
-
from typing import List, Literal, Optional, Type, Union
|
|
3
|
+
from typing import List, Literal, Optional, Type, Union, cast
|
|
4
4
|
|
|
5
5
|
import attr
|
|
6
6
|
from fastapi import Path, Query
|
|
@@ -24,7 +24,7 @@ try:
|
|
|
24
24
|
import orjson # noqa
|
|
25
25
|
from fastapi.responses import ORJSONResponse as JSONResponse
|
|
26
26
|
except ImportError: # pragma: nocover
|
|
27
|
-
from starlette.responses import JSONResponse # type: ignore
|
|
27
|
+
from starlette.responses import JSONResponse # type: ignore [assignment]
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def create_request_model(
|
|
@@ -49,7 +49,8 @@ def create_request_model(
|
|
|
49
49
|
|
|
50
50
|
# Handle GET requests
|
|
51
51
|
if all([issubclass(m, APIRequest) for m in models]):
|
|
52
|
-
|
|
52
|
+
get_model = attr.make_class(model_name, attrs={}, bases=tuple(models))
|
|
53
|
+
return cast(Type[APIRequest], get_model)
|
|
53
54
|
|
|
54
55
|
# Handle POST requests
|
|
55
56
|
elif all([issubclass(m, BaseModel) for m in models]):
|
|
@@ -57,35 +58,38 @@ def create_request_model(
|
|
|
57
58
|
for k, field_info in model.model_fields.items(): # type: ignore
|
|
58
59
|
fields[k] = (field_info.annotation, field_info)
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
post_model = create_model(model_name, **fields, __base__=base_model) # type: ignore
|
|
62
|
+
return cast(Type[BaseModel], post_model)
|
|
61
63
|
|
|
62
64
|
raise TypeError("Mixed Request Model types. Check extension request types.")
|
|
63
65
|
|
|
64
66
|
|
|
65
67
|
def create_get_request_model(
|
|
66
68
|
extensions: Optional[List[ApiExtension]],
|
|
67
|
-
base_model: Type[
|
|
69
|
+
base_model: Type[APIRequest] = BaseSearchGetRequest,
|
|
68
70
|
) -> Type[APIRequest]:
|
|
69
71
|
"""Wrap create_request_model to create the GET request model."""
|
|
70
|
-
|
|
72
|
+
model = create_request_model(
|
|
71
73
|
"SearchGetRequest",
|
|
72
74
|
base_model=base_model,
|
|
73
75
|
extensions=extensions,
|
|
74
76
|
request_type="GET",
|
|
75
77
|
)
|
|
78
|
+
return cast(Type[APIRequest], model)
|
|
76
79
|
|
|
77
80
|
|
|
78
81
|
def create_post_request_model(
|
|
79
82
|
extensions: Optional[List[ApiExtension]],
|
|
80
|
-
base_model: Type[
|
|
83
|
+
base_model: Type[BaseModel] = BaseSearchPostRequest,
|
|
81
84
|
) -> Type[BaseModel]:
|
|
82
85
|
"""Wrap create_request_model to create the POST request model."""
|
|
83
|
-
|
|
86
|
+
model = create_request_model(
|
|
84
87
|
"SearchPostRequest",
|
|
85
88
|
base_model=base_model,
|
|
86
89
|
extensions=extensions,
|
|
87
90
|
request_type="POST",
|
|
88
91
|
)
|
|
92
|
+
return cast(Type[BaseModel], model)
|
|
89
93
|
|
|
90
94
|
|
|
91
95
|
@attr.s
|
|
@@ -121,7 +125,7 @@ class ItemCollectionUri(APIRequest, DatetimeMixin):
|
|
|
121
125
|
description="Limits the number of results that are included in each page of the response (capped to 10_000)." # noqa: E501
|
|
122
126
|
),
|
|
123
127
|
] = attr.ib(default=10)
|
|
124
|
-
bbox: Optional[BBox] = attr.ib(default=None, converter=_bbox_converter) # type: ignore
|
|
128
|
+
bbox: Optional[BBox] = attr.ib(default=None, converter=_bbox_converter) # type: ignore [misc]
|
|
125
129
|
datetime: DateTimeQueryType = attr.ib(default=None, validator=_validate_datetime)
|
|
126
130
|
|
|
127
131
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|