Install
openclaw skills install @ivangdavila/pyWrites, debugs, and reviews Python code — runtime traps, packaging, typing, async, tests, performance. Use when Python raises or misbehaves: ModuleNotFoundError, circular imports, AttributeError on None, UnboundLocalError, UnicodeDecodeError, mutable default arguments, is vs ==, float rounding, naive vs aware datetimes, or a wrong answer with no exception; when pip, uv, poetry, venv, pyproject or a lockfile fight over dependencies, or a package installs but will not import; when threads, asyncio, multiprocessing or the GIL hang, deadlock, or leak memory; when pytest passes but should not, mocks patch the wrong module, or async tests never run; when mypy or pyright errors need clearing; when a script is slow, eats RAM, or gets OOM-killed; when subprocess calls hang or logs never appear; or when upgrading Python breaks the build. Not for library-specific problems — pandas, numpy, django, fastapi and flask have their own skills.
openclaw skills install @ivangdavila/pyUser preferences live in ~/Clawic/data/py/config.yaml (see Configuration); nothing else is stored on the user's machine. If you have data at an old location (~/py/ or ~/clawic/py/), move it to ~/Clawic/data/py/.
pandas, numpy, django, fastapi, flask have their own skills| Situation | Play |
|---|---|
| Wrong value, no exception | repr() at input, midpoint, output; the first wrong one is the bug — suspect aliasing, coercion, stale default → debugging.md |
| A traceback you have not seen before | Message → cause table, then the matching chain → debugging.md |
Installed it, still ModuleNotFoundError | Wrong interpreter: check sys.executable, then use python -m pip → packaging.md |
| venvs, lockfiles, uv vs pip vs poetry, publishing | Applications pin everything with hashes; libraries declare ranges → packaging.md |
cannot import name X from partially initialized module | Circular import; switch to import a + a.f() before restructuring → imports.md |
is vs ==, float rounding, money, NaN, string surgery | is only for None/True/False/sentinels; Decimal from strings → types.md |
| Aliasing, copies, ordering, membership cost | [[]]*3 shares one list; x in list is O(n) → collections.md |
| State leaks across calls: defaults, closures, decorators, generators | Defaults evaluate once, at def time → functions.md |
Class design: shared attributes, hashability, MRO, __slots__ | __eq__ without __hash__ makes instances unhashable → classes.md |
| dict vs dataclass vs NamedTuple vs pydantic; validating input | Validate once at the boundary, plain objects inside → data-modeling.md |
| Slow, hanging, or racy: GIL, threads, asyncio, multiprocessing | Pure-Python CPU → processes; I/O → threads or asyncio → concurrency.md |
| Too slow, or too much memory | Profile first, then fix the top line or nothing → performance.md |
| Tests pass but should not; mock patches nothing; async tests skipped | Patch where the name is USED; autospec=True → testing.md |
| mypy or pyright errors, annotating a legacy codebase | Freeze a baseline, then ratchet one package at a time → type-checking.md |
| Formatting, import order, lint rules, pre-commit, style review comments | One formatter owns layout; ruff check for bugs; same versions in hook and CI → linting.md |
UnicodeDecodeError, CSV and JSON traps, atomic writes, temp files | Declare encoding="utf-8"; write temp then os.replace → files.md |
| Timezones, DST, parsing dates, measuring elapsed time | Aware datetimes everywhere; time.monotonic() for durations → datetime.md |
| Calling git/ffmpeg/anything external, hanging or failing silently | run([...], check=True, timeout=…), never shell=True with interpolation → subprocess.md |
| Writing a script or CLI: arguments, exit codes, pipes, signals | sys.exit(main()); stdout for data, stderr for logs → cli.md |
| Nothing appears in the logs, or deciding what to log | Four gates: basicConfig no-op, logger level, handler level, propagation → logging.md |
| Exception design, chaining, retries, timeouts | raise X from exc; exponential backoff with jitter → errors.md |
| Calling an HTTP API: sessions, status codes, redirects, streaming a big body | One client per process; raise_for_status() before .json() → http.md |
A local database, cache, or queue in a file; database is locked | WAL plus an explicit busy timeout; one connection per thread → sqlite.md |
| Untrusted input: pickle, eval, SQL, paths, archives, secrets | Unpickling executes code; parameterize SQL; contain paths → security.md |
| "Is there something in the stdlib for this?" | Counter, deque, bisect, itertools, pathlib, secrets, sqlite3 → stdlib.md |
| Notebook works for you and nobody else; results change between runs | Restart and run all; %pip not !pip; strip outputs before committing → notebooks.md |
| Upgrading Python, choosing a version, a module that vanished | Run the suite with -W error::DeprecationWarning on the OLD version first → versions.md |
| Anything else | Core Rules below, then reproduce with python -I -c '<the five suspect lines>' and re-add one thing at a time |
Each file above is one sub-job and is self-contained: read SKILL.md by default, open exactly one guide when the situation matches.
def f(xs=None) then if xs is None: xs = []. Never xs = xs or [] — a caller passing an empty list to be filled gets a fresh list instead; their reference stays empty.is only for None, True, False, and sentinel objects; == for everything else. Interning makes is on ints and strings pass in tests and fail in production (types.md).RuntimeError, lists silently skip elements. Iterate a copy (for x in list(xs)) or collect changes and apply after.multiprocessing; I/O → threads or asyncio; threads never speed up pure-Python CPU work, because the GIL serializes bytecode (concurrency.md).except Exception:, never bare except: — bare also catches KeyboardInterrupt and SystemExit, making the process unkillable. Re-raise with bare raise, keeping the original traceback (errors.md).with. CPython's refcounting closes leaked handles by accident; exceptions and other interpreters (PyPy) expose the leak.decimal.Decimal('1.10') from strings — Decimal(1.1) imports the float error it was meant to avoid. Float comparisons via math.isclose (types.md).encoding='utf-8' at every I/O boundary — the default follows the platform locale until UTF-8 becomes the default (PEP 686, python >=3.15), so the code breaks first on Windows and in lean containers (files.md).if __name__ == "__main__": — top-level code runs on every import and again in every multiprocessing spawn child (imports.md).The syntax and stdlib floors that shape everyday code. Individual guides carry additional floors — mostly changed defaults and new keyword arguments — inline in this same python >=X.Y form, next to the instruction each one gates. Support windows, removals, and the upgrade procedure: versions.md.
| Feature | Needs |
|---|---|
Guaranteed dict insertion order, dataclasses, breakpoint(), from __future__ import annotations, sys.stdout.reconfigure | python >=3.7 |
Walrus :=, positional-only /, functools.cached_property, TypedDict/Protocol/Literal/Final, basicConfig(force=), self-documenting f"{value=}" | python >=3.8 |
Builtin generics list[int], d1 | d2 (dict merge), zoneinfo, removeprefix/removesuffix, functools.cache, asyncio.to_thread, importlib.resources.files, Path.is_relative_to, graphlib, argparse.BooleanOptionalAction, executor.shutdown(cancel_futures=) | python >=3.9 |
X | Y unions, match, zip(strict=True), dataclass slots=/kw_only=, itertools.pairwise, ParamSpec, EncodingWarning and -X warn_default_encoding | python >=3.10 |
ExceptionGroup/except*, asyncio.TaskGroup, tomllib, Self, StrEnum, exc.add_note(), contextlib.chdir, full-ISO fromisoformat, NotRequired | python >=3.11 |
PEP 695 generics (def f[T]()), @override, itertools.batched, tarfile(filter="data") | python >=3.12 |
Experimental free-threaded build; PEP 594 module removals (cgi, telnetlib, …) | python >=3.13 |
forkserver as the Linux multiprocessing default, lazy annotations (PEP 649) | python >=3.14 |
| UTF-8 as the default text encoding everywhere (PEP 686), retiring the locale default | python >=3.15 |
Before delivering Python code, check:
xs or [] where an empty argument is meaningfulopen, subprocess, and decode boundary declares encoding="utf-8"; every file, lock, socket, and connection is acquired in a withfrom, nothing swallowed without a log line or a written reason%s arguments and no secretsmin_python; annotations match type_strictnesseval, pickle, a shell string, an SQL string, or a path join without containmentchange_workflow decides when that run happens, never whether it happenedUser-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/py/config.yaml. Never interview the user — record a preference the moment it is stated.
| Variable | Type | Default | Effect |
|---|---|---|---|
| min_python | 3.9 | 3.10 | 3.11 | 3.12 | 3.13 | 3.14 | 3.11 | Gates which Version Floors features may be emitted unguarded, and which fallback appears instead |
| package_manager | pip | uv | poetry | pdm | conda | pip | Chooses the install, lock, and venv commands in every example (packaging.md) |
| type_strictness | none | gradual | strict | gradual | How far annotations go: none skips hints, gradual annotates public boundaries, strict turns on disallow_untyped_defs (type-checking.md) |
| test_runner | pytest | unittest | pytest | Shape of emitted tests, fixtures, and mocking guidance (testing.md) |
| target_os | linux | macos | windows | cross | cross | Path, encoding, and multiprocessing start-method assumptions; cross flags all three |
| src_layout | src | flat | src | Project scaffolding, editable-install and import advice (packaging.md) |
| line_length | number (79-120) | 88 | Formatting of emitted code and the ruff/black config value (linting.md) |
| change_workflow | test-first | fix-first | test-first | Order of the work: test-first writes the failing test before the fix, fix-first patches first and backfills the test against the reverted change (testing.md) |
Preference areas — customizable dimensions; a stated preference gets recorded in config.yaml and applied:
linting.md)packaging.md and versions.md guidancepickle, eval, shell=True, missing timeouts and unpinned dependencies — affects security.md and cli.md--dry-run pass on destructive scripts, a profile before any optimization — affects the sequence of every task, never the correctness rules| Trap | Why it fails | Do instead |
|---|---|---|
Bare pip install | Installs into whatever interpreter is first on PATH — the "installed it, still ModuleNotFoundError" loop | python -m pip install inside an activated venv (packaging.md) |
except Exception: pass | Turns a crash into a wrong answer three layers away | Log with logger.exception, or write down why this failure is expected (errors.md) |
assert user.is_admin as a check | python -O removes every assert, including that one | Raise explicitly (security.md) |
time.time() to measure a duration | NTP can step the clock backwards mid-measurement and produce negative elapsed time | time.monotonic() (datetime.md) |
datetime.utcnow() | Returns a NAIVE datetime holding UTC: compares wrong against aware values, deprecated in python >=3.12 | datetime.now(timezone.utc) (datetime.md) |
| f-strings inside logging calls | Formats even when the level is disabled, and every line becomes a unique string to the aggregator | log.info("user %s", uid) (logging.md) |
sys.path.append(...) to fix an import | Works from one entry point, breaks under pytest, packaging, and any other cwd | Editable install with a src/ layout (imports.md) |
shell=True with an interpolated value | Command injection — and /bin/sh is not bash | Argument list; shlex.quote if a shell is unavoidable (subprocess.md) |
verify=False to silence an SSL error | Disables peer authentication for every request in the process | Install the CA bundle (security.md) |
pickle across a process, user, or network boundary | Unpickling executes attacker-controlled code before you can inspect it | JSON/msgpack, or an HMAC-signed payload (security.md) |
raise e when re-raising | Appends the current line and hides where the exception really came from | Bare raise (errors.md) |
| Type hints treated as validation | Never enforced at runtime: def f(x: int) happily takes a string | Checker in CI plus runtime validation at the boundary (type-checking.md) |
x in list or list.pop(0) inside a loop | O(n) per operation makes the loop O(n²) | set for membership, deque for both ends (collections.md) |
ThreadPoolExecutor matches it with far less ceremony. Boundary: high connection counts AND an async-native stack end to end → asyncio; one blocking driver anywhere in the chain forfeits the benefit (concurrency.md).Any as a defect; the pragmatic school types boundaries and lets internals be inferred. Boundary: libraries and cross-team APIs → annotate fully; single-owner scripts and glue → boundaries only. Either way, one checker in CI (type-checking.md).venv + pip exists on every machine and surprises nobody; uv/poetry/hatch are faster and manage more. Boundary: a project with a real dependency problem, many contributors, or several interpreters justifies the tool; one that installs three libraries does not (packaging.md).try/except over pre-checking, and it is genuinely more correct under concurrency — the file can disappear between exists() and open(). Boundary: LBYL when the operation is expensive or irreversible, EAFP when the failure is cheap and the race is real.More Clawic skills, get them at https://clawic.com/skills/py (install if the user confirms):
pandas — DataFrame-specific traps and idiomsfastapi — async web APIs in Pythondjango — Django ORM and framework patternsprofiling — when the question is "why is it slow", measure firstdebugging — language-agnostic fault isolation (bisection, hypothesis discipline, minimal repro); the Python-specific version of that job is this skill's debugging.mdPart of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/py.