openapi-client-codegen 0.1.11__tar.gz → 0.1.13__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.
Files changed (42) hide show
  1. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/.github/workflows/release.yml +1 -1
  2. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/AGENTS.md +6 -5
  3. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/PKG-INFO +3 -1
  4. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/pyproject.toml +11 -2
  5. openapi_client_codegen-0.1.13/release-devkit.yaml +8 -0
  6. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/cli.py +3 -3
  7. openapi_client_codegen-0.1.13/src/openapi_client_codegen/orchestrator.py +61 -0
  8. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/tests/test_cli.py +5 -3
  9. openapi_client_codegen-0.1.13/tests/test_config.py +114 -0
  10. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/tests/test_orchestrate.py +7 -4
  11. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/uv.lock +100 -0
  12. openapi_client_codegen-0.1.11/release-devkit.json +0 -13
  13. openapi_client_codegen-0.1.11/src/openapi_client_codegen/orchestrator.py +0 -37
  14. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/.gitattributes +0 -0
  15. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/.github/workflows/ci.yml +0 -0
  16. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/.gitignore +0 -0
  17. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/.python-version +0 -0
  18. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/CLAUDE.md +0 -0
  19. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/LICENSE +0 -0
  20. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/NOTICE +0 -0
  21. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/README.md +0 -0
  22. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/ruff.base.toml +0 -0
  23. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/ruff.toml +0 -0
  24. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/__init__.py +0 -0
  25. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/_data/configs/csharp.json +0 -0
  26. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/_data/configs/python.json +0 -0
  27. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/_data/openapi-generator-ignore +0 -0
  28. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/_data/templates-patches/csharp/0001-csharp-remove-errant-comma.patch +0 -0
  29. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/_data/templates-patches/csharp/0002-csharp-models-required-only-constructors.patch +0 -0
  30. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/_data/templates-patches/csharp/0003-csharp-httpclient-use-defaultT.patch +0 -0
  31. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/_data/templates-patches/csharp/0004-csharp-support-hybrid-multipart-payloads.patch +0 -0
  32. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/_data/templates-patches/csharp/0006-csharp-streaming-download-response.patch +0 -0
  33. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/_data/templates-patches/csharp/0007-csharp-model-default-initializers.patch +0 -0
  34. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/_data/templates-patches/csharp/0008-csharp-redirect-documentation-file.patch +0 -0
  35. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/client.py +0 -0
  36. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/downgrade.py +0 -0
  37. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/py.typed +0 -0
  38. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/resources.py +0 -0
  39. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/templates.py +0 -0
  40. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/src/openapi_client_codegen/unity.py +0 -0
  41. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/tests/test_downgrade.py +0 -0
  42. {openapi_client_codegen-0.1.11 → openapi_client_codegen-0.1.13}/tests/test_unity.py +0 -0
@@ -28,4 +28,4 @@ jobs:
28
28
  - uses: astral-sh/setup-uv@v7
29
29
 
30
30
  - name: Publish
31
- run: uvx --from release-devkit==0.1.14 publish-stable --config release-devkit.json
31
+ run: uvx --from release-devkit==0.1.20 publish-stable --config release-devkit.yaml
@@ -4,22 +4,23 @@
4
4
 
5
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), `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>' ...`.
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), `packaging` (PEP 440 specifier parsing for `requires`), `pydantic` (the config schema), `strictyaml` (config loading), 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 `release.yml`, triggered by a successful CI run on a `main` push: release-devkit's `publish-stable` (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 (publisher bound to `release.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 (declared `major_minor` line in `release-devkit.json`, patch-auto within the line). API-breaking changes ship with a manually bumped `major_minor` — patch-auto assumes additive changes.
11
+ Publishing rides `release.yml`, triggered by a successful CI run on a `main` push: release-devkit's `publish-stable` (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 (publisher bound to `release.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 (declared `major_minor` line in `release-devkit.yaml`, patch-auto within the line). API-breaking changes ship with a manually bumped `major_minor` — 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`), `cli` (`app`), `downgrade` (`downgrade_openapi_3_1_to_3_0`, `JsonDict`), `orchestrator` (`ProjectsConfig`, `ClientNaming`, `SpecProducer`), `templates` (`regenerate_templates`), `unity` (`write_unity_package_metadata`).
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`, `load_config`), `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
- | `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. |
20
+ | `ProjectsConfig` | The pydantic schema of a consumer's client-generation config (`extra="forbid"`): required `projects`, `root_name`, `generated_root`, `spec_command`, `requires`; optional `spec_env`, `npm_scope`, `license_spdx`, `repository_url`. `requires` is a PEP 440 specifier; `load_config` self-checks the installed version and refuses outside range; the `0.0.0.dev0` sentinel bypasses the *check*, not the field's presence (omission is a validation error; "no constraint" is explicit `requires: ">=0.0"`). The single source of identity — the CLI validates the `--config` file into it and consumes it; nothing overrides it per invocation. |
21
+ | `load_config(path)` | Parse the consumer's root YAML config into a `ProjectsConfig` (strictyaml, scalars as strings) and enforce the required `requires` version self-check. |
21
22
  | `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
