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.tomlunder[project]CHANGELOG.mduses project version frompyproject.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
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
.envfiles orpython-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, noTestCaseclasses, noself.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__.pydefines 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.tomlunder[project] - Always ask before bumping the version — never increment automatically
- Update
CHANGELOG.mdbefore bumping the version - Breaking changes to the public API require a major version bump
16. Documentation and Task Management
- Keep
PROJECT.mdandCHANGELOG.mdup to date when making changes README.mdmust 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.