261 lines
7.7 KiB
Markdown
261 lines
7.7 KiB
Markdown
|
|
# 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("<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:
|
||
|
|
```bash
|
||
|
|
poetry run ruff check
|
||
|
|
poetry run mypy
|
||
|
|
poetry run pytest
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 11. Poetry
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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
|
||
|
|
|
||
|
|
```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/<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:
|
||
|
|
|
||
|
|
```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`.
|