CLI — goga tool pybuggy endpoint diff¶
Read-only drift report between the specifications and the generated artifacts: for every compared
unit it prints one JSON document with the structural difference between the endpoint contract taken
from the current spec and the artifacts previously written by generate under api/.
An endpoint whose artifacts match the spec prints an empty diff — {}.
goga tool pybuggy endpoint diff # all specs, every endpoint
goga tool pybuggy endpoint diff -s shop # single spec, every endpoint
goga tool pybuggy endpoint diff clients_startup_get # only the listed endpoints
Options and arguments¶
| Element | Meaning |
|---|---|
endpoint-ids (positional, variadic) |
Restrict the comparison to endpoints with these ids (raw clients_startup_get-style ids, not directory segments); an empty/absent filter — every endpoint of the selected specs |
-s/--spec <name> |
Restrict the report to a single spec; unknown name → ClickException("spec not found: <name>") |
Filtering semantics:
- Argument not passed (or an empty list) — a no-op filter: all endpoints of the selected specs are compared.
- An id found in at least one selected spec — only the matching endpoints are compared.
- An id not found in any selected spec →
click.ClickException("endpoint not found: <id>"), non-zero exit. Several missing ids — all are listed (sorted); nothing is printed.
Validation runs before any output, so an unknown id never produces a partial report. The
endpoint-ids filter narrows only the spec side: removed artifact directories are still discovered
over the whole api/<spec>/ tree of every selected spec (see below).
Output format¶
One JSON document per compared unit, one line each, keyed by the unit's identifier:
{"clients_startup_get": {}}
{"clients_startup_get": {"values_changed": {"root['request_body']['properties']['note']['type']": {"old_value": "integer", "new_value": "string"}}}}
{"clients_startup_get": {"dictionary_item_added": ["root['schemas']['418']"]}}
{"legacy_endpoint_get": {"values_changed": {"root": {"new_value": {}, "old_value": {"parameters": {}, "request_body": {}, "vars": {}, "schemas": {"200": {"type": "object"}}}}}}}
- The key is the raw endpoint id for spec-side entries and the sanitized artifact directory segment for removed directories.
- An endpoint with no drift prints an empty diff value — every compared endpoint prints a document, with or without an endpoint-id filter.
- Order is deterministic: specs in config order, endpoints in extraction order, removed directories sorted by segment.
- The categories and
old_value/new_valuepairs come fromdeepdiffverbatim; the command does not filter, rename or reorder them. The comparison direction is fixed — the generated side is the old/left value, the spec side the new/right one — soold_valuealways describes the artifacts andnew_valuethe current spec.
One-sided endpoints¶
- An endpoint of the spec without an artifact directory is reported as added: the spec side is
compared against an empty contract, and adding a contract as a whole surfaces as a
values_changedentry onroot(an emptyold_value, the full contract as thenew_value). - An artifact directory under
api/<spec>/whose segment matches no endpoint of the spec is reported as removed: an empty contract is compared against the directory's artifacts, which likewise surfaces as avalues_changedentry onroot(the full artifact contract as theold_value, an emptynew_value). - Per-key
dictionary_item_added/dictionary_item_removedcategories appear for nested keys under shared ancestors — e.g. a status code present on only one side under a commonschemaskey; one-sided comparisons of the contract as a whole do not decompose per key. - Removed-side discovery scans the whole
api/<spec>/tree of each selected spec in every run — it is not narrowed byendpoint-ids— and skips tooling directories:__pycache__(left underapi/<spec>/by importing the generated fixture package) and hidden.-prefixed directories are never reported as removed. - A spec with no
api/<spec>/tree at all reports every endpoint as added and fails nothing.
What is compared¶
Both sides are normalized to the same four-key structure before comparing:
| Key | Spec side | Generated side |
|---|---|---|
parameters |
Endpoint.query_params |
meta.json → parameters |
request_body |
Endpoint.request |
meta.json → request_body |
vars |
Endpoint.path_params |
meta.json → vars |
schemas |
Endpoint.response ({status_code: schema}) |
schemas/<status_code>.json |
Only meta.json and schemas/*.json take part — api.py, __init__.py markers and tests/
directories are never compared. Schema values are read as plain JSON without interpretation, and
spec-side values are normalized to JSON-native ones first (a YAML date becomes an ISO 8601 string,
a non-finite number — .nan/.inf — becomes null, the value generate writes for them), so a
matching pair of sides never reports spurious type_changes and a tree regenerated from an
unchanged spec compares equal instead of drifting on every run.
Special cases¶
| Case | Behavior |
|---|---|
Spec without paths, not a mapping, without an openapi/swagger version key, or with an invalid response status key |
click.ClickException |
| Spec without operations | No per-endpoint output; every artifact directory of that spec is reported as removed |
Null parameters:, a null requestBody:/responses: (or their nested content:/application/json:), or a null response entry |
Extracted as empty; never a traceback |
Missing, unreadable or corrupt meta.json / schema JSON |
click.ClickException |
| Two ids sanitizing to the same segment | Both endpoints are compared against the one directory; not an error |
| Any successful run | Exit 0 — drift is a result, not a failure |
| Whole run | Read-only — the artifact tree is left byte-identical |