How this library was generated¶
Every subpackage under action0.open_meteo — the clients, the operation
classes, the enums, the models and their JSON converters — was generated
by action0-client-openapi
from Open-Meteo’s OpenAPI schemas, and is checked in like hand-written
code. This page is the worked example of that generator: exactly what we
ran, and what came out.
The schemas¶
Open-Meteo publishes one OpenAPI 3.1 document per service in the
open-meteo/open-meteo
repository. The nine documents are vendored under schemas/ (pinned to
an upstream commit — schemas/README.md records which). The Geocoding
API has no upstream schema, so schemas/geocoding.yml was written for
this project against the API docs and live responses — authoring a small
OpenAPI file and generating from it beats hand-writing a client.
The generation runs¶
tools/regenerate.sh runs the generator once per schema — the whole
script is ten calls of this shape:
uv run action0-openapi schemas/forecast.yml \
-o src/action0/open_meteo \
--package-name forecast \
--client-name ForecastClient \
--force
--package-name and --client-name override the defaults derived from
the schema’s info.title (which would have been the mouthful
open_meteo_weather_forecast_api_client / OpenMeteoWeatherForecastApiClient).
Everything else comes from the schema itself:
The base URL. Open-Meteo declares its
serversper path, not at the document level; the generator falls back to them, soForecastClientdefaults tohttps://api.open-meteo.comandAirQualityClienttohttps://air-quality-api.open-meteo.com— no--base-urlflags needed.Enums for the weather variables.
hourly=temperature_2m,rain-style parameters are enum arrays in the schema, so the generated fields arelist[GetV1ForecastHourlyItem]and your IDE completes the legal values.Typed dynamic keys. The ensemble and seasonal APIs answer with per-member keys (
temperature_2m_member01, …) declared asadditionalProperties; the generated models collect them in a typed catch-all field:additional_properties: dict[str, list[float]].Dates.
start_date/end_dateparameters areformat: date, so the fields aredatetime.dateand serialize to ISO 8601.Comma-joined variable lists. The schemas declare
explode: falseon the array parameters, so the generated fields carry aserialize=join lambda — the request sayshourly=temperature_2m,rain, Open-Meteo’s documented form.Typed errors. The documented 400 answers (
{"error": true, "reason": ...}) become anerrors.pyper package: aBadRequestErrorsubclassingaction0.client.APIError, raised by the operations’ generatedcheck()with the parsed payload as.error.
Each run writes one package: client.py, operations.py, models.py,
errors.py, __init__.py (re-exporting everything) and py.typed,
each file headed by a generated by action0-client-openapi vX.Y.Z from forecast.yml — do not edit line.
Living with generated code¶
The generated packages are treated exactly like hand-written code —
reviewed in PRs, type-checked strictly (mypy --strict, pyright and ty
run over them in CI) and linted (they come out of the generator already
ruff format-clean). What is not done is editing them by hand: when
Open-Meteo’s schemas change, we re-vendor them, run
bash tools/regenerate.sh, and review the diff — hand edits would be
lost on the next regeneration, which the do not edit headers are there
to remind you of. Hand-written code (the tests, the examples, the docs)
lives outside the generated packages.
This project also feeds back the other way: generating it surfaced
three generator gaps — the additionalProperties catch-all, the
path-level servers fallback and a naming fix (get_v_1_... →
get_v1_...) — all fixed in action0-client-openapi 0.1.1 before this
library was first generated. A second round followed the same day:
honoring explode: false and generating typed error exceptions landed
in 0.1.2, and this library picked both up by regenerating — the diff
was reviewed like any other change.