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
@@ -1,559 +0,0 @@
1
- import ast
2
- import dataclasses
3
- from datetime import datetime
4
- from typing import Any, Literal
5
- from uuid import UUID
6
-
7
- from openapi_pydantic import DataType, Reference, Schema
8
- from openapi_pydantic.v3.parser import OpenAPIv3
9
- from pydantic import BaseModel, Field, RootModel
10
-
11
- from otterapi.codegen.ast_utils import _call, _name, _subscript, _union_expr
12
- from otterapi.codegen.openapi_processor import OpenAPIProcessor
13
- from otterapi.codegen.utils import sanitize_identifier, sanitize_parameter_field_name
14
-
15
- _PRIMITIVE_TYPE_MAP = {
16
- ('string', None): str,
17
- ('string', 'date-time'): datetime,
18
- ('string', 'date'): datetime,
19
- ('string', 'uuid'): UUID,
20
- ('integer', None): int,
21
- ('integer', 'int32'): int,
22
- ('integer', 'int64'): int,
23
- ('number', None): float,
24
- ('number', 'float'): float,
25
- ('number', 'double'): float,
26
- ('boolean', None): bool,
27
- ('null', None): None,
28
- (None, None): None,
29
- }
30
-
31
-
32
- @dataclasses.dataclass
33
- class Type:
34
- reference: str | None # reference is None if type is 'primitive'
35
- name: str | None
36
- annotation_ast: ast.expr | ast.stmt
37
- type: Literal['primitive', 'root', 'model']
38
- implementation_ast: ast.expr | ast.stmt | None = dataclasses.field(
39
- default_factory=None
40
- )
41
- dependencies: set[str] = dataclasses.field(default_factory=set)
42
- implementation_imports: dict[str, set[str]] = dataclasses.field(
43
- default_factory=dict
44
- )
45
- annotation_imports: dict[str, set[str]] = dataclasses.field(default_factory=dict)
46
-
47
- def __eq__(self, other):
48
- """Deep comparison of Type objects, including AST nodes."""
49
- if not isinstance(other, Type):
50
- return False
51
-
52
- # Compare simple fields
53
- if (
54
- self.reference != other.reference
55
- or self.name != other.name
56
- or self.type != other.type
57
- ):
58
- return False
59
-
60
- # Compare AST nodes by dumping them to strings
61
- if ast.dump(self.annotation_ast) != ast.dump(other.annotation_ast):
62
- return False
63
-
64
- # Compare implementation AST (can be None)
65
- if self.implementation_ast is None and other.implementation_ast is None:
66
- pass # Both None, equal
67
- elif self.implementation_ast is None or other.implementation_ast is None:
68
- return False # One is None, other isn't
69
- else:
70
- if ast.dump(self.implementation_ast) != ast.dump(other.implementation_ast):
71
- return False
72
-
73
- # Compare imports and dependencies
74
- if (
75
- self.dependencies != other.dependencies
76
- or self.implementation_imports != other.implementation_imports
77
- or self.annotation_imports != other.annotation_imports
78
- ):
79
- return False
80
-
81
- return True
82
-
83
- def __hash__(self):
84
- """Make Type hashable based on its name (for use in sets/dicts)."""
85
- # We only hash based on name since we use name as the key in the types dict
86
- return hash(self.name)
87
-
88
- def add_dependency(self, type_: 'Type') -> None:
89
- self.dependencies.add(type_.name)
90
- for dep in type_.dependencies:
91
- self.dependencies.add(dep)
92
-
93
- def copy_imports_from_sub_types(self, types: list['Type']):
94
- for t in types:
95
- for module, names in t.annotation_imports.items():
96
- if module not in self.annotation_imports:
97
- self.annotation_imports[module] = set()
98
- self.annotation_imports[module].update(names)
99
-
100
- for module, names in t.implementation_imports.items():
101
- if module not in self.implementation_imports:
102
- self.implementation_imports[module] = set()
103
- self.implementation_imports[module].update(names)
104
-
105
- def add_implementation_import(self, module: str, name: str | list[str]) -> None:
106
- if isinstance(name, str):
107
- name = [name]
108
-
109
- if module not in self.implementation_imports:
110
- self.implementation_imports[module] = set()
111
-
112
- for n in name:
113
- self.implementation_imports[module].add(n)
114
-
115
- def add_annotation_import(self, module: str, name: str | list[str]) -> None:
116
- if isinstance(name, str):
117
- name = [name]
118
-
119
- if module not in self.annotation_imports:
120
- self.annotation_imports[module] = set()
121
-
122
- for n in name:
123
- self.annotation_imports[module].add(n)
124
-
125
-
126
- @dataclasses.dataclass
127
- class Parameter:
128
- name: str
129
- name_sanitized: str
130
- location: str # query, path, header, cookie, body
131
- required: bool
132
- type: Type | None = None
133
- description: str | None = None
134
-
135
-
136
- @dataclasses.dataclass
137
- class Endpoint:
138
- sync_ast: ast.FunctionDef
139
- async_ast: ast.AsyncFunctionDef
140
-
141
- sync_fn_name: str
142
- async_fn_name: str
143
-
144
- name: str
145
- imports: dict[str, set[str]] = dataclasses.field(default_factory=dict)
146
-
147
- def add_imports(self, imports: list[dict[str, set[str]]]):
148
- for imports_ in imports:
149
- for module, names in imports_.items():
150
- if module not in self.imports:
151
- self.imports[module] = set()
152
- self.imports[module].update(names)
153
-
154
-
155
- class TypeGen(OpenAPIProcessor):
156
- def __init__(self, openapi: OpenAPIv3):
157
- super().__init__(openapi)
158
- self.types: dict[str, Type] = {}
159
-
160
- def add_type(self, type_: Type, base_name: str | None = None) -> Type:
161
- """Add a type to the registry. If a type with the same name but different definition
162
- already exists, generate a unique name using the base_name prefix.
163
- Returns the type (potentially with a modified name).
164
- """
165
- # Skip types without names (primitive types, inline types, etc.)
166
- if not type_.name:
167
- return type_
168
-
169
- # If type with same name and same definition exists, just return the existing one
170
- if type_.name in self.types:
171
- existing = self.types[type_.name]
172
- if existing == type_:
173
- # Same type already registered, return the existing one
174
- # This avoids creating Detail20, Detail21 when they're identical
175
- return existing
176
- else:
177
- # Different definition with same name - generate a unique name
178
- if base_name:
179
- # Use base_name as prefix for endpoint-specific types
180
- unique_name = f'{base_name}{type_.name}'
181
- if unique_name not in self.types:
182
- type_.name = unique_name
183
- type_.annotation_ast = _name(unique_name)
184
- # Update the implementation_ast name if it's a ClassDef
185
- if isinstance(type_.implementation_ast, ast.ClassDef):
186
- type_.implementation_ast.name = unique_name
187
- else:
188
- # Check if even the base_name version is the same
189
- if (
190
- unique_name in self.types
191
- and self.types[unique_name] == type_
192
- ):
193
- return self.types[unique_name]
194
- # If even that exists with different def, add a counter
195
- counter = 1
196
- while f'{unique_name}{counter}' in self.types:
197
- candidate = f'{unique_name}{counter}'
198
- if self.types[candidate] == type_:
199
- return self.types[candidate]
200
- counter += 1
201
- unique_name = f'{unique_name}{counter}'
202
- type_.name = unique_name
203
- type_.annotation_ast = _name(unique_name)
204
- if isinstance(type_.implementation_ast, ast.ClassDef):
205
- type_.implementation_ast.name = unique_name
206
- else:
207
- # No base_name provided, just add a counter
208
- counter = 1
209
- original_name = type_.name
210
- while f'{original_name}{counter}' in self.types:
211
- candidate = f'{original_name}{counter}'
212
- if self.types[candidate] == type_:
213
- # Found identical type with numbered name
214
- return self.types[candidate]
215
- counter += 1
216
- unique_name = f'{original_name}{counter}'
217
- type_.name = unique_name
218
- type_.annotation_ast = _name(unique_name)
219
- if isinstance(type_.implementation_ast, ast.ClassDef):
220
- type_.implementation_ast.name = unique_name
221
-
222
- self.types[type_.name] = type_
223
- return type_
224
-
225
- def _resolve_reference(self, reference: Reference | Schema) -> tuple[Schema, str]:
226
- if hasattr(reference, 'ref'):
227
- if not reference.ref.startswith('#/components/schemas/'):
228
- raise ValueError(f'Unsupported reference format: {reference.ref}')
229
-
230
- schema_name = reference.ref.split('/')[-1]
231
- schemas = self.openapi.components.schemas
232
-
233
- if schema_name not in schemas:
234
- raise ValueError(
235
- f"Referenced schema '{schema_name}' not found in components.schemas"
236
- )
237
-
238
- return schemas[schema_name], sanitize_identifier(schema_name)
239
- return reference, sanitize_identifier(
240
- reference.title
241
- ) if reference.title else None
242
-
243
- def _get_primitive_type_ast(self, schema: Schema) -> Type:
244
- key = (schema.type, schema.schema_format or None)
245
- mapped = _PRIMITIVE_TYPE_MAP.get(key, Any)
246
-
247
- type_ = Type(
248
- None,
249
- sanitize_identifier(schema.title) if schema.title else None,
250
- annotation_ast=_name(mapped.__name__ if mapped is not None else 'None'),
251
- implementation_ast=None,
252
- type='primitive',
253
- )
254
-
255
- if mapped is not None and mapped.__module__ != 'builtins':
256
- type_.add_annotation_import(mapped.__module__, mapped.__name__)
257
- return type_
258
-
259
- def _create_pydantic_field(
260
- self, field_name: str, field_schema: Schema, field_type: Type
261
- ) -> str:
262
- if hasattr(field_schema, 'ref'):
263
- field_schema, _ = self._resolve_reference(field_schema)
264
-
265
- field_keywords = list()
266
-
267
- sanitized_field_name = sanitize_parameter_field_name(field_name)
268
-
269
- value = None
270
- if field_schema.default is not None and isinstance(
271
- field_schema.default, (str, int, float, bool)
272
- ):
273
- field_keywords.append(
274
- ast.keyword(arg='default', value=ast.Constant(field_schema.default))
275
- )
276
- elif field_schema.default is None and not field_schema.required:
277
- field_keywords.append(ast.keyword(arg='default', value=ast.Constant(None)))
278
-
279
- if sanitized_field_name != field_name:
280
- field_keywords.append(
281
- ast.keyword(
282
- arg='alias',
283
- value=ast.Constant(field_name), # original name before adding _
284
- )
285
- )
286
- field_name = sanitized_field_name
287
-
288
- if field_keywords:
289
- value = _call(
290
- func=_name(Field.__name__),
291
- keywords=field_keywords,
292
- )
293
-
294
- field_type.add_implementation_import(
295
- module=Field.__module__, name=Field.__name__
296
- )
297
-
298
- return ast.AnnAssign(
299
- target=_name(field_name),
300
- annotation=field_type.annotation_ast,
301
- value=value,
302
- simple=1,
303
- )
304
-
305
- def _create_pydantic_root_model(
306
- self,
307
- schema: Schema,
308
- item_type: Type | None = None,
309
- name: str | None = None,
310
- base_name: str | None = None,
311
- ) -> Type:
312
- name = (
313
- name
314
- or base_name
315
- or (sanitize_identifier(schema.title) if schema.title else None)
316
- )
317
- if not name:
318
- raise ValueError('Root model must have a name')
319
-
320
- model = ast.ClassDef(
321
- name=name,
322
- bases=[_subscript(RootModel.__name__, item_type.annotation_ast)],
323
- keywords=[],
324
- body=[ast.Pass()],
325
- decorator_list=[],
326
- type_params=[],
327
- )
328
-
329
- type_ = Type(
330
- reference=None,
331
- name=name,
332
- annotation_ast=_name(name),
333
- implementation_ast=model,
334
- type='root',
335
- )
336
- type_.add_implementation_import(
337
- module=RootModel.__module__, name=RootModel.__name__
338
- )
339
- type_.copy_imports_from_sub_types([item_type] if item_type else [])
340
- if item_type is not None:
341
- type_.add_dependency(item_type)
342
- type_ = self.add_type(type_, base_name=base_name)
343
-
344
- return type_
345
-
346
- def _create_pydantic_model(
347
- self, schema: Schema, name: str | None = None, base_name: str | None = None
348
- ) -> Type:
349
- base_bases = []
350
- if schema.allOf:
351
- for base_schema in schema.allOf:
352
- base = self._create_object_type(schema=base_schema, base_name=base_name)
353
- base_bases.append(base)
354
-
355
- if schema.anyOf or schema.oneOf:
356
- types_ = [
357
- self._create_object_type(schema=t, base_name=base_name)
358
- for t in (schema.anyOf or schema.oneOf)
359
- ]
360
-
361
- union_type = Type(
362
- reference=None,
363
- name=None, # Union type doesn't need a name, it's used inline
364
- annotation_ast=_union_expr(types=[t.annotation_ast for t in types_]),
365
- implementation_ast=None,
366
- type='primitive',
367
- )
368
- union_type.copy_imports_from_sub_types(types_)
369
- union_type.add_annotation_import('typing', 'Union')
370
- return union_type
371
-
372
- name = name or (
373
- sanitize_identifier(schema.title) if schema.title else 'UnnamedModel'
374
- )
375
-
376
- bases = [b.name for b in base_bases] or [BaseModel.__name__]
377
- bases = [_name(base) for base in bases]
378
-
379
- body = []
380
- field_types = []
381
- for property_name, property_schema in schema.properties.items():
382
- type_ = self.schema_to_type(property_schema, base_name=base_name)
383
- field = self._create_pydantic_field(property_name, property_schema, type_)
384
-
385
- body.append(field)
386
- field_types.append(type_)
387
-
388
- model = ast.ClassDef(
389
- name=name,
390
- bases=bases,
391
- keywords=[],
392
- body=body or [ast.Pass()],
393
- decorator_list=[],
394
- type_params=[],
395
- )
396
-
397
- type_ = Type(
398
- reference=None,
399
- name=name,
400
- annotation_ast=_name(name),
401
- implementation_ast=model,
402
- dependencies=set(),
403
- type='model',
404
- )
405
-
406
- # Add base class dependencies
407
- if base_bases:
408
- for base in base_bases:
409
- type_.add_dependency(base)
410
-
411
- # Add field type dependencies
412
- for field_type in field_types:
413
- if field_type.name:
414
- type_.dependencies.add(field_type.name)
415
- type_.dependencies.update(field_type.dependencies)
416
-
417
- type_.add_implementation_import(
418
- module=BaseModel.__module__, name=BaseModel.__name__
419
- )
420
- type_.add_implementation_import(module=Field.__module__, name=Field.__name__)
421
- type_.copy_imports_from_sub_types(field_types)
422
-
423
- type_ = self.add_type(type_, base_name=base_name)
424
- return type_
425
-
426
- def _create_array_type(
427
- self, schema: Schema, name: str | None = None, base_name: str | None = None
428
- ) -> Type:
429
- if schema.type != DataType.ARRAY:
430
- raise ValueError('Schema is not an array')
431
-
432
- if not schema.items:
433
- type_ = Type(
434
- None,
435
- None,
436
- _subscript(
437
- list.__name__,
438
- ast.Name(id=Any.__name__, ctx=ast.Load()),
439
- ),
440
- 'primitive',
441
- )
442
-
443
- type_.add_annotation_import(module=list.__module__, name=list.__name__)
444
- type_.add_annotation_import(module=Any.__module__, name=Any.__name__)
445
-
446
- return type_
447
-
448
- item_type = self.schema_to_type(schema.items, base_name=base_name)
449
-
450
- type_ = Type(
451
- None,
452
- None,
453
- annotation_ast=_subscript(
454
- list.__name__,
455
- item_type.annotation_ast,
456
- ),
457
- implementation_ast=None,
458
- type='primitive',
459
- )
460
-
461
- type_.add_annotation_import(list.__module__, list.__name__)
462
- type_.copy_imports_from_sub_types([item_type])
463
-
464
- if item_type:
465
- type_.add_dependency(item_type)
466
-
467
- return type_
468
-
469
- def _create_object_type(
470
- self,
471
- schema: Schema | Reference,
472
- name: str | None = None,
473
- base_name: str | None = None,
474
- ) -> Type:
475
- schema, schema_name = self._resolve_reference(schema)
476
- if (
477
- not schema.properties
478
- and not schema.allOf
479
- and not schema.anyOf
480
- and not schema.oneOf
481
- ):
482
- type_ = Type(
483
- None,
484
- None,
485
- annotation_ast=_subscript(
486
- dict.__name__,
487
- ast.Tuple(
488
- elts=[
489
- ast.Name(id=str.__name__, ctx=ast.Load()),
490
- ast.Name(id=Any.__name__, ctx=ast.Load()),
491
- ]
492
- ),
493
- ),
494
- implementation_ast=None,
495
- type='primitive',
496
- )
497
-
498
- type_.add_annotation_import(dict.__module__, dict.__name__)
499
- type_.add_annotation_import(Any.__module__, Any.__name__)
500
-
501
- return type_
502
-
503
- return self._create_pydantic_model(
504
- schema, schema_name or name, base_name=base_name
505
- )
506
-
507
- def schema_to_type(
508
- self, schema: Schema | Reference, base_name: str | None = None
509
- ) -> Type:
510
- if isinstance(schema, Reference):
511
- ref_name = schema.ref.split('/')[-1]
512
- sanitized_ref_name = sanitize_identifier(ref_name)
513
- if sanitized_ref_name in self.types:
514
- return self.types[sanitized_ref_name]
515
-
516
- schema, schema_name = self._resolve_reference(schema)
517
-
518
- if schema.type == DataType.ARRAY:
519
- type_ = self._create_array_type(
520
- schema=schema, name=schema_name, base_name=base_name
521
- )
522
- elif schema.type == DataType.OBJECT or schema.type is None:
523
- type_ = self._create_object_type(
524
- schema, name=schema_name, base_name=base_name
525
- )
526
- else:
527
- type_ = self._get_primitive_type_ast(schema)
528
-
529
- return type_
530
-
531
- def get_sorted_types(self) -> list[Type]:
532
- """Returns the types sorted in dependency order using topological sort.
533
- Types with no dependencies come first.
534
- """
535
- sorted_types: list[Type] = []
536
- temp_mark: set[str] = set()
537
- perm_mark: set[str] = set()
538
-
539
- def visit(type_: Type):
540
- if type_.name in perm_mark:
541
- return
542
- if type_.name in temp_mark:
543
- raise ValueError(f'Cyclic dependency detected for type: {type_.name}')
544
-
545
- temp_mark.add(type_.name)
546
-
547
- for dep_name in type_.dependencies:
548
- if dep_name in self.types:
549
- visit(self.types[dep_name])
550
-
551
- perm_mark.add(type_.name)
552
- temp_mark.remove(type_.name)
553
- sorted_types.append(type_)
554
-
555
- for type_ in self.types.values():
556
- if type_.name not in perm_mark:
557
- visit(type_)
558
-
559
- return list(reversed(sorted_types))
@@ -1,54 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: otterapi
3
- Version: 0.0.5
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: openapi-pydantic>=0.5.1
23
- Requires-Dist: pydantic-settings>=2.11.0
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.
34
-
35
- ## 🚀 Quick Start
36
-
37
- ```bash
38
- # Generate from a pyproject.toml or any of the default config names (otter.yml, otter.yaml)
39
- otter generate
40
-
41
- # Generate from an otterapi config file
42
- otter generate -c otter.yml
43
- ```
44
-
45
- ## 📝 Example Config
46
-
47
- ```yaml
48
- documents:
49
- - source: https://petstore3.swagger.io/api/v3/openapi.json
50
- output: petstore_client
51
-
52
- - source: ./local-users-api.json
53
- output: users_client
54
- ```
@@ -1,16 +0,0 @@
1
- README.md,sha256=ahWBisuW6qpU7SUSTe2gDZWTOVSFYqS2B4YYMjd0idM,625
2
- otterapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- otterapi/__main__.py,sha256=jVpOa4WaSqM3912DD0GQ3gj7Jui_jgqo5zK7DnKUE58,67
4
- otterapi/cli.py,sha256=dw-Pvcv5AqvJb8dWIg5-Wk4LBu6WRsxkkTYF3nxFUrA,2436
5
- otterapi/config.py,sha256=eEVjHSJNDw1xsKicAySEOUdVHGxH9DEz5ZEoFEI2iEI,2098
6
- otterapi/codegen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- otterapi/codegen/ast_utils.py,sha256=XYnu4WF-Ow1xjqVaeu_pKMKQ_FPOVBGPgUXrfPs-31o,2846
8
- otterapi/codegen/endpoints.py,sha256=v4SseRv7bnMrzTJMbRf70wOCoI4ZOhItLW7F0iNg16M,15901
9
- otterapi/codegen/generator.py,sha256=cRCWomte2zzShQfDuBeaAzsuFGPloYTWN4YU53-ct5g,13250
10
- otterapi/codegen/openapi_processor.py,sha256=HtF4SGxmKor_vSRpN2zsjz3sAcj4oCHGJbZUBbOR3PM,1080
11
- otterapi/codegen/type_generator.py,sha256=6U9OUBvBJ9lY2HjvAvJIRmsIpSu1V2D8DbZLpmIaigk,19997
12
- otterapi/codegen/utils.py,sha256=s85oyOu9ZgDqDngoHWSpt5jQm_os2unZjx8TGi5fTpQ,2289
13
- otterapi-0.0.5.dist-info/METADATA,sha256=P77pd8wmmmSX8kpISjSYRjeo5_dbLV4Z8JwugJqakFc,1686
14
- otterapi-0.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- otterapi-0.0.5.dist-info/entry_points.txt,sha256=vRm02sAdS5QjrXjlKTIr7xKXw821MEQv2bRDp-gJuCE,48
16
- otterapi-0.0.5.dist-info/RECORD,,