CLI — goga tool pybuggy endpoint generate¶
Scaffolds the api/ fixture tree from specifications: response JSON schemas, a per-endpoint
meta.json input contract, a pytest fixture module api.py per endpoint, empty __init__.py
package markers along the path, and empty tests/ directories.
goga tool pybuggy endpoint generate # all specs, skip existing
goga tool pybuggy endpoint generate -s shop -f # single spec, overwrite
goga tool pybuggy endpoint generate clients_startup_get # only the listed endpoints
Options and arguments¶
| Element | Meaning |
|---|---|
endpoint-ids (positional, variadic) |
Restrict generation to endpoints with these ids; an empty/absent filter — all endpoints |
-s/--spec <name> |
Restrict to a single spec; unknown name → ClickException("spec not found: <name>") |
-f/--force |
Overwrite existing artifacts (see below) |
An id not found in any selected spec → click.ClickException("endpoint not found: <id>");
validation runs before any artifact is written, so an unknown id never leaves a
partially generated tree.
Artifact tree¶
api/__init__.py # empty package marker
api/<spec>/__init__.py # empty package marker
api/<spec>/<endpoint.dir>/__init__.py # empty package marker
api/<spec>/<endpoint.dir>/schemas/<status_code>.json
api/<spec>/<endpoint.dir>/meta.json
api/<spec>/<endpoint.dir>/api.py
tests/<spec>/<endpoint.dir>/ # empty directory
<endpoint.dir> is the endpoint id sanitized to a Python identifier segment: every non-word
character becomes _ (the dot in /v1.0/clients → v1_0_clients_get; Unicode word characters
are preserved, as Python identifiers allow them) and a leading
digit is prefixed with _. The directory is a package-name segment loaded by dotted name, so it
must be importable; the fixture def name inside api.py derives from the same sanitized value,
keeping the two in lockstep. The endpoint-ids filter still keys on the raw id as produced by
build_endpoint_id. When two distinct ids of one spec sanitize to the same segment
(/v1.0/clients and /v1_0/clients), generate fails with a ClickException naming both —
their directories are never merged.
Example — spec shop, endpoint clients_startup_get with statuses 200, 404:
api/__init__.py
api/shop/__init__.py
api/shop/clients_startup_get/__init__.py
api/shop/clients_startup_get/schemas/200.json
api/shop/clients_startup_get/schemas/404.json
api/shop/clients_startup_get/meta.json
api/shop/clients_startup_get/api.py
tests/shop/clients_startup_get/
<status_code>.json holds the prettified, expanded response schema (indent=2,
ensure_ascii=False); statuses without application/json get {}.
meta.json¶
meta.json describes the endpoint input contract and is written for every generated
endpoint — it is never omitted, even when the endpoint declares no input data. It contains
exactly three keys:
| Key | Content | Source field |
|---|---|---|
parameters |
{name: schema} of the query parameters |
Endpoint.query_params |
request_body |
request-body schema | Endpoint.request |
vars |
{name: schema} of the URL path variables |
Endpoint.path_params |
Each key holds {} when the endpoint declares no such data; no key is ever dropped or
renamed. Schemas are taken from the endpoint exactly as extracted from the specification
(already nullable-normalized — consumers do not normalize them again). The file is
serialized as prettified JSON (indent=2, ensure_ascii=False), matching the
<status_code>.json convention.
Consumers read meta.json to build URL substitutions (vars) and request payloads
(request_body, parameters) without re-parsing the specification.
Contents of api.py¶
A fixture module scoped to a single endpoint, formatted with ruff (double quotes, sorted
imports, line length 120):
@pytest.fixture(scope="function")named{method}_{path_part}— accepts theapifixture, returns the endpoint route object for that path and method.- The route derives from the endpoint path by replacing
{param}with:param, preserving the parameter name and case (/clients/{orderID}/status→/clients/:orderID/status). class Request(BaseModel)models the request body, generated bydatamodel-code-generator: nested objects become separate classes, arrays become typedlist[...], enums becomeLiteral[...], optional fields becomeT | None = None. When the body has no properties, theRequestclass and thepydanticimport are omitted.
Example (POST /clients/calls/{orderID}/status, body {note: string}):
import pytest
from goga_tool_pybuggy.api import Api, Endpoint
from pydantic import BaseModel
class Request(BaseModel):
note: str
@pytest.fixture(scope="function")
def post_clients_calls_orderid_status(api: Api) -> Endpoint:
return Endpoint(api, "/clients/calls/:orderID/status", method="POST")
--force semantics¶
- Without
-f: existing<status_code>.json,meta.json,api.pyand__init__.pyfiles are silently skipped; missing files and directories are created. Idempotent. - With
-f: files are overwritten,__init__.pymarkers are rewritten empty, the entire artifact tree regenerates uniformly. __init__.pymarkers are placed only along the path toapi.py— never undertests/.- A tree regenerated without
-fdoes not gainmeta.jsonnext to already-present artifacts until-fis used or the file is missing — the established skip semantics, by design.
Special cases¶
| Case | Behavior |
|---|---|
Spec without paths (including an empty file) |
click.ClickException |
| Spec with an invalid response status key | click.ClickException — the key becomes a schemas/ filename, so only a 3-digit code, default, or a range wildcard (2XX) is accepted |
| Spec without endpoints | Skipped silently; no artifacts |
| Endpoint without a body (or a body without fields) | api.py without class Request; the fixture is generated anyway |
| Endpoint with no query parameters, request body, or path variables | meta.json with all three keys as {} |
Null parameters:, a null requestBody:/responses: (or their nested content:/application/json:), or a null response entry |
Extracted as empty; never a traceback |
Non-finite YAML numbers (.nan, .inf) in a schema or contract value |
Written as null — the JSON tokens NaN/Infinity are not valid strict JSON |
ruff not found, or a ruff invocation fails while formatting api.py |
click.ClickException ("ruff executable not found…" / "ruff failed: …") — endpoints written before the failing one are already on disk |
| Two distinct paths mapping to the same artifact directory | click.ClickException naming both paths — detected before any write |
Preconditions¶
- Spec files must be present in
location(after pull or placed manually). - The config must be valid and reside at the fixed path.
- Artifacts are written to the current working directory.
ruffis installed (onPATHor in the running interpreter's venv) —api.pygeneration requires it.