Python Development Guidelines
Use this skill when helping with Python code. It captures the user's preferred package choices and practical conventions. Apply it as guidance, not as a reason to add dependencies unnecessarily.
Default assumptions
- Prefer simple, explicit Python over over-engineered abstractions.
- Preserve the project's existing style when it conflicts with these preferences.
- Use type hints for public functions, data models, CLI entrypoints, and non-trivial logic.
- Prefer
uvfor dependency management and Python tooling when the environment allows it. - Prefer
pathlib.Pathfor filesystem paths in new code. - Prefer
rufffor linting/formatting andpyrightfor type checking when the project has not already chosen alternatives. - Do not introduce a preferred package if the task can be solved cleanly with existing project dependencies.
Preferred package choices
Logging: loguru
Use loguru for application logging when the project does not already standardize on logging.
Guidelines:
- Import with
from loguru import logger. - Use structured, contextual messages rather than
print(). - Prefer exception-aware logging:
try: ... except Exception: logger.exception("Failed to process item") raise - For libraries, avoid surprising global logger configuration unless the project already does it.
Terminal output: rich
Prefer rich for human-friendly terminal output, tables, progress bars, tracebacks, panels, and colored CLI messages.
Guidelines:
- Use
rich.print,Console,Table,Progress, orPanelwhen output readability matters. - Keep
logurufor logs andrichfor user-facing terminal presentation. - Avoid excessive colors or decorative output in scripts that are likely to be piped or parsed.
- For Typer apps, use Rich for formatted output when it improves clarity.
Configuration and environment variables: pydantic-settings
Prefer pydantic-settings for structured application configuration: reading environment variables, loading local .env files, parsing types, applying defaults, and validating required settings.
Use python-dotenv only for lightweight scripts or projects that simply need to load local .env values into os.environ without a structured settings model.
Guidelines:
- Import with
from pydantic_settings import BaseSettings, SettingsConfigDictfor Pydantic v2 projects. - Define a dedicated
Settingsmodel near application startup or configuration boundaries, not deep inside business logic. - Use
SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")when local.envloading is desired. - Use
env_prefixandenv_nested_delimiterwhen settings need namespacing or nested models. - Use
SecretStr/SecretBytesfor sensitive settings that may appear in logs or repr output. - Read, validate, and document configuration explicitly; do not rely on scattered
os.getenv()calls for application settings. - Do not commit real
.envfiles or secrets. - Provide
.env.examplewhen adding new required environment variables. - Keep
python-dotenvas the lighter fallback for small scripts, test setup, or projects that do not otherwise use Pydantic.
Notifications: notifiers
Use notifiers for explicit user-facing notifications such as completion alerts, failure alerts, or operational signals.
Guidelines:
- Keep notification configuration outside source code when it contains credentials or endpoints.
- Do not hardcode tokens, webhook URLs, or secrets.
- Treat notification failures as non-critical unless the task is specifically about alerting reliability.
Dates and times: arrow
Prefer arrow for user-facing datetime parsing, formatting, timezone handling, and relative date logic.
Guidelines:
- Be explicit about timezone assumptions.
- Avoid naive datetimes for data interchange or persisted records.
- For financial or China-local workflows, prefer
Asia/Shanghaiwhen the project context indicates it. - Convert to standard
datetimeonly when required by APIs, databases, or libraries.
HTTP: httpx
Prefer httpx for HTTP clients.
Guidelines:
- Use
httpx.Clientorhttpx.AsyncClientfor repeated requests. - Set timeouts explicitly.
- Use
response.raise_for_status()unless handling status codes intentionally. - Keep retry/backoff logic explicit or use the project's existing retry helper.
- Do not disable TLS verification except for a clearly documented local/dev-only reason.
Example:
import httpx
with httpx.Client(timeout=10.0) as client:
response = client.get(url)
response.raise_for_status()
data = response.json()
Dataframes / tabular data: polars
Prefer polars for dataframe-style transformations, CSV/Parquet IO, and larger tabular workloads.
Guidelines:
- Use lazy execution (
scan_*,.lazy()) for large datasets or pipelines. - Use eager dataframes for small, simple transformations.
- Prefer expression APIs over Python loops.
- Be explicit about schemas and null handling when data quality matters.
- Avoid converting to pandas unless an external API requires it.
CLI apps: typer
Prefer typer for command-line interfaces.
Guidelines:
- Use typed function parameters as the CLI contract.
- Keep command functions thin; delegate business logic to testable functions.
- Use
typer.Optionandtyper.Argumentfor clarity when defaults or help text matter. - Return clear exit behavior; raise
typer.Exit(code=...)for intentional non-zero exits.
Validation and models: pydantic
Prefer pydantic for structured inputs, API payloads, parsed records, and validation boundaries. Prefer pydantic-settings for application configuration.
Guidelines:
- Use models at system boundaries: config, API input/output, scraped records, messages, and persisted data.
- Keep internal transformations simple; not every dict needs a model.
- Prefer field validators for normalization and validation that belongs to the model.
- Avoid swallowing validation errors; report them with enough context.
- Follow the project's installed Pydantic major version conventions (
BaseModel,field_validatorfor v2;validatorfor v1 if required by the project).
Filesystem paths: pathlib
Prefer pathlib.Path for new filesystem code.
Guidelines:
- Accept
str | Pathat public boundaries when useful, then normalize withPath(...)internally. - Use
Path.read_text(),Path.write_text(),Path.open(),Path.iterdir(), and/path joining instead ofos.pathfor straightforward path work. - Keep
os,shutil, or lower-level APIs when they are clearer or required for permissions, environment variables, process management, or platform-specific behavior.
Testing: pytest
Prefer pytest for tests.
Guidelines:
- Add or update tests when fixing bugs or changing behavior.
- Use small, focused tests with clear arrange/act/assert structure.
- Use fixtures for reusable setup, not for hidden control flow.
- Use
pytest.mark.parametrizefor input/output matrices. - Mock network, filesystem, time, and external services unless the test is explicitly an integration test.
- Test CLI behavior with the project's existing runner style; for Typer projects, use
typer.testing.CliRunnerwhen appropriate.
Linting, formatting, and type checking: ruff + pyright
Prefer ruff for linting and formatting, and pyright for static type checking, when the project has not standardized on another toolchain.
Guidelines:
- Run existing project commands first; otherwise prefer
uv run ruff check .,uv run ruff format ., anduv run pyright. - Use
ruff check --fixonly for targeted, safe fixes; inspect non-trivial rewrites. - Do not reformat unrelated files during small bug fixes.
- Treat type checker suppressions as last resorts; prefer narrowing types, small helper functions, or clearer data models.
Security-sensitive Python code
Be extra cautious around subprocesses, file paths, deserialization, secrets, and network calls.
Guidelines:
- Prefer
subprocess.run([...], shell=False, check=True, timeout=...)over shell strings. - Never pass untrusted input to
shell=True; avoidshell=Trueunless there is a documented reason. - Avoid unsafe deserialization such as
pickleor unsafe YAML loading for untrusted data. - Redact secrets from logs, exceptions, CLI output, test snapshots, and
.env.exampleplaceholders. - Validate user-controlled paths before reading, writing, deleting, or extracting archives.
Implementation workflow
- Inspect project conventions first: dependencies,
pyproject.toml, existing imports, test layout, and style. - Prefer the project's existing toolchain over adding new dependencies.
- If a preferred package is absent but clearly useful, mention the dependency addition before using it.
- Make surgical changes and preserve surrounding style.
- Verify with targeted tests or type/lint commands where practical.
Dependency management
When adding packages in a uv project, prefer telling the user or using the project-approved command, typically:
uv add package-name
For dev-only tools:
uv add --dev package-name
Use uv add --group <name> package-name for named dependency groups such as lint, test, or docs when the project separates development tooling. Keep runtime dependencies in [project.dependencies], optional public features in [project.optional-dependencies], and internal development tools in [dependency-groups].
Do not manually edit lockfiles unless that is already the project convention.
Review checklist
Before finishing Python work, check:
- No
print()left in application code unless intentionally part of CLI output. - HTTP calls have explicit timeouts.
- Secrets are not hardcoded in code, tests, or examples.
- Configuration uses
pydantic-settingswhen structured validation is useful; lightweight.envloading remains local/dev-oriented and documented. - Sensitive settings use secret-aware types or explicit redaction where practical.
- Terminal output uses
richonly where it helps human readability. - Datetime timezone behavior is explicit where relevant.
- Pydantic models match the installed version style.
- Filesystem code uses
pathlibwhere it improves clarity. - Subprocess and deserialization code avoids unsafe defaults.
- Ruff/pyright or the project's equivalent checks are run when practical.
- Tests cover changed behavior or the absence of tests is explained.
- New dependencies are justified and documented.