Initial commit

This commit is contained in:
2026-01-30 13:17:42 +02:00
commit 580d27028b
19 changed files with 335 additions and 0 deletions

12
.dockerignore Normal file
View File

@@ -0,0 +1,12 @@
.git
.gitignore
.env
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env
venv
.venv
*.db

1
.env.tmpl Normal file
View File

@@ -0,0 +1 @@
EXAMPLE_ENV_VARIABLE="..."

40
.gitignore vendored Normal file
View File

@@ -0,0 +1,40 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*/__pycache__/
*.py[cod]
# Distribution / packaging
.Python
env/
.venv
venv
# Unit test / coverage reports
.coverage
.coverage.*
.cache
coverage.xml
# Pycharm
.idea
# VS Code
.vscode/
# Jupyter NB Checkpoints
.ipynb_checkpoints/
# Mac OS-specific storage files
.DS_Store
# Caches
.mypy_cache/
.pytest_cache/
.ruff_cache/
.neptune/
/cache/*
!/cache/.gitkeep
# Personal files
credentials.json
.env

38
AGENTS.md Normal file
View File

@@ -0,0 +1,38 @@
# Repository Guidelines
## Project Structure & Module Organization
- `uv_app/` holds application code (`settings.py` for env-loaded config, `logging_config.py` for basic logging, `__main__.py` entrypoint placeholder). Keep new modules small and focused; prefer `uv_app/feature/<module>.py` for grouped logic.
- `tests/` contains pytest suites; mirror module paths (e.g., `uv_app/example_util.py``tests/example_util_test.py`).
- `notebooks/` is for exploratory work; avoid depending on it for runtime logic and keep outputs trimmed before committing.
- Root files: `pyproject.toml` (project + tooling), `uv.lock` (dependency lock), `Dockerfile` (runtime build), `README.md` (quickstart).
## Build, Test, and Development Commands
- Install + sync env: `uv sync` (creates venv and installs deps).
- Run full format + lint: `uv run poe x` (runs `ruff format` then `ruff check --fix`).
- Tests: `uv run pytest` (add `-k <pattern>` for focused runs).
## Dependency Management
- Use `uv add <package>` to add dependencies; this updates `pyproject.toml` and `uv.lock`.
- For dev dependencies, use `uv add --dev <package>`.
- To remove packages, use `uv remove <package>`.
# Docker & Containerization
- The `Dockerfile.main` sets up a lightweight Python environment using the locked dependencies from `uv.lock`.
- The `Dockerfile.tests` sets up a pytest environment for running tests in a containerized setup.
- Use `docker compose build` to build images.
- Use `docker compose up main` to build and run the app container or `docker compose run main` if application is job-based.
- Use `docker compose run tests` to run tests in a containerized environment.
## Coding Style & Naming Conventions
- Python 3.14; Use type hints on. Use double quotes (enforced by ruff `flake8-quotes`), Google-style docstrings when needed.
- Follow Ruff config (`select = ["ALL"]` with targeted ignores). Keep new ignores local and justified.
- Modules and files: lowercase with underscores; classes: `PascalCase`; functions/vars: `snake_case`. Tests mirror subject names and use descriptive test function names.
## Testing Guidelines
- Framework: pytest. Parametrize where inputs vary.
- Place unit tests alongside corresponding modules under `tests/`; name files `<module>_test.py`.
- Add regression tests with clear arrange/act/assert sections. Aim to cover new branches; prefer deterministic data over randomness.
## Configuration & Secrets
- Settings load via `pydantic-settings` with `.env` discovery (`find_dotenv`). Keep secrets out of git; document required keys in `.env.example` if adding new settings.
- Seperate settings classes based on purpose, for example PostgresSettings, AppSettings, etc.

24
Dockerfile.main Normal file
View File

@@ -0,0 +1,24 @@
FROM python:3.14-slim AS builder
WORKDIR /app
ENV UV_COMPILE_BYTECODE=1
COPY pyproject.toml uv.lock ./
RUN pip install --no-cache-dir uv \
&& uv sync --frozen --no-install-project --no-dev
COPY uv_app/ ./uv_app/
FROM python:3.14-slim
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app/uv_app ./uv_app
RUN adduser --disabled-password --gecos '' appuser && chown -R appuser /app
USER appuser
EXPOSE 8080
ENTRYPOINT ["/app/.venv/bin/python", "-m", "uv_app"]
CMD ["main"]

24
Dockerfile.test Normal file
View File

@@ -0,0 +1,24 @@
FROM python:3.14-slim AS builder
WORKDIR /app
ENV UV_COMPILE_BYTECODE=1
COPY pyproject.toml uv.lock ./
RUN pip install --no-cache-dir uv \
&& uv sync --frozen --no-install-project --group dev
COPY uv_app/ ./uv_app/
COPY tests/ ./tests/
FROM python:3.14-slim
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app/uv_app ./uv_app
COPY --from=builder /app/tests ./tests
RUN adduser --disabled-password --gecos '' appuser && chown -R appuser /app
USER appuser
CMD ["/app/.venv/bin/pytest", "-q"]

15
README.md Normal file
View File

@@ -0,0 +1,15 @@
## uv-project-template
## Installation
To create and install virtual environment:
```bash
uv sync
```
During development, you can lint and format code using:
```bash
uv run poe x
```

14
compose.yml Normal file
View File

@@ -0,0 +1,14 @@
services:
main:
build:
context: .
dockerfile: Dockerfile.main
env_file:
- .env
tests:
build:
context: .
dockerfile: Dockerfile.test
env_file:
- .env

0
notebooks/.gitkeep Normal file
View File

71
pyproject.toml Normal file
View File

@@ -0,0 +1,71 @@
[project]
name = "uv_app"
version = "0.1.2"
description = ""
readme = "README.md"
requires-python = ">=3.14.0, <3.15"
dependencies = [
"click>=8.3.1",
"pydantic-settings>=2.12.0",
"python-dotenv>=1.2.1",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.poe.tasks]
lint = "uv run ruff check . --fix"
format = "uv run ruff format ."
x = ["format", "lint"]
[dependency-groups]
dev = [
"poethepoet>=0.32.2",
"pytest>=8.3.3",
"ruff>=0.14.6",
]
[tool.ruff]
exclude = [".venv"]
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"ANN002", # Missing type annotation for args
"ANN003", # Missing type annotation for kwarg
"ERA001", # Commented out code
"S104", # Possible binding to all interfaces
"BLE001", # Do not catch Exception
"FBT", # Bools in arguments
"DTZ", # Datetime timezone
"EM", # f-strings in exception messages
"FIX", # Left out TODO, FIXME, etc.
"INT", # f-string in function execeution before calls
"G", # Logging linting
"TD", # Rules for TODO
"E501", # Line too long
"E722", # Do not use bare except
"W505", # Doc line too long
"D100", # Missing docstring
"D101", # Missing docstring
"D102", # Missing docstring
"D103", # Missing docstring
"D104", # Missing docstring
"D105", # Missing docstring
"D106", # Missing docstring
"D107", # Missing docstring
]
[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["S101"]
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"
inline-quotes = "double"
multiline-quotes = "double"

0
tests/__init__.py Normal file
View File

15
tests/example_test.py Normal file
View File

@@ -0,0 +1,15 @@
import pytest
from uv_app.example_util import addition
@pytest.mark.parametrize(
("a", "b", "expected"),
[
(2, 3, 5),
(1.5, 2.5, 4.0),
(-1, 1, 0),
],
)
def test_addition_returns_sum(a: float, b: float, expected: float) -> None:
assert addition(a, b) == expected

3
uv_app/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
from uv_app import logging_config, settings
__all__ = ["logging_config", "settings"]

16
uv_app/__main__.py Normal file
View File

@@ -0,0 +1,16 @@
import click
@click.group()
def cli() -> None:
"""Application Command Line Interface."""
@cli.command()
def main() -> None:
"""An example command that prints a message."""
click.echo("This is an example command from uv_app.")
if __name__ == "__main__":
cli()

1
uv_app/core/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""Core utilities for the application."""

31
uv_app/core/db.py Normal file
View File

@@ -0,0 +1,31 @@
import os
from typing import Optional
import psycopg2
from dotenv import load_dotenv
# Force reload environment variables from .env, ignoring system vars.
load_dotenv(override=True)
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT")
DB_NAME = os.getenv("DB_NAME")
DB_USER = os.getenv("DB_USER")
DB_PASSWORD = os.getenv("DB_PASSWORD")
def connect_to_db() -> Optional[psycopg2.extensions.connection]:
"""Establish a connection to the PostgreSQL database."""
try:
return psycopg2.connect(
host=DB_HOST,
port=DB_PORT,
database=DB_NAME,
user=DB_USER,
password=DB_PASSWORD,
# Set the client encoding to UTF-8.
options="-c client_encoding=utf8",
)
except psycopg2.Error as exc:
print(f"Error connecting to PostgreSQL database: {exc}")
return None

3
uv_app/example_util.py Normal file
View File

@@ -0,0 +1,3 @@
def addition(a: float, b: float) -> float | int:
"""Returns the sum of two numbers."""
return a + b

9
uv_app/logging_config.py Normal file
View File

@@ -0,0 +1,9 @@
import logging
LOGGER = logging.getLogger(__name__)
logging.basicConfig(
format="%(asctime)s [%(levelname)s] - <%(name)s> - %(message)s",
level=logging.INFO,
handlers=[logging.StreamHandler()],
)

18
uv_app/settings.py Normal file
View File

@@ -0,0 +1,18 @@
from dotenv import find_dotenv
from pydantic_settings import BaseSettings, SettingsConfigDict
DEFAULT_CONFIG_SETTINGS = SettingsConfigDict(
env_file=find_dotenv(),
env_file_encoding="utf-8",
env_ignore_empty=True,
extra="ignore",
validate_default=True,
)
class AppSettings(BaseSettings):
model_config = DEFAULT_CONFIG_SETTINGS
EXAMPLE_ENV_VARIABLE: str
app_settings = AppSettings()