openapi-client-codegen 0.1.4__py3-none-any.whl → 0.1.6__py3-none-any.whl

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.
@@ -3,71 +3,21 @@ from pathlib import Path
3
3
  from tempfile import TemporaryDirectory
4
4
  from typing import Annotated
5
5
 
6
- from typer import Argument, Option, Typer
6
+ 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 .naming import DefaultNamingPolicy
11
- from .orchestrator import SpecProducer, generate_projects
10
+ from .orchestrator import ClientNaming, ProjectsConfig, SpecProducer
12
11
  from .templates import regenerate_templates
13
12
 
14
13
  app = Typer(pretty_exceptions_show_locals=False)
15
14
 
16
15
 
17
16
  @app.command()
18
- def downgrade(
19
- input_file: Annotated[Path, Argument(help="OpenAPI 3.1 JSON to downgrade to 3.0.3")],
20
- output_file: Annotated[Path | None, Argument(help="Write here instead of overwriting input_file")] = None,
21
- ) -> None:
22
- schema = json.loads(input_file.read_text(encoding="utf-8"))
23
- downgrade_openapi_3_1_to_3_0(schema)
24
- destination = output_file if output_file is not None else input_file
25
- destination.write_text(json.dumps(schema, indent=2), encoding="utf-8")
26
-
27
-
28
- @app.command()
29
- def generate(
30
- spec_file: Annotated[Path, Argument(help="OpenAPI 3.0 spec JSON to generate from")],
31
- output_dir: Annotated[Path, Argument(help="Directory to sync the generated client into")],
32
- root_name: Annotated[str, Option(help="Root name handed to DefaultNamingPolicy")],
33
- project: Annotated[str, Option(help="Project path; its last segment names the client")],
34
- generator: Annotated[str, Option(help="openapi-generator generator name")] = "csharp",
35
- npm_scope: Annotated[str | None, Option(help="npm scope composing the UPM package identity")] = None,
36
- license_spdx: Annotated[str | None, Option(help="SPDX license id written to package.json")] = None,
37
- repository_url: Annotated[str | None, Option(help="git repository URL written to package.json")] = None,
38
- ) -> None:
39
- with TemporaryDirectory() as templates_directory_string:
40
- templates_directory = Path(templates_directory_string)
41
- templates_dir = templates_directory if generator == "csharp" else None
42
- if templates_dir is not None:
43
- regenerate_templates(templates_dir)
44
-
45
- generate_client(
46
- spec_file.read_text(encoding="utf-8"),
47
- generator,
48
- output_dir,
49
- DefaultNamingPolicy(root_name)(project),
50
- templates_dir=templates_dir,
51
- npm_scope=npm_scope,
52
- license_spdx=license_spdx,
53
- repository_url=repository_url,
54
- )
55
-
56
-
57
- @app.command("generate-projects")
58
17
  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
- ),
18
+ config: Annotated[
19
+ Path, Option(help="JSON config carrying the projects mapping, the client identity, and the spec command")
67
20
  ],
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
21
  project: Annotated[str | None, Option(help="Generate only this project, as keyed in the config")] = None,
72
22
  client: Annotated[str | None, Option(help="Generate only this client generator")] = None,
73
23
  no_cache: Annotated[
@@ -75,17 +25,56 @@ def generate_projects_command(
75
25
  ] = False,
76
26
  root: Annotated[Path, Option(help="Repository root the project paths resolve against")] = Path(),
77
27
  ) -> 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
