From 77cc9549eccd0353d64380a7a415438cc57bf097 Mon Sep 17 00:00:00 2001 From: OctaveLauby Date: Wed, 3 Jun 2026 11:28:10 +0200 Subject: [PATCH 1/6] doc: add uv workspaces to todo --- CONTRIBUTING.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e9a37e..9ac7e5d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,6 +39,29 @@ make ci - ADD Observability (OpenTelemetry - Jaeger) - ADD Settings management (dotenv or config files) - ADD Proper Error management (API Problem) +- UPDATE code structure and use [uv workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces/) + + ``` + . + ├── ... + ├── .venv/... + ├── pyproject.toml + ├── uv.lock + ├── apps + │ └── olapi/ + │ ├── src/olapi/... + │ ├── tests/... + │ ├── pyproject.toml + │ └── README.md + └── packages + ├── ... + └── authentication/ + ├── src/authentication/... + ├── tests/... + ├── pyproject.toml + └── README.md + ``` + - REWORK Authentication - Activate Keycloak secret - Remove auth endpoints and let keycloak handle the authentication (e.g. `standardFlowEnabled=true & directAccessGrantsEnabled=false` in realm.json) From dc4a511e56437e6a0ab7b2cf37428fa527dfcfbf Mon Sep 17 00:00:00 2001 From: OctaveLauby Date: Wed, 3 Jun 2026 11:37:06 +0200 Subject: [PATCH 2/6] chore: fix ci command in makefile --- Makefile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index bd94ae9..133b5d0 100644 --- a/Makefile +++ b/Makefile @@ -16,9 +16,6 @@ help: # Setup -sync: - uv sync - rebuild: docker compose down --rmi local -v docker compose build @@ -29,6 +26,9 @@ run: # Contribute +sync: + uv sync + format: uv run ruff check --fix . uv run ruff format . @@ -43,8 +43,7 @@ typecheck: check: lint typecheck - test: uv run pytest -ci: sync lint check test +ci: sync lint typecheck test From b2306cc357e737a4d258094630ca1fc5a38ba4a8 Mon Sep 17 00:00:00 2001 From: OctaveLauby Date: Thu, 4 Jun 2026 10:22:12 +0200 Subject: [PATCH 3/6] doc: change target structure --- CONTRIBUTING.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ac7e5d..3f01215 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,7 +39,7 @@ make ci - ADD Observability (OpenTelemetry - Jaeger) - ADD Settings management (dotenv or config files) - ADD Proper Error management (API Problem) -- UPDATE code structure and use [uv workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces/) +- UPDATE code structure and use [uv workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces/) with [app vs package vs lib conventions](https://docs.astral.sh/uv/concepts/projects/init/). ``` . @@ -49,11 +49,12 @@ make ci ├── uv.lock ├── apps │ └── olapi/ - │ ├── src/olapi/... + │ ├── ... + │ ├── main.py │ ├── tests/... │ ├── pyproject.toml │ └── README.md - └── packages + └── libraries ├── ... └── authentication/ ├── src/authentication/... From ab39a9b9b753da6f679b28e8674fbbae4dba2c7f Mon Sep 17 00:00:00 2001 From: OctaveLauby Date: Thu, 4 Jun 2026 14:05:58 +0200 Subject: [PATCH 4/6] chore: switch to uv workspaces --- .python-version | 1 + CONTRIBUTING.md | 24 --------- Dockerfile | 10 ++-- Makefile | 3 +- README.md | 32 +++++++----- apps/olapi/README.md | 27 ++++++++++ {src => apps}/olapi/auth.py | 6 +-- {src => apps}/olapi/database.py | 2 +- .../olapi/dtos}/__init__.py | 0 {src => apps}/olapi/dtos/auth.py | 0 {src => apps}/olapi/dtos/user.py | 2 +- {src => apps}/olapi/main.py | 10 ++-- {src/olapi => apps/olapi/models}/__init__.py | 0 {src => apps}/olapi/models/base.py | 0 {src => apps}/olapi/models/user.py | 2 +- apps/olapi/pyproject.toml | 44 ++++++++++++++++ .../dtos => apps/olapi/routers}/__init__.py | 0 {src => apps}/olapi/routers/auth.py | 10 ++-- {src => apps}/olapi/routers/temporary.py | 4 +- {src => apps}/olapi/settings.py | 0 {tests => apps/olapi/tests}/conftest.py | 8 +-- {tests => apps/olapi/tests}/test_temporary.py | 4 +- docker-compose.yml | 4 +- libs/authentication/README.md | 24 +++++++++ libs/authentication/pyproject.toml | 18 +++++++ .../src/authentication}/__init__.py | 0 .../src}/authentication/exceptions.py | 0 .../src}/authentication/keycloak.py | 0 .../src/authentication/py.typed | 0 libs/authentication/tests/test_keycloak.py | 17 +++++++ pyproject.toml | 43 +++++++--------- scripts/request_api.py | 1 - tests/__init__.py | 0 uv.lock | 51 ++++++++++++------- 34 files changed, 235 insertions(+), 112 deletions(-) create mode 100644 .python-version create mode 100644 apps/olapi/README.md rename {src => apps}/olapi/auth.py (93%) rename {src => apps}/olapi/database.py (92%) rename {src/authentication => apps/olapi/dtos}/__init__.py (100%) rename {src => apps}/olapi/dtos/auth.py (100%) rename {src => apps}/olapi/dtos/user.py (94%) rename {src => apps}/olapi/main.py (72%) rename {src/olapi => apps/olapi/models}/__init__.py (100%) rename {src => apps}/olapi/models/base.py (100%) rename {src => apps}/olapi/models/user.py (93%) create mode 100644 apps/olapi/pyproject.toml rename {src/olapi/dtos => apps/olapi/routers}/__init__.py (100%) rename {src => apps}/olapi/routers/auth.py (92%) rename {src => apps}/olapi/routers/temporary.py (73%) rename {src => apps}/olapi/settings.py (100%) rename {tests => apps/olapi/tests}/conftest.py (87%) rename {tests => apps/olapi/tests}/test_temporary.py (92%) create mode 100644 libs/authentication/README.md create mode 100644 libs/authentication/pyproject.toml rename {src/olapi/models => libs/authentication/src/authentication}/__init__.py (100%) rename {src => libs/authentication/src}/authentication/exceptions.py (100%) rename {src => libs/authentication/src}/authentication/keycloak.py (100%) rename src/olapi/routers/__init__.py => libs/authentication/src/authentication/py.typed (100%) create mode 100644 libs/authentication/tests/test_keycloak.py delete mode 100644 tests/__init__.py diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f01215..6e9a37e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,30 +39,6 @@ make ci - ADD Observability (OpenTelemetry - Jaeger) - ADD Settings management (dotenv or config files) - ADD Proper Error management (API Problem) -- UPDATE code structure and use [uv workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces/) with [app vs package vs lib conventions](https://docs.astral.sh/uv/concepts/projects/init/). - - ``` - . - ├── ... - ├── .venv/... - ├── pyproject.toml - ├── uv.lock - ├── apps - │ └── olapi/ - │ ├── ... - │ ├── main.py - │ ├── tests/... - │ ├── pyproject.toml - │ └── README.md - └── libraries - ├── ... - └── authentication/ - ├── src/authentication/... - ├── tests/... - ├── pyproject.toml - └── README.md - ``` - - REWORK Authentication - Activate Keycloak secret - Remove auth endpoints and let keycloak handle the authentication (e.g. `standardFlowEnabled=true & directAccessGrantsEnabled=false` in realm.json) diff --git a/Dockerfile b/Dockerfile index 2c80acf..3e29c75 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,12 +3,16 @@ FROM python:3.12-slim COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ WORKDIR /app -COPY pyproject.toml uv.lock README.md ./ -COPY src ./src +COPY pyproject.toml uv.lock ./ +COPY libs ./libs +COPY apps ./apps RUN uv sync --frozen --no-dev ENV PATH="/app/.venv/bin:$PATH" +# The olapi app is a flat uv-app; run it from its own directory so `main:app` resolves. +WORKDIR /app/apps/olapi + EXPOSE 8000 -CMD ["uvicorn", "olapi.main:app", "--host", "0.0.0.0"] +CMD ["uvicorn", "main:app", "--host", "0.0.0.0"] diff --git a/Makefile b/Makefile index 133b5d0..e24214d 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ format: uv run ruff format . lint: - uv run ruff check src + uv run ruff check uv run ruff format --check typecheck: @@ -45,5 +45,6 @@ check: lint typecheck test: uv run pytest + uv run --directory apps/olapi pytest ci: sync lint typecheck test diff --git a/README.md b/README.md index 2cb0838..a30fb1c 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,13 @@ Then: ### Structure +Using [uv workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces/): + ``` . ├── Makefile ├── README.md - ├── README_DEV.md + ├── CONTRIBUTING.md ├── docker-compose.yml ├── pyproject.toml ├── ... @@ -67,20 +69,22 @@ Then: │ └── ... ├── scripts/ │ └── ... - └── src/ - ├── authentication/ # Keycloak client - │ └── ... - └── olapi/ # FastAPI application - ├── __init__.py - ├── main.py - ├── settings.py - ├── ... - ├── models/ # tables - │ ├── ... - ├── routers/ # endpoints + ├── apps/ + │ └── olapi/ # FastAPI application (flat uv-app) + │ ├── main.py + │ ├── settings.py + │ ├── ... + │ ├── models/ # tables + │ ├── routers/ # endpoints + │ ├── dtos/ # dtos + │ ├── tests/ + │ └── pyproject.toml + └── libs/ + └── authentication/ # Keycloak client (packaged library) + ├── src/authentication/ │ └── ... - └── dtos/ # dtos - └── ... + ├── tests/ + └── pyproject.toml ``` ### Shortcuts diff --git a/apps/olapi/README.md b/apps/olapi/README.md new file mode 100644 index 0000000..089be47 --- /dev/null +++ b/apps/olapi/README.md @@ -0,0 +1,27 @@ +# olapi + +The Social Media API application (FastAPI). + +This is a flat [uv-app](https://docs.astral.sh/uv/concepts/projects/init/#applications): +modules live directly under this directory and are imported relative to it (e.g. +`from main import app`). The app is not packaged or installed. + +## Run + +From the repository root: + +```bash +make run # docker compose up (postgres + keycloak + api) +``` + +Or directly with uvicorn from this directory: + +```bash +uv run uvicorn main:app --reload +``` + +## Test + +```bash +uv run --directory apps/olapi pytest +``` diff --git a/src/olapi/auth.py b/apps/olapi/auth.py similarity index 93% rename from src/olapi/auth.py rename to apps/olapi/auth.py index 565c5dd..70b4d0b 100644 --- a/src/olapi/auth.py +++ b/apps/olapi/auth.py @@ -6,9 +6,9 @@ from authentication import exceptions as auth_exceptions from authentication.keycloak import KeycloakClient -from olapi.database import get_session -from olapi.models.user import UserModel -from olapi.settings import settings +from database import get_session +from models.user import UserModel +from settings import settings oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login", auto_error=False) auth_client = KeycloakClient( diff --git a/src/olapi/database.py b/apps/olapi/database.py similarity index 92% rename from src/olapi/database.py rename to apps/olapi/database.py index 62bf9ae..871cdf1 100644 --- a/src/olapi/database.py +++ b/apps/olapi/database.py @@ -3,7 +3,7 @@ from sqlalchemy import create_engine from sqlalchemy.orm import Session, sessionmaker -from olapi.settings import settings +from settings import settings # TODO: use context managers ? diff --git a/src/authentication/__init__.py b/apps/olapi/dtos/__init__.py similarity index 100% rename from src/authentication/__init__.py rename to apps/olapi/dtos/__init__.py diff --git a/src/olapi/dtos/auth.py b/apps/olapi/dtos/auth.py similarity index 100% rename from src/olapi/dtos/auth.py rename to apps/olapi/dtos/auth.py diff --git a/src/olapi/dtos/user.py b/apps/olapi/dtos/user.py similarity index 94% rename from src/olapi/dtos/user.py rename to apps/olapi/dtos/user.py index 233f3c9..976b9dc 100644 --- a/src/olapi/dtos/user.py +++ b/apps/olapi/dtos/user.py @@ -3,7 +3,7 @@ from pydantic import BaseModel, EmailStr, Field -from olapi.models.user import UserModel +from models.user import UserModel class UserCreatePayload(BaseModel): diff --git a/src/olapi/main.py b/apps/olapi/main.py similarity index 72% rename from src/olapi/main.py rename to apps/olapi/main.py index d400e1f..abcdf27 100644 --- a/src/olapi/main.py +++ b/apps/olapi/main.py @@ -4,11 +4,11 @@ from fastapi import APIRouter, Depends, FastAPI -from olapi.auth import check_authentication -from olapi.database import engine -from olapi.models.base import BaseModel -from olapi.routers import auth, temporary -from olapi.settings import LOGGING_CONFIG +from auth import check_authentication +from database import engine +from models.base import BaseModel +from routers import auth, temporary +from settings import LOGGING_CONFIG dictConfig(LOGGING_CONFIG) diff --git a/src/olapi/__init__.py b/apps/olapi/models/__init__.py similarity index 100% rename from src/olapi/__init__.py rename to apps/olapi/models/__init__.py diff --git a/src/olapi/models/base.py b/apps/olapi/models/base.py similarity index 100% rename from src/olapi/models/base.py rename to apps/olapi/models/base.py diff --git a/src/olapi/models/user.py b/apps/olapi/models/user.py similarity index 93% rename from src/olapi/models/user.py rename to apps/olapi/models/user.py index a0f6f06..4772a17 100644 --- a/src/olapi/models/user.py +++ b/apps/olapi/models/user.py @@ -3,7 +3,7 @@ from sqlalchemy import DateTime, String, func from sqlalchemy.orm import Mapped, mapped_column -from olapi.models.base import BaseModel +from models.base import BaseModel class UserModel(BaseModel): diff --git a/apps/olapi/pyproject.toml b/apps/olapi/pyproject.toml new file mode 100644 index 0000000..ff8f301 --- /dev/null +++ b/apps/olapi/pyproject.toml @@ -0,0 +1,44 @@ +[project] +name = "olapi" +version = "0.0.1" +description = "Social Media API" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "fastapi>=0.110", + "uvicorn[standard]>=0.27", + "sqlalchemy>=2.0", + "psycopg[binary]>=3.1", + "pydantic[email]>=2.6", + "python-jose[cryptography]>=3.3", + "python-ulid>=2.2", + "httpx2>=2.3", + "authentication", +] + +# Flat uv-app: no [build-system], so the app is not built or installed as a +# package. Its modules are imported relative to this directory (see pythonpath). + +[tool.uv.sources] +authentication = { workspace = true } + +[tool.pytest.ini_options] +pythonpath = ["."] + +[tool.ruff] +extend = "../../pyproject.toml" + +# The app is flat, so isort can't auto-detect its own modules; the authentication +# workspace library is also a first-party concern. Group them together. +[tool.ruff.lint.isort] +known-first-party = [ + "authentication", + "auth", + "conftest", + "database", + "dtos", + "main", + "models", + "routers", + "settings", +] diff --git a/src/olapi/dtos/__init__.py b/apps/olapi/routers/__init__.py similarity index 100% rename from src/olapi/dtos/__init__.py rename to apps/olapi/routers/__init__.py diff --git a/src/olapi/routers/auth.py b/apps/olapi/routers/auth.py similarity index 92% rename from src/olapi/routers/auth.py rename to apps/olapi/routers/auth.py index 5c50004..ab3e904 100644 --- a/src/olapi/routers/auth.py +++ b/apps/olapi/routers/auth.py @@ -6,12 +6,12 @@ from sqlalchemy.orm import Session from ulid import ULID +from auth import auth_client from authentication import exceptions as auth_exceptions -from olapi.auth import auth_client -from olapi.database import get_session -from olapi.dtos.auth import Credentials, TokenResponse -from olapi.dtos.user import User, UserCreatePayload -from olapi.models.user import UserModel +from database import get_session +from dtos.auth import Credentials, TokenResponse +from dtos.user import User, UserCreatePayload +from models.user import UserModel logger = logging.getLogger(__name__) diff --git a/src/olapi/routers/temporary.py b/apps/olapi/routers/temporary.py similarity index 73% rename from src/olapi/routers/temporary.py rename to apps/olapi/routers/temporary.py index 18f666d..4ca5c95 100644 --- a/src/olapi/routers/temporary.py +++ b/apps/olapi/routers/temporary.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, Depends -from olapi.auth import get_user -from olapi.models.user import UserModel +from auth import get_user +from models.user import UserModel router = APIRouter() diff --git a/src/olapi/settings.py b/apps/olapi/settings.py similarity index 100% rename from src/olapi/settings.py rename to apps/olapi/settings.py diff --git a/tests/conftest.py b/apps/olapi/tests/conftest.py similarity index 87% rename from tests/conftest.py rename to apps/olapi/tests/conftest.py index 6d6e1b6..236f1c5 100644 --- a/tests/conftest.py +++ b/apps/olapi/tests/conftest.py @@ -6,10 +6,10 @@ from sqlalchemy.orm import Session, sessionmaker from sqlalchemy.pool import StaticPool -from olapi.auth import check_authentication -from olapi.database import get_session -from olapi.main import app -from olapi.models.base import BaseModel +from auth import check_authentication +from database import get_session +from main import app +from models.base import BaseModel TEST_AUTH_ID = "test-auth-id" diff --git a/tests/test_temporary.py b/apps/olapi/tests/test_temporary.py similarity index 92% rename from tests/test_temporary.py rename to apps/olapi/tests/test_temporary.py index 70c18f7..339aa49 100644 --- a/tests/test_temporary.py +++ b/apps/olapi/tests/test_temporary.py @@ -1,8 +1,8 @@ -from conftest import TEST_AUTH_ID from fastapi.testclient import TestClient from sqlalchemy.orm import Session -from olapi.models.user import UserModel +from conftest import TEST_AUTH_ID +from models.user import UserModel def test_get_hello(app_client: TestClient, db_session: Session): diff --git a/docker-compose.yml b/docker-compose.yml index 4bfb52b..0ca52ae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,7 +26,7 @@ services: api: build: . - command: uvicorn olapi.main:app --host 0.0.0.0 --reload --reload-dir /app/src + command: uvicorn main:app --host 0.0.0.0 --reload --reload-dir /app/apps/olapi depends_on: postgres: condition: service_healthy @@ -35,4 +35,4 @@ services: ports: - "8000:8000" volumes: - - ./src:/app/src + - ./apps/olapi:/app/apps/olapi diff --git a/libs/authentication/README.md b/libs/authentication/README.md new file mode 100644 index 0000000..89c8b07 --- /dev/null +++ b/libs/authentication/README.md @@ -0,0 +1,24 @@ +# authentication + +Keycloak authentication client library (src-layout, packaged with `uv_build`). + +Exposes a `KeycloakClient` and related exceptions used by workspace apps to +register users, issue tokens, and validate them. + +## Use + +Declare it as a workspace dependency in a member's `pyproject.toml`: + +```toml +dependencies = ["authentication"] + +[tool.uv.sources] +authentication = { workspace = true } +``` + +Then import: + +```python +from authentication.keycloak import KeycloakClient +from authentication import exceptions +``` diff --git a/libs/authentication/pyproject.toml b/libs/authentication/pyproject.toml new file mode 100644 index 0000000..13bbbc9 --- /dev/null +++ b/libs/authentication/pyproject.toml @@ -0,0 +1,18 @@ +[project] +name = "authentication" +version = "0.0.1" +description = "Keycloak authentication client" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "httpx2>=2.3", + "python-jose[cryptography]>=3.3", + "starlette>=0.37", +] + +[build-system] +requires = ["uv_build>=0.8.3,<0.9.0"] +build-backend = "uv_build" + +[tool.ruff] +extend = "../../pyproject.toml" diff --git a/src/olapi/models/__init__.py b/libs/authentication/src/authentication/__init__.py similarity index 100% rename from src/olapi/models/__init__.py rename to libs/authentication/src/authentication/__init__.py diff --git a/src/authentication/exceptions.py b/libs/authentication/src/authentication/exceptions.py similarity index 100% rename from src/authentication/exceptions.py rename to libs/authentication/src/authentication/exceptions.py diff --git a/src/authentication/keycloak.py b/libs/authentication/src/authentication/keycloak.py similarity index 100% rename from src/authentication/keycloak.py rename to libs/authentication/src/authentication/keycloak.py diff --git a/src/olapi/routers/__init__.py b/libs/authentication/src/authentication/py.typed similarity index 100% rename from src/olapi/routers/__init__.py rename to libs/authentication/src/authentication/py.typed diff --git a/libs/authentication/tests/test_keycloak.py b/libs/authentication/tests/test_keycloak.py new file mode 100644 index 0000000..8fb97e6 --- /dev/null +++ b/libs/authentication/tests/test_keycloak.py @@ -0,0 +1,17 @@ +import httpx2 +import pytest + +from authentication.keycloak import check_response + + +def _response(status_code: int) -> httpx2.Response: + return httpx2.Response(status_code, request=httpx2.Request("GET", "http://keycloak/test")) + + +def test_check_response_passes_on_success(): + check_response(_response(200)) + + +def test_check_response_raises_on_error(): + with pytest.raises(httpx2.HTTPStatusError): + check_response(_response(404)) diff --git a/pyproject.toml b/pyproject.toml index 2f58b00..65de0db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,19 +1,5 @@ -[project] -name = "olapi" -version = "0.0.1" -description = "Social Media API" -readme = "README.md" -requires-python = ">=3.12" -dependencies = [ - "fastapi>=0.110", - "uvicorn[standard]>=0.27", - "sqlalchemy>=2.0", - "psycopg[binary]>=3.1", - "pydantic[email]>=2.6", - "python-jose[cryptography]>=3.3", - "python-ulid>=2.2", - "httpx2>=2.3", -] +[tool.uv.workspace] +members = ["apps/*", "libs/*"] [dependency-groups] dev = [ @@ -23,13 +9,16 @@ dev = [ "pytest>=9.0.3", ] -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.hatch.build.targets.wheel] -packages = ["src/olapi", "src/authentication"] +[tool.pytest.ini_options] +# Libraries are collected here; apps run in their own pytest process (see the +# Makefile) so the flat app modules don't collide with library test discovery. +testpaths = ["libs"] +# importlib import mode imports each test file under a unique name, so +# identically-named test files across members can coexist without __init__.py. +addopts = ["--import-mode=importlib"] +# Shared lint config for the whole workspace. Members extend this via +# `[tool.ruff] extend = "../../pyproject.toml"`. [tool.ruff] target-version = "py312" line-length = 100 @@ -54,8 +43,10 @@ pythonVersion = "3.12" typeCheckingMode = "strict" venvPath = "." venv = ".venv" -include = ["src", "tests"] +include = ["apps", "libs"] -[tool.pytest.ini_options] -pythonpath = ["src", "tests"] -testpaths = ["tests"] +# The olapi app is a flat, non-packaged uv-app: its modules live directly under +# apps/olapi and are imported as `from main import ...`. This execution +# environment puts that directory on the import path for the type checker. +[[tool.pyright.executionEnvironments]] +root = "apps/olapi" diff --git a/scripts/request_api.py b/scripts/request_api.py index 39fffa2..0a61be7 100644 --- a/scripts/request_api.py +++ b/scripts/request_api.py @@ -1,7 +1,6 @@ import logging import httpx2 - from authentication.keycloak import check_response from olapi.dtos.auth import Credentials, TokenResponse from olapi.dtos.user import User, UserCreatePayload diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/uv.lock b/uv.lock index 2a33c22..954dec9 100644 --- a/uv.lock +++ b/uv.lock @@ -6,6 +6,20 @@ resolution-markers = [ "python_full_version < '3.15'", ] +[manifest] +members = [ + "authentication", + "olapi", +] + +[manifest.dependency-groups] +dev = [ + { name = "pyright", specifier = ">=1.1.350" }, + { name = "pytest", specifier = ">=9.0.3" }, + { name = "ruff", specifier = ">=0.6" }, + { name = "types-python-jose", specifier = ">=3.3" }, +] + [[package]] name = "annotated-doc" version = "0.0.4" @@ -37,6 +51,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, ] +[[package]] +name = "authentication" +version = "0.0.1" +source = { editable = "libs/authentication" } +dependencies = [ + { name = "httpx2" }, + { name = "python-jose", extra = ["cryptography"] }, + { name = "starlette" }, +] + +[package.metadata] +requires-dist = [ + { name = "httpx2", specifier = ">=2.3" }, + { name = "python-jose", extras = ["cryptography"], specifier = ">=3.3" }, + { name = "starlette", specifier = ">=0.37" }, +] + [[package]] name = "cffi" version = "2.0.0" @@ -388,8 +419,9 @@ wheels = [ [[package]] name = "olapi" version = "0.0.1" -source = { editable = "." } +source = { virtual = "apps/olapi" } dependencies = [ + { name = "authentication" }, { name = "fastapi" }, { name = "httpx2" }, { name = "psycopg", extra = ["binary"] }, @@ -400,16 +432,9 @@ dependencies = [ { name = "uvicorn", extra = ["standard"] }, ] -[package.dev-dependencies] -dev = [ - { name = "pyright" }, - { name = "pytest" }, - { name = "ruff" }, - { name = "types-python-jose" }, -] - [package.metadata] requires-dist = [ + { name = "authentication", editable = "libs/authentication" }, { name = "fastapi", specifier = ">=0.110" }, { name = "httpx2", specifier = ">=2.3" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.1" }, @@ -420,14 +445,6 @@ requires-dist = [ { name = "uvicorn", extras = ["standard"], specifier = ">=0.27" }, ] -[package.metadata.requires-dev] -dev = [ - { name = "pyright", specifier = ">=1.1.350" }, - { name = "pytest", specifier = ">=9.0.3" }, - { name = "ruff", specifier = ">=0.6" }, - { name = "types-python-jose", specifier = ">=3.3" }, -] - [[package]] name = "packaging" version = "26.2" From a2525b233cc49cc001ac2a369a1479a5af29af9b Mon Sep 17 00:00:00 2001 From: OctaveLauby Date: Thu, 4 Jun 2026 14:20:52 +0200 Subject: [PATCH 5/6] fix: scripts usage --- README.md | 2 +- {scripts => apps/olapi/scripts}/request_api.py | 5 +++-- .../authentication/scripts}/run_keycloak_client.py | 0 3 files changed, 4 insertions(+), 3 deletions(-) rename {scripts => apps/olapi/scripts}/request_api.py (92%) rename {scripts => libs/authentication/scripts}/run_keycloak_client.py (100%) diff --git a/README.md b/README.md index a30fb1c..78d8e64 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ make rebuild Then: -* **Requests on API**: `uv run scripts/request_api.py` +* **Requests on API**: `uv run --directory apps/olapi python -m scripts.request_api` * **See swagger**: [http://localhost:8000/docs](http://localhost:8000/docs) * **Check keycloak users**: [http://localhost:8080/admin/master/console/#/olapi/users](http://localhost:8080/admin/master/console/#/olapi/users) * username=admin diff --git a/scripts/request_api.py b/apps/olapi/scripts/request_api.py similarity index 92% rename from scripts/request_api.py rename to apps/olapi/scripts/request_api.py index 0a61be7..14ff0c1 100644 --- a/scripts/request_api.py +++ b/apps/olapi/scripts/request_api.py @@ -1,9 +1,10 @@ import logging import httpx2 + from authentication.keycloak import check_response -from olapi.dtos.auth import Credentials, TokenResponse -from olapi.dtos.user import User, UserCreatePayload +from dtos.auth import Credentials, TokenResponse +from dtos.user import User, UserCreatePayload logger = logging.getLogger(__name__) diff --git a/scripts/run_keycloak_client.py b/libs/authentication/scripts/run_keycloak_client.py similarity index 100% rename from scripts/run_keycloak_client.py rename to libs/authentication/scripts/run_keycloak_client.py From ed81496a69cdd356a53d33ea01393639a8eb0cc9 Mon Sep 17 00:00:00 2001 From: OctaveLauby Date: Thu, 4 Jun 2026 14:25:56 +0200 Subject: [PATCH 6/6] chore: create docker directory for docker-compose config --- README.md | 9 +++++---- docker-compose.yml | 6 ++++-- Dockerfile => docker/Dockerfile | 0 {keycloak => docker/keycloak}/ABOUT.md | 0 {keycloak => docker/keycloak}/realm.json | 0 5 files changed, 9 insertions(+), 6 deletions(-) rename Dockerfile => docker/Dockerfile (100%) rename {keycloak => docker/keycloak}/ABOUT.md (100%) rename {keycloak => docker/keycloak}/realm.json (100%) diff --git a/README.md b/README.md index 78d8e64..351cf36 100644 --- a/README.md +++ b/README.md @@ -65,10 +65,10 @@ Using [uv workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces/): ├── docker-compose.yml ├── pyproject.toml ├── ... - ├── keycloak/ # Settings for keycloak server - │ └── ... - ├── scripts/ - │ └── ... + ├── docker/ # docker stack config + │ ├── Dockerfile # api image build + │ └── keycloak/ # keycloak realm import + │ └── ... ├── apps/ │ └── olapi/ # FastAPI application (flat uv-app) │ ├── main.py @@ -77,6 +77,7 @@ Using [uv workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces/): │ ├── models/ # tables │ ├── routers/ # endpoints │ ├── dtos/ # dtos + │ ├── scripts/ # API usage scripts │ ├── tests/ │ └── pyproject.toml └── libs/ diff --git a/docker-compose.yml b/docker-compose.yml index 0ca52ae..bcf4d67 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,12 +20,14 @@ services: KC_BOOTSTRAP_ADMIN_USERNAME: admin KC_BOOTSTRAP_ADMIN_PASSWORD: admin volumes: - - ./keycloak:/opt/keycloak/data/import:ro + - ./docker/keycloak:/opt/keycloak/data/import:ro ports: - "8080:8080" api: - build: . + build: + context: . + dockerfile: docker/Dockerfile command: uvicorn main:app --host 0.0.0.0 --reload --reload-dir /app/apps/olapi depends_on: postgres: diff --git a/Dockerfile b/docker/Dockerfile similarity index 100% rename from Dockerfile rename to docker/Dockerfile diff --git a/keycloak/ABOUT.md b/docker/keycloak/ABOUT.md similarity index 100% rename from keycloak/ABOUT.md rename to docker/keycloak/ABOUT.md diff --git a/keycloak/realm.json b/docker/keycloak/realm.json similarity index 100% rename from keycloak/realm.json rename to docker/keycloak/realm.json