agentleFS
tiago-oliveira-catiago-oliveira-ca/zenodotus-harness/skills/code-standards/python/SKILL.md

python

Skill1 starsChanged 8 days agoMITView on GitHub

Comprehensive Python code standards based on PEP 8, PEP 20, PEP 257, and the Google Python Style Guide — the base layer for all Python work; routes to framework standards (FastAPI, DRF, Airflow) in `frameworks/` and deeper architecture references. Use whenever writing, reviewing, or refactoring Python. Trigger on: "write Python", "Python function", "is this Pythonic", "Python best practices", "PEP 8", "Python code review", "FastAPI", "Django REST Framework", "DRF", "Airflow", "DAG", "DDD", "clean architecture", "hexagonal architecture", "design pattern", or any Python development task.

  • Installs packages

We split this skill into the rules it gives an agent

28 rules. 0 are common, 12 are distinctive, and 16 are very specific to this project.

  • 0

    Common

    Most of the 40 edited versions we read keep these.

  • 12

    Distinctive

    This author's own choices. Worth a look.

  • 16

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    When writing, reviewing, or refactoring Python code, follow these standards (based on PEP 8, PEP 20, PEP 257, and the [Google Python Style

    line 15
  2. Very specific:

    Never: use l (el), O (oh), or I (eye) as single-character variable names.

    line 130
  3. Distinctive:

    Avoid abbreviations that are ambiguous or unfamiliar outside your project.

    line 131
  4. Very specific:

    Don't include the type in the variable name (e.g., id_to_name_dict).

    line 132
  5. Distinctive:

    Always use descriptive names.

    line 133
  6. Very specific:

    Always use self as the first argument to instance methods.

    line 137
  7. Very specific:

    Always use cls as the first argument to class methods.

    line 138
  8. Very specific:

    If an argument name clashes with a keyword, append a trailing underscore (class_).

    line 139
  9. Distinctive:

    Use sparingly and only when they add non-obvious context.

    line 153
  10. Distinctive:

    Never state the obvious:

    line 155
  11. Distinctive:

    Write docstrings for all public modules, functions, classes, and methods.

    line 181
  12. Distinctive:

    If multi-line, leave a blank line after the summary, then add elaboration.

    line 183
  13. Distinctive:

    Every module should begin with a docstring:

    line 247
  14. Very specific:

    Use built-in generics (list, dict, tuple, set) instead of imports from typing where possible (Python 3.9+).

    line 275
  15. Very specific:

    Use X | Y union syntax instead of Union[X, Y] (Python 3.10+).

    line 276
  16. Very specific:

    Don't annotate the return value of __init__.

    line 278
  17. Very specific:

    Use built-in exception classes when appropriate (e.g., ValueError, TypeError).

    line 307
  18. Very specific:

    Never: use bare except: or catch Exception unless re-raising or creating an isolation point.

    line 309
  19. Very specific:

    Use finally for cleanup.

    line 311
  20. Very specific:

    Do not use assert for validation of production data—only for internal invariants and tests.

    line 312
  21. Very specific:

    Use is / is not for comparisons with singletons like None, never == / !=:

    line 345
  22. Distinctive:

    Use implicit truthiness where possible:

    line 359
  23. Very specific:

    Use with statements for resource management (files, sockets, locks):

    line 391
  24. Distinctive:

    Prefer comprehensions for simple cases.

    line 406
  25. Distinctive:

    Never: use mutable objects as default argument values:

    line 425
  26. Very specific:

    Always wrap executable code in a __main__ check:

    line 440
  27. Very specific:

    Use Ruff as the one formatter/linter.

    line 464
  28. Distinctive:

    When writing or reviewing Python code, verify:

    line 531

“Common” is measured: kept by at least 6 in 10 of the 40 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Python Code Standards

When writing, reviewing, or refactoring Python code, follow these standards (based on PEP 8, PEP 20, PEP 257, and the Google Python Style Guide).

This is the base layer for all Python code, general or framework-specific; test code itself belongs to zeno-stack/zeno-python-testing. This is one skill: core Python standards live in this file, framework-specific standards live as plain reference files in frameworks/, and deeper same-language material lives in references/. If the task involves a specific framework, apply this file's standards plus the matching framework reference below — each frameworks/ file assumes this file's PEP 8/naming/docstring/typing rules and only covers what's specific to that framework:

FrameworkReference
FastAPI, Pydantic v2, async SQLAlchemyframeworks/fastapi.md
Django REST Framework, Django, DRFframeworks/django-rest-framework.md
Apache Airflow 3, Astronomer, DAG authoringframeworks/airflow.md

For deeper, non-framework-specific Python material, load the matching file in references/:

ConcernReference
DDD, Clean/Hexagonal Architecture, SOLID, ports & adapters, design patterns in idiomatic Pythonreferences/ddd-clean-architecture.md
Strategic DDD (bounded contexts, context mapping, EventStorming), TDD practice, BDD/Gherkin, Kafka event-driven testingreferences/ddd-tdd-bdd-workflow.md
Pythonic structural idioms — EAFP vs LBYL, advanced typing (Protocol, TypeVar), exception chaining, custom context managers, generators, dataclasses, decorators, concurrency model choice (threads/processes/asyncio), package layout, __slots__references/pythonic-idioms.md

For domain modeling, Clean/Hexagonal Architecture, SOLID applied to Python specifically, or choosing/rejecting a design pattern or dependency-injection approach, additionally load references/ddd-clean-architecture.md — load it alongside this file (and the framework reference, if any) whenever the task is architectural, not just stylistic: aggregates, ports and adapters, a Repository/Unit-of-Work, a composition root, or a "should I use a design pattern here" question.

For strategic domain design (bounded contexts, context mapping, EventStorming), TDD/BDD process, or Kafka-specific event-driven testing, additionally load references/ddd-tdd-bdd-workflow.md — load it alongside references/ddd-clean-architecture.md, not instead of it: that file owns tactical patterns (aggregates, repositories, UoW) and the test pyramid; this one owns strategic design and the TDD/BDD/Kafka-testing methodology built around those tactical patterns.


1. The Zen of Python (PEP 20)

These guiding principles should inform every design and coding decision:

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

2–4. Layout, Imports, Whitespace — enforced by Ruff, not restated here

Raw PEP 8 mechanics (indentation and continuation-line alignment, blank-line counts, import ordering, operator spacing, whitespace pet peeves) are mechanically enforced by this skill's own Ruff configuration in §11 — run it rather than reviewing for these by hand; a rule the formatter already enforces is not worth a human (or model) pass. The house choices layered on top of plain PEP 8, which Ruff is configured to enforce and which do need stating:

  • Line length: 79 characters for code (Ruff line-length = 79), 72 for docstrings/comments — the stricter PEP 8 limit, not the common 88/100 relaxations.
  • 4 spaces, never tabs; hanging indents add a level, closing brackets align with the first character of the construct's line.
  • Import groups stdlib → third-party → local, blank line between groups, one module per import line, no wildcard imports (from x import * breaks static tooling), and prefer absolute imports; explicit relative imports are acceptable within a package.
  • Module-level dunders (__all__, __version__) sit after the module docstring but before any imports except from __future__.

5. Naming Conventions

5.1 Summary Table

TypeConventionExample
Modulelowercase_with_underscoresmy_module.py
Packagelowercase (no underscores)mypackage
ClassCapitalizedWords (CamelCase)MyClass
ExceptionCapitalizedWords + Error suffixConnectionError
Functionlowercase_with_underscorescalculate_total()
Methodlowercase_with_underscoresget_name()
Variablelowercase_with_underscorestotal_count
ConstantUPPER_CASE_WITH_UNDERSCORESMAX_OVERFLOW
Type VariableCapitalizedWords (short)T, AnyStr

5.2 Underscore Conventions

PatternMeaning
_single_leadingWeak "internal use" indicator; not imported by from M import *
single_trailing_Used to avoid conflicts with Python keywords (e.g., class_)
__double_leadingTriggers name mangling in classes
__dunder__"Magic" attributes/methods; never invent new ones

5.3 Names to Avoid

  • Never use l (el), O (oh), or I (eye) as single-character variable names.
  • Avoid abbreviations that are ambiguous or unfamiliar outside your project.
  • Don't include the type in the variable name (e.g., id_to_name_dict).
  • Always use descriptive names; descriptiveness should be proportional to scope.

5.4 Function and Method Arguments

  • Always use self as the first argument to instance methods.
  • Always use cls as the first argument to class methods.
  • If an argument name clashes with a keyword, append a trailing underscore (class_).

6. Comments

6.1 Block Comments

  • Indented to the same level as the code they describe.
  • Each line starts with # followed by a single space.
  • Separate paragraphs within a block comment with a line containing only #.

6.2 Inline Comments

  • Use sparingly and only when they add non-obvious context.
  • Separate from the statement by at least two spaces, starting with # .
  • Never state the obvious:
# ❌ Wrong
x = x + 1  # Increment x

# ✅ Correct
x = x + 1  # Compensate for border

6.3 TODO Comments

Format (Google Style):

# TODO(username): Refactor this to use the new Widget class.
# TODO: b/12345678 - Remove this after the migration is complete.

7. Docstrings (PEP 257 + Google Style)

7.1 General Rules

  • Always use """triple double quotes""".
  • Write docstrings for all public modules, functions, classes, and methods.
  • The first line is a summary ending in a period, question mark, or exclamation point.
  • If multi-line, leave a blank line after the summary, then add elaboration.
  • One-liner docstrings keep closing """ on the same line.
  • Multi-line docstrings place the closing """ on its own line.

7.2 One-Line Docstrings

def kos_root():
    """Return the pathname of the KOS root directory."""
    global _kos_root
    if _kos_root:
        return _kos_root

7.3 Multi-Line Docstrings (Google Style)

def fetch_smalltable_rows(
    table_handle: smalltable.Table,
    keys: Sequence[bytes | str],
    require_all_keys: bool = False,
) -> Mapping[bytes, tuple[str, ...]]:
    """Fetch rows from a Smalltable.

    Retrieve rows pertaining to the given keys from the Table instance
    represented by table_handle. String keys will be UTF-8 encoded.

    Args:
        table_handle: An open smalltable.Table instance.
        keys: A sequence of strings representing the key of each table row
            to fetch. String keys will be UTF-8 encoded.
        require_all_keys: If True, only rows with values set for all keys
            will be returned.

    Returns:
        A dict mapping keys to the corresponding table row data fetched.
        Each row is represented as a tuple of strings.

    Raises:
        IOError: An error occurred accessing the smalltable.
    """

7.4 Class Docstrings

class SampleClass:
    """Summary of class here.

    Longer class information...

    Attributes:
        likes_spam: A boolean indicating if we like SPAM or not.
        eggs: An integer count of the eggs we have laid.
    """

    def __init__(self, likes_spam: bool = False):
        """Initialize SampleClass with preference settings."""
        self.likes_spam = likes_spam
        self.eggs = 0

7.5 Module Docstrings

Every module should begin with a docstring:

"""A one-line summary of the module, terminated by a period.

Leave one blank line. The rest of this docstring should contain an
overall description of the module or program. Optionally, it may also
contain a brief description of exported classes and functions and/or
usage examples.

Typical usage example:

    foo = ClassFoo()
    bar = foo.function_bar()
"""

Cross-cutting doc discipline (when to document, keeping docs in sync with code / drift, driving Sphinx/mkdocstrings): see documenting-code. Prefer docstrings with runnable doctest examples — they self-verify and are the cheapest drift guard.


8. Type Annotations (PEP 484 + Google Style)

8.1 General Rules

  • Annotate all public APIs with type hints.
  • Use built-in generics (list, dict, tuple, set) instead of imports from typing where possible (Python 3.9+).
  • Use X | Y union syntax instead of Union[X, Y] (Python 3.10+).
  • Annotating self or cls is generally unnecessary.
  • Don't annotate the return value of __init__.

8.2 Examples

def greeting(name: str) -> str:
    """Generate a greeting message."""
    return f"Hello, {name}!"

def process_items(items: list[int], threshold: float = 0.5) -> dict[str, int]:
    """Process items above a threshold."""
    return {"above": sum(1 for x in items if x > threshold)}

# For complex signatures, break one parameter per line
def my_method(
    self,
    first_var: int,
    second_var: Foo,
    third_var: Bar | None,
) -> int:
    ...

9. Exception Handling (Google Style)

9.1 Rules

  • Use built-in exception classes when appropriate (e.g., ValueError, TypeError).
  • Custom exceptions should inherit from an existing exception class and end with Error.
  • Never use bare except: or catch Exception unless re-raising or creating an isolation point.
  • Minimize code inside try/except blocks.
  • Use finally for cleanup.
  • Do not use assert for validation of production data—only for internal invariants and tests.

9.2 Example

def connect_to_next_port(self, minimum: int) -> int:
    """Connect to the next available port.

    Args:
        minimum: A port value greater or equal to 1024.

    Returns:
        The new minimum port.

    Raises:
        ConnectionError: If no available port is found.
    """
    if minimum < 1024:
        raise ValueError(f"Min. port must be at least 1024, not {minimum}.")
    port = self._find_next_open_port(minimum)
    if port is None:
        raise ConnectionError(
            f"Could not connect to service on port {minimum} or higher."
        )
    return port

10. Programming Recommendations

10.1 Comparisons

  • Use is / is not for comparisons with singletons like None, never == / !=:
# ✅ Correct
if foo is not None:
    ...

# ❌ Wrong
if foo != None:
    ...

10.2 Boolean Checks

  • Use implicit truthiness where possible:
# ✅ Correct
if items:
    ...
if not items:
    ...

# ❌ Wrong
if len(items) != 0:
    ...
if items == []:
    ...

10.3 String Concatenation

  • For joining many strings, use "".join() instead of += in loops:
# ✅ Correct
result = "".join(items)

# ❌ Wrong (O(n²) in some implementations)
result = ""
for item in items:
    result += item

10.4 Context Managers

  • Use with statements for resource management (files, sockets, locks):
# ✅ Correct
with open("file.txt") as f:
    data = f.read()

# ❌ Wrong
f = open("file.txt")
data = f.read()
f.close()

10.5 Comprehensions & Generators

  • Prefer comprehensions for simple cases; avoid for complex logic:
# ✅ Good — simple and readable
squares = [x * x for x in range(10)]

# ❌ Bad — too complex, use a loop instead
result = [
    (x, y, z)
    for x in range(5)
    for y in range(5)
    if x != y
    for z in range(5)
    if y != z
]

10.6 Default Arguments

  • Never use mutable objects as default argument values:
# ✅ Correct
def foo(a: list[int] | None = None):
    if a is None:
        a = []

# ❌ Wrong
def foo(a: list[int] = []):
    ...

10.7 The Main Guard

  • Always wrap executable code in a __main__ check:
def main():
    ...

if __name__ == "__main__":
    main()

11. Linting & Formatting Tools

Enforce these standards automatically using the following tools:

ToolPurposeInstall
RuffFast linter + formatter — the single recommended tool; replaces flake8, isort, and blackpip install ruff
mypyStatic type checkerpip install mypy
pylintComprehensive linter (optional; Google style guide references it)pip install pylint
banditSecurity linter for common Python security issuespip install bandit
pip-auditDependency vulnerability scanning (run in CI)pip install pip-audit

Use Ruff as the one formatter/linter. Black and isort are legacy standalone alternatives — do not add them alongside Ruff, which already covers both (ruff format = Black-compatible formatting, the I rule = isort).

Recommended pyproject.toml configuration:

[tool.ruff]
line-length = 79
target-version = "py311"

[tool.ruff.lint]
select = [
    "E",     # pycodestyle errors
    "W",     # pycodestyle warnings
    "F",     # pyflakes
    "I",     # isort
    "N",     # pep8-naming
    "D",     # pydocstyle
    "UP",    # pyupgrade
    "B",     # bugbear
    "SIM",   # simplify
    "ANN",   # annotations
]

[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true

Anti-pattern quick reference

Anti-patternInstead
Tabs for indentation4 spaces per level, always
import os, sys on one lineOne import per line, grouped stdlib → third-party → local
Wildcard imports (from module import *)Import the module or specific names explicitly
Spaces around = in keyword arguments/defaults (imag = 0.0)imag=0.0 — no spaces for kwargs and default values
l, O, I as single-character variable namesDescriptive names, proportional to scope
Inline comments stating the obvious (x = x + 1 # Increment x)Comment only non-obvious context (# Compensate for border)
foo != None / foo == Nonefoo is not None / foo is None
if len(items) != 0 or items == []Implicit truthiness: if items: / if not items:
Building strings with += in a loop"".join(items)
Manual open() / close() for files, sockets, lockswith context manager
Multi-clause comprehensions with complex logicA plain loop
Mutable default arguments (def foo(a: list[int] = []))Default to None and guard inside the function
Bare except: or catching Exception broadlyCatch specific exceptions; re-raise or isolate deliberately
assert to validate production dataRaise real exceptions (ValueError, etc.); reserve assert for internal invariants and tests
Executable code at module top levelWrap in main() behind if __name__ == "__main__":
type(obj) == listisinstance(obj, list) — respects subclasses
Re-raising a translated exception without fromraise NewError(...) from exc — preserves the original traceback chain
Threads for CPU-bound workProcessPoolExecutor — the GIL blocks thread-based CPU parallelism (see references/pythonic-idioms.md)
Regular class instantiated by the thousands with no __slots__Add __slots__ once instance count matters for memory

Pre-flight checklist — run before delivering any Python code

When writing or reviewing Python code, verify:

  • Indentation: 4 spaces, no tabs
  • Line length: ≤79 chars (72 for docstrings/comments)
  • Imports: Ordered (stdlib → third-party → local), one per line, no wildcards
  • Blank lines: 2 around top-level definitions, 1 around methods
  • Naming: snake_case for functions/variables, CamelCase for classes, UPPER_CASE for constants
  • Docstrings: Triple double quotes, present on all public APIs, Google style Args/Returns/Raises
  • Type hints: Present on public APIs, using modern syntax
  • Whitespace: No trailing whitespace, proper operator spacing
  • Comparisons: is/is not for singletons, implicit truthiness for containers
  • No mutable defaults: Use None + guard instead
  • Context managers: with for files, sockets, locks
  • Main guard: if __name__ == "__main__" present
  • Exception handling: Specific exceptions, no bare except:
  • Exception chaining: raise ... from used when translating one exception into another
  • Generators: Large or streamed data uses a generator, not a fully-materialized list
  • Concurrency: Tool matches the workload — threads for blocking I/O, processes for CPU, asyncio for async-native I/O (see references/pythonic-idioms.md)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.