otterapi 0.0.5__py3-none-any.whl → 0.0.6__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 (52) hide show
  1. README.md +581 -8
  2. otterapi/__init__.py +73 -0
  3. otterapi/cli.py +327 -29
  4. otterapi/codegen/__init__.py +115 -0
  5. otterapi/codegen/ast_utils.py +134 -5
  6. otterapi/codegen/client.py +1271 -0
  7. otterapi/codegen/codegen.py +1736 -0
  8. otterapi/codegen/dataframes.py +392 -0
  9. otterapi/codegen/emitter.py +473 -0
  10. otterapi/codegen/endpoints.py +2597 -343
  11. otterapi/codegen/pagination.py +1026 -0
  12. otterapi/codegen/schema.py +593 -0
  13. otterapi/codegen/splitting.py +1397 -0
  14. otterapi/codegen/types.py +1345 -0
  15. otterapi/codegen/utils.py +180 -1
  16. otterapi/config.py +1017 -24
  17. otterapi/exceptions.py +231 -0
  18. otterapi/openapi/__init__.py +46 -0
  19. otterapi/openapi/v2/__init__.py +86 -0
  20. otterapi/openapi/v2/spec.json +1607 -0
  21. otterapi/openapi/v2/v2.py +1776 -0
  22. otterapi/openapi/v3/__init__.py +131 -0
  23. otterapi/openapi/v3/spec.json +1651 -0
  24. otterapi/openapi/v3/v3.py +1557 -0
  25. otterapi/openapi/v3_1/__init__.py +133 -0
  26. otterapi/openapi/v3_1/spec.json +1411 -0
  27. otterapi/openapi/v3_1/v3_1.py +798 -0
  28. otterapi/openapi/v3_2/__init__.py +133 -0
  29. otterapi/openapi/v3_2/spec.json +1666 -0
  30. otterapi/openapi/v3_2/v3_2.py +777 -0
  31. otterapi/tests/__init__.py +3 -0
  32. otterapi/tests/fixtures/__init__.py +455 -0
  33. otterapi/tests/test_ast_utils.py +680 -0
  34. otterapi/tests/test_codegen.py +610 -0
  35. otterapi/tests/test_dataframe.py +1038 -0
  36. otterapi/tests/test_exceptions.py +493 -0
  37. otterapi/tests/test_openapi_support.py +616 -0
  38. otterapi/tests/test_openapi_upgrade.py +215 -0
  39. otterapi/tests/test_pagination.py +1101 -0
  40. otterapi/tests/test_splitting_config.py +319 -0
  41. otterapi/tests/test_splitting_integration.py +427 -0
  42. otterapi/tests/test_splitting_resolver.py +512 -0
  43. otterapi/tests/test_splitting_tree.py +525 -0
  44. otterapi-0.0.6.dist-info/METADATA +627 -0
  45. otterapi-0.0.6.dist-info/RECORD +48 -0
  46. {otterapi-0.0.5.dist-info → otterapi-0.0.6.dist-info}/WHEEL +1 -1
  47. otterapi/codegen/generator.py +0 -358
  48. otterapi/codegen/openapi_processor.py +0 -27
  49. otterapi/codegen/type_generator.py +0 -559
  50. otterapi-0.0.5.dist-info/METADATA +0 -54
  51. otterapi-0.0.5.dist-info/RECORD +0 -16
  52. {otterapi-0.0.5.dist-info → otterapi-0.0.6.dist-info}/entry_points.txt +0 -0
README.md CHANGED
@@ -2,25 +2,598 @@
2
2
 
3
3
  > *A cute and intelligent OpenAPI client generator that dives deep into your OpenAPIs*
4
4
 
5
- **OtterAPI** is a sleek Python library that transforms OpenAPI specifications into clean, type-safe client code.
5
+ **OtterAPI** is a sleek Python library that transforms OpenAPI specifications into clean, type-safe client code with Pydantic models and httpx-based HTTP clients.
6
+
7
+ ## ✨ Features
8
+
9
+ - **Type-Safe Code Generation** - Generates Pydantic models and fully typed endpoint functions
10
+ - **Sync & Async Support** - Generate both synchronous and asynchronous API clients
11
+ - **OpenAPI 3.x Support** - Full support for OpenAPI 3.0, 3.1, and 3.2 specifications
12
+ - **Module Splitting** - Organize large APIs into multiple organized files
13
+ - **Customizable Client** - Generated client class with configurable base URL, timeout, and headers
14
+ - **Environment Variable Support** - Use `${VAR}` or `${VAR:-default}` syntax in config files
6
15
 
