Skip to content
This repository was archived by the owner on Jul 21, 2025. It is now read-only.

Commit e6a05e1

Browse files
authored
feat: stac-geoparquet backend (#29)
- Closes #28
1 parent f00ad42 commit e6a05e1

File tree

14 files changed

+167
-15
lines changed

14 files changed

+167
-15
lines changed

data/naip.parquet

1.82 MB
Binary file not shown.

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ requires-python = ">=3.12"
77
dependencies = [
88
"fastapi[standard]>=0.115.6",
99
"pydantic>=2.10.4",
10+
"pydantic-settings>=2.7.1",
11+
"stacrs>=0.3.0",
1012
]
1113

1214
[dependency-groups]
@@ -18,6 +20,7 @@ files = ["**/*.py"]
1820

1921
[tool.pytest.ini_options]
2022
asyncio_mode = "auto"
23+
asyncio_default_fixture_loop_scope = "function"
2124

2225
[tool.ruff.lint]
2326
select = ["E", "F", "I"]

scripts/dev

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
set -e
44

5-
uv run fastapi dev
5+
TISTAC_BACKEND=data/naip.parquet uv run fastapi dev

src/tistac/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .settings import Settings
2+
3+
__all__ = ["Settings"]

src/tistac/app.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
1-
from fastapi import FastAPI
1+
from typing import Annotated
22

3+
from fastapi import Depends, FastAPI, Query
4+
5+
from .backend import Backend
36
from .item_collection import ItemCollection
47
from .root import Root
8+
from .search import GetSearch, Search
9+
from .settings import Settings
510

611

7-
def build() -> FastAPI:
12+
def build(settings: Settings | None = None) -> FastAPI:
813
"""Builds a new TiStac application."""
914

15+
if settings is None:
16+
settings = Settings() # type: ignore
17+
18+
async def get_settings() -> Settings:
19+
return settings
20+
21+
backend = settings.get_backend()
22+
23+
async def get_backend() -> Backend:
24+
return backend
25+
1026
app = FastAPI()
1127

1228
@app.get("/")
@@ -16,15 +32,24 @@ async def root() -> Root:
1632
return Root()
1733

1834
@app.get("/search")
19-
async def get_search() -> ItemCollection:
35+
async def get_search(
36+
get_search: Annotated[GetSearch, Query()],
37+
backend: Annotated[Backend, Depends(get_backend)],
38+
settings: Annotated[Settings, Depends(get_settings)],
39+
) -> ItemCollection:
2040
"""Searches this STAC API via a GET request."""
21-
22-
return ItemCollection()
41+
search = get_search.into_search()
42+
search = settings.update_search(search)
43+
return await backend.search(search)
2344

2445
@app.post("/search")
25-
async def post_search() -> ItemCollection:
46+
async def post_search(
47+
search: Search,
48+
backend: Annotated[Backend, Depends(get_backend)],
49+
settings: Annotated[Settings, Depends(get_settings)],
50+
) -> ItemCollection:
2651
"""Searches this STAC API via a POST request."""
27-
28-
return ItemCollection()
52+
search = settings.update_search(search)
53+
return await backend.search(search)
2954

3055
return app

src/tistac/backend.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from abc import ABC, abstractmethod
2+
3+
from .item_collection import ItemCollection
4+
from .search import Search
5+
6+
7+
class Backend(ABC):
8+
"""A TiStac backend."""
9+
10+
@abstractmethod
11+
async def search(self, search: Search) -> ItemCollection:
12+
"""Searches this backend."""

src/tistac/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
DEFAULT_LIMIT = 20
2+
"""The default page size."""

src/tistac/item_collection.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Any
2+
13
from pydantic import BaseModel
24

35

@@ -7,3 +9,5 @@ class ItemCollection(BaseModel):
79
The features of this collection may not be valid STAC items, if the `fields`
810
extension is used.
911
"""
12+
13+
features: list[dict[str, Any]]

src/tistac/search.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from pydantic import BaseModel, Field
2+
3+
4+
class Search(BaseModel):
5+
"""A STAC API POST search."""
6+
7+
limit: int | None = Field(default=None)
8+
"""The maximum number of items per page."""
9+
10+
11+
class GetSearch(BaseModel):
12+
"""A STAC API GET search."""
13+
14+
def into_search(self) -> Search:
15+
"""Converts this GET search into a POST search."""
16+
return Search()

src/tistac/settings.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from pydantic import Field
2+
from pydantic_settings import BaseSettings, SettingsConfigDict
3+
4+
from .backend import Backend
5+
from .constants import DEFAULT_LIMIT
6+
from .search import Search
7+
8+
9+
class Settings(BaseSettings):
10+
"""TiStac settings."""
11+
12+
model_config = SettingsConfigDict(env_prefix="tistac_")
13+
14+
backend: str
15+
default_limit: int = Field(default=DEFAULT_LIMIT)
16+
17+
def get_backend(self) -> Backend:
18+
"""Returns the configured backend."""
19+
from .stac_geoparquet import StacGeoparquetBackend
20+
21+
return StacGeoparquetBackend(self.backend)
22+
23+
def update_search(self, search: Search) -> Search:
24+
"""Updates a search with some default settings."""
25+
if search.limit is None:
26+
search.limit = self.default_limit
27+
return search

0 commit comments

Comments
 (0)