Back to skills
extension
Category: Development & EngineeringNo API key required

python-dev-practices

Python development best practices for modern Python projects. Activated when working with Python files, pyproject.toml, or discussing Python patterns, testing, linting, type hints.

personAuthor: jakexiaohubgithub

Python development practices

Purpose

Comprehensive guide for Python development covering modern Python practices, type safety, testing strategies, and coding conventions.

When to use

This skill activates when:

  • Creating or modifying Python files
  • Working with pyproject.toml configuration
  • Writing property-based or fuzz tests
  • Handling type hints and type safety
  • Setting up development environment
  • Configuring linting, formatting, or type checking

Core principles

Minimal dependencies philosophy

CRITICAL: Standard library first, dependencies last.

  • ALWAYS prefer standard library solutions
  • Evaluate stdlib before considering third-party code
  • Implementation over dependencies

Type safety requirements

MANDATORY: Comprehensive type hints for ALL code

from typing import Protocol
from typing_extensions import TypeIs, ReadOnly, TypedDict

class Config(TypedDict):
    """Configuration with immutable fields."""
    name: ReadOnly[str]
    options: ReadOnly[list[str]]

def is_valid_config(value: object) -> TypeIs[Config]:
    """Type narrowing with TypeIs."""
    return isinstance(value, dict) and 'name' in value

Modern Python features

Leverage Python 3.10+ capabilities:

# Pattern matching (3.10+)
match argument:
    case {"type": "option", "name": str(name)}:
        return process_option(name)
    case {"type": "value", "data": list(data)}:
        return process_value(data)
    case _:
        raise ValueError(f"Unknown argument: {argument}")

# Dataclasses with slots and frozen
from dataclasses import dataclass

@dataclass(slots=True, frozen=True)
class Result:
    """Immutable result with memory efficiency."""
    success: bool
    data: dict[str, Any]
    errors: list[str]

Import organization

Follow project conventions with isort/Ruff:

# Standard library imports first
import sys
from collections.abc import Sequence
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Protocol

# Third-party (minimal)
from typing_extensions import Doc, ReadOnly, TypeIs

# Local imports
from yourpackage._module import Module
from yourpackage.validation import Validator

Class organization standards

MANDATORY class member ordering:

class Processor:
    # 1. Class State: Constants and class attributes
    DEFAULT_CONFIG = "default"
    RESERVED_NAMES = frozenset(["__init__", "__new__"])

    # 2. Lifecycle: __init__ and __new__
    def __init__(self, config: Config) -> None:
        self._config = config
        self._state = ProcessorState()

    # 3. Magic Methods: __repr__, __str__, __len__
    def __repr__(self) -> str:
        return f"Processor(config={self._config!r})"

    # 4. Public Interface: Properties, then public methods
    @property
    def name(self) -> str:
        return self._config.get("name", self.DEFAULT_CONFIG)

    def process(self, data: Sequence[str]) -> Result:
        """Process data."""
        return self._process_impl(list(data))

    # 5. Internals: Private/protected methods at bottom
    def _process_impl(self, data: list[str]) -> Result:
        pass

Testing strategy

Domain-centric test organization

tests/
├── unit/
│   ├── processing/
│   │   ├── test_data_processing.py
│   │   └── test_transformations.py
│   └── validation/
│       └── test_input_validation.py
├── properties/
│   └── test_invariants.py
└── fuzz/
    └── test_fuzzing.py

Error handling patterns

class AppError(Exception):
    """Base exception for application."""
    pass

class ValidationError(AppError):
    """Raised when validation fails."""

    def __init__(self, message: str, field: str | None = None) -> None:
        super().__init__(message)
        self.field = field

def process_data(data: Sequence[str]) -> Result:
    """Process with clear error messages."""
    if not data:
        raise ValidationError("No data provided")

    try:
        return _process_impl(data)
    except ValueError as e:
        raise ValidationError(f"Invalid data format: {e}") from e

Package management with uv

Essential commands

# CRITICAL: ALWAYS use 'uv run python' for script execution
uv run python scripts/analyze.py
uv run python -m yourpackage

# Install dependencies
uv sync

# Run tools
uv run pytest
uv run ruff check .
uv run basedpyright

Code quality tools

Configuration (pyproject.toml)

[project]
requires-python = ">=3.14"
dependencies = ["typing-extensions>=4.8"]

[tool.basedpyright]
typeCheckingMode = "strict"
pythonVersion = "3.14"

[tool.ruff]
line-length = 88
target-version = "py314"

[tool.ruff.lint]
select = ["ALL"]
ignore = ["D", "ANN101", "ANN102"]

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--strict-markers --cov=src --cov-report=term-missing"

Security practices

import shlex
from pathlib import Path

def validate_input(user_input: str) -> str:
    """Validate and sanitize user input."""
    # Prevent command injection
    if any(char in user_input for char in [';', '|', '&', '`', '$']):
        raise ValueError("Invalid characters in input")
    return shlex.quote(user_input)

def safe_path_resolution(user_path: str, base: Path) -> Path:
    """Safely resolve paths to prevent traversal."""
    path = (base / user_path).resolve()
    if not path.is_relative_to(base):
        raise ValueError("Path traversal detected")
    return path

Quick reference

Testing checklist

  • [ ] Unit tests with >95% coverage
  • [ ] Property-based tests for invariants
  • [ ] Fuzz tests for robustness
  • [ ] Benchmarks for critical paths
  • [ ] Type checking passes (basedpyright)
  • [ ] Linting passes (ruff)
  • [ ] Domain-centric test organization

Remember: Prioritize type safety, comprehensive testing, and standard library usage in all development.