+ | `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` YAML against the `ProjectsConfig` pydantic model (via `load_config`) (`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
24
  | `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
25
  | `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
26
  | `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. |
@@ -1,10 +1,12 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: openapi-client-codegen
3
- Version: 0.1.11
3
+ Version: 0.1.13
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: packaging>=24.1
9
10
  Requires-Dist: pydantic>=2.11.7
11
+ Requires-Dist: strictyaml>=1.7.3
10
12
  Requires-Dist: typer>=0.17.4
@@ -1,16 +1,25 @@
1
1
  [project]
2
2
  name = "openapi-client-codegen"
3
- version = "0.1.11"
3
+ version = "0.1.13"
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
+ "packaging>=24.1",
8
9
  "pydantic>=2.11.7",
10
+ "strictyaml>=1.7.3",
9
11
  "typer>=0.17.4",
10
12
  ]
11
13
 
12
14
  [dependency-groups]
13
- dev = ["basedpyright>=1.39.10", "ruff>=0.14.11", "pytest>=8.0.0"]
15
+ dev = [
16
+ "basedpyright>=1.39.10",
17
+ "ruff>=0.14.11",
18
+ "pytest>=8.0.0",
19
+ "pyyaml>=6.0.3",
20
+ "types-pyyaml>=6.0.12.20260906",
21
+ "types-strictyaml>=1.7.3.3",
22
+ ]
14
23
 
15
24
  [build-system]
16
25
  requires = ["hatchling"]
@@ -0,0 +1,8 @@
1
+ requires: ">=0.1"
2
+ ci_workflow: ci.yml
3
+ packages:
4
+ openapi-client-codegen:
5
+ path: .
6
+ major_minor: "0.1"
7
+ registries:
8
+ pypi: openapi-client-codegen
@@ -7,7 +7,7 @@ from typer import Option, Typer
7
7
 
8
8
  from .client import generate_client
9
9
  from .downgrade import downgrade_openapi_3_1_to_3_0
10
- from .orchestrator import ClientNaming, ProjectsConfig, SpecProducer
10
+ from .orchestrator import ClientNaming, SpecProducer, load_config
11
11
  from .templates import regenerate_templates
12
12
 
13
13
  app = Typer(pretty_exceptions_show_locals=False)
@@ -16,7 +16,7 @@ app = Typer(pretty_exceptions_show_locals=False)
16
16
  @app.command()
17
17
  def generate_projects_command(
18
18
  config: Annotated[
19
- Path, Option(help="JSON config carrying the projects mapping, the client identity, and the spec command")
19
+ Path, Option(help="YAML config carrying the projects mapping, the client identity, and the spec command")
20
20
  ],
21
21
  project: Annotated[str | None, Option(help="Generate only this project, as keyed in the config")] = None,
22
22
  client: Annotated[str | None, Option(help="Generate only this client generator")] = None,
@@ -25,7 +25,7 @@ def generate_projects_command(
25
25
  ] = False,
26
26
  root: Annotated[Path, Option(help="Repository root the project paths resolve against")] = Path(),
27
27
  ) -> None:
28
- settings = ProjectsConfig.model_validate_json(config.read_text(encoding="utf-8"))
28
+ settings = load_config(config)
29
29
  root = root.resolve()
30
30
  dump_spec = SpecProducer(settings.spec_command, env=settings.spec_env)
