Files
Tagger/DESIGN_DOCUMENT_MODULE.md
2026-04-16 08:49:32 +02:00

7.7 KiB

Python Library Development Guidelines

Document Version: v1

Note on Versioning:

  • This document version is independent — reused across projects
  • Project version source of truth: pyproject.toml under [project]
  • CHANGELOG.md uses project version from pyproject.toml
  • README.md — Project overview, public API description, installation and usage examples
  • AGENTS.md — Rules for AI assistants
  • PROJECT.md — Project goals and current state
  • CHANGELOG.md — Version history

Documentation Organization

All detailed documentation of features and systems belongs in the docs/ folder, not in the project root.

The root directory contains only the core documents: DESIGN_DOCUMENT_MODULE.md, AGENTS.md, PROJECT.md, CHANGELOG.md.


1. Code Style

  • PEP8 with 150-character lines (Ruff)
  • 4 spaces indentation
  • snake_case functions/variables, PascalCase classes, SCREAMING_SNAKE_CASE constants
  • Type hints required on all functions
  • Import order: stdlib → third-party → local

2. SOLID Principles

  • SRP — One class = one responsibility
  • OCP — Open for extension, closed for modification
  • LSP — Subclasses substitutable for parents
  • ISP — Small interfaces over large ones
  • DIP — Depend on abstractions

3. Dependency Injection

Pass dependencies via constructor. Never instantiate dependencies inside a class.


4. Protocols Over Inheritance

Prefer typing.Protocol and composition over class inheritance.


5. Data Classes

Use @dataclass for internal data structures, pydantic.BaseModel for data that requires validation.


6. Logging

This is a library. Libraries must never configure logging sinks — that is the responsibility of the consuming application.

Usage in library code

from loguru import logger

def some_function() -> None:
    logger.debug("Detail message")
    logger.info("Milestone message")

Always use the module-level logger from loguru directly. Never call logger.add() or logger.remove() inside library code.

Behavior for consumers

Loguru has a default sink to stderr enabled. Consumers who also use loguru get library logs automatically in their configured sinks. Consumers who want to suppress or filter library logs use the package name as the identifier:

from loguru import logger

logger.disable("<package_name>")                                    # suppress all
logger.add(sys.stderr, filter={"<package_name>": "WARNING"})        # WARNING and above only
logger.enable("<package_name>")                                     # re-enable

This must be documented in README.md.

Rules

  • Never call logger.add() inside library code — no file sinks, no stdout sinks
  • Never call logger.remove() inside library code
  • Never log secrets, passwords, tokens, or API keys
  • Never use print() inside library code — not for debugging, not for output

Log levels

Level When to use
DEBUG Per-item detail: individual operations, cache hits, internal state
INFO Significant milestones visible to the consuming application
WARNING Recoverable issues: fallback used, unexpected but non-fatal state
ERROR Failures the caller must know about — operation cannot continue

7. Environment and Secrets

  • Libraries do not use .env files or python-dotenv
  • Configuration is passed by the caller via arguments or constructor parameters
  • Never read os.getenv() inside library code unless explicitly documented as a supported configuration mechanism

8. Error Handling

Define specific exception types in src/<package>/exceptions.py. Use a fail-fast approach — surface errors early rather than silently continuing. All public exceptions must be exported from the top-level __init__.py.


9. Testing

  • pytest only — no unittest, no TestCase classes, no self.assert*
  • Arrange-Act-Assert pattern
  • Test naming: test_<action>_<context>
  • Do not commit poetry.lock — this is a library; consumers pin their own dependencies

10. Tooling

Tool Purpose
Ruff Formatting and linting
mypy Static type checking
pytest Testing

Run before every commit:

poetry run ruff check
poetry run mypy
poetry run pytest

11. Poetry

poetry install                    # Install all dependencies
poetry add <pkg>                  # Add runtime dependency
poetry add --group dev <pkg>      # Add dev dependency
poetry remove <pkg>               # Remove dependency
poetry run <cmd>                  # Run command in virtualenv
poetry build                      # Build sdist and wheel into dist/
poetry publish                    # Publish to PyPI

Never edit pyproject.toml directly to add or remove dependencies.

pyproject.toml configuration for a library

[project]
name = "your-library"
version = "0.1.0"
description = "Short description"
requires-python = ">=3.x"
dependencies = []

[tool.poetry]
packages = [{include = "your_library", from = "src"}]

[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"

Do not add [tool.poetry.scripts] — libraries have no entry points.

poetry.lock

Do not commit poetry.lock for libraries. It is only committed for applications. Add poetry.lock to .gitignore.


12. Project Structure

project/
├── src/
│   └── your_library/
│       ├── __init__.py       # Public API — export everything the caller needs
│       ├── exceptions.py     # All public exception types
│       └── ...               # Internal modules
├── tests/                    # Tests
├── docs/                     # Detailed documentation
├── .venv/                    # Virtual environment (managed by Poetry)
└── pyproject.toml            # Project config and dependencies
  • No entry point scripts in the project root — this is a library, not an application
  • __init__.py defines the public API; callers import from the top-level package only

13. Public API

  • Everything intended for external use must be exported from src/<package>/__init__.py
  • Use __all__ to explicitly declare the public surface
  • Internal modules are prefixed with _ or kept unexported
  • Public API must be stable across patch versions; breaking changes require a major version bump

14. Distribution

Build and publish with Poetry:

poetry build      # Creates dist/*.tar.gz and dist/*.whl
poetry publish    # Publishes to PyPI (requires credentials)

dist/ is not committed to the repository — add it to .gitignore.


15. Versioning

  • Follow semantic versioning: MAJOR.MINOR.PATCH
  • Version is defined in pyproject.toml under [project]
  • Always ask before bumping the version — never increment automatically
  • Update CHANGELOG.md before bumping the version
  • Breaking changes to the public API require a major version bump

16. Documentation and Task Management

  • Keep PROJECT.md and CHANGELOG.md up to date when making changes
  • README.md must contain installation instructions and usage examples for the public API
  • Document architectural changes in this file or in docs/

Task notation

Tasks are written as single-line comments directly in code, or in PROJECT.md for cross-cutting concerns:

# TODO: one-liner description of a task to be done
# FIXME: one-liner description of a known bug to be fixed

No other task format is used — no checkboxes, no numbered lists in documentation.

If a # TODO: comment already exists at a specific location in code, do not repeat it in PROJECT.md.