openapi-client-codegen 0.1.0__tar.gz → 0.1.2__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.2/.gitattributes +1 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/.github/workflows/ci.yml +4 -1
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/AGENTS.md +8 -5
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/PKG-INFO +1 -1
- openapi_client_codegen-0.1.2/README.md +74 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/pyproject.toml +1 -1
- openapi_client_codegen-0.1.2/ruff.base.toml +204 -0
- openapi_client_codegen-0.1.2/ruff.toml +1 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/cli.py +38 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/client.py +1 -1
- openapi_client_codegen-0.1.2/src/openapi_client_codegen/orchestrator.py +95 -0
- openapi_client_codegen-0.1.2/src/openapi_client_codegen/py.typed +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/templates.py +1 -1
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/tests/test_downgrade.py +1 -1
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/tests/test_naming.py +1 -1
- openapi_client_codegen-0.1.2/tests/test_orchestrate.py +157 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/tests/test_unity.py +1 -1
- openapi_client_codegen-0.1.0/README.md +0 -73
- openapi_client_codegen-0.1.0/ruff.toml +0 -71
- openapi_client_codegen-0.1.0/src/openapi_client_codegen/__init__.py +0 -15
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/.gitignore +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/.python-version +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/CLAUDE.md +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/LICENSE +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/NOTICE +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/publish-config.json +0 -0
- /openapi_client_codegen-0.1.0/src/openapi_client_codegen/py.typed → /openapi_client_codegen-0.1.2/src/openapi_client_codegen/__init__.py +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/_data/configs/csharp.json +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/_data/configs/python.json +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/_data/openapi-generator-ignore +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/_data/templates-patches/csharp/0001-csharp-remove-errant-comma.patch +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/_data/templates-patches/csharp/0002-csharp-models-required-only-constructors.patch +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/_data/templates-patches/csharp/0003-csharp-httpclient-use-defaultT.patch +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/_data/templates-patches/csharp/0004-csharp-support-hybrid-multipart-payloads.patch +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/_data/templates-patches/csharp/0006-csharp-streaming-download-response.patch +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/_data/templates-patches/csharp/0007-csharp-model-default-initializers.patch +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/_data/templates-patches/csharp/0008-csharp-redirect-documentation-file.patch +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/downgrade.py +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/naming.py +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/resources.py +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/unity.py +0 -0
- {openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/uv.lock +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruff.base.toml -text
|
|
@@ -32,6 +32,9 @@ jobs:
|
|
|
32
32
|
- name: Test
|
|
33
33
|
run: uv run pytest
|
|
34
34
|
|
|
35
|
+
- name: Ruff config drift
|
|
36
|
+
run: uvx --from python-devkit==0.1.3 sync-ruff --check
|
|
37
|
+
|
|
35
38
|
publish:
|
|
36
39
|
needs: check
|
|
37
40
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
@@ -48,4 +51,4 @@ jobs:
|
|
|
48
51
|
- uses: astral-sh/setup-uv@v7
|
|
49
52
|
|
|
50
53
|
- name: Publish
|
|
51
|
-
run: uvx --from
|
|
54
|
+
run: uvx --from release-kit==0.1.0 --with bashrun==0.1.0 publish-packages --config publish-config.json
|
|
@@ -2,7 +2,7 @@
|
|
|
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: downgrading 3.1→3.0, 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** (injected as a required strategy — any callable from project directory to raw spec JSON; the org's uv convention ships as `dump_openapi_spec`), the project→client mapping, the naming root, the npm scope, license, repository URL, and the generated output root.
|
|
6
6
|
|
|
7
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), both 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
|
|
|
@@ -12,17 +12,20 @@ Publishing rides `ci.yml`'s `publish` job on every push to `main` (gated on the
|
|
|
12
12
|
|
|
13
13
|
## Public API
|
|
14
14
|
|
|
15
|
-
|
|
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`), `downgrade` (`downgrade_openapi_3_1_to_3_0`, `JsonDict`), `naming` (`ClientNaming`, `DefaultNamingPolicy`, `NamingPolicy`), `orchestrator` (`generate_projects`, `dump_openapi_spec`, `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
|
+
| `generate_projects(projects, root_name, generated_root, dump_spec, npm_scope=None, license_spdx=None, repository_url=None, project=None, client=None, no_cache=False, root=Path())` | The project orchestrator: per mapping entry (`{project path: [generators]}`), produce the spec via the injected `dump_spec` strategy, 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>`. `project` / `client` restrict the run to one entry; `root` is what project paths resolve against. |
|
|
21
|
+
| `SpecProducer(command)` | Callable class wrapping any shell command that prints the spec to stdout, run with the project directory as cwd; the CLI `--spec-command` bridge. The strategy contract itself is untyped vocabulary — any `Callable[[Path], str]` (project directory in, raw OpenAPI JSON out) satisfies `dump_spec`. |
|
|
22
|
+
| `dump_openapi_spec(project)` | The org strategy: `uv run --project . python -m src.dump_openapi` with `CODEGEN=1` gating heavy imports (per-call env overlay). Shipped as the exemplar — `dump_spec` is required, so every consumer names its strategy explicitly. |
|
|
20
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. |
|
|
21
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. |
|
|
22
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. |
|
|
23
26
|
| `DefaultNamingPolicy(root_name)` / `ClientNaming` / `NamingPolicy` | Package-name derivation. `DefaultNamingPolicy("placeframe")("docker/api")` → base `api-client`, dashed `placeframe-api-client`, underscored `placeframe_api_client`, camel `PlaceframeApiClient`. |
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
`generate_projects` orchestrates the whole run — `regenerate_templates` once, then per project: produce (via the injected strategy) → downgrade → committed-spec skip → per-client generate. Consumers hand it their parsed mapping, their spec-production strategy, and their identity parameters. The generator version pin and the shipped configs/patches/ignore-file live in `src/openapi_client_codegen/_data/` (paths in `_data.py`).
|
|
26
29
|
|
|
27
30
|
## Constraints
|
|
28
31
|
|
|
@@ -34,7 +37,7 @@ To author a new patch: author the raw templates (`openapi-generator-cli author t
|
|
|
34
37
|
|
|
35
38
|
### Generator env vars are passed per-call, never set at module level
|
|
36
39
|
|
|
37
|
-
`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 only the generator this package runs itself
|
|
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 only the generator this package runs itself. The shipped `dump_openapi_spec` strategy follows the same rule: `CODEGEN=1` (which gates a service's heavy imports so the app imports cleanly for the dump) rides bashrun's per-call env overlay and never touches this process's `os.environ`.
|
|
38
41
|
|
|
39
42
|
### The Unity reference set is coupled to the patched templates
|
|
40
43
|
|
|
@@ -48,7 +51,7 @@ The generated `.csproj` stays inside the UPM package because it is the `dotnet p
|
|
|
48
51
|
|
|
49
52
|
### What the consumer owns
|
|
50
53
|
|
|
51
|
-
The
|
|
54
|
+
The spec-production strategy (`dump_spec`, required — the package is consumable by projects that use no Python, no uv, or no dump entry point at all; the org's `dump_openapi_spec` ships beside it, and a project whose spec is a committed file needs only a file read), the project→clients mapping (in whatever config shape it likes — the verb takes the parsed mapping, not a file path), the naming root, the npm scope, license, repository URL, and the generated output root. The orchestrator iterates the mapping itself and fixes `DefaultNamingPolicy`; a consumer needing a different policy calls the per-client API directly — `NamingPolicy` is a `Protocol`, any callable `(project: str) -> ClientNaming` works there.
|
|
52
55
|
|
|
53
56
|
## See also
|
|
54
57
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: openapi-client-codegen
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
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
|
|
@@ -0,0 +1,74 @@
|
|
|
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 — the org's uv convention ships as `dump_openapi_spec`), 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
|
+
A thin CLI exposes the OpenAPI 3.1→3.0 downgrade, single-client generation, and the multi-project orchestrator:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv run openapi-client-codegen downgrade path/to/openapi.json # 3.1→3.0 in place
|
|
18
|
+
uv run openapi-client-codegen downgrade path/to/openapi.json out.json # write elsewhere
|
|
19
|
+
|
|
20
|
+
uv run openapi-client-codegen generate path/to/openapi.json generated/csharp/api-client \
|
|
21
|
+
--root-name myproject --project services/api \
|
|
22
|
+
--npm-scope org.example.myproject --license-spdx Apache-2.0 \
|
|
23
|
+
--repository-url https://github.com/org/repo.git
|
|
24
|
+
|
|
25
|
+
uv run openapi-client-codegen generate-projects clients.json \
|
|
26
|
+
--spec-command 'uv run --project . python -m src.dump_openapi' \
|
|
27
|
+
--root-name myproject --generated-root generated \
|
|
28
|
+
--npm-scope org.example.myproject --license-spdx Apache-2.0 \
|
|
29
|
+
--repository-url https://github.com/org/repo.git
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`clients.json` maps each project path to its client generators (`{"services/api": ["python", "csharp"]}`). `--spec-command` names the strategy: a shell command that prints the project's raw OpenAPI JSON to stdout, run with the project directory as cwd (env prefixes like `CODEGEN=1 …` inline directly). 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>`.
|
|
33
|
+
|
|
34
|
+
The same surface is the library API, for in-process consumption from a consumer's own command — `dump_spec` is required, so every consumer names its strategy explicitly:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import json
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
|
|
40
|
+
from openapi_client_codegen.orchestrator import dump_openapi_spec, generate_projects
|
|
41
|
+
|
|
42
|
+
projects = json.loads(Path("clients.json").read_text(encoding="utf-8"))
|
|
43
|
+
generate_projects(
|
|
44
|
+
projects,
|
|
45
|
+
root_name="myproject",
|
|
46
|
+
generated_root=Path("generated"),
|
|
47
|
+
dump_spec=dump_openapi_spec,
|
|
48
|
+
npm_scope="org.example.myproject",
|
|
49
|
+
license_spdx="Apache-2.0",
|
|
50
|
+
repository_url="https://github.com/org/repo.git",
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
A non-uv project passes its own callable instead — e.g. `dump_spec=lambda project: bash_output("dotnet run -- dump-openapi", cwd=project)`, or a file read for a project whose spec is committed rather than dumped.
|
|
55
|
+
|
|
56
|
+
## Consuming from another repo
|
|
57
|
+
|
|
58
|
+
Install from PyPI:
|
|
59
|
+
|
|
60
|
+
```toml
|
|
61
|
+
[project]
|
|
62
|
+
dependencies = ["openapi-client-codegen>=0.1.0"]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`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.
|
|
66
|
+
|
|
67
|
+
## Development
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv run ruff check .
|
|
71
|
+
uv run ruff format --check .
|
|
72
|
+
uv run basedpyright
|
|
73
|
+
uv run pytest
|
|
74
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "openapi-client-codegen"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
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 = [
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Org-canonical ruff configuration, hosted in python-devkit.
|
|
2
|
+
# Consuming repos carry this file verbatim as ruff.base.toml, written by the
|
|
3
|
+
# sync-ruff verb (uvx --from python-devkit sync-ruff) and drift-gated in CI.
|
|
4
|
+
# Never edit a synced copy: change this canonical, release python-devkit, and
|
|
5
|
+
# re-run sync-ruff in each consuming repo.
|
|
6
|
+
target-version = "py313"
|
|
7
|
+
line-length = 120
|
|
8
|
+
preview = true
|
|
9
|
+
required-version = ">=0.14.11"
|
|
10
|
+
|
|
11
|
+
[lint]
|
|
12
|
+
select = ["ALL"]
|
|
13
|
+
ignore = [
|
|
14
|
+
# ── Temporarily ignored (inline noqas exist for these rules but are redundant
|
|
15
|
+
# while the rules are globally ignored — RUF100 suppresses the "unused noqa" warning
|
|
16
|
+
# until we un-ignore each ticket's rules)
|
|
17
|
+
"RUF100",
|
|
18
|
+
|
|
19
|
+
# ── Permanently ignored ──────────────────────────────────────────────
|
|
20
|
+
# Type annotations — basedpyright strict mode handles this
|
|
21
|
+
"ANN",
|
|
22
|
+
# EM101 (string literal in exception) — the msg = ... indirection adds verbosity without benefit
|
|
23
|
+
"EM101",
|
|
24
|
+
# TRY003 (raise vanilla args) — requires custom exception class for every raise site;
|
|
25
|
+
# the real problem (interpolation) is caught by EM102/EM103
|
|
26
|
+
"TRY003",
|
|
27
|
+
# A003 (class attribute shadowing) — class attributes via self.x never conflict with builtins
|
|
28
|
+
"A003",
|
|
29
|
+
# Function complexity counting — C901 already covers structural complexity;
|
|
30
|
+
# these counting rules are largely redundant
|
|
31
|
+
"PLR0911",
|
|
32
|
+
"PLR0912",
|
|
33
|
+
"PLR0913",
|
|
34
|
+
"PLR0914",
|
|
35
|
+
"PLR0915",
|
|
36
|
+
"PLR0917",
|
|
37
|
+
|
|
38
|
+
# ── Temporarily ignored (will enable when conventions are established) ─
|
|
39
|
+
# Docstrings — project policy is "no docstrings" for now
|
|
40
|
+
"D",
|
|
41
|
+
# Copyright notices — not yet established
|
|
42
|
+
"CPY",
|
|
43
|
+
# Docstring formatting — blocked on D
|
|
44
|
+
"DOC",
|
|
45
|
+
# TODO/FIXME comment formatting
|
|
46
|
+
"TD",
|
|
47
|
+
"FIX",
|
|
48
|
+
|
|
49
|
+
# The "Ticket N" blocks below are the transitional cleanup slices tracked
|
|
50
|
+
# by PLE-248 (Enforce the deferred ruff rules):
|
|
51
|
+
# https://linear.app/plerionplatforms/issue/PLE-248
|
|
52
|
+
# Ticket 3 (T201, print → logging) is tracked by PLE-336:
|
|
53
|
+
# https://linear.app/plerionplatforms/issue/PLE-336
|
|
54
|
+
# Lifting a rule = remove it here in python-devkit's canonical config,
|
|
55
|
+
# release, and run sync-ruff in every consuming repo.
|
|
56
|
+
|
|
57
|
+
# ── Ticket 1: Auto-fix formatting & type modernization ───────────────
|
|
58
|
+
# Zero risk. Purely mechanical, all auto-fixable via ruff --fix.
|
|
59
|
+
"COM812", # missing trailing commas
|
|
60
|
+
"I001", # import sorting
|
|
61
|
+
"ISC003", # explicit string concatenation
|
|
62
|
+
"RUF031", # parenthesized tuple in subscript
|
|
63
|
+
"TC001", # move import into TYPE_CHECKING block
|
|
64
|
+
"TC002", # move third-party import into TYPE_CHECKING block
|
|
65
|
+
"TC003", # move stdlib import into TYPE_CHECKING block
|
|
66
|
+
"TC006", # move typing import into TYPE_CHECKING block (PEP 695)
|
|
67
|
+
"TC008", # move import into TYPE_CHECKING block (quoted annotation)
|
|
68
|
+
"UP006", # use X instead of typing.X
|
|
69
|
+
"UP007", # use X | Y instead of Union
|
|
70
|
+
"UP011", # unnecessary parentheses to functools.lru_cache
|
|
71
|
+
"UP015", # unnecessary open mode parameters
|
|
72
|
+
"UP017", # use datetime.UTC alias
|
|
73
|
+
"UP034", # extraneous parentheses
|
|
74
|
+
"UP035", # deprecated import (use collections.abc, etc.)
|
|
75
|
+
"UP040", # use PEP 695 type alias
|
|
76
|
+
"UP042", # use PEP 695 type parameters
|
|
77
|
+
"UP045", # use X | None instead of Optional
|
|
78
|
+
|
|
79
|
+
# ── Ticket 2: Auto-fix code simplifications ──────────────────────────
|
|
80
|
+
# Very low risk. Auto-fixable with trivial semantic changes.
|
|
81
|
+
"C401", # unnecessary generator — use set/dict comprehension
|
|
82
|
+
"C416", # unnecessary comprehension — use list/dict
|
|
83
|
+
"ERA001", # commented-out code
|
|
84
|
+
"FURB101", # read-text can replace open+read
|
|
85
|
+
"FURB103", # write-text can replace open+write
|
|
86
|
+
"FURB110", # use ternary instead of try/except for default
|
|
87
|
+
"FURB113", # use list.extend instead of repeated append
|
|
88
|
+
"FURB118", # use operator module instead of lambda
|
|
89
|
+
"LOG004", # use __name__ as logger name
|
|
90
|
+
"PERF401", # use list comprehension instead of manual append
|
|
91
|
+
"PIE810", # unnecessary spread of single-element starred
|
|
92
|
+
"PLC0207", # use a single __all__ definition
|
|
93
|
+
"PLC1901", # compare to empty string using falsy check
|
|
94
|
+
"PLR1711", # useless return at end of function
|
|
95
|
+
"PLR6104", # use augmented assignment
|
|
96
|
+
"PLR6201", # use a set literal for membership test
|
|
97
|
+
"PLW1510", # subprocess.run without explicit check= argument
|
|
98
|
+
"PLW1514", # open without explicit encoding
|
|
99
|
+
"PT001", # use @pytest.fixture over @pytest.fixture()
|
|
100
|
+
"PT018", # composite assertion — split into multiple asserts
|
|
101
|
+
"PTH101", # os.chmod → Path.chmod
|
|
102
|
+
"PTH111", # os.path.expanduser → Path.expanduser
|
|
103
|
+
"PTH112", # os.path.isdir → Path.is_dir
|
|
104
|
+
"PTH118", # os.path.join → Path /
|
|
105
|
+
"PTH123", # open() → Path.open()
|
|
106
|
+
"PTH201", # pathlib.Path(".")
|
|
107
|
+
"RET501", # unnecessary explicit return None
|
|
108
|
+
"RET504", # unnecessary assignment before return
|
|
109
|
+
"RET505", # unnecessary else after return
|
|
110
|
+
"RET506", # unnecessary else after raise
|
|
111
|
+
"RET507", # unnecessary else after continue
|
|
112
|
+
"RSE102", # unnecessary parentheses on raised exception
|
|
113
|
+
"RUF005", # consider spread operator instead of concatenation
|
|
114
|
+
"RUF010", # use explicit conversion flag in f-string
|
|
115
|
+
"RUF015", # prefer next() over single-element slice
|
|
116
|
+
"RUF029", # function is declared async but has no await
|
|
117
|
+
"RUF052", # local variable is assigned but never used (underscore prefix)
|
|
118
|
+
"RUF056", # unnecessary default value for keyword argument
|
|
119
|
+
"SIM102", # collapsible if statements
|
|
120
|
+
"SIM105", # use contextlib.suppress instead of try/except/pass
|
|
121
|
+
"SIM115", # use context manager for opening files
|
|
122
|
+
"SIM117", # use single with statement for multiple context managers
|
|
123
|
+
"SIM118", # use key in dict instead of key in dict.keys()
|
|
124
|
+
"TID252", # prefer absolute imports over relative
|
|
125
|
+
|
|
126
|
+
# ── Ticket 3: print → logging ────────────────────────────────────────
|
|
127
|
+
# All code should use logging, including CLI scripts.
|
|
128
|
+
"T201", # print found
|
|
129
|
+
|
|
130
|
+
# ── Ticket 4: Exception message interpolation ────────────────────────
|
|
131
|
+
# Remove f-strings from exception messages — use string literals.
|
|
132
|
+
"EM102", # f-string in exception message
|
|
133
|
+
|
|
134
|
+
# ── Ticket 5: Missing __init__.py + shebangs ─────────────────────────
|
|
135
|
+
# Add __init__.py to installable packages; remove vestigial shebangs.
|
|
136
|
+
"INP001", # implicit namespace package — add __init__.py
|
|
137
|
+
"EXE001", # shebang present but file not executable
|
|
138
|
+
|
|
139
|
+
# ── Ticket 6: Type ignore specificity ────────────────────────────────
|
|
140
|
+
# Replace blanket type: ignore with specific error codes.
|
|
141
|
+
"PGH003", # blanket type: ignore
|
|
142
|
+
|
|
143
|
+
# ── Ticket 7: Boolean trap refactoring ───────────────────────────────
|
|
144
|
+
# Change function signatures — callers must update too.
|
|
145
|
+
"FBT001", # boolean positional argument
|
|
146
|
+
"FBT002", # boolean default value in function definition
|
|
147
|
+
"FBT003", # boolean positional value in call
|
|
148
|
+
|
|
149
|
+
# ── Ticket 8: Exception handling patterns ────────────────────────────
|
|
150
|
+
# Behavioral changes to error handling.
|
|
151
|
+
"B020", # loop variable overwritten by except
|
|
152
|
+
"B903", # generator should use yield from
|
|
153
|
+
"B904", # raise without from inside except
|
|
154
|
+
"B905", # zip() without strict= parameter
|
|
155
|
+
"BLE001", # blind except Exception
|
|
156
|
+
"S101", # assert outside tests
|
|
157
|
+
"TRY002", # create custom exception class
|
|
158
|
+
"TRY300", # try/except/return — move return into else
|
|
159
|
+
|
|
160
|
+
# ── Ticket 9: Complexity, naming & refactoring ───────────────────────
|
|
161
|
+
# Structural changes requiring judgment.
|
|
162
|
+
"A001", # builtin shadowing (dir, bytes)
|
|
163
|
+
"ARG001", # unused function argument
|
|
164
|
+
"ARG002", # unused method argument
|
|
165
|
+
"C901", # function too complex
|
|
166
|
+
"E501", # line too long
|
|
167
|
+
"N802", # function name should be lowercase
|
|
168
|
+
"N806", # variable in function should be lowercase
|
|
169
|
+
"N815", # mixed-case variable in class scope
|
|
170
|
+
"N818", # exception name should end in Error
|
|
171
|
+
"PLC0415", # import not at top level
|
|
172
|
+
"PLE0704", # bare raise not inside except handler
|
|
173
|
+
"PLR1702", # too many nested blocks
|
|
174
|
+
"PLR2004", # magic value used in comparison
|
|
175
|
+
"PLR6301", # method could be function or static method
|
|
176
|
+
"PLW0603", # global statement
|
|
177
|
+
"PLW2901", # loop variable overwritten
|
|
178
|
+
"RUF001", # ambiguous unicode — replace with ASCII
|
|
179
|
+
"RUF003", # ambiguous unicode in comment
|
|
180
|
+
"S106", # possible hardcoded password
|
|
181
|
+
"S108", # probable insecure temp file/dir usage
|
|
182
|
+
"S110", # try/except/pass on broad exception
|
|
183
|
+
"S202", # use of tarfile.extractall
|
|
184
|
+
# S404/S603: prerequisite — bash.py needs env, stderr=STDOUT merge, and no-raise-with-capture
|
|
185
|
+
# before context_sha.py, deptry_check.py, test_context_sha.py, list_debug_targets.py can migrate
|
|
186
|
+
"S404", # suspicious subprocess import
|
|
187
|
+
"S603", # subprocess call with non-literal
|
|
188
|
+
"S606", # starting process without shell
|
|
189
|
+
"S607", # starting process with partial path
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
[lint.per-file-ignores]
|
|
193
|
+
# Tests use assert as the standard pytest mechanism; fixtures take arguments
|
|
194
|
+
# that pytest consumes implicitly; pytest-style test classes need no self use
|
|
195
|
+
"**/tests/**" = ["S101", "ARG001", "ARG002", "PLR6301"]
|
|
196
|
+
|
|
197
|
+
[lint.ruff]
|
|
198
|
+
# RUF067 in strict mode: __init__.py files must be empty — no re-exports, no
|
|
199
|
+
# side effects. Consumers import submodule paths (from pkg.mod import thing);
|
|
200
|
+
# the package layout is the public surface.
|
|
201
|
+
strictly-empty-init-modules = true
|
|
202
|
+
|
|
203
|
+
[lint.flake8-builtins]
|
|
204
|
+
ignorelist = ["id", "map"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
extend = "ruff.base.toml"
|
{openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/cli.py
RENAMED
|
@@ -8,6 +8,7 @@ from typer import Argument, Option, Typer
|
|
|
8
8
|
from .client import generate_client
|
|
9
9
|
from .downgrade import downgrade_openapi_3_1_to_3_0
|
|
10
10
|
from .naming import DefaultNamingPolicy
|
|
11
|
+
from .orchestrator import SpecProducer, generate_projects
|
|
11
12
|
from .templates import regenerate_templates
|
|
12
13
|
|
|
13
14
|
app = Typer(pretty_exceptions_show_locals=False)
|
|
@@ -51,3 +52,40 @@ def generate(
|
|
|
51
52
|
license_spdx=license_spdx,
|
|
52
53
|
repository_url=repository_url,
|
|
53
54
|
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.command("generate-projects")
|
|
58
|
+
def generate_projects_command(
|
|
59
|
+
config: Annotated[Path, Option(help="JSON file mapping project paths to client generator lists")],
|
|
60
|
+
root_name: Annotated[str, Option(help="Root name handed to DefaultNamingPolicy")],
|
|
61
|
+
generated_root: Annotated[Path, Option(help="Root directory the generated clients sync into")],
|
|
62
|
+
spec_command: Annotated[
|
|
63
|
+
str,
|
|
64
|
+
Option(
|
|
65
|
+
help="Shell command printing the project's raw OpenAPI JSON to stdout; runs with the project directory as cwd"
|
|
66
|
+
),
|
|
67
|
+
],
|
|
68
|
+
npm_scope: Annotated[str | None, Option(help="npm scope composing the UPM package identity")] = None,
|
|
69
|
+
license_spdx: Annotated[str | None, Option(help="SPDX license id written to package.json")] = None,
|
|
70
|
+
repository_url: Annotated[str | None, Option(help="git repository URL written to package.json")] = None,
|
|
71
|
+
project: Annotated[str | None, Option(help="Generate only this project, as keyed in the config")] = None,
|
|
72
|
+
client: Annotated[str | None, Option(help="Generate only this client generator")] = None,
|
|
73
|
+
no_cache: Annotated[
|
|
74
|
+
bool, Option("--no-cache", help="Regenerate even when the committed spec is unchanged")
|
|
75
|
+
] = False,
|
|
76
|
+
root: Annotated[Path, Option(help="Repository root the project paths resolve against")] = Path(),
|
|
77
|
+
) -> None:
|
|
78
|
+
projects: dict[str, list[str]] = json.loads(config.read_text(encoding="utf-8"))
|
|
79
|
+
generate_projects(
|
|
80
|
+
projects,
|
|
81
|
+
root_name=root_name,
|
|
82
|
+
generated_root=generated_root,
|
|
83
|
+
dump_spec=SpecProducer(spec_command),
|
|
84
|
+
npm_scope=npm_scope,
|
|
85
|
+
license_spdx=license_spdx,
|
|
86
|
+
repository_url=repository_url,
|
|
87
|
+
project=project,
|
|
88
|
+
client=client,
|
|
89
|
+
no_cache=no_cache,
|
|
90
|
+
root=root,
|
|
91
|
+
)
|
{openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/client.py
RENAMED
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
from shutil import copytree
|
|
5
5
|
from tempfile import NamedTemporaryFile, TemporaryDirectory
|
|
6
6
|
|
|
7
|
-
from bashrun import bash
|
|
7
|
+
from bashrun.bash import bash
|
|
8
8
|
|
|
9
9
|
from .resources import CONFIGS_DIR, IGNORE_FILE, OPENAPI_GENERATOR_CLI_VERSION
|
|
10
10
|
from .naming import ClientNaming
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from tempfile import TemporaryDirectory
|
|
7
|
+
|
|
8
|
+
from bashrun.bash import bash_output
|
|
9
|
+
|
|
10
|
+
from .client import generate_client
|
|
11
|
+
from .downgrade import downgrade_openapi_3_1_to_3_0
|
|
12
|
+
from .naming import DefaultNamingPolicy
|
|
13
|
+
from .templates import regenerate_templates
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SpecProducer:
|
|
17
|
+
def __init__(self, command: str) -> None:
|
|
18
|
+
self.command = command
|
|
19
|
+
|
|
20
|
+
def __call__(self, project: Path) -> str:
|
|
21
|
+
return bash_output(self.command, cwd=project)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def generate_projects(
|
|
25
|
+
projects: dict[str, list[str]],
|
|
26
|
+
root_name: str,
|
|
27
|
+
generated_root: Path,
|
|
28
|
+
dump_spec: Callable[[Path], str],
|
|
29
|
+
npm_scope: str | None = None,
|
|
30
|
+
license_spdx: str | None = None,
|
|
31
|
+
repository_url: str | None = None,
|
|
32
|
+
project: str | None = None,
|
|
33
|
+
client: str | None = None,
|
|
34
|
+
no_cache: bool = False,
|
|
35
|
+
root: Path = Path(),
|
|
36
|
+
) -> None:
|
|
37
|
+
root = root.resolve()
|
|
38
|
+
naming = DefaultNamingPolicy(root_name)
|
|
39
|
+
|
|
40
|
+
with TemporaryDirectory() as templates_directory_string:
|
|
41
|
+
templates_directory = Path(templates_directory_string)
|
|
42
|
+
regenerate_templates(templates_directory)
|
|
43
|
+
|
|
44
|
+
for project_name, clients in projects.items():
|
|
45
|
+
if project is not None and project != project_name:
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
openapi_spec = _produce_and_cache_spec(root / project_name, dump_spec, no_cache)
|
|
49
|
+
|
|
50
|
+
if openapi_spec is None:
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
names = naming(project_name)
|
|
54
|
+
|
|
55
|
+
for client_name in clients:
|
|
56
|
+
if client is not None and client_name != client:
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
generate_client(
|
|
60
|
+
openapi_spec,
|
|
61
|
+
client_name,
|
|
62
|
+
generated_root / client_name / names.base,
|
|
63
|
+
names,
|
|
64
|
+
templates_dir=templates_directory,
|
|
65
|
+
npm_scope=npm_scope,
|
|
66
|
+
license_spdx=license_spdx,
|
|
67
|
+
repository_url=repository_url,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def dump_openapi_spec(project: Path) -> str:
|
|
72
|
+
# CODEGEN=1 gates a service package's heavy imports so the app imports cleanly for the spec
|
|
73
|
+
# dump; it reaches the child through bashrun's per-call env overlay.
|
|
74
|
+
return bash_output("uv run --project . python -m src.dump_openapi", cwd=project, env={"CODEGEN": "1"})
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _produce_and_cache_spec(project: Path, dump_spec: Callable[[Path], str], no_cache: bool) -> str | None:
|
|
78
|
+
print(f"Producing OpenAPI spec for project: {project}")
|
|
79
|
+
|
|
80
|
+
openapi_json = json.loads(dump_spec(project))
|
|
81
|
+
downgrade_openapi_3_1_to_3_0(openapi_json)
|
|
82
|
+
openapi_spec = json.dumps(openapi_json, indent=2)
|
|
83
|
+
|
|
84
|
+
spec_path = project / "openapi.json"
|
|
85
|
+
|
|
86
|
+
if spec_path.exists():
|
|
87
|
+
old_spec = spec_path.read_text(encoding="utf-8")
|
|
88
|
+
|
|
89
|
+
if openapi_spec == old_spec and not no_cache:
|
|
90
|
+
print("OpenAPI spec unchanged, skipping client generation")
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
spec_path.write_text(openapi_spec, encoding="utf-8")
|
|
94
|
+
|
|
95
|
+
return openapi_spec
|
|
File without changes
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from openapi_client_codegen import orchestrator
|
|
9
|
+
from openapi_client_codegen.downgrade import JsonDict, downgrade_openapi_3_1_to_3_0
|
|
10
|
+
from openapi_client_codegen.naming import ClientNaming
|
|
11
|
+
from openapi_client_codegen.orchestrator import SpecProducer, dump_openapi_spec, generate_projects
|
|
12
|
+
|
|
13
|
+
RAW_SCHEMA: JsonDict = {"openapi": "3.1.0", "info": {"title": "demo", "version": "0.0.0"}, "paths": {}}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Recorder:
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
self.generated: list[tuple[str, Path, ClientNaming]] = []
|
|
19
|
+
self.dumps: list[Path | None] = []
|
|
20
|
+
self.commands: list[tuple[str, Path | None, dict[str, str] | None]] = []
|
|
21
|
+
|
|
22
|
+
def fake_dump_spec(self, project: Path) -> str:
|
|
23
|
+
self.dumps.append(project)
|
|
24
|
+
return json.dumps(RAW_SCHEMA)
|
|
25
|
+
|
|
26
|
+
def fake_bash_output(self, command: str, *, cwd: Path | None = None, env: dict[str, str] | None = None) -> str:
|
|
27
|
+
self.commands.append((command, cwd, env))
|
|
28
|
+
return json.dumps(RAW_SCHEMA)
|
|
29
|
+
|
|
30
|
+
def fake_regenerate_templates(self, target_dir: Path, extra_patches_dir: Path | None = None) -> None:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
def fake_generate_client(
|
|
34
|
+
self,
|
|
35
|
+
spec: str,
|
|
36
|
+
generator: str,
|
|
37
|
+
output_dir: Path,
|
|
38
|
+
names: ClientNaming,
|
|
39
|
+
templates_dir: Path | None = None,
|
|
40
|
+
extra_references: list[str] | None = None,
|
|
41
|
+
npm_scope: str | None = None,
|
|
42
|
+
license_spdx: str | None = None,
|
|
43
|
+
repository_url: str | None = None,
|
|
44
|
+
) -> None:
|
|
45
|
+
self.generated.append((generator, output_dir, names))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.fixture
|
|
49
|
+
def recorder(monkeypatch: pytest.MonkeyPatch) -> Recorder:
|
|
50
|
+
recorder = Recorder()
|
|
51
|
+
monkeypatch.setattr(orchestrator, "bash_output", recorder.fake_bash_output)
|
|
52
|
+
monkeypatch.setattr(orchestrator, "regenerate_templates", recorder.fake_regenerate_templates)
|
|
53
|
+
monkeypatch.setattr(orchestrator, "generate_client", recorder.fake_generate_client)
|
|
54
|
+
return recorder
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def downgraded_spec() -> str:
|
|
58
|
+
schema = copy.deepcopy(RAW_SCHEMA)
|
|
59
|
+
downgrade_openapi_3_1_to_3_0(schema)
|
|
60
|
+
return json.dumps(schema, indent=2)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def make_project(root: Path, name: str) -> Path:
|
|
64
|
+
project = root / name
|
|
65
|
+
project.mkdir(parents=True)
|
|
66
|
+
return project
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_generates_all_clients_and_writes_downgraded_spec(tmp_path: Path, recorder: Recorder) -> None:
|
|
70
|
+
make_project(tmp_path, "docker/api")
|
|
71
|
+
|
|
72
|
+
generate_projects(
|
|
73
|
+
{"docker/api": ["python", "csharp"]},
|
|
74
|
+
"placeframe",
|
|
75
|
+
tmp_path / "generated",
|
|
76
|
+
recorder.fake_dump_spec,
|
|
77
|
+
root=tmp_path,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
assert [(generator, output_dir.name) for generator, output_dir, _ in recorder.generated] == [
|
|
81
|
+
("python", "api-client"),
|
|
82
|
+
("csharp", "api-client"),
|
|
83
|
+
]
|
|
84
|
+
assert all(output_dir.parent.parent == tmp_path / "generated" for _, output_dir, _ in recorder.generated)
|
|
85
|
+
|
|
86
|
+
written = (tmp_path / "docker" / "api" / "openapi.json").read_text(encoding="utf-8")
|
|
87
|
+
assert written == downgraded_spec()
|
|
88
|
+
assert json.loads(written)["openapi"] == "3.0.3"
|
|
89
|
+
|
|
90
|
+
assert recorder.dumps == [tmp_path.resolve() / "docker" / "api"]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_skips_generation_when_committed_spec_unchanged(tmp_path: Path, recorder: Recorder) -> None:
|
|
94
|
+
project = make_project(tmp_path, "docker/api")
|
|
95
|
+
(project / "openapi.json").write_text(downgraded_spec(), encoding="utf-8")
|
|
96
|
+
|
|
97
|
+
generate_projects(
|
|
98
|
+
{"docker/api": ["python"]}, "placeframe", tmp_path / "generated", recorder.fake_dump_spec, root=tmp_path
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
assert recorder.generated == []
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_no_cache_forces_generation_despite_unchanged_spec(tmp_path: Path, recorder: Recorder) -> None:
|
|
105
|
+
project = make_project(tmp_path, "docker/api")
|
|
106
|
+
(project / "openapi.json").write_text(downgraded_spec(), encoding="utf-8")
|
|
107
|
+
|
|
108
|
+
generate_projects(
|
|
109
|
+
{"docker/api": ["python"]},
|
|
110
|
+
"placeframe",
|
|
111
|
+
tmp_path / "generated",
|
|
112
|
+
recorder.fake_dump_spec,
|
|
113
|
+
no_cache=True,
|
|
114
|
+
root=tmp_path,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
assert len(recorder.generated) == 1
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_project_and_client_filters(tmp_path: Path, recorder: Recorder) -> None:
|
|
121
|
+
make_project(tmp_path, "docker/api")
|
|
122
|
+
make_project(tmp_path, "docker/localizer")
|
|
123
|
+
|
|
124
|
+
generate_projects(
|
|
125
|
+
{"docker/api": ["python", "csharp"], "docker/localizer": ["python"]},
|
|
126
|
+
"placeframe",
|
|
127
|
+
tmp_path / "generated",
|
|
128
|
+
recorder.fake_dump_spec,
|
|
129
|
+
project="docker/api",
|
|
130
|
+
client="csharp",
|
|
131
|
+
root=tmp_path,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
assert len(recorder.generated) == 1
|
|
135
|
+
generator, output_dir, names = recorder.generated[0]
|
|
136
|
+
assert generator == "csharp"
|
|
137
|
+
assert output_dir == tmp_path / "generated" / "csharp" / "api-client"
|
|
138
|
+
assert names.dashed == "placeframe-api-client"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_dump_openapi_spec_pins_the_org_convention(recorder: Recorder) -> None:
|
|
142
|
+
dump_openapi_spec(Path("docker/api"))
|
|
143
|
+
|
|
144
|
+
assert len(recorder.commands) == 1
|
|
145
|
+
command, cwd, env = recorder.commands[0]
|
|
146
|
+
assert command == "uv run --project . python -m src.dump_openapi"
|
|
147
|
+
assert cwd == Path("docker/api")
|
|
148
|
+
assert env == {"CODEGEN": "1"}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_command_spec_producer_runs_with_project_cwd(recorder: Recorder) -> None:
|
|
152
|
+
producer: Callable[[Path], str] = SpecProducer("dotnet run -- dump-openapi")
|
|
153
|
+
|
|
154
|
+
produced = producer(Path("services/api"))
|
|
155
|
+
|
|
156
|
+
assert json.loads(produced) == RAW_SCHEMA
|
|
157
|
+
assert recorder.commands == [("dotnet run -- dump-openapi", Path("services/api"), None)]
|
|
@@ -1,73 +0,0 @@
|
|
|
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 OpenAPI spec, the project→client mapping, the package-name policy, and the output paths. 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
|
-
A thin CLI exposes the OpenAPI 3.1→3.0 downgrade and single-client generation:
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
uv run openapi-client-codegen downgrade path/to/openapi.json # 3.1→3.0 in place
|
|
18
|
-
uv run openapi-client-codegen downgrade path/to/openapi.json out.json # write elsewhere
|
|
19
|
-
|
|
20
|
-
uv run openapi-client-codegen generate path/to/openapi.json generated/csharp/api-client \
|
|
21
|
-
--root-name myproject --project services/api \
|
|
22
|
-
--npm-scope org.example.myproject --license-spdx Apache-2.0 \
|
|
23
|
-
--repository-url https://github.com/org/repo.git
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Client generation itself is driven from Python (it needs the consumer's project map, naming policy, and output paths):
|
|
27
|
-
|
|
28
|
-
```python
|
|
29
|
-
import json
|
|
30
|
-
import tempfile
|
|
31
|
-
from pathlib import Path
|
|
32
|
-
|
|
33
|
-
from openapi_client_codegen import (
|
|
34
|
-
DefaultNamingPolicy,
|
|
35
|
-
downgrade_openapi_3_1_to_3_0,
|
|
36
|
-
generate_client,
|
|
37
|
-
regenerate_templates,
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
naming = DefaultNamingPolicy("myproject")
|
|
41
|
-
templates_dir = Path(tempfile.mkdtemp())
|
|
42
|
-
regenerate_templates(templates_dir)
|
|
43
|
-
|
|
44
|
-
# Produce the raw OpenAPI JSON however your app exposes it, then downgrade it.
|
|
45
|
-
schema = json.loads(my_openapi_spec_json())
|
|
46
|
-
downgrade_openapi_3_1_to_3_0(schema)
|
|
47
|
-
spec = json.dumps(schema, indent=2)
|
|
48
|
-
|
|
49
|
-
generate_client(spec, "python", Path("generated/python/api-client"), naming("services/api"))
|
|
50
|
-
generate_client(
|
|
51
|
-
spec, "csharp", Path("generated/csharp/api-client"), naming("services/api"), templates_dir=templates_dir
|
|
52
|
-
)
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Consuming from another repo
|
|
56
|
-
|
|
57
|
-
Install from PyPI:
|
|
58
|
-
|
|
59
|
-
```toml
|
|
60
|
-
[project]
|
|
61
|
-
dependencies = ["openapi-client-codegen>=0.1.0"]
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
`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.
|
|
65
|
-
|
|
66
|
-
## Development
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
uv run ruff check .
|
|
70
|
-
uv run ruff format --check .
|
|
71
|
-
uv run basedpyright
|
|
72
|
-
uv run pytest
|
|
73
|
-
```
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
target-version = "py313"
|
|
2
|
-
line-length = 120
|
|
3
|
-
preview = true
|
|
4
|
-
|
|
5
|
-
[lint]
|
|
6
|
-
select = ["ALL"]
|
|
7
|
-
ignore = [
|
|
8
|
-
# ── Permanent ─────────────────────────────────────────────────────────
|
|
9
|
-
# Type annotations — basedpyright strict mode handles this
|
|
10
|
-
"ANN",
|
|
11
|
-
# No docstrings, no copyright headers
|
|
12
|
-
"D",
|
|
13
|
-
"DOC",
|
|
14
|
-
"CPY",
|
|
15
|
-
# TODO/FIXME comment formatting
|
|
16
|
-
"TD",
|
|
17
|
-
"FIX",
|
|
18
|
-
# Exception message ergonomics — literal-indirection and custom-class churn without benefit
|
|
19
|
-
"EM101",
|
|
20
|
-
"EM102",
|
|
21
|
-
"TRY003",
|
|
22
|
-
# Class attribute names never conflict with builtins via self.x
|
|
23
|
-
"A003",
|
|
24
|
-
# Function-size counters — C901 already covers structural complexity
|
|
25
|
-
"PLR0911",
|
|
26
|
-
"PLR0912",
|
|
27
|
-
"PLR0913",
|
|
28
|
-
"PLR0914",
|
|
29
|
-
"PLR0915",
|
|
30
|
-
"PLR0917",
|
|
31
|
-
# Typer's Option/Argument in argument defaults is its standard API pattern
|
|
32
|
-
"B008",
|
|
33
|
-
# Boolean parameters read fine as keyword-only flags here
|
|
34
|
-
"FBT001",
|
|
35
|
-
"FBT002",
|
|
36
|
-
"FBT003",
|
|
37
|
-
# Relative imports are the intra-package convention
|
|
38
|
-
"TID252",
|
|
39
|
-
# Formatter-owned; enforced by `ruff format`
|
|
40
|
-
"COM812",
|
|
41
|
-
"I001",
|
|
42
|
-
# Magic values in comparisons are readable in this CLI
|
|
43
|
-
"PLR2004",
|
|
44
|
-
# The formatter owns wrapping; long string literals and URLs stay on one line
|
|
45
|
-
"E501",
|
|
46
|
-
# src-layout tests need no __init__.py
|
|
47
|
-
"INP001",
|
|
48
|
-
# Runtime-used typing imports stay at module level
|
|
49
|
-
"TC001",
|
|
50
|
-
"TC002",
|
|
51
|
-
"TC003",
|
|
52
|
-
# Small literal tuples read fine in membership tests
|
|
53
|
-
"PLR6201",
|
|
54
|
-
|
|
55
|
-
# ── Deferred to PLE-248 — Enforce the deferred ruff rules
|
|
56
|
-
# https://linear.app/plerionplatforms/issue/PLE-248
|
|
57
|
-
# Mirrors placeframe's root ruff.toml transitional Ticket-N blocks; lifting
|
|
58
|
-
# a rule here should happen alongside lifting it from placeframe's ruff.toml.
|
|
59
|
-
"C901", # function too complex — Ticket 9
|
|
60
|
-
"PLR1702", # too many nested blocks — Ticket 9
|
|
61
|
-
|
|
62
|
-
# ── Deferred to PLE-336 — Replace print() with structured logging
|
|
63
|
-
# https://linear.app/plerionplatforms/issue/PLE-336
|
|
64
|
-
"T201", # print found
|
|
65
|
-
]
|
|
66
|
-
|
|
67
|
-
[lint.per-file-ignores]
|
|
68
|
-
"tests/**" = ["S101", "S404", "S607", "PLR6301"]
|
|
69
|
-
|
|
70
|
-
[lint.flake8-builtins]
|
|
71
|
-
ignorelist = ["id", "map"]
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
from .client import generate_client
|
|
2
|
-
from .downgrade import downgrade_openapi_3_1_to_3_0
|
|
3
|
-
from .naming import ClientNaming, DefaultNamingPolicy, NamingPolicy
|
|
4
|
-
from .templates import regenerate_templates
|
|
5
|
-
from .unity import write_unity_package_metadata
|
|
6
|
-
|
|
7
|
-
__all__ = [
|
|
8
|
-
"ClientNaming",
|
|
9
|
-
"DefaultNamingPolicy",
|
|
10
|
-
"NamingPolicy",
|
|
11
|
-
"downgrade_openapi_3_1_to_3_0",
|
|
12
|
-
"generate_client",
|
|
13
|
-
"regenerate_templates",
|
|
14
|
-
"write_unity_package_metadata",
|
|
15
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/naming.py
RENAMED
|
File without changes
|
|
File without changes
|
{openapi_client_codegen-0.1.0 → openapi_client_codegen-0.1.2}/src/openapi_client_codegen/unity.py
RENAMED
|
File without changes
|
|
File without changes
|