Brian Bjarke JensenandCursor 7991dabdbc Add config and client injection with test reorganization.
Introduce typed config objects, optional adapter injection, and .env loading to simplify testing while preserving env-based defaults for production usage.

Co-authored-by: Cursor <[email protected]>
2026-07-06 20:44:08 +02:00
2025-08-31 17:50:04 +02:00

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:

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)

Copy .env.example to .env for local development. RedisConfig.from_env() and MinioConfig.from_env() load .env automatically when resolving configuration from the environment.

Configuration injection

Adapters accept optional config and client keyword arguments for explicit setup and testing:

from python_repositories import RedisAdapter, RedisConfig, MinioAdapter, MinioConfig

redis = RedisAdapter(config=RedisConfig(uri="redis://localhost:6379"))
minio = MinioAdapter(
    config=MinioConfig(
        endpoint="localhost:9000",
        access_key="minioadmin",
        secret_key="minioadmin",
        bucket="my-bucket",
    )
)

When both config and client are provided, connect() skips client creation (the caller owns the client lifecycle). config is required whenever client is injected.

Load a .env file explicitly:

from python_repositories import load_dotenv

load_dotenv()  # optional — from_env() also loads .env by default

Calling RedisAdapter() or MinioAdapter() with no arguments still loads configuration from environment variables (and .env if present).

Quick start

JSON documents with Redis

from python_repositories.examples.user_json_repository import UserJsonRepository

with UserJsonRepository() as repo:
    repo.save_user("alice", {"name": "Alice", "email": "[email protected]"})
    user = repo.get_user("alice")
    repo.delete_user("alice")

Binary objects with MinIO

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

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

from python_repositories import (
    ConnectionAwareInterface,
    ContextAwareInterface,
    JsonRepositoryInterface,
    MinioAdapter,
    MinioConfig,
    ObjectRepositoryInterface,
    RedisAdapter,
    RedisConfig,
    load_dotenv,
)

Development

uv sync --all-extras
uv run pre-commit install   # once per clone — runs hooks on git commit
uv run pytest tests/unit/ -v                          # fast, no Docker
uv run pytest -m "not integration" -v                 # all non-Docker tests
uv run pytest -v                                      # full suite (requires Docker)

Integration tests are marked with @pytest.mark.integration and require Docker (testcontainers). Run unit tests alone for quick local feedback.

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 (ruff, mypy, pyupgrade, prettier, and general file hygiene).

To run all hooks manually without committing:

uv run pre-commit run --all-files

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).
  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 bumps pyproject.toml and syncs uv.lock, commits, tags vX.Y.Z, creates a Gitea release with auto-generated notes, and pushes the tag.
  4. 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.10.3.2
[minor] or [feat] minor 0.3.10.4.0
[major] or [breaking] major 0.3.11.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).

Manual release

You can still push a v*.*.* tag manually; publish.yml will build and publish. The current baseline version is 0.3.1.

S
Description
Various python repository interfaces exposed as a python package.
Readme MIT
2.5 MiB
Languages
Python 92.5%
Shell 7.1%
Dockerfile 0.4%