- )
28
+ settings = ProjectsConfig.model_validate_json(config.read_text(encoding="utf-8"))
29
+ root = root.resolve()
30
+ dump_spec = SpecProducer(settings.spec_command, env=settings.spec_env)
31
+
32
+ with TemporaryDirectory() as templates_directory_string:
33
+ templates_directory = Path(templates_directory_string)
34
+ regenerate_templates(templates_directory)
35
+
36
+ for project_name, clients in settings.projects.items():
37
+ if project is not None and project != project_name:
38
+ continue
39
+
40
+ project_directory = root / project_name
41
+ print(f"Producing OpenAPI spec for project: {project_directory}")
42
+
43
+ openapi_json = json.loads(dump_spec(project_directory))
44
+ downgrade_openapi_3_1_to_3_0(openapi_json)
45
+ openapi_spec = json.dumps(openapi_json, indent=2)
46
+
47
+ spec_path = project_directory / "openapi.json"
48
+
49
+ if spec_path.exists():
50
+ old_spec = spec_path.read_text(encoding="utf-8")
51
+
52
+ if openapi_spec == old_spec and not no_cache:
53
+ print("OpenAPI spec unchanged, skipping client generation")
54
+ continue
55
+
56
+ spec_path.write_text(openapi_spec, encoding="utf-8")
57
+
58
+ base = f"{project_name.rsplit('/', maxsplit=1)[-1]}-client"
59
+ dashed = f"{settings.root_name}-{base}"
60
+ names = ClientNaming(
61
+ base=base,
62
+ dashed=dashed,
63
+ underscored=dashed.replace("-", "_"),
64
+ camel=f"{settings.root_name.capitalize()}{''.join(part.capitalize() for part in base.split('-'))}",
65
+ )
66
+
67
+ for client_name in clients:
68
+ if client is not None and client_name != client:
69
+ continue
70
+
71
+ generate_client(
72
+ openapi_spec,
73
+ client_name,
74
+ settings.generated_root / client_name / names.base,
75
+ names,
76
+ templates_dir=templates_directory,
77
+ npm_scope=settings.npm_scope,
78
+ license_spdx=settings.license_spdx,
79
+ repository_url=settings.repository_url,
80
+ )
@@ -1,15 +1,20 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
  from os import walk
3
5
  from pathlib import Path
4
6
  from shutil import copytree
5
7
  from tempfile import NamedTemporaryFile, TemporaryDirectory
8
+ from typing import TYPE_CHECKING
6
9
 
7
10
  from bashrun.bash import bash
8
11
 
9
12
  from .resources import CONFIGS_DIR, IGNORE_FILE, OPENAPI_GENERATOR_CLI_VERSION
10
- from .naming import ClientNaming
11
13
  from .unity import write_unity_package_metadata
12
14
 
15
+ if TYPE_CHECKING:
16
+ from .orchestrator import ClientNaming
17
+
13
18
  _HTTP_METHODS = ("get", "put", "post", "delete", "patch", "options", "head", "trace")
14
19
 
15
20
 
@@ -1,95 +1,37 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
- from collections.abc import Callable
3
+ from dataclasses import dataclass
5
4
  from pathlib import Path
6
- from tempfile import TemporaryDirectory
7
5
 
8
6
  from bashrun.bash import bash_output
7
+ from pydantic import BaseModel, ConfigDict
9
8
 
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
9
 
10
+ class ProjectsConfig(BaseModel):
11
+ model_config = ConfigDict(extra="forbid")
15
12
 
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)
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
54
21
 
55
- for client_name in clients:
56
- if client is not None and client_name != client:
57
- continue
58
22
 
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
- )
23
+ @dataclass(frozen=True)
24
+ class ClientNaming:
25
+ base: str
26
+ dashed: str
27
+ underscored: str
28
+ camel: str
69
29
 
70
30
 
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")
31
+ class SpecProducer:
32
+ def __init__(self, command: str, env: dict[str, str] | None = None) -> None:
33
+ self.command = command
34
+ self.env = env
94
35
 
95
- return openapi_spec
36
+ def __call__(self, project: Path) -> str:
37
+ return bash_output(self.command, cwd=project, env=self.env)
@@ -1,9 +1,10 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: openapi-client-codegen
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: OpenAPI client-generation wrapper: Litestar spec dump, 3.1->3.0 downgrade, patched openapi-generator templates, Unity package metadata
5
5
  License-File: LICENSE
6
6
  License-File: NOTICE
7
7
  Requires-Python: >=3.13
8
8
  Requires-Dist: bashrun>=0.1.0
9
+ Requires-Dist: pydantic>=2.11.7
9
10
  Requires-Dist: typer>=0.17.4
@@ -1,9 +1,8 @@
1
1
  openapi_client_codegen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- openapi_client_codegen/cli.py,sha256=2KZLSgzPrLc0Re2wtLrokO6QybFfIvqi2PbTBgpADrU,4145
3
- openapi_client_codegen/client.py,sha256=rQ2j24MyV9mcaAXDCIVrXeJuiKjsufcw5_h7wEv-xr8,4638
2
+ openapi_client_codegen/cli.py,sha256=Gtv8sUzIf3othiPGcp2U6RPAbc8iQ1h1HRNe8s_FgNQ,3243
3
+ openapi_client_codegen/client.py,sha256=-lflu6eix2nCcHFkMrbCp0YgL9PVyQuLQS1r5Zfd0YQ,4736
4
4
  openapi_client_codegen/downgrade.py,sha256=7cxQZJRgWR5lNpvyJT-6pg4lVj72LsMRfdYzsNF9DtY,5230
