pyopenapi-gen 2.7.2__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.
Files changed (137) hide show
  1. pyopenapi_gen/__init__.py +224 -0
  2. pyopenapi_gen/__main__.py +6 -0
  3. pyopenapi_gen/cli.py +62 -0
  4. pyopenapi_gen/context/CLAUDE.md +284 -0
  5. pyopenapi_gen/context/file_manager.py +52 -0
  6. pyopenapi_gen/context/import_collector.py +382 -0
  7. pyopenapi_gen/context/render_context.py +726 -0
  8. pyopenapi_gen/core/CLAUDE.md +224 -0
  9. pyopenapi_gen/core/__init__.py +0 -0
  10. pyopenapi_gen/core/auth/base.py +22 -0
  11. pyopenapi_gen/core/auth/plugins.py +89 -0
  12. pyopenapi_gen/core/cattrs_converter.py +810 -0
  13. pyopenapi_gen/core/exceptions.py +20 -0
  14. pyopenapi_gen/core/http_status_codes.py +218 -0
  15. pyopenapi_gen/core/http_transport.py +222 -0
  16. pyopenapi_gen/core/loader/__init__.py +12 -0
  17. pyopenapi_gen/core/loader/loader.py +174 -0
  18. pyopenapi_gen/core/loader/operations/__init__.py +12 -0
  19. pyopenapi_gen/core/loader/operations/parser.py +161 -0
  20. pyopenapi_gen/core/loader/operations/post_processor.py +62 -0
  21. pyopenapi_gen/core/loader/operations/request_body.py +90 -0
  22. pyopenapi_gen/core/loader/parameters/__init__.py +10 -0
  23. pyopenapi_gen/core/loader/parameters/parser.py +186 -0
  24. pyopenapi_gen/core/loader/responses/__init__.py +10 -0
  25. pyopenapi_gen/core/loader/responses/parser.py +111 -0
  26. pyopenapi_gen/core/loader/schemas/__init__.py +11 -0
  27. pyopenapi_gen/core/loader/schemas/extractor.py +275 -0
  28. pyopenapi_gen/core/pagination.py +64 -0
  29. pyopenapi_gen/core/parsing/__init__.py +13 -0
  30. pyopenapi_gen/core/parsing/common/__init__.py +1 -0
  31. pyopenapi_gen/core/parsing/common/ref_resolution/__init__.py +9 -0
  32. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/__init__.py +0 -0
  33. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/cyclic_properties.py +66 -0
  34. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/direct_cycle.py +33 -0
  35. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/existing_schema.py +22 -0
  36. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/list_response.py +54 -0
  37. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/missing_ref.py +52 -0
  38. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/new_schema.py +50 -0
  39. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/stripped_suffix.py +51 -0
  40. pyopenapi_gen/core/parsing/common/ref_resolution/resolve_schema_ref.py +86 -0
  41. pyopenapi_gen/core/parsing/common/type_parser.py +73 -0
  42. pyopenapi_gen/core/parsing/context.py +187 -0
  43. pyopenapi_gen/core/parsing/cycle_helpers.py +126 -0
  44. pyopenapi_gen/core/parsing/keywords/__init__.py +1 -0
  45. pyopenapi_gen/core/parsing/keywords/all_of_parser.py +81 -0
  46. pyopenapi_gen/core/parsing/keywords/any_of_parser.py +84 -0
  47. pyopenapi_gen/core/parsing/keywords/array_items_parser.py +72 -0
  48. pyopenapi_gen/core/parsing/keywords/one_of_parser.py +77 -0
  49. pyopenapi_gen/core/parsing/keywords/properties_parser.py +98 -0
  50. pyopenapi_gen/core/parsing/schema_finalizer.py +169 -0
  51. pyopenapi_gen/core/parsing/schema_parser.py +804 -0
  52. pyopenapi_gen/core/parsing/transformers/__init__.py +0 -0
  53. pyopenapi_gen/core/parsing/transformers/inline_enum_extractor.py +285 -0
  54. pyopenapi_gen/core/parsing/transformers/inline_object_promoter.py +120 -0
  55. pyopenapi_gen/core/parsing/unified_cycle_detection.py +293 -0
  56. pyopenapi_gen/core/postprocess_manager.py +260 -0
  57. pyopenapi_gen/core/spec_fetcher.py +148 -0
  58. pyopenapi_gen/core/streaming_helpers.py +84 -0
  59. pyopenapi_gen/core/telemetry.py +69 -0
  60. pyopenapi_gen/core/utils.py +456 -0
  61. pyopenapi_gen/core/warning_collector.py +83 -0
  62. pyopenapi_gen/core/writers/code_writer.py +135 -0
  63. pyopenapi_gen/core/writers/documentation_writer.py +222 -0
  64. pyopenapi_gen/core/writers/line_writer.py +217 -0
  65. pyopenapi_gen/core/writers/python_construct_renderer.py +321 -0
  66. pyopenapi_gen/core_package_template/README.md +21 -0
  67. pyopenapi_gen/emit/models_emitter.py +143 -0
  68. pyopenapi_gen/emitters/CLAUDE.md +286 -0
  69. pyopenapi_gen/emitters/client_emitter.py +51 -0
  70. pyopenapi_gen/emitters/core_emitter.py +181 -0
  71. pyopenapi_gen/emitters/docs_emitter.py +44 -0
  72. pyopenapi_gen/emitters/endpoints_emitter.py +247 -0
  73. pyopenapi_gen/emitters/exceptions_emitter.py +187 -0
  74. pyopenapi_gen/emitters/mocks_emitter.py +185 -0
  75. pyopenapi_gen/emitters/models_emitter.py +426 -0
  76. pyopenapi_gen/generator/CLAUDE.md +352 -0
  77. pyopenapi_gen/generator/client_generator.py +567 -0
  78. pyopenapi_gen/generator/exceptions.py +7 -0
  79. pyopenapi_gen/helpers/CLAUDE.md +325 -0
  80. pyopenapi_gen/helpers/__init__.py +1 -0
  81. pyopenapi_gen/helpers/endpoint_utils.py +532 -0
  82. pyopenapi_gen/helpers/type_cleaner.py +334 -0
  83. pyopenapi_gen/helpers/type_helper.py +112 -0
  84. pyopenapi_gen/helpers/type_resolution/__init__.py +1 -0
  85. pyopenapi_gen/helpers/type_resolution/array_resolver.py +57 -0
  86. pyopenapi_gen/helpers/type_resolution/composition_resolver.py +79 -0
  87. pyopenapi_gen/helpers/type_resolution/finalizer.py +105 -0
  88. pyopenapi_gen/helpers/type_resolution/named_resolver.py +172 -0
  89. pyopenapi_gen/helpers/type_resolution/object_resolver.py +216 -0
  90. pyopenapi_gen/helpers/type_resolution/primitive_resolver.py +109 -0
  91. pyopenapi_gen/helpers/type_resolution/resolver.py +47 -0
  92. pyopenapi_gen/helpers/url_utils.py +14 -0
  93. pyopenapi_gen/http_types.py +20 -0
  94. pyopenapi_gen/ir.py +165 -0
  95. pyopenapi_gen/py.typed +1 -0
  96. pyopenapi_gen/types/CLAUDE.md +140 -0
  97. pyopenapi_gen/types/__init__.py +11 -0
  98. pyopenapi_gen/types/contracts/__init__.py +13 -0
  99. pyopenapi_gen/types/contracts/protocols.py +106 -0
  100. pyopenapi_gen/types/contracts/types.py +28 -0
  101. pyopenapi_gen/types/resolvers/__init__.py +7 -0
  102. pyopenapi_gen/types/resolvers/reference_resolver.py +71 -0
  103. pyopenapi_gen/types/resolvers/response_resolver.py +177 -0
  104. pyopenapi_gen/types/resolvers/schema_resolver.py +498 -0
  105. pyopenapi_gen/types/services/__init__.py +5 -0
  106. pyopenapi_gen/types/services/type_service.py +165 -0
  107. pyopenapi_gen/types/strategies/__init__.py +5 -0
  108. pyopenapi_gen/types/strategies/response_strategy.py +310 -0
  109. pyopenapi_gen/visit/CLAUDE.md +272 -0
  110. pyopenapi_gen/visit/client_visitor.py +477 -0
  111. pyopenapi_gen/visit/docs_visitor.py +38 -0
  112. pyopenapi_gen/visit/endpoint/__init__.py +1 -0
  113. pyopenapi_gen/visit/endpoint/endpoint_visitor.py +292 -0
  114. pyopenapi_gen/visit/endpoint/generators/__init__.py +1 -0
  115. pyopenapi_gen/visit/endpoint/generators/docstring_generator.py +123 -0
  116. pyopenapi_gen/visit/endpoint/generators/endpoint_method_generator.py +222 -0
  117. pyopenapi_gen/visit/endpoint/generators/mock_generator.py +140 -0
  118. pyopenapi_gen/visit/endpoint/generators/overload_generator.py +252 -0
  119. pyopenapi_gen/visit/endpoint/generators/request_generator.py +103 -0
  120. pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py +705 -0
  121. pyopenapi_gen/visit/endpoint/generators/signature_generator.py +83 -0
  122. pyopenapi_gen/visit/endpoint/generators/url_args_generator.py +207 -0
  123. pyopenapi_gen/visit/endpoint/processors/__init__.py +1 -0
  124. pyopenapi_gen/visit/endpoint/processors/import_analyzer.py +78 -0
  125. pyopenapi_gen/visit/endpoint/processors/parameter_processor.py +171 -0
  126. pyopenapi_gen/visit/exception_visitor.py +90 -0
  127. pyopenapi_gen/visit/model/__init__.py +0 -0
  128. pyopenapi_gen/visit/model/alias_generator.py +93 -0
  129. pyopenapi_gen/visit/model/dataclass_generator.py +553 -0
  130. pyopenapi_gen/visit/model/enum_generator.py +212 -0
  131. pyopenapi_gen/visit/model/model_visitor.py +198 -0
  132. pyopenapi_gen/visit/visitor.py +97 -0
  133. pyopenapi_gen-2.7.2.dist-info/METADATA +1169 -0
  134. pyopenapi_gen-2.7.2.dist-info/RECORD +137 -0
  135. pyopenapi_gen-2.7.2.dist-info/WHEEL +4 -0
  136. pyopenapi_gen-2.7.2.dist-info/entry_points.txt +2 -0
  137. pyopenapi_gen-2.7.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,1169 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyopenapi-gen
