Python Recipes
August 5, 2026 · View on GitHub
Everything Python-specific in one place. Universal requirements live in anatomy.
Package layout
Every Python recipe uses this shape:
contrib/python/my-recipe/
pyproject.toml # project spec + deps
uv.lock # pinned deps
.env.example # env vars the recipe reads
manifest.yaml # recipe metadata (see anatomy)
README.md # description, setup, run
app/
__init__.py # runs load_dotenv(), then imports agent
agent.py # your agent code
tests/
test_runnability.py # import smoke test
Best practice: name the Python package app. Not strictly
enforced, but the root Ruff/isort configuration assumes it
(known-first-party = ["app"]) — other names produce wrong
import ordering.
Minimum Python: 3.11.
The fast path
New recipe:
"scaffold a new Python recipe at contrib/python/my-recipe"
# ... write your agent in app/agent.py ...
"prepare the python recipe contrib/python/my-recipe"
Updating a recipe:
"prepare the python recipe contrib/python/my-recipe"
prepare-python-recipe is safe to re-run and won't overwrite
your .env.example or hand-written Python code.
Full skill reference: skills catalog.
Deprecated models:
gemini-2.0-flashandgemini-2.5-flashare no longer accepted. Usegemini-3.5-flash.
Copy-paste starters
The scaffold-python-recipe skill creates these for you. Use
them by hand if you're setting up a recipe manually.
pyproject.toml
[project]
name = "my-recipe" # see "Project name" below
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"google-adk",
"python-dotenv>=1.0.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Project name. [project].name is derived from where the
recipe lives:
| Location | [project].name | Example |
|---|---|---|
core/<language>/<recipe> | the folder name | deep-search |
contrib/<language>/<recipe> | the folder name | financial-advisor |
skills/<vertical>/<solution> | <vertical>-<solution> | retail-product-search |
Vertical skills join the vertical in because the folder name
alone is not unique across verticals — skills/retail/product-search
and skills/grocery/product-search would otherwise both claim the
distribution name product-search. It also keeps the package name
aligned with the skill's SKILL.md name: field.
Optional: add description if you want it in your pyproject.toml
— it must match manifest.description exactly. Do NOT add
[tool.ruff] or a local ruff.toml (Ruff config is
centralised at the repo root).
.env.example
GOOGLE_CLOUD_PROJECT=your-project-id
GOOGLE_CLOUD_LOCATION=us-central1
MODEL_NAME=gemini-3.5-flash
Every env var your code reads must appear here. Read model
names from MODEL_NAME — never hardcode a model literal in
source. Deprecated: gemini-2.0-flash, gemini-2.5-flash.
app/__init__.py
from dotenv import load_dotenv
load_dotenv()
from . import agent # noqa: E402
Call load_dotenv() here, not in agent.py. __init__.py
runs first when the package is imported, so .env loads
before any env-var reads at agent-import time. The
# noqa: E402 tells Ruff the late import is intentional.
tests/test_runnability.py
"""Runnability tests for the recipe."""
def test_agent_runnability() -> None:
"""Verify agent.py imports and defines root_agent."""
import app.agent
assert app.agent.root_agent is not None
This is the bare minimum; you may need to tweak or extend it
to make it work for your recipe. The core idea is to import
app.agent and check whether root_agent is None.
Integration tests
Tests that hit real external resources (Gemini, BigQuery, third-party APIs). CI skips them for speed and credentials — run them locally before opening a PR.
Excluded from CI:
| Pattern | Excluded |
|---|---|
tests/integration/ | Everything under this directory |
**/test_integration.py | Files with this exact name, any depth |
Adding one: create a file matching either pattern, write
it with real API calls, document credential setup in the
recipe's README.md. Run locally:
cd contrib/python/my-recipe
uv run pytest tests/integration # integration only
uv run pytest # full suite
Local commands
# Structural checks (all validators at once)
uv run validate contrib/python/my-recipe
# Or individual: manifest / structure / readme
uv run validate readme contrib/python/my-recipe
# Format and lint
uv run ruff format contrib/python/my-recipe
uv run ruff check contrib/python/my-recipe
# Tests (mirrors CI — integration excluded)
cd contrib/python/my-recipe
uv run pytest --ignore=tests/integration \
--ignore-glob="**/test_integration.py"