From 2d396098fff7be6afe90313cd9e6c3fc6cc73e6b Mon Sep 17 00:00:00 2001 From: ShavrinAleksei Date: Sat, 27 Dec 2025 09:34:39 +0000 Subject: [PATCH 01/15] chore: add litestar, granian and msgspec dependencies --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 06cc2ec..dc42c04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,9 @@ requires-python = ">=3.14" classifiers = [ "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.14" ] dynamic = [ "version" ] dependencies = [ + "granian>=2.6", + "litestar>=2.19", + "msgspec>=0.20", "pydantic-settings>=2.12", ] From 7a10073e086e5ee62d63b472abdddebe210d9035 Mon Sep 17 00:00:00 2001 From: ShavrinAleksei Date: Sat, 27 Dec 2025 09:43:38 +0000 Subject: [PATCH 02/15] feat(metadata): add api version --- src/app/__init__.py | 7 +++++-- src/app/__meta__.py | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/__init__.py b/src/app/__init__.py index 1e4e3d4..9708609 100644 --- a/src/app/__init__.py +++ b/src/app/__init__.py @@ -1,4 +1,7 @@ -from .__meta__ import __version__ +from .__meta__ import __api_version__, __version__ -__all__ = ("__version__",) +__all__ = ( + "__api_version__", + "__version__", +) diff --git a/src/app/__meta__.py b/src/app/__meta__.py index 0beccff..3a8c66c 100644 --- a/src/app/__meta__.py +++ b/src/app/__meta__.py @@ -1 +1,2 @@ __version__ = "0.0.0a0" +__api_version__ = "0.0.0a0" From 46f4d24c2b4cbc2ad480e93ff0490ce89181f5b0 Mon Sep 17 00:00:00 2001 From: ShavrinAleksei Date: Sat, 27 Dec 2025 09:46:11 +0000 Subject: [PATCH 03/15] feat(api): add health response schema --- src/app/interface/__init__.py | 0 src/app/interface/http/__init__.py | 0 src/app/interface/http/schema/__init__.py | 0 src/app/interface/http/schema/system.py | 9 +++++++++ 4 files changed, 9 insertions(+) create mode 100644 src/app/interface/__init__.py create mode 100644 src/app/interface/http/__init__.py create mode 100644 src/app/interface/http/schema/__init__.py create mode 100644 src/app/interface/http/schema/system.py diff --git a/src/app/interface/__init__.py b/src/app/interface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/app/interface/http/__init__.py b/src/app/interface/http/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/app/interface/http/schema/__init__.py b/src/app/interface/http/schema/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/app/interface/http/schema/system.py b/src/app/interface/http/schema/system.py new file mode 100644 index 0000000..46677d3 --- /dev/null +++ b/src/app/interface/http/schema/system.py @@ -0,0 +1,9 @@ +import typing + +from msgspec import Struct + + +class HealthResponse(Struct): + """Health response schema.""" + + status: typing.Literal["ok"] = "ok" From 5131e9e67b22a377803dec70ff996e923e628ad8 Mon Sep 17 00:00:00 2001 From: ShavrinAleksei Date: Sat, 27 Dec 2025 09:48:47 +0000 Subject: [PATCH 04/15] feat(api): export system schema --- src/app/interface/http/schema/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/interface/http/schema/__init__.py b/src/app/interface/http/schema/__init__.py index e69de29..747bb64 100644 --- a/src/app/interface/http/schema/__init__.py +++ b/src/app/interface/http/schema/__init__.py @@ -0,0 +1,4 @@ +from . import system + + +__all__ = ("system",) From f8b7b988bb269978b5c797b628a24f8ab50393c8 Mon Sep 17 00:00:00 2001 From: ShavrinAleksei Date: Sat, 27 Dec 2025 09:53:37 +0000 Subject: [PATCH 05/15] feat(api): add health check endpoint --- src/app/interface/http/controller/__init__.py | 4 ++++ src/app/interface/http/controller/system.py | 8 ++++++++ 2 files changed, 12 insertions(+) create mode 100644 src/app/interface/http/controller/__init__.py create mode 100644 src/app/interface/http/controller/system.py diff --git a/src/app/interface/http/controller/__init__.py b/src/app/interface/http/controller/__init__.py new file mode 100644 index 0000000..747bb64 --- /dev/null +++ b/src/app/interface/http/controller/__init__.py @@ -0,0 +1,4 @@ +from . import system + + +__all__ = ("system",) diff --git a/src/app/interface/http/controller/system.py b/src/app/interface/http/controller/system.py new file mode 100644 index 0000000..0514cc2 --- /dev/null +++ b/src/app/interface/http/controller/system.py @@ -0,0 +1,8 @@ +from litestar import get +from src.app.interface.http.schema.system import HealthResponse + + +@get("/health") +def health() -> HealthResponse: + """Check service availability.""" + return HealthResponse(status="ok") From ea84c271f8beab2956910c8bda888ae36ffd9b90 Mon Sep 17 00:00:00 2001 From: ShavrinAleksei Date: Sat, 27 Dec 2025 10:11:55 +0000 Subject: [PATCH 06/15] refactor(api): update import path --- src/app/interface/http/controller/system.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/interface/http/controller/system.py b/src/app/interface/http/controller/system.py index 0514cc2..5f787ab 100644 --- a/src/app/interface/http/controller/system.py +++ b/src/app/interface/http/controller/system.py @@ -1,5 +1,6 @@ from litestar import get -from src.app.interface.http.schema.system import HealthResponse + +from app.interface.http.schema.system import HealthResponse @get("/health") From bb43021364335ad54a44b9ed2263d0f869ccbc42 Mon Sep 17 00:00:00 2001 From: ShavrinAleksei Date: Sat, 27 Dec 2025 10:13:25 +0000 Subject: [PATCH 07/15] fix(api): add sync to thread false value for health sync endpoint --- src/app/interface/http/controller/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/interface/http/controller/system.py b/src/app/interface/http/controller/system.py index 5f787ab..1d56f6d 100644 --- a/src/app/interface/http/controller/system.py +++ b/src/app/interface/http/controller/system.py @@ -3,7 +3,7 @@ from app.interface.http.schema.system import HealthResponse -@get("/health") +@get("/health", sync_to_thread=False) def health() -> HealthResponse: """Check service availability.""" return HealthResponse(status="ok") From a5d283650b65a2f39cdb82b17fc06f7a45776e07 Mon Sep 17 00:00:00 2001 From: ShavrinAleksei Date: Sat, 27 Dec 2025 10:15:47 +0000 Subject: [PATCH 08/15] feat(http): add asgi app --- src/app/interface/http/__init__.py | 4 ++++ src/app/interface/http/asgi.py | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/app/interface/http/asgi.py diff --git a/src/app/interface/http/__init__.py b/src/app/interface/http/__init__.py index e69de29..4a9e627 100644 --- a/src/app/interface/http/__init__.py +++ b/src/app/interface/http/__init__.py @@ -0,0 +1,4 @@ +from . import asgi + + +__all__ = ("asgi",) diff --git a/src/app/interface/http/asgi.py b/src/app/interface/http/asgi.py new file mode 100644 index 0000000..41b53e1 --- /dev/null +++ b/src/app/interface/http/asgi.py @@ -0,0 +1,27 @@ +from litestar import Litestar +from litestar.openapi import OpenAPIConfig + +from app import __api_version__ +from app.interface.http.controller.system import health +from app.platform.config.loaders import load_logging_config +from app.platform.logging import configure_logging + + +def create_asgi_application() -> Litestar: + """Create and configure the ASGI application. + + Returns + ------- + Litestar + ASGI application. + """ + app = Litestar( + route_handlers=[ + health, + ], + openapi_config=OpenAPIConfig(title="Land Sight API", version=__api_version__), + ) + logging_config = load_logging_config() + configure_logging(config=logging_config) + + return app From 41313c333f9546a1d3afa294bb7d30fe74f12214 Mon Sep 17 00:00:00 2001 From: Vlad Korneev Date: Mon, 29 Dec 2025 14:19:54 +0300 Subject: [PATCH 09/15] chore: add granian[reload] to dev group dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index dc42c04..cdccf85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dependencies = [ [dependency-groups] dev = [ + "granian[reload]>=2.6", { include-group = "changelog" }, { include-group = "docs" }, { include-group = "lint" }, From 728726c442caf2a74efc968718590bb987a1db39 Mon Sep 17 00:00:00 2001 From: Vlad Korneev Date: Mon, 29 Dec 2025 14:21:34 +0300 Subject: [PATCH 10/15] chore: add run app dev serve command --- justfile | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/justfile b/justfile index 3cc5ad2..1dfa7e3 100644 --- a/justfile +++ b/justfile @@ -4,6 +4,9 @@ set dotenv-load := true VENV_DIR := ".venv" BUILD_DIR := "build" CACHE_DIR := ".cache" + +APP_HOST := env_var_or_default("APP_HOST", "127.0.0.1") +APP_PORT := env_var_or_default("APP_PORT", "8000") DOCS_HOST := env_var_or_default("DOCS_HOST", "127.0.0.1") DOCS_PORT := env_var_or_default("DOCS_PORT", "8008") @@ -60,3 +63,12 @@ changelog-build: changelog-fragment: @uv run --group=changelog towncrier create + +app-dev-serve: + @uv run --group=dev granian \ + --host="{{ APP_HOST }}" \ + --port="{{ APP_PORT }}" \ + --interface="asgi" \ + --factory \ + --reload \ + app.interface.http.asgi:create_asgi_application From 596cdd63b42f6828414a1d77d2ea6525fb61217a Mon Sep 17 00:00:00 2001 From: ShavrinAleksei Date: Mon, 29 Dec 2025 12:24:28 +0000 Subject: [PATCH 11/15] chore: add async pytest support --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index cdccf85..22a9c2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,6 +122,7 @@ lint.isort.lines-after-imports = 2 lint.pydocstyle.convention = "numpy" [tool.pytest.ini_options] +anyio_mode = "auto" cache_dir = ".cache/pytest" pythonpath = [ "src" ] testpaths = [ "tests" ] From b8b51c064befaee072b8181e6188420da3207652 Mon Sep 17 00:00:00 2001 From: ShavrinAleksei Date: Mon, 29 Dec 2025 12:25:38 +0000 Subject: [PATCH 12/15] chore: add pytest warning ignore for pydantic v1 --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 22a9c2d..1950beb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -137,6 +137,7 @@ log_cli = true log_cli_level = "INFO" filterwarnings = [ "error", + "ignore:Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater.:UserWarning", ] [tool.coverage.run] From 407cc387c4f7dc61d6f9974256d5230eabc54962 Mon Sep 17 00:00:00 2001 From: ShavrinAleksei Date: Mon, 29 Dec 2025 12:31:20 +0000 Subject: [PATCH 13/15] test(integration): add conftest file with app and test_client fixtures --- tests/integration/conftest.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/integration/conftest.py diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 0000000..4e7e922 --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,27 @@ +import typing + +import pytest +from litestar.testing import AsyncTestClient + +from app.interface.http.asgi import create_asgi_application + + +if typing.TYPE_CHECKING: + from collections.abc import AsyncIterator + + from litestar import Litestar + + +@pytest.fixture(scope="session") +def app() -> Litestar: + """Provide Litestar application instance for tests.""" + app = create_asgi_application() + app.debug = True + return app + + +@pytest.fixture(scope="function") +async def test_client(app: Litestar) -> AsyncIterator[AsyncTestClient[Litestar]]: + """Provide async test client bound to the Litestar app.""" + async with AsyncTestClient(app=app) as client: + yield client From d6ffaf4589102d78dc09090c4b9d4c4ccc1f9867 Mon Sep 17 00:00:00 2001 From: ShavrinAleksei Date: Mon, 29 Dec 2025 12:34:02 +0000 Subject: [PATCH 14/15] test(integration): add health check endpoint test --- .../interface/http/controller/test_system.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/integration/interface/http/controller/test_system.py diff --git a/tests/integration/interface/http/controller/test_system.py b/tests/integration/interface/http/controller/test_system.py new file mode 100644 index 0000000..43060fa --- /dev/null +++ b/tests/integration/interface/http/controller/test_system.py @@ -0,0 +1,14 @@ +import typing + +from litestar import Litestar, status_codes + + +if typing.TYPE_CHECKING: + from litestar.testing import AsyncTestClient + + +async def test_health_check_endpoint(test_client: AsyncTestClient[Litestar]) -> None: + """Health check endpoint returns HTTP 200 and ok status.""" + response = await test_client.get("/health") + assert response.status_code == status_codes.HTTP_200_OK + assert response.json() == {"status": "ok"} From f349df9a857c71e905694d1b8dff422f468b74b6 Mon Sep 17 00:00:00 2001 From: ShavrinAleksei Date: Mon, 29 Dec 2025 13:10:55 +0000 Subject: [PATCH 15/15] chore: update env.example --- .env.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.env.example b/.env.example index 9bb6bfd..20b2efb 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,7 @@ LOGGING_LEVEL=INFO LOGGING_FORMATTER=pretty + +APP_HOST=127.0.0.1 +APP_PORT=8000 +DOCS_HOST=127.0.0.1 +DOCS_PORT=8008