pyopenapi-gen 0.21.1__py3-none-any.whl → 0.22.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 +1 -1
- pyopenapi_gen/emitters/endpoints_emitter.py +18 -3
- pyopenapi_gen/emitters/mocks_emitter.py +185 -0
- pyopenapi_gen/generator/client_generator.py +15 -0
- pyopenapi_gen/visit/client_visitor.py +253 -2
- pyopenapi_gen/visit/endpoint/endpoint_visitor.py +209 -1
- pyopenapi_gen/visit/endpoint/generators/mock_generator.py +140 -0
- pyopenapi_gen-0.22.0.dist-info/METADATA +1139 -0
- {pyopenapi_gen-0.21.1.dist-info → pyopenapi_gen-0.22.0.dist-info}/RECORD +12 -10
- pyopenapi_gen-0.21.1.dist-info/METADATA +0 -645
- {pyopenapi_gen-0.21.1.dist-info → pyopenapi_gen-0.22.0.dist-info}/WHEEL +0 -0
- {pyopenapi_gen-0.21.1.dist-info → pyopenapi_gen-0.22.0.dist-info}/entry_points.txt +0 -0
- {pyopenapi_gen-0.21.1.dist-info → pyopenapi_gen-0.22.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
pyopenapi_gen/__init__.py,sha256=
|
|
1
|
+
pyopenapi_gen/__init__.py,sha256=JkkSu5QNIMudrm3Wqxzv2pTYx-aTJYPw0EeMkoXr_Mg,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
|
|
@@ -71,11 +71,12 @@ pyopenapi_gen/emitters/CLAUDE.md,sha256=iZYEZq1a1h033rxuh97cMpsKUElv72ysvTm3-QQU
|
|
|
71
71
|
pyopenapi_gen/emitters/client_emitter.py,sha256=kmMVnG-wAOJm7TUm0xOQ5YnSJfYxz1SwtpiyoUCbcCA,1939
|
|
72
72
|
pyopenapi_gen/emitters/core_emitter.py,sha256=YSuqDlYv3P687TsVT_Z9n7a6GZepGAvv3-N1Q2kK6Zg,7975
|
|
73
73
|
pyopenapi_gen/emitters/docs_emitter.py,sha256=aouKqhRdtVvYfGVsye_uqM80nONRy0SqN06cr1l3OgA,1137
|
|
74
|
-
pyopenapi_gen/emitters/endpoints_emitter.py,sha256=
|
|
74
|
+
pyopenapi_gen/emitters/endpoints_emitter.py,sha256=m4Kts3-hxflNyCXcKtakyeHLELKLqVjcKLl4_F_1rhQ,10716
|
|
75
75
|
pyopenapi_gen/emitters/exceptions_emitter.py,sha256=PfbDQX7dfgg2htvxEh40t7FR7b3BrK8jeRd5INu_kjk,7547
|
|
76
|
+
pyopenapi_gen/emitters/mocks_emitter.py,sha256=D8fjD8KbgG-yFiqCEEAe2rKE2B439pW6bRLvoqQ6SdI,7431
|
|
76
77
|
pyopenapi_gen/emitters/models_emitter.py,sha256=wJwtvCGEmhy5yhojfoUW2CXNOQytGlN4J8-GcwoYIMY,22221
|
|
77
78
|
pyopenapi_gen/generator/CLAUDE.md,sha256=BS9KkmLvk2WD-Io-_apoWjGNeMU4q4LKy4UOxYF9WxM,10870
|
|
78
|
-
pyopenapi_gen/generator/client_generator.py,sha256=
|
|
79
|
+
pyopenapi_gen/generator/client_generator.py,sha256=THpFt296rHAQRRus9HqbtMswQadbk_5ZLr-5nRkzOHY,30368
|
|
79
80
|
pyopenapi_gen/helpers/CLAUDE.md,sha256=GyIJ0grp4SkD3plAUzyycW4nTUZf9ewtvvsdAGkmIZw,10609
|
|
80
81
|
pyopenapi_gen/helpers/__init__.py,sha256=m4jSQ1sDH6CesIcqIl_kox4LcDFabGxBpSIWVwbHK0M,39
|
|
81
82
|
pyopenapi_gen/helpers/endpoint_utils.py,sha256=9qVMIOxJUdJePFskA2A0AFMox_h64Liyc4EfNZNXrXU,22155
|
|
@@ -104,15 +105,16 @@ pyopenapi_gen/types/services/type_service.py,sha256=3IH0GBLeC-ruokBmhvC8iR5GT5tm
|
|
|
104
105
|
pyopenapi_gen/types/strategies/__init__.py,sha256=bju8_KEPNIow1-woMO-zJCgK_E0M6JnFq0NFsK1R4Ss,171
|
|
105
106
|
pyopenapi_gen/types/strategies/response_strategy.py,sha256=TmXX2YDQB0cQQ7BvRCGSJM6Iu1p0MMJioCi-_8HPCAg,7341
|
|
106
107
|
pyopenapi_gen/visit/CLAUDE.md,sha256=Rq2e4S74TXv0ua2ZcCrO6cwCCccf3Yph44oVdj1yFPY,8297
|
|
107
|
-
pyopenapi_gen/visit/client_visitor.py,sha256=
|
|
108
|
+
pyopenapi_gen/visit/client_visitor.py,sha256=7Ey9j0oI6z8bSAUpXdmStYqmQMxZ2NDvMcaHGO0pkO0,20798
|
|
108
109
|
pyopenapi_gen/visit/docs_visitor.py,sha256=hqgd4DAoy7T5Bap4mpH4R-nIZSyAWwFYmrIuNHM03Rg,1644
|
|
109
110
|
pyopenapi_gen/visit/exception_visitor.py,sha256=D4LtLqdeS34kw6WbwhoWeMQzlh9uHqGNZjFtY0kq3Q4,3855
|
|
110
111
|
pyopenapi_gen/visit/visitor.py,sha256=KyiOWpNpJXkEOICpJJzqMmzbK4h0F5_7XZkE4R9GYRc,3626
|
|
111
112
|
pyopenapi_gen/visit/endpoint/__init__.py,sha256=DftIZSWp6Z8jKWoJE2VGKL4G_5cqwFXe9v-PALMmsGk,73
|
|
112
|
-
pyopenapi_gen/visit/endpoint/endpoint_visitor.py,sha256=
|
|
113
|
+
pyopenapi_gen/visit/endpoint/endpoint_visitor.py,sha256=VlIMcEr2DWlcPqcYjHp12sUPaqE-HgSiBteOoylBeG0,12591
|
|
113
114
|
pyopenapi_gen/visit/endpoint/generators/__init__.py,sha256=-X-GYnJZ9twiEBr_U0obW8VuSoY6IJmYaxinn-seMzA,50
|
|
114
115
|
pyopenapi_gen/visit/endpoint/generators/docstring_generator.py,sha256=_VpRabZ2g_N42TTWGI6gtPxqzlZD65dOHm8U_YvtWbQ,5891
|
|
115
116
|
pyopenapi_gen/visit/endpoint/generators/endpoint_method_generator.py,sha256=-Hpae2H1gD4DbBb5LikvAw1rI_cUTaGZV8l4OgI9EbE,10234
|
|
117
|
+
pyopenapi_gen/visit/endpoint/generators/mock_generator.py,sha256=LNKnhS6_qab1heHF5oR0B-BIIeqUVyWL9g1Tb_iag44,5242
|
|
116
118
|
pyopenapi_gen/visit/endpoint/generators/overload_generator.py,sha256=XijowsP3SS6kJrX3Yq1h2foO5_Nranzrels3N0hinCQ,9347
|
|
117
119
|
pyopenapi_gen/visit/endpoint/generators/request_generator.py,sha256=MKtZ6Fa050gCgqAGhXeo--p_AzqV9RmDd8e4Zvglgo0,5349
|
|
118
120
|
pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py,sha256=Jb_8hRQY1OfpThupa7UdCNxa712m3WaeNIbLhlYk1bg,24193
|
|
@@ -126,8 +128,8 @@ pyopenapi_gen/visit/model/alias_generator.py,sha256=wEMHipPA1_CFxvQ6CS9j4qgXK93s
|
|
|
126
128
|
pyopenapi_gen/visit/model/dataclass_generator.py,sha256=L794s2yyYUO__akGd120NJMV7g2xqiPfQyZo8CduIVM,14426
|
|
127
129
|
pyopenapi_gen/visit/model/enum_generator.py,sha256=AXqKUFuWUUjUF_6_HqBKY8vB5GYu35Pb2C2WPFrOw1k,10061
|
|
128
130
|
pyopenapi_gen/visit/model/model_visitor.py,sha256=TC6pbxpQiy5FWhmQpfllLuXA3ImTYNMcrazkOFZCIyo,9470
|
|
129
|
-
pyopenapi_gen-0.
|
|
130
|
-
pyopenapi_gen-0.
|
|
131
|
-
pyopenapi_gen-0.
|
|
132
|
-
pyopenapi_gen-0.
|
|
133
|
-
pyopenapi_gen-0.
|
|
131
|
+
pyopenapi_gen-0.22.0.dist-info/METADATA,sha256=H7eYO9pdSaTjX0g4zP7SdwOEjU7PFFZtNR7XspSAoDc,37434
|
|
132
|
+
pyopenapi_gen-0.22.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
133
|
+
pyopenapi_gen-0.22.0.dist-info/entry_points.txt,sha256=gxSlNiwom50T3OEZnlocA6qRjGdV0bn6hN_Xr-Ub5wA,56
|
|
134
|
+
pyopenapi_gen-0.22.0.dist-info/licenses/LICENSE,sha256=UFAyTWKa4w10-QerlJaHJeep7G2gcwpf-JmvI2dS2Gc,1088
|
|
135
|
+
pyopenapi_gen-0.22.0.dist-info/RECORD,,
|
|
@@ -1,645 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: pyopenapi-gen
|
|
3
|
-
Version: 0.21.1
|
|
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.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
|
|
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
|
-
[](https://python.org)
|
|
62
|
-
[](https://opensource.org/licenses/MIT)
|
|
63
|
-
[](https://github.com/psf/black)
|
|
64
|
-
[](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 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
|
-
## 🐍 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
|
-
|
|
374
|
-
## 🔧 Configuration Options
|
|
375
|
-
|
|
376
|
-
### Standalone Client (Default)
|
|
377
|
-
```bash
|
|
378
|
-
pyopenapi-gen openapi.yaml \
|
|
379
|
-
--project-root . \
|
|
380
|
-
--output-package my_api_client
|
|
381
|
-
```
|
|
382
|
-
Creates self-contained client with embedded core dependencies.
|
|
383
|
-
|
|
384
|
-
### Shared Core (Multiple Clients)
|
|
385
|
-
```bash
|
|
386
|
-
pyopenapi-gen openapi.yaml \
|
|
387
|
-
--project-root . \
|
|
388
|
-
--output-package clients.api_client \
|
|
389
|
-
--core-package clients.core
|
|
390
|
-
```
|
|
391
|
-
Multiple clients share a single core implementation.
|
|
392
|
-
|
|
393
|
-
### Additional Options
|
|
394
|
-
```bash
|
|
395
|
-
--force # Overwrite without prompting
|
|
396
|
-
--no-postprocess # Skip formatting and type checking
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
## ✨ Key Features
|
|
400
|
-
|
|
401
|
-
| Feature | Description |
|
|
402
|
-
|---------|-------------|
|
|
403
|
-
| 🔒 **Type Safety** | Complete type hints, dataclass models, and mypy compatibility |
|
|
404
|
-
| ⚡ **Async-First** | Built for modern Python `async`/`await` patterns with `httpx` |
|
|
405
|
-
| 🔌 **Pluggable Auth** | Bearer, API key, OAuth2, and custom authentication strategies |
|
|
406
|
-
| 🔄 **Smart Pagination** | Auto-detected cursor/page/offset patterns with async iteration |
|
|
407
|
-
| 📦 **Zero Dependencies** | Generated clients are completely self-contained |
|
|
408
|
-
| 🛡️ **Robust Parsing** | Advanced cycle detection and graceful handling of complex specs |
|
|
409
|
-
| 🎯 **Structured Errors** | Rich exception hierarchy with meaningful error messages |
|
|
410
|
-
| 🏷️ **Tag Organization** | Operations grouped by OpenAPI tags for intuitive navigation |
|
|
411
|
-
|
|
412
|
-
## Generated Client Structure
|
|
413
|
-
|
|
414
|
-
```
|
|
415
|
-
my_api_client/
|
|
416
|
-
├── client.py # Main APIClient with tag-grouped methods
|
|
417
|
-
├── core/ # Self-contained runtime dependencies
|
|
418
|
-
│ ├── config.py # Configuration management
|
|
419
|
-
│ ├── http_transport.py # HTTP client abstraction
|
|
420
|
-
│ ├── exceptions.py # Error hierarchy
|
|
421
|
-
│ └── auth/ # Authentication plugins
|
|
422
|
-
├── models/ # Dataclass models from schemas
|
|
423
|
-
│ └── user.py
|
|
424
|
-
├── endpoints/ # Operation methods grouped by tag
|
|
425
|
-
│ └── users.py
|
|
426
|
-
└── __init__.py
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
## 🔐 Authentication
|
|
430
|
-
|
|
431
|
-
PyOpenAPI Generator supports multiple authentication patterns out of the box:
|
|
432
|
-
|
|
433
|
-
### Bearer Token
|
|
434
|
-
```python
|
|
435
|
-
from my_api_client.core.auth.plugins import BearerAuth
|
|
436
|
-
|
|
437
|
-
config = ClientConfig(
|
|
438
|
-
base_url="https://api.example.com",
|
|
439
|
-
auth=BearerAuth("your-token")
|
|
440
|
-
)
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
### API Key (Header, Query, or Cookie)
|
|
444
|
-
```python
|
|
445
|
-
from my_api_client.core.auth.plugins import ApiKeyAuth
|
|
446
|
-
|
|
447
|
-
config = ClientConfig(
|
|
448
|
-
base_url="https://api.example.com",
|
|
449
|
-
auth=ApiKeyAuth("your-key", location="header", name="X-API-Key")
|
|
450
|
-
)
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
### OAuth2 with Refresh
|
|
454
|
-
```python
|
|
455
|
-
from my_api_client.core.auth.plugins import OAuth2Auth
|
|
456
|
-
|
|
457
|
-
def refresh_token():
|
|
458
|
-
# Your token refresh logic
|
|
459
|
-
return "new-token"
|
|
460
|
-
|
|
461
|
-
config = ClientConfig(
|
|
462
|
-
base_url="https://api.example.com",
|
|
463
|
-
auth=OAuth2Auth("initial-token", refresh_callback=refresh_token)
|
|
464
|
-
)
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
### Composite Authentication
|
|
468
|
-
```python
|
|
469
|
-
from my_api_client.core.auth.base import CompositeAuth
|
|
470
|
-
from my_api_client.core.auth.plugins import BearerAuth, HeadersAuth
|
|
471
|
-
|
|
472
|
-
config = ClientConfig(
|
|
473
|
-
base_url="https://api.example.com",
|
|
474
|
-
auth=CompositeAuth(
|
|
475
|
-
BearerAuth("token"),
|
|
476
|
-
HeadersAuth({"X-Custom-Header": "value"})
|
|
477
|
-
)
|
|
478
|
-
)
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
## 📊 Advanced Features
|
|
482
|
-
|
|
483
|
-
### Pagination Support
|
|
484
|
-
```python
|
|
485
|
-
# Manual pagination
|
|
486
|
-
page = 1
|
|
487
|
-
while True:
|
|
488
|
-
users = await client.users.list_users(page=page, limit=20)
|
|
489
|
-
if not users:
|
|
490
|
-
break
|
|
491
|
-
# Process users
|
|
492
|
-
page += 1
|
|
493
|
-
|
|
494
|
-
# Automatic pagination (if supported by the API)
|
|
495
|
-
async for user in client.users.list_users_paginated():
|
|
496
|
-
print(f"User: {user.name}")
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
### Error Handling
|
|
500
|
-
```python
|
|
501
|
-
try:
|
|
502
|
-
user = await client.users.get_user(user_id=123)
|
|
503
|
-
except client.exceptions.UserNotFoundError as e:
|
|
504
|
-
print(f"User not found: {e.detail}")
|
|
505
|
-
except client.exceptions.ClientError as e:
|
|
506
|
-
print(f"Client error: {e}")
|
|
507
|
-
except client.exceptions.ServerError as e:
|
|
508
|
-
print(f"Server error: {e}")
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
### Response Unwrapping
|
|
512
|
-
Many APIs wrap responses in a `data` field. PyOpenAPI Generator automatically detects and unwraps these patterns:
|
|
513
|
-
|
|
514
|
-
```python
|
|
515
|
-
# API returns: {"data": {"id": 1, "name": "John"}, "meta": {...}}
|
|
516
|
-
# Your code receives: User(id=1, name="John")
|
|
517
|
-
user = await client.users.get_user(user_id=1)
|
|
518
|
-
print(user.name) # "John"
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
## 🚧 Known Limitations
|
|
522
|
-
|
|
523
|
-
Some OpenAPI features have simplified implementations. Contributions welcome!
|
|
524
|
-
|
|
525
|
-
| Limitation | Current Behavior |
|
|
526
|
-
|------------|------------------|
|
|
527
|
-
| **Parameter Serialization** | Uses HTTP client defaults instead of OpenAPI `style`/`explode` |
|
|
528
|
-
| **Complex Multipart** | Basic file upload support; complex schemas simplified |
|
|
529
|
-
| **Response Headers** | Only response body returned, headers ignored |
|
|
530
|
-
| **Parameter Defaults** | Schema defaults not applied to method signatures |
|
|
531
|
-
|
|
532
|
-
> 💡 **Contributing**: See our [Contributing Guide](CONTRIBUTING.md) to help enhance OpenAPI specification coverage!
|
|
533
|
-
|
|
534
|
-
## 🏗️ Architecture
|
|
535
|
-
|
|
536
|
-
PyOpenAPI Generator uses a sophisticated three-stage pipeline designed for enterprise-grade reliability:
|
|
537
|
-
|
|
538
|
-
```mermaid
|
|
539
|
-
graph TD
|
|
540
|
-
A[OpenAPI Spec] --> B[Loading Stage]
|
|
541
|
-
B --> C[Intermediate Representation]
|
|
542
|
-
C --> D[Unified Type Resolution]
|
|
543
|
-
D --> E[Visiting Stage]
|
|
544
|
-
E --> F[Python Code AST]
|
|
545
|
-
F --> G[Emitting Stage]
|
|
546
|
-
G --> H[Generated Files]
|
|
547
|
-
H --> I[Post-Processing]
|
|
548
|
-
I --> J[Final Client Package]
|
|
549
|
-
|
|
550
|
-
subgraph "Key Components"
|
|
551
|
-
K[Schema Parser]
|
|
552
|
-
L[Cycle Detection]
|
|
553
|
-
M[Reference Resolution]
|
|
554
|
-
N[Type Service]
|
|
555
|
-
O[Code Emitters]
|
|
556
|
-
end
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
### Why This Architecture?
|
|
560
|
-
|
|
561
|
-
**Complex Schema Handling**: Modern OpenAPI specs contain circular references, deep nesting, and intricate type relationships. Our architecture handles these robustly.
|
|
562
|
-
|
|
563
|
-
**Production Ready**: Each stage has clear responsibilities and clean interfaces, enabling comprehensive testing and reliable code generation.
|
|
564
|
-
|
|
565
|
-
**Extensible**: Plugin-based authentication, customizable type resolution, and modular emitters make the system adaptable to various use cases.
|
|
566
|
-
|
|
567
|
-
## 📚 Documentation
|
|
568
|
-
|
|
569
|
-
- **[Architecture Guide](docs/architecture.md)** - Deep dive into the system design
|
|
570
|
-
- **[Type Resolution](docs/unified_type_resolution.md)** - How types are resolved and generated
|
|
571
|
-
- **[Contributing Guide](CONTRIBUTING.md)** - How to contribute to the project
|
|
572
|
-
- **[API Reference](docs/)** - Complete API documentation
|
|
573
|
-
|
|
574
|
-
## 🤝 Contributing
|
|
575
|
-
|
|
576
|
-
We welcome contributions! PyOpenAPI Generator is designed to be extensible and maintainable.
|
|
577
|
-
|
|
578
|
-
### Quick Start for Contributors
|
|
579
|
-
```bash
|
|
580
|
-
# 1. Fork and clone the repository
|
|
581
|
-
git clone https://github.com/your-username/pyopenapi-gen.git
|
|
582
|
-
cd pyopenapi-gen
|
|
583
|
-
|
|
584
|
-
# 2. Set up development environment
|
|
585
|
-
source .venv/bin/activate # Activate virtual environment
|
|
586
|
-
poetry install --with dev
|
|
587
|
-
|
|
588
|
-
# 3. Run quality checks
|
|
589
|
-
make quality-fix # Auto-fix formatting and linting
|
|
590
|
-
make quality # Run all quality checks
|
|
591
|
-
make test # Run tests with coverage
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
### Development Workflow
|
|
595
|
-
```bash
|
|
596
|
-
# Essential commands for development
|
|
597
|
-
make quality-fix # Auto-fix formatting and linting issues
|
|
598
|
-
make quality # Run all quality checks (format, lint, typecheck, security)
|
|
599
|
-
make test # Run tests with 85% coverage requirement
|
|
600
|
-
make test-fast # Run tests, stop on first failure
|
|
601
|
-
|
|
602
|
-
# Individual quality commands
|
|
603
|
-
make format # Format code with Black
|
|
604
|
-
make lint-fix # Fix linting issues with Ruff
|
|
605
|
-
make typecheck # Type checking with mypy
|
|
606
|
-
make security # Security scanning with Bandit
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
### Release Process
|
|
610
|
-
The project uses **automated semantic versioning** with conventional commits:
|
|
611
|
-
|
|
612
|
-
```bash
|
|
613
|
-
# Conventional commit format triggers automatic releases
|
|
614
|
-
git commit -m "feat(auth): add OAuth2 support" # → Minor version bump
|
|
615
|
-
git commit -m "fix(parser): resolve memory leak" # → Patch version bump
|
|
616
|
-
|
|
617
|
-
# Push to main triggers automatic PyPI release
|
|
618
|
-
git push origin main
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
All releases are automatically published to PyPI with generated changelogs. See [Release Management](CLAUDE.md#release-management--semantic-versioning) for complete details.
|
|
622
|
-
|
|
623
|
-
See our [Contributing Guide](CONTRIBUTING.md) for detailed information on:
|
|
624
|
-
- 📋 Development setup and workflow
|
|
625
|
-
- 🧪 Testing guidelines and standards
|
|
626
|
-
- 📖 Documentation standards
|
|
627
|
-
- 🔄 Pull request process
|
|
628
|
-
- 🏗️ Architecture and design patterns
|
|
629
|
-
|
|
630
|
-
## 📄 License
|
|
631
|
-
|
|
632
|
-
MIT License - see [LICENSE](LICENSE) file for details.
|
|
633
|
-
|
|
634
|
-
Generated clients are self-contained and can be distributed under any license compatible with your project.
|
|
635
|
-
|
|
636
|
-
## 🙏 Acknowledgments
|
|
637
|
-
|
|
638
|
-
- Built with [httpx](https://www.python-httpx.org/) for modern async HTTP
|
|
639
|
-
- Type safety with [mypy](https://mypy.readthedocs.io/) strict mode
|
|
640
|
-
- Code quality with [Black](https://black.readthedocs.io/) and [Ruff](https://docs.astral.sh/ruff/)
|
|
641
|
-
- Visitor pattern for clean, maintainable code generation
|
|
642
|
-
|
|
643
|
-
---
|
|
644
|
-
|
|
645
|
-
**Made with ❤️ for the Python community**
|
|
File without changes
|
|
File without changes
|