pyopenapi-gen 0.8.3__py3-none-any.whl → 0.8.5__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 (36) hide show
  1. pyopenapi_gen/cli.py +5 -22
  2. pyopenapi_gen/context/import_collector.py +8 -8
  3. pyopenapi_gen/core/loader/operations/parser.py +1 -1
  4. pyopenapi_gen/core/parsing/context.py +2 -1
  5. pyopenapi_gen/core/parsing/cycle_helpers.py +1 -1
  6. pyopenapi_gen/core/parsing/keywords/properties_parser.py +4 -4
  7. pyopenapi_gen/core/parsing/schema_parser.py +4 -4
  8. pyopenapi_gen/core/parsing/transformers/inline_enum_extractor.py +1 -1
  9. pyopenapi_gen/core/postprocess_manager.py +39 -13
  10. pyopenapi_gen/core/schemas.py +101 -16
  11. pyopenapi_gen/core/writers/python_construct_renderer.py +57 -9
  12. pyopenapi_gen/emitters/endpoints_emitter.py +1 -1
  13. pyopenapi_gen/helpers/endpoint_utils.py +4 -22
  14. pyopenapi_gen/helpers/type_cleaner.py +1 -1
  15. pyopenapi_gen/helpers/type_resolution/composition_resolver.py +1 -1
  16. pyopenapi_gen/helpers/type_resolution/finalizer.py +1 -1
  17. pyopenapi_gen/types/contracts/types.py +0 -1
  18. pyopenapi_gen/types/resolvers/response_resolver.py +5 -33
  19. pyopenapi_gen/types/resolvers/schema_resolver.py +2 -2
  20. pyopenapi_gen/types/services/type_service.py +0 -18
  21. pyopenapi_gen/types/strategies/__init__.py +5 -0
  22. pyopenapi_gen/types/strategies/response_strategy.py +187 -0
  23. pyopenapi_gen/visit/endpoint/endpoint_visitor.py +1 -20
  24. pyopenapi_gen/visit/endpoint/generators/docstring_generator.py +5 -3
  25. pyopenapi_gen/visit/endpoint/generators/endpoint_method_generator.py +12 -6
  26. pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py +352 -343
  27. pyopenapi_gen/visit/endpoint/generators/signature_generator.py +7 -4
  28. pyopenapi_gen/visit/endpoint/processors/import_analyzer.py +4 -2
  29. pyopenapi_gen/visit/endpoint/processors/parameter_processor.py +1 -1
  30. pyopenapi_gen/visit/model/dataclass_generator.py +32 -1
  31. pyopenapi_gen-0.8.5.dist-info/METADATA +383 -0
  32. {pyopenapi_gen-0.8.3.dist-info → pyopenapi_gen-0.8.5.dist-info}/RECORD +35 -33
  33. pyopenapi_gen-0.8.3.dist-info/METADATA +0 -224
  34. {pyopenapi_gen-0.8.3.dist-info → pyopenapi_gen-0.8.5.dist-info}/WHEEL +0 -0
  35. {pyopenapi_gen-0.8.3.dist-info → pyopenapi_gen-0.8.5.dist-info}/entry_points.txt +0 -0
  36. {pyopenapi_gen-0.8.3.dist-info → pyopenapi_gen-0.8.5.dist-info}/licenses/LICENSE +0 -0
@@ -10,8 +10,9 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional
10
10
  from pyopenapi_gen.core.utils import NameSanitizer
11
11
  from pyopenapi_gen.core.writers.code_writer import CodeWriter
12
12
 
13
- # Import necessary helpers from endpoint_utils if they were used directly or indirectly
14
- from pyopenapi_gen.helpers.endpoint_utils import get_param_type, get_return_type_unified # Added
13
+ # Import necessary helpers from endpoint_utils
14
+ from pyopenapi_gen.helpers.endpoint_utils import get_param_type
15
+ from pyopenapi_gen.types.strategies.response_strategy import ResponseStrategy
15
16
 
16
17
  if TYPE_CHECKING:
17
18
  from pyopenapi_gen import IROperation
@@ -32,13 +33,15 @@ class EndpointMethodSignatureGenerator:
32
33
  op: IROperation,
33
34
  context: RenderContext,
34
35
  ordered_params: List[Dict[str, Any]],
36
+ strategy: ResponseStrategy,
35
37
  ) -> None:
36
38
  """Writes the method signature to the provided CodeWriter."""