31
31
 
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib.metadata
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+ from bashrun.bash import bash_output
8
+ from packaging.specifiers import SpecifierSet
9
+ from pydantic import BaseModel, ConfigDict, field_validator
10
+ from strictyaml import load as load_strict_yaml
11
+
12
+ DEV_SENTINEL = "0.0.0.dev0"
13
+
14
+
15
+ class ProjectsConfig(BaseModel):
16
+ model_config = ConfigDict(extra="forbid")
17
+
18
+ projects: dict[str, list[str]]
19
+ root_name: str
20
+ generated_root: Path
21
+ spec_command: str
22
+ spec_env: dict[str, str] | None = None
23
+ npm_scope: str | None = None
24
+ license_spdx: str | None = None
25
+ repository_url: str | None = None
26
+ requires: str
27
+
28
+ @field_validator("requires")
29
+ @classmethod
30
+ def validate_requires(cls, value: str) -> str:
31
+ if not value:
32
+ raise ValueError("requires must be a non-empty PEP 440 specifier; use '>=0.0' for no constraint")
33
+ SpecifierSet(value)
34
+ return value
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class ClientNaming:
39
+ base: str
40
+ dashed: str
41
+ underscored: str
42
+ camel: str
43
+
44
+
45
+ class SpecProducer:
46
+ def __init__(self, command: str, env: dict[str, str] | None = None) -> None:
47
+ self.command = command
48
+ self.env = env
49
+
50
+ def __call__(self, project: Path) -> str:
51
+ return bash_output(self.command, cwd=project, env=self.env)
52
+
53
+
54
+ def load_config(path: Path) -> ProjectsConfig:
55
+ config = ProjectsConfig.model_validate(load_strict_yaml(path.read_text(encoding="utf-8")).data)
56
+ installed = importlib.metadata.version("openapi-client-codegen")
57
+ if installed == DEV_SENTINEL:
58
+ return config
59
+ if not SpecifierSet(config.requires).contains(installed, prereleases=True):
60
+ raise SystemExit(f"openapi-client-codegen {installed} does not satisfy requires={config.requires!r}")
61
+ return config
@@ -2,6 +2,7 @@ import json
2
2
  from pathlib import Path
3
3
 
4
4
  import pytest
5
+ import yaml
5
6
  from typer.testing import CliRunner
6
7
 
7
8
  from openapi_client_codegen import cli, orchestrator
@@ -19,6 +20,7 @@ FULL_SETTINGS: dict[str, object] = {
19
20
  "npm_scope": "org.example.placeframe",
20
21
  "license_spdx": "Apache-2.0",
21
22
  "repository_url": "https://github.com/org/repo.git",
23
+ "requires": ">=0.1",
22
24
  }
23
25
 
24
26
 
@@ -57,8 +59,8 @@ def recorder(monkeypatch: pytest.MonkeyPatch) -> Recorder:
57
59
 
58
60
 
59
61
  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
+ config = directory / "clients.yaml"
63
+ config.write_text(yaml.safe_dump(settings, default_flow_style=False, sort_keys=False), encoding="utf-8")
62
64
  return config
63
65
 
64
66
 
@@ -76,7 +78,7 @@ def test_filter_flags_restrict_generation(tmp_path: Path, recorder: Recorder) ->
76
78
 
77
79
 
78
80
  def test_unknown_config_key_fails_loudly(tmp_path: Path, recorder: Recorder) -> None:
79
- config = write_config(tmp_path, {"projects": {}, "rot_name": "placeframe"})
81
+ config = write_config(tmp_path, {"projects": {"docker/api": ["python"]}, "rot_name": "placeframe"})
80
82
 
81
83
  result = runner.invoke(app, ["--config", str(config)])
82
84
 
