# Python development rules
These rules describe how you must develop any Python projects.
Use the `uv` package & project manager to operate all projects.
If `uv` is not available in your environment, you can install it with `pipx` if available:
```bash
pipx install uv
```
Or with curl, as `uv` can later update itself:
```
curl -LsSf https://astral.sh/uv/install.sh | sh
```
Once in a dedicated directory, the project can be set up with:
```bash
uv self update # ensure latest uv
uv init --bare # create pyproject.toml file
uv venv # create .venv/ setup
git init -b main
```
The `uv init` step might optionally take `--app`, `--lib`, or `--package` as argument instead of `--bare` if you know already that it will be more appropriate:
- `--app`: web servers, command-line interfaces (with a main.py file at the root)
- `--lib`: functions and objects for other projects to consume (no entry point)
- `--package`: Python package development with a build system
## Basic rules
- Source code is in a `./$CODE/` directory (e.g., `src/`, `mypack/`, etc.)
- Tests are in a parallel `./tests/` directory
- All tools must be run via `uv run` (do not invoke tools directly).
- All code is **strictly type-checked**, no excuses.
- **Never** add import statements anywhere else than the header of a module.
## Required tools
- ruff (code formatting, linting, including bandit-like security audit)
- ty (type safety)
- pytest (test runner)
- pip-audit (dependencies audit)
Install tool-chain into the project environment with `uv`:
```bash
uv add --dev ruff ty pytest pip-audit
````
### Format the code
Ruff will automatically fix any code formatting issues.
```bash
uv run ruff format $CODE tests
```
### Lint and auto-fix where possible
Use an extensive ruleset for security, bugs, and other poor coding habits.
Enforce PEP8 naming with N, too.
Ruff formatting handles line lengths, so that can be ignored.
```bash
uv run ruff check $CODE tests --select E,F,W,S,B,C4,I,N --ignore E501 --fix
```
Configure this in the `pyproject.toml` file:
```toml
[tool.ruff]
select = [
"E", # pycodestyle errors (default)
"W", # pycodestyle warnings (default)
"F", # pyflakes
"B", # flake8-bugbear
"S", # flake8-bandit
"C4", # flake8-comprehensions
"I", # isort
"N", # pep8-naming
]
```
### Write type-safe code
The use of `Any` is a smell. Only use `Any` if there is a technically clear justification.
```bash
uv run ty $CODE
```
- `ty` suppression syntax: `# type: ignore` (plain — bracketed error codes are not supported).
### Do test-driven development
Write failing tests before implementation. Follow [@testing](Tech/Software%20Development/Skills/tdd/SKILL.md) guidelines. And run the tests with `pytest`:
```bash
uv run pytest tests
```
### Audit dependencies
Only needed when you add a new package of once in a while, to ensure no dependencies have known vulnerabilities.
```bash
uv run pip-audit
```
## Code standards
- Keep functions small (a few lines), single-purpose, and easy to test (if public).
- Ensure all public functions, interfaces, and non-private variables are type-checked and tested.
- Always strive to reduce the size of the public API.
- Never make an exception like "if TYPE_CHECKED: ...", assume all code is type-checked.
- Always prefer expressive code over adding comments.
- Extract logic into short, well-named functions or classes that **describe intent**.
- Only comment when the logic still is genuinely hard to understand.
- Never add imports anywhere except at the head of the module.
- Follow the SOLID principles when designing or writing code: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion.
- Before writing manual conversion glue to bridge a third-party library with stdlib, check for a compat/interop submodule (e.g. `<pkg>.compat`).
- Closed sets of string values used across call sites (error codes, statuses, taxonomies) should be an `Enum`, never repeated literals.
**Bad: opaque condition**
```python
if user.role == 'admin' and \
user.verified and \
not user.suspended and \
time.time() < user.expires_at:
show_dashboard()
```
**Good: expressive logic**
```python
class User:
...
def can_access_dashboard(self) -> bool:
return (self.role == 'admin'
and self.verified
and not self.suspended
and not self._has_session_expired())
def _has_session_expired(self) -> bool:
return time.time() >= self.expires_at
if user.can_access_dashboard():
show_dashboard()
```
### Error handling
- Define custom error types/classes
- Never silently catch errors without handling
- Use specific catch blocks instead of catching all errors
- Log errors with sufficient context for debugging (e.g. user ID, document ID, relevant settings)
### Logging
- Provide context through `LoggerAdapater` and `Filter` classes.
- Capture and track context with the `contextvars` StdLib package.
- Refer to https://docs.python.org/3/howto/logging.html and https://docs.python.org/3/howto/logging-cookbook.html for proper logging practices.
### Dependencies
- ONLY ADD DEPENDENCIES WHEN NEEDED.
- Do not add dependencies just because they are planned.
- Only add a dependency when writing code that needs it.
- Only add dependencies ad hoc if the project does not compile or start because of a missing dependency.
### Documentation
- When creating code blocks in documentation, do not add comments inside the code blocks; Add the comments outside, in the text area.
## Definition of Done
After making a test green and the refactoring is done, ensure all tools pass or fix any issues.
Note that `ruff` has to be configured in the `pyproject.toml` file or you need to use the extended arguments to select rulesets shown above in the linting section.
```bash
uv run ruff format $CODE tests
uv run ty $CODE
uv run ruff check $CODE tests --fix
uv run pytest tests
uv run pip-audit
```
### Git pre-commit hooks to test DoD
Instead of running the above checks every time manually, create git commit hooks for ruff, ty, and pytest.
Set the proper hooks up using `uvx pre-commit` and a `.pre-commit-config.yaml` configuration, and mention that approach in the project's README.
Run pip-audit only when you add a dependency.