stac-fastapi.types 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_types-2.5.0/PKG-INFO +36 -0
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/setup.py +6 -2
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi/types/config.py +5 -0
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi/types/core.py +60 -29
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi/types/links.py +1 -1
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi/types/rfc3339.py +13 -3
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi/types/search.py +36 -42
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi/types/stac.py +3 -1
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi/types/version.py +1 -1
- stac_fastapi_types-2.5.0/stac_fastapi.types.egg-info/PKG-INFO +36 -0
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi.types.egg-info/SOURCES.txt +3 -1
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi.types.egg-info/requires.txt +2 -2
- stac_fastapi_types-2.5.0/tests/test_limit.py +22 -0
- stac_fastapi_types-2.5.0/tests/test_rfc3339.py +105 -0
- stac-fastapi.types-2.4.8/PKG-INFO +0 -18
- stac-fastapi.types-2.4.8/stac_fastapi.types.egg-info/PKG-INFO +0 -18
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/README.md +0 -0
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/setup.cfg +0 -0
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi/types/__init__.py +0 -0
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi/types/conformance.py +0 -0
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi/types/errors.py +0 -0
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi/types/extension.py +0 -0
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi/types/requests.py +0 -0
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi.types.egg-info/dependency_links.txt +0 -0
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi.types.egg-info/not-zip-safe +0 -0
- {stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi.types.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: stac-fastapi.types
|
|
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: fastapi>=0.73.0
|
|
22
|
+
Requires-Dist: attrs
|
|
23
|
+
Requires-Dist: pydantic[dotenv]<2
|
|
24
|
+
Requires-Dist: stac_pydantic==2.0.*
|
|
25
|
+
Requires-Dist: pystac==1.*
|
|
26
|
+
Requires-Dist: iso8601<2.2.0,>=1.0.2
|
|
27
|
+
Provides-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
|
+
Provides-Extra: docs
|
|
34
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
35
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
36
|
+
Requires-Dist: pdocs; extra == "docs"
|
|
@@ -8,10 +8,10 @@ with open("README.md") as f:
|
|
|
8
8
|
install_requires = [
|
|
9
9
|
"fastapi>=0.73.0",
|
|
10
10
|
"attrs",
|
|
11
|
-
"pydantic[dotenv]",
|
|
11
|
+
"pydantic[dotenv]<2",
|
|
12
12
|
"stac_pydantic==2.0.*",
|
|
13
13
|
"pystac==1.*",
|
|
14
|
-
"iso8601>=1.0.2,<
|
|
14
|
+
"iso8601>=1.0.2,<2.2.0",
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
extra_reqs = {
|
|
@@ -37,6 +37,10 @@ setup(
|
|
|
37
37
|
"Intended Audience :: Information Technology",
|
|
38
38
|
"Intended Audience :: Science/Research",
|
|
39
39
|
"Programming Language :: Python :: 3.8",
|
|
40
|
+
"Programming Language :: Python :: 3.9",
|
|
41
|
+
"Programming Language :: Python :: 3.10",
|
|
42
|
+
"Programming Language :: Python :: 3.11",
|
|
43
|
+
"Programming Language :: Python :: 3.12",
|
|
40
44
|
"License :: OSI Approved :: MIT License",
|
|
41
45
|
],
|
|
42
46
|
keywords="STAC FastAPI COG",
|
|
@@ -22,6 +22,11 @@ class ApiSettings(BaseSettings):
|
|
|
22
22
|
# `pydantic.BaseSettings` instead
|
|
23
23
|
default_includes: Optional[Set[str]] = None
|
|
24
24
|
|
|
25
|
+
stac_fastapi_title: str = "stac-fastapi"
|
|
26
|
+
stac_fastapi_description: str = "stac-fastapi"
|
|
27
|
+
stac_fastapi_version: str = "0.1"
|
|
28
|
+
stac_fastapi_landing_id: str = "stac-fastapi"
|
|
29
|
+
|
|
25
30
|
app_host: str = "0.0.0.0"
|
|
26
31
|
app_port: int = 8000
|
|
27
32
|
reload: bool = True
|
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
"""Base clients."""
|
|
2
|
+
|
|
2
3
|
import abc
|
|
3
|
-
from datetime import datetime
|
|
4
4
|
from typing import Any, Dict, List, Optional, Union
|
|
5
5
|
from urllib.parse import urljoin
|
|
6
6
|
|
|
7
7
|
import attr
|
|
8
8
|
from fastapi import Request
|
|
9
9
|
from stac_pydantic.links import Relations
|
|
10
|
-
from stac_pydantic.shared import MimeTypes
|
|
10
|
+
from stac_pydantic.shared import BBox, MimeTypes
|
|
11
11
|
from stac_pydantic.version import STAC_VERSION
|
|
12
12
|
from starlette.responses import Response
|
|
13
13
|
|
|
14
14
|
from stac_fastapi.types import stac as stac_types
|
|
15
|
+
from stac_fastapi.types.config import ApiSettings
|
|
15
16
|
from stac_fastapi.types.conformance import BASE_CONFORMANCE_CLASSES
|
|
16
17
|
from stac_fastapi.types.extension import ApiExtension
|
|
17
18
|
from stac_fastapi.types.requests import get_base_url
|
|
19
|
+
from stac_fastapi.types.rfc3339 import DateTimeType
|
|
18
20
|
from stac_fastapi.types.search import BaseSearchPostRequest
|
|
19
21
|
from stac_fastapi.types.stac import Conformance
|
|
20
22
|
|
|
21
23
|
NumType = Union[float, int]
|
|
22
24
|
StacType = Dict[str, Any]
|
|
23
25
|
|
|
26
|
+
api_settings = ApiSettings()
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
@attr.s # type:ignore
|
|
26
30
|
class BaseTransactionsClient(abc.ABC):
|
|
@@ -101,18 +105,18 @@ class BaseTransactionsClient(abc.ABC):
|
|
|
101
105
|
|
|
102
106
|
@abc.abstractmethod
|
|
103
107
|
def update_collection(
|
|
104
|
-
self, collection: stac_types.Collection, **kwargs
|
|
108
|
+
self, collection_id: str, collection: stac_types.Collection, **kwargs
|
|
105
109
|
) -> Optional[Union[stac_types.Collection, Response]]:
|
|
106
110
|
"""Perform a complete update on an existing collection.
|
|
107
111
|
|
|
108
|
-
Called with `PUT /collections`. It is expected that this item
|
|
109
|
-
exists. The update should do a diff against the saved collection and
|
|
112
|
+
Called with `PUT /collections/{collection_id}`. It is expected that this item
|
|
113
|
+
already exists. The update should do a diff against the saved collection and
|
|
110
114
|
perform any necessary updates. Partial updates are not supported by the
|
|
111
115
|
transactions extension.
|
|
112
116
|
|
|
113
117
|
Args:
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
collection_id: id of the existing collection to be updated
|
|
119
|
+
collection: the updated collection (must be complete)
|
|
116
120
|
|
|
117
121
|
Returns:
|
|
118
122
|
The updated collection.
|
|
@@ -214,17 +218,18 @@ class AsyncBaseTransactionsClient(abc.ABC):
|
|
|
214
218
|
|
|
215
219
|
@abc.abstractmethod
|
|
216
220
|
async def update_collection(
|
|
217
|
-
self, collection: stac_types.Collection, **kwargs
|
|
221
|
+
self, collection_id: str, collection: stac_types.Collection, **kwargs
|
|
218
222
|
) -> Optional[Union[stac_types.Collection, Response]]:
|
|
219
223
|
"""Perform a complete update on an existing collection.
|
|
220
224
|
|
|
221
|
-
Called with `PUT /collections`. It is expected that this item
|
|
222
|
-
exists. The update should do a diff against the saved collection and
|
|
225
|
+
Called with `PUT /collections/{collection_id}`. It is expected that this item
|
|
226
|
+
already exists. The update should do a diff against the saved collection and
|
|
223
227
|
perform any necessary updates. Partial updates are not supported by the
|
|
224
228
|
transactions extension.
|
|
225
229
|
|
|
226
230
|
Args:
|
|
227
|
-
|
|
231
|
+
collection_id: id of the existing collection to be updated
|
|
232
|
+
collection: the updated collection (must be complete)
|
|
228
233
|
|
|
229
234
|
Returns:
|
|
230
235
|
The updated collection.
|
|
@@ -253,9 +258,9 @@ class LandingPageMixin(abc.ABC):
|
|
|
253
258
|
"""Create a STAC landing page (GET /)."""
|
|
254
259
|
|
|
255
260
|
stac_version: str = attr.ib(default=STAC_VERSION)
|
|
256
|
-
landing_page_id: str = attr.ib(default=
|
|
257
|
-
title: str = attr.ib(default=
|
|
258
|
-
description: str = attr.ib(default=
|
|
261
|
+
landing_page_id: str = attr.ib(default=api_settings.stac_fastapi_landing_id)
|
|
262
|
+
title: str = attr.ib(default=api_settings.stac_fastapi_title)
|
|
263
|
+
description: str = attr.ib(default=api_settings.stac_fastapi_description)
|
|
259
264
|
|
|
260
265
|
def _landing_page(
|
|
261
266
|
self,
|
|
@@ -289,7 +294,7 @@ class LandingPageMixin(abc.ABC):
|
|
|
289
294
|
{
|
|
290
295
|
"rel": Relations.conformance.value,
|
|
291
296
|
"type": MimeTypes.json,
|
|
292
|
-
"title": "STAC/
|
|
297
|
+
"title": "STAC/OGC conformance classes implemented by this server",
|
|
293
298
|
"href": urljoin(base_url, "conformance"),
|
|
294
299
|
},
|
|
295
300
|
{
|
|
@@ -367,6 +372,20 @@ class BaseCoreClient(LandingPageMixin, abc.ABC):
|
|
|
367
372
|
extension_schemas=[],
|
|
368
373
|
)
|
|
369
374
|
|
|
375
|
+
# Add Queryables link
|
|
376
|
+
if self.extension_is_enabled("FilterExtension"):
|
|
377
|
+
landing_page["links"].append(
|
|
378
|
+
{
|
|
379
|
+
# TODO: replace this with Relations.queryables.value,
|
|
380
|
+
"rel": "http://www.opengis.net/def/rel/ogc/1.0/queryables",
|
|
381
|
+
# TODO: replace this with MimeTypes.jsonschema,
|
|
382
|
+
"type": "application/schema+json",
|
|
383
|
+
"title": "Queryables",
|
|
384
|
+
"href": urljoin(base_url, "queryables"),
|
|
385
|
+
"method": "GET",
|
|
386
|
+
}
|
|
387
|
+
)
|
|
388
|
+
|
|
370
389
|
# Add Collections links
|
|
371
390
|
collections = self.all_collections(request=kwargs["request"])
|
|
372
391
|
for collection in collections["collections"]:
|
|
@@ -436,8 +455,8 @@ class BaseCoreClient(LandingPageMixin, abc.ABC):
|
|
|
436
455
|
self,
|
|
437
456
|
collections: Optional[List[str]] = None,
|
|
438
457
|
ids: Optional[List[str]] = None,
|
|
439
|
-
bbox: Optional[
|
|
440
|
-
datetime: Optional[
|
|
458
|
+
bbox: Optional[BBox] = None,
|
|
459
|
+
datetime: Optional[DateTimeType] = None,
|
|
441
460
|
limit: Optional[int] = 10,
|
|
442
461
|
query: Optional[str] = None,
|
|
443
462
|
token: Optional[str] = None,
|
|
@@ -499,8 +518,8 @@ class BaseCoreClient(LandingPageMixin, abc.ABC):
|
|
|
499
518
|
def item_collection(
|
|
500
519
|
self,
|
|
501
520
|
collection_id: str,
|
|
502
|
-
bbox: Optional[
|
|
503
|
-
datetime: Optional[
|
|
521
|
+
bbox: Optional[BBox] = None,
|
|
522
|
+
datetime: Optional[DateTimeType] = None,
|
|
504
523
|
limit: int = 10,
|
|
505
524
|
token: str = None,
|
|
506
525
|
**kwargs,
|
|
@@ -564,6 +583,22 @@ class AsyncBaseCoreClient(LandingPageMixin, abc.ABC):
|
|
|
564
583
|
conformance_classes=self.conformance_classes(),
|
|
565
584
|
extension_schemas=[],
|
|
566
585
|
)
|
|
586
|
+
|
|
587
|
+
# Add Queryables link
|
|
588
|
+
if self.extension_is_enabled("FilterExtension"):
|
|
589
|
+
landing_page["links"].append(
|
|
590
|
+
{
|
|
591
|
+
# TODO: replace this with Relations.queryables.value,
|
|
592
|
+
"rel": "http://www.opengis.net/def/rel/ogc/1.0/queryables",
|
|
593
|
+
# TODO: replace this with MimeTypes.jsonschema,
|
|
594
|
+
"type": "application/schema+json",
|
|
595
|
+
"title": "Queryables",
|
|
596
|
+
"href": urljoin(base_url, "queryables"),
|
|
597
|
+
"method": "GET",
|
|
598
|
+
}
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
# Add Collections links
|
|
567
602
|
collections = await self.all_collections(request=kwargs["request"])
|
|
568
603
|
for collection in collections["collections"]:
|
|
569
604
|
landing_page["links"].append(
|
|
@@ -581,9 +616,7 @@ class AsyncBaseCoreClient(LandingPageMixin, abc.ABC):
|
|
|
581
616
|
"rel": "service-desc",
|
|
582
617
|
"type": "application/vnd.oai.openapi+json;version=3.0",
|
|
583
618
|
"title": "OpenAPI service description",
|
|
584
|
-
"href": urljoin(
|
|
585
|
-
str(request.base_url), request.app.openapi_url.lstrip("/")
|
|
586
|
-
),
|
|
619
|
+
"href": urljoin(base_url, request.app.openapi_url.lstrip("/")),
|
|
587
620
|
}
|
|
588
621
|
)
|
|
589
622
|
|
|
@@ -593,9 +626,7 @@ class AsyncBaseCoreClient(LandingPageMixin, abc.ABC):
|
|
|
593
626
|
"rel": "service-doc",
|
|
594
627
|
"type": "text/html",
|
|
595
628
|
"title": "OpenAPI service documentation",
|
|
596
|
-
"href": urljoin(
|
|
597
|
-
str(request.base_url), request.app.docs_url.lstrip("/")
|
|
598
|
-
),
|
|
629
|
+
"href": urljoin(base_url, request.app.docs_url.lstrip("/")),
|
|
599
630
|
}
|
|
600
631
|
)
|
|
601
632
|
|
|
@@ -632,8 +663,8 @@ class AsyncBaseCoreClient(LandingPageMixin, abc.ABC):
|
|
|
632
663
|
self,
|
|
633
664
|
collections: Optional[List[str]] = None,
|
|
634
665
|
ids: Optional[List[str]] = None,
|
|
635
|
-
bbox: Optional[
|
|
636
|
-
datetime: Optional[
|
|
666
|
+
bbox: Optional[BBox] = None,
|
|
667
|
+
datetime: Optional[DateTimeType] = None,
|
|
637
668
|
limit: Optional[int] = 10,
|
|
638
669
|
query: Optional[str] = None,
|
|
639
670
|
token: Optional[str] = None,
|
|
@@ -699,8 +730,8 @@ class AsyncBaseCoreClient(LandingPageMixin, abc.ABC):
|
|
|
699
730
|
async def item_collection(
|
|
700
731
|
self,
|
|
701
732
|
collection_id: str,
|
|
702
|
-
bbox: Optional[
|
|
703
|
-
datetime: Optional[
|
|
733
|
+
bbox: Optional[BBox] = None,
|
|
734
|
+
datetime: Optional[DateTimeType] = None,
|
|
704
735
|
limit: int = 10,
|
|
705
736
|
token: str = None,
|
|
706
737
|
**kwargs,
|
|
@@ -10,7 +10,7 @@ from stac_pydantic.shared import MimeTypes
|
|
|
10
10
|
# These can be inferred from the item/collection so they aren't included in the database
|
|
11
11
|
# Instead they are dynamically generated when querying the database using the
|
|
12
12
|
# classes defined below
|
|
13
|
-
INFERRED_LINK_RELS = ["self", "item", "parent", "collection", "root"]
|
|
13
|
+
INFERRED_LINK_RELS = ["self", "item", "parent", "collection", "root", "items"]
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def filter_links(links: List[Dict]) -> List[Dict]:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""rfc3339."""
|
|
2
2
|
import re
|
|
3
3
|
from datetime import datetime, timezone
|
|
4
|
-
from typing import Optional, Tuple
|
|
4
|
+
from typing import Optional, Tuple, Union
|
|
5
5
|
|
|
6
6
|
import iso8601
|
|
7
7
|
from pystac.utils import datetime_to_str
|
|
@@ -11,6 +11,13 @@ RFC33339_PATTERN = (
|
|
|
11
11
|
r"(Z|([-+])(\d\d):(\d\d))$"
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
+
DateTimeType = Union[
|
|
15
|
+
datetime,
|
|
16
|
+
Tuple[datetime, datetime],
|
|
17
|
+
Tuple[datetime, None],
|
|
18
|
+
Tuple[None, datetime],
|
|
19
|
+
]
|
|
20
|
+
|
|
14
21
|
|
|
15
22
|
def rfc3339_str_to_datetime(s: str) -> datetime:
|
|
16
23
|
"""Convert a string conforming to RFC 3339 to a :class:`datetime.datetime`.
|
|
@@ -40,7 +47,7 @@ def rfc3339_str_to_datetime(s: str) -> datetime:
|
|
|
40
47
|
|
|
41
48
|
def str_to_interval(
|
|
42
49
|
interval: str,
|
|
43
|
-
) -> Optional[
|
|
50
|
+
) -> Optional[DateTimeType]:
|
|
44
51
|
"""Extract a tuple of datetimes from an interval string.
|
|
45
52
|
|
|
46
53
|
Interval strings are defined by
|
|
@@ -59,7 +66,10 @@ def str_to_interval(
|
|
|
59
66
|
raise ValueError("Empty interval string is invalid.")
|
|
60
67
|
|
|
61
68
|
values = interval.split("/")
|
|
62
|
-
if len(values)
|
|
69
|
+
if len(values) == 1:
|
|
70
|
+
# Single date for == date case
|
|
71
|
+
return rfc3339_str_to_datetime(values[0])
|
|
72
|
+
elif len(values) > 2:
|
|
63
73
|
raise ValueError(
|
|
64
74
|
f"Interval string '{interval}' contains more than one forward slash."
|
|
65
75
|
)
|
|
@@ -12,6 +12,7 @@ from typing import Any, Callable, Dict, Generator, List, Optional, Union
|
|
|
12
12
|
|
|
13
13
|
import attr
|
|
14
14
|
from geojson_pydantic.geometries import (
|
|
15
|
+
GeometryCollection,
|
|
15
16
|
LineString,
|
|
16
17
|
MultiLineString,
|
|
17
18
|
MultiPoint,
|
|
@@ -20,13 +21,13 @@ from geojson_pydantic.geometries import (
|
|
|
20
21
|
Polygon,
|
|
21
22
|
_GeometryBase,
|
|
22
23
|
)
|
|
23
|
-
from pydantic import BaseModel, ConstrainedInt, validator
|
|
24
|
+
from pydantic import BaseModel, ConstrainedInt, Field, validator
|
|
24
25
|
from pydantic.errors import NumberNotGtError
|
|
25
26
|
from pydantic.validators import int_validator
|
|
26
27
|
from stac_pydantic.shared import BBox
|
|
27
28
|
from stac_pydantic.utils import AutoValueEnum
|
|
28
29
|
|
|
29
|
-
from stac_fastapi.types.rfc3339 import
|
|
30
|
+
from stac_fastapi.types.rfc3339 import DateTimeType, str_to_interval
|
|
30
31
|
|
|
31
32
|
# Be careful: https://github.com/samuelcolvin/pydantic/issues/1423#issuecomment-642797287
|
|
32
33
|
NumType = Union[float, int]
|
|
@@ -82,6 +83,14 @@ def str2list(x: str) -> Optional[List]:
|
|
|
82
83
|
return x.split(",")
|
|
83
84
|
|
|
84
85
|
|
|
86
|
+
def str2bbox(x: str) -> Optional[BBox]:
|
|
87
|
+
"""Convert string to BBox based on , delimiter."""
|
|
88
|
+
if x:
|
|
89
|
+
t = tuple(float(v) for v in str2list(x))
|
|
90
|
+
assert len(t) == 4
|
|
91
|
+
return t
|
|
92
|
+
|
|
93
|
+
|
|
85
94
|
@attr.s # type:ignore
|
|
86
95
|
class APIRequest(abc.ABC):
|
|
87
96
|
"""Generic API Request base class."""
|
|
@@ -98,9 +107,9 @@ class BaseSearchGetRequest(APIRequest):
|
|
|
98
107
|
|
|
99
108
|
collections: Optional[str] = attr.ib(default=None, converter=str2list)
|
|
100
109
|
ids: Optional[str] = attr.ib(default=None, converter=str2list)
|
|
101
|
-
bbox: Optional[
|
|
102
|
-
intersects: Optional[str] = attr.ib(default=None)
|
|
103
|
-
datetime: Optional[
|
|
110
|
+
bbox: Optional[BBox] = attr.ib(default=None, converter=str2bbox)
|
|
111
|
+
intersects: Optional[str] = attr.ib(default=None, converter=str2list)
|
|
112
|
+
datetime: Optional[DateTimeType] = attr.ib(default=None, converter=str_to_interval)
|
|
104
113
|
limit: Optional[int] = attr.ib(default=10)
|
|
105
114
|
|
|
106
115
|
|
|
@@ -119,22 +128,28 @@ class BaseSearchPostRequest(BaseModel):
|
|
|
119
128
|
ids: Optional[List[str]]
|
|
120
129
|
bbox: Optional[BBox]
|
|
121
130
|
intersects: Optional[
|
|
122
|
-
Union[
|
|
131
|
+
Union[
|
|
132
|
+
Point,
|
|
133
|
+
MultiPoint,
|
|
134
|
+
LineString,
|
|
135
|
+
MultiLineString,
|
|
136
|
+
Polygon,
|
|
137
|
+
MultiPolygon,
|
|
138
|
+
GeometryCollection,
|
|
139
|
+
]
|
|
123
140
|
]
|
|
124
|
-
datetime: Optional[
|
|
125
|
-
limit: Optional[Limit] = 10
|
|
141
|
+
datetime: Optional[DateTimeType]
|
|
142
|
+
limit: Optional[Limit] = Field(default=10)
|
|
126
143
|
|
|
127
144
|
@property
|
|
128
145
|
def start_date(self) -> Optional[datetime]:
|
|
129
146
|
"""Extract the start date from the datetime string."""
|
|
130
|
-
|
|
131
|
-
return interval[0] if interval else None
|
|
147
|
+
return self.datetime[0] if self.datetime else None
|
|
132
148
|
|
|
133
149
|
@property
|
|
134
150
|
def end_date(self) -> Optional[datetime]:
|
|
135
151
|
"""Extract the end date from the datetime string."""
|
|
136
|
-
|
|
137
|
-
return interval[1] if interval else None
|
|
152
|
+
return self.datetime[1] if self.datetime else None
|
|
138
153
|
|
|
139
154
|
@validator("intersects")
|
|
140
155
|
def validate_spatial(cls, v, values):
|
|
@@ -143,10 +158,12 @@ class BaseSearchPostRequest(BaseModel):
|
|
|
143
158
|
raise ValueError("intersects and bbox parameters are mutually exclusive")
|
|
144
159
|
return v
|
|
145
160
|
|
|
146
|
-
@validator("bbox")
|
|
147
|
-
def validate_bbox(cls, v: BBox):
|
|
161
|
+
@validator("bbox", pre=True)
|
|
162
|
+
def validate_bbox(cls, v: Union[str, BBox]) -> BBox:
|
|
148
163
|
"""Check order of supplied bbox coordinates."""
|
|
149
164
|
if v:
|
|
165
|
+
if type(v) == str:
|
|
166
|
+
v = str2bbox(v)
|
|
150
167
|
# Validate order
|
|
151
168
|
if len(v) == 4:
|
|
152
169
|
xmin, ymin, xmax, ymax = v
|
|
@@ -173,34 +190,11 @@ class BaseSearchPostRequest(BaseModel):
|
|
|
173
190
|
|
|
174
191
|
return v
|
|
175
192
|
|
|
176
|
-
@validator("datetime")
|
|
177
|
-
def validate_datetime(cls, v):
|
|
178
|
-
"""
|
|
179
|
-
if
|
|
180
|
-
|
|
181
|
-
else:
|
|
182
|
-
# Single date is interpreted as end date
|
|
183
|
-
values = ["..", v]
|
|
184
|
-
|
|
185
|
-
dates = []
|
|
186
|
-
for value in values:
|
|
187
|
-
if value == ".." or value == "":
|
|
188
|
-
dates.append("..")
|
|
189
|
-
continue
|
|
190
|
-
|
|
191
|
-
# throws ValueError if invalid RFC 3339 string
|
|
192
|
-
dates.append(rfc3339_str_to_datetime(value))
|
|
193
|
-
|
|
194
|
-
if dates[0] == ".." and dates[1] == "..":
|
|
195
|
-
raise ValueError(
|
|
196
|
-
"Invalid datetime range, both ends of range may not be open"
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
if ".." not in dates and dates[0] > dates[1]:
|
|
200
|
-
raise ValueError(
|
|
201
|
-
"Invalid datetime range, must match format (begin_date, end_date)"
|
|
202
|
-
)
|
|
203
|
-
|
|
193
|
+
@validator("datetime", pre=True)
|
|
194
|
+
def validate_datetime(cls, v: Union[str, DateTimeType]) -> DateTimeType:
|
|
195
|
+
"""Parse datetime."""
|
|
196
|
+
if type(v) == str:
|
|
197
|
+
v = str_to_interval(v)
|
|
204
198
|
return v
|
|
205
199
|
|
|
206
200
|
@property
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import sys
|
|
3
3
|
from typing import Any, Dict, List, Literal, Optional, Union
|
|
4
4
|
|
|
5
|
+
from stac_pydantic.shared import BBox
|
|
6
|
+
|
|
5
7
|
# Avoids a Pydantic error:
|
|
6
8
|
# TypeError: You should use `typing_extensions.TypedDict` instead of
|
|
7
9
|
# `typing.TypedDict` with Python < 3.9.2. Without it, there is no way to
|
|
@@ -64,7 +66,7 @@ class Item(TypedDict, total=False):
|
|
|
64
66
|
stac_extensions: Optional[List[str]]
|
|
65
67
|
id: str
|
|
66
68
|
geometry: Dict[str, Any]
|
|
67
|
-
bbox:
|
|
69
|
+
bbox: BBox
|
|
68
70
|
properties: Dict[str, Any]
|
|
69
71
|
links: List[Dict[str, Any]]
|
|
70
72
|
assets: Dict[str, Any]
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Library version."""
|
|
2
|
-
__version__ = "2.
|
|
2
|
+
__version__ = "2.5.0"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: stac-fastapi.types
|
|
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: fastapi>=0.73.0
|
|
22
|
+
Requires-Dist: attrs
|
|
23
|
+
Requires-Dist: pydantic[dotenv]<2
|
|
24
|
+
Requires-Dist: stac_pydantic==2.0.*
|
|
25
|
+
Requires-Dist: pystac==1.*
|
|
26
|
+
Requires-Dist: iso8601<2.2.0,>=1.0.2
|
|
27
|
+
Provides-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
|
+
Provides-Extra: docs
|
|
34
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
35
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
36
|
+
Requires-Dist: pdocs; extra == "docs"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pydantic import ValidationError
|
|
3
|
+
|
|
4
|
+
from stac_fastapi.types.search import BaseSearchPostRequest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.parametrize("value", [0, -1])
|
|
8
|
+
def test_limit_ge(value):
|
|
9
|
+
with pytest.raises(ValidationError):
|
|
10
|
+
BaseSearchPostRequest(limit=value)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.parametrize("value", [1, 10_000])
|
|
14
|
+
def test_limit(value):
|
|
15
|
+
search = BaseSearchPostRequest(limit=value)
|
|
16
|
+
assert search.limit == value
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.mark.parametrize("value", [10_001, 100_000, 1_000_000])
|
|
20
|
+
def test_limit_le(value):
|
|
21
|
+
search = BaseSearchPostRequest(limit=value)
|
|
22
|
+
assert search.limit == 10_000
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from datetime import timezone
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from stac_fastapi.types.rfc3339 import (
|
|
6
|
+
now_in_utc,
|
|
7
|
+
now_to_rfc3339_str,
|
|
8
|
+
rfc3339_str_to_datetime,
|
|
9
|
+
str_to_interval,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
invalid_datetimes = [
|
|
13
|
+
"1985-04-12", # date only
|
|
14
|
+
"1937-01-01T12:00:27.87+0100", # invalid TZ format, no sep :
|
|
15
|
+
"37-01-01T12:00:27.87Z", # invalid year, must be 4 digits
|
|
16
|
+
"1985-12-12T23:20:50.52", # no TZ
|
|
17
|
+
"21985-12-12T23:20:50.52Z", # year must be 4 digits
|
|
18
|
+
"1985-13-12T23:20:50.52Z", # month > 12
|
|
19
|
+
"1985-12-32T23:20:50.52Z", # day > 31
|
|
20
|
+
"1985-12-01T25:20:50.52Z", # hour > 24
|
|
21
|
+
"1985-12-01T00:60:50.52Z", # minute > 59
|
|
22
|
+
"1985-12-01T00:06:61.52Z", # second > 60
|
|
23
|
+
"1985-04-12T23:20:50.Z", # fractional sec . but no frac secs
|
|
24
|
+
"1985-04-12T23:20:50,Z", # fractional sec , but no frac secs
|
|
25
|
+
"1990-12-31T23:59:61Z", # second > 60 w/o fractional seconds
|
|
26
|
+
"1985-04-12T23:20:50,52Z", # comma as frac sec sep allowed in ISO8601 but not RFC3339
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
valid_datetimes = [
|
|
30
|
+
"1985-04-12T23:20:50.52Z",
|
|
31
|
+
"1996-12-19T16:39:57-00:00",
|
|
32
|
+
"1996-12-19T16:39:57+00:00",
|
|
33
|
+
"1996-12-19T16:39:57-08:00",
|
|
34
|
+
"1996-12-19T16:39:57+08:00",
|
|
35
|
+
"1937-01-01T12:00:27.87+01:00",
|
|
36
|
+
"1985-04-12T23:20:50.52Z",
|
|
37
|
+
"1937-01-01T12:00:27.8710+01:00",
|
|
38
|
+
"1937-01-01T12:00:27.8+01:00",
|
|
39
|
+
"1937-01-01T12:00:27.8Z",
|
|
40
|
+
"2020-07-23T00:00:00.000+03:00",
|
|
41
|
+
"2020-07-23T00:00:00+03:00",
|
|
42
|
+
"1985-04-12t23:20:50.000z",
|
|
43
|
+
"2020-07-23T00:00:00Z",
|
|
44
|
+
"2020-07-23T00:00:00.0Z",
|
|
45
|
+
"2020-07-23T00:00:00.01Z",
|
|
46
|
+
"2020-07-23T00:00:00.012Z",
|
|
47
|
+
"2020-07-23T00:00:00.0123Z",
|
|
48
|
+
"2020-07-23T00:00:00.01234Z",
|
|
49
|
+
"2020-07-23T00:00:00.012345Z",
|
|
50
|
+
"2020-07-23T00:00:00.0123456Z",
|
|
51
|
+
"2020-07-23T00:00:00.01234567Z",
|
|
52
|
+
"2020-07-23T00:00:00.012345678Z",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
invalid_intervals = [
|
|
56
|
+
"/"
|
|
57
|
+
"../"
|
|
58
|
+
"/.."
|
|
59
|
+
"../.."
|
|
60
|
+
"/1984-04-12T23:20:50.52Z/1985-04-12T23:20:50.52Z", # extra start /
|
|
61
|
+
"1984-04-12T23:20:50.52Z/1985-04-12T23:20:50.52Z/", # extra end /
|
|
62
|
+
"1986-04-12T23:20:50.52Z/1985-04-12T23:20:50.52Z", # start > end
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
valid_intervals = [
|
|
66
|
+
"../1985-04-12T23:20:50.52Z",
|
|
67
|
+
"1985-04-12T23:20:50.52Z/..",
|
|
68
|
+
"/1985-04-12T23:20:50.52Z",
|
|
69
|
+
"1985-04-12T23:20:50.52Z/",
|
|
70
|
+
"1985-04-12T23:20:50.52Z/1986-04-12T23:20:50.52Z",
|
|
71
|
+
"1985-04-12T23:20:50.52+01:00/1986-04-12T23:20:50.52+01:00",
|
|
72
|
+
"1985-04-12T23:20:50.52-01:00/1986-04-12T23:20:50.52-01:00",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.mark.parametrize("test_input", invalid_datetimes)
|
|
77
|
+
def test_parse_invalid_str_to_datetime(test_input):
|
|
78
|
+
with pytest.raises(ValueError):
|
|
79
|
+
rfc3339_str_to_datetime(test_input)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.mark.parametrize("test_input", valid_datetimes)
|
|
83
|
+
def test_parse_valid_str_to_datetime(test_input):
|
|
84
|
+
assert rfc3339_str_to_datetime(test_input)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.mark.parametrize("test_input", invalid_intervals)
|
|
88
|
+
def test_parse_invalid_interval_to_datetime(test_input):
|
|
89
|
+
with pytest.raises(ValueError):
|
|
90
|
+
str_to_interval(test_input)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@pytest.mark.parametrize("test_input", valid_intervals)
|
|
94
|
+
def test_parse_valid_interval_to_datetime(test_input):
|
|
95
|
+
assert str_to_interval(test_input)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_now_functions() -> None:
|
|
99
|
+
now1 = now_in_utc()
|
|
100
|
+
now2 = now_in_utc()
|
|
101
|
+
|
|
102
|
+
assert now1 < now2
|
|
103
|
+
assert now1.tzinfo == timezone.utc
|
|
104
|
+
|
|
105
|
+
rfc3339_str_to_datetime(now_to_rfc3339_str())
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: stac-fastapi.types
|
|
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.types
|
|
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
|
|
File without changes
|
|
File without changes
|
{stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi.types.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{stac-fastapi.types-2.4.8 → stac_fastapi_types-2.5.0}/stac_fastapi.types.egg-info/top_level.txt
RENAMED
|
File without changes
|