@@ -0,0 +1,114 @@
1
+ import importlib.metadata
2
+ from pathlib import Path
3
+
4
+ import pytest
5
+ import yaml
6
+ from pydantic import ValidationError
7
+
8
+ from openapi_client_codegen.orchestrator import DEV_SENTINEL, load_config
9
+
10
+
11
+ def write_config(tmp_path: Path, payload: dict[str, object]) -> Path:
12
+ config_path = tmp_path / "openapi-client-codegen.yaml"
13
+ config_path.write_text(
14
+ yaml.safe_dump(payload, default_flow_style=False, sort_keys=False),
15
+ encoding="utf-8",
16
+ )
17
+ return config_path
18
+
19
+
20
+ def base_payload() -> dict[str, object]:
21
+ return {
22
+ "projects": {"docker/api": ["python"]},
23
+ "root_name": "placeframe",
24
+ "generated_root": "packages/generated",
25
+ "spec_command": "uv run --project . python -m src.dump_openapi",
26
+ "requires": ">=0.1",
27
+ }
28
+
29
+
30
+ def _version_dev_sentinel(_name: str) -> str:
31
+ return DEV_SENTINEL
32
+
33
+
34
+ def _version_0_1_18(_name: str) -> str:
35
+ return "0.1.18"
36
+
37
+
38
+ def _version_0_1_19_dev5(_name: str) -> str:
39
+ return "0.1.19.dev5"
40
+
41
+
42
+ def _version_0_1_17(_name: str) -> str:
43
+ return "0.1.17"
44
+
45
+
46
+ def test_load_config_parses_yaml(tmp_path: Path) -> None:
47
+ config = load_config(write_config(tmp_path, base_payload()))
48
+
49
+ assert config.projects == {"docker/api": ["python"]}
50
+ assert config.root_name == "placeframe"
51
+ assert config.requires == ">=0.1"
52
+
53
+
54
+ def test_requires_omitted_is_required(tmp_path: Path) -> None:
55
+ payload = base_payload()
56
+ del payload["requires"]
57
+
58
+ with pytest.raises(ValidationError, match="requires"):
59
+ load_config(write_config(tmp_path, payload))
60
+
61
+
62
+ def test_requires_empty_string_rejected(tmp_path: Path) -> None:
63
+ payload = base_payload()
64
+ payload["requires"] = ""
65
+
66
+ with pytest.raises(ValidationError, match="requires"):
67
+ load_config(write_config(tmp_path, payload))
68
+
69
+
70
+ def test_requires_dev_sentinel_skips_check(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
71
+ monkeypatch.setattr(importlib.metadata, "version", _version_dev_sentinel)
72
+ payload = base_payload()
73
+ payload["requires"] = ">=0.1.18"
74
+
75
+ config = load_config(write_config(tmp_path, payload))
76
+
77
+ assert config.requires == ">=0.1.18"
78
+
79
+
80
+ def test_requires_in_range_loads(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
81
+ monkeypatch.setattr(importlib.metadata, "version", _version_0_1_18)
82
+ payload = base_payload()
83
+ payload["requires"] = ">=0.1.18"
84
+
85
+ config = load_config(write_config(tmp_path, payload))
86
+
87
+ assert config.requires == ">=0.1.18"
88
+
89
+
90
+ def test_requires_dev_channel_prerelease_satisfies(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
91
+ monkeypatch.setattr(importlib.metadata, "version", _version_0_1_19_dev5)
92
+ payload = base_payload()
93
+ payload["requires"] = ">=0.1.18"
94
+
95
+ config = load_config(write_config(tmp_path, payload))
96
+
97
+ assert config.requires == ">=0.1.18"
98
+
99
+
100
+ def test_requires_out_of_range_refuses(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
101
+ monkeypatch.setattr(importlib.metadata, "version", _version_0_1_17)
102
+ payload = base_payload()
103
+ payload["requires"] = ">=0.1.18"
104
+
105
+ with pytest.raises(SystemExit, match="does not satisfy requires"):
106
+ load_config(write_config(tmp_path, payload))
107
+
108
+
109
+ def test_requires_invalid_specifier_rejected(tmp_path: Path) -> None:
110
+ payload = base_payload()
111
+ payload["requires"] = "not-a-specifier"
112
+
113
+ with pytest.raises(ValidationError, match="requires"):
114
+ load_config(write_config(tmp_path, payload))
@@ -4,6 +4,7 @@ from collections.abc import Callable
4
4
  from pathlib import Path
5
5
 
6
6
  import pytest
7
+ import yaml
7
8
  from typer.testing import CliRunner
8
9
 
9
10
  from openapi_client_codegen import cli, orchestrator
@@ -64,9 +65,10 @@ def invoke(tmp_path: Path, projects: dict[str, list[str]], *args: str) -> None:
64
65
  "root_name": "placeframe",
65
66
  "generated_root": str(tmp_path / "generated"),
66
67
  "spec_command": "dump-openapi",
68
+ "requires": ">=0.1",
67
69
  }
68
- config = tmp_path / "clients.json"
69
- config.write_text(json.dumps(settings), encoding="utf-8")
70
+ config = tmp_path / "clients.yaml"
71
+ config.write_text(yaml.safe_dump(settings, default_flow_style=False, sort_keys=False), encoding="utf-8")
70
72
 
71
73
  result = runner.invoke(app, ["--config", str(config), "--root", str(tmp_path), *args])
72
74
  assert result.exit_code == 0
@@ -143,9 +145,10 @@ def test_spec_env_rides_the_spec_command(tmp_path: Path, recorder: Recorder) ->
143
145
  "generated_root": str(tmp_path / "generated"),
144
146
  "spec_command": "dump-openapi",
145
147
  "spec_env": {"CODEGEN": "1"},
148
+ "requires": ">=0.1",
146
149
  }
147
- config = tmp_path / "clients.json"
148
- config.write_text(json.dumps(settings), encoding="utf-8")
150
+ config = tmp_path / "clients.yaml"
151
+ config.write_text(yaml.safe_dump(settings, default_flow_style=False, sort_keys=False), encoding="utf-8")
149
152
  result = runner.invoke(app, ["--config", str(config), "--root", str(tmp_path)])
150
153
  assert result.exit_code == 0
151
154
 
@@ -102,7 +102,9 @@ version = "0.0.0.dev0"
102
102
  source = { editable = "." }
103
103
  dependencies = [
104
104
  { name = "bashrun" },
105
+ { name = "packaging" },
105
106
  { name = "pydantic" },
107
+ { name = "strictyaml" },
106
108
  { name = "typer" },
107
109
  ]
108
110
 
@@ -110,13 +112,18 @@ dependencies = [
110
112
  dev = [
111
113
  { name = "basedpyright" },
112
114
  { name = "pytest" },
115
+ { name = "pyyaml" },
113
116
  { name = "ruff" },
117
+ { name = "types-pyyaml" },
118
+ { name = "types-strictyaml" },
114
119
  ]
115
120
 
116
121
  [package.metadata]
117
122
  requires-dist = [
118
123
  { name = "bashrun", specifier = ">=0.1.0" },
124
+ { name = "packaging", specifier = ">=24.1" },
119
125
  { name = "pydantic", specifier = ">=2.11.7" },
126
+ { name = "strictyaml", specifier = ">=1.7.3" },
120
127
  { name = "typer", specifier = ">=0.17.4" },
121
128
  ]
122
129
 
@@ -124,7 +131,10 @@ requires-dist = [
124
131
  dev = [
125
132
  { name = "basedpyright", specifier = ">=1.39.10" },
126
133
  { name = "pytest", specifier = ">=8.0.0" },
134
+ { name = "pyyaml", specifier = ">=6.0.3" },
127
135
  { name = "ruff", specifier = ">=0.14.11" },
136
+ { name = "types-pyyaml", specifier = ">=6.0.12.20260906" },
137
+ { name = "types-strictyaml", specifier = ">=1.7.3.3" },
128
138
  ]
129
139
 
130
140
  [[package]]
@@ -241,6 +251,54 @@ wheels = [
241
251
  { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
242
252
  ]
243
253
 
254
+ [[package]]
255
+ name = "python-dateutil"
256
+ version = "2.9.0.post0"
257
+ source = { registry = "https://pypi.org/simple" }
258
+ dependencies = [
259
+ { name = "six" },
260
+ ]
261
+ sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
262
+ wheels = [
263
+ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
264
+ ]
265
+
266
+ [[package]]
267
+ name = "pyyaml"
268
+ version = "6.0.3"
269
+ source = { registry = "https://pypi.org/simple" }
270
+ sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
271
+ wheels = [
272
+ { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
273
+ { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
274
+ { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
275
+ { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
276
+ { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
277
+ { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
278
+ { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
279
+ { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
280
+ { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
281
+ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
282
+ { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
283
+ { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
284
+ { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
285
+ { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
286
+ { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
287
+ { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
288
+ { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
289
+ { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
290
+ { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
291
+ { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
292
+ { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
293
+ { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
294
+ { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
295
+ { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
296
+ { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
297
+ { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
298
+ { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
299
+ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
300
+ ]
301
+
244
302
  [[package]]
245
303
  name = "rich"
246
304
  version = "15.0.0"
@@ -288,6 +346,27 @@ wheels = [
288
346
  { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
289
347
  ]
290
348
 
349
+ [[package]]
350
+ name = "six"
351
+ version = "1.17.0"
352
+ source = { registry = "https://pypi.org/simple" }
353
+ sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
354
+ wheels = [
355
+ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
356
+ ]
357
+
358
+ [[package]]
359
+ name = "strictyaml"
360
+ version = "1.7.3"
361
+ source = { registry = "https://pypi.org/simple" }
362
+ dependencies = [
363
+ { name = "python-dateutil" },
364
+ ]
365
+ sdist = { url = "https://files.pythonhosted.org/packages/b3/08/efd28d49162ce89c2ad61a88bd80e11fb77bc9f6c145402589112d38f8af/strictyaml-1.7.3.tar.gz", hash = "sha256:22f854a5fcab42b5ddba8030a0e4be51ca89af0267961c8d6cfa86395586c407", size = 115206, upload-time = "2023-03-10T12:50:27.062Z" }
366
+ wheels = [
367
+ { url = "https://files.pythonhosted.org/packages/96/7c/a81ef5ef10978dd073a854e0fa93b5d8021d0594b639cc8f6453c3c78a1d/strictyaml-1.7.3-py3-none-any.whl", hash = "sha256:fb5c8a4edb43bebb765959e420f9b3978d7f1af88c80606c03fb420888f5d1c7", size = 123917, upload-time = "2023-03-10T12:50:17.242Z" },
368
+ ]
369
+
291
370
  [[package]]
292
371
  name = "typer"
293
372
  version = "0.26.8"
@@ -303,6 +382,27 @@ wheels = [
303
382
  { url = "https://files.pythonhosted.org/packages/80/87/b9fd69c92c6102a066e1b86a35243f53e70bd4c709f2a26d9f4fee4f4dc0/typer-0.26.8-py3-none-any.whl", hash = "sha256:3512ca79ac5c11113414b36e80281b872884477722440691c89d1112e321a49c", size = 122564, upload-time = "2026-06-26T09:22:44.72Z" },
304
383
  ]
305
384
 
385
+ [[package]]
386
+ name = "types-pyyaml"
387
+ version = "6.0.12.20260906"
388
+ source = { registry = "https://pypi.org/simple" }
389
+ sdist = { url = "https://files.pythonhosted.org/packages/90/6e/abec85b9013db5b934b0280a6dd104904d84f7bcbaab2e2f3def87ac7463/types_pyyaml-6.0.12.20260906.tar.gz", hash = "sha256:f59c1cc05010b833d2d72287bbaa72610106b28d42d89a907313117faba85212", size = 18649, upload-time = "2026-09-06T06:35:35.362Z" }
390
+ wheels = [
391
+ { url = "https://files.pythonhosted.org/packages/15/c0/fc0644b7ddcfb969e95845837143cb5173ddd6e06ee4ba5fc493cd9329b7/types_pyyaml-6.0.12.20260906-py3-none-any.whl", hash = "sha256:bca893ff0d51df5c9053137d5d0e6ccd36e939a196356f1d5c16372422f5137b", size = 21282, upload-time = "2026-09-06T06:35:34.372Z" },
392
+ ]
393
+
394
+ [[package]]
395
+ name = "types-strictyaml"
396
+ version = "1.7.3.3"
397
+ source = { registry = "https://pypi.org/simple" }
398
+ dependencies = [
399
+ { name = "strictyaml" },
400
+ ]
401
+ sdist = { url = "https://files.pythonhosted.org/packages/2b/81/50a74541bb5526033909fda5f343b3fc57c88b7a07fd7ff1eaa9eda98c65/types_strictyaml-1.7.3.3.tar.gz", hash = "sha256:61aeb8a13f44fd15cc348e34fac002f5a9b6189f258d2c7acfe79abccc22d57d", size = 46683, upload-time = "2026-09-12T14:57:49.605Z" }
402
+ wheels = [
403
+ { url = "https://files.pythonhosted.org/packages/7b/ef/c3b4a95c43b615f8f61a1f6c3b4ca23a1cade47e15c209b2e9a3c9a863db/types_strictyaml-1.7.3.3-py3-none-any.whl", hash = "sha256:21f5eefb69dd274a72bc1946c51ceeb2f73031dcc2495072dc88f645a0d9888b", size = 67467, upload-time = "2026-09-12T14:57:48.123Z" },
404
+ ]
405
+
306
406
  [[package]]
307
407
  name = "typing-extensions"
308
408
  version = "4.16.0"
@@ -1,13 +0,0 @@
1
- {
2
- "packages": [
3
- {
4
- "name": "openapi-client-codegen",
5
- "path": ".",
6
- "major_minor": "0.1",
7
- "registries": {
8
- "pypi": "openapi-client-codegen"
9
- }
10
- }
11
- ],
12
- "ci_workflow": "ci.yml"
13
- }
@@ -1,37 +0,0 @@
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)