Modern Python 3.12+ development with strict type hints, ruff linting, uv package manager, async/await patterns, dataclasses vs Pydantic v2, pytest conventions, virtual environments, src layout project structure, and pyproject.toml configuration. Use when writing, reviewing, or scaffolding Python code.
You are a Python expert specializing in modern Python 3.14+ with strict typing, fast tooling (ruff, uv), and production-grade patterns.
myproject/
pyproject.toml
src/
myproject/
__init__.py
main.py
models.py
services/
__init__.py
user.py
tests/
conftest.py
test_models.py
test_services/
test_user.py
The src/ layout prevents accidental imports of the local package without installing it. Always use it for libraries. Flat layout is acceptable for single-file scripts and small apps only.
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.14"
dependencies = [
"httpx>=0.27",
"pydantic>=2.6",
]
[project.optional-dependencies]
dev = ["ruff", "pytest", "pytest-asyncio", "mypy", "pre-commit"]
[project.scripts]
myproject = "myproject.main:cli"
[tool.ruff]
target-version = "py314"
line-length = 100
src = ["src"]
[tool.ruff.lint]
select = [
"E", "F", "W", # pyflakes + pycodestyle
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"B", # flake8-bugbear
"A", # flake8-builtins
"SIM", # flake8-simplify
"RUF", # ruff-specific
"ANN", # flake8-annotations (type hint enforcement)
"PT", # flake8-pytest-style
]
ignore = ["ANN101"] # don't require type annotation for `self`
[tool.ruff.lint.isort]
known-first-party = ["myproject"]
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
[tool.mypy]
strict = true
python_version = "3.14"
[tool.pyright]
pythonVersion = "3.14"
typeCheckingMode = "strict"
reportMissingTypeStubs = false
# Install uv (replaces pip, pip-tools, virtualenv, pipx)
# Install via package manager (recommended)
# macOS: brew install uv
# Windows: winget install astral-sh.uv
# Linux: snap install astral-uv --classic
# Create project with virtual environment
uv init myproject && cd myproject
uv venv # creates .venv
uv add httpx pydantic # adds to pyproject.toml + installs
uv add --dev ruff pytest # dev dependencies
uv sync # install all deps from lockfile
uv run pytest # run inside venv without activation
uv run ruff check src/ # lint
uv run mypy src/ # type check with mypy
uv run pyright src/ # type check with pyright (faster, VSCode default)
Why uv over pip: 10-100x faster, built-in lockfile (uv.lock), replaces 5 tools in one binary, written in Rust.
Type checker comparison:
from collections.abc import Sequence, Mapping
from typing import TypeAlias, TypeVar, Self
# Use builtin generics (3.12+), not typing.List/Dict
def process(items: list[str]) -> dict[str, int]: ...
# Use collections.abc for parameter types (accept more input types)
def find(items: Sequence[str], key: str) -> int | None: ...
# TypeAlias for complex types
UserId: TypeAlias = int
Headers: TypeAlias = Mapping[str, str]
# TypeVar with bounds
T = TypeVar("T", bound="Base")
def clone(obj: T) -> T:
return obj.model_copy()
# 3.12 type statement (new syntax)
type Point = tuple[float, float]
type Handler[T] = Callable[[T], Awaitable[None]]
Rules:
Any unless interfacing with untyped third-party code. Even then, cast immediately.X | None not Optional[X] (3.10+ syntax).collections.abc types for parameters (Sequence, Mapping, Iterable), concrete types for return values (list, dict).# Dataclass: for internal data containers, no validation needed
from dataclasses import dataclass, field
@dataclass(frozen=True, slots=True)
class Point:
x: float
y: float
label: str = ""
tags: list[str] = field(default_factory=list)
# Pydantic v2: for external data (APIs, config, user input) — validates on creation
from pydantic import BaseModel, Field, field_validator
class UserCreate(BaseModel):
model_config = {"strict": True}
name: str = Field(min_length=1, max_length=100)
email: str
age: int = Field(ge=0, le=150)
@field_validator("email")
@classmethod
def validate_email(cls, v: str) -> str:
if "@" not in v:
raise ValueError("invalid email")
return v.lower()
Decision rule: Pydantic for boundaries (API input, config files, external data). Dataclasses for everything internal. Never use plain dicts for structured data.
import asyncio
import httpx
# Concurrent HTTP requests
async def fetch_all(urls: list[str]) -> list[str]:
async with httpx.AsyncClient(timeout=10.0) as client:
tasks = [client.get(url) for url in urls]
responses = await asyncio.gather(*tasks, return_exceptions=True)
results: list[str] = []
for resp in responses:
if isinstance(resp, BaseException):
results.append(f"ERROR: {resp}")
else:
results.append(resp.text)
return results
# Semaphore for rate limiting
async def fetch_limited(urls: list[str], max_concurrent: int = 10) -> list[str]:
sem = asyncio.Semaphore(max_concurrent)
async with httpx.AsyncClient() as client:
async def _fetch(url: str) -> str:
async with sem:
resp = await client.get(url)
return resp.text
return await asyncio.gather(*[_fetch(u) for u in urls])
# Structured concurrency with TaskGroup (3.11+)
async def process_batch(items: list[str]) -> None:
async with asyncio.TaskGroup() as tg:
for item in items:
tg.create_task(process_one(item))
# All tasks complete or all cancelled on first exception
Async rules:
httpx not requests for async HTTP. aiohttp is also fine.open(), time.sleep(), requests.get()) in async code. Use asyncio.to_thread() for unavoidable blocking calls.TaskGroup (3.11+) over raw gather for structured concurrency and better error handling.# tests/conftest.py — shared fixtures
import pytest
from myproject.db import Database
@pytest.fixture
async def db() -> AsyncGenerator[Database, None]:
database = Database(":memory:")
await database.connect()
yield database
await database.disconnect()
@pytest.fixture
def sample_user() -> dict[str, str]:
return {"name": "Alice", "email": "alice@example.com"}
# tests/test_user.py
import pytest
from myproject.services.user import create_user, UserError
async def test_create_user_success(db: Database, sample_user: dict[str, str]) -> None:
user = await create_user(db, **sample_user)
assert user.name == "Alice"
assert user.id is not None
async def test_create_user_duplicate_email(db: Database, sample_user: dict[str, str]) -> None:
await create_user(db, **sample_user)
with pytest.raises(UserError, match="already exists"):
await create_user(db, **sample_user)
@pytest.mark.parametrize("email,valid", [
("user@example.com", True),
("invalid", False),
("", False),
("a@b.co", True),
])
def test_email_validation(email: str, valid: bool) -> None:
if valid:
assert validate_email(email) == email.lower()
else:
with pytest.raises(ValueError):
validate_email(email)
Pytest rules: Name files test_*.py. Name functions test_*. Use fixtures, not setUp/tearDown. Use parametrize for data-driven tests. Use conftest.py for shared fixtures (pytest discovers them automatically).
def f(items=[]) shares the list across calls. Use def f(items: list[str] | None = None) then items = items or [].except: or except Exception: without re-raising. Catch specific exceptions.% or .format(): Use f-strings: f"Hello {name}".import *: Never in production code. Pollutes namespace, breaks type checkers.data["user"]["address"]["city"] is untyped and crashes with KeyError.os.path for path manipulation: Use pathlib.Path everywhere.print() for logging: Use logging module or structlog for structured logging.if __name__ == "__main__": in executable modules.npx skills add medy-gribkov/python-best-practices下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer