Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
14 changes: 0 additions & 14 deletions Dockerfile

This file was deleted.

12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ help:

# Setup

sync:
uv sync

rebuild:
docker compose down --rmi local -v
docker compose build
Expand All @@ -29,12 +26,15 @@ run:

# Contribute

sync:
uv sync

format:
uv run ruff check --fix .
uv run ruff format .

lint:
uv run ruff check src
uv run ruff check
uv run ruff format --check

typecheck:
Expand All @@ -43,8 +43,8 @@ typecheck:

check: lint typecheck


test:
uv run pytest
uv run --directory apps/olapi pytest

ci: sync lint check test
ci: sync lint typecheck test
43 changes: 24 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -55,32 +55,37 @@ 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
├── ...
├── keycloak/ # Settings for keycloak server
│ └── ...
├── scripts/
│ └── ...
└── src/
├── authentication/ # Keycloak client
│ └── ...
└── olapi/ # FastAPI application
├── __init__.py
├── main.py
├── settings.py
├── ...
├── models/ # tables
│ ├── ...
├── routers/ # endpoints
├── docker/ # docker stack config
│ ├── Dockerfile # api image build
│ └── keycloak/ # keycloak realm import
│ └── ...
├── apps/
│ └── olapi/ # FastAPI application (flat uv-app)
│ ├── main.py
│ ├── settings.py
│ ├── ...
│ ├── models/ # tables
│ ├── routers/ # endpoints
│ ├── dtos/ # dtos
│ ├── scripts/ # API usage scripts
│ ├── tests/
│ └── pyproject.toml
└── libs/
└── authentication/ # Keycloak client (packaged library)
├── src/authentication/
│ └── ...
── dtos/ # dtos
└── ...
── tests/
└── pyproject.toml
```

### Shortcuts
Expand Down
27 changes: 27 additions & 0 deletions apps/olapi/README.md
Original file line number Diff line number Diff line change
@@ -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
```
6 changes: 3 additions & 3 deletions src/olapi/auth.py → apps/olapi/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion src/olapi/database.py → apps/olapi/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?

Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion src/olapi/dtos/user.py → apps/olapi/dtos/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from pydantic import BaseModel, EmailStr, Field

from olapi.models.user import UserModel
from models.user import UserModel


class UserCreatePayload(BaseModel):
Expand Down
10 changes: 5 additions & 5 deletions src/olapi/main.py → apps/olapi/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion src/olapi/models/user.py → apps/olapi/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
44 changes: 44 additions & 0 deletions apps/olapi/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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",
]
File renamed without changes.
10 changes: 5 additions & 5 deletions src/olapi/routers/auth.py → apps/olapi/routers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down
Original file line number Diff line number Diff line change
@@ -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()

Expand Down
4 changes: 2 additions & 2 deletions scripts/request_api.py → apps/olapi/scripts/request_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
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__)

Expand Down
File renamed without changes.
8 changes: 4 additions & 4 deletions tests/conftest.py → apps/olapi/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
@@ -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):
Expand Down
10 changes: 6 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ 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: .
command: uvicorn olapi.main:app --host 0.0.0.0 --reload --reload-dir /app/src
build:
context: .
dockerfile: docker/Dockerfile
command: uvicorn main:app --host 0.0.0.0 --reload --reload-dir /app/apps/olapi
depends_on:
postgres:
condition: service_healthy
Expand All @@ -35,4 +37,4 @@ services:
ports:
- "8000:8000"
volumes:
- ./src:/app/src
- ./apps/olapi:/app/apps/olapi
18 changes: 18 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM python:3.12-slim

COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

WORKDIR /app
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", "main:app", "--host", "0.0.0.0"]
File renamed without changes.
File renamed without changes.
24 changes: 24 additions & 0 deletions libs/authentication/README.md
Original file line number Diff line number Diff line change
@@ -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
```
Loading
Loading