pyopenapi-gen 0.15.0__py3-none-any.whl → 0.17.0__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.

Potentially problematic release.


This version of pyopenapi-gen might be problematic. Click here for more details.

pyopenapi_gen/__init__.py CHANGED
@@ -8,6 +8,8 @@ code‑generation pipeline can rely on.
8
8
 
9
9
  from __future__ import annotations
10
10
 
11
+ from pathlib import Path
12
+
11
13
  # Removed dataclasses, field, Enum, unique from here, they are in ir.py and http_types.py
12
14
  from typing import (
13
15
  TYPE_CHECKING,
@@ -30,6 +32,11 @@ from .ir import (
30
32
  )
31
33
 
32
34
  __all__ = [
35
+ # Main API
36
+ "generate_client",
37
+ "ClientGenerator",
38
+ "GenerationError",
39
+ # IR classes
33
40
  "HTTPMethod",
34
41
  "IRParameter",
35
42
  "IRResponse",
@@ -37,52 +44,13 @@ __all__ = [
37
44
  "IRSchema",
38
45
  "IRSpec",
39
46
  "IRRequestBody",
40
- # Keep existing __all__ entries for lazy loaded items
47
+ # Utilities
41
48
  "load_ir_from_spec",
42
49
  "WarningCollector",
43
50
  ]
44
51
 
45
52
  # Semantic version of the generator core – automatically managed by semantic-release.
46
- __version__: str = "0.14.3"
47
-
48
-
49
- # ---------------------------------------------------------------------------
50
- # HTTP Method Enum - REMOVED FROM HERE
51
- # ---------------------------------------------------------------------------
52
-
53
- # @unique
54
- # class HTTPMethod(str, Enum):
55
- # ...
56
-
57
-
58
- # ---------------------------------------------------------------------------
59
- # IR Dataclasses - REMOVED FROM HERE
60
- # ---------------------------------------------------------------------------
61
-
62
- # @dataclass(slots=True)
63
- # class IRParameter:
64
- # ...
65
-
66
- # @dataclass(slots=True)
67
- # class IRResponse:
68
- # ...
69
-
70
- # @dataclass(slots=True)
71
- # class IRRequestBody:
72
- # ...
73
-
74
- # @dataclass(slots=True)
75
- # class IROperation:
76
- # ...
77
-
78
- # @dataclass(slots=True)
79
- # class IRSchema:
80
- # ...
81
-
82
- # @dataclass(slots=True)
83
- # class IRSpec:
84
- # ...
85
-
53
+ __version__: str = "0.17.0"
86
54
 
87
55
  # ---------------------------------------------------------------------------
88
56
  # Lazy-loading and autocompletion support (This part remains)
@@ -91,6 +59,7 @@ if TYPE_CHECKING:
91
59
  # Imports for static analysis
92
60
  from .core.loader.loader import load_ir_from_spec # noqa: F401
93
61
  from .core.warning_collector import WarningCollector # noqa: F401
62
+ from .generator.client_generator import ClientGenerator, GenerationError # noqa: F401
94
63
 
95
64
  # Expose loader and collector at package level
96
65
  # __all__ is already updated above
@@ -103,12 +72,152 @@ def __getattr__(name: str) -> Any:
103
72
 
104
73
  return _func
105
74
  if name == "WarningCollector":
106
- from .core.warning_collector import WarningCollector as _cls
75
+ from .core.warning_collector import WarningCollector as _warning_cls
76
+
77
+ return _warning_cls
78
+ if name == "ClientGenerator":
79
+ from .generator.client_generator import ClientGenerator as _gen_cls
80
+
81
+ return _gen_cls
82
+ if name == "GenerationError":
83
+ from .generator.client_generator import GenerationError as _exc
107
84
 
108
- return _cls
85
+ return _exc
109
86
  raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
110
87
 
111
88
 
112
89
  def __dir__() -> List[str]:
113
90
  # Ensure dir() and completion shows all exports
114
91
  return __all__.copy()
92
+
93
+
94
+ # ---------------------------------------------------------------------------
95
+ # Main Programmatic API
96
+ # ---------------------------------------------------------------------------
97
+
98
+
99
+ def generate_client(
100
+ spec_path: str,
101
+ project_root: str,
102
+ output_package: str,
103
+ core_package: str | None = None,
104
+ force: bool = False,
105
+ no_postprocess: bool = False,
106
+ verbose: bool = False,
107
+ ) -> List[Path]:
108
+ """Generate a Python client from an OpenAPI specification.
109
+
110
+ This is the main entry point for programmatic usage of pyopenapi_gen.
111
+ It provides a simple function-based API for generating OpenAPI clients
112
+ without needing to instantiate classes or understand internal structure.
113
+
114
+ Args:
115
+ spec_path: Path to the OpenAPI specification file (YAML or JSON).
116
+ Can be a relative or absolute file path.
117
+
118
+ project_root: Root directory of your Python project where the generated
119
+ client will be placed. This is the directory that contains
120
+ your top-level Python packages.
121
+
122
+ output_package: Python package name for the generated client.
123
+ Uses dot notation (e.g., 'pyapis.my_client').
124
+ The client will be generated at:
125
+ {project_root}/{output_package_as_path}/
126
+
127
+ core_package: Optional Python package name for shared core runtime.
128
+ If not specified, defaults to {output_package}.core.
129
+ Use this when generating multiple clients that share
130
+ common runtime code (auth, config, http transport, etc.).
131
+
132
+ force: If True, overwrites existing output without diff checking.
133
+ If False (default), compares with existing output and only
134
+ updates if changes are detected.
135
+
136
+ no_postprocess: If True, skips post-processing (Black formatting and
137
+ mypy type checking). Useful for faster iteration during
138
+ development.
139
+
140
+ verbose: If True, prints detailed progress information during generation.
141
+
142
+ Returns:
143
+ List of Path objects for all generated files.
144
+
145
+ Raises:
146
+ GenerationError: If generation fails due to invalid spec, file I/O
147
+ errors, or other issues during code generation.
148
+
149
+ Examples:
150
+ Basic usage with default settings:
151
+
152
+ >>> from pyopenapi_gen import generate_client
153
+ >>>
154
+ >>> files = generate_client(
155
+ ... spec_path="input/openapi.yaml",
156
+ ... project_root=".",
157
+ ... output_package="pyapis.my_client"
158
+ ... )
159
+ >>> print(f"Generated {len(files)} files")
160
+
161
+ Generate multiple clients sharing a common core package:
162
+
163
+ >>> from pyopenapi_gen import generate_client
164
+ >>>
165
+ >>> # Generate first client (creates shared core)
166
+ >>> generate_client(
167
+ ... spec_path="api_v1.yaml",
168
+ ... project_root=".",
169
+ ... output_package="pyapis.client_v1",
170
+ ... core_package="pyapis.core"
171
+ ... )
172
+ >>>
173
+ >>> # Generate second client (reuses core)
174
+ >>> generate_client(
175
+ ... spec_path="api_v2.yaml",
176
+ ... project_root=".",
177
+ ... output_package="pyapis.client_v2",
178
+ ... core_package="pyapis.core"
179
+ ... )
180
+
181
+ Handle generation errors:
182
+
183
+ >>> from pyopenapi_gen import generate_client, GenerationError
184
+ >>>
185
+ >>> try:
186
+ ... generate_client(
187
+ ... spec_path="openapi.yaml",
188
+ ... project_root=".",
189
+ ... output_package="pyapis.my_client",
190
+ ... verbose=True
191
+ ... )
192
+ ... except GenerationError as e:
193
+ ... print(f"Generation failed: {e}")
194
+
195
+ Force regeneration with verbose output:
196
+
197
+ >>> generate_client(
198
+ ... spec_path="openapi.yaml",
199
+ ... project_root=".",
200
+ ... output_package="pyapis.my_client",
201
+ ... force=True,
202
+ ... verbose=True
203
+ ... )
204
+
205
+ Notes:
206
+ - Generated clients are completely self-contained and require no
207
+ runtime dependency on pyopenapi_gen
208
+ - All generated code is automatically formatted with Black and
209
+ type-checked with mypy (unless no_postprocess=True)
210
+ - The generated client uses modern async/await patterns with httpx
211
+ - Type hints are included for all generated code
212
+ """
213
+ from .generator.client_generator import ClientGenerator
214
+
215
+ generator = ClientGenerator(verbose=verbose)
216
+ return generator.generate(
217
+ spec_path=spec_path,
218
+ project_root=Path(project_root),
219
+ output_package=output_package,
220
+ core_package=core_package,
221
+ force=force,
222
+ no_postprocess=no_postprocess,
223
+ )
@@ -103,9 +103,9 @@ def parse_parameter(
103
103
  if not inline_is_specific:
104
104
  sch = comp_schema
105
105
  break
106
- except Exception:
107
- # Be conservative on any unexpected structure
108
- pass
106
+ except Exception as e:
107
+ # Log unexpected structure but continue with inline schema
108
+ logger.debug(f"Could not check component parameter for '{param_name}': {e}. Using inline schema.")
109
109
 
110
110
  # For parameters, we want to avoid creating complex schemas for simple enum arrays
111
111
  # Check if this is a simple enum array and handle it specially
@@ -1,4 +1,4 @@
1
- import subprocess
1
+ import subprocess # nosec B404 - Required for running code formatters (Black, Ruff) and mypy
2
2
  import sys
3
3
  from pathlib import Path
4
4
  from typing import List, Union
@@ -71,7 +71,7 @@ class PostprocessManager:
71
71
  """Remove unused imports from multiple targets using Ruff (bulk operation)."""
72
72
  if not targets:
73
73
  return
74
- result = subprocess.run(
74
+ result = subprocess.run( # nosec B603 - Controlled subprocess with hardcoded command
75
75
  [
76
76
  sys.executable,
77
77
  "-m",
@@ -95,7 +95,7 @@ class PostprocessManager:
95
95
  """Sort imports in multiple targets using Ruff (bulk operation)."""
96
96
  if not targets:
97
97
  return
98
- result = subprocess.run(
98
+ result = subprocess.run( # nosec B603 - Controlled subprocess with hardcoded command
99
99
  [
100
100
  sys.executable,
101
101
  "-m",
@@ -119,7 +119,7 @@ class PostprocessManager:
119
119
  """Format code in multiple targets using Ruff (bulk operation)."""
120
120
  if not targets:
121
121
  return
122
- result = subprocess.run(
122
+ result = subprocess.run( # nosec B603 - Controlled subprocess with hardcoded command
123
123
  [
124
124
  sys.executable,
125
125
  "-m",
@@ -139,7 +139,7 @@ class PostprocessManager:
139
139
 
140
140
  def remove_unused_imports(self, target: Union[str, Path]) -> None:
141
141
  """Remove unused imports from the target using Ruff."""
142
- result = subprocess.run(
142
+ result = subprocess.run( # nosec B603 - Controlled subprocess with hardcoded command
143
143
  [
144
144
  sys.executable,
145
145
  "-m",
@@ -161,7 +161,7 @@ class PostprocessManager:
161
161
 
162
162
  def sort_imports(self, target: Union[str, Path]) -> None:
163
163
  """Sort imports in the target using Ruff."""
164
- result = subprocess.run(
164
+ result = subprocess.run( # nosec B603 - Controlled subprocess with hardcoded command
165
165
  [
166
166
  sys.executable,
167
167
  "-m",
@@ -183,7 +183,7 @@ class PostprocessManager:
183
183
 
184
184
  def format_code(self, target: Union[str, Path]) -> None:
185
185
  """Format code in the target using Ruff."""
186
- result = subprocess.run(
186
+ result = subprocess.run( # nosec B603 - Controlled subprocess with hardcoded command
187
187
  [
188
188
  sys.executable,
189
189
  "-m",
@@ -223,7 +223,7 @@ class PostprocessManager:
223
223
  cmd.append("--cache-dir=/tmp/mypy_cache_temp")
224
224
  cmd.extend([str(f) for f in python_files])
225
225
 
226
- result = subprocess.run(
226
+ result = subprocess.run( # nosec B603 - Controlled subprocess with hardcoded command
227
227
  cmd,
228
228
  stdout=subprocess.PIPE,
229
229
  stderr=subprocess.PIPE,
@@ -62,6 +62,8 @@ class TelemetryClient:
62
62
  # Using print as a stub for actual telemetry transport
63
63
  # In production, this would be replaced with a proper telemetry client
64
64
  print("TELEMETRY", json.dumps(data))
65
- except Exception:
66
- # Silently ignore any telemetry errors to avoid affecting main execution
67
- pass
65
+ except Exception as e:
66
+ # Telemetry failures should not affect execution, but log for debugging
67
+ import logging
68
+
69
+ logging.getLogger(__name__).debug(f"Telemetry event failed: {e}")
@@ -289,6 +289,7 @@ class OpenAPISchemaResolver(SchemaTypeResolver):
289
289
  "hostname": "str",
290
290
  "ipv4": "str",
291
291
  "ipv6": "str",
292
+ "binary": "bytes",
292
293
  }
293
294
 
294
295
  python_type = format_mapping.get(format_type, "str")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyopenapi-gen
3
- Version: 0.15.0
3
+ Version: 0.17.0
4
4
  Summary: Modern, async-first Python client generator for OpenAPI specifications with advanced cycle detection and unified type resolution
5
5
  Project-URL: Homepage, https://github.com/your-org/pyopenapi-gen
6
6
  Project-URL: Documentation, https://github.com/your-org/pyopenapi-gen/blob/main/README.md
@@ -1,4 +1,4 @@
1
- pyopenapi_gen/__init__.py,sha256=AUYdbyW8dbrYxo2DvttZkFO79T5QHsS96P8z4TU8y6U,3017
1
+ pyopenapi_gen/__init__.py,sha256=nLxgWZNVSv0wDYh4S5NlwKP7DrQGisi4J3IL7WCmLX8,7533
2
2
  pyopenapi_gen/__main__.py,sha256=4-SCaCNhBd7rtyRK58uoDbdl93J0KhUeajP_b0CPpLE,110
3
3
  pyopenapi_gen/cli.py,sha256=9T_XF3-ih_JlM_BOkmHft-HoMCGOqL5UrnAHBJ0fb5w,2320
4
4
  pyopenapi_gen/http_types.py,sha256=EMMYZBt8PNVZKPFu77TQija-JI-nOKyXvpiQP9-VSWE,467
@@ -14,10 +14,10 @@ pyopenapi_gen/core/exceptions.py,sha256=HYFiYdmzsZUl46vB8M3B6Vpp6m8iqjUcKDWdL4yE
14
14
  pyopenapi_gen/core/http_status_codes.py,sha256=nn8QdXmkv5BQA9C-HTn9wmAVWyEyB9bAHpHve6EZX4M,6249
15
15
  pyopenapi_gen/core/http_transport.py,sha256=Ssyh5a4wWWZlvxG1jhTmynZUTBe5rtVgyDK5FIUXyos,9617
16
16
  pyopenapi_gen/core/pagination.py,sha256=mXSOyfVblWF0jc8dnqXIi0Vgj2UxC_uO_WuvdRIBzs4,2295
17
- pyopenapi_gen/core/postprocess_manager.py,sha256=cia8FbDXbulk44ElT1CTlypu1oFjNM41y1gWy8-HSug,9362
17
+ pyopenapi_gen/core/postprocess_manager.py,sha256=Ia4H47lInhVxkHnDB46t4U-6BLpLXzhrmFaLSRmc8Uw,9865
18
18
  pyopenapi_gen/core/schemas.py,sha256=Ngehd-Aj4Drs_5i7eL0L5Eu1B_kfT4qxL94TJ_H_I7w,5523
19
19
  pyopenapi_gen/core/streaming_helpers.py,sha256=5fkzH9xsgzZWOTKFrZpmje07S7n7CcOpjteb5dig7ds,2664
20
- pyopenapi_gen/core/telemetry.py,sha256=vZ8PqNctndrmII9I1EHCyvp2bVGofBuHjm7II0tIqps,2203
20
+ pyopenapi_gen/core/telemetry.py,sha256=LNMrlrUNVcp591w9cX4uvzlFrPB6ZZoGRIuCOHlDCqA,2296
21
21
  pyopenapi_gen/core/utils.py,sha256=rgKNyoOXRleGueUFItM5fpxUxwNMJOGISC_XxSPCPLg,14141
22
22
  pyopenapi_gen/core/warning_collector.py,sha256=DYl9D7eZYs04mDU84KeonS-5-d0aM7hNqraXTex31ss,2799
23
23
  pyopenapi_gen/core/auth/base.py,sha256=E2KUerA_mYv9D7xulUm-lenIxqZHqanjA4oRKpof2ZE,792
@@ -29,7 +29,7 @@ pyopenapi_gen/core/loader/operations/parser.py,sha256=E53NCTdTp9a6VKxhSx_6r-jP3B
29
29
  pyopenapi_gen/core/loader/operations/post_processor.py,sha256=Rzb3GSiLyJk-0hTBZ6s6iWAj4KqE4Rfo3w-q2wm_R7w,2487
30
30
  pyopenapi_gen/core/loader/operations/request_body.py,sha256=A6kX2oyNh3Ag_h-Flk8gDzZjiUl3lq8F6jGb3o-UhFg,3205
31
31
  pyopenapi_gen/core/loader/parameters/__init__.py,sha256=p13oSibCRC5RCfsP6w7yD9MYs5TXcdI4WwPv7oGUYKk,284
32
- pyopenapi_gen/core/loader/parameters/parser.py,sha256=0qGFcbnyWXacrnbxDPicw59JVwl3B3f4NP4tXVJBS9E,8033
32
+ pyopenapi_gen/core/loader/parameters/parser.py,sha256=TROPMq6iyL9Somh7K1ADjqyDGXL0s4Wcieg6hM5vdYE,8145
33
33
  pyopenapi_gen/core/loader/responses/__init__.py,sha256=6APWoH3IdNkgVmI0KsgZoZ6knDaG-S-pnUCa6gkzT8E,216
34
34
  pyopenapi_gen/core/loader/responses/parser.py,sha256=6dZ86WvyBxPq68UQjF_F8F2FtmGtMm5ZB8UjKXZTu9Y,4063
35
35
  pyopenapi_gen/core/loader/schemas/__init__.py,sha256=rlhujYfw_IzWgzhVhYMJ3eIhE6C5Vi1Ylba-BHEVqOg,296
@@ -98,7 +98,7 @@ pyopenapi_gen/types/contracts/types.py,sha256=k3tCjMu6M1zcVXF6jzr-Q9S6LXaMEMoKoO
98
98
  pyopenapi_gen/types/resolvers/__init__.py,sha256=_5kA49RvyOTyXgt0GbbOfHJcdQw2zHxvU9af8GGyNWc,295
99
99
  pyopenapi_gen/types/resolvers/reference_resolver.py,sha256=ue6gw665Wo07POuAg5820A6AXiCVyQY-unfIzGjxrSY,2034
100
100
  pyopenapi_gen/types/resolvers/response_resolver.py,sha256=_W6-Z_SBQ8tDMd_VqveO5AiFs7Od7eAuqevpUIDCT5o,6481
101
- pyopenapi_gen/types/resolvers/schema_resolver.py,sha256=PWw77fPh8SG4i1YD45-cOmjmrA1HQkIz1u75AILlvM0,23617
101
+ pyopenapi_gen/types/resolvers/schema_resolver.py,sha256=GoL2z_uMrS3Jsp1eN0_eAfyXyRxPnryUt6o-9UXcO3I,23652
102
102
  pyopenapi_gen/types/services/__init__.py,sha256=inSUKmY_Vnuym6tC-AhvjCTj16GbkfxCGLESRr_uQPE,123
103
103
  pyopenapi_gen/types/services/type_service.py,sha256=3IH0GBLeC-ruokBmhvC8iR5GT5tmFg1n6tcLZYa5TSk,6998
104
104
  pyopenapi_gen/types/strategies/__init__.py,sha256=bju8_KEPNIow1-woMO-zJCgK_E0M6JnFq0NFsK1R4Ss,171
@@ -125,8 +125,8 @@ pyopenapi_gen/visit/model/alias_generator.py,sha256=wEMHipPA1_CFxvQ6CS9j4qgXK93s
125
125
  pyopenapi_gen/visit/model/dataclass_generator.py,sha256=L794s2yyYUO__akGd120NJMV7g2xqiPfQyZo8CduIVM,14426
126
126
  pyopenapi_gen/visit/model/enum_generator.py,sha256=AXqKUFuWUUjUF_6_HqBKY8vB5GYu35Pb2C2WPFrOw1k,10061
127
127
  pyopenapi_gen/visit/model/model_visitor.py,sha256=TC6pbxpQiy5FWhmQpfllLuXA3ImTYNMcrazkOFZCIyo,9470
128
- pyopenapi_gen-0.15.0.dist-info/METADATA,sha256=iRFEkYUsm_-6q06MRBl03b1y3J7Utw1xqiZ7NF6aFSQ,14025
129
- pyopenapi_gen-0.15.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
130
- pyopenapi_gen-0.15.0.dist-info/entry_points.txt,sha256=gxSlNiwom50T3OEZnlocA6qRjGdV0bn6hN_Xr-Ub5wA,56
131
- pyopenapi_gen-0.15.0.dist-info/licenses/LICENSE,sha256=UFAyTWKa4w10-QerlJaHJeep7G2gcwpf-JmvI2dS2Gc,1088
132
- pyopenapi_gen-0.15.0.dist-info/RECORD,,
128
+ pyopenapi_gen-0.17.0.dist-info/METADATA,sha256=YkgkvtWTtSfD9_1lhlfVYRRwM9pzBYpR7HVKROSP0C0,14025
129
+ pyopenapi_gen-0.17.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
130
+ pyopenapi_gen-0.17.0.dist-info/entry_points.txt,sha256=gxSlNiwom50T3OEZnlocA6qRjGdV0bn6hN_Xr-Ub5wA,56
131
+ pyopenapi_gen-0.17.0.dist-info/licenses/LICENSE,sha256=UFAyTWKa4w10-QerlJaHJeep7G2gcwpf-JmvI2dS2Gc,1088
132
+ pyopenapi_gen-0.17.0.dist-info/RECORD,,