-
Notifications
You must be signed in to change notification settings - Fork 107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Updating to stac pydantic 3 #627
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
FROM python:3.8-slim as base | ||
FROM python:3.9-slim as base | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agree to remove python 3.8 but then we should use python 3.11 IMO |
||
# Any python libraries that require system libraries to be installed will likely | ||
# need the following packages in order to build | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
"""Fastapi app creation.""" | ||
from typing import Any, Dict, List, Optional, Tuple, Type, Union | ||
|
||
from typing import Any, Optional, Tuple, Type, Union | ||
|
||
import attr | ||
from brotli_asgi import BrotliMiddleware | ||
|
@@ -9,7 +10,7 @@ | |
from stac_pydantic import Collection, Item, ItemCollection | ||
from stac_pydantic.api import ConformanceClasses, LandingPage | ||
from stac_pydantic.api.collections import Collections | ||
from stac_pydantic.version import STAC_VERSION | ||
from stac_pydantic.api.version import STAC_API_VERSION | ||
from starlette.responses import JSONResponse, Response | ||
|
||
from stac_fastapi.api.errors import DEFAULT_STATUS_CODES, add_exception_handlers | ||
|
@@ -67,8 +68,8 @@ class StacApi: | |
|
||
settings: ApiSettings = attr.ib() | ||
client: Union[AsyncBaseCoreClient, BaseCoreClient] = attr.ib() | ||
extensions: List[ApiExtension] = attr.ib(default=attr.Factory(list)) | ||
exceptions: Dict[Type[Exception], int] = attr.ib( | ||
extensions: list[ApiExtension] = attr.ib(default=attr.Factory(list)) | ||
exceptions: dict[Type[Exception], int] = attr.ib( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI is using https://stackoverflow.com/questions/37087457/difference-between-defining-typing-dict-and-dict There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same goes for List There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vincentsarago I found that during the |
||
default=attr.Factory(lambda: DEFAULT_STATUS_CODES) | ||
) | ||
app: FastAPI = attr.ib( | ||
|
@@ -85,7 +86,7 @@ class StacApi: | |
router: APIRouter = attr.ib(default=attr.Factory(APIRouter)) | ||
title: str = attr.ib(default="stac-fastapi") | ||
api_version: str = attr.ib(default="0.1") | ||
stac_version: str = attr.ib(default=STAC_VERSION) | ||
stac_version: str = attr.ib(default=STAC_API_VERSION) | ||
description: str = attr.ib(default="stac-fastapi") | ||
search_get_request_model: Type[BaseSearchGetRequest] = attr.ib( | ||
default=BaseSearchGetRequest | ||
|
@@ -95,12 +96,12 @@ class StacApi: | |
) | ||
pagination_extension = attr.ib(default=TokenPaginationExtension) | ||
response_class: Type[Response] = attr.ib(default=JSONResponse) | ||
middlewares: List = attr.ib( | ||
middlewares: list = attr.ib( | ||
default=attr.Factory( | ||
lambda: [BrotliMiddleware, CORSMiddleware, ProxyHeaderMiddleware] | ||
) | ||
) | ||
route_dependencies: List[Tuple[List[Scope], List[Depends]]] = attr.ib(default=[]) | ||
route_dependencies: list[Tuple[list[Scope], list[Depends]]] = attr.ib(default=[]) | ||
|
||
def get_extension(self, extension: Type[ApiExtension]) -> Optional[ApiExtension]: | ||
"""Get an extension. | ||
|
@@ -125,9 +126,9 @@ def register_landing_page(self): | |
self.router.add_api_route( | ||
name="Landing Page", | ||
path="/", | ||
response_model=LandingPage | ||
if self.settings.enable_response_models | ||
else None, | ||
response_model=( | ||
LandingPage if self.settings.enable_response_models else None | ||
), | ||
response_class=self.response_class, | ||
response_model_exclude_unset=False, | ||
response_model_exclude_none=True, | ||
|
@@ -146,9 +147,9 @@ def register_conformance_classes(self): | |
self.router.add_api_route( | ||
name="Conformance Classes", | ||
path="/conformance", | ||
response_model=ConformanceClasses | ||
if self.settings.enable_response_models | ||
else None, | ||
response_model=( | ||
ConformanceClasses if self.settings.enable_response_models else None | ||
), | ||
response_class=self.response_class, | ||
response_model_exclude_unset=True, | ||
response_model_exclude_none=True, | ||
|
@@ -187,9 +188,11 @@ def register_post_search(self): | |
self.router.add_api_route( | ||
name="Search", | ||
path="/search", | ||
response_model=(ItemCollection if not fields_ext else None) | ||
if self.settings.enable_response_models | ||
else None, | ||
response_model=( | ||
(ItemCollection if not fields_ext else None) | ||
if self.settings.enable_response_models | ||
else None | ||
), | ||
response_class=GeoJSONResponse, | ||
response_model_exclude_unset=True, | ||
response_model_exclude_none=True, | ||
|
@@ -209,9 +212,11 @@ def register_get_search(self): | |
self.router.add_api_route( | ||
name="Search", | ||
path="/search", | ||
response_model=(ItemCollection if not fields_ext else None) | ||
if self.settings.enable_response_models | ||
else None, | ||
response_model=( | ||
(ItemCollection if not fields_ext else None) | ||
if self.settings.enable_response_models | ||
else None | ||
), | ||
response_class=GeoJSONResponse, | ||
response_model_exclude_unset=True, | ||
response_model_exclude_none=True, | ||
|
@@ -230,9 +235,9 @@ def register_get_collections(self): | |
self.router.add_api_route( | ||
name="Get Collections", | ||
path="/collections", | ||
response_model=Collections | ||
if self.settings.enable_response_models | ||
else None, | ||
response_model=( | ||
Collections if self.settings.enable_response_models else None | ||
), | ||
response_class=self.response_class, | ||
response_model_exclude_unset=True, | ||
response_model_exclude_none=True, | ||
|
@@ -280,9 +285,9 @@ def register_get_item_collection(self): | |
self.router.add_api_route( | ||
name="Get ItemCollection", | ||
path="/collections/{collection_id}/items", | ||
response_model=ItemCollection | ||
if self.settings.enable_response_models | ||
else None, | ||
response_model=( | ||
ItemCollection if self.settings.enable_response_models else None | ||
), | ||
response_class=GeoJSONResponse, | ||
response_model_exclude_unset=True, | ||
response_model_exclude_none=True, | ||
|
@@ -318,7 +323,7 @@ def register_core(self): | |
self.register_get_collection() | ||
self.register_get_item_collection() | ||
|
||
def customize_openapi(self) -> Optional[Dict[str, Any]]: | ||
def customize_openapi(self) -> Optional[dict[str, Any]]: | ||
"""Customize openapi schema.""" | ||
if self.app.openapi_schema: | ||
return self.app.openapi_schema | ||
|
@@ -346,7 +351,7 @@ async def ping(): | |
self.app.include_router(mgmt_router, tags=["Liveliness/Readiness"]) | ||
|
||
def add_route_dependencies( | ||
self, scopes: List[Scope], dependencies=List[Depends] | ||
self, scopes: list[Scope], dependencies=list[Depends] | ||
) -> None: | ||
"""Add custom dependencies to routes. | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
"""Application settings.""" | ||
|
||
import enum | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,9 +4,8 @@ | |
from typing import Optional, Type, Union | ||
|
||
import attr | ||
from fastapi import Body, Path | ||
from fastapi import Path | ||
from pydantic import BaseModel, create_model | ||
from pydantic.fields import UndefinedType | ||
|
||
from stac_fastapi.types.extension import ApiExtension | ||
from stac_fastapi.types.search import ( | ||
|
@@ -44,31 +43,8 @@ def create_request_model( | |
# Handle POST requests | ||
elif all([issubclass(m, BaseModel) for m in models]): | ||
for model in models: | ||
for k, v in model.__fields__.items(): | ||
field_info = v.field_info | ||
body = Body( | ||
None | ||
if isinstance(field_info.default, UndefinedType) | ||
else field_info.default, | ||
default_factory=field_info.default_factory, | ||
alias=field_info.alias, | ||
alias_priority=field_info.alias_priority, | ||
title=field_info.title, | ||
description=field_info.description, | ||
const=field_info.const, | ||
gt=field_info.gt, | ||
ge=field_info.ge, | ||
lt=field_info.lt, | ||
le=field_info.le, | ||
multiple_of=field_info.multiple_of, | ||
min_items=field_info.min_items, | ||
max_items=field_info.max_items, | ||
min_length=field_info.min_length, | ||
max_length=field_info.max_length, | ||
regex=field_info.regex, | ||
extra=field_info.extra, | ||
) | ||
fields[k] = (v.outer_type_, body) | ||
for k, field_info in model.model_fields.items(): | ||
fields[k] = (field_info.annotation, field_info) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @thomas-maschler sorry I should have checked before putting in the pull request it looks like most of our changes are the same. I found the |
||
return create_model(model_name, **fields, __base__=base_model) | ||
|
||
raise TypeError("Mixed Request Model types. Check extension request types.") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed? pydantic 2 does support python 3.8 https://github.com/pydantic/pydantic/blob/main/pyproject.toml#L65