openapi-client-codegen 0.1.5__tar.gz → 0.1.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openapi_client_codegen-0.1.7/.github/workflows/ci.yml +44 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/AGENTS.md +11 -11
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/PKG-INFO +2 -1
- openapi_client_codegen-0.1.7/README.md +70 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/pyproject.toml +2 -1
- openapi_client_codegen-0.1.7/src/openapi_client_codegen/cli.py +80 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/client.py +8 -2
- openapi_client_codegen-0.1.7/src/openapi_client_codegen/orchestrator.py +37 -0
- openapi_client_codegen-0.1.7/tests/test_cli.py +107 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/tests/test_orchestrate.py +71 -43
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/uv.lock +103 -0
- openapi_client_codegen-0.1.5/.github/workflows/ci.yml +0 -27
- openapi_client_codegen-0.1.5/README.md +0 -74
- openapi_client_codegen-0.1.5/src/openapi_client_codegen/cli.py +0 -91
- openapi_client_codegen-0.1.5/src/openapi_client_codegen/naming.py +0 -26
- openapi_client_codegen-0.1.5/src/openapi_client_codegen/orchestrator.py +0 -95
- openapi_client_codegen-0.1.5/tests/test_naming.py +0 -19
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/.gitattributes +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/.gitignore +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/.python-version +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/CLAUDE.md +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/LICENSE +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/NOTICE +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/publish-config.json +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/ruff.base.toml +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/ruff.toml +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/__init__.py +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/_data/configs/csharp.json +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/_data/configs/python.json +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/_data/openapi-generator-ignore +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/_data/templates-patches/csharp/0001-csharp-remove-errant-comma.patch +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/_data/templates-patches/csharp/0002-csharp-models-required-only-constructors.patch +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/_data/templates-patches/csharp/0003-csharp-httpclient-use-defaultT.patch +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/_data/templates-patches/csharp/0004-csharp-support-hybrid-multipart-payloads.patch +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/_data/templates-patches/csharp/0006-csharp-streaming-download-response.patch +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/_data/templates-patches/csharp/0007-csharp-model-default-initializers.patch +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/_data/templates-patches/csharp/0008-csharp-redirect-documentation-file.patch +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/downgrade.py +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/py.typed +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/resources.py +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/templates.py +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/unity.py +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/tests/test_downgrade.py +0 -0
- {openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/tests/test_unity.py +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
check:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v5
|
|
20
|
+
|
|
21
|
+
- uses: astral-sh/setup-uv@v7
|
|
22
|
+
with:
|
|
23
|
+
enable-cache: true
|
|
24
|
+
|
|
25
|
+
- name: Preflight
|
|
26
|
+
run: uvx --from python-devkit==0.1.5 preflight-python
|
|
27
|
+
|
|
28
|
+
publish:
|
|
29
|
+
needs: check
|
|
30
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
permissions:
|
|
33
|
+
contents: write
|
|
34
|
+
id-token: write
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v5
|
|
37
|
+
|
|
38
|
+
# Workaround: fetch-tags is broken with shallow clones
|
|
39
|
+
- run: git fetch --tags origin
|
|
40
|
+
|
|
41
|
+
- uses: astral-sh/setup-uv@v7
|
|
42
|
+
|
|
43
|
+
- name: Publish
|
|
44
|
+
run: uvx --from release-devkit==0.1.3 publish-packages --config publish-config.json
|
|
@@ -2,30 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
## What this is
|
|
4
4
|
|
|
5
|
-
`openapi-client-codegen` wraps [OpenAPI Generator](https://openapi-generator.tech/) to turn an OpenAPI schema into typed client packages — a Python (`httpx`) client and a Unity-consumable C# (`httpclient`) client. It owns every step that is generic across consumers: the project orchestration loop, downgrading 3.1→3.0, the committed-spec unchanged skip, authoring and patching the C# templates, running the generator, and writing the Unity package metadata. The caller owns what is inherently project-specific: **producing the OpenAPI spec** (
|
|
5
|
+
`openapi-client-codegen` wraps [OpenAPI Generator](https://openapi-generator.tech/) to turn an OpenAPI schema into typed client packages — a Python (`httpx`) client and a Unity-consumable C# (`httpclient`) client. It owns every step that is generic across consumers: the project orchestration loop, downgrading 3.1→3.0, the committed-spec unchanged skip, authoring and patching the C# templates, running the generator, and writing the Unity package metadata. The caller owns what is inherently project-specific: **producing the OpenAPI spec** (a command string in the consumer's config, run by the library's `SpecProducer`), the project→client mapping, the naming root, the npm scope, license, repository URL, and the generated output root — all as fields of the `ProjectsConfig` model.
|
|
6
6
|
|
|
7
|
-
The package is `openapi_client_codegen` (src-layout under `src/openapi_client_codegen/`), renamed from `openapi-clientgen` before the first publish (operator, 2026-09-20 — no artifact carries the old name). Its Python dependencies are `bashrun` (the guardrailed subprocess wrapper) and `typer` (the thin CLI),
|
|
7
|
+
The package is `openapi_client_codegen` (src-layout under `src/openapi_client_codegen/`), renamed from `openapi-clientgen` before the first publish (operator, 2026-09-20 — no artifact carries the old name). Its Python dependencies are `bashrun` (the guardrailed subprocess wrapper), `pydantic` (the config schema), and `typer` (the thin CLI), all from PyPI — git-source pins only in scratch branches testing unreleased changes. At **runtime** it also needs **Java (JDK 11+)** and **`uvx`** on PATH — the generator itself runs as `uvx --from 'openapi-generator-cli[jdk4py]==<pin>' ...`.
|
|
8
8
|
|
|
9
9
|
## Release flow
|
|
10
10
|
|
|
11
|
-
Publishing rides `ci.yml`'s `publish` job on every push to `main` (gated on the check job): release-devkit's publish
|
|
11
|
+
Publishing rides `ci.yml`'s `publish` job on every push to `main` (gated on the check job): release-devkit's `publish-packages` (an inlined, version-pinned `uvx` step in the publish job, keeping OIDC identity local), never a project dependency (openapi-client-codegen sits inside release-devkit's transitive dependency graph; a project-level edge is a resolver cycle) — computes the plan from the tag ledger and path-diff, patches the version ephemerally, and publishes to PyPI under OIDC trusted publishing (pending publisher bound to `ci.yml`, no environment). The committed `pyproject.toml` version is permanently the `0.0.0.dev0` sentinel; the `openapi-client-codegen-v*` tags are the version ledger (first release `0.1.0`, patch-auto thereafter). API-breaking changes ship with a manually bumped version — patch-auto assumes additive changes.
|
|
12
12
|
|
|
13
13
|
## Public API
|
|
14
14
|
|
|
15
|
-
The package init is empty (RUF067 strict mode — the canonical ruff config bans any content in `__init__.py`); import each symbol from its defining module: `client` (`generate_client`), `
|
|
15
|
+
The package init is empty (RUF067 strict mode — the canonical ruff config bans any content in `__init__.py`); import each symbol from its defining module: `client` (`generate_client`), `cli` (`app`), `downgrade` (`downgrade_openapi_3_1_to_3_0`, `JsonDict`), `orchestrator` (`ProjectsConfig`, `ClientNaming`, `SpecProducer`), `templates` (`regenerate_templates`), `unity` (`write_unity_package_metadata`).
|
|
16
16
|
|
|
17
17
|
| Symbol | Role |
|
|
18
18
|
|---|---|
|
|
19
19
|
| `downgrade_openapi_3_1_to_3_0(schema)` | In-place 3.1.0→3.0.3 fixup (openapi-generator rejects 3.1). Pure function. |
|
|
20
|
-
| `
|
|
21
|
-
| `SpecProducer(command)` | Callable class wrapping any
|
|
22
|
-
| `
|
|
20
|
+
| `ProjectsConfig` | The pydantic schema of a consumer's client-generation config (`extra="forbid"`): required `projects`, `root_name`, `generated_root`, `spec_command`; optional `spec_env`, `npm_scope`, `license_spdx`, `repository_url`. The single source of identity — the CLI validates the `--config` file into it and consumes it; nothing overrides it per invocation. |
|
|
21
|
+
| `SpecProducer(command, env=None)` | Callable class wrapping any command that prints the spec to stdout, run with the project directory as cwd and an optional per-call env overlay; the CLI command builds it from the config's `spec_command` / `spec_env`, and programmatic consumers construct it directly. |
|
|
22
|
+
| `app` | The single-command CLI (invoked directly, no subcommand) behind the `openapi-client-codegen` console script and consumer `[project.scripts]` aliases (e.g. placeframe's `generate-clients`). It validates the `--config` JSON against the `ProjectsConfig` pydantic model (`extra="forbid"`: unknown keys rejected — a `ValidationError` propagates) and then runs the whole orchestration itself: per mapping entry (`projects` maps project paths to generator lists), produce the spec via `SpecProducer`, downgrade it, write `<project>/openapi.json` unless byte-identical to the committed spec (`no_cache` forces through), and `generate_client` per listed generator into `<generated_root>/<generator>/<names.base>`. The flags are invocation-scoped only (filters, `--no-cache`, `--root`; project paths resolve against `root`). Commands run shell-less (shlex-split, operators rejected), so env gating is the structured `spec_env`, never a `VAR=…` prefix. |
|
|
23
23
|
| `regenerate_templates(target_dir, extra_patches_dir=None)` | Author the C# templates into `target_dir/csharp` and apply the shipped (then optional extra) patches. |
|
|
24
24
|
| `generate_client(spec, generator, output_dir, names, templates_dir=None, extra_references=None, npm_scope=None, license_spdx=None, repository_url=None)` | Run the generator for one `(spec, generator)` and sync the result into `output_dir`. `templates_dir` is required for `csharp`. For csharp, `npm_scope` composes the UPM identity `{scope}.{base-minus-dashes}` (e.g. `org.outernet.placeframe.apiclient`); unset falls back to the legacy `org.nuget.{camel-lower}` identity. `license_spdx` / `repository_url` populate the manifest's `license` / `repository` fields when given. |
|
|
25
25
|
| `write_unity_package_metadata(package_dir, package_name, extra_references=None, npm_name=None, license_spdx=None, repository_url=None)` | Write `package.json` / `.asmdef` / `csc.rsp` / `Directory.Build.props`. Called by `generate_client` for csharp. |
|
|
26
|
-
| `
|
|
26
|
+
| `ClientNaming` | The derived per-project package identity: `base` (last path segment + `-client`), `dashed` (`{root_name}-{base}`), `underscored`, `camel`. Constructed inline by the CLI command per project; `DefaultNamingPolicy("placeframe")("docker/api")` semantics live there. |
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
The CLI command orchestrates the whole run — `regenerate_templates` once, then per project: produce (via the config-built `SpecProducer`) → downgrade → committed-spec skip → per-client generate. The generator version pin and the shipped configs/patches/ignore-file live in `src/openapi_client_codegen/_data/` (paths in `_data.py`).
|
|
29
29
|
|
|
30
30
|
## Constraints
|
|
31
31
|
|
|
@@ -37,7 +37,7 @@ To author a new patch: author the raw templates (`openapi-generator-cli author t
|
|
|
37
37
|
|
|
38
38
|
### Generator env vars are passed per-call, never set at module level
|
|
39
39
|
|
|
40
|
-
`JAVA_OPTS=-Dlog.level=warn` (quiets the generator) is passed as `bash(..., env={"JAVA_OPTS": "-Dlog.level=warn"})` on the two `openapi-generator-cli` calls that need it, so it reaches only those child processes and never touches this process's `os.environ`. `bashrun`'s `env=` overlays the inherited environment for that one child (see its AGENTS.md), which is the whole point — the alternative, mutating the global `os.environ`, leaks the var into every later subprocess. This covers
|
|
40
|
+
`JAVA_OPTS=-Dlog.level=warn` (quiets the generator) is passed as `bash(..., env={"JAVA_OPTS": "-Dlog.level=warn"})` on the two `openapi-generator-cli` calls that need it, so it reaches only those child processes and never touches this process's `os.environ`. `bashrun`'s `env=` overlays the inherited environment for that one child (see its AGENTS.md), which is the whole point — the alternative, mutating the global `os.environ`, leaks the var into every later subprocess. This covers the generator this package runs itself; a consumer's spec command needing the same gating (e.g. `CODEGEN=1`) carries it in the config's structured `spec_env` key, which rides the same per-call overlay.
|
|
41
41
|
|
|
42
42
|
### The Unity reference set is coupled to the patched templates
|
|
43
43
|
|
|
@@ -51,7 +51,7 @@ The generated `.csproj` stays inside the UPM package because it is the `dotnet p
|
|
|
51
51
|
|
|
52
52
|
### What the consumer owns
|
|
53
53
|
|
|
54
|
-
The spec-production
|
|
54
|
+
The spec-production command (`spec_command` + optional `spec_env`, required — the package is consumable by projects that use no Python, no uv, or no dump entry point at all; a project whose spec is a committed file needs only `cat openapi.json`), the project→clients mapping, the naming root, the npm scope, license, repository URL, and the generated output root — all fields of the `ProjectsConfig` model, all carried in the consumer's config file, none overridable per invocation. Naming derivation is fixed inside the CLI command (`ClientNaming` built inline per project from the root name and the project's last path segment); a consumer needing different names calls the per-client API directly with its own `ClientNaming`.
|
|
55
55
|
|
|
56
56
|
## See also
|
|
57
57
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: openapi-client-codegen
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: OpenAPI client-generation wrapper: Litestar spec dump, 3.1->3.0 downgrade, patched openapi-generator templates, Unity package metadata
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
License-File: NOTICE
|
|
7
7
|
Requires-Python: >=3.13
|
|
8
8
|
Requires-Dist: bashrun>=0.1.0
|
|
9
|
+
Requires-Dist: pydantic>=2.11.7
|
|
9
10
|
Requires-Dist: typer>=0.17.4
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# openapi-client-codegen
|
|
2
|
+
|
|
3
|
+
Generate typed API clients from an OpenAPI schema. `openapi-client-codegen` wraps [OpenAPI Generator](https://openapi-generator.tech/): it downgrades OpenAPI 3.1→3.0 (the generator only accepts 3.0), authors and patches the C# templates, runs the generator, and writes Unity package metadata. It produces a Python (`httpx`) client and a Unity-consumable C# (`httpclient`) client.
|
|
4
|
+
|
|
5
|
+
The generic steps live here; the caller supplies the spec-production strategy (any callable from project directory to raw spec JSON — a shell command carried in the CLI config), the project→client mapping, the naming root, the npm scope, license, repository URL, and the output root. See [`AGENTS.md`](./AGENTS.md) for the API surface, the lib-owns/consumer-owns boundary, and the patch mechanism.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Python 3.13+ and [uv](https://docs.astral.sh/uv/)
|
|
10
|
+
- **Java (JDK 11+)** and **`uvx`** on PATH at runtime — the generator runs as `uvx --from 'openapi-generator-cli[jdk4py]==<pin>' ...`
|
|
11
|
+
|
|
12
|
+
## CLI
|
|
13
|
+
|
|
14
|
+
The CLI is the multi-project orchestrator, invoked directly (single command, no subcommand):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv run openapi-client-codegen --config clients.json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`clients.json` carries everything consumer-owned in one place:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"projects": {"services/api": ["python", "csharp"]},
|
|
25
|
+
"root_name": "myproject",
|
|
26
|
+
"generated_root": "generated",
|
|
27
|
+
"spec_command": "uv run --project . python -m src.dump_openapi",
|
|
28
|
+
"spec_env": {"CODEGEN": "1"},
|
|
29
|
+
"npm_scope": "org.example.myproject",
|
|
30
|
+
"license_spdx": "Apache-2.0",
|
|
31
|
+
"repository_url": "https://github.com/org/repo.git"
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`spec_command` is a command that prints the project's raw OpenAPI JSON to stdout, run with the project directory as cwd — no shell sits in front of it (commands are shlex-split; operators like `|` are rejected), so env gating rides the structured `spec_env` key, overlaid per call. The config is the single source of identity: `root_name`, `generated_root`, and `spec_command` are required there, and nothing overrides them per invocation — a different identity is a different config file. The remaining flags are invocation-scoped (`--project` / `--client` filters, `--no-cache`, `--root`). The orchestrator downgrades each produced spec, skips generation when the committed `<project>/openapi.json` is byte-identical (`--no-cache` forces through), and syncs each client under `<generated-root>/<generator>/<base-name>`.
|
|
36
|
+
|
|
37
|
+
To run it as a first-class command in a consumer repo, point a `[project.scripts]` entry at the same app — e.g. `generate-clients = "openapi_client_codegen.cli:app"`.
|
|
38
|
+
|
|
39
|
+
The same config drives the library API, for in-process consumption from a consumer's own command:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
|
|
44
|
+
from openapi_client_codegen.orchestrator import ProjectsConfig, generate_projects
|
|
45
|
+
|
|
46
|
+
config = ProjectsConfig.model_validate_json(Path("clients.json").read_text(encoding="utf-8"))
|
|
47
|
+
generate_projects(config)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
A non-uv project declares its own `spec_command` instead — e.g. `dotnet run -- dump-openapi`; a project whose spec is a committed file uses `cat openapi.json`.
|
|
51
|
+
|
|
52
|
+
## Consuming from another repo
|
|
53
|
+
|
|
54
|
+
Install from PyPI:
|
|
55
|
+
|
|
56
|
+
```toml
|
|
57
|
+
[project]
|
|
58
|
+
dependencies = ["openapi-client-codegen>=0.1.0"]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`bashrun` resolves transitively. At **runtime** the generator also needs Java (JDK 11+) and `uvx` on PATH. To test an unreleased change, pin the repo at a git ref in a scratch branch instead (`openapi-client-codegen = { git = "…", rev = "<sha>" }` under `[tool.uv.sources]`) and drop the pin when the release lands.
|
|
62
|
+
|
|
63
|
+
## Development
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uv run ruff check .
|
|
67
|
+
uv run ruff format --check .
|
|
68
|
+
uv run basedpyright
|
|
69
|
+
uv run pytest
|
|
70
|
+
```
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "openapi-client-codegen"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.7"
|
|
4
4
|
description = "OpenAPI client-generation wrapper: Litestar spec dump, 3.1->3.0 downgrade, patched openapi-generator templates, Unity package metadata"
|
|
5
5
|
requires-python = ">=3.13"
|
|
6
6
|
dependencies = [
|
|
7
7
|
"bashrun>=0.1.0",
|
|
8
|
+
"pydantic>=2.11.7",
|
|
8
9
|
"typer>=0.17.4",
|
|
9
10
|
]
|
|
10
11
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from tempfile import TemporaryDirectory
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
from typer import Option, Typer
|
|
7
|
+
|
|
8
|
+
from .client import generate_client
|
|
9
|
+
from .downgrade import downgrade_openapi_3_1_to_3_0
|
|
10
|
+
from .orchestrator import ClientNaming, ProjectsConfig, SpecProducer
|
|
11
|
+
from .templates import regenerate_templates
|
|
12
|
+
|
|
13
|
+
app = Typer(pretty_exceptions_show_locals=False)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command()
|
|
17
|
+
def generate_projects_command(
|
|
18
|
+
config: Annotated[
|
|
19
|
+
Path, Option(help="JSON config carrying the projects mapping, the client identity, and the spec command")
|
|
20
|
+
],
|
|
21
|
+
project: Annotated[str | None, Option(help="Generate only this project, as keyed in the config")] = None,
|
|
22
|
+
client: Annotated[str | None, Option(help="Generate only this client generator")] = None,
|
|
23
|
+
no_cache: Annotated[
|
|
24
|
+
bool, Option("--no-cache", help="Regenerate even when the committed spec is unchanged")
|
|
25
|
+
] = False,
|
|
26
|
+
root: Annotated[Path, Option(help="Repository root the project paths resolve against")] = Path(),
|
|
27
|
+
) -> None:
|
|
28
|
+
settings = ProjectsConfig.model_validate_json(config.read_text(encoding="utf-8"))
|
|
29
|
+
root = root.resolve()
|
|
30
|
+
dump_spec = SpecProducer(settings.spec_command, env=settings.spec_env)
|
|
31
|
+
|
|
32
|
+
with TemporaryDirectory() as templates_directory_string:
|
|
33
|
+
templates_directory = Path(templates_directory_string)
|
|
34
|
+
regenerate_templates(templates_directory)
|
|
35
|
+
|
|
36
|
+
for project_name, clients in settings.projects.items():
|
|
37
|
+
if project is not None and project != project_name:
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
project_directory = root / project_name
|
|
41
|
+
print(f"Producing OpenAPI spec for project: {project_directory}")
|
|
42
|
+
|
|
43
|
+
openapi_json = json.loads(dump_spec(project_directory))
|
|
44
|
+
downgrade_openapi_3_1_to_3_0(openapi_json)
|
|
45
|
+
openapi_spec = json.dumps(openapi_json, indent=2)
|
|
46
|
+
|
|
47
|
+
spec_path = project_directory / "openapi.json"
|
|
48
|
+
|
|
49
|
+
if spec_path.exists():
|
|
50
|
+
old_spec = spec_path.read_text(encoding="utf-8")
|
|
51
|
+
|
|
52
|
+
if openapi_spec == old_spec and not no_cache:
|
|
53
|
+
print("OpenAPI spec unchanged, skipping client generation")
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
spec_path.write_text(openapi_spec, encoding="utf-8")
|
|
57
|
+
|
|
58
|
+
base = f"{project_name.rsplit('/', maxsplit=1)[-1]}-client"
|
|
59
|
+
dashed = f"{settings.root_name}-{base}"
|
|
60
|
+
names = ClientNaming(
|
|
61
|
+
base=base,
|
|
62
|
+
dashed=dashed,
|
|
63
|
+
underscored=dashed.replace("-", "_"),
|
|
64
|
+
camel=f"{settings.root_name.capitalize()}{''.join(part.capitalize() for part in base.split('-'))}",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
for client_name in clients:
|
|
68
|
+
if client is not None and client_name != client:
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
generate_client(
|
|
72
|
+
openapi_spec,
|
|
73
|
+
client_name,
|
|
74
|
+
settings.generated_root / client_name / names.base,
|
|
75
|
+
names,
|
|
76
|
+
templates_dir=templates_directory,
|
|
77
|
+
npm_scope=settings.npm_scope,
|
|
78
|
+
license_spdx=settings.license_spdx,
|
|
79
|
+
repository_url=settings.repository_url,
|
|
80
|
+
)
|
{openapi_client_codegen-0.1.5 → openapi_client_codegen-0.1.7}/src/openapi_client_codegen/client.py
RENAMED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import json
|
|
4
|
+
import sys
|
|
2
5
|
from os import walk
|
|
3
6
|
from pathlib import Path
|
|
4
7
|
from shutil import copytree
|
|
5
8
|
from tempfile import NamedTemporaryFile, TemporaryDirectory
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
6
10
|
|
|
7
11
|
from bashrun.bash import bash
|
|
8
12
|
|
|
9
13
|
from .resources import CONFIGS_DIR, IGNORE_FILE, OPENAPI_GENERATOR_CLI_VERSION
|
|
10
|
-
from .naming import ClientNaming
|
|
11
14
|
from .unity import write_unity_package_metadata
|
|
12
15
|
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from .orchestrator import ClientNaming
|
|
18
|
+
|
|
13
19
|
_HTTP_METHODS = ("get", "put", "post", "delete", "patch", "options", "head", "trace")
|
|
14
20
|
|
|
15
21
|
|
|
@@ -112,4 +118,4 @@ def generate_client(
|
|
|
112
118
|
copytree(temporary_directory, output_dir, dirs_exist_ok=True)
|
|
113
119
|
|
|
114
120
|
if generator == "python":
|
|
115
|
-
bash(f"uv pip install {output_dir.resolve().as_posix()}")
|
|
121
|
+
bash(f"uv pip install --python {sys.executable} {output_dir.resolve().as_posix()}")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from bashrun.bash import bash_output
|
|
7
|
+
from pydantic import BaseModel, ConfigDict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ProjectsConfig(BaseModel):
|
|
11
|
+
model_config = ConfigDict(extra="forbid")
|
|
12
|
+
|
|
13
|
+
projects: dict[str, list[str]]
|
|
14
|
+
root_name: str
|
|
15
|
+
generated_root: Path
|
|
16
|
+
spec_command: str
|
|
17
|
+
spec_env: dict[str, str] | None = None
|
|
18
|
+
npm_scope: str | None = None
|
|
19
|
+
license_spdx: str | None = None
|
|
20
|
+
repository_url: str | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class ClientNaming:
|
|
25
|
+
base: str
|
|
26
|
+
dashed: str
|
|
27
|
+
underscored: str
|
|
28
|
+
camel: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SpecProducer:
|
|
32
|
+
def __init__(self, command: str, env: dict[str, str] | None = None) -> None:
|
|
33
|
+
self.command = command
|
|
34
|
+
self.env = env
|
|
35
|
+
|
|
36
|
+
def __call__(self, project: Path) -> str:
|
|
37
|
+
return bash_output(self.command, cwd=project, env=self.env)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from typer.testing import CliRunner
|
|
6
|
+
|
|
7
|
+
from openapi_client_codegen import cli, orchestrator
|
|
8
|
+
from openapi_client_codegen.cli import app
|
|
9
|
+
from openapi_client_codegen.orchestrator import ClientNaming
|
|
10
|
+
|
|
11
|
+
runner = CliRunner()
|
|
12
|
+
|
|
13
|
+
FULL_SETTINGS: dict[str, object] = {
|
|
14
|
+
"projects": {"docker/api": ["python", "csharp"]},
|
|
15
|
+
"root_name": "placeframe",
|
|
16
|
+
"generated_root": "packages/generated",
|
|
17
|
+
"spec_command": "uv run --project . python -m src.dump_openapi",
|
|
18
|
+
"spec_env": {"CODEGEN": "1"},
|
|
19
|
+
"npm_scope": "org.example.placeframe",
|
|
20
|
+
"license_spdx": "Apache-2.0",
|
|
21
|
+
"repository_url": "https://github.com/org/repo.git",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Recorder:
|
|
26
|
+
def __init__(self) -> None:
|
|
27
|
+
self.generated: list[tuple[str, Path]] = []
|
|
28
|
+
|
|
29
|
+
def fake_bash_output(self, command: str, *, cwd: Path | None = None, env: dict[str, str] | None = None) -> str:
|
|
30
|
+
return json.dumps({"openapi": "3.1.0", "info": {"title": "demo", "version": "0.0.0"}, "paths": {}})
|
|
31
|
+
|
|
32
|
+
def fake_regenerate_templates(self, target_dir: Path, extra_patches_dir: Path | None = None) -> None:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
def fake_generate_client(
|
|
36
|
+
self,
|
|
37
|
+
spec: str,
|
|
38
|
+
generator: str,
|
|
39
|
+
output_dir: Path,
|
|
40
|
+
names: ClientNaming,
|
|
41
|
+
templates_dir: Path | None = None,
|
|
42
|
+
extra_references: list[str] | None = None,
|
|
43
|
+
npm_scope: str | None = None,
|
|
44
|
+
license_spdx: str | None = None,
|
|
45
|
+
repository_url: str | None = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
self.generated.append((generator, output_dir))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.fixture
|
|
51
|
+
def recorder(monkeypatch: pytest.MonkeyPatch) -> Recorder:
|
|
52
|
+
recorder = Recorder()
|
|
53
|
+
monkeypatch.setattr(orchestrator, "bash_output", recorder.fake_bash_output)
|
|
54
|
+
monkeypatch.setattr(cli, "regenerate_templates", recorder.fake_regenerate_templates)
|
|
55
|
+
monkeypatch.setattr(cli, "generate_client", recorder.fake_generate_client)
|
|
56
|
+
return recorder
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def write_config(directory: Path, settings: dict[str, object]) -> Path:
|
|
60
|
+
config = directory / "clients.json"
|
|
61
|
+
config.write_text(json.dumps(settings), encoding="utf-8")
|
|
62
|
+
return config
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_filter_flags_restrict_generation(tmp_path: Path, recorder: Recorder) -> None:
|
|
66
|
+
config = write_config(tmp_path, FULL_SETTINGS)
|
|
67
|
+
(tmp_path / "docker" / "api").mkdir(parents=True)
|
|
68
|
+
|
|
69
|
+
result = runner.invoke(
|
|
70
|
+
app,
|
|
71
|
+
["--config", str(config), "--root", str(tmp_path), "--project", "docker/api", "--client", "csharp"],
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
assert result.exit_code == 0
|
|
75
|
+
assert recorder.generated == [("csharp", Path("packages/generated") / "csharp" / "api-client")]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_unknown_config_key_fails_loudly(tmp_path: Path, recorder: Recorder) -> None:
|
|
79
|
+
config = write_config(tmp_path, {"projects": {}, "rot_name": "placeframe"})
|
|
80
|
+
|
|
81
|
+
result = runner.invoke(app, ["--config", str(config)])
|
|
82
|
+
|
|
83
|
+
assert result.exit_code != 0
|
|
84
|
+
assert "rot_name" in str(result.exception)
|
|
85
|
+
assert recorder.generated == []
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_missing_projects_mapping_fails(tmp_path: Path, recorder: Recorder) -> None:
|
|
89
|
+
config = write_config(tmp_path, {"root_name": "placeframe"})
|
|
90
|
+
|
|
91
|
+
result = runner.invoke(app, ["--config", str(config)])
|
|
92
|
+
|
|
93
|
+
assert result.exit_code != 0
|
|
94
|
+
assert "projects" in str(result.exception)
|
|
95
|
+
assert recorder.generated == []
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_missing_required_field_fails(tmp_path: Path, recorder: Recorder) -> None:
|
|
99
|
+
settings = dict(FULL_SETTINGS)
|
|
100
|
+
del settings["spec_command"]
|
|
101
|
+
config = write_config(tmp_path, settings)
|
|
102
|
+
|
|
103
|
+
result = runner.invoke(app, ["--config", str(config)])
|
|
104
|
+
|
|
105
|
+
assert result.exit_code != 0
|
|
106
|
+
assert "spec_command" in str(result.exception)
|
|
107
|
+
assert recorder.generated == []
|