37
39
  # Logic from EndpointMethodGenerator._write_method_signature
38
40
  for p_info in ordered_params: # Renamed p to p_info to avoid conflict if IRParameter is named p
39
41
  context.add_typing_imports_for_type(p_info["type"])
40
42
 
41
- return_type = get_return_type_unified(op, context, self.schemas)
43
+ # Use strategy return type instead of computing it again
44
+ return_type = strategy.return_type
42
45
  context.add_typing_imports_for_type(return_type)
43
46
 
44
47
  # Check if AsyncIterator is in return_type or any parameter type
@@ -63,7 +66,7 @@ class EndpointMethodSignatureGenerator:
63
66
  args = ["self"]
64
67
  for p_orig in ordered_params:
65
68
  p = p_orig.copy() # Work with a copy
66
- arg_str = f"{NameSanitizer.sanitize_method_name(p['name'])}: {p['type']}" # Ensure param name is sanitized
69
+ arg_str = f"{NameSanitizer.sanitize_method_name(p["name"])}: {p["type"]}" # Ensure param name is sanitized
67
70
  if not p.get("required", False):
68
71
  # Default value handling: if default is None, it should be ' = None'
69
72
  # If default is a string, it should be ' = "default_value"'
@@ -11,12 +11,12 @@ from typing import TYPE_CHECKING, Any, Dict, Optional # IO for multipart type h
11
11
  from pyopenapi_gen.helpers.endpoint_utils import (
12
12
  get_param_type,
13
13
  get_request_body_type,
14
- get_return_type_unified,
15
14
  )
16
15
 
17
16
  if TYPE_CHECKING:
18
17
  from pyopenapi_gen import IROperation # IRParameter for op.parameters type hint
19
18
  from pyopenapi_gen.context.render_context import RenderContext
19
+ from pyopenapi_gen.types.strategies.response_strategy import ResponseStrategy
20
20
 
21
21
  logger = logging.getLogger(__name__)
22
22
 
@@ -31,6 +31,7 @@ class EndpointImportAnalyzer:
31
31
  self,
32
32
  op: IROperation,
33
33
  context: RenderContext,
34
+ response_strategy: ResponseStrategy,
34
35
  ) -> None:
35
36
  """Analyzes the operation and registers imports with the RenderContext."""
36
37
  for param in op.parameters: # op.parameters are IRParameter objects
@@ -60,7 +61,8 @@ class EndpointImportAnalyzer:
60
61
  if body_param_type:
61
62
  context.add_typing_imports_for_type(body_param_type)
62
63
 
63
- return_type = get_return_type_unified(op, context, self.schemas)
64
+ # Use the response strategy's return type for import analysis
65
+ return_type = response_strategy.return_type
64
66
  context.add_typing_imports_for_type(return_type)
65
67
 
66
68
  # Check for AsyncIterator in return type or parameter types
@@ -123,7 +123,7 @@ class EndpointParameterProcessor:
123
123
  param_details_map[body_specific_param_info["name"]] = body_specific_param_info
124
124
  else:
125
125
  logger.warning(
126
- f"Request body parameter name '{body_specific_param_info['name']}' "
126
+ f"Request body parameter name '{body_specific_param_info["name"]}' "
127
127
  f"for operation '{op.operation_id}'"
128
128
  f"collides with an existing path/query/header parameter. Check OpenAPI spec."
129
129
  )
@@ -4,7 +4,7 @@ Generates Python code for dataclasses from IRSchema objects.
4
4
 
5
5
  import json
6
6
  import logging
7
- from typing import Dict, List, Optional, Tuple
7
+ from typing import Any, Dict, List, Optional, Tuple
8
8
 
9
9
  from pyopenapi_gen import IRSchema
10
10
  from pyopenapi_gen.context.render_context import RenderContext
@@ -81,6 +81,22 @@ class DataclassGenerator:
81
81
  )
82
82
  return "None"
83
83
 