3
+ Version: 2.7.2
4
+ Summary: Modern, async-first Python client generator for OpenAPI specifications with advanced cycle detection and unified type resolution
5
+ Project-URL: Homepage, https://github.com/your-org/pyopenapi-gen
6
+ Project-URL: Documentation, https://github.com/your-org/pyopenapi-gen/blob/main/README.md
7
+ Project-URL: Repository, https://github.com/your-org/pyopenapi-gen
8
+ Project-URL: Issues, https://github.com/your-org/pyopenapi-gen/issues
9
+ Project-URL: Changelog, https://github.com/your-org/pyopenapi-gen/blob/main/CHANGELOG.md
10
+ Project-URL: Bug Reports, https://github.com/your-org/pyopenapi-gen/issues
11
+ Project-URL: Source Code, https://github.com/your-org/pyopenapi-gen
12
+ Author-email: Mindhive Oy <contact@mindhive.fi>
13
+ Maintainer-email: Ville Venäläinen | Mindhive Oy <ville@mindhive.fi>
14
+ License: MIT
15
+ License-File: LICENSE
16
+ Keywords: api,async,client,code-generation,enterprise,generator,http,openapi,python,rest,swagger,type-safe
17
+ Classifier: Development Status :: 4 - Beta
18
+ Classifier: Environment :: Console
19
+ Classifier: Framework :: AsyncIO
20
+ Classifier: Intended Audience :: Developers
21
+ Classifier: License :: OSI Approved :: MIT License
22
+ Classifier: Natural Language :: English
23
+ Classifier: Operating System :: MacOS
24
+ Classifier: Operating System :: Microsoft :: Windows
25
+ Classifier: Operating System :: POSIX :: Linux
26
+ Classifier: Programming Language :: Python :: 3
27
+ Classifier: Programming Language :: Python :: 3 :: Only
28
+ Classifier: Programming Language :: Python :: 3.12
29
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
30
+ Classifier: Topic :: Software Development :: Code Generators
31
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
32
+ Classifier: Topic :: System :: Networking
33
+ Classifier: Typing :: Typed
34
+ Requires-Python: <4.0.0,>=3.12
35
+ Requires-Dist: cattrs>=25.3.0
36
+ Requires-Dist: click>=8.0.0
37
+ Requires-Dist: dataclass-wizard<0.37.0,>=0.36.1
38
+ Requires-Dist: httpx>=0.24.0
39
+ Requires-Dist: openapi-core>=0.19
40
+ Requires-Dist: openapi-spec-validator>=0.7
41
+ Requires-Dist: pyyaml>=6.0
42
+ Requires-Dist: typer>=0.14.0
43
+ Requires-Dist: urllib3<3.0.0,>=2.6.0
44
+ Provides-Extra: dev
45
+ Requires-Dist: bandit[toml]>=1.7.0; extra == 'dev'
46
+ Requires-Dist: black>=23.0; extra == 'dev'
47
+ Requires-Dist: dataclass-wizard>=0.22.0; extra == 'dev'
48
+ Requires-Dist: httpx>=0.24.0; extra == 'dev'
49
+ Requires-Dist: mypy>=1.7; extra == 'dev'
50
+ Requires-Dist: pytest-asyncio>=0.20.0; extra == 'dev'
51
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
52
+ Requires-Dist: pytest-timeout>=2.1.0; extra == 'dev'
53
+ Requires-Dist: pytest-xdist>=3.0.0; extra == 'dev'
54
+ Requires-Dist: pytest>=7.0; extra == 'dev'
55
+ Requires-Dist: ruff>=0.4; extra == 'dev'
56
+ Requires-Dist: safety>=2.0.0; extra == 'dev'
57
+ Requires-Dist: types-pyyaml>=6.0.12; extra == 'dev'
58
+ Requires-Dist: types-toml>=0.10.8; extra == 'dev'
59
+ Description-Content-Type: text/markdown
60
+
61
+ # PyOpenAPI Generator
62
+
63
+ [![Python](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://python.org)
64
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
65
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
66
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
67
+
68
+ **Modern, enterprise-grade Python client generator for OpenAPI specifications.**
69
+
70
+ Generate async-first Python clients from OpenAPI specs with complete type safety, automatic field mapping, and zero runtime dependencies.
71
+
72
+ ## Why PyOpenAPI Generator?
73
+
74
+ ### Modern Python Architecture
75
+
76
+ - **Async-First**: All operations use `async`/`await` with `httpx` for high performance
77
+ - **Complete Type Safety**: Full type hints, dataclass models, and mypy strict mode compatibility
78
+ - **Truly Independent**: Generated clients require no runtime dependency on this package
79
+
80
+ ### Enterprise-Grade Features
81
+
82
+ - **Complex Schema Handling**: Advanced cycle detection for circular references and deep nesting
83
+ - **Automatic Field Mapping**: Seamless conversion between API naming (snake_case, camelCase) and Python conventions
84
+ - **Pluggable Authentication**: Bearer tokens, API keys, OAuth2, custom auth, or combine multiple strategies
85
+ - **Streaming Support**: Built-in SSE and binary streaming for real-time data
86
+
87
+ ### Superior Developer Experience
88
+
89
+ - **Rich IDE Support**: Full autocomplete, inline docs, and type checking in modern editors
90
+ - **Tag-Based Organization**: Operations automatically grouped by OpenAPI tags for intuitive navigation
91
+ - **Structured Exceptions**: Type-safe error handling with meaningful exception hierarchy
92
+ - **Easy Testing**: Auto-generated Protocol classes for each endpoint enable strict type-safe mocking
93
+
94
+ ## Installation
95
+
96
+ ```bash
97
+ pip install pyopenapi-gen
98
+ ```
99
+
100
+ Or with Poetry:
101
+
102
+ ```bash
103
+ poetry add pyopenapi-gen
104
+ ```
105
+
106
+ ## ⚡ Quick Start
107
+
108
+ ### 1. Generate Your First Client
109
+
110
+ ```bash
111
+ pyopenapi-gen openapi.yaml \
112
+ --project-root . \
113
+ --output-package my_api_client
114
+ ```
115
+
116
+ This creates a complete Python package at `./my_api_client/` with:
117
+
118
+ - Type-safe models from your schemas
119
+ - Async methods for all operations
120
+ - Built-in authentication support
121
+ - Complete independence from this generator
122
+
123
+ ### 2. Use the Generated Client
124
+
125
+ ```python
126
+ import asyncio
127
+ from my_api_client.client import APIClient
128
+ from my_api_client.core.config import ClientConfig
129
+ from my_api_client.core.http_transport import HttpxTransport
130
+ from my_api_client.core.auth.plugins import BearerAuth
131
+
132
+ async def main():
133
+ # Configure the client
134
+ config = ClientConfig(
135
+ base_url="https://api.example.com",
136
+ timeout=30.0
137
+ )
138
+
139
+ # Optional: Add authentication
140
+ auth = BearerAuth("your-api-token")
141
+ transport = HttpxTransport(
142
+ base_url=config.base_url,
143
+ timeout=config.timeout,
144
+ auth=auth
145
+ )
146
+
147
+ # Use as async context manager
148
+ async with APIClient(config, transport=transport) as client:
149
+ # Type-safe API calls with full IDE support
150
+ users = await client.users.list_users(limit=10)
151
+
152
+ # All operations are fully typed
153
+ user = await client.users.get_user(user_id=123)
154
+ print(f"User: {user.name}, Email: {user.email}")
155
+
156
+ asyncio.run(main())
157
+ ```
158
+
159
+ ## Using as a Library (Programmatic API)
160
+
161
+ 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.
162
+
163
+ ### How to Use Programmatically
164
+
165
+ #### Basic Usage
166
+
167
+ ```python
168
+ from pyopenapi_gen import generate_client
169
+
170
+ # Simple client generation
171
+ files = generate_client(
172
+ spec_path="input/openapi.yaml",
173
+ project_root=".",
174
+ output_package="pyapis.my_client"
175
+ )
176
+
177
+ print(f"Generated {len(files)} files")
178
+ ```
179
+
180
+ #### Advanced Usage with All Options
181
+
182
+ ```python
183
+ from pyopenapi_gen import generate_client, GenerationError
184
+
185
+ try:
186
+ files = generate_client(
187
+ spec_path="input/openapi.yaml",
188
+ project_root=".",
189
+ output_package="pyapis.my_client",
190
+ core_package="pyapis.core", # Optional shared core
191
+ force=True, # Overwrite without diff check
192
+ no_postprocess=False, # Run Black + mypy
193
+ verbose=True # Show progress
194
+ )
195
+
196
+ # Process generated files
197
+ for file_path in files:
198
+ print(f"Generated: {file_path}")
199
+
200
+ except GenerationError as e:
201
+ print(f"Generation failed: {e}")
202
+ ```
203
+
204
+ #### Multi-Client Generation Script
205
+
206
+ ```python
207
+ from pyopenapi_gen import generate_client
208
+ from pathlib import Path
209
+
210
+ # Configuration for multiple clients
211
+ clients = [
212
+ {"spec": "api_v1.yaml", "package": "pyapis.client_v1"},
213
+ {"spec": "api_v2.yaml", "package": "pyapis.client_v2"},
214
+ ]
215
+
216
+ # Shared core package
217
+ core_package = "pyapis.core"
218
+
219
+ # Generate all clients
220
+ for client_config in clients:
221
+ print(f"Generating {client_config['package']}...")
222
+
223
+ generate_client(
224
+ spec_path=client_config["spec"],
225
+ project_root=".",
226
+ output_package=client_config["package"],
227
+ core_package=core_package,
228
+ force=True,
229
+ verbose=True
230
+ )
231
+
232
+ print("All clients generated successfully!")
233
+ ```
234
+
235
+ ### API Reference
236
+
237
+ #### `generate_client()` Function
238
+
239
+ ```python
240
+ def generate_client(
241
+ spec_path: str,
242
+ project_root: str,
243
+ output_package: str,
244
+ core_package: str | None = None,
245
+ force: bool = False,
246
+ no_postprocess: bool = False,
247
+ verbose: bool = False,
248
+ ) -> List[Path]
249
+ ```
250
+
251
+ **Parameters**:
252
+
253
+ - `spec_path`: Path to OpenAPI spec file (YAML or JSON)
254
+ - `project_root`: Root directory of your Python project
255
+ - `output_package`: Python package name (e.g., `'pyapis.my_client'`)
256
+ - `core_package`: Optional shared core package name (defaults to `{output_package}.core`)
257
+ - `force`: Skip diff check and overwrite existing output
258
+ - `no_postprocess`: Skip Black formatting and mypy type checking
259
+ - `verbose`: Print detailed progress information
260
+
261
+ **Returns**: List of `Path` objects for all generated files
262
+
263
+ **Raises**: `GenerationError` if generation fails
264
+
265
+ #### `ClientGenerator` Class (Advanced)
266
+
267
+ For advanced use cases requiring more control:
268
+
269
+ ```python
270
+ from pyopenapi_gen import ClientGenerator, GenerationError
271
+ from pathlib import Path
272
+
273
+ # Create generator with custom settings
274
+ generator = ClientGenerator(verbose=True)
275
+
276
+ # Generate with full control
277
+ try:
278
+ files = generator.generate(
279
+ spec_path="openapi.yaml",
280
+ project_root=Path("."),
281
+ output_package="pyapis.my_client",
282
+ core_package="pyapis.core",
283
+ force=False,
284
+ no_postprocess=False
285
+ )
286
+ except GenerationError as e:
287
+ print(f"Generation failed: {e}")
288
+ ```
289
+
290
+ #### `GenerationError` Exception
291
+
292
+ Raised when generation fails. Contains contextual information about the failure:
293
+
294
+ ```python
295
+ from pyopenapi_gen import generate_client, GenerationError
296
+
297
+ try:
298
+ generate_client(
299
+ spec_path="invalid.yaml",
300
+ project_root=".",
301
+ output_package="test"
302
+ )
303
+ except GenerationError as e:
304
+ # Exception message includes context
305
+ print(f"Error: {e}")
306
+ # Typical causes:
307
+ # - Invalid OpenAPI specification
308
+ # - File I/O errors
309
+ # - Type checking failures
310
+ # - Invalid project structure
311
+ ```
312
+
313
+ ## Configuration Options
314
+
315
+ ### Standalone Client (Default)
316
+
317
+ ```bash
318
+ pyopenapi-gen openapi.yaml \
319
+ --project-root . \
320
+ --output-package my_api_client
321
+ ```
322
+
323
+ Creates self-contained client with embedded core dependencies.
324
+
325
+ ### Shared Core (Multiple Clients)
326
+
327
+ ```bash
328
+ pyopenapi-gen openapi.yaml \
329
+ --project-root . \
330
+ --output-package clients.api_client \
331
+ --core-package clients.core
332
+ ```
333
+
334
+ Multiple clients share a single core implementation.
335
+
336
+ ### Additional Options
337
+
338
+ ```bash
339
+ --force # Overwrite without prompting
340
+ --no-postprocess # Skip formatting and type checking
341
+ ```
342
+
343
+ ## Authentication
344
+
345
+ The generated clients support flexible authentication through the transport layer. Authentication plugins modify requests before they're sent.
346
+
347
+ ### Bearer Token Authentication
348
+
349
+ ```python
350
+ from my_api_client.core.auth.plugins import BearerAuth
351
+ from my_api_client.core.http_transport import HttpxTransport
352
+
353
+ auth = BearerAuth("your-api-token")
354
+ transport = HttpxTransport(
355
+ base_url="https://api.example.com",
356
+ auth=auth
357
+ )
358
+
359
+ async with APIClient(config, transport=transport) as client:
360
+ # All requests automatically include: Authorization: Bearer your-api-token
361
+ users = await client.users.list_users()
362
+ ```
363
+
364
+ ### API Key (Header, Query, or Cookie)
365
+
366
+ ```python
367
+ from my_api_client.core.auth.plugins import ApiKeyAuth
368
+
369
+ # API key in header
370
+ auth = ApiKeyAuth("your-key", location="header", name="X-API-Key")
371
+
372
+ # API key in query string
373
+ auth = ApiKeyAuth("your-key", location="query", name="api_key")
374
+
375
+ # API key in cookie
376
+ auth = ApiKeyAuth("your-key", location="cookie", name="session")
377
+
378
+ transport = HttpxTransport(
379
+ base_url="https://api.example.com",
380
+ auth=auth
381
+ )
382
+ ```
383
+
384
+ ### OAuth2 with Token Refresh
385
+
386
+ ```python
387
+ from my_api_client.core.auth.plugins import OAuth2Auth
388
+
389
+ async def refresh_token(current_token: str) -> str:
390
+ # Your token refresh logic
391
+ # Call your auth server to get a new token
392
+ new_token = await get_new_token()
393
+ return new_token
394
+
395
+ auth = OAuth2Auth(
396
+ access_token="initial-token",
397
+ refresh_callback=refresh_token
398
+ )
399
+
400
+ transport = HttpxTransport(
401
+ base_url="https://api.example.com",
402
+ auth=auth
403
+ )
404
+ ```
405
+
406
+ ### Composite Authentication (Multiple Auth Methods)
407
+
408
+ ```python
409
+ from my_api_client.core.auth.base import CompositeAuth
410
+ from my_api_client.core.auth.plugins import BearerAuth, HeadersAuth
411
+
412
+ # Combine multiple authentication methods
413
+ auth = CompositeAuth(
414
+ BearerAuth("api-token"),
415
+ HeadersAuth({"X-Client-ID": "my-app", "X-Version": "1.0"})
416
+ )
417
+
418
+ transport = HttpxTransport(
419
+ base_url="https://api.example.com",
420
+ auth=auth
421
+ )
422
+
423
+ # All requests include both Authorization header and custom headers
424
+ ```
425
+
426
+ ### Custom Authentication
427
+
428
+ ```python
429
+ from typing import Any
430
+ from my_api_client.core.auth.base import BaseAuth
431
+
432
+ class CustomAuth(BaseAuth):
433
+ """Your custom authentication logic"""
434
+
435
+ def __init__(self, api_key: str, secret: str):
436
+ self.api_key = api_key
437
+ self.secret = secret
438
+
439
+ async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
440
+ # Add custom authentication logic
441
+ headers = dict(request_args.get("headers", {}))
442
+ headers["X-API-Key"] = self.api_key
443
+ headers["X-Signature"] = self._generate_signature()
444
+ request_args["headers"] = headers
445
+ return request_args
446
+
447
+ def _generate_signature(self) -> str:
448
+ # Your signature generation logic
449
+ return "signature"
450
+
451
+ auth = CustomAuth("key", "secret")
452
+ transport = HttpxTransport(base_url="https://api.example.com", auth=auth)
453
+ ```
454
+
455
+ ## Advanced Features
456
+
457
+ ### Error Handling
458
+
459
+ The generated client raises structured exceptions for all non-2xx responses:
460
+
461
+ ```python
462
+ from my_api_client.core.exceptions import HTTPError, ClientError, ServerError
463
+
464
+ try:
465
+ user = await client.users.get_user(user_id=123)
466
+ print(f"Found user: {user.name}")
467
+
468
+ except ClientError as e:
469
+ # 4xx errors - client-side issues
470
+ print(f"Client error {e.status_code}: {e.response.text}")
471
+ if e.status_code == 404:
472
+ print("User not found")
473
+ elif e.status_code == 401:
474
+ print("Authentication required")
475
+
476
+ except ServerError as e:
477
+ # 5xx errors - server-side issues
478
+ print(f"Server error {e.status_code}: {e.response.text}")
479
+
480
+ except HTTPError as e:
481
+ # Catch-all for any HTTP errors
482
+ print(f"HTTP error {e.status_code}: {e.response.text}")
483
+ ```
484
+
485
+ ### Streaming Responses
486
+
487
+ For operations that return streaming data (like SSE or file downloads):
488
+
489
+ ```python
490
+ # Server-Sent Events (SSE)
491
+ async for event in client.events.stream_events():
492
+ print(f"Received event: {event}")
493
+
494
+ # Binary streaming (files, large downloads)
495
+ async with client.files.download_file(file_id=123) as response:
496
+ async for chunk in response:
497
+ # Process binary chunks
498
+ file.write(chunk)
499
+ ```
500
+
501
+ ### Automatic Field Name Mapping
502
+
503
+ Generated models use cattrs with Meta class for seamless API ↔ Python field name conversion:
504
+
505
+ ```python
506
+ from my_api_client.models.user import User
507
+
508
+ # API returns camelCase: {"firstName": "John", "lastName": "Doe"}
509
+ # Python uses snake_case automatically
510
+ user_data = await client.users.get_user(user_id=1)
511
+ print(user_data.first_name) # "John" - automatically mapped
512
+ print(user_data.last_name) # "Doe"
513
+
514
+ # Serialization back to API format works automatically
515
+ new_user = User(first_name="Jane", last_name="Smith")
516
+ created = await client.users.create_user(user=new_user)
517
+ # Sends: {"firstName": "Jane", "lastName": "Smith"}
518
+ ```
519
+
520
+ ### Type Safety and IDE Support
521
+
522
+ All generated code includes complete type hints:
523
+
524
+ ```python
525
+ # Your IDE provides autocomplete for all methods
526
+ client.users. # IDE shows: list_users(), get_user(), create_user(), etc.
527
+
528
+ # All parameters are typed
529
+ await client.users.create_user(
530
+ user=User( # IDE autocompletes User fields
531
+ name="John",
532
+ email="john@example.com",
533
+ age=30 # Type checking catches wrong types
534
+ )
535
+ )
536
+
537
+ # Return types are fully specified
538
+ user: User = await client.users.get_user(user_id=1)
539
+ # mypy validates the entire chain
540
+ ```
541
+
542
+ ## 💼 Common Use Cases
543
+
544
+ ### Microservice Communication
545
+
546
+ ```python
547
+ # Generate clients for internal services
548
+ pyopenapi-gen services/user-api/openapi.yaml \
549
+ --project-root . \
550
+ --output-package myapp.clients.users
551
+
552
+ pyopenapi-gen services/order-api/openapi.yaml \
553
+ --project-root . \
554
+ --output-package myapp.clients.orders
555
+
556
+ # Use in your application
557
+ from myapp.clients.users.client import APIClient as UserClient
558
+ from myapp.clients.orders.client import APIClient as OrderClient
559
+
560
+ async def process_order(user_id: int, order_id: int):
561
+ async with UserClient(user_config) as user_client:
562
+ user = await user_client.users.get_user(user_id=user_id)
563
+
564
+ async with OrderClient(order_config) as order_client:
565
+ order = await order_client.orders.get_order(order_id=order_id)
566
+ ```
567
+
568
+ ### SDK Generation for Public APIs
569
+
570
+ ```python
571
+ # Generate a distributable SDK
572
+ pyopenapi-gen public-api.yaml \
573
+ --project-root sdk \
574
+ --output-package mycompany_sdk
575
+
576
+ # Package structure for distribution:
577
+ # sdk/
578
+ # mycompany_sdk/
579
+ # __init__.py
580
+ # client.py
581
+ # models/
582
+ # endpoints/
583
+ # core/
584
+ # setup.py
585
+ # README.md
586
+
587
+ # Users install: pip install mycompany-sdk
588
+ # Users use: from mycompany_sdk.client import APIClient
589
+ ```
590
+
591
+ ### Multi-Environment Setup
592
+
593
+ ```python
594
+ # Generate once, configure per environment
595
+ from my_api_client.client import APIClient
596
+ from my_api_client.core.config import ClientConfig
597
+ from my_api_client.core.http_transport import HttpxTransport
598
+ from my_api_client.core.auth.plugins import BearerAuth
599
+
600
+ # Development
601
+ dev_config = ClientConfig(base_url="https://dev-api.example.com")
602
+ dev_auth = BearerAuth(os.getenv("DEV_API_TOKEN"))
603
+ dev_transport = HttpxTransport(dev_config.base_url, auth=dev_auth)
604
+
605
+ # Production
606
+ prod_config = ClientConfig(base_url="https://api.example.com")
607
+ prod_auth = BearerAuth(os.getenv("PROD_API_TOKEN"))
608
+ prod_transport = HttpxTransport(prod_config.base_url, auth=prod_auth)
609
+
610
+ # Use the same client code with different configs
611
+ async with APIClient(dev_config, transport=dev_transport) as client:
612
+ users = await client.users.list_users()
613
+ ```
614
+
615
+ ### Testing with Mock Servers
616
+
617
+ ```python
618
+ # Point generated client at mock server for testing
619
+ import pytest
620
+ from my_api_client.client import APIClient
621
+ from my_api_client.core.config import ClientConfig
622
+
623
+ @pytest.fixture
624
+ async def api_client(mock_server_url):
625
+ """API client pointing to mock server"""
626
+ config = ClientConfig(base_url=mock_server_url)
627
+ async with APIClient(config) as client:
628
+ yield client
629
+
630
+ async def test_user_creation(api_client):
631
+ # Mock server returns predictable responses
632
+ user = await api_client.users.create_user(
633
+ user={"name": "Test User", "email": "test@example.com"}
634
+ )
635
+ assert user.name == "Test User"
636
+ ```
637
+
638
+ ## Testing and Mocking
639
+
640
+ ### Protocol-Based Design for Strict Type Safety
641
+
642
+ The generator **automatically creates Protocol classes** for every endpoint client, enforcing strict type safety through explicit contracts. This enables easy testing with compile-time guarantees.
643
+
644
+ #### Generated Protocol Structure
645
+
646
+ For each OpenAPI tag, the generator creates:
647
+
648
+ ```python
649
+ # Generated automatically from your OpenAPI spec:
650
+
651
+ @runtime_checkable
652
+ class UsersClientProtocol(Protocol):
653
+ """Protocol defining the interface of UsersClient for dependency injection."""
654
+
655
+ async def get_user(self, user_id: int) -> User: ...
656
+ async def list_users(self, limit: int = 10) -> list[User]: ...
657
+ async def create_user(self, user: User) -> User: ...
658
+
659
+ class UsersClient(UsersClientProtocol):
660
+ """Real implementation - explicitly implements the protocol"""
661
+
662
+ def __init__(self, transport: HttpTransport, base_url: str) -> None:
663
+ self._transport = transport
664
+ self.base_url = base_url
665
+
666
+ async def get_user(self, user_id: int) -> User:
667
+ # Real HTTP implementation
668
+ ...
669
+ ```
670
+
671
+ **Key Point**: The real implementation **explicitly inherits from the Protocol**, ensuring mypy validates it implements all methods correctly!
672
+
673
+ ### Creating Type-Safe Mocks
674
+
675
+ Your mocks **must explicitly inherit from the generated Protocol** to get compile-time safety:
676
+
677
+ ```python
678
+ import pytest
679
+ from my_api_client.endpoints.users import UsersClientProtocol
680
+ from my_api_client.endpoints.orders import OrdersClientProtocol
681
+ from my_api_client.models.user import User
682
+ from my_api_client.models.order import Order
683
+
684
+ class MockUsersClient(UsersClientProtocol):
685
+ """
686
+ Mock implementation that explicitly inherits from the generated Protocol.
687
+
688
+ CRITICAL: If UsersClientProtocol changes (new method, different signature),
689
+ mypy will immediately flag this class as incomplete.
690
+ """
691
+
692
+ def __init__(self):
693
+ self.calls: list[tuple[str, dict]] = [] # Track method calls
694
+ self.mock_data: dict[int, User] = {} # Store mock responses
695
+
696
+ async def get_user(self, user_id: int) -> User:
697
+ """Mock implementation of get_user"""
698
+ self.calls.append(("get_user", {"user_id": user_id}))
699
+
700
+ # Return mock data
701
+ if user_id in self.mock_data:
702
+ return self.mock_data[user_id]
703
+
704
+ # Return default mock user
705
+ return User(
706
+ id=user_id,
707
+ name="Test User",
708
+ email=f"user{user_id}@example.com"
709
+ )
710
+
711
+ async def list_users(self, limit: int = 10) -> list[User]:
712
+ """Mock implementation of list_users"""
713
+ self.calls.append(("list_users", {"limit": limit}))
714
+ return [
715
+ User(id=1, name="User 1", email="user1@example.com"),
716
+ User(id=2, name="User 2", email="user2@example.com"),
717
+ ][:limit]
718
+
719
+ async def create_user(self, user: User) -> User:
720
+ """Mock implementation of create_user"""
721
+ self.calls.append(("create_user", {"user": user}))
722
+ user.id = 123
723
+ return user
724
+
725
+ class MockOrdersClient(OrdersClientProtocol):
726
+ """Mock OrdersClient - explicitly implements the protocol"""
727
+
728
+ async def get_order(self, order_id: int) -> Order:
729
+ return Order(id=order_id, status="completed", total=99.99)
730
+
731
+ async def create_order(self, order: Order) -> Order:
732
+ order.id = 456
733
+ order.status = "pending"
734
+ return order
735
+
736
+ # Type checking ensures mocks match protocols at compile time!
737
+ # If you forget a method or have wrong signatures:
738
+ # mypy error: Cannot instantiate abstract class 'MockUsersClient' with abstract method 'new_method'
739
+
740
+ @pytest.fixture
741
+ def mock_users_client() -> UsersClientProtocol:
742
+ """
743
+ Fixture providing a mock users client.
744
+ Return type annotation ensures type safety.
745
+ """
746
+ return MockUsersClient()
747
+
748
+ @pytest.fixture
749
+ def mock_orders_client() -> OrdersClientProtocol:
750
+ """Fixture providing a mock orders client"""
751
+ return MockOrdersClient()
752
+ ```
753
+
754
+ ### Using Mocked Endpoint Clients in Your Code
755
+
756
+ Now inject the mocks into your business logic:
757
+
758
+ ```python
759
+ async def test_user_service_with_mocks(mock_users_client, mock_orders_client):
760
+ """Test your business logic with mocked API clients"""
761
+
762
+ # Your business logic that depends on API clients
763
+ async def process_user_order(users_client, orders_client, user_id: int):
764
+ user = await users_client.get_user(user_id=user_id)
765
+ order = await orders_client.create_order(Order(user_id=user.id, items=[]))
766
+ return user, order
767
+
768
+ # Test with mocked clients
769
+ user, order = await process_user_order(
770
+ mock_users_client,
771
+ mock_orders_client,
772
+ user_id=123
773
+ )
774
+
775
+ # Assertions on business logic results
776
+ assert user.name == "Test User"
777
+ assert order.status == "pending"
778
+
779
+ # Verify interactions with the mock
780
+ assert len(mock_users_client.calls) == 1
781
+ assert mock_users_client.calls[0] == ("get_user", {"user_id": 123})
782
+ ```
783
+
784
+ ### Dependency Injection Pattern
785
+
786
+ Structure your code to accept **Protocol types**, not concrete implementations:
787
+
788
+ ```python
789
+ from my_api_client.endpoints.users import UsersClientProtocol
790
+ from my_api_client.endpoints.orders import OrdersClientProtocol
791
+
792
+ class UserService:
793
+ """
794
+ Service that depends on Protocol interfaces.
795
+
796
+ CRITICAL: Accept Protocol types, not concrete classes!
797
+ This allows injecting both real clients and mocks.
798
+ """
799
+
800
+ def __init__(
801
+ self,
802
+ users_client: UsersClientProtocol, # Protocol type!
803
+ orders_client: OrdersClientProtocol # Protocol type!
804
+ ):
805
+ self.users = users_client
806
+ self.orders = orders_client
807
+
808
+ async def get_user_with_orders(self, user_id: int):
809
+ user = await self.users.get_user(user_id=user_id)
810
+ orders = await self.orders.list_orders(user_id=user_id)
811
+ return {"user": user, "orders": orders}
812
+
813
+ # In production: inject real clients (they implement the protocols)
814
+ from my_api_client.client import APIClient
815
+ from my_api_client.core.config import ClientConfig
816
+
817
+ config = ClientConfig(base_url="https://api.example.com")
818
+ async with APIClient(config) as client:
819
+ service = UserService(
820
+ users_client=client.users, # UsersClient implements UsersClientProtocol
821
+ orders_client=client.orders # OrdersClient implements OrdersClientProtocol
822
+ )
823
+ result = await service.get_user_with_orders(user_id=123)
824
+
825
+ # In tests: inject mocks (they also implement the protocols)
826
+ async def test_user_service(mock_users_client, mock_orders_client):
827
+ service = UserService(
828
+ users_client=mock_users_client, # MockUsersClient implements UsersClientProtocol
829
+ orders_client=mock_orders_client # MockOrdersClient implements OrdersClientProtocol
830
+ )
831
+
832
+ result = await service.get_user_with_orders(user_id=123)
833
+
834
+ assert result["user"].name == "Test User"
835
+ assert len(result["orders"]) > 0
836
+
837
+ # Verify mock was called correctly
838
+ assert ("get_user", {"user_id": 123}) in mock_users_client.calls
839
+ ```
840
+
841
+ ### Benefits of Generated Protocols
842
+
843
+ 1. **Automatic Generation**: Protocols are generated from your OpenAPI spec - no manual writing
844
+ 2. **Compile-Time Safety**: mypy catches missing/incorrect methods immediately
845
+ 3. **Forced Updates**: When API changes, stale mocks break at compile time, not runtime
846
+ 4. **Test at Right Level**: Mock business operations (get_user, create_order), not HTTP transport
847
+ 5. **IDE Support**: Full autocomplete and inline errors for protocol implementations
848
+ 6. **Refactoring Safety**: Rename operations? All implementations must update or fail type checking
849
+ 7. **Documentation**: Protocol serves as explicit, enforced contract documentation
850
+ 8. **No Runtime Overhead**: Protocols are pure type-checking, zero runtime cost
851
+
852
+ ### Real-World Testing Example
853
+
854
+ Complete example showing protocol-based testing in action:
855
+
856
+ ```python
857
+ # my_service.py
858
+ from my_api_client.endpoints.users import UsersClientProtocol
859
+ from my_api_client.models.user import User
860
+
861
+ class UserRegistrationService:
862
+ """Business logic for user registration"""
863
+
864
+ def __init__(self, users_client: UsersClientProtocol):
865
+ self.users_client = users_client
866
+
867
+ async def register_user(self, name: str, email: str) -> User:
868
+ """Register a new user with validation"""
869
+ # Business logic
870
+ if not email or "@" not in email:
871
+ raise ValueError("Invalid email")
872
+
873
+ # Use API client
874
+ user = User(name=name, email=email)
875
+ return await self.users_client.create_user(user=user)
876
+
877
+ # test_my_service.py
878
+ import pytest
879
+ from my_service import UserRegistrationService
880
+ from my_api_client.endpoints.users import UsersClientProtocol
881
+ from my_api_client.models.user import User
882
+
883
+ class MockUsersClient(UsersClientProtocol):
884
+ """Type-safe mock for testing"""
885
+
886
+ def __init__(self):
887
+ self.created_users: list[User] = []
888
+
889
+ async def get_user(self, user_id: int) -> User:
890
+ return User(id=user_id, name="Test", email="test@example.com")
891
+
892
+ async def list_users(self, limit: int = 10) -> list[User]:
893
+ return []
894
+
895
+ async def create_user(self, user: User) -> User:
896
+ user.id = 123 # Simulate server assigning ID
897
+ self.created_users.append(user)
898
+ return user
899
+
900
+ @pytest.fixture
901
+ def mock_users_client() -> UsersClientProtocol:
902
+ return MockUsersClient()
903
+
904
+ async def test_register_user__valid_data__creates_user(mock_users_client):
905
+ """
906
+ When: Registering with valid data
907
+ Then: User is created via API
908
+ """
909
+ service = UserRegistrationService(mock_users_client)
910
+
911
+ user = await service.register_user(name="John", email="john@example.com")
912
+
913
+ assert user.id == 123
914
+ assert user.name == "John"
915
+ assert len(mock_users_client.created_users) == 1
916
+
917
+ async def test_register_user__invalid_email__raises_error(mock_users_client):
918
+ """
919
+ When: Registering with invalid email
920
+ Then: ValueError is raised
921
+ """
922
+ service = UserRegistrationService(mock_users_client)
923
+
924
+ with pytest.raises(ValueError, match="Invalid email"):
925
+ await service.register_user(name="John", email="invalid")
926
+
927
+ # If UsersClientProtocol changes (e.g., create_user signature changes):
928
+ # mypy error: Cannot instantiate abstract class 'MockUsersClient' with abstract method 'create_user'
929
+ # This forces you to update your mock, keeping tests in sync with API!
930
+ ```
931
+
932
+ ### Auto-Generated Mock Helper Classes
933
+
934
+ The generator creates ready-to-use mock helper classes in the `mocks/` directory, providing a faster path to testable code.
935
+
936
+ #### Generated Mocks Structure
937
+
938
+ ```
939
+ my_api_client/
940
+ ├── mocks/
941
+ │ ├── __init__.py # Exports MockAPIClient and all endpoint mocks
942
+ │ ├── mock_client.py # MockAPIClient with auto-create pattern
943
+ │ └── endpoints/
944
+ │ ├── __init__.py # Exports MockUsersClient, MockOrdersClient, etc.
945
+ │ ├── mock_users.py # MockUsersClient helper
946
+ │ └── mock_orders.py # MockOrdersClient helper
947
+ ```
948
+
949
+ #### Quick Start with Auto-Generated Mocks
950
+
951
+ Instead of manually creating mock classes, inherit from the generated helpers:
952
+
953
+ ```python
954
+ from my_api_client.mocks import MockAPIClient, MockUsersClient
955
+ from my_api_client.models.user import User
956
+
957
+ # Option 1: Override specific methods
958
+ class TestUsersClient(MockUsersClient):
959
+ """Inherit from generated mock, override only what you need"""
960
+
961
+ async def get_user(self, user_id: int) -> User:
962
+ return User(id=user_id, name="Test User", email="test@example.com")
963
+
964
+ # list_users and create_user will raise NotImplementedError with helpful messages
965
+
966
+ # Option 2: Use MockAPIClient with hybrid auto-create pattern
967
+ client = MockAPIClient(users=TestUsersClient())
968
+
969
+ # Access your custom mock
970
+ user = await client.users.get_user(user_id=123)
971
+ assert user.name == "Test User"
972
+
973
+ # Other endpoints auto-created with NotImplementedError stubs
974
+ # await client.orders.get_order(order_id=1) # Raises: NotImplementedError: Override MockOrdersClient.get_order()
975
+ ```
976
+
977
+ #### Hybrid Auto-Create Pattern
978
+
979
+ `MockAPIClient` automatically creates mock instances for all endpoint clients you don't explicitly provide:
980
+
981
+ ```python
982
+ from my_api_client.mocks import MockAPIClient, MockUsersClient, MockOrdersClient
983
+ from my_api_client.models.user import User
984
+ from my_api_client.models.order import Order
985
+
986
+ # Override only the clients you need for this test
987
+ class TestUsersClient(MockUsersClient):
988
+ async def get_user(self, user_id: int) -> User:
989
+ return User(id=user_id, name="Test User", email="test@example.com")
990
+
991
+ class TestOrdersClient(MockOrdersClient):
992
+ async def get_order(self, order_id: int) -> Order:
993
+ return Order(id=order_id, status="completed", total=99.99)
994
+
995
+ # Create client with partial overrides
996
+ client = MockAPIClient(
997
+ users=TestUsersClient(),
998
+ orders=TestOrdersClient()
999
+ # products, payments, etc. auto-created with NotImplementedError stubs
1000
+ )
1001
+
1002
+ # Use your custom mocks
1003
+ user = await client.users.get_user(user_id=123)
1004
+ order = await client.orders.get_order(order_id=456)
1005
+
1006
+ # Unimplemented endpoints provide clear error messages
1007
+ # await client.products.list_products() # NotImplementedError: Override MockProductsClient.list_products()
1008
+ ```
1009
+
1010
+ #### NotImplementedError Guidance
1011
+
1012
+ Generated mock helpers raise `NotImplementedError` with helpful messages:
1013
+
1014
+ ```python
1015
+ from my_api_client.mocks import MockUsersClient
1016
+
1017
+ mock = MockUsersClient()
1018
+
1019
+ # Attempting to call unimplemented method:
1020
+ await mock.get_user(user_id=123)
1021
+ # NotImplementedError: MockUsersClient.get_user() not implemented.
1022
+ # Override this method in your test:
1023
+ # class TestUsersClient(MockUsersClient):
1024
+ # async def get_user(self, user_id: int) -> User:
1025
+ # return User(...)
1026
+ ```
1027
+
1028
+ #### Comparison: Manual vs Auto-Generated
1029
+
1030
+ **Manual Protocol Implementation** (always available):
1031
+
1032
+ ```python
1033
+ from my_api_client.endpoints.users import UsersClientProtocol
1034
+
1035
+ class MockUsersClient(UsersClientProtocol):
1036
+ """Full control, implement all methods"""
1037
+
1038
+ async def get_user(self, user_id: int) -> User: ...
1039
+ async def list_users(self, limit: int = 10) -> list[User]: ...
1040
+ async def create_user(self, user: User) -> User: ...
1041
+ ```
1042
+
1043
+ **Auto-Generated Helper** (faster, less boilerplate):
1044
+
1045
+ ```python
1046
+ from my_api_client.mocks import MockUsersClient
1047
+
1048
+ class TestUsersClient(MockUsersClient):
1049
+ """Override only what you need"""
1050
+
1051
+ async def get_user(self, user_id: int) -> User:
1052
+ return User(id=user_id, name="Test User", email="test@example.com")
1053
+
1054
+ # Other methods inherited with NotImplementedError stubs
1055
+ ```
1056
+
1057
+ **Use auto-generated mocks when**:
1058
+
1059
+ - You want to quickly get started with testing
1060
+ - You only need to override specific methods
1061
+ - You prefer helpful NotImplementedError messages over abstract method errors
1062
+
1063
+ **Use manual Protocol implementation when**:
1064
+
1065
+ - You need complete control over all mock behavior
1066
+ - You're building reusable test fixtures
1067
+ - You want explicit tracking of all method calls
1068
+
1069
+ Both approaches are type-safe and provide compile-time validation!
1070
+
1071
+ ## Supported OpenAPI Formats
1072
+
1073
+ The generator maps OpenAPI `format` specifiers to appropriate Python types with automatic serialisation/deserialisation:
1074
+
1075
+ | OpenAPI Format | Python Type | Notes |
1076
+ | ------------------ | ----------------------- | -------------------------------- |
1077
+ | `date-time` | `datetime.datetime` | ISO 8601 parsing |
1078
+ | `date` | `datetime.date` | ISO 8601 parsing |
1079
+ | `time` | `datetime.time` | ISO 8601 parsing |
1080
+ | `duration` | `datetime.timedelta` | ISO 8601 duration |
1081
+ | `uuid` | `uuid.UUID` | Standard UUID format |
1082
+ | `binary` | `bytes` | Raw binary data |
1083
+ | `byte` | `bytes` | Base64 encoded (auto-decoded) |
1084
+ | `ipv4` | `ipaddress.IPv4Address` | IPv4 address validation |
1085
+ | `ipv6` | `ipaddress.IPv6Address` | IPv6 address validation |
1086
+ | `uri` / `url` | `str` | No special handling |
1087
+ | `email` | `str` | No special handling |
1088
+ | `hostname` | `str` | No special handling |
1089
+ | `password` | `str` | No special handling |
1090
+ | `int32` / `int64` | `int` | Python int (unlimited precision) |
1091
+ | `float` / `double` | `float` | Python float |
1092
+
1093
+ > 💡 For `byte` format, the generated client automatically handles base64 encoding/decoding via cattrs hooks.
1094
+
1095
+ ## Known Limitations
1096
+
1097
+ Some OpenAPI features have simplified implementations:
1098
+
1099
+ | Feature | Current Behavior | Workaround |
1100
+ | --------------------------- | --------------------------------------------------- | -------------------------------------------------- |
1101
+ | **Parameter Serialization** | Uses httpx defaults (not OpenAPI `style`/`explode`) | Manually format complex parameters |
1102
+ | **Response Headers** | Only body is returned, headers are ignored | Use custom transport to access full response |
1103
+ | **Multipart Forms** | Basic file upload only | Complex multipart schemas may need manual handling |
1104
+ | **Parameter Defaults** | Schema defaults not in method signatures | Pass defaults explicitly when calling |
1105
+ | **WebSockets** | Not currently supported | Use separate WebSocket library |
1106
+
1107
+ > 💡 These limitations rarely affect real-world usage. Most APIs work perfectly with the current implementation.
1108
+
1109
+ ## Architecture
1110
+
1111
+ PyOpenAPI Generator uses a sophisticated three-stage pipeline designed for enterprise-grade reliability:
1112
+
1113
+ ```mermaid
1114
+ graph TD
1115
+ A[OpenAPI Spec] --> B[Loading Stage]
1116
+ B --> C[Intermediate Representation]
1117
+ C --> D[Unified Type Resolution]
1118
+ D --> E[Visiting Stage]
1119
+ E --> F[Python Code AST]
1120
+ F --> G[Emitting Stage]
1121
+ G --> H[Generated Files]
1122
+ H --> I[Post-Processing]
1123
+ I --> J[Final Client Package]
1124
+
1125
+ subgraph "Key Components"
1126
+ K[Schema Parser]
1127
+ L[Cycle Detection]
1128
+ M[Reference Resolution]
1129
+ N[Type Service]
1130
+ O[Code Emitters]
1131
+ end
1132
+ ```
1133
+
1134
+ ### Why This Architecture?
1135
+
1136
+ **Complex Schema Handling**: Modern OpenAPI specs contain circular references, deep nesting, and intricate type relationships. Our architecture handles these robustly.
1137
+
1138
+ **Production Ready**: Each stage has clear responsibilities and clean interfaces, enabling comprehensive testing and reliable code generation.
1139
+
1140
+ **Extensible**: Plugin-based authentication, customizable type resolution, and modular emitters make the system adaptable to various use cases.
1141
+
1142
+ ## 📚 Documentation
1143
+
1144
+ - **[Architecture Guide](docs/architecture.md)** - Deep dive into the system design
1145
+ - **[Type Resolution](docs/unified_type_resolution.md)** - How types are resolved and generated
1146
+ - **[Contributing Guide](CONTRIBUTING.md)** - How to contribute to the project
1147
+ - **[API Reference](docs/)** - Complete API documentation
1148
+
1149
+ ## 🤝 Contributing
1150
+
1151
+ We welcome contributions! Whether you're fixing bugs, adding features, or improving documentation, your help is appreciated.
1152
+
1153
+ **For Contributors**: See our [Contributing Guide](CONTRIBUTING.md) for:
1154
+
1155
+ - Development setup and workflow
1156
+ - Testing requirements (85% coverage, mypy strict mode)
1157
+ - Code quality standards
1158
+ - Pull request process
1159
+
1160
+ **Quick Links**:
1161
+
1162
+ - [Architecture Documentation](docs/architecture.md) - System design and patterns
1163
+ - [Issue Tracker](https://github.com/mindhiveoy/pyopenapi_gen/issues) - Report bugs or request features
1164
+
1165
+ ## 📄 License
1166
+
1167
+ MIT License - see [LICENSE](LICENSE) file for details.
1168
+
1169
+ Generated clients are self-contained and can be distributed under any license compatible with your project.