Python code quality with Ruff linting/formatting and ty type checking. Use when configuring Ruff rules, setting up ty type checking, writing pyproject.toml quality config, creating pixi quality tasks, enforcing type annotations, or fixing lint errors—e.g., "set up ruff and ty", "configure Python linting", "add type checking to project", "fix ruff violations".
Install with Tessl CLI
npx tessl i github:jjjermiah/dotagents --skill python-code-quality100
Does it follow best practices?
Validation for skill structure
Enforce consistent Python code quality using Ruff (linting + formatting) and ty (type checking) from Astral. This skill defines the standard configuration, rule selection, and pixi task integration for all Python projects.
YOU MUST query Context7 before writing Ruff or ty configuration. These tools evolve rapidly. Stale knowledge produces invalid config.
Context7 slugs:
Ruff docs: /websites/astral_sh_ruff
ty docs: /websites/astral_sh_tyQuery Context7 for any non-trivial config question. No exceptions.
| Tool | Role | Config Files |
|---|---|---|
ruff check | Linting (800+ rules) | pyproject.toml [tool.ruff] or ruff.toml |
ruff format | Formatting (Black-compatible) | Same as above |
ty check | Type checking (Rust-based) | pyproject.toml [tool.ty] or ty.toml |
Both tools are installed via pixi/conda: ruff = "*" and ty = "*".
| Project Layout | Ruff Config | ty Config |
|---|---|---|
| Single package | pyproject.toml [tool.ruff] | pyproject.toml [tool.ty] |
| Workspace (monorepo) | Root ruff.toml shared | Per-package pyproject.toml [tool.ty] |
| Workspace (per-package rules) | Per-package pyproject.toml [tool.ruff] | Per-package pyproject.toml [tool.ty] |
Ruff auto-discovers the closest config file per-file. ty must be pointed at specific directories or uses the config in its working directory.
This is the baseline for new projects. Adapt to your needs.
[tool.ruff]
target-version = "py313" # Match your requires-python
line-length = 88
src = ["src"]
[tool.ruff.lint]
select = [
# === Errors & Core ===
"E", # pycodestyle errors
"F", # Pyflakes (undefined names, unused imports)
"W", # pycodestyle warnings
# === Imports ===
"I", # isort (import sorting)
# === Modernization ===
"UP", # pyupgrade (modern Python idioms)
# === Bug Detection ===
"B", # flake8-bugbear (common bugs)
# === Style & Simplification ===
"N", # pep8-naming conventions
"SIM", # flake8-simplify (reduce complexity)
"C4", # flake8-comprehensions (better comprehensions)
]
ignore = ["T201"] # Allow print() — remove for libraries
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["ALL"] # Relax all rules in tests
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"For projects requiring annotations, no print statements, and stricter hygiene:
[tool.ruff.lint]
select = [
# --- Core (always enabled) ---
"E", "F", "W", "I", "UP", "B", "N", "SIM", "C4",
# --- Annotations ---
"ANN", # flake8-annotations (require type hints)
# --- Import hygiene ---
"ICN", # flake8-import-conventions
"TID", # flake8-tidy-imports
# --- Code quality ---
"A", # flake8-builtins (no shadowing builtins)
"T10", # flake8-debugger (no breakpoints)
"T20", # flake8-print (no print statements)
"EM", # flake8-errmsg (clear error messages)
# --- Standards ---
"PTH", # flake8-use-pathlib (prefer pathlib)
"PL", # Pylint rules
][tool.ty.environment]
python-version = "3.13" # Match your requires-python
python-platform = "linux" # Or "darwin", "all" for cross-platform
[tool.ty.src]
include = ["src"] # Where your source code livesty rules have three severity levels: error, warn, ignore.
[tool.ty.rules]
# Promote to errors (non-zero exit on violation)
possibly-missing-import = "error"
possibly-missing-attribute = "error"
# Demote noisy rules to warnings
possibly-unresolved-reference = "warn"
# Disable rules that produce false positives
division-by-zero = "ignore"[[tool.ty.overrides]]
include = ["tests/**", "**/test_*.py"]
[tool.ty.overrides.rules]
possibly-unresolved-reference = "warn"[tool.ty.analysis]
allowed-unresolved-imports = ["some_optional_dep.**"]For monorepos with a single shared ruff config:
cache-dir = ".cache/ruff/.ruff-cache"
line-length = 88
include = ["packages/**/*.py", "libs/**/*.py"]
extend-exclude = ["dev/**"]
preview = true # Enable preview rules (opt-in)
[format]
quote-style = "double"
indent-style = "space"
docstring-code-format = true
docstring-code-line-length = 88
[lint]
select = ["E", "F", "W", "I", "UP", "B", "N", "SIM", "C4", "ANN"]
[lint.per-file-ignores]
"*.ipynb" = ["A", "ANN", "T20"] # Relax for notebooks
"tests/**" = ["ALL"]Define a quality feature in pixi.toml with Ruff and ty:
[feature.quality.dependencies]
ruff = "*"
ty = "*"
[feature.quality.tasks]
lint = {cmd = "ruff check src/", description = "Run ruff linter"}
lint-fix = {cmd = "ruff check --fix src/", description = "Lint with auto-fix"}
format = {cmd = "ruff format src/", description = "Format code"}
format-check = {cmd = "ruff format --check src/", description = "Check formatting"}
typecheck = {cmd = "ty check src/", description = "Run ty type checker"}
qc = {depends-on = ["lint", "format-check", "typecheck"], description = "All quality checks"}Include the feature in your environment:
[environments]
default = {features = ["quality", ...]}For monorepos, run tools on the correct directories:
[feature.quality.tasks]
# Ruff auto-discovers closest pyproject.toml per-file
lint = {cmd = "ruff check libs/", description = "Lint all packages"}
lint-fix = {cmd = "ruff check --fix libs/", description = "Lint with auto-fix"}
format = {cmd = "ruff format libs/", description = "Format all packages"}
format-check = {cmd = "ruff format --check libs/", description = "Check formatting"}
# ty must be told which packages to check
typecheck = {cmd = "ty check libs/pkg_a libs/pkg_b", description = "Type check all"}
qc = {depends-on = ["lint", "format-check", "typecheck"], description = "All QC"}ruff check --fix <path> # Apply safe fixes
ruff check --fix --unsafe-fixes <path> # Include unsafe fixes (review diff!)x = eval("1+1") # noqa: S307 — required for dynamic config loading
# ty suppression
result = dynamic_call() # type: ignore[possibly-unresolved-reference]YOU MUST add a comment explaining WHY when suppressing a rule inline.
Unexplained noqa comments are technical debt.
target-version to match requires-python in your projectsrc = ["src"] in Ruff config so import sorting works correctlyruff format BEFORE ruff check (formatting can introduce lint issues)per-file-ignores for tests, notebooks—never disable rules globally
when only certain files need relaxationqc task in pixi that runs lint + format-check + typecheckF (Pyflakes) or E4/E7/E9 rules — these catch real bugsselect = ["ALL"] without heavy ignore — too noisy, creates churnruff.toml and pyproject.toml [tool.ruff] in the same project
(Ruff uses the first config it finds; mixing causes silent ignoring)per-file-ignoresruff and ty in dev depsqc task orchestrationIf you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.