# 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` ## Related Documents - **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 ```python 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: ```python from loguru import logger logger.disable("") # suppress all logger.add(sys.stderr, filter={"": "WARNING"}) # WARNING and above only logger.enable("") # 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//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__` - 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: ```bash poetry run ruff check poetry run mypy poetry run pytest ``` --- ## 11. Poetry ```bash poetry install # Install all dependencies poetry add # Add runtime dependency poetry add --group dev # Add dev dependency poetry remove # Remove dependency poetry run # 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 ```toml [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//__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: ```bash 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: ```python # 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`.