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