Python Coding Standards
Sylla-specific standards for the Python data-engine codebase. All rules are settled and enforced in code review. Full rationale and code examples for every rule are in references/standards.md.
How to Apply This Skill
- For quick lookups (rule name, convention), use the Quick Reference tables below.
- For rationale, code examples, or edge cases, consult
references/standards.md.
- When reviewing code, run through the Code Smell Checklist at the bottom of this file.
All work must satisfy readability, KISS, DRY, YAGNI, and security — specifically: no eval/exec/pickle, T | None over Optional[T], explicit return types on public functions, and @property for computed attributes.
Quick Reference
Type Annotations
| Rule |
Standard |
| Optional types |
T | None — never Optional[T] |
| Return types on public functions |
Required |
| Return types on internal helpers |
Inferred is acceptable |
| Computed attributes |
@property — never get_x() methods |
| Generics |
list[str], dict[str, int] — never List, Dict from typing |
| Datetime |
datetime.now(timezone.utc) — never datetime.utcnow() |
Imports & Modules
| Rule |
Standard |
| Import location |
Top of module always |
| Function-level imports |
Only to break circular dependencies |
| Import order |
stdlib > third-party > local (isort enforced) |
| Unused imports |
Remove — never comment out |
Functions & Classes
| Rule |
Standard |
| Docstrings |
Google-style on public APIs |
| Max function length |
~50 lines — split if longer |
| Nesting |
Max 3 levels — use early returns |
| Abstract base classes |
ABC + @abstractmethod for shared contracts |
| Protocols |
Use for structural typing when ABC is too rigid |
Error Handling
| Rule |
Standard |
Bare except |
Banned |
Broad except Exception |
Only at top-level boundaries with logging |
Silent except: pass |
Banned |
| Logging library |
structlog with bound loggers |
| Error messages |
Include context (IDs, operation name) |
| Custom exceptions |
Inherit from domain-specific base, not bare Exception |
Async Patterns
| Rule |
Standard |
| Parallel async calls |
asyncio.gather(*tasks) |
| HTTP client |
httpx.AsyncClient with explicit timeouts |
| Sync calls in async |
Banned — use asyncio.to_thread() if unavoidable |
| Client lifecycle |
Context managers (async with) for connection pools |
Pydantic Models
| Rule |
Standard |
| Extra fields |
model_config = ConfigDict(extra="forbid") |
| Field metadata |
Field(description="...") on every field |
| Computed attributes |
@property — never get_x() |
| Enums in models |
Use StrEnum for string enums |
Naming
| Thing |
Convention |
| Files & folders |
snake_case |
| Variables & functions |
snake_case |
| Classes |
PascalCase |
| Constants |
SCREAMING_SNAKE_CASE |
| Private attributes |
Single leading underscore _name |
| Type variables |
PascalCase (T, ResponseT) |
| Boolean variables |
is_, has_, can_ prefix |
Tooling
| Tool |
Config |
| Black |
line-length 88, target py311 |
| isort |
profile "black", line_length 88 |
| MyPy |
strict — disallow_untyped_defs = true, warn_return_any = true |
| Pytest |
pythonpath = ["."], testpaths = ["tests"] |
| Flake8 |
Ignore E203, W503 (Black-incompatible rules) |
Code Smell Checklist
Before opening a PR, check:
1---2name: coding-standards-23description: This skill should be used when the user asks about "Python coding standards", "Python code style", "type hints", "type annotations", "Optional vs union", "T | None", "mypy", "black", "isort", "flake8", "ruff", "Pydantic models", "ConfigDict", "async patterns", "asyncio", "structlog", "error handling in Python", "Python naming conventions", "snake_case", "reviewing a Python PR", "does this pass Python code review", "BaseHarvester", "Dramatiq actors", "pydantic-settings", "pytest conventions", or wants to know whether code follows Sylla's Python data-engine repository standards.4---56# Python Coding Standards78Sylla-specific standards for the Python data-engine codebase. All rules are settled and enforced in code review. Full rationale and code examples for every rule are in **`references/standards.md`**.910## How to Apply This Skill1112- For quick lookups (rule name, convention), use the Quick Reference tables below.13- For rationale, code examples, or edge cases, consult `references/standards.md`.14- When reviewing code, run through the Code Smell Checklist at the bottom of this file.1516---1718All work must satisfy readability, KISS, DRY, YAGNI, and security — specifically: no `eval`/`exec`/`pickle`, `T | None` over `Optional[T]`, explicit return types on public functions, and `@property` for computed attributes.1920---2122## Quick Reference2324### Type Annotations2526| Rule | Standard |27|------|----------|28| Optional types | `T \| None` — never `Optional[T]` |29| Return types on public functions | Required |30| Return types on internal helpers | Inferred is acceptable |31| Computed attributes | `@property` — never `get_x()` methods |32| Generics | `list[str]`, `dict[str, int]` — never `List`, `Dict` from `typing` |33| Datetime | `datetime.now(timezone.utc)` — never `datetime.utcnow()` |3435### Imports & Modules3637| Rule | Standard |38|------|----------|39| Import location | Top of module always |40| Function-level imports | Only to break circular dependencies |41| Import order | stdlib > third-party > local (isort enforced) |42| Unused imports | Remove — never comment out |4344### Functions & Classes4546| Rule | Standard |47|------|----------|48| Docstrings | Google-style on public APIs |49| Max function length | ~50 lines — split if longer |50| Nesting | Max 3 levels — use early returns |51| Abstract base classes | `ABC` + `@abstractmethod` for shared contracts |52| Protocols | Use for structural typing when ABC is too rigid |5354### Error Handling5556| Rule | Standard |57|------|----------|58| Bare `except` | Banned |59| Broad `except Exception` | Only at top-level boundaries with logging |60| Silent `except: pass` | Banned |61| Logging library | `structlog` with bound loggers |62| Error messages | Include context (IDs, operation name) |63| Custom exceptions | Inherit from domain-specific base, not bare `Exception` |6465### Async Patterns6667| Rule | Standard |68|------|----------|69| Parallel async calls | `asyncio.gather(*tasks)` |70| HTTP client | `httpx.AsyncClient` with explicit timeouts |71| Sync calls in async | Banned — use `asyncio.to_thread()` if unavoidable |72| Client lifecycle | Context managers (`async with`) for connection pools |7374### Pydantic Models7576| Rule | Standard |77|------|----------|78| Extra fields | `model_config = ConfigDict(extra="forbid")` |79| Field metadata | `Field(description="...")` on every field |80| Computed attributes | `@property` — never `get_x()` |81| Enums in models | Use `StrEnum` for string enums |8283### Naming8485| Thing | Convention |86|-------|-----------|87| Files & folders | snake_case |88| Variables & functions | snake_case |89| Classes | PascalCase |90| Constants | SCREAMING_SNAKE_CASE |91| Private attributes | Single leading underscore `_name` |92| Type variables | PascalCase (`T`, `ResponseT`) |93| Boolean variables | `is_`, `has_`, `can_` prefix |9495### Tooling9697| Tool | Config |98|------|--------|99| Black | line-length 88, target py311 |100| isort | profile "black", line_length 88 |101| MyPy | strict — `disallow_untyped_defs = true`, `warn_return_any = true` |102| Pytest | `pythonpath = ["."]`, `testpaths = ["tests"]` |103| Flake8 | Ignore E203, W503 (Black-incompatible rules) |104105---106107## Code Smell Checklist108109Before opening a PR, check:110111- [ ] `Optional[T]` — change to `T | None`112- [ ] `datetime.utcnow()` — change to `datetime.now(timezone.utc)`113- [ ] `get_x()` method for a simple computed attribute — change to `@property`114- [ ] Import inside a function body (no circular dependency reason) — move to top of module115- [ ] Function >~50 lines — split it116- [ ] >3 levels of nesting — use early returns117- [ ] Bare `except` or `except: pass` — catch specific exceptions, log the error118- [ ] `except Exception` in non-boundary code — narrow the exception type119- [ ] Magic number/string — extract to `SCREAMING_SNAKE_CASE` constant120- [ ] `typing.List`, `typing.Dict`, `typing.Tuple` — use built-in `list`, `dict`, `tuple`121- [ ] Missing return type on public function — add explicit annotation122- [ ] `eval()`, `exec()`, `pickle.loads()` — find a safe alternative123- [ ] HTTP request without timeout — add explicit timeout124- [ ] `datetime.utcnow()` — use `datetime.now(timezone.utc)`125- [ ] Sync blocking call inside `async` function — use `asyncio.to_thread()`126- [ ] N+1 query pattern (DB/API call inside loop) — batch or use `asyncio.gather`127- [ ] Pydantic model missing `ConfigDict(extra="forbid")` — add it128- [ ] `get_x()` method that is a simple property — convert to `@property`