7
16
  ## 🚀 Quick Start
8
17
 
18
+ ### Installation
19
+
20
+ ```bash
21
+ pip install otterapi
22
+ ```
23
+
24
+ ### Basic Usage
25
+
26
+ 1. Create an `otter.yml` configuration file:
27
+
28
+ ```yaml
29
+ documents:
30
+ - source: https://petstore3.swagger.io/api/v3/openapi.json
31
+ output: petstore_client
32
+ ```
33
+
34
+ 2. Generate the client:
35
+
9
36
  ```bash
10
- # Generate from a pyproject.toml or any of the default config names (otter.yml, otter.yaml)
11
37
  otter generate
38
+ ```
39
+
40
+ 3. Use the generated code:
41
+
42
+ ```python
43
+ from petstore_client import get_pet_by_id, aget_pet_by_id
12
44
 
13
- # Generate from an otterapi config file
14
- otter generate -c otter.yml
45
+ # Synchronous usage
46
+ pet = get_pet_by_id(pet_id=123)
47
+
48
+ # Asynchronous usage
49
+ import asyncio
50
+ pet = asyncio.run(aget_pet_by_id(pet_id=123))
15
51
  ```
16
52
 
17
- ## 📝 Example Config
53
+ ## 📝 Configuration
54
+
55
+ ### Basic Configuration
18
56
 
