# Python Development Guidelines **Document Version:** v8 > **Note on Versioning:** > - This document version is independent — reused across projects > - **Project version** source of truth: `pyproject.toml` > - Version propagates: `pyproject.toml` → `constants.py` → code > - `CHANGELOG.md` uses project version from `pyproject.toml` ## Related Documents - **README.md** — Project overview, tool descriptions, build instructions - **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: `README.md`, `AGENTS.md`, `DESIGN_DOCUMENT.md`, `PROJECT.md`, `CHANGELOG.md`. --- ## 1. Code Style - **PEP8** with 120-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 and Console Output ### Logging — loguru Use **loguru** for all internal logging. Never log secrets, passwords, tokens, or API keys. #### Log sinks | Sink | Level | Format | |------|-------|--------| | File `logs/{AppName}_{time}.log` | `DEBUG` | full (timestamp + level + message) | | stdout | `INFO` | full | File sink retains **max 10 log files** (`retention=10`). No rotation by size — each run creates a new file via `{time}` in the filename. The `DEBUG` sink is only active when `constants.DEFAULT_DEBUG` is `True` (controlled by `ENV_DEBUG=true` in `.env`). Additional sinks (e.g. GUI log panels) may be added per project as needed. #### Log levels | Level | When to use | |-------|-------------| | `DEBUG` | Per-item detail: individual file reads/writes, cache hits, per-row operations | | `INFO` | User-visible milestones: file loaded, process started/finished, file saved | | `WARNING` | Recoverable issues: fallback used, unexpected but non-fatal state | | `ERROR` | Failures the user must know about: missing file, failed save, missing config — operation cannot continue | ### Console output — print() `print()` is **allowed** for direct user-facing console communication: input prompts, progress lines, and result summaries. This applies to console tools that interact with the user through stdin/stdout. `print()` is **not** a substitute for loguru. It must not be used for debugging or internal event tracking. --- ## 7. Environment and Secrets - Secrets in `.env` file; controlled by `ENV_DEBUG=true/false` - Load via `python-dotenv` and `os.getenv()` - Never commit `.env` --- ## 8. Error Handling Define specific exception types. Use a fail-fast approach — surface errors early rather than silently continuing. --- ## 9. Testing - **pytest** only — no `unittest`, no `TestCase` classes, no `self.assert*` - Arrange-Act-Assert pattern - Test naming: `test__` --- ## 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 ``` Never edit `pyproject.toml` directly to add or remove dependencies. ### poetry.lock `poetry.lock` **is committed** for applications — it pins the exact dependency graph and keeps builds reproducible. --- ## 12. Project Structure ``` project/ ├── entry_point.py # Entry point(s) — named by purpose or tool name ├── src/ # All application modules ├── tests/ # Tests ├── docs/ # Detailed documentation ├── .venv/ # Virtual environment (managed by Poetry) └── pyproject.toml # Project config and dependencies ``` A project may have multiple entry points (e.g. `cli.py`, `gui.py`, or per-tool scripts). --- ## 13. Distribution and Deployment When a project is distributed as a standalone executable (no Python required on target machine): - Use **PyInstaller** to compile each entry point into a single `.exe` - Each tool has its own `.spec` file in the project root - **Console tools use `console=True`** in the `.spec` — they rely on `input()` and `print()` for user interaction. GUI applications turn the console off with `ENV_BUILD_CONSOLE=false`; that flag is independent of `ENV_DEBUG` - Compiled executables are stored in `dist/` and **committed to the repository** — the repository serves as the distribution channel for internal teams - `.gitignore` must **not** exclude `dist/` in projects that use this deployment model ### prebuild.py `prebuild.py` runs before PyInstaller. It verifies that the active interpreter is the project `.venv`, prints the resolved version, and rewrites the `console=` line in the `.spec` to match `.env`. | Variable | Default | Effect | |----------|---------|--------| | `ENV_BUILD_CONSOLE` | `true` | Value written into `console=` in the `.spec` | | `ENV_BUILD_SPEC` | `.spec` | Which `.spec` file to update | `prebuild.py` touches one `.spec` per run — with several entry points, set `ENV_BUILD_SPEC` for each build. Build commands: ```bash poetry run python prebuild.py poetry run pyinstaller ToolName.spec ``` > This section applies only to projects that produce standalone executables. Skip for libraries or web services. --- ## 14. 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 --- ## 15. Documentation and Task Management - Keep `PROJECT.md` and `CHANGELOG.md` up to date when making changes - Document architectural changes in this file or in `docs/` ### Task notation Tasks are written as single-line comments directly in code, using the **Todo Tree** tags defined in `AGENTS.md` (`TODO`, `FIXME`, `BUG`, `HACK`, `NOTE`). `PROJECT.md` carries only cross-cutting tasks that have no single place in the code. ```python # TODO: extract this into a separate loader # FIXME: crashes on an empty file # BUG: rounding is off by one cent on negative amounts # HACK: temporary workaround until the API adds paging # NOTE: order matters here, the parser is stateful ``` No other task format is used — **no checkboxes, no numbered lists in documentation**. If a tag already exists at a specific location in code, do not repeat it in `PROJECT.md`.