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.
- README.md +581 -8
- otterapi/__init__.py +73 -0
- otterapi/cli.py +327 -29
- otterapi/codegen/__init__.py +115 -0
- otterapi/codegen/ast_utils.py +134 -5
- otterapi/codegen/client.py +1271 -0
- otterapi/codegen/codegen.py +1736 -0
- otterapi/codegen/dataframes.py +392 -0
- otterapi/codegen/emitter.py +473 -0
- otterapi/codegen/endpoints.py +2597 -343
- otterapi/codegen/pagination.py +1026 -0
- otterapi/codegen/schema.py +593 -0
- otterapi/codegen/splitting.py +1397 -0
- otterapi/codegen/types.py +1345 -0
- otterapi/codegen/utils.py +180 -1
- otterapi/config.py +1017 -24
- otterapi/exceptions.py +231 -0
- otterapi/openapi/__init__.py +46 -0
- otterapi/openapi/v2/__init__.py +86 -0
- otterapi/openapi/v2/spec.json +1607 -0
- otterapi/openapi/v2/v2.py +1776 -0
- otterapi/openapi/v3/__init__.py +131 -0
- otterapi/openapi/v3/spec.json +1651 -0
- otterapi/openapi/v3/v3.py +1557 -0
- otterapi/openapi/v3_1/__init__.py +133 -0
- otterapi/openapi/v3_1/spec.json +1411 -0
- otterapi/openapi/v3_1/v3_1.py +798 -0
- otterapi/openapi/v3_2/__init__.py +133 -0
- otterapi/openapi/v3_2/spec.json +1666 -0
- otterapi/openapi/v3_2/v3_2.py +777 -0
- otterapi/tests/__init__.py +3 -0
- otterapi/tests/fixtures/__init__.py +455 -0
- otterapi/tests/test_ast_utils.py +680 -0
- otterapi/tests/test_codegen.py +610 -0
- otterapi/tests/test_dataframe.py +1038 -0
- otterapi/tests/test_exceptions.py +493 -0
- otterapi/tests/test_openapi_support.py +616 -0
- otterapi/tests/test_openapi_upgrade.py +215 -0
- otterapi/tests/test_pagination.py +1101 -0
- otterapi/tests/test_splitting_config.py +319 -0
- otterapi/tests/test_splitting_integration.py +427 -0
- otterapi/tests/test_splitting_resolver.py +512 -0
- otterapi/tests/test_splitting_tree.py +525 -0
- otterapi-0.0.6.dist-info/METADATA +627 -0
- otterapi-0.0.6.dist-info/RECORD +48 -0
- {otterapi-0.0.5.dist-info → otterapi-0.0.6.dist-info}/WHEEL +1 -1
- otterapi/codegen/generator.py +0 -358
- otterapi/codegen/openapi_processor.py +0 -27
- otterapi/codegen/type_generator.py +0 -559
- otterapi-0.0.5.dist-info/METADATA +0 -54
- otterapi-0.0.5.dist-info/RECORD +0 -16
- {otterapi-0.0.5.dist-info → otterapi-0.0.6.dist-info}/entry_points.txt +0 -0
otterapi/exceptions.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Custom exceptions for OtterAPI.
|
|
2
|
+
|
|
3
|
+
This module defines a hierarchy of exceptions used throughout the OtterAPI library
|
|
4
|
+
to provide clear, actionable error messages for different failure scenarios.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OtterAPIError(Exception):
|
|
9
|
+
"""Base exception for all OtterAPI errors.
|
|
10
|
+
|
|
11
|
+
All exceptions raised by OtterAPI inherit from this class, making it easy
|
|
12
|
+
to catch all OtterAPI-related errors with a single except clause.
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
try:
|
|
16
|
+
codegen.generate()
|
|
17
|
+
except OtterAPIError as e:
|
|
18
|
+
print(f"OtterAPI error: {e}")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, message: str, *args, **kwargs):
|
|
22
|
+
self.message = message
|
|
23
|
+
super().__init__(message, *args, **kwargs)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SchemaError(OtterAPIError):
|
|
27
|
+
"""Base exception for schema-related errors."""
|
|
28
|
+
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SchemaLoadError(SchemaError):
|
|
33
|
+
"""Failed to load an OpenAPI schema from a source.
|
|
34
|
+
|
|
35
|
+
This exception is raised when the schema cannot be loaded from
|
|
36
|
+
the specified URL or file path.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
source: The source path or URL that failed to load.
|
|
40
|
+
cause: The underlying exception that caused the failure.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, source: str, cause: Exception | None = None):
|
|
44
|
+
self.source = source
|
|
45
|
+
self.cause = cause
|
|
46
|
+
message = f"Failed to load schema from '{source}'"
|
|
47
|
+
if cause:
|
|
48
|
+
message += f': {cause}'
|
|
49
|
+
super().__init__(message)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SchemaValidationError(SchemaError):
|
|
53
|
+
"""Schema failed OpenAPI specification validation.
|
|
54
|
+
|
|
55
|
+
This exception is raised when the loaded schema does not conform
|
|
56
|
+
to the OpenAPI specification.
|
|
57
|
+
|
|
58
|
+
Attributes:
|
|
59
|
+
source: The source path or URL of the invalid schema.
|
|
60
|
+
errors: List of validation error messages.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, source: str, errors: list[str] | None = None):
|
|
64
|
+
self.source = source
|
|
65
|
+
self.errors = errors or []
|
|
66
|
+
message = f"Schema validation failed for '{source}'"
|
|
67
|
+
if errors:
|
|
68
|
+
message += f': {"; ".join(errors)}'
|
|
69
|
+
super().__init__(message)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class SchemaReferenceError(SchemaError):
|
|
73
|
+
"""Failed to resolve a $ref reference in the schema.
|
|
74
|
+
|
|
75
|
+
This exception is raised when a JSON Reference ($ref) cannot be resolved,
|
|
76
|
+
either because the referenced component doesn't exist or the reference
|
|
77
|
+
format is not supported.
|
|
78
|
+
|
|
79
|
+
Attributes:
|
|
80
|
+
reference: The $ref string that could not be resolved.
|
|
81
|
+
reason: Explanation of why the reference couldn't be resolved.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(self, reference: str, reason: str | None = None):
|
|
85
|
+
self.reference = reference
|
|
86
|
+
self.reason = reason
|
|
87
|
+
message = f"Failed to resolve reference '{reference}'"
|
|
88
|
+
if reason:
|
|
89
|
+
message += f': {reason}'
|
|
90
|
+
super().__init__(message)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class CodeGenerationError(OtterAPIError):
|
|
94
|
+
"""Error during code generation.
|
|
95
|
+
|
|
96
|
+
This exception is raised when the code generation process fails
|
|
97
|
+
after successfully loading and validating the schema.
|
|
98
|
+
|
|
99
|
+
Attributes:
|
|
100
|
+
context: Additional context about what was being generated.
|
|
101
|
+
cause: The underlying exception that caused the failure.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self, message: str, context: str | None = None, cause: Exception | None = None
|
|
106
|
+
):
|
|
107
|
+
self.context = context
|
|
108
|
+
self.cause = cause
|
|
109
|
+
full_message = message
|
|
110
|
+
if context:
|
|
111
|
+
full_message = f'{message} (while generating {context})'
|
|
112
|
+
if cause:
|
|
113
|
+
full_message += f': {cause}'
|
|
114
|
+
super().__init__(full_message)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class TypeGenerationError(CodeGenerationError):
|
|
118
|
+
"""Error generating a type from a schema.
|
|
119
|
+
|
|
120
|
+
This exception is raised when a specific type cannot be generated
|
|
121
|
+
from an OpenAPI schema definition.
|
|
122
|
+
|
|
123
|
+
Attributes:
|
|
124
|
+
type_name: The name of the type being generated.
|
|
125
|
+
schema_path: The path to the schema in the OpenAPI document.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def __init__(
|
|
129
|
+
self,
|
|
130
|
+
type_name: str,
|
|
131
|
+
schema_path: str | None = None,
|
|
132
|
+
cause: Exception | None = None,
|
|
133
|
+
):
|
|
134
|
+
self.type_name = type_name
|
|
135
|
+
self.schema_path = schema_path
|
|
136
|
+
message = f"Failed to generate type '{type_name}'"
|
|
137
|
+
if schema_path:
|
|
138
|
+
message += f" at '{schema_path}'"
|
|
139
|
+
super().__init__(message, context=type_name, cause=cause)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class EndpointGenerationError(CodeGenerationError):
|
|
143
|
+
"""Error generating an endpoint function.
|
|
144
|
+
|
|
145
|
+
This exception is raised when an endpoint function cannot be generated
|
|
146
|
+
from an OpenAPI operation definition.
|
|
147
|
+
|
|
148
|
+
Attributes:
|
|
149
|
+
operation_id: The operationId of the endpoint.
|
|
150
|
+
method: The HTTP method of the endpoint.
|
|
151
|
+
path: The URL path of the endpoint.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
def __init__(
|
|
155
|
+
self,
|
|
156
|
+
operation_id: str,
|
|
157
|
+
method: str | None = None,
|
|
158
|
+
path: str | None = None,
|
|
159
|
+
cause: Exception | None = None,
|
|
160
|
+
):
|
|
161
|
+
self.operation_id = operation_id
|
|
162
|
+
self.method = method
|
|
163
|
+
self.path = path
|
|
164
|
+
message = f"Failed to generate endpoint '{operation_id}'"
|
|
165
|
+
if method and path:
|
|
166
|
+
message += f' ({method.upper()} {path})'
|
|
167
|
+
super().__init__(message, context=operation_id, cause=cause)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class ConfigurationError(OtterAPIError):
|
|
171
|
+
"""Error in configuration.
|
|
172
|
+
|
|
173
|
+
This exception is raised when the configuration is invalid or
|
|
174
|
+
cannot be loaded.
|
|
175
|
+
|
|
176
|
+
Attributes:
|
|
177
|
+
config_path: The path to the configuration file, if applicable.
|
|
178
|
+
field: The specific configuration field that is invalid.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def __init__(
|
|
182
|
+
self, message: str, config_path: str | None = None, field: str | None = None
|
|
183
|
+
):
|
|
184
|
+
self.config_path = config_path
|
|
185
|
+
self.field = field
|
|
186
|
+
full_message = message
|
|
187
|
+
if config_path:
|
|
188
|
+
full_message = f"{message} in '{config_path}'"
|
|
189
|
+
if field:
|
|
190
|
+
full_message += f' (field: {field})'
|
|
191
|
+
super().__init__(full_message)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class OutputError(OtterAPIError):
|
|
195
|
+
"""Error writing generated output.
|
|
196
|
+
|
|
197
|
+
This exception is raised when the generated code cannot be written
|
|
198
|
+
to the output location.
|
|
199
|
+
|
|
200
|
+
Attributes:
|
|
201
|
+
output_path: The path where output was being written.
|
|
202
|
+
cause: The underlying exception that caused the failure.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def __init__(self, output_path: str, cause: Exception | None = None):
|
|
206
|
+
self.output_path = output_path
|
|
207
|
+
self.cause = cause
|
|
208
|
+
message = f"Failed to write output to '{output_path}'"
|
|
209
|
+
if cause:
|
|
210
|
+
message += f': {cause}'
|
|
211
|
+
super().__init__(message)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class UnsupportedFeatureError(OtterAPIError):
|
|
215
|
+
"""Attempted to use an unsupported feature.
|
|
216
|
+
|
|
217
|
+
This exception is raised when the schema uses a feature that
|
|
218
|
+
is not yet supported by OtterAPI.
|
|
219
|
+
|
|
220
|
+
Attributes:
|
|
221
|
+
feature: Description of the unsupported feature.
|
|
222
|
+
suggestion: Optional suggestion for a workaround.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def __init__(self, feature: str, suggestion: str | None = None):
|
|
226
|
+
self.feature = feature
|
|
227
|
+
self.suggestion = suggestion
|
|
228
|
+
message = f'Unsupported feature: {feature}'
|
|
229
|
+
if suggestion:
|
|
230
|
+
message += f'. {suggestion}'
|
|
231
|
+
super().__init__(message)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from typing import Annotated, Any, Literal
|
|
2
|
+
|
|
3
|
+
from pydantic import Discriminator, RootModel, Tag
|
|
4
|
+
|
|
5
|
+
from otterapi.openapi.v2 import Swagger
|
|
6
|
+
from otterapi.openapi.v3 import OpenAPI as OpenAPIv3_0
|
|
7
|
+
from otterapi.openapi.v3_1 import OpenAPI as OpenAPIv3_1
|
|
8
|
+
from otterapi.openapi.v3_2 import OpenAPI as OpenAPIv3_2
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
'UniversalOpenAPI',
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _get_openapi_version(data: Any) -> Literal['2.0', '3.0', '3.1', '3.2']:
|
|
16
|
+
"""Discriminator function to determine the OpenAPI version from raw data."""
|
|
17
|
+
if isinstance(data, dict):
|
|
18
|
+
# Check for Swagger 2.0 (uses 'swagger' field)
|
|
19
|
+
if 'swagger' in data:
|
|
20
|
+
return '2.0'
|
|
21
|
+
# Check for OpenAPI 3.x (uses 'openapi' field)
|
|
22
|
+
openapi_version = data.get('openapi', '')
|
|
23
|
+
if openapi_version.startswith('3.0'):
|
|
24
|
+
return '3.0'
|
|
25
|
+
elif openapi_version.startswith('3.1'):
|
|
26
|
+
return '3.1'
|
|
27
|
+
elif openapi_version.startswith('3.2'):
|
|
28
|
+
return '3.2'
|
|
29
|
+
# Default fallback - will let Pydantic try to match
|
|
30
|
+
return '3.0'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class UniversalOpenAPI(
|
|
34
|
+
RootModel[
|
|
35
|
+
Annotated[
|
|
36
|
+
Annotated[Swagger, Tag('2.0')]
|
|
37
|
+
| Annotated[OpenAPIv3_0, Tag('3.0')]
|
|
38
|
+
| Annotated[OpenAPIv3_1, Tag('3.1')]
|
|
39
|
+
| Annotated[OpenAPIv3_2, Tag('3.2')],
|
|
40
|
+
Discriminator(_get_openapi_version),
|
|
41
|
+
]
|
|
42
|
+
]
|
|
43
|
+
):
|
|
44
|
+
"""Universal OpenAPI model that can parse Swagger 2.0, OpenAPI 3.0, 3.1, or 3.2 documents."""
|
|
45
|
+
|
|
46
|
+
pass
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""OpenAPI/Swagger 2.0 specification models."""
|
|
2
|
+
|
|
3
|
+
from otterapi.openapi.v2.v2 import (
|
|
4
|
+
XML,
|
|
5
|
+
ApiKeySecurity,
|
|
6
|
+
BasicAuthenticationSecurity,
|
|
7
|
+
BodyParameter,
|
|
8
|
+
CollectionFormat,
|
|
9
|
+
CollectionFormatWithMulti,
|
|
10
|
+
Contact,
|
|
11
|
+
ExternalDocs,
|
|
12
|
+
FileSchema,
|
|
13
|
+
Header,
|
|
14
|
+
Info,
|
|
15
|
+
JsonReference,
|
|
16
|
+
License,
|
|
17
|
+
NonBodyParameter,
|
|
18
|
+
OAuth2AccessCodeSecurity,
|
|
19
|
+
OAuth2ApplicationSecurity,
|
|
20
|
+
OAuth2Flow,
|
|
21
|
+
OAuth2ImplicitSecurity,
|
|
22
|
+
OAuth2PasswordSecurity,
|
|
23
|
+
Operation,
|
|
24
|
+
Parameter,
|
|
25
|
+
ParameterLocation,
|
|
26
|
+
PathItem,
|
|
27
|
+
Paths,
|
|
28
|
+
PrimitivesItems,
|
|
29
|
+
PrimitiveType,
|
|
30
|
+
Response,
|
|
31
|
+
Responses,
|
|
32
|
+
ResponseValue,
|
|
33
|
+
Schema,
|
|
34
|
+
SchemeType,
|
|
35
|
+
SecurityScheme,
|
|
36
|
+
SecuritySchemeType,
|
|
37
|
+
Swagger,
|
|
38
|
+
Tag,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
# Main model
|
|
43
|
+
'Swagger',
|
|
44
|
+
# Info models
|
|
45
|
+
'Info',
|
|
46
|
+
'Contact',
|
|
47
|
+
'License',
|
|
48
|
+
'Tag',
|
|
49
|
+
'ExternalDocs',
|
|
50
|
+
# Schema models
|
|
51
|
+
'Schema',
|
|
52
|
+
'FileSchema',
|
|
53
|
+
'XML',
|
|
54
|
+
# Parameter models
|
|
55
|
+
'Parameter',
|
|
56
|
+
'BodyParameter',
|
|
57
|
+
'NonBodyParameter',
|
|
58
|
+
'PrimitivesItems',
|
|
59
|
+
# Response models
|
|
60
|
+
'Response',
|
|
61
|
+
'Responses',
|
|
62
|
+
'ResponseValue',
|
|
63
|
+
'Header',
|
|
64
|
+
# Operation models
|
|
65
|
+
'Operation',
|
|
66
|
+
'PathItem',
|
|
67
|
+
'Paths',
|
|
68
|
+
# Security models
|
|
69
|
+
'SecurityScheme',
|
|
70
|
+
'BasicAuthenticationSecurity',
|
|
71
|
+
'ApiKeySecurity',
|
|
72
|
+
'OAuth2ImplicitSecurity',
|
|
73
|
+
'OAuth2PasswordSecurity',
|
|
74
|
+
'OAuth2ApplicationSecurity',
|
|
75
|
+
'OAuth2AccessCodeSecurity',
|
|
76
|
+
# Enums
|
|
77
|
+
'SchemeType',
|
|
78
|
+
'ParameterLocation',
|
|
79
|
+
'PrimitiveType',
|
|
80
|
+
'CollectionFormat',
|
|
81
|
+
'CollectionFormatWithMulti',
|
|
82
|
+
'SecuritySchemeType',
|
|
83
|
+
'OAuth2Flow',
|
|
84
|
+
# Utilities
|
|
85
|
+
'JsonReference',
|
|
86
|
+
]
|