Initial commit
This commit is contained in:
12
.dockerignore
Normal file
12
.dockerignore
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.env
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
env
|
||||||
|
venv
|
||||||
|
.venv
|
||||||
|
*.db
|
||||||
40
.gitignore
vendored
Normal file
40
.gitignore
vendored
Normal 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
38
AGENTS.md
Normal 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
24
Dockerfile.main
Normal 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
24
Dockerfile.test
Normal 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
15
README.md
Normal 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
14
compose.yml
Normal 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
0
notebooks/.gitkeep
Normal file
71
pyproject.toml
Normal file
71
pyproject.toml
Normal 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
0
tests/__init__.py
Normal file
15
tests/example_test.py
Normal file
15
tests/example_test.py
Normal 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
3
uv_app/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from uv_app import logging_config, settings
|
||||||
|
|
||||||
|
__all__ = ["logging_config", "settings"]
|
||||||
16
uv_app/__main__.py
Normal file
16
uv_app/__main__.py
Normal 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
1
uv_app/core/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Core utilities for the application."""
|
||||||
31
uv_app/core/db.py
Normal file
31
uv_app/core/db.py
Normal 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
3
uv_app/example_util.py
Normal 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
9
uv_app/logging_config.py
Normal 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
18
uv_app/settings.py
Normal 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()
|
||||||
Reference in New Issue
Block a user