pyopenapi-gen 0.21.0__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.

@@ -1,645 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: pyopenapi-gen
3
- Version: 0.21.0
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
- [![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 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**