Skip to main content

python-generator

Design of the ejected Redocly `python` client generator. Read it, and update it, before changing generators/python/.

소스 정보

저장소
Redocly/redocly-cli
최근 소스 활동
2026년 10월 8일 14:40
감지된 SKILL.md 언어
영어
스타
1,519
포크
228

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
python-generator
description
Design of the ejected Redocly `python` client generator. Read it, and update it, before changing generators/python/.
# The `python` generator — its skill This file is the DESIGN of your ejected `python` generator (`generators/python/`): **to change the generator, edit this skill first, then make the code match it** — a diff to `generators/python/` that has no covering sentence here is incomplete. ## What it emits One self-contained `<stem>.py`: typed dataclass models, a sync `Client` and an async `AsyncClient`, and the embedded runtime. Python ≥ 3.9; the only dependency is [httpx](https://www.python-httpx.org/) (`pip install httpx`). ## Design decisions that must hold - **The file name is an importable module name.** The `--output` stem follows the TypeScript convention (`openapi.client.ts`), and `openapi.client.py` cannot be imported by name — nor can hyphens or a leading digit. The stem is converted with `identifierFor(stem, snake)`, so `rebilly-core.client.ts` emits `rebilly_core_client.py` and `import rebilly_core_client` just works. - **Models are dataclasses by default**, required fields first (a dataclass constraint), optionals `Optional[T] = None`. Wire names live in a `_field_map: ClassVar[Dict[str, str]]`; decode/encode is reflective (`_decode.py`, `get_type_hints`) — no per-model codecs. - **`models: pydantic` emits `BaseModel` classes instead**, for the FastAPI-shaped half of the ecosystem that expects them. A wire name becomes `Field(alias=…)` with `populate_by_name=True`, so `_field_map` is not emitted in this mode — the alias is the mapping. Everything else is unchanged: the same class names, the same field names, the same `Optional[T] = None`, the same enums and union aliases, the same client and runtime. Switching modes must not change a call site. - **A discriminated union carries its discriminator into the pydantic annotation.** The decoder hands a whole object tree to `model_validate`, so a union nested in a model is resolved by pydantic and never reaches the `DISCRIMINATORS` table that dataclass mode walks. Pydantic resolves it correctly from `Annotated[Union[...], Field(discriminator=…)]`, which it accepts only when every member types that property as a `Literal` — and the mapping already pins one value per member, so the members get `Literal["cat"]`. Such a union registers no table entry: pydantic owns it at every depth, and the `Literal` makes the decoder's member probe exact. A union whose members never declare the property keeps the plain `Union` and the table entry, and pydantic then matches nested members its own way — the description is what has to change there. - **One runtime serves both model modes.** `_decode.py` dispatches on the target: a class with `model_validate` is validated by pydantic, a dataclass is hydrated reflectively, and `encode` mirrors that with `model_dump(by_alias=True, exclude_none=True, mode="json")`. A second runtime variant per mode would double the surface that has to stay in step, and pydantic's `ValidationError` already subclasses `ValueError`, so union member probing needs no new except clause. - **`models: pydantic` adds a dependency, and the header says so.** The default mode keeps httpx as the only requirement; the pydantic header asks for both. A mode that quietly needed a package the file never named would fail at import with nothing to act on. - **Every parameter is its own argument, so their names share one namespace** with the arguments the method declares itself (`body`, `headers`, `timeout`, `retry`, `idempotency_key`). Build them with `uniqueIdentifiers(..., { taken: … })`: OpenAPI lets one operation use a name in two locations (`id` in the path AND in the query), and a `def` that declared one name twice is a `SyntaxError`. The wire name is untouched, so the request is unchanged. - **Naming:** fields/methods snake*case via `identifierFor(..., RESERVED_WORDS.python)`; reserved words get a trailing underscore (`class*`); `+1`/`-1`become`plus_1`/`minus_1`. - **Enums** are `class X(str, Enum)` with SCREAMING members; **unions** are `Union[...]` aliases. A DISCRIMINATED union registers its dispatch table in the runtime's `DISCRIMINATORS` registry (`DISCRIMINATORS[Pet] = ("petType", {"cat": Cat, ...})`), and `decode()` routes through it — `isinstance` narrowing works on decoded members. Undiscriminated unions decode by trying each member in order (the first that hydrates wins — see `_decode.py`). **allOf** is flattened via `flattenAllOf`. - **Auth keys match the other languages.** `auth={"apiKey": {...}}` is the documented key — the same spelling TypeScript and PHP use, and the same as the scheme kind — with `api_key` accepted as an alias so a snake_case config keeps working. - **Errors:** `errorMode` maps to raising `ApiError` (default) or returning a `Result` dataclass — the only generator with both modes outside TypeScript. - **Dates:** `dateType: Date` annotates `format: date-time` as `datetime` and `date` as `date`; `_decode.py` parses ISO strings into them and `encode()` writes `isoformat()` back. The default (`string`) keeps the wire shape. - **Response headers:** an operation that DECLARES success-response headers gains a `<op>_with_headers()` variant (sync and async) returning `Envelope[T]` — `data`, `headers` (coerced to int/bool/str with snake_case keys; absent/unparsable values omitted), and the raw `response`. Operations without declared headers get no variant, and the base method stays body-only. - **Servers:** when the description declares servers, a `Servers` class is emitted with one static method per server; server VARIABLES become keyword arguments defaulting to the spec's defaults (`Servers.production(organization_id="org_x")`), so templated base URLs need no manual string building. The client's baked default stays `servers[0]` with variable defaults substituted. - **Stream bodies pass through.** For an untyped `multipart/form-data` operation (`isUntypedMultipartBody`) or a binary operation (`isBinaryContentType`), the body argument is `Any`, and a body that is `bytes`/`bytearray`, a `str`, a file-like object (a callable `.read`), or an iterator/async iterator of bytes goes to httpx `content=`, never through `json=` or `to_multipart`; a `dict` body of an untyped multipart operation keeps the `to_multipart` path. The runtime sets the operation's declared content type only when the caller set none (matched case-insensitively), and never for a multipart body, whose boundary is the caller's. A body that cannot be replayed (a file-like object or iterator, as opposed to `bytes` or `str`) is sent in exactly one attempt, whatever the retry policy says. The per-attempt `timeout` still applies. Both the sync and the async `send` behave the same. Typed multipart bodies are unaffected: they go straight to `to_multipart` and never through stream detection, so a model field named `read` cannot turn one into a raw upload. - **Parity surface:** auth (bearer/basic/apiKey), retries with `Retry-After` + jittered backoff, timeouts, idempotency keys, middleware, pagination (`<op>_pages()` / `<op>_items()` + `aiter` mirrors), SSE (`iter_sse`/`aiter_sse`), multipart. - The runtime is hand-written in `runtime/*.py` in this folder and embedded as strings at prepare time — generator code never builds runtime logic from templates. Under `--runtime module` the same sources are written as sibling `_*.py` files instead (package-relative imports become sibling imports; the client star-imports each module). - Authored ONLY with the neutral toolkit (`Printer`, naming, schema, pagination helpers) — the dogfooding guard fails otherwise. - **It documents itself.** With `client.docs` (or `--docs`), the `docs` hook writes `<stem>.python.md`: the security schemes, then one section per operation with its parameters, body, response type, and behavior notes. The call snippets come from this generator's own `sample` hook, so the page can only show the syntax of the SDK beside it, and the layout comes from `renderReferencePage` in the authoring toolkit — reachable from an ejected copy through `@redocly/client-generator`. Pagination on the page is decided by `paginationRuleFor`, the same helper this generator resolves pagination with. ## The modify loop 1. Edit this skill: state the new behavior or decision. 2. Make `generators/python/` match it. 3. Run `redocly generate-client` and inspect the `git diff` of the generated output — generated files are never hand-edited. Newer built-in versions merge in with `redocly eject-generator python --update`.
GitHub에서 보기