Python Development Standards (Mark)
Python-specific standards following modern tooling and type-safe practices.
Goal
Guide Python development with modern tooling (UV, basedpyright, ruff) and consistent patterns. Ensure type-safe code with proper annotations, enforce Python 3.10+ syntax, and maintain documentation quality through Meadow Docstring Format (MDF).
When to Use This Skill
Use this skill when:
- Writing or editing Python code (.py files)
- Setting up a new Python project
- Adding type annotations to existing code
- Running Python type checkers or linters
- Working with Python package management (UV)
- Creating Python docstrings
- Refactoring Python code for type safety
- Writing Python scripts or CLI tools
Do NOT use this skill when:
- Working with other programming languages
- The project uses older Python (< 3.10) without migration plans
- The codebase has existing patterns that explicitly override these standards
- Project already uses poetry/pipenv with no intent to migrate
- One-off throwaway scripts without type checking requirements
Process
Step 1: Identify Project Context
Check the existing project structure:
# Check what the project uses
ls pyproject.toml # UV/poetry/pipenv
ls poetry.lock # Poetry
ls Pipfile # Pipenv
ls requirements.txt # pip
ls .python-version # Python version
- Identify Python version target (default to 3.10+)
- Check for existing
AGENTS.mdand read it - Understand codebase patterns from existing files
- Default to UV for new projects
Step 2: Project Management with UV
Use UV, not pip:
# Project setup
uv init
uv add <package>
uv remove <package>
# Running commands
uv run <command>
# Quick tool invocation without installing
uvx <tool>
Do NOT use:
pip install— useuv addinsteadpip freeze— useuv pip freezeor checkpyproject.tomlpoetry add— useuv addinstead (unless project uses poetry)pipenv install— useuv addinstead (unless project uses pipenv)
Step 3: Write Type-Annotated Code
Use Python 3.10+ syntax:
# ✅ CORRECT - Python 3.10+
def process(items: list[str]) -> dict[str, int | None]:
result: list[int] = []
value: str | None = None
...
# ❌ WRONG - Old syntax
from typing import List, Dict, Union, Optional
def process(items: List[str]) -> Dict[str, Optional[int]]:
result: List[int] = []
value: Optional[str] = None
...
Step 4: Add File Header and Structure
Canonical header format:
"""
project-name: brief description
with all my heart, 2024-2025, mark joshwel <mark@joshwel.co>
SPDX-License-Identifier: Unlicense OR 0BSD
"""
# Standard library imports
from pathlib import Path
from typing import Final, NamedTuple
# Third-party imports (prefix to avoid pollution)
from somelib import Thing as _Thing
# Local imports
from .utils import Result
# === Constants ===
VERSION: Final[str] = "1.0.0"
# === Type Aliases ===
Query: TypeAlias = str | Path
# === Data Types ===
class Config(NamedTuple):
debug: bool = False
Step 5: Run Type Checkers
Use both basedpyright and mypy:
uv run basedpyright
uv run mypy
Golden rule: Exhaust all other options before using ignores:
- Type narrowing with
isinstance(),hasattr(), or guard clauses - Type guards using
TypeIsfromtyping_extensions - Data contracts with proper protocols or dataclasses
- Type stubs (
.pyifiles) for untyped third-party libraries - Configuration fixes in
pyproject.toml
Only as last resort:
- For basedpyright:
# pyright: ignore[specificDiagnostic] - For mypy:
# type: ignore
Step 6: Format with Ruff
# Sort imports first (if new modules imported)
ruff check --select I --fix
# Format code
ruff format
# Check alongside basedpyright
ruff check
Step 7: Write MDF Docstrings
Use meadow Docstring Format. See mdf-majo skill for full specification.
Key Points:
- Use backticks with Python syntax:
`variable: Type` - Sections: preamble, body, attributes/arguments, methods, returns, raises, usage
- Use latest syntax even if codebase targets older Python
Step 8: Verify Before Committing
Both type checkers must pass without sweeping errors under the rug:
uv run basedpyright
uv run mypy
ruff check --select I --fix
ruff format
ruff check
Constraints
- ALWAYS use UV for new projects (not pip, poetry, or pipenv)
- ALWAYS use Python 3.10+ syntax (
list[str]notList[str],|notUnion) - ALWAYS include file header with SPDX identifier
- ALWAYS use basedpyright AND mypy — both must pass
- ALWAYS use
NamedTupleoverdataclassunless mutability is required - NEVER use
# noqafor type errors — use proper type ignores - NEVER use ignores without exhausting other options first
- NEVER write documentation unless explicitly requested
Key Patterns Summary
Result Type for Error Handling
class Result(NamedTuple, Generic[ResultType]):
value: ResultType
error: BaseException | None = None
def __bool__(self) -> bool:
return self.error is None
def get(self) -> ResultType:
if self.error is not None:
raise self.error
return self.value
# Usage
def parse_file(path: Path) -> Result[Data]:
try:
return Result(do_parsing(path))
except Exception as exc:
return Result(EMPTY_DATA, error=exc)
result = parse_file(some_path)
if not result:
print(f"error: {result.cry(string=True)}", file=stderr)
exit(1)
data = result.get()
NamedTuple Over Dataclass
# ✅ PREFERRED - NamedTuple (immutable, hashable)
class Behaviour(NamedTuple):
query: str | list[str] = ""
debug: bool = False
# ❌ AVOID - dataclass (unless mutability required)
@dataclass
class Config:
debug: bool = False
CLI Argument Handling
Use sys.argv for simple scripts:
import sys
if "-h" in sys.argv or "--help" in sys.argv:
print("Usage: script.py [-h] [-d] [-v]")
sys.exit(0)
debug = "-d" in sys.argv or "--debug" in sys.argv
files = [arg for arg in sys.argv[1:] if not arg.startswith("-")]
Use argparse for complex scripts (subcommands, validation, required args):
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("files", nargs="+", help="files to process")
parser.add_argument("--format", choices=["json", "yaml"], default="json")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
Error Handling
from sys import stderr
# Error messages to stderr
print("error: surplus is not installed", file=stderr)
# Specific exit codes
def main() -> int:
# 0. parse arguments
# 1. validate inputs
# 2. load data
# 3. process
# 4. output
return 0 # success
if __name__ == "__main__":
exit(main())
Advanced Patterns
For detailed examples of advanced patterns, see:
- references/ADVANCED_PATTERNS.md — Walrus operator, match/case, generators, nested functions, progress bars, pathlib operations, and more
Testing Skills
Test implicit invocation (without naming the skill):
| Prompt | Should Trigger? | |--------|-----------------| | "Help me write a Python script" | ✅ Yes | | "I need to add type hints to this code" | ✅ Yes | | "Set up a new Python project" | ✅ Yes | | "Run the tests for this codebase" | ❌ No (use testing-specific skill) | | "Explain what Python is" | ❌ No (exploratory, not actionable) |
Constraints test:
- Does the code use UV commands instead of pip?
- Are type annotations in Python 3.10+ syntax?
- Does the file header include SPDX identifier?
- Are both basedpyright and mypy satisfied?
Integration
This skill extends dev-standards-majo. Always ensure dev-standards-majo is loaded for:
- AGENTS.md maintenance
- British English spellings
- Universal code principles
Works alongside:
mdf-majo- meadow Docstring Format (MDF) specificationmdf-md-api-docs-majo— Writing API refs or docs from code using the MDFgit-majo— For committing Python code changeswriting-docs-majo— For writing Python API documentationshell-majo— For shell scripting within Python projectstask-planning-majo— For planning complex Python projects
微信扫一扫