# python-repositories Unified repository interfaces and technology-specific adapters for Python projects. Subclass an adapter in your own repository to add domain-specific methods while reusing connection management and CRUD operations. ## Architecture | Layer | Responsibility | | ---------------- | ----------------------------------------------------------------- | | **Interfaces** | Abstract contracts for connection, context, and CRUD | | **Adapters** | Technology-specific base classes (`RedisAdapter`, `MinioAdapter`) | | **Your project** | Subclass an adapter and add domain methods | Connection adapters expose `connect()`, `disconnect()`, and `is_connected()`. The latter verifies backend reachability with a cached health probe (default TTL: 1 second). Subclasses may override `health_check_ttl_seconds`. ## Optional dependencies Repository **interfaces** import with the base package. **Adapters** require the matching extra; importing an adapter without its extra raises `ImportError` with install instructions. Install with the extras you need: ```bash uv add python-repositories[redis] uv add python-repositories[minio] uv add python-repositories[redis,minio] ``` ### Redis (`JsonRepositoryInterface`) Requires Redis with the RedisJSON module (e.g. redis-stack). | Environment variable | Description | | -------------------- | ---------------------------------------------------- | | `REDIS_URI` | Redis connection URL (e.g. `redis://localhost:6379`) | ### MinIO (`ObjectRepositoryInterface`) | Environment variable | Description | | -------------------- | ------------------------------------------- | | `MINIO_ENDPOINT` | MinIO server endpoint | | `MINIO_ACCESS_KEY` | Access key | | `MINIO_SECRET_KEY` | Secret key | | `MINIO_BUCKET` | Bucket name (created on connect if missing) | ## Quick start ### JSON documents with Redis ```python from python_repositories.examples.user_json_repository import UserJsonRepository with UserJsonRepository() as repo: repo.save_user("alice", {"name": "Alice", "email": "alice@example.com"}) user = repo.get_user("alice") repo.delete_user("alice") ``` ### Binary objects with MinIO ```python from io import BytesIO from python_repositories.examples.artifact_object_repository import ( ArtifactObjectRepository, ) with ArtifactObjectRepository() as repo: repo.store_artifact("report-1", BytesIO(b"pdf bytes here")) data = repo.get_artifact("report-1") ``` ### Subclassing in your own project ```python from python_repositories import RedisAdapter class UserRepository(RedisAdapter): def _key(self, user_id: str) -> str: return f"user:{user_id}" def get_user(self, user_id: str) -> dict | None: return self.get(self._key(user_id)) def save_user(self, user_id: str, user: dict) -> None: self.set(self._key(user_id), user) ``` ## Public API ```python from python_repositories import ( ConnectionAwareInterface, ContextAwareInterface, JsonRepositoryInterface, ObjectRepositoryInterface, RedisAdapter, MinioAdapter, ) ``` ## Development ```bash uv sync --all-extras uv run pre-commit install # once per clone — runs hooks on git commit uv run pytest tests/integration/ -v ``` `pre-commit` is included in the dev dependency group. `uv sync` installs the CLI, but git does not run hooks until you install them with `pre-commit install` (one time per clone). After that, commits run the checks defined in [`.pre-commit-config.yaml`](.pre-commit-config.yaml) (ruff, mypy, pyupgrade, prettier, and general file hygiene). To run all hooks manually without committing: ```bash uv run pre-commit run --all-files ``` Integration tests require Docker (testcontainers). ## Releases Releases are automated when a pull request is merged to `main`. CI reads the **merged PR title** to decide whether and how to bump the version. ### How it works 1. Open a PR targeting `main` (see [`.gitea/PULL_REQUEST_TEMPLATE.md`](.gitea/PULL_REQUEST_TEMPLATE.md)). 2. If the PR changes files under `python_repositories/`, the title **must** start with a version bump prefix (enforced by CI). 3. On merge, [`release.yml`](.gitea/workflows/release.yml) bumps [`pyproject.toml`](pyproject.toml) and syncs [`uv.lock`](uv.lock), commits, tags `vX.Y.Z`, creates a Gitea release with auto-generated notes, and pushes the tag. 4. [`publish.yml`](.gitea/workflows/publish.yml) builds and publishes the package to the Gitea Package Registry. Docs-, CI-, and test-only PRs do not need a prefix and will not trigger a release. ### PR title prefixes | Prefix | Bump | Example | | ------------------------- | ---------- | --------------------- | | `[patch]` or `[fix]` | patch | `0.3.1` → `0.3.2` | | `[minor]` or `[feat]` | minor | `0.3.1` → `0.4.0` | | `[major]` or `[breaking]` | major | `0.3.1` → `1.0.0` | | _(none)_ | no release | docs / CI / deps only | Example titles: - `[minor] Add public repository interfaces and subclassable adapter CRUD API` - `[patch] Fix mypy context manager typing for adapter subclasses` ### Release notes Release notes are generated from commits since the previous tag (see [`scripts/ci/generate-release-notes.sh`](scripts/ci/generate-release-notes.sh)). ### Manual release You can still push a `v*.*.*` tag manually; `publish.yml` will build and publish. The current baseline version is **0.3.1**.