Python Patterns
Overview
This skill curates the upstream python-patterns workflow from sickn33/antigravity-awesome-skills into a practical decision aid for real Python work.
Use it when the task is not “write some Python,” but “choose the right Python approach.” The core idea from upstream remains intact: teach decision-making, not pattern memorization.
This skill is strongest when the operator must decide among:
- sync vs async execution models
- threads vs processes vs in-process work
- framework or stack fit based on constraints
- where type hints add the most leverage
- how to structure a project around
pyproject.toml, tests, and imports - how to verify that a chosen pattern is actually working
Open the support files when needed:
references/runtime-practices.mdfor decision matrices and failure modesexamples/implementation-example.mdfor worked before/after scenariosscripts/validate-runtime.pyto inspect a repository for layout, tooling, async, and subprocess risks
When to Use This Skill
Use this skill when:
- the user wants Python architecture guidance rather than a single code snippet
- the task involves framework selection, concurrency choices, typing strategy, packaging, or project layout
- the request asks whether a system should be async, typed, packaged, or reorganized
- the operator needs to justify tradeoffs, not just produce an implementation
- a repository should be checked for Python structure or runtime-pattern mismatches before changes are proposed
Do not use this skill as the primary router when:
- the task is language-agnostic system design with little Python-specific impact
- the user already chose the architecture and only needs feature implementation
- the work is mainly deployment, containerization, CI, or cloud operations rather than Python design
Operating Table
| Situation | Start here | Why it matters |
|---|---|---|
| Need to decide sync vs async | references/runtime-practices.md |
Gives a concrete decision matrix instead of treating async as a default |
| Need to choose how work runs in the background | references/runtime-practices.md |
Separates in-request work, thread/process offloading, and external job systems |
| Need to improve project structure | examples/implementation-example.md |
Shows realistic pyproject.toml and src/ tradeoffs with expected outcomes |
| Need typing guidance | references/runtime-practices.md |
Focuses typing on API boundaries and static-analysis value |
| Need to inspect an existing repo | scripts/validate-runtime.py |
Reports concrete findings on metadata, layout, test/import risks, async smells, and subprocess safety |
| Need to explain a recommendation | SKILL.md + linked support files |
Keeps the answer tied to explicit tradeoffs and verification steps |
Workflow
Clarify the decision.
- What is being chosen: framework, concurrency model, typing depth, layout, or background-task pattern?
- What constraints matter: latency, throughput, CPU load, I/O wait, packaging needs, team familiarity, deployment model?
Classify the workload.
- Mostly sequential business logic: start sync.
- High I/O concurrency with async-capable dependencies: consider async.
- Blocking I/O from libraries you cannot replace: consider thread offloading.
- CPU-heavy work: consider processes or external workers.
Inventory the dependency stack.
- Are the database, HTTP, messaging, and framework layers async-capable end to end?
- Are there blocking SDKs that would negate async benefits?
- Is this a package, an internal service, or a script repo?
Choose the smallest sound pattern.
- Prefer the least complex model that fits the workload.
- Do not adopt async only because it is modern.
- Do not force
src/layout or heavy typing if the repo is a small script-only tool.
Define verification gates before implementation.
- tests run in the intended import mode
- lint/format checks run cleanly
- type checking is applied where it adds signal
- blocking calls are identified in async paths
- performance claims are measured, not assumed
Implement or recommend with explicit tradeoffs. Include:
- why this pattern was chosen
- what alternative was rejected
- what failure mode the choice avoids
- how the team can verify the result
Validate the repository when relevant. Run:
python scripts/validate-runtime.py .Then use the findings to refine the recommendation.
Decision Principles
1. Framework and stack selection
Do not rank frameworks generically. Choose based on constraints.
Prefer questions like:
- Does the app need high fan-out I/O concurrency?
- Are the core dependencies async-native?
- Is this a small internal service where simplicity matters more than concurrency features?
- Does the team need strong typing, validation, or admin tooling?
- Will the app be packaged and reused, or only deployed as one service?
A good answer names selection criteria and tradeoffs. A weak answer says “always use X.”
2. Async vs sync
Default to synchronous code unless there is a real need for structured I/O concurrency.
Use async when:
- the workload is I/O-bound
- the framework and key dependencies are async-capable
- request handlers would otherwise spend significant time waiting on sockets or similar external operations
- the team can debug event-loop behavior and cancellation correctly
Stay sync when:
- work is mostly CPU or straightforward request/response logic
- important libraries are blocking
- concurrency demands are low
- introducing async would create a mixed model with little benefit
Do not claim async is inherently faster. It is often better for coordinating many I/O-bound operations, not as a blanket performance upgrade.
3. Background work
Separate these cases clearly:
- small in-process follow-up work: safe only if it is brief, observable, and failure handling is explicit
- blocking I/O offload: use threads or executor-style offloading where appropriate
- CPU-heavy work: prefer processes or an external worker system
- durable long-running jobs: use an external queue/worker model rather than pretending in-request background tasks are reliable job infrastructure
If subprocesses are involved, prefer argument lists over shell command strings, validate inputs, and make timeouts explicit.
4. Type hints
Treat typing as a way to improve API clarity and static analysis.
Prioritize:
- public interfaces
- module boundaries
- complex data structures and return types
- internal helpers only when the signal is worth the maintenance cost
Important boundary:
- type hints help static tools and readers
- they do not automatically enforce runtime validation
5. Project structure
Use pyproject.toml as the default metadata and tool-configuration anchor.
Use src/ layout when:
- you are building a package
- you want tests to exercise installed/importable code more reliably
- import-path mistakes have caused issues before
A flat layout can still be reasonable when:
- the repo is a small script collection
- packaging is not a goal
- the extra structure would add ceremony without operational benefit
The goal is not aesthetic purity. The goal is predictable imports, packaging behavior, and test execution.
Examples
See examples/implementation-example.md for complete worked scenarios.
Typical use cases covered there:
- fixing an async endpoint that still performs blocking work
- choosing
src/layout for a reusable package while avoiding unnecessary structure for simple script repos - introducing gradual typing at module boundaries instead of annotating everything at once
Troubleshooting
RuntimeError: event loop is already running
Usually means async code is being started from an environment that already owns the loop, or async entry points are being nested incorrectly.
Check:
- where the loop is created
- whether framework code already manages it
- whether helper code is calling top-level loop runners from inside async contexts
Service stayed slow after async adoption
Common causes:
- blocking database or HTTP clients remain in the request path
- CPU-heavy work still runs on the event loop
- concurrency was added without measuring the actual bottleneck
Check dependencies, profile the hot path, and confirm the workload was actually I/O-bound.
Tests pass locally but imports fail after packaging or install
Common causes:
- flat-layout imports accidentally rely on the repository root being on
PYTHONPATH - tests import source files directly rather than the installable package
- project metadata and package discovery are incomplete
Inspect layout choices and run the validator script for import-risk hints.
Type checker noise exploded after partial adoption
Common causes:
- typing was started in unstable internal code rather than stable boundaries
- strictness was enabled repo-wide too early
- untyped third-party interfaces were not isolated behind typed wrappers
Adopt types module by module. Raise strictness where signal is high.
Background task blocks the server
Common causes:
- CPU-heavy work left in request handlers
- “fire-and-forget” tasks still use blocking code
- exceptions are not surfaced or monitored
Move work to the right execution model and make timeout, retry, and cancellation behavior explicit.
shell=True or shell-string command construction appears in helpers
This is a security review point.
Prefer:
subprocess.run(["python", "-m", "pytest"], check=True)
Over:
subprocess.run("python -m pytest", shell=True, check=True)
Especially when any input is dynamic.
Additional Resources
references/runtime-practices.md— decision matrices, checklists, and common failure modesexamples/implementation-example.md— concrete scenarios with chosen and rejected patternsscripts/validate-runtime.py— repository scanner for Python runtime-pattern mismatches
Suggested validation commands when appropriate:
python scripts/validate-runtime.py .
python -m pytest
If the repo already uses Ruff or mypy, include them in the verification gate rather than adding tools unconditionally.
Related Skills
Use a different or additional skill when the center of gravity shifts to:
- framework-specific implementation details
- packaging and publishing mechanics beyond architecture choice
- deployment, containers, CI/CD, or production operations
- deep performance profiling beyond pattern selection
Operator Notes
A strong answer from this skill:
- starts from workload and constraints
- recommends the least complex sound pattern
- avoids universal claims about frameworks or async
- makes typing and structure decisions intentionally
- includes a verification plan, not just a recommendation
A weak answer:
- prescribes one framework by habit
- treats async as automatically faster
- equates type hints with runtime validation
- forces
src/layout everywhere - gives no way to verify the recommendation in the repository