5
- openapi_client_codegen/naming.py,sha256=c-GmnQk0zb2bsOi0gXOaUD5P_1c-dLfw8FaaNLQxb1I,742
6
- openapi_client_codegen/orchestrator.py,sha256=83Kb1kT1R0fgfaq6MBA0HDBjw-L-Mo051cyqHROlSzI,3076
5
+ openapi_client_codegen/orchestrator.py,sha256=AU_KvKiCZBeRx9J3dPIQfQnNi23tyupQED6cSETQr5s,893
7
6
  openapi_client_codegen/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
7
  openapi_client_codegen/resources.py,sha256=Vavy0_zqot1eDrFpDJ_BT4Sbx47vh7zuclPjQtW3QMw,266
9
8
  openapi_client_codegen/templates.py,sha256=ll3NQfdnpVgDOz4WJrEga5lHljEOOItSD1aeVP5Hp80,1198
@@ -18,9 +17,9 @@ openapi_client_codegen/_data/templates-patches/csharp/0004-csharp-support-hybrid
18
17
  openapi_client_codegen/_data/templates-patches/csharp/0006-csharp-streaming-download-response.patch,sha256=ma-QkJA94jmN3scbJifaVPMcg_NNZ8klVO2HUrLMz_Y,2417
19
18
  openapi_client_codegen/_data/templates-patches/csharp/0007-csharp-model-default-initializers.patch,sha256=_fIimm7K0Gh1IYE3_fYyhUom_g-Bm4HlN9jLgsP27IE,862
20
19
  openapi_client_codegen/_data/templates-patches/csharp/0008-csharp-redirect-documentation-file.patch,sha256=HQXDgm_CzFYK4oxOoqBkYAz4wrKKPlYpLgTNHI_0IYc,1363
21
- openapi_client_codegen-0.1.4.dist-info/METADATA,sha256=fOUz6g98d7WWBmFGt8nMcL7jBsBkCKLxXFlc3-IaKxw,336
22
- openapi_client_codegen-0.1.4.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
23
- openapi_client_codegen-0.1.4.dist-info/entry_points.txt,sha256=mw8FL3PBefHSxRjhBJunBk9G1efUKgxNXwAlghbc31E,74
24
- openapi_client_codegen-0.1.4.dist-info/licenses/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
25
- openapi_client_codegen-0.1.4.dist-info/licenses/NOTICE,sha256=jvglZza3GCdN7v4_TLuXUqX9C_uABlwAKUkcIeKwk_8,26
26
- openapi_client_codegen-0.1.4.dist-info/RECORD,,
20
+ openapi_client_codegen-0.1.6.dist-info/METADATA,sha256=wXc2KsOyunO09qTp9UPPHXKYm_S8M10J55jIBNe_oQw,368
21
+ openapi_client_codegen-0.1.6.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
22
+ openapi_client_codegen-0.1.6.dist-info/entry_points.txt,sha256=mw8FL3PBefHSxRjhBJunBk9G1efUKgxNXwAlghbc31E,74
23
+ openapi_client_codegen-0.1.6.dist-info/licenses/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
24
+ openapi_client_codegen-0.1.6.dist-info/licenses/NOTICE,sha256=jvglZza3GCdN7v4_TLuXUqX9C_uABlwAKUkcIeKwk_8,26
25
+ openapi_client_codegen-0.1.6.dist-info/RECORD,,
@@ -1,26 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import Protocol
3
-
4
-
5
- @dataclass(frozen=True)
6
- class ClientNaming:
7
- base: str
8
- dashed: str
9
- underscored: str
10
- camel: str
11
-
12
-
13
- class NamingPolicy(Protocol):
14
- def __call__(self, project: str) -> ClientNaming: ...
15
-
16
-
17
- @dataclass(frozen=True)
18
- class DefaultNamingPolicy:
19
- root_name: str
20
-
21
- def __call__(self, project: str) -> ClientNaming:
22
- base = f"{project.rsplit('/', maxsplit=1)[-1]}-client"
23
- dashed = f"{self.root_name}-{base}"
24
- underscored = dashed.replace("-", "_")
25
- camel = f"{self.root_name.capitalize()}{''.join(part.capitalize() for part in base.split('-'))}"
26
- return ClientNaming(base=base, dashed=dashed, underscored=underscored, camel=camel)