84
+ def _requires_field_mapping(self, api_field: str, python_field: str) -> bool:
85
+ """Check if field mapping is required between API and Python field names."""
86
+ return api_field != python_field
87
+
88
+ def _generate_field_mappings(self, properties: Dict[str, Any], sanitized_names: Dict[str, str]) -> Dict[str, str]:
89
+ """Generate field mappings for BaseSchema configuration."""
90
+ mappings = {}
91
+ for api_name, python_name in sanitized_names.items():
92
+ if api_name in properties and self._requires_field_mapping(api_name, python_name):
93
+ mappings[api_name] = python_name
94
+ return mappings
95
+
96
+ def _has_any_mappings(self, properties: Dict[str, Any], sanitized_names: Dict[str, str]) -> bool:
97
+ """Check if any field mappings are needed."""
98
+ return bool(self._generate_field_mappings(properties, sanitized_names))
99
+
84
100
  def generate(
85
101
  self,
86
102
  schema: IRSchema,
@@ -116,6 +132,7 @@ class DataclassGenerator:
116
132
 
117
133
  class_name = base_name
118
134
  fields_data: List[Tuple[str, str, Optional[str], Optional[str]]] = []
135
+ field_mappings: Dict[str, str] = {}
119
136
 
120
137
  if schema.type == "array" and schema.items:
121
138
  field_name_for_array_content = "items"
@@ -161,6 +178,10 @@ class DataclassGenerator:
161
178
  # Sanitize the property name for use as a Python attribute
162
179
  field_name = NameSanitizer.sanitize_method_name(prop_name)
163
180
 
181
+ # Track field mapping if the names differ
182
+ if self._requires_field_mapping(prop_name, field_name):
183
+ field_mappings[prop_name] = field_name
184
+
164
185
  py_type = self.type_service.resolve_schema_type(prop_schema, context, required=is_required)
165
186
  py_type = TypeFinalizer(context)._clean_type(py_type)
166
187
 
@@ -168,18 +189,28 @@ class DataclassGenerator:
168
189
  if not is_required:
169
190
  default_expr = self._get_field_default(prop_schema, context)
170
191
 
192
+ # Enhance field documentation for mapped fields
171
193
  field_doc = prop_schema.description
194
+ if field_mappings.get(prop_name) == field_name and prop_name != field_name:
195
+ if field_doc:
196
+ field_doc = f"{field_doc} (maps from '{prop_name}')"
197
+ else:
198
+ field_doc = f"Maps from '{prop_name}'"
199
+
172
200
  fields_data.append((field_name, py_type, default_expr, field_doc))
173
201
 
174
202
  # logger.debug(
175
203
  # f"DataclassGenerator: Preparing to render dataclass '{class_name}' with fields: {fields_data}."
176
204
  # )
177
205
 
206
+ # Always use BaseSchema for better developer experience
207
+ # Only include field mappings if there are actual mappings needed
178
208
  rendered_code = self.renderer.render_dataclass(
179
209
  class_name=class_name,
180
210
  fields=fields_data,
181
211
  description=schema.description,
182
212
  context=context,
213
+ field_mappings=field_mappings if field_mappings else None,
183
214
  )
184
215
 
185
216
  assert rendered_code.strip(), "Generated dataclass code cannot be empty."
@@ -0,0 +1,383 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyopenapi-gen
3
+ Version: 0.8.5
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: click<8.2.0,>=8.0.0
36
+ Requires-Dist: dataclass-wizard>=0.22.0
37
+ Requires-Dist: httpx>=0.24.0
38
+ Requires-Dist: openapi-core>=0.19
39
+ Requires-Dist: openapi-spec-validator>=0.7
40
+ Requires-Dist: pyyaml>=6.0
41
+ Requires-Dist: typer<0.14.0,>=0.12.0
42
+ Provides-Extra: dev
43
+ Requires-Dist: bandit[toml]>=1.7.0; extra == 'dev'
44
+ Requires-Dist: black>=23.0; extra == 'dev'
45
+ Requires-Dist: dataclass-wizard>=0.22.0; extra == 'dev'
46
+ Requires-Dist: httpx>=0.24.0; extra == 'dev'
47
+ Requires-Dist: mypy>=1.7; extra == 'dev'
48
+ Requires-Dist: pytest-asyncio>=0.20.0; extra == 'dev'
49
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
50
+ Requires-Dist: pytest-timeout>=2.1.0; extra == 'dev'
51
+ Requires-Dist: pytest-xdist>=3.0.0; extra == 'dev'
52
+ Requires-Dist: pytest>=7.0; extra == 'dev'
53
+ Requires-Dist: ruff>=0.4; extra == 'dev'
54
+ Requires-Dist: safety>=2.0.0; extra == 'dev'
55
+ Requires-Dist: types-pyyaml>=6.0.12; extra == 'dev'
56
+ Requires-Dist: types-toml>=0.10.8; extra == 'dev'
57
+ Description-Content-Type: text/markdown
58
+
59
+ # PyOpenAPI Generator
60
+
61
+ [![Python](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://python.org)
62
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
63
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
64
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
65
+
66
+ **Modern, enterprise-grade Python client generator for OpenAPI specifications.**
67
+
68
+ PyOpenAPI Generator creates async-first, strongly-typed Python clients from OpenAPI specs. Built for production use with advanced cycle detection, unified type resolution, and zero runtime dependencies.
69
+
70
+ ## 🚀 Why PyOpenAPI Generator?
71
+
72
+ ### Modern Python Architecture
73
+ - **Async-First**: Built for `async`/`await` with `httpx` for optimal performance
74
+ - **Type Safety**: Complete type hints, dataclass models, and mypy compatibility
75
+ - **Zero Dependencies**: Generated clients are completely self-contained
76
+
77
+ ### Enterprise-Grade Reliability
78
+ - **Advanced Cycle Detection**: Handles complex schemas with circular references
79
+ - **Unified Type Resolution**: Consistent, testable type resolution across all components
80
+ - **Production Ready**: Comprehensive error handling and robust code generation
81
+
82
+ ### Developer Experience
83
+ - **IDE Support**: Rich autocomplete and type checking in modern IDEs
84
+ - **Tag Organization**: Operations grouped by OpenAPI tags for intuitive navigation
85
+ - **Smart Features**: Auto-detected pagination, response unwrapping, and structured exceptions
86
+
87
+ ## 📦 Installation
88
+
89
+ ```bash
90
+ pip install pyopenapi-gen
91
+ ```
92
+
93
+ Or with Poetry:
94
+ ```bash
95
+ poetry add pyopenapi-gen
96
+ ```
97
+
98
+ ## ⚡ Quick Start
99
+
100
+ ### 1. Generate Your First Client
101
+ ```bash
102
+ pyopenapi-gen gen openapi.yaml \
103
+ --project-root . \
104
+ --output-package my_api_client
105
+ ```
106
+
107
+ ### 2. Use the Generated Client
108
+ ```python
109
+ import asyncio
110
+ from my_api_client.client import APIClient
111
+ from my_api_client.core.config import ClientConfig
112
+
113
+ async def main():
114
+ config = ClientConfig(base_url="https://api.example.com")
115
+ async with APIClient(config) as client:
116
+ # Type-safe API calls with full IDE support
117
+ users = await client.users.list_users(page=1)
118
+
119
+ # Automatic pagination
120
+ async for user in client.users.list_users_paginated():
121
+ print(f"User: {user.name}")
122
+
123
+ asyncio.run(main())
124
+ ```
125
+
126
+ ## 🔧 Configuration Options
127
+
128
+ ### Standalone Client (Default)
129
+ ```bash
130
+ pyopenapi-gen gen openapi.yaml \
131
+ --project-root . \
132
+ --output-package my_api_client
133
+ ```
134
+ Creates self-contained client with embedded core dependencies.
135
+
136
+ ### Shared Core (Multiple Clients)
137
+ ```bash
138
+ pyopenapi-gen gen openapi.yaml \
139
+ --project-root . \
140
+ --output-package clients.api_client \
141
+ --core-package clients.core
142
+ ```
143
+ Multiple clients share a single core implementation.
144
+
145
+ ### Additional Options
146
+ ```bash
147
+ --force # Overwrite without prompting
148
+ --no-postprocess # Skip formatting and type checking
149
+ ```
150
+
151
+ ## ✨ Key Features
152
+
153
+ | Feature | Description |
154
+ |---------|-------------|
155
+ | 🔒 **Type Safety** | Complete type hints, dataclass models, and mypy compatibility |
156
+ | ⚡ **Async-First** | Built for modern Python `async`/`await` patterns with `httpx` |
157
+ | 🔌 **Pluggable Auth** | Bearer, API key, OAuth2, and custom authentication strategies |
158
+ | 🔄 **Smart Pagination** | Auto-detected cursor/page/offset patterns with async iteration |
159
+ | 📦 **Zero Dependencies** | Generated clients are completely self-contained |
160
+ | 🛡️ **Robust Parsing** | Advanced cycle detection and graceful handling of complex specs |
161
+ | 🎯 **Structured Errors** | Rich exception hierarchy with meaningful error messages |
162
+ | 🏷️ **Tag Organization** | Operations grouped by OpenAPI tags for intuitive navigation |
163
+
164
+ ## Generated Client Structure
165
+
166
+ ```
167
+ my_api_client/
168
+ ├── client.py # Main APIClient with tag-grouped methods
169
+ ├── core/ # Self-contained runtime dependencies
170
+ │ ├── config.py # Configuration management
171
+ │ ├── http_transport.py # HTTP client abstraction
172
+ │ ├── exceptions.py # Error hierarchy
173
+ │ └── auth/ # Authentication plugins
174
+ ├── models/ # Dataclass models from schemas
175
+ │ └── user.py
176
+ ├── endpoints/ # Operation methods grouped by tag
177
+ │ └── users.py
178
+ └── __init__.py
179
+ ```
180
+
181
+ ## 🔐 Authentication
182
+
183
+ PyOpenAPI Generator supports multiple authentication patterns out of the box:
184
+
185
+ ### Bearer Token
186
+ ```python
187
+ from my_api_client.core.auth.plugins import BearerAuth
188
+
189
+ config = ClientConfig(
190
+ base_url="https://api.example.com",
191
+ auth=BearerAuth("your-token")
192
+ )
193
+ ```
194
+
195
+ ### API Key (Header, Query, or Cookie)
196
+ ```python
197
+ from my_api_client.core.auth.plugins import ApiKeyAuth
198
+
199
+ config = ClientConfig(
200
+ base_url="https://api.example.com",
201
+ auth=ApiKeyAuth("your-key", location="header", name="X-API-Key")
202
+ )
203
+ ```
204
+
205
+ ### OAuth2 with Refresh
206
+ ```python
207
+ from my_api_client.core.auth.plugins import OAuth2Auth
208
+
209
+ def refresh_token():
210
+ # Your token refresh logic
211
+ return "new-token"
212
+
213
+ config = ClientConfig(
214
+ base_url="https://api.example.com",
215
+ auth=OAuth2Auth("initial-token", refresh_callback=refresh_token)
216
+ )
217
+ ```
218
+
219
+ ### Composite Authentication
220
+ ```python
221
+ from my_api_client.core.auth.base import CompositeAuth
222
+ from my_api_client.core.auth.plugins import BearerAuth, HeadersAuth
223
+
224
+ config = ClientConfig(
225
+ base_url="https://api.example.com",
226
+ auth=CompositeAuth(
227
+ BearerAuth("token"),
228
+ HeadersAuth({"X-Custom-Header": "value"})
229
+ )
230
+ )
231
+ ```
232
+
233
+ ## 📊 Advanced Features
234
+
235
+ ### Pagination Support
236
+ ```python
237
+ # Manual pagination
238
+ page = 1
239
+ while True:
240
+ users = await client.users.list_users(page=page, limit=20)
241
+ if not users:
242
+ break
243
+ # Process users
244
+ page += 1
245
+
246
+ # Automatic pagination (if supported by the API)
247
+ async for user in client.users.list_users_paginated():
248
+ print(f"User: {user.name}")
249
+ ```
250
+
251
+ ### Error Handling
252
+ ```python
253
+ try:
254
+ user = await client.users.get_user(user_id=123)
255
+ except client.exceptions.UserNotFoundError as e:
256
+ print(f"User not found: {e.detail}")
257
+ except client.exceptions.ClientError as e:
258
+ print(f"Client error: {e}")
259
+ except client.exceptions.ServerError as e:
260
+ print(f"Server error: {e}")
261
+ ```
262
+
263
+ ### Response Unwrapping
264
+ Many APIs wrap responses in a `data` field. PyOpenAPI Generator automatically detects and unwraps these patterns:
265
+
266
+ ```python
267
+ # API returns: {"data": {"id": 1, "name": "John"}, "meta": {...}}
268
+ # Your code receives: User(id=1, name="John")
269
+ user = await client.users.get_user(user_id=1)
270
+ print(user.name) # "John"
271
+ ```
272
+
273
+ ## 🚧 Known Limitations
274
+
275
+ Some OpenAPI features have simplified implementations. Contributions welcome!
276
+
277
+ | Limitation | Current Behavior |
278
+ |------------|------------------|
279
+ | **Parameter Serialization** | Uses HTTP client defaults instead of OpenAPI `style`/`explode` |
280
+ | **Complex Multipart** | Basic file upload support; complex schemas simplified |
281
+ | **Response Headers** | Only response body returned, headers ignored |
282
+ | **Parameter Defaults** | Schema defaults not applied to method signatures |
283
+
284
+ > 💡 **Contributing**: See our [Contributing Guide](CONTRIBUTING.md) to help enhance OpenAPI specification coverage!
285
+
286
+ ## 🏗️ Architecture
287
+
288
+ PyOpenAPI Generator uses a sophisticated three-stage pipeline designed for enterprise-grade reliability:
289
+
290
+ ```mermaid
291
+ graph TD
292
+ A[OpenAPI Spec] --> B[Loading Stage]
293
+ B --> C[Intermediate Representation]
294
+ C --> D[Unified Type Resolution]
295
+ D --> E[Visiting Stage]
296
+ E --> F[Python Code AST]
297
+ F --> G[Emitting Stage]
298
+ G --> H[Generated Files]
299
+ H --> I[Post-Processing]
300
+ I --> J[Final Client Package]
301
+
302
+ subgraph "Key Components"
303
+ K[Schema Parser]
304
+ L[Cycle Detection]
305
+ M[Reference Resolution]
306
+ N[Type Service]
307
+ O[Code Emitters]
308
+ end
309
+ ```
310
+
311
+ ### Why This Architecture?
312
+
313
+ **Complex Schema Handling**: Modern OpenAPI specs contain circular references, deep nesting, and intricate type relationships. Our architecture handles these robustly.
314
+
315
+ **Production Ready**: Each stage has clear responsibilities and clean interfaces, enabling comprehensive testing and reliable code generation.
316
+
317
+ **Extensible**: Plugin-based authentication, customizable type resolution, and modular emitters make the system adaptable to various use cases.
318
+
319
+ ## 📚 Documentation
320
+
321
+ - **[Architecture Guide](docs/architecture.md)** - Deep dive into the system design
322
+ - **[Type Resolution](docs/unified_type_resolution.md)** - How types are resolved and generated
323
+ - **[Contributing Guide](CONTRIBUTING.md)** - How to contribute to the project
324
+ - **[API Reference](docs/)** - Complete API documentation
325
+
326
+ ## 🤝 Contributing
327
+
328
+ We welcome contributions! PyOpenAPI Generator is designed to be extensible and maintainable.
329
+
330
+ ### Quick Start for Contributors
331
+ ```bash
332
+ # 1. Fork and clone the repository
333
+ git clone https://github.com/your-username/pyopenapi-gen.git
334
+ cd pyopenapi-gen
335
+
336
+ # 2. Set up development environment
337
+ source .venv/bin/activate # Activate virtual environment
338
+ poetry install --with dev
339
+
340
+ # 3. Run quality checks
341
+ make quality-fix # Auto-fix formatting and linting
342
+ make quality # Run all quality checks
343
+ make test # Run tests with coverage
344
+ ```
345
+
346
+ ### Development Workflow
347
+ ```bash
348
+ # Essential commands for development
349
+ make quality-fix # Auto-fix formatting and linting issues
350
+ make quality # Run all quality checks (format, lint, typecheck, security)
351
+ make test # Run tests with 85% coverage requirement
352
+ make test-fast # Run tests, stop on first failure
353
+
354
+ # Individual quality commands
355
+ make format # Format code with Black
356
+ make lint-fix # Fix linting issues with Ruff
357
+ make typecheck # Type checking with mypy
358
+ make security # Security scanning with Bandit
359
+ ```
360
+
361
+ See our [Contributing Guide](CONTRIBUTING.md) for detailed information on:
362
+ - 📋 Development setup and workflow
363
+ - 🧪 Testing guidelines and standards
364
+ - 📖 Documentation standards
365
+ - 🔄 Pull request process
366
+ - 🏗️ Architecture and design patterns
367
+
368
+ ## 📄 License
369
+
370
+ MIT License - see [LICENSE](LICENSE) file for details.
371
+
372
+ Generated clients are self-contained and can be distributed under any license compatible with your project.
373
+
374
+ ## 🙏 Acknowledgments
375
+
376
+ - Built with [httpx](https://www.python-httpx.org/) for modern async HTTP
377
+ - Type safety with [mypy](https://mypy.readthedocs.io/) strict mode
378
+ - Code quality with [Black](https://black.readthedocs.io/) and [Ruff](https://docs.astral.sh/ruff/)
379
+ - Visitor pattern for clean, maintainable code generation
380
+
381
+ ---
382
+
383
+ **Made with ❤️ for the Python community**