python-openapi 0.1.8__tar.gz → 0.1.10__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.
- {python-openapi-0.1.8 → python_openapi-0.1.10}/LICENSE +1 -1
- {python-openapi-0.1.8 → python_openapi-0.1.10}/PKG-INFO +6 -1
- {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/__init__.py +6 -10
- {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/generator.py +153 -133
- {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/operations.py +19 -45
- {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/options.py +12 -15
- {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/proxy.py +11 -22
- python_openapi-0.1.10/pyopenapi/py.typed +0 -0
- {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/specification.py +14 -11
- {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/utility.py +21 -22
- {python-openapi-0.1.8 → python_openapi-0.1.10}/pyproject.toml +3 -0
- {python-openapi-0.1.8 → python_openapi-0.1.10}/python_openapi.egg-info/PKG-INFO +6 -1
- {python-openapi-0.1.8 → python_openapi-0.1.10}/python_openapi.egg-info/SOURCES.txt +4 -1
- python_openapi-0.1.10/python_openapi.egg-info/requires.txt +2 -0
- {python-openapi-0.1.8 → python_openapi-0.1.10}/setup.cfg +8 -3
- python_openapi-0.1.10/tests/test_openapi.py +163 -0
- python_openapi-0.1.10/tests/test_proxy.py +70 -0
- python-openapi-0.1.8/python_openapi.egg-info/requires.txt +0 -2
- {python-openapi-0.1.8 → python_openapi-0.1.10}/README.md +0 -0
- {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/__main__.py +0 -0
- {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/metadata.py +0 -0
- {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/template.html +0 -0
- {python-openapi-0.1.8 → python_openapi-0.1.10}/python_openapi.egg-info/dependency_links.txt +0 -0
- {python-openapi-0.1.8 → python_openapi-0.1.10}/python_openapi.egg-info/top_level.txt +0 -0
- {python-openapi-0.1.8 → python_openapi-0.1.10}/python_openapi.egg-info/zip-safe +0 -0
- {python-openapi-0.1.8 → python_openapi-0.1.10}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-openapi
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
4
4
|
Summary: Generate an OpenAPI specification from a Python class definition
|
|
5
5
|
Home-page: https://github.com/hunyadi/pyopenapi
|
|
6
6
|
Author: Levente Hunyadi
|
|
@@ -15,11 +15,16 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.9
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
20
|
Classifier: Topic :: Software Development :: Code Generators
|
|
19
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
22
|
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.8
|
|
21
24
|
Description-Content-Type: text/markdown
|
|
22
25
|
License-File: LICENSE
|
|
26
|
+
Requires-Dist: aiohttp>=3.11
|
|
27
|
+
Requires-Dist: json_strong_typing>=0.3.7
|
|
23
28
|
|
|
24
29
|
# Generate an OpenAPI specification from a Python class
|
|
25
30
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
from typing import Any, Callable, Optional, TypeVar
|
|
1
|
+
from typing import Any, Callable, List, Optional, TypeVar
|
|
2
2
|
|
|
3
3
|
from .metadata import WebMethod
|
|
4
|
-
from .options import *
|
|
5
|
-
from .utility import Specification
|
|
4
|
+
from .options import * # noqa: F403
|
|
5
|
+
from .utility import Specification as Specification
|
|
6
6
|
|
|
7
|
-
__version__ = "0.1.
|
|
7
|
+
__version__ = "0.1.10"
|
|
8
8
|
|
|
9
9
|
T = TypeVar("T")
|
|
10
10
|
|
|
@@ -29,13 +29,9 @@ def webmethod(
|
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
31
|
if request_example is not None and request_examples is not None:
|
|
32
|
-
raise ValueError(
|
|
33
|
-
"arguments `request_example` and `request_examples` are exclusive"
|
|
34
|
-
)
|
|
32
|
+
raise ValueError("arguments `request_example` and `request_examples` are exclusive")
|
|
35
33
|
if response_example is not None and response_examples is not None:
|
|
36
|
-
raise ValueError(
|
|
37
|
-
"arguments `response_example` and `response_examples` are exclusive"
|
|
38
|
-
)
|
|
34
|
+
raise ValueError("arguments `response_example` and `response_examples` are exclusive")
|
|
39
35
|
|
|
40
36
|
if request_example:
|
|
41
37
|
request_examples = [request_example]
|
|
@@ -1,31 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
import dataclasses
|
|
2
|
+
import hashlib
|
|
3
|
+
import ipaddress
|
|
4
|
+
import typing
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from http import HTTPStatus
|
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
|
2
8
|
|
|
3
9
|
from strong_typing.core import JsonType
|
|
4
|
-
from strong_typing.docstring import parse_type
|
|
5
|
-
from strong_typing.inspection import
|
|
6
|
-
is_generic_list,
|
|
7
|
-
is_type_optional,
|
|
8
|
-
is_type_union,
|
|
9
|
-
unwrap_generic_list,
|
|
10
|
-
unwrap_optional_type,
|
|
11
|
-
unwrap_union_types,
|
|
12
|
-
)
|
|
10
|
+
from strong_typing.docstring import Docstring, parse_type
|
|
11
|
+
from strong_typing.inspection import is_generic_list, is_type_optional, is_type_union, unwrap_generic_list, unwrap_optional_type, unwrap_union_types
|
|
13
12
|
from strong_typing.name import python_type_to_name
|
|
14
|
-
from strong_typing.schema import
|
|
15
|
-
|
|
16
|
-
Schema,
|
|
17
|
-
SchemaOptions,
|
|
18
|
-
get_schema_identifier,
|
|
19
|
-
)
|
|
20
|
-
from strong_typing.serialization import object_to_json
|
|
13
|
+
from strong_typing.schema import JsonSchemaGenerator, Schema, SchemaOptions, get_schema_identifier, register_schema
|
|
14
|
+
from strong_typing.serialization import json_dump_string, object_to_json
|
|
21
15
|
|
|
22
|
-
from .operations import
|
|
23
|
-
|
|
24
|
-
HTTPMethod,
|
|
25
|
-
get_endpoint_events,
|
|
26
|
-
get_endpoint_operations,
|
|
27
|
-
)
|
|
28
|
-
from .options import *
|
|
16
|
+
from .operations import EndpointOperation, HTTPMethod, get_endpoint_events, get_endpoint_operations
|
|
17
|
+
from .options import HTTPStatusCode, Options
|
|
29
18
|
from .specification import (
|
|
30
19
|
Components,
|
|
31
20
|
Document,
|
|
@@ -39,13 +28,39 @@ from .specification import (
|
|
|
39
28
|
RequestBody,
|
|
40
29
|
Response,
|
|
41
30
|
ResponseRef,
|
|
31
|
+
SchemaOrRef,
|
|
42
32
|
SchemaRef,
|
|
43
33
|
Tag,
|
|
44
34
|
TagGroup,
|
|
45
35
|
)
|
|
46
36
|
|
|
37
|
+
register_schema(
|
|
38
|
+
ipaddress.IPv4Address,
|
|
39
|
+
schema={
|
|
40
|
+
"type": "string",
|
|
41
|
+
"format": "ipv4",
|
|
42
|
+
"title": "IPv4 address",
|
|
43
|
+
"description": "IPv4 address, according to dotted-quad ABNF syntax as defined in RFC 2673, section 3.2.",
|
|
44
|
+
},
|
|
45
|
+
examples=["192.0.2.0", "198.51.100.1", "203.0.113.255"],
|
|
46
|
+
)
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
register_schema(
|
|
49
|
+
ipaddress.IPv6Address,
|
|
50
|
+
schema={
|
|
51
|
+
"type": "string",
|
|
52
|
+
"format": "ipv6",
|
|
53
|
+
"title": "IPv6 address",
|
|
54
|
+
"description": "IPv6 address, as defined in RFC 2373, section 2.2.",
|
|
55
|
+
},
|
|
56
|
+
examples=[
|
|
57
|
+
"FEDC:BA98:7654:3210:FEDC:BA98:7654:3210",
|
|
58
|
+
"1080:0:0:0:8:800:200C:417A",
|
|
59
|
+
"1080::8:800:200C:417A",
|
|
60
|
+
"FF01::101",
|
|
61
|
+
"::1",
|
|
62
|
+
],
|
|
63
|
+
)
|
|
49
64
|
|
|
50
65
|
|
|
51
66
|
def http_status_to_string(status_code: HTTPStatusCode) -> str:
|
|
@@ -135,9 +150,7 @@ class ContentBuilder:
|
|
|
135
150
|
self.schema_transformer = schema_transformer
|
|
136
151
|
self.sample_transformer = sample_transformer
|
|
137
152
|
|
|
138
|
-
def build_content(
|
|
139
|
-
self, payload_type: type, examples: Optional[List[Any]] = None
|
|
140
|
-
) -> Dict[str, MediaType]:
|
|
153
|
+
def build_content(self, payload_type: type, examples: Optional[List[Any]] = None) -> Dict[str, MediaType]:
|
|
141
154
|
"Creates the content subtree for a request or response."
|
|
142
155
|
|
|
143
156
|
if is_generic_list(payload_type):
|
|
@@ -149,35 +162,75 @@ class ContentBuilder:
|
|
|
149
162
|
|
|
150
163
|
return {media_type: self.build_media_type(item_type, examples)}
|
|
151
164
|
|
|
152
|
-
def build_media_type(
|
|
153
|
-
self, item_type: type, examples: Optional[List[Any]] = None
|
|
154
|
-
) -> MediaType:
|
|
155
|
-
|
|
165
|
+
def build_media_type(self, item_type: type, examples: Optional[List[Any]] = None) -> MediaType:
|
|
156
166
|
schema = self.schema_builder.classdef_to_ref(item_type)
|
|
157
167
|
if self.schema_transformer:
|
|
158
|
-
schema_transformer: Callable[[SchemaOrRef], SchemaOrRef] = self.schema_transformer
|
|
168
|
+
schema_transformer: Callable[[SchemaOrRef], SchemaOrRef] = self.schema_transformer
|
|
159
169
|
schema = schema_transformer(schema)
|
|
170
|
+
|
|
171
|
+
if not examples:
|
|
172
|
+
return MediaType(schema=schema)
|
|
173
|
+
|
|
174
|
+
if len(examples) == 1:
|
|
175
|
+
return MediaType(schema=schema, example=self._build_example(examples[0]))
|
|
176
|
+
|
|
160
177
|
return MediaType(
|
|
161
178
|
schema=schema,
|
|
162
179
|
examples=self._build_examples(examples),
|
|
163
180
|
)
|
|
164
181
|
|
|
165
|
-
def _build_examples(
|
|
166
|
-
|
|
167
|
-
|
|
182
|
+
def _build_examples(self, examples: List[Any]) -> Dict[str, Union[Example, ExampleRef]]:
|
|
183
|
+
"Creates a set of several examples for a media type."
|
|
184
|
+
|
|
185
|
+
builder = ExampleBuilder(self.sample_transformer)
|
|
186
|
+
|
|
187
|
+
results: Dict[str, Union[Example, ExampleRef]] = {}
|
|
188
|
+
for example in examples:
|
|
189
|
+
name, value = builder.get_named(example)
|
|
190
|
+
results[name] = Example(value=value)
|
|
191
|
+
|
|
192
|
+
return results
|
|
168
193
|
|
|
169
|
-
|
|
170
|
-
|
|
194
|
+
def _build_example(self, example: Any) -> Any:
|
|
195
|
+
"Creates a single example for a media type."
|
|
171
196
|
|
|
172
|
-
|
|
173
|
-
|
|
197
|
+
builder = ExampleBuilder(self.sample_transformer)
|
|
198
|
+
return builder.get_anonymous(example)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class ExampleBuilder:
|
|
202
|
+
sample_transformer: Callable[[JsonType], JsonType]
|
|
203
|
+
|
|
204
|
+
def __init__(
|
|
205
|
+
self,
|
|
206
|
+
sample_transformer: Optional[Callable[[JsonType], JsonType]] = None,
|
|
207
|
+
) -> None:
|
|
208
|
+
if sample_transformer:
|
|
209
|
+
self.sample_transformer = sample_transformer
|
|
174
210
|
else:
|
|
175
|
-
sample_transformer = lambda sample: sample
|
|
211
|
+
self.sample_transformer = lambda sample: sample # noqa: E731
|
|
212
|
+
|
|
213
|
+
def _get_value(self, example: Any) -> JsonType:
|
|
214
|
+
return self.sample_transformer(object_to_json(example))
|
|
215
|
+
|
|
216
|
+
def get_anonymous(self, example: Any) -> JsonType:
|
|
217
|
+
return self._get_value(example)
|
|
218
|
+
|
|
219
|
+
def get_named(self, example: Any) -> Tuple[str, JsonType]:
|
|
220
|
+
value = self._get_value(example)
|
|
221
|
+
|
|
222
|
+
name: Optional[str] = None
|
|
176
223
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
224
|
+
if type(example).__str__ is not object.__str__:
|
|
225
|
+
friendly_name = str(example)
|
|
226
|
+
if friendly_name.isprintable():
|
|
227
|
+
name = friendly_name
|
|
228
|
+
|
|
229
|
+
if name is None:
|
|
230
|
+
hash_string = hashlib.md5(json_dump_string(value).encode("utf-8")).digest().hex()
|
|
231
|
+
name = f"ex-{hash_string}"
|
|
232
|
+
|
|
233
|
+
return name, value
|
|
181
234
|
|
|
182
235
|
|
|
183
236
|
@dataclass
|
|
@@ -210,15 +263,11 @@ class ResponseBuilder:
|
|
|
210
263
|
def __init__(self, content_builder: ContentBuilder) -> None:
|
|
211
264
|
self.content_builder = content_builder
|
|
212
265
|
|
|
213
|
-
def _get_status_responses(
|
|
214
|
-
self, options: ResponseOptions
|
|
215
|
-
) -> Dict[str, StatusResponse]:
|
|
266
|
+
def _get_status_responses(self, options: ResponseOptions) -> Dict[str, StatusResponse]:
|
|
216
267
|
status_responses: Dict[str, StatusResponse] = {}
|
|
217
268
|
|
|
218
269
|
for response_type in options.type_descriptions.keys():
|
|
219
|
-
status_code = http_status_to_string(
|
|
220
|
-
options.status_catalog.get(response_type, options.default_status_code)
|
|
221
|
-
)
|
|
270
|
+
status_code = http_status_to_string(options.status_catalog.get(response_type, options.default_status_code))
|
|
222
271
|
|
|
223
272
|
# look up response for status code
|
|
224
273
|
if status_code not in status_responses:
|
|
@@ -230,17 +279,11 @@ class ResponseBuilder:
|
|
|
230
279
|
|
|
231
280
|
# append examples that have the matching response type
|
|
232
281
|
if options.examples:
|
|
233
|
-
status_response.examples.extend(
|
|
234
|
-
example
|
|
235
|
-
for example in options.examples
|
|
236
|
-
if isinstance(example, response_type)
|
|
237
|
-
)
|
|
282
|
+
status_response.examples.extend(example for example in options.examples if isinstance(example, response_type))
|
|
238
283
|
|
|
239
|
-
return status_responses
|
|
284
|
+
return dict(sorted(status_responses.items()))
|
|
240
285
|
|
|
241
|
-
def build_response(
|
|
242
|
-
self, options: ResponseOptions
|
|
243
|
-
) -> Dict[str, Union[Response, ResponseRef]]:
|
|
286
|
+
def build_response(self, options: ResponseOptions) -> Dict[str, Union[Response, ResponseRef]]:
|
|
244
287
|
"""
|
|
245
288
|
Groups responses that have the same status code.
|
|
246
289
|
"""
|
|
@@ -258,10 +301,7 @@ class ResponseBuilder:
|
|
|
258
301
|
description = " **OR** ".join(
|
|
259
302
|
filter(
|
|
260
303
|
None,
|
|
261
|
-
(
|
|
262
|
-
options.type_descriptions[response_type]
|
|
263
|
-
for response_type in response_types
|
|
264
|
-
),
|
|
304
|
+
(options.type_descriptions[response_type] for response_type in response_types),
|
|
265
305
|
)
|
|
266
306
|
)
|
|
267
307
|
|
|
@@ -290,6 +330,27 @@ class ResponseBuilder:
|
|
|
290
330
|
return Response(description=description)
|
|
291
331
|
|
|
292
332
|
|
|
333
|
+
def schema_error_wrapper(schema: SchemaOrRef) -> Schema:
|
|
334
|
+
"Wraps an error output schema into a top-level error schema."
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
"type": "object",
|
|
338
|
+
"properties": {
|
|
339
|
+
"error": schema, # type: ignore
|
|
340
|
+
},
|
|
341
|
+
"additionalProperties": False,
|
|
342
|
+
"required": [
|
|
343
|
+
"error",
|
|
344
|
+
],
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def sample_error_wrapper(error: JsonType) -> JsonType:
|
|
349
|
+
"Wraps an error output sample into a top-level error sample."
|
|
350
|
+
|
|
351
|
+
return {"error": error}
|
|
352
|
+
|
|
353
|
+
|
|
293
354
|
class Generator:
|
|
294
355
|
endpoint: type
|
|
295
356
|
options: Options
|
|
@@ -302,6 +363,7 @@ class Generator:
|
|
|
302
363
|
schema_generator = JsonSchemaGenerator(
|
|
303
364
|
SchemaOptions(
|
|
304
365
|
definitions_path="#/components/schemas/",
|
|
366
|
+
use_examples=self.options.use_examples,
|
|
305
367
|
property_description_fun=options.property_description_fun,
|
|
306
368
|
)
|
|
307
369
|
)
|
|
@@ -310,18 +372,14 @@ class Generator:
|
|
|
310
372
|
|
|
311
373
|
def _build_type_tag(self, ref: str, schema: Schema) -> Tag:
|
|
312
374
|
definition = f'<SchemaDefinition schemaRef="#/components/schemas/{ref}" />'
|
|
313
|
-
title = schema.get("title")
|
|
314
|
-
description = schema.get("description")
|
|
375
|
+
title = typing.cast(str, schema.get("title"))
|
|
376
|
+
description = typing.cast(str, schema.get("description"))
|
|
315
377
|
return Tag(
|
|
316
378
|
name=ref,
|
|
317
|
-
description="\n\n".join(
|
|
318
|
-
s for s in (title, description, definition) if s is not None
|
|
319
|
-
),
|
|
379
|
+
description="\n\n".join(s for s in (title, description, definition) if s is not None),
|
|
320
380
|
)
|
|
321
381
|
|
|
322
|
-
def _build_extra_tag_groups(
|
|
323
|
-
self, extra_types: Dict[str, List[type]]
|
|
324
|
-
) -> Dict[str, List[Tag]]:
|
|
382
|
+
def _build_extra_tag_groups(self, extra_types: Dict[str, List[type]]) -> Dict[str, List[Tag]]:
|
|
325
383
|
"""
|
|
326
384
|
Creates a dictionary of tag group captions as keys, and tag lists as values.
|
|
327
385
|
|
|
@@ -338,15 +396,14 @@ class Generator:
|
|
|
338
396
|
schema = self.schema_builder.classdef_to_named_schema(name, extra_type)
|
|
339
397
|
tag_list.append(self._build_type_tag(name, schema))
|
|
340
398
|
|
|
341
|
-
|
|
399
|
+
if tag_list:
|
|
400
|
+
extra_tags[category_name] = tag_list
|
|
342
401
|
|
|
343
402
|
return extra_tags
|
|
344
403
|
|
|
345
404
|
def _build_operation(self, op: EndpointOperation) -> Operation:
|
|
346
405
|
doc_string = parse_type(op.func_ref)
|
|
347
|
-
doc_params = dict(
|
|
348
|
-
(param.name, param.description) for param in doc_string.params.values()
|
|
349
|
-
)
|
|
406
|
+
doc_params = dict((param.name, param.description) for param in doc_string.params.values())
|
|
350
407
|
|
|
351
408
|
# parameters passed in URL component path
|
|
352
409
|
path_parameters = [
|
|
@@ -364,7 +421,7 @@ class Generator:
|
|
|
364
421
|
query_parameters = []
|
|
365
422
|
for param_name, param_type in op.query_params:
|
|
366
423
|
if is_type_optional(param_type):
|
|
367
|
-
inner_type = unwrap_optional_type(param_type)
|
|
424
|
+
inner_type: type = unwrap_optional_type(param_type)
|
|
368
425
|
required = False
|
|
369
426
|
else:
|
|
370
427
|
inner_type = param_type
|
|
@@ -387,11 +444,7 @@ class Generator:
|
|
|
387
444
|
builder = ContentBuilder(self.schema_builder)
|
|
388
445
|
request_name, request_type = op.request_param
|
|
389
446
|
requestBody = RequestBody(
|
|
390
|
-
content={
|
|
391
|
-
"application/json": builder.build_media_type(
|
|
392
|
-
request_type, op.request_examples
|
|
393
|
-
)
|
|
394
|
-
},
|
|
447
|
+
content={"application/json": builder.build_media_type(request_type, op.request_examples)},
|
|
395
448
|
description=doc_params.get(request_name),
|
|
396
449
|
required=True,
|
|
397
450
|
)
|
|
@@ -401,30 +454,22 @@ class Generator:
|
|
|
401
454
|
# success response types
|
|
402
455
|
if doc_string.returns is None and is_type_union(op.response_type):
|
|
403
456
|
# split union of return types into a list of response types
|
|
457
|
+
success_type_docstring: Dict[type, Docstring] = {typing.cast(type, item): parse_type(item) for item in unwrap_union_types(op.response_type)}
|
|
404
458
|
success_type_descriptions = {
|
|
405
|
-
item:
|
|
406
|
-
for item in unwrap_union_types(op.response_type)
|
|
459
|
+
item: doc_string.short_description for item, doc_string in success_type_docstring.items() if doc_string.short_description
|
|
407
460
|
}
|
|
408
461
|
else:
|
|
409
462
|
# use return type as a single response type
|
|
410
|
-
success_type_descriptions = {
|
|
411
|
-
op.response_type: (
|
|
412
|
-
doc_string.returns.description if doc_string.returns else "OK"
|
|
413
|
-
)
|
|
414
|
-
}
|
|
463
|
+
success_type_descriptions = {op.response_type: (doc_string.returns.description if doc_string.returns else "OK")}
|
|
415
464
|
|
|
416
465
|
response_examples = op.response_examples or []
|
|
417
|
-
success_examples = [
|
|
418
|
-
example
|
|
419
|
-
for example in response_examples
|
|
420
|
-
if not isinstance(example, Exception)
|
|
421
|
-
]
|
|
466
|
+
success_examples = [example for example in response_examples if not isinstance(example, Exception)]
|
|
422
467
|
|
|
423
468
|
content_builder = ContentBuilder(self.schema_builder)
|
|
424
469
|
response_builder = ResponseBuilder(content_builder)
|
|
425
470
|
response_options = ResponseOptions(
|
|
426
471
|
success_type_descriptions,
|
|
427
|
-
success_examples,
|
|
472
|
+
success_examples if self.options.use_examples else None,
|
|
428
473
|
self.options.success_responses,
|
|
429
474
|
"200",
|
|
430
475
|
)
|
|
@@ -432,27 +477,12 @@ class Generator:
|
|
|
432
477
|
|
|
433
478
|
# failure response types
|
|
434
479
|
if doc_string.raises:
|
|
435
|
-
exception_types: Dict[type, str] = {
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
exception_examples = [
|
|
439
|
-
example
|
|
440
|
-
for example in response_examples
|
|
441
|
-
if isinstance(example, Exception)
|
|
442
|
-
]
|
|
480
|
+
exception_types: Dict[type, str] = {item.raise_type: item.description for item in doc_string.raises.values()}
|
|
481
|
+
exception_examples = [example for example in response_examples if isinstance(example, Exception)]
|
|
443
482
|
|
|
444
483
|
if self.options.error_wrapper:
|
|
445
|
-
schema_transformer =
|
|
446
|
-
|
|
447
|
-
"properties": {
|
|
448
|
-
"error": schema,
|
|
449
|
-
},
|
|
450
|
-
"additionalProperties": False,
|
|
451
|
-
"required": [
|
|
452
|
-
"error",
|
|
453
|
-
],
|
|
454
|
-
}
|
|
455
|
-
sample_transformer = lambda error: {"error": error}
|
|
484
|
+
schema_transformer = schema_error_wrapper
|
|
485
|
+
sample_transformer = sample_error_wrapper
|
|
456
486
|
else:
|
|
457
487
|
schema_transformer = None
|
|
458
488
|
sample_transformer = None
|
|
@@ -465,7 +495,7 @@ class Generator:
|
|
|
465
495
|
response_builder = ResponseBuilder(content_builder)
|
|
466
496
|
response_options = ResponseOptions(
|
|
467
497
|
exception_types,
|
|
468
|
-
exception_examples,
|
|
498
|
+
exception_examples if self.options.use_examples else None,
|
|
469
499
|
self.options.error_responses,
|
|
470
500
|
"500",
|
|
471
501
|
)
|
|
@@ -477,9 +507,7 @@ class Generator:
|
|
|
477
507
|
f"{op.func_name}_callback": {
|
|
478
508
|
"{$request.query.callback}": PathItem(
|
|
479
509
|
post=Operation(
|
|
480
|
-
requestBody=RequestBody(
|
|
481
|
-
content=builder.build_content(op.event_type)
|
|
482
|
-
),
|
|
510
|
+
requestBody=RequestBody(content=builder.build_content(op.event_type)),
|
|
483
511
|
responses={"200": Response(description="OK")},
|
|
484
512
|
)
|
|
485
513
|
)
|
|
@@ -503,7 +531,7 @@ class Generator:
|
|
|
503
531
|
def generate(self) -> Document:
|
|
504
532
|
paths: Dict[str, PathItem] = {}
|
|
505
533
|
endpoint_classes: Set[type] = set()
|
|
506
|
-
for op in get_endpoint_operations(self.endpoint):
|
|
534
|
+
for op in get_endpoint_operations(self.endpoint, use_examples=self.options.use_examples):
|
|
507
535
|
endpoint_classes.add(op.defining_class)
|
|
508
536
|
|
|
509
537
|
operation = self._build_operation(op)
|
|
@@ -539,10 +567,7 @@ class Generator:
|
|
|
539
567
|
)
|
|
540
568
|
|
|
541
569
|
# types that are produced/consumed by operations
|
|
542
|
-
type_tags = [
|
|
543
|
-
self._build_type_tag(ref, schema)
|
|
544
|
-
for ref, schema in self.schema_builder.schemas.items()
|
|
545
|
-
]
|
|
570
|
+
type_tags = [self._build_type_tag(ref, schema) for ref, schema in self.schema_builder.schemas.items()]
|
|
546
571
|
|
|
547
572
|
# types that are emitted by events
|
|
548
573
|
event_tags: List[Tag] = []
|
|
@@ -555,17 +580,11 @@ class Generator:
|
|
|
555
580
|
extra_tag_groups: Dict[str, List[Tag]] = {}
|
|
556
581
|
if self.options.extra_types is not None:
|
|
557
582
|
if isinstance(self.options.extra_types, list):
|
|
558
|
-
extra_tag_groups = self._build_extra_tag_groups(
|
|
559
|
-
{"AdditionalTypes": self.options.extra_types}
|
|
560
|
-
)
|
|
583
|
+
extra_tag_groups = self._build_extra_tag_groups({"AdditionalTypes": self.options.extra_types})
|
|
561
584
|
elif isinstance(self.options.extra_types, dict):
|
|
562
|
-
extra_tag_groups = self._build_extra_tag_groups(
|
|
563
|
-
self.options.extra_types
|
|
564
|
-
)
|
|
585
|
+
extra_tag_groups = self._build_extra_tag_groups(self.options.extra_types)
|
|
565
586
|
else:
|
|
566
|
-
raise TypeError(
|
|
567
|
-
f"type mismatch for collection of extra types: {type(self.options.extra_types)}"
|
|
568
|
-
)
|
|
587
|
+
raise TypeError(f"type mismatch for collection of extra types: {type(self.options.extra_types)}")
|
|
569
588
|
|
|
570
589
|
# list all operations and types
|
|
571
590
|
tags: List[Tag] = []
|
|
@@ -611,8 +630,9 @@ class Generator:
|
|
|
611
630
|
securitySchemes = None
|
|
612
631
|
|
|
613
632
|
return Document(
|
|
614
|
-
openapi="
|
|
633
|
+
openapi=".".join(str(item) for item in self.options.version),
|
|
615
634
|
info=self.options.info,
|
|
635
|
+
jsonSchemaDialect=("https://json-schema.org/draft/2020-12/schema" if self.options.version >= (3, 1, 0) else None),
|
|
616
636
|
servers=[self.options.server],
|
|
617
637
|
paths=paths,
|
|
618
638
|
components=Components(
|