Skip to main content

loom-python

Python language expertise for idiomatic, production-quality code.

Informations de source

Dépôt
cosmix/loom
Dernière activité de la source
24 septembre 2026 à 17:08
Langue détectée de SKILL.md
anglais
Étoiles
61
Forks
4

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
loom-python
description
Python language expertise for idiomatic, production-quality code.
triggers
["python","py","pip","uv","poetry","virtualenv","pytest","pydantic","fastapi","django","flask","pandas","numpy","dataclass","type hints","asyncio","mypy","ruff","black","sklearn","pytorch","tensorflow","jupyter","pipenv","conda"]
# Python Language Expertise ## Overview Idiomatic, production-grade Python: modern typing, async, packaging, and the web/data/ML ecosystem. Assumes competence — the value is in what is easy to get wrong (interning, mutable defaults, blocking the event loop, GIL, dataclass/Pydantic boundaries), not syntax tutorials. ## Tooling (uv / ruff / mypy) Ruff is a strong default for linting and formatting. `uv` is a capable project and environment manager, but preserve the repository's existing package manager and lockfile unless a migration is explicitly requested. **`uv`** — a single tool for project environments, locking, Python installation, and pip-compatible workflows. Use it for new work or repositories that already use it; do not treat it as an implicit replacement for Poetry or another established workflow. | Task | Command | | --- | --- | | New project | `uv init` (writes `pyproject.toml`) | | Add / remove dep | `uv add httpx` / `uv remove httpx` (updates `pyproject.toml` + `uv.lock`) | | Add dev dep | `uv add --dev pytest` | | Sync env from lock | `uv sync` (creates `.venv`, exact/reproducible) | | Run in project env | `uv run pytest` (auto-syncs first; no manual activate) | | Install a Python | `uv python install 3.12` | | Ephemeral tool | `uvx ruff check` (= `uv tool run`) | | pip shim | `uv pip install ...` (drop-in, no project file) | - In a uv-managed project, use `uv add`/`uv remove` so the manifest and `uv.lock` stay in sync; use `uv sync --frozen` in CI to fail on drift. **`ruff`** — single linter+formatter replacing flake8, isort, pyupgrade, pydocstyle, autoflake, and much of bandit. - `ruff check --fix` (lint+autofix), `ruff format` (near-identical to black, replaces it). - Config lives under `[tool.ruff.lint]` since 0.1 — top-level `select` is deprecated and warns. - Useful rule groups: `E`/`W` pycodestyle, `F` pyflakes, `I` isort, `UP` pyupgrade (keeps typing current), `B` bugbear (catches mutable defaults, `lru_cache` on methods), `SIM`, `S` bandit-security, `ASYNC`, `PL` pylint, `RUF`. Suppress with `# noqa: E501` (never bare `# noqa`). **`mypy --strict`** for type checking; `pyright`/`basedpyright` (what Pylance runs) is faster and stricter on inference. Always scope ignores: `# type: ignore[arg-type]`, never bare `# type: ignore`. ## Type Hints - 3.9+: built-in generics `list[T]`, `dict[K, V]`. 3.10+: `X | Y`, `X | None` (not `Union`/`Optional`). - Import `Callable`/`Sequence`/`Mapping`/`Iterator` from `collections.abc`, not `typing`. - **Accept abstract, return concrete:** take `Iterable`/`Sequence`/`Mapping` (max caller flexibility); return `list`/`dict` (callers know exactly what they get). - `Self` (3.11+) for fluent builders / alternate constructors. `TypedDict` for structured dicts (JSON payloads, `**kwargs`), `Protocol` for structural typing, `ParamSpec` for signature-preserving decorators — see Expert Practices. ```python from collections.abc import Iterable from typing import TypedDict, NotRequired, Self class MovieRow(TypedDict): # dict shape checkable by mypy title: str year: NotRequired[int] # 3.11+: optional key def deduplicate(items: Iterable[str]) -> list[str]: # abstract in, concrete out return list(dict.fromkeys(items)) class Builder: def with_name(self, name: str) -> Self: self._name = name return self ``` ## Async / Await Prefer `asyncio.TaskGroup` (3.11+) over `gather()`: on failure it cancels remaining sibling tasks, awaits them, and re-raises all errors as an `ExceptionGroup` (handled with `except*`). `gather()` does NOT cancel siblings on failure (they "won't be cancelled and will continue to run"), and `return_exceptions=True` silently mixes return values and exception objects into one list. Reserve `gather()` for genuinely independent fire-and-forget results. ⚠ **Never block the event loop.** A coroutine that calls blocking IO (`requests`, sync DB drivers, `open().read()`), `time.sleep()`, or CPU-heavy work freezes *every* task on that loop. Offload: `await asyncio.to_thread(fn, *args)` (3.9+) for blocking IO; `loop.run_in_executor(process_pool, fn)` for CPU work; `await asyncio.sleep()` not `time.sleep()`. See Async Gotchas for the full set. ```python import asyncio async def process_batch(urls: list[str]) -> list[dict]: async with asyncio.TaskGroup() as tg: tasks = [tg.create_task(fetch_data(url)) for url in urls] return [t.result() for t in tasks] # ExceptionGroup raised on any failure try: await process_batch(urls) except* ConnectionError as eg: # subgroup handling for exc in eg.exceptions: logger.error("fetch failed: %s", exc) rows = await asyncio.to_thread(cursor.execute, sql) # sync driver off the loop ``` ## Dataclasses vs Pydantic (the validation boundary) **Choose by trust, not habit.** | | `@dataclass` (stdlib) / `attrs` | Pydantic v2 `BaseModel` | | --- | --- | --- | | Validation | **None** — type hints are not enforced at runtime | Validates + coerces on construction | | Use for | Internal, already-trusted structs; hot paths | Untrusted edges: API bodies, config, external JSON | | Cost | Free | Per-instance validation (Rust core, but non-zero) | Don't trust a dataclass with external input (it will happily hold `age="banana"`); don't pay Pydantic's validation cost for internal structs constructed in a tight loop. For perf-critical validated decode, `msgspec` is the fastest option. ```python from dataclasses import dataclass, field from pydantic import BaseModel, Field, field_validator @dataclass(slots=True) # internal; slots=True saves memory + blocks typos class Config: host: str port: int = 8080 tags: list[str] = field(default_factory=list) # NEVER tags: list = [] class UserCreate(BaseModel): # untrusted edge email: str = Field(min_length=5) name: str = Field(max_length=100) @field_validator("email") @classmethod def norm_email(cls, v: str) -> str: if "@" not in v: raise ValueError("invalid email") # NEVER assert (-O strips it) return v.lower() ``` ⚠ Plain `@dataclass` sets `__hash__ = None` (unhashable — cannot be a set member / dict key). Use `frozen=True` for a hashable value object. `slots=True`/`kw_only=True` are 3.10+. ## Packaging (`pyproject.toml`) ```toml [project] name = "mypackage" version = "0.1.0" requires-python = ">=3.11" dependencies = ["httpx>=0.25", "pydantic>=2.0"] [dependency-groups] # PEP 735; `uv add --dev` targets this dev = ["pytest>=8", "pytest-asyncio>=0.23", "mypy>=1.9", "ruff>=0.4"] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.ruff.lint] # MUST be nested since ruff 0.1; top-level is deprecated select = ["E4", "E7", "E9", "F", "I", "UP", "B", "SIM"] [tool.mypy] strict = true [tool.pytest.ini_options] asyncio_mode = "auto" # else async tests are silently UNCOLLECTED (default "strict") ``` ## Web Frameworks ### FastAPI Type hints drive validation, serialization, and OpenAPI. Use `Annotated[..., Depends(...)]` for injection; `response_model` to shape/filter output. ```python from typing import Annotated from fastapi import FastAPI, Depends, HTTPException, status app = FastAPI() async def get_db() -> AsyncIterator[AsyncSession]: # yield-dependency: cleanup after response async with AsyncSession(engine) as s: yield s @app.post("/users/", response_model=User, status_code=status.HTTP_201_CREATED) async def create_user(user: UserCreate, db: Annotated[AsyncSession, Depends(get_db)]) -> User: return await UserService(db).create(user) ``` ⚠ Gotchas: - **`def` vs `async def` path ops:** a `def` handler runs in FastAPI's threadpool (blocking libs OK). An `async def` handler runs on the event loop — any blocking call inside stalls *all* requests. Pick `def` for sync ORMs, `async def` only with async libs. - `response_model` strips fields not on the model — the primary guard against leaking hashes/tokens; don't return the raw ORM object. - `Depends` results are cached per-request; use `BackgroundTasks` for post-response work. ### Django ```python from django.db import models, transaction class User(models.Model): email = models.EmailField(unique=True) class Meta: indexes = [models.Index(fields=["email"])] with transaction.atomic(): # rolls back on exception user = User.objects.create(email=e) Profile.objects.create(user=user) ``` ⚠ QuerySets are lazy (evaluated on iteration/`len`/slice). Kill N+1 with `.select_related()` (FK/1:1 join) and `.prefetch_related()` (M2M/reverse). Batch with `bulk_create`/`bulk_update`. `.only()`/`.defer()` to trim columns. ## Data & ML (dense reference) The APIs are well-known; these are the correctness/perf traps that bite in review. **pandas** - **Vectorize.** `df["c"] = df["a"] * df["b"]`, `.map`/`np.where`/masks — never `iterrows`/`itertuples`/row-wise `apply` (Python-level loop, 10–100× slower). - **Assign via `.loc`**, never chained (`df[m]["c"] = ...`) — raises `SettingWithCopyWarning` and may no-op. pandas 3.0 makes Copy-on-Write the default, ending the ambiguity. - Memory: `read_csv(usecols=, dtype=)`; nullable `Int64`/`boolean` for NA-bearing ints; `category` dtype for low-cardinality strings; chunk with `read_csv(chunksize=)` or move to `polars`/`dask`/`pyarrow` at scale. - `merge` silently fans out on non-unique keys (row explosion) — validate with `merge(..., validate="one_to_many")`. **numpy** - Slices are **views**, not copies — `b = a[1:3]; b[0] = 9` mutates `a`. `.copy()` to break aliasing. - Broadcasting + ufuncs over Python loops; watch silent `int` overflow on fixed-width dtypes. - Use `rng = np.random.default_rng(seed)` (Generator API), not legacy global `np.random.seed`/`np.random.rand`. **scikit-learn** - Wrap preprocessing + estimator in a `Pipeline` so scalers/encoders fit on the **train fold only** inside CV — fitting a scaler on the full dataset before splitting leaks test statistics and inflates scores. `ColumnTransformer` for heterogeneous columns. Set `random_state` for reproducibility. **PyTorch** - Training step order: `optimizer.zero_grad()` → forward → `loss.backward()` → `optimizer.step()`. Forgetting `zero_grad` accumulates gradients. - Eval: `model.eval()` (disables dropout, freezes BatchNorm running stats) **and** `torch.inference_mode()` / `torch.no_grad()` (no autograd graph). Both are needed; they do different things. - Accumulate metrics as `loss.item()`/`.detach()` — keeping tensors alive retains the whole graph → memory blowup. `DataLoader(num_workers>0, pin_memory=True)` for GPU throughput; move batches with `.to(device, non_blocking=True)`. ## Testing with pytest Async fixtures must use `@pytest_asyncio.fixture` (a plain `@pytest.fixture` on `async def` yields an un-awaited coroutine under strict mode). Session/module-scoped async fixtures need `loop_scope=`; the `event_loop` fixture was removed in pytest-asyncio 1.0. Built-in fixtures worth reaching for: - `tmp_path` (per-test `pathlib.Path` dir), `monkeypatch` (`setattr`/`setenv`/`chdir`/`syspath_prepend`, all auto-undone at teardown — never patch globals by hand), `capsys`/`caplog`, `pytest.raises(ValueError, match="regex")`. - `conftest.py` fixtures are auto-discovered by every test in that directory tree — no import. Fixture `scope=` is `function` (default) / `class` / `module` / `session`; `autouse=True` applies without being requested. - Run tips: `-x` stop on first fail, `-k EXPR` select, `--lf` last-failed, `-q`, parametrize `ids=` for readable names. ```python import pytest, pytest_asyncio @pytest_asyncio.fixture async def db_session() -> AsyncIterator[AsyncSession]: async with AsyncSession(engine) as s: yield s @pytest.mark.parametrize("email,valid", [("a@b.com", True), ("bad", False)]) def test_email(email: str, valid: bool) -> None: if valid: User(email=email) else: with pytest.raises(ValueError): User(email=email) def test_env(monkeypatch): monkeypatch.setenv("API_KEY", "test") # auto-restored after the test assert load_config().key == "test" ``` ## Loom Test Runner Adapter
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub