pyopenapi-gen 0.16.1__py3-none-any.whl → 0.18.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
@@ -50,7 +50,7 @@ __all__ = [
50
50
  ]
51
51
 
52
52
  # Semantic version of the generator core – automatically managed by semantic-release.
53
- __version__: str = "0.16.1"
53
+ __version__: str = "0.18.0"
54
54
 
55
55
  # ---------------------------------------------------------------------------
56
56
  # Lazy-loading and autocompletion support (This part remains)
@@ -10,6 +10,7 @@ from ....types.strategies import ResponseStrategyResolver
10
10
  from ..processors.import_analyzer import EndpointImportAnalyzer
11
11
  from ..processors.parameter_processor import EndpointParameterProcessor
12
12
  from .docstring_generator import EndpointDocstringGenerator
13
+ from .overload_generator import OverloadMethodGenerator
13
14
  from .request_generator import EndpointRequestGenerator
14
15
  from .response_handler_generator import EndpointResponseHandlerGenerator
15
16
  from .signature_generator import EndpointMethodSignatureGenerator
@@ -34,13 +35,16 @@ class EndpointMethodGenerator:
34
35
  self.url_args_generator = EndpointUrlArgsGenerator(self.schemas)
35
36
  self.request_generator = EndpointRequestGenerator(self.schemas)
36
37
  self.response_handler_generator = EndpointResponseHandlerGenerator(self.schemas)
38
+ self.overload_generator = OverloadMethodGenerator(self.schemas)
37
39
 
38
40
  def generate(self, op: IROperation, context: RenderContext) -> str:
39
41
  """
40
42
  Generate a fully functional async endpoint method for the given operation.
41
43
  Returns the method code as a string.
44
+
45
+ If the operation has multiple content types, generates @overload signatures
46
+ followed by the implementation method with runtime dispatch.
42
47
  """
43
- writer = CodeWriter()
44
48
  context.add_import(f"{context.core_package_name}.http_transport", "HttpTransport")
45
49
  context.add_import(f"{context.core_package_name}.exceptions", "HTTPError")
46
50
 
@@ -51,6 +55,16 @@ class EndpointMethodGenerator:
51
55
  # Pass the response strategy to import analyzer for consistent import resolution
52
56
  self.import_analyzer.analyze_and_register_imports(op, context, response_strategy)
53
57
 
58
+ # Check if operation has multiple content types
59
+ if self.overload_generator.has_multiple_content_types(op):
60
+ return self._generate_overloaded_method(op, context, response_strategy)
61
+ else:
62
+ return self._generate_standard_method(op, context, response_strategy)
63
+
64
+ def _generate_standard_method(self, op: IROperation, context: RenderContext, response_strategy: Any) -> str:
65
+ """Generate standard method without overloads."""
66
+ writer = CodeWriter()
67
+
54
68
  ordered_params, primary_content_type, resolved_body_type = self.parameter_processor.process_parameters(
55
69
  op, context
56
70
  )
@@ -91,3 +105,111 @@ class EndpointMethodGenerator:
91
105
  writer.dedent() # This matches the indent() from _write_method_signature
92
106
 
93
107
  return writer.get_code().strip()
108
+
109
+ def _generate_overloaded_method(self, op: IROperation, context: RenderContext, response_strategy: Any) -> str:
110
+ """Generate method with @overload signatures for multiple content types."""
111
+ parts = []
112
+
113
+ # Generate overload signatures
114
+ overload_sigs = self.overload_generator.generate_overload_signatures(op, context, response_strategy)
115
+ parts.extend(overload_sigs)
116
+
117
+ # Generate implementation method
118
+ impl_method = self._generate_implementation_method(op, context, response_strategy)
119
+ parts.append(impl_method)
120
+
121
+ # Join with double newlines between overloads and implementation
122
+ return "\n\n".join(parts)
123
+
124
+ def _generate_implementation_method(self, op: IROperation, context: RenderContext, response_strategy: Any) -> str:
125
+ """Generate the implementation method with runtime dispatch for multiple content types."""
126
+ # Type narrowing: request_body is guaranteed to exist when this method is called
127
+ assert (
128
+ op.request_body is not None
129
+ ), "request_body should not be None in _generate_implementation_method" # nosec B101 - Type narrowing for mypy, validated by has_multiple_content_types
130
+
131
+ writer = CodeWriter()
132
+
133
+ # Generate implementation signature (accepts all content-type parameters as optional)
134
+ impl_sig = self.overload_generator.generate_implementation_signature(op, context, response_strategy)
135
+ writer.write_block(impl_sig)
136
+
137
+ # Generate docstring
138
+ ordered_params, primary_content_type, _ = self.parameter_processor.process_parameters(op, context)
139
+ writer.indent()
140
+ writer.write_line('"""')
141
+ writer.write_line(f"{op.summary or op.operation_id}")
142
+ writer.write_line("")
143
+ writer.write_line("Supports multiple content types:")
144
+ for content_type in op.request_body.content.keys():
145
+ writer.write_line(f"- {content_type}")
146
+ writer.write_line('"""')
147
+
148
+ # Generate URL construction
149
+ writer.write_line(f'url = f"{{self.base_url}}{op.path}"')
150
+ writer.write_line("")
151
+
152
+ # Generate runtime dispatch logic
153
+ writer.write_line("# Runtime dispatch based on content type")
154
+
155
+ first_content_type = True
156
+ for content_type in op.request_body.content.keys():
157
+ param_info = self.overload_generator._get_content_type_param_info(
158
+ content_type, op.request_body.content[content_type], context
159
+ )
160
+
161
+ if first_content_type:
162
+ writer.write_line(f"if {param_info['name']} is not None:")
163
+ first_content_type = False
164
+ else:
165
+ writer.write_line(f"elif {param_info['name']} is not None:")
166
+
167
+ writer.indent()
168
+
169
+ # Generate request call for this content type
170
+ if content_type == "application/json":
171
+ writer.write_line(f"json_body = {param_info['name']}")
172
+ writer.write_line("response = await self._transport.request(")
173
+ writer.indent()
174
+ writer.write_line(f'"{op.method.value.upper()}", url,')
175
+ writer.write_line("params=None,")
176
+ writer.write_line("json=json_body,")
177
+ writer.write_line("headers=None")
178
+ writer.dedent()
179
+ writer.write_line(")")
180
+ elif content_type == "multipart/form-data":
181
+ writer.write_line(f"files_data = {param_info['name']}")
182
+ writer.write_line("response = await self._transport.request(")
183
+ writer.indent()
184
+ writer.write_line(f'"{op.method.value.upper()}", url,')
185
+ writer.write_line("params=None,")
186
+ writer.write_line("files=files_data,")
187
+ writer.write_line("headers=None")
188
+ writer.dedent()
189
+ writer.write_line(")")
190
+ else:
191
+ writer.write_line(f"data = {param_info['name']}")
192
+ writer.write_line("response = await self._transport.request(")
193
+ writer.indent()
194
+ writer.write_line(f'"{op.method.value.upper()}", url,')
195
+ writer.write_line("params=None,")
196
+ writer.write_line("data=data,")
197
+ writer.write_line("headers=None")
198
+ writer.dedent()
199
+ writer.write_line(")")
200
+
201
+ writer.dedent()
202
+
203
+ # Add else clause for error
204
+ writer.write_line("else:")
205
+ writer.indent()
206
+ writer.write_line('raise ValueError("One of the content-type parameters must be provided")')
207
+ writer.dedent()
208
+ writer.write_line("")
209
+
210
+ # Generate response handling (reuse existing generator)
211
+ self.response_handler_generator.generate_response_handling(writer, op, context, response_strategy)
212
+
213
+ writer.dedent()
214
+
215
+ return writer.get_code().strip()
@@ -0,0 +1,243 @@
1
+ """Generator for @overload signatures when operations have multiple content types."""
2
+
3
+ import logging
4
+ from typing import Any
5
+
6
+ from pyopenapi_gen import IROperation
7
+
8
+ from ....context.render_context import RenderContext
9
+ from ....core.writers.code_writer import CodeWriter
10
+ from ..processors.parameter_processor import EndpointParameterProcessor
11
+ from .docstring_generator import EndpointDocstringGenerator
12
+ from .signature_generator import EndpointMethodSignatureGenerator
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class OverloadMethodGenerator:
18
+ """
19
+ Generates @overload signatures for operations with multiple content types.
20
+
21
+ When an operation's request body accepts multiple content types
22
+ (e.g., application/json and multipart/form-data), this generator creates
23
+ type-safe @overload signatures following PEP 484.
24
+ """
25
+
26
+ def __init__(self, schemas: dict[str, Any] | None = None) -> None:
27
+ self.schemas = schemas or {}
28
+ self.parameter_processor = EndpointParameterProcessor(self.schemas)
29
+ self.signature_generator = EndpointMethodSignatureGenerator(self.schemas)
30
+ self.docstring_generator = EndpointDocstringGenerator(self.schemas)
31
+
32
+ def has_multiple_content_types(self, op: IROperation) -> bool:
33
+ """
34
+ Check if operation request body has multiple content types.
35
+
36
+ Args:
37
+ op: The operation to check
38
+
39
+ Returns:
40
+ True if operation has request body with multiple content types
41
+ """
42
+ if not op.request_body:
43
+ return False
44
+ return len(op.request_body.content) > 1
45
+
46
+ def generate_overload_signatures(
47
+ self, op: IROperation, context: RenderContext, response_strategy: Any
48
+ ) -> list[str]:
49
+ """
50
+ Generate @overload signatures for each content type.
51
+
52
+ Args:
53
+ op: The operation with multiple content types
54
+ context: Render context for import tracking
55
+ response_strategy: Response strategy for return type resolution
56
+
57
+ Returns:
58
+ List of @overload signature code strings
59
+ """
60
+ if not self.has_multiple_content_types(op):
61
+ return []
62
+
63
+ # Ensure typing.overload is imported
64
+ context.add_import("typing", "overload")
65
+ context.add_import("typing", "Literal")
66
+ context.add_import("typing", "IO")
67
+ context.add_import("typing", "Any")
68
+
69
+ overload_signatures = []
70
+
71
+ # Type narrowing: request_body is guaranteed to exist by has_multiple_content_types check
72
+ assert (
73
+ op.request_body is not None
74
+ ), "request_body should not be None after has_multiple_content_types check" # nosec B101 - Type narrowing for mypy, validated by has_multiple_content_types
75
+
76
+ for content_type, schema in op.request_body.content.items():
77
+ signature_code = self._generate_single_overload(op, content_type, schema, context, response_strategy)
78
+ overload_signatures.append(signature_code)
79
+
80
+ return overload_signatures
81
+
82
+ def _generate_single_overload(
83
+ self,
84
+ op: IROperation,
85
+ content_type: str,
86
+ schema: Any,
87
+ context: RenderContext,
88
+ response_strategy: Any,
89
+ ) -> str:
90
+ """
91
+ Generate a single @overload signature for one content type.
92
+
93
+ Args:
94
+ op: The operation
95
+ content_type: The media type (e.g., "application/json")
96
+ schema: The schema for this content type
97
+ context: Render context
98
+ response_strategy: Response strategy for return type
99
+
100
+ Returns:
101
+ @overload signature code as string
102
+ """
103
+ writer = CodeWriter()
104
+
105
+ # Write @overload decorator
106
+ writer.write_line("@overload")
107
+
108
+ # Determine parameter name and type based on content type
109
+ param_info = self._get_content_type_param_info(content_type, schema, context)
110
+
111
+ # Build parameter list from operation parameters directly
112
+ param_parts = ["self"]
113
+
114
+ # Add path, query, and header parameters from operation
115
+ if op.parameters:
116
+ from ....types.services.type_service import UnifiedTypeService
117
+
118
+ type_service = UnifiedTypeService(self.schemas)
119
+
120
+ for param in op.parameters:
121
+ if param.param_in in ("path", "query", "header"):
122
+ param_type = type_service.resolve_schema_type(param.schema, context, required=param.required)
123
+ param_parts.append(f"{param.name}: {param_type}")
124
+
125
+ # Add keyword-only separator
126
+ param_parts.append("*")
127
+
128
+ # Add content-type-specific parameter
129
+ param_parts.append(f"{param_info['name']}: {param_info['type']}")
130
+
131
+ # Add content_type parameter with Literal type
132
+ param_parts.append(f'content_type: Literal["{content_type}"] = "{content_type}"')
133
+
134
+ # Get return type from response strategy
135
+ return_type = response_strategy.return_type
136
+
137
+ # Write signature
138
+ params_str = ",\n ".join(param_parts)
139
+ writer.write_line(f"async def {op.operation_id}(")
140
+ writer.indent()
141
+ writer.write_line(params_str)
142
+ writer.dedent()
143
+ writer.write_line(f") -> {return_type}: ...")
144
+
145
+ return writer.get_code()
146
+
147
+ def _get_content_type_param_info(self, content_type: str, schema: Any, context: RenderContext) -> dict[str, str]:
148
+ """
149
+ Get parameter name and type hint for a content type.
150
+
151
+ Args:
152
+ content_type: Media type string
153
+ schema: Schema for this content type
154
+ context: Render context for type resolution
155
+
156
+ Returns:
157
+ Dictionary with 'name' and 'type' keys
158
+ """
159
+ from ....types.services.type_service import UnifiedTypeService
160
+
161
+ type_service = UnifiedTypeService(self.schemas)
162
+
163
+ # Map content types to parameter names and base types
164
+ if content_type == "application/json":
165
+ # For JSON, resolve the actual schema type
166
+ type_hint = type_service.resolve_schema_type(schema, context, required=True)
167
+ return {"name": "body", "type": type_hint}
168
+
169
+ elif content_type == "multipart/form-data":
170
+ # For multipart, always use files dict
171
+ return {"name": "files", "type": "dict[str, IO[Any]]"}
172
+
173
+ elif content_type == "application/x-www-form-urlencoded":
174
+ # For form data, use dict
175
+ return {"name": "data", "type": "dict[str, str]"}
176
+
177
+ else:
178
+ # Default: use body with Any type
179
+ logger.warning(f"Unknown content type {content_type}, using body: Any")
180
+ return {"name": "body", "type": "Any"}
181
+
182
+ def generate_implementation_signature(self, op: IROperation, context: RenderContext, response_strategy: Any) -> str:
183
+ """
184
+ Generate the actual implementation method signature with optional parameters.
185
+
186
+ This signature accepts all possible content-type parameters as optional,
187
+ and includes runtime dispatch logic.
188
+
189
+ Args:
190
+ op: The operation
191
+ context: Render context
192
+ response_strategy: Response strategy for return type
193
+
194
+ Returns:
195
+ Implementation method signature code
196
+ """
197
+ writer = CodeWriter()
198
+
199
+ # Build parameter list
200
+ param_parts = ["self"]
201
+
202
+ # Add path, query, and header parameters from operation
203
+ if op.parameters:
204
+ from ....types.services.type_service import UnifiedTypeService
205
+
206
+ type_service = UnifiedTypeService(self.schemas)
207
+
208
+ for param in op.parameters:
209
+ if param.param_in in ("path", "query", "header"):
210
+ param_type = type_service.resolve_schema_type(param.schema, context, required=param.required)
211
+ param_parts.append(f"{param.name}: {param_type}")
212
+
213
+ # Add keyword-only separator
214
+ param_parts.append("*")
215
+
216
+ # Add all possible content-type parameters as optional
217
+ if op.request_body:
218
+ param_types_seen = set()
219
+
220
+ for content_type, schema in op.request_body.content.items():
221
+ param_info = self._get_content_type_param_info(content_type, schema, context)
222
+
223
+ # Avoid duplicate parameters (e.g., if multiple JSON variants)
224
+ param_key = param_info["name"]
225
+ if param_key not in param_types_seen:
226
+ param_parts.append(f"{param_info['name']}: {param_info['type']} | None = None")
227
+ param_types_seen.add(param_key)
228
+
229
+ # Add content_type parameter (no Literal, just str)
230
+ param_parts.append('content_type: str = "application/json"')
231
+
232
+ # Get return type
233
+ return_type = response_strategy.return_type
234
+
235
+ # Write signature
236
+ params_str = ",\n ".join(param_parts)
237
+ writer.write_line(f"async def {op.operation_id}(")
238
+ writer.indent()
239
+ writer.write_line(params_str)
240
+ writer.dedent()
241
+ writer.write_line(f") -> {return_type}:")
242
+
243
+ return writer.get_code()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyopenapi-gen
3
- Version: 0.16.1
3
+ Version: 0.18.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
@@ -123,6 +123,254 @@ async def main():
123
123
  asyncio.run(main())
124
124
  ```
125
125
 
126
+ ## 🐍 Using as a Library (Programmatic API)
127
+
128
+ ### Why Programmatic Usage?
129
+ The generator was designed to work both as a CLI tool and as a Python library. Programmatic usage enables integration with build systems, CI/CD pipelines, code generators, and custom tooling. You get the same powerful code generation capabilities with full Python API access.
130
+
131
+ ### What Is the Programmatic API?
132
+ A simple, function-based API that wraps the internal `ClientGenerator` class, providing a clean entry point for library usage without requiring knowledge of internal structure.
133
+
134
+ ```mermaid
135
+ graph TD
136
+ A[Your Build Script] --> B[generate_client Function]
137
+ B --> C[ClientGenerator]
138
+ C --> D[Load OpenAPI Spec]
139
+ D --> E[Generate Code]
140
+ E --> F[Write Files]
141
+ F --> G[Post-Process]
142
+ G --> H[Return File List]
143
+
144
+ subgraph "Public API"
145
+ B
146
+ I[ClientGenerator Class]
147
+ J[GenerationError Exception]
148
+ end
149
+
150
+ subgraph "Advanced API"
151
+ K[load_ir_from_spec]
152
+ L[IR Models]
153
+ M[WarningCollector]
154
+ end
155
+ ```
156
+
157
+ ### How to Use Programmatically
158
+
159
+ #### Basic Usage
160
+ ```python
161
+ from pyopenapi_gen import generate_client
162
+
163
+ # Simple client generation
164
+ files = generate_client(
165
+ spec_path="input/openapi.yaml",
166
+ project_root=".",
167
+ output_package="pyapis.my_client"
168
+ )
169
+
170
+ print(f"Generated {len(files)} files")
171
+ ```
172
+
173
+ #### Advanced Usage with All Options
174
+ ```python
175
+ from pyopenapi_gen import generate_client, GenerationError
176
+
177
+ try:
178
+ files = generate_client(
179
+ spec_path="input/openapi.yaml",
180
+ project_root=".",
181
+ output_package="pyapis.my_client",
182
+ core_package="pyapis.core", # Optional shared core
183
+ force=True, # Overwrite without diff check
184
+ no_postprocess=False, # Run Black + mypy
185
+ verbose=True # Show progress
186
+ )
187
+
188
+ # Process generated files
189
+ for file_path in files:
190
+ print(f"Generated: {file_path}")
191
+
192
+ except GenerationError as e:
193
+ print(f"Generation failed: {e}")
194
+ ```
195
+
196
+ #### Multi-Client Generation Script
197
+ ```python
198
+ from pyopenapi_gen import generate_client
199
+ from pathlib import Path
200
+
201
+ # Configuration for multiple clients
202
+ clients = [
203
+ {"spec": "api_v1.yaml", "package": "pyapis.client_v1"},
204
+ {"spec": "api_v2.yaml", "package": "pyapis.client_v2"},
205
+ ]
206
+
207
+ # Shared core package
208
+ core_package = "pyapis.core"
209
+
210
+ # Generate all clients
211
+ for client_config in clients:
212
+ print(f"Generating {client_config['package']}...")
213
+
214
+ generate_client(
215
+ spec_path=client_config["spec"],
216
+ project_root=".",
217
+ output_package=client_config["package"],
218
+ core_package=core_package,
219
+ force=True,
220
+ verbose=True
221
+ )
222
+
223
+ print("All clients generated successfully!")
224
+ ```
225
+
226
+ #### Integration with Build Systems
227
+ ```python
228
+ # Example: Custom build script
229
+ import sys
230
+ from pathlib import Path
231
+ from pyopenapi_gen import generate_client, GenerationError
232
+
233
+ def build_api_clients():
234
+ """Generate all API clients as part of build process"""
235
+
236
+ specs_dir = Path("specs")
237
+
238
+ # Find all OpenAPI specs
239
+ spec_files = list(specs_dir.glob("*.yaml")) + list(specs_dir.glob("*.json"))
240
+
241
+ if not spec_files:
242
+ print("No OpenAPI specs found in specs/")
243
+ return False
244
+
245
+ # Generate clients
246
+ for spec_file in spec_files:
247
+ client_name = spec_file.stem
248
+ package_name = f"pyapis.{client_name}"
249
+
250
+ print(f"Generating client for {spec_file.name}...")
251
+
252
+ try:
253
+ generate_client(
254
+ spec_path=str(spec_file),
255
+ project_root="src",
256
+ output_package=package_name,
257
+ core_package="pyapis.core",
258
+ force=True
259
+ )
260
+ except GenerationError as e:
261
+ print(f"Failed to generate {client_name}: {e}", file=sys.stderr)
262
+ return False
263
+
264
+ return True
265
+
266
+ if __name__ == "__main__":
267
+ success = build_api_clients()
268
+ sys.exit(0 if success else 1)
269
+ ```
270
+
271
+ ### API Reference
272
+
273
+ #### `generate_client()` Function
274
+
275
+ ```python
276
+ def generate_client(
277
+ spec_path: str,
278
+ project_root: str,
279
+ output_package: str,
280
+ core_package: str | None = None,
281
+ force: bool = False,
282
+ no_postprocess: bool = False,
283
+ verbose: bool = False,
284
+ ) -> List[Path]
285
+ ```
286
+
287
+ **Parameters**:
288
+ - `spec_path`: Path to OpenAPI spec file (YAML or JSON)
289
+ - `project_root`: Root directory of your Python project
290
+ - `output_package`: Python package name (e.g., `'pyapis.my_client'`)
291
+ - `core_package`: Optional shared core package name (defaults to `{output_package}.core`)
292
+ - `force`: Skip diff check and overwrite existing output
293
+ - `no_postprocess`: Skip Black formatting and mypy type checking
294
+ - `verbose`: Print detailed progress information
295
+
296
+ **Returns**: List of `Path` objects for all generated files
297
+
298
+ **Raises**: `GenerationError` if generation fails
299
+
300
+ #### `ClientGenerator` Class (Advanced)
301
+
302
+ For advanced use cases requiring more control:
303
+
304
+ ```python
305
+ from pyopenapi_gen import ClientGenerator, GenerationError
306
+ from pathlib import Path
307
+
308
+ # Create generator with custom settings
309
+ generator = ClientGenerator(verbose=True)
310
+
311
+ # Generate with full control
312
+ try:
313
+ files = generator.generate(
314
+ spec_path="openapi.yaml",
315
+ project_root=Path("."),
316
+ output_package="pyapis.my_client",
317
+ core_package="pyapis.core",
318
+ force=False,
319
+ no_postprocess=False
320
+ )
321
+ except GenerationError as e:
322
+ print(f"Generation failed: {e}")
323
+ ```
324
+
325
+ #### `GenerationError` Exception
326
+
327
+ Raised when generation fails. Contains contextual information about the failure:
328
+
329
+ ```python
330
+ from pyopenapi_gen import generate_client, GenerationError
331
+
332
+ try:
333
+ generate_client(
334
+ spec_path="invalid.yaml",
335
+ project_root=".",
336
+ output_package="test"
337
+ )
338
+ except GenerationError as e:
339
+ # Exception message includes context
340
+ print(f"Error: {e}")
341
+ # Typical causes:
342
+ # - Invalid OpenAPI specification
343
+ # - File I/O errors
344
+ # - Type checking failures
345
+ # - Invalid project structure
346
+ ```
347
+
348
+ ### Comparison: CLI vs Programmatic API
349
+
350
+ **CLI Usage**:
351
+ ```bash
352
+ pyopenapi-gen input/openapi.yaml \
353
+ --project-root . \
354
+ --output-package pyapis.my_client \
355
+ --force \
356
+ --verbose
357
+ ```
358
+
359
+ **Equivalent Programmatic Usage**:
360
+ ```python
361
+ from pyopenapi_gen import generate_client
362
+
363
+ generate_client(
364
+ spec_path="input/openapi.yaml",
365
+ project_root=".",
366
+ output_package="pyapis.my_client",
367
+ force=True,
368
+ verbose=True
369
+ )
370
+ ```
371
+
372
+ Both approaches use the same underlying implementation and produce identical results.
373
+
126
374
  ## 🔧 Configuration Options
127
375
 
128
376
  ### Standalone Client (Default)
@@ -1,4 +1,4 @@
1
- pyopenapi_gen/__init__.py,sha256=Rsdh_Bq_KbSs8h3R5rbT8FlYZ4Ps-IsdoGIe-1jdmfQ,7533
1
+ pyopenapi_gen/__init__.py,sha256=PuqDvWUTyOlVU2Et6YBu9am_aSyMIC-O3YrbFuGnEXY,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
@@ -112,7 +112,8 @@ pyopenapi_gen/visit/endpoint/__init__.py,sha256=DftIZSWp6Z8jKWoJE2VGKL4G_5cqwFXe
112
112
  pyopenapi_gen/visit/endpoint/endpoint_visitor.py,sha256=1aS0i2D_Y-979_7aBd0W6IS2UVO6wMujsMMw8Qt8PRE,3574
113
113
  pyopenapi_gen/visit/endpoint/generators/__init__.py,sha256=-X-GYnJZ9twiEBr_U0obW8VuSoY6IJmYaxinn-seMzA,50
114
114
  pyopenapi_gen/visit/endpoint/generators/docstring_generator.py,sha256=_VpRabZ2g_N42TTWGI6gtPxqzlZD65dOHm8U_YvtWbQ,5891
115
- pyopenapi_gen/visit/endpoint/generators/endpoint_method_generator.py,sha256=wUJ4_gaA1gRrFCHYFCObBIankxGQu0MNqiOSoZOZmoA,4352
115
+ pyopenapi_gen/visit/endpoint/generators/endpoint_method_generator.py,sha256=X0tdSVmfUTDSOVKmC5cQLO3SfC_hsRCQpt5lPoC6MYI,9998
116
+ pyopenapi_gen/visit/endpoint/generators/overload_generator.py,sha256=1W8pt5iUSDsgxJJUoxfQyRZkxCwFi1WbvVM__SazCe0,9074
116
117
  pyopenapi_gen/visit/endpoint/generators/request_generator.py,sha256=MKtZ6Fa050gCgqAGhXeo--p_AzqV9RmDd8e4Zvglgo0,5349
117
118
  pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py,sha256=Jb_8hRQY1OfpThupa7UdCNxa712m3WaeNIbLhlYk1bg,24193
118
119
  pyopenapi_gen/visit/endpoint/generators/signature_generator.py,sha256=ENi34Rf2x1Ijtvca652ihV9L2UUT1O2SEsG-66Pm_Z8,3873
@@ -125,8 +126,8 @@ pyopenapi_gen/visit/model/alias_generator.py,sha256=wEMHipPA1_CFxvQ6CS9j4qgXK93s
125
126
  pyopenapi_gen/visit/model/dataclass_generator.py,sha256=L794s2yyYUO__akGd120NJMV7g2xqiPfQyZo8CduIVM,14426
126
127
  pyopenapi_gen/visit/model/enum_generator.py,sha256=AXqKUFuWUUjUF_6_HqBKY8vB5GYu35Pb2C2WPFrOw1k,10061
127
128
  pyopenapi_gen/visit/model/model_visitor.py,sha256=TC6pbxpQiy5FWhmQpfllLuXA3ImTYNMcrazkOFZCIyo,9470
128
- pyopenapi_gen-0.16.1.dist-info/METADATA,sha256=U-hI_V_vy2JhZBXeDQvaEo4gRXM_YHX2aVA5_kLUAAc,14025
129
- pyopenapi_gen-0.16.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
130
- pyopenapi_gen-0.16.1.dist-info/entry_points.txt,sha256=gxSlNiwom50T3OEZnlocA6qRjGdV0bn6hN_Xr-Ub5wA,56
131
- pyopenapi_gen-0.16.1.dist-info/licenses/LICENSE,sha256=UFAyTWKa4w10-QerlJaHJeep7G2gcwpf-JmvI2dS2Gc,1088
132
- pyopenapi_gen-0.16.1.dist-info/RECORD,,
129
+ pyopenapi_gen-0.18.0.dist-info/METADATA,sha256=kLYKgzczN64FbkIQ0y_Tg-jnZ78yU8VrFZVeVL-LKic,20551
130
+ pyopenapi_gen-0.18.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
131
+ pyopenapi_gen-0.18.0.dist-info/entry_points.txt,sha256=gxSlNiwom50T3OEZnlocA6qRjGdV0bn6hN_Xr-Ub5wA,56
132
+ pyopenapi_gen-0.18.0.dist-info/licenses/LICENSE,sha256=UFAyTWKa4w10-QerlJaHJeep7G2gcwpf-JmvI2dS2Gc,1088
133
+ pyopenapi_gen-0.18.0.dist-info/RECORD,,