19
57
  ```yaml
20
58
  documents:
21
59
  - source: https://petstore3.swagger.io/api/v3/openapi.json
22
60
  output: petstore_client
23
61
 
24
- - source: ./local-users-api.json
25
- output: users_client
26
- ```
62
+ - source: ./local-api.json
63
+ output: local_client
64
+ base_url: https://api.example.com
65
+ ```
66
+
67
+ ### Full Configuration Options
68
+
69
+ ```yaml
70
+ documents:
71
+ - source: https://api.example.com/openapi.json # URL or file path (required)
72
+ output: ./client # Output directory (required)
73
+ base_url: https://api.example.com # Override base URL from spec
74
+ models_file: models.py # Models filename (default: models.py)
75
+ endpoints_file: endpoints.py # Endpoints filename (default: endpoints.py)
76
+ generate_async: true # Generate async functions (default: true)
77
+ generate_sync: true # Generate sync functions (default: true)
78
+ client_class_name: MyAPIClient # Client class name (default: from API title)
79
+ module_split: # Module splitting configuration
80
+ enabled: false # Enable splitting (default: false)
81
+ # ... see Module Splitting section below
82
+ ```
83
+
84
+ ---
85
+
86
+ ## 📦 Module Splitting
87
+
88
+ For large APIs with many endpoints, OtterAPI can split the generated code into multiple organized modules instead of a single `endpoints.py` file.
89
+
90
+ ### Why Use Module Splitting?
91
+
92
+ - **Better Organization** - Group related endpoints together
93
+ - **Easier Navigation** - Find endpoints quickly in smaller files
94
+ - **Improved IDE Performance** - Smaller files load faster
95
+ - **Cleaner Imports** - Import only what you need from specific modules
96
+
97
+ ### Enabling Module Splitting
98
+
99
+ ```yaml
100
+ documents:
101
+ - source: https://api.example.com/openapi.json
102
+ output: ./client
103
+ module_split:
104
+ enabled: true
105
+ strategy: tag
106
+ ```
107
+
108
+ ### Splitting Strategies
109
+
110
+ #### `tag` - Split by OpenAPI Tags
111
+
112
+ Uses the first tag from each operation to determine the module:
113
+
114
+ ```yaml
115
+ module_split:
116
+ enabled: true
117
+ strategy: tag
118
+ min_endpoints: 1
119
+ ```
120
+
121
+ **Result:** Endpoints tagged with `["Users"]` go to `users.py`, `["Orders"]` go to `orders.py`, etc.
122
+
123
+ #### `path` - Split by URL Path
124
+
125
+ Uses the first segment(s) of the URL path:
126
+
127
+ ```yaml
128
+ module_split:
129
+ enabled: true
130
+ strategy: path
131
+ path_depth: 1 # Number of path segments to use
132
+ global_strip_prefixes: # Remove these prefixes first
133
+ - /api/v1
134
+ - /api/v2
135
+ ```
136
+
137
+ **Result:** `/api/v1/users/123` → `users.py`, `/api/v1/orders/456` → `orders.py`
138
+
139
+ #### `custom` - Explicit Module Mapping
140
+
141
+ Define exactly which paths go to which modules using glob patterns:
142
+
143
+ ```yaml
144
+ module_split:
145
+ enabled: true
146
+ strategy: custom
147
+ module_map:
148
+ users:
149
+ - /users
150
+ - /users/*
151
+ - /users/**
152
+ orders:
153
+ - /orders/*
154
+ - /orders/**
155
+ health:
156
+ - /health
157
+ - /ready
158
+ - /live
159
+ ```
160
+
161
+ #### `hybrid` - Combined Strategy (Default)
162
+
163
+ Tries custom module_map first, then falls back to tags, then path:
164
+
165
+ ```yaml
166
+ module_split:
167
+ enabled: true
168
+ strategy: hybrid
169
+ module_map:
170
+ health: # Custom mapping takes priority
171
+ - /health
172
+ - /ready
173
+ # Remaining endpoints use tags if available, otherwise path
174
+ ```
175
+
176
+ #### `none` - All to Fallback
177
+
178
+ All endpoints go to a single fallback module:
179
+
180
+ ```yaml
181
+ module_split:
182
+ enabled: true
183
+ strategy: none
184
+ fallback_module: api # All endpoints go here
185
+ ```
186
+
187
+ ### Pattern Syntax
188
+
189
+ The module map supports glob patterns:
190
+
191
+ | Pattern | Matches | Example |
192
+ |---------|---------|---------|
193
+ | `/users` | Exact path | `/users` only |
194
+ | `/users/*` | Single segment | `/users/123`, `/users/abc` |
195
+ | `/users/**` | Any depth | `/users/123`, `/users/123/profile/settings` |
196
+ | `/v?/users` | Single character | `/v1/users`, `/v2/users` |
197
+
198
+ ### Nested Module Maps
199
+
200
+ Create hierarchical module structures:
201
+
202
+ ```yaml
203
+ module_split:
204
+ enabled: true
205
+ strategy: custom
206
+ module_map:
207
+ identity: # Parent module
208
+ users: # Child: identity/users.py
209
+ - /users/*
210
+ - /users/**
211
+ auth: # Child: identity/auth.py
212
+ - /auth/*
213
+ - /login
214
+ - /logout
215
+ roles: # Child: identity/roles.py
216
+ - /roles/*
217
+ billing:
218
+ invoices:
219
+ - /invoices/*
220
+ payments:
221
+ - /payments/*
222
+ ```
223
+
224
+ ### Advanced Module Definition
225
+
226
+ Full control over each module:
227
+
228
+ ```yaml
229
+ module_split:
230
+ enabled: true
231
+ strategy: custom
232
+ module_map:
233
+ v2_api:
234
+ paths: # Explicit paths key
235
+ - /v2/**
236
+ strip_prefix: /v2 # Strip this prefix from paths in this module
237
+ description: "API v2 endpoints (deprecated)" # Module docstring
238
+ modules: # Nested submodules
239
+ users:
240
+ paths:
241
+ - /users/*
242
+ billing:
243
+ paths:
244
+ - /billing/*
245
+ ```
246
+
247
+ ### Module Split Options Reference
248
+
249
+ | Option | Type | Default | Description |
250
+ |--------|------|---------|-------------|
251
+ | `enabled` | bool | `false` | Enable module splitting |
252
+ | `strategy` | string | `hybrid` | Strategy: `none`, `path`, `tag`, `hybrid`, `custom` |
253
+ | `fallback_module` | string | `common` | Module name for unmatched endpoints |
254
+ | `min_endpoints` | int | `2` | Minimum endpoints per module (smaller modules get consolidated) |
255
+ | `flat_structure` | bool | `false` | `true`: flat files, `false`: nested directories |
256
+ | `path_depth` | int | `1` | Path segments to use for `path` strategy (1-5) |
257
+ | `global_strip_prefixes` | list | common prefixes | Prefixes to strip from all paths before matching |
258
+ | `module_map` | object | `{}` | Custom module mappings |
259
+
260
+ ### Output Structure Examples
261
+
262
+ **Flat Structure (default):**
263
+
264
+ ```
265
+ client/
266
+ ├── __init__.py # Re-exports all endpoints
267
+ ├── models.py # Pydantic models
268
+ ├── _client.py # Base client class
269
+ ├── client.py # User-customizable client
270
+ ├── users.py # User endpoints
271
+ ├── orders.py # Order endpoints
272
+ └── health.py # Health check endpoints
273
+ ```
274
+
275
+ **Nested Structure** (`flat_structure: false` with nested module_map):
276
+
277
+ ```
278
+ client/
279
+ ├── __init__.py
280
+ ├── models.py
281
+ ├── _client.py
282
+ ├── client.py
283
+ ├── identity/
284
+ │ ├── __init__.py
285
+ │ ├── users.py
286
+ │ └── auth.py
287
+ └── billing/
288
+ ├── __init__.py
289
+ └── invoices.py
290
+ ```
291
+
292
+ ### Complete Example
293
+
294
+ ```yaml
295
+ documents:
296
+ - source: https://api.mycompany.com/openapi.json
297
+ output: ./mycompany_client
298
+ module_split:
299
+ enabled: true
300
+ strategy: custom
301
+
302
+ # Strip API version prefixes
303
+ global_strip_prefixes:
304
+ - /api/v1
305
+ - /api/v2
306
+ - /api/v3
307
+
308
+ # Consolidate small modules (< 3 endpoints) into fallback
309
+ min_endpoints: 3
310
+ fallback_module: misc
311
+
312
+ # Custom module organization
313
+ module_map:
314
+ # Simple health checks
315
+ health:
316
+ - /health
317
+ - /ready
318
+ - /metrics
319
+
320
+ # User management
321
+ users:
322
+ - /users
323
+ - /users/*
324
+ - /users/**
325
+
326
+ # Authentication
327
+ auth:
328
+ - /auth/*
329
+ - /login
330
+ - /logout
331
+ - /refresh
332
+
333
+ # Nested billing module
334
+ billing:
335
+ paths:
336
+ - /billing/**
337
+ description: "Billing and payment endpoints"
338
+ modules:
339
+ invoices:
340
+ - /invoices/*
341
+ subscriptions:
342
+ - /subscriptions/*
343
+ payments:
344
+ - /payments/*
345
+ ```
346
+
347
+ ---
348
+
349
+ ## 📊 DataFrame Conversion
350
+
351
+ OtterAPI can generate additional methods that return pandas or polars DataFrames directly, making it easy to analyze API responses.
352
+
353
+ ### Enabling DataFrame Methods
354
+
355
+ ```yaml
356
+ documents:
357
+ - source: https://api.example.com/openapi.json
358
+ output: ./client
359
+ dataframe:
360
+ enabled: true
361
+ pandas: true # Generate _df methods (default: true when enabled)
362
+ polars: true # Generate _pl methods (default: false)
363
+ ```
364
+
365
+ ### Generated Methods
366
+
367
+ When enabled, endpoints that return lists get additional DataFrame methods:
368
+
369
+ | Original Method | Pandas Method | Polars Method |
370
+ |-----------------|---------------|---------------|
371
+ | `get_users()` | `get_users_df()` | `get_users_pl()` |
372
+ | `aget_users()` | `aget_users_df()` | `aget_users_pl()` |
373
+
374
+ ### Basic Usage
375
+
376
+ ```python
377
+ from client import find_pets_by_status, find_pets_by_status_df, find_pets_by_status_pl
378
+
379
+ # Get as Pydantic models (existing behavior)
380
+ pets = find_pets_by_status("available")
381
+ for pet in pets:
382
+ print(f"{pet.id}: {pet.name}")
383
+
384
+ # Get as pandas DataFrame
385
+ pdf = find_pets_by_status_df("available")
386
+ print(pdf.head())
387
+ print(pdf.describe())
388
+
389
+ # Get as polars DataFrame
390
+ plf = find_pets_by_status_pl("available")
391
+ print(plf.schema)
392
+ print(plf.head())
393
+ ```
394
+
395
+ ### Handling Nested Responses
396
+
397
+ For APIs that return data nested under a key (e.g., `{"data": {"users": [...]}}`):
398
+
399
+ ```yaml
400
+ dataframe:
401
+ enabled: true
402
+ pandas: true
403
+ polars: true
404
+ default_path: "data.items" # Default path for all endpoints
405
+ endpoints:
406
+ get_users:
407
+ path: "data.users" # Override for specific endpoint
408
+ get_analytics:
409
+ path: "response.events"
410
+ ```
411
+
412
+ You can also override the path at runtime:
413
+
414
+ ```python
415
+ # Use configured path
416
+ df = get_users_df()
417
+
418
+ # Override path at call time
419
+ df = get_users_df(path="response.data.users")
420
+ ```
421
+
422
+ ### Selective Generation
423
+
424
+ Control which endpoints get DataFrame methods:
425
+
426
+ ```yaml
427
+ dataframe:
428
+ enabled: true
429
+ pandas: true
430
+ polars: true
431
+ include_all: false # Don't generate for all endpoints
432
+ endpoints:
433
+ list_users:
434
+ enabled: true # Only generate for this endpoint
435
+ get_analytics:
436
+ enabled: true
437
+ path: "events"
438
+ polars: true
439
+ pandas: false # Only polars for this endpoint
440
+ ```
441
+
442
+ ### DataFrame Configuration Options
443
+
444
+ | Option | Type | Default | Description |
445
+ |--------|------|---------|-------------|
446
+ | `enabled` | bool | `false` | Enable DataFrame method generation |
447
+ | `pandas` | bool | `true` | Generate `_df` methods (pandas) |
448
+ | `polars` | bool | `false` | Generate `_pl` methods (polars) |
449
+ | `default_path` | string | `null` | Default JSON path for extracting data |
450
+ | `include_all` | bool | `true` | Generate for all list-returning endpoints |
451
+ | `endpoints` | object | `{}` | Per-endpoint configuration overrides |
452
+
453
+ ### Per-Endpoint Options
454
+
455
+ | Option | Type | Default | Description |
456
+ |--------|------|---------|-------------|
457
+ | `enabled` | bool | inherits | Override whether to generate methods |
458
+ | `path` | string | inherits | JSON path to extract data |
459
+ | `pandas` | bool | inherits | Override pandas generation |
460
+ | `polars` | bool | inherits | Override polars generation |
461
+
462
+ ---
463
+
464
+ ## 📖 Using Generated Code
465
+
466
+ ### Direct Function Imports
467
+
468
+ ```python
469
+ # Import specific endpoints
470
+ from client import get_user, create_user, list_orders
471
+
472
+ # Async versions are prefixed with 'a'
473
+ from client import aget_user, acreate_user, alist_orders
474
+
475
+ # Sync usage
476
+ user = get_user(user_id=123)
477
+ orders = list_orders(status="pending", limit=10)
478
+
479
+ # Async usage
480
+ import asyncio
481
+
482
+ async def main():
483
+ user = await aget_user(user_id=123)
484
+ orders = await alist_orders(status="pending")
485
+
486
+ asyncio.run(main())
487
+ ```
488
+
489
+ ### Module-Specific Imports (with splitting)
490
+
491
+ ```python
492
+ # Import from specific modules
493
+ from client.users import get_user, create_user
494
+ from client.orders import list_orders, get_order
495
+ from client.auth import login, logout
496
+ ```
497
+
498
+ ### Using the Client Class
499
+
500
+ ```python
501
+ from client import Client
502
+
503
+ # Create client with default settings
504
+ client = Client()
505
+
506
+ # Or customize the client
507
+ client = Client(
508
+ base_url="https://api.example.com",
509
+ timeout=30.0,
510
+ headers={
511
+ "Authorization": "Bearer your-token",
512
+ "X-Custom-Header": "value"
513
+ }
514
+ )
515
+
516
+ # Use client methods (sync)
517
+ user = client.get_user(user_id=123)
518
+ orders = client.list_orders(status="pending")
519
+
520
+ # Use async methods
521
+ import asyncio
522
+
523
+ async def main():
524
+ user = await client.aget_user(user_id=123)
525
+
526
+ asyncio.run(main())
527
+ ```
528
+
529
+ ### Working with Models
530
+
531
+ ```python
532
+ from client.models import User, Order, CreateUserRequest
533
+
534
+ # Models are Pydantic BaseModels
535
+ new_user = CreateUserRequest(
536
+ name="John Doe",
537
+ email="john@example.com"
538
+ )
539
+
540
+ # Create user
541
+ user = create_user(body=new_user)
542
+
543
+ # Access typed response
544
+ print(user.id)
545
+ print(user.name)
546
+ print(user.email)
547
+ ```
548
+
549
+ ---
550
+
551
+ ## 🔧 CLI Reference
552
+
553
+ ```bash
554
+ # Generate from default config (otter.yml, otter.yaml, or pyproject.toml)
555
+ otter generate
556
+
557
+ # Generate from specific config file
558
+ otter generate -c my-config.yml
559
+
560
+ # Initialize a new config file
561
+ otter init
562
+
563
+ # Validate configuration
564
+ otter validate
565
+ ```
566
+
567
+ ---
568
+
569
+ ## 🛠 Development
570
+
571
+ ```bash
572
+ # Clone the repository
573
+ git clone https://github.com/yourusername/otterapi.git
574
+ cd otterapi
575
+
576
+ # Install dependencies with uv
577
+ uv sync
578
+
579
+ # Run tests
580
+ uv run pytest
581
+
582
+ # Run tests with coverage
583
+ uv run pytest --cov=otterapi
584
+
585
+ # Run the generator
586
+ uv run python -m otterapi generate
587
+
588
+ # Format code
589
+ uv run ruff format .
590
+
591
+ # Lint code
592
+ uv run ruff check .
593
+ ```
594
+
595
+ ---
596
+
597
+ ## 📄 License
598
+
599
+ MIT License - see LICENSE for details.
otterapi/__init__.py CHANGED
@@ -0,0 +1,73 @@
1
+ """OtterAPI - Generate type-safe Python clients from OpenAPI specifications.
2
+
3
+ OtterAPI is a code generation library that creates fully-typed Python client
4
+ code from OpenAPI 3.x specifications. It generates Pydantic models for data
5
+ validation and httpx-based endpoint functions for making API requests.
6
+
7
+ Quick Start:
8
+ >>> from otterapi import Codegen, DocumentConfig
9
+ >>>
10
+ >>> config = DocumentConfig(
11
+ ... source="https://api.example.com/openapi.json",
12
+ ... output="./client"
13
+ ... )
14
+ >>> codegen = Codegen(config)
15
+ >>> codegen.generate()
16
+
17
+ CLI Usage:
18
+ $ otterapi generate --source ./api.yaml --output ./client
19
+ $ otterapi init # Create a configuration file interactively
20
+ $ otterapi validate ./api.yaml # Validate an OpenAPI spec
21
+
22
+ For more information, see the documentation at:
23
+ https://github.com/yourusername/otterapi
24
+ """
25
+
26
+ from otterapi.codegen.codegen import Codegen
27
+ from otterapi.codegen.schema import SchemaLoader, SchemaResolver
28
+ from otterapi.codegen.types import TypeGenerator, TypeRegistry
29
+ from otterapi.config import CodegenConfig, DocumentConfig, get_config
30
+ from otterapi.exceptions import (
31
+ CodeGenerationError,
32
+ ConfigurationError,
33
+ EndpointGenerationError,
34
+ OtterAPIError,
35
+ OutputError,
36
+ SchemaError,
37
+ SchemaLoadError,
38
+ SchemaReferenceError,
39
+ SchemaValidationError,
40
+ TypeGenerationError,
41
+ UnsupportedFeatureError,
42
+ )
43
+
44
+ __all__ = [
45
+ # Main classes
46
+ 'Codegen',
47
+ 'SchemaLoader',
48
+ 'SchemaResolver',
49
+ 'TypeRegistry',
50
+ 'TypeGenerator',
51
+ # Configuration
52
+ 'CodegenConfig',
53
+ 'DocumentConfig',
54
+ 'get_config',
55
+ # Exceptions
56
+ 'OtterAPIError',
57
+ 'SchemaError',
58
+ 'SchemaLoadError',
59
+ 'SchemaValidationError',
60
+ 'SchemaReferenceError',
61
+ 'CodeGenerationError',
62
+ 'TypeGenerationError',
63
+ 'EndpointGenerationError',
64
+ 'ConfigurationError',
65
+ 'OutputError',
66
+ 'UnsupportedFeatureError',
67
+ ]
68
+
69
+ # Version is dynamically set by setuptools-scm or hatch-vcs
70
+ try:
71
+ from otterapi._version import version as __version__
72
+ except ImportError:
73
+ __version__ = 'unknown'