python-openapi 0.1.10__py3-none-any.whl → 0.2.0__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.
- pyopenapi/__init__.py +11 -51
- pyopenapi/decorators.py +51 -0
- pyopenapi/generator.py +42 -34
- pyopenapi/metadata.py +11 -3
- pyopenapi/operations.py +26 -17
- pyopenapi/options.py +16 -9
- pyopenapi/proxy.py +15 -6
- pyopenapi/specification.py +34 -26
- pyopenapi/utility.py +12 -8
- {python_openapi-0.1.10.dist-info → python_openapi-0.2.0.dist-info}/METADATA +17 -15
- python_openapi-0.2.0.dist-info/RECORD +18 -0
- {python_openapi-0.1.10.dist-info → python_openapi-0.2.0.dist-info}/WHEEL +1 -1
- python_openapi-0.1.10.dist-info/RECORD +0 -17
- {python_openapi-0.1.10.dist-info → python_openapi-0.2.0.dist-info/licenses}/LICENSE +0 -0
- {python_openapi-0.1.10.dist-info → python_openapi-0.2.0.dist-info}/top_level.txt +0 -0
- {python_openapi-0.1.10.dist-info → python_openapi-0.2.0.dist-info}/zip-safe +0 -0
pyopenapi/__init__.py
CHANGED
|
@@ -1,54 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
Generate an OpenAPI specification from a Python class definition
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
from .options import * # noqa: F403
|
|
5
|
-
from .utility import Specification as Specification
|
|
4
|
+
Copyright 2022-2025, Levente Hunyadi
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
:see: https://github.com/hunyadi/pyopenapi
|
|
7
|
+
"""
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
request_example: Optional[Any] = None,
|
|
16
|
-
response_example: Optional[Any] = None,
|
|
17
|
-
request_examples: Optional[List[Any]] = None,
|
|
18
|
-
response_examples: Optional[List[Any]] = None,
|
|
19
|
-
) -> Callable[[T], T]:
|
|
20
|
-
"""
|
|
21
|
-
Decorator that supplies additional metadata to an endpoint operation function.
|
|
22
|
-
|
|
23
|
-
:param route: The URL path pattern associated with this operation which path parameters are substituted into.
|
|
24
|
-
:param public: True if the operation can be invoked without prior authentication.
|
|
25
|
-
:param request_example: A sample request that the operation might take.
|
|
26
|
-
:param response_example: A sample response that the operation might produce.
|
|
27
|
-
:param request_examples: Sample requests that the operation might take. Pass a list of objects, not JSON.
|
|
28
|
-
:param response_examples: Sample responses that the operation might produce. Pass a list of objects, not JSON.
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
if request_example is not None and request_examples is not None:
|
|
32
|
-
raise ValueError("arguments `request_example` and `request_examples` are exclusive")
|
|
33
|
-
if response_example is not None and response_examples is not None:
|
|
34
|
-
raise ValueError("arguments `response_example` and `response_examples` are exclusive")
|
|
35
|
-
|
|
36
|
-
if request_example:
|
|
37
|
-
request_examples = [request_example]
|
|
38
|
-
if response_example:
|
|
39
|
-
response_examples = [response_example]
|
|
40
|
-
|
|
41
|
-
def wrap(cls: T) -> T:
|
|
42
|
-
setattr(
|
|
43
|
-
cls,
|
|
44
|
-
"__webmethod__",
|
|
45
|
-
WebMethod(
|
|
46
|
-
route=route,
|
|
47
|
-
public=public or False,
|
|
48
|
-
request_examples=request_examples,
|
|
49
|
-
response_examples=response_examples,
|
|
50
|
-
),
|
|
51
|
-
)
|
|
52
|
-
return cls
|
|
53
|
-
|
|
54
|
-
return wrap
|
|
9
|
+
__version__ = "0.2.0"
|
|
10
|
+
__author__ = "Levente Hunyadi"
|
|
11
|
+
__copyright__ = "Copyright 2022-2025, Levente Hunyadi"
|
|
12
|
+
__license__ = "MIT"
|
|
13
|
+
__maintainer__ = "Levente Hunyadi"
|
|
14
|
+
__status__ = "Production"
|
pyopenapi/decorators.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generate an OpenAPI specification from a Python class definition
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2025, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/pyopenapi
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Callable, Optional, TypeVar
|
|
10
|
+
|
|
11
|
+
from .metadata import WebMethod
|
|
12
|
+
from .options import * # noqa: F403
|
|
13
|
+
from .utility import Specification as Specification
|
|
14
|
+
|
|
15
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def webmethod(
|
|
19
|
+
route: Optional[str] = None,
|
|
20
|
+
public: Optional[bool] = False,
|
|
21
|
+
request_example: Optional[Any] = None,
|
|
22
|
+
response_example: Optional[Any] = None,
|
|
23
|
+
request_examples: Optional[list[Any]] = None,
|
|
24
|
+
response_examples: Optional[list[Any]] = None,
|
|
25
|
+
) -> Callable[[F], F]:
|
|
26
|
+
"""
|
|
27
|
+
Decorator that supplies additional metadata to an endpoint operation function.
|
|
28
|
+
|
|
29
|
+
:param route: The URL path pattern associated with this operation which path parameters are substituted into.
|
|
30
|
+
:param public: True if the operation can be invoked without prior authentication.
|
|
31
|
+
:param request_example: A sample request that the operation might take.
|
|
32
|
+
:param response_example: A sample response that the operation might produce.
|
|
33
|
+
:param request_examples: Sample requests that the operation might take. Pass a list of objects, not JSON.
|
|
34
|
+
:param response_examples: Sample responses that the operation might produce. Pass a list of objects, not JSON.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
if request_example is not None and request_examples is not None:
|
|
38
|
+
raise ValueError("arguments `request_example` and `request_examples` are exclusive")
|
|
39
|
+
if response_example is not None and response_examples is not None:
|
|
40
|
+
raise ValueError("arguments `response_example` and `response_examples` are exclusive")
|
|
41
|
+
|
|
42
|
+
if request_example:
|
|
43
|
+
request_examples = [request_example]
|
|
44
|
+
if response_example:
|
|
45
|
+
response_examples = [response_example]
|
|
46
|
+
|
|
47
|
+
def wrap(cls: F) -> F:
|
|
48
|
+
cls.__webmethod__ = WebMethod(route=route, public=public or False, request_examples=request_examples, response_examples=response_examples) # type: ignore[attr-defined]
|
|
49
|
+
return cls
|
|
50
|
+
|
|
51
|
+
return wrap
|
pyopenapi/generator.py
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generate an OpenAPI specification from a Python class definition
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2025, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/pyopenapi
|
|
7
|
+
"""
|
|
8
|
+
|
|
1
9
|
import dataclasses
|
|
2
10
|
import hashlib
|
|
3
11
|
import ipaddress
|
|
4
12
|
import typing
|
|
5
13
|
from dataclasses import dataclass
|
|
6
14
|
from http import HTTPStatus
|
|
7
|
-
from typing import Any, Callable,
|
|
15
|
+
from typing import Any, Callable, Optional, Union
|
|
8
16
|
|
|
9
|
-
from strong_typing.core import JsonType
|
|
17
|
+
from strong_typing.core import JsonType, Schema
|
|
10
18
|
from strong_typing.docstring import Docstring, parse_type
|
|
11
19
|
from strong_typing.inspection import is_generic_list, is_type_optional, is_type_union, unwrap_generic_list, unwrap_optional_type, unwrap_union_types
|
|
12
20
|
from strong_typing.name import python_type_to_name
|
|
13
|
-
from strong_typing.schema import JsonSchemaGenerator,
|
|
21
|
+
from strong_typing.schema import JsonSchemaGenerator, SchemaOptions, get_schema_identifier, register_schema
|
|
14
22
|
from strong_typing.serialization import json_dump_string, object_to_json
|
|
15
23
|
|
|
16
24
|
from .operations import EndpointOperation, HTTPMethod, get_endpoint_events, get_endpoint_operations
|
|
@@ -78,7 +86,7 @@ def http_status_to_string(status_code: HTTPStatusCode) -> str:
|
|
|
78
86
|
|
|
79
87
|
class SchemaBuilder:
|
|
80
88
|
schema_generator: JsonSchemaGenerator
|
|
81
|
-
schemas:
|
|
89
|
+
schemas: dict[str, Schema]
|
|
82
90
|
|
|
83
91
|
def __init__(self, schema_generator: JsonSchemaGenerator) -> None:
|
|
84
92
|
self.schema_generator = schema_generator
|
|
@@ -150,7 +158,7 @@ class ContentBuilder:
|
|
|
150
158
|
self.schema_transformer = schema_transformer
|
|
151
159
|
self.sample_transformer = sample_transformer
|
|
152
160
|
|
|
153
|
-
def build_content(self, payload_type: type, examples: Optional[
|
|
161
|
+
def build_content(self, payload_type: type, examples: Optional[list[Any]] = None) -> dict[str, MediaType]:
|
|
154
162
|
"Creates the content subtree for a request or response."
|
|
155
163
|
|
|
156
164
|
if is_generic_list(payload_type):
|
|
@@ -162,7 +170,7 @@ class ContentBuilder:
|
|
|
162
170
|
|
|
163
171
|
return {media_type: self.build_media_type(item_type, examples)}
|
|
164
172
|
|
|
165
|
-
def build_media_type(self, item_type: type, examples: Optional[
|
|
173
|
+
def build_media_type(self, item_type: type, examples: Optional[list[Any]] = None) -> MediaType:
|
|
166
174
|
schema = self.schema_builder.classdef_to_ref(item_type)
|
|
167
175
|
if self.schema_transformer:
|
|
168
176
|
schema_transformer: Callable[[SchemaOrRef], SchemaOrRef] = self.schema_transformer
|
|
@@ -179,12 +187,12 @@ class ContentBuilder:
|
|
|
179
187
|
examples=self._build_examples(examples),
|
|
180
188
|
)
|
|
181
189
|
|
|
182
|
-
def _build_examples(self, examples:
|
|
190
|
+
def _build_examples(self, examples: list[Any]) -> dict[str, Union[Example, ExampleRef]]:
|
|
183
191
|
"Creates a set of several examples for a media type."
|
|
184
192
|
|
|
185
193
|
builder = ExampleBuilder(self.sample_transformer)
|
|
186
194
|
|
|
187
|
-
results:
|
|
195
|
+
results: dict[str, Union[Example, ExampleRef]] = {}
|
|
188
196
|
for example in examples:
|
|
189
197
|
name, value = builder.get_named(example)
|
|
190
198
|
results[name] = Example(value=value)
|
|
@@ -208,7 +216,7 @@ class ExampleBuilder:
|
|
|
208
216
|
if sample_transformer:
|
|
209
217
|
self.sample_transformer = sample_transformer
|
|
210
218
|
else:
|
|
211
|
-
self.sample_transformer = lambda sample: sample
|
|
219
|
+
self.sample_transformer = lambda sample: sample
|
|
212
220
|
|
|
213
221
|
def _get_value(self, example: Any) -> JsonType:
|
|
214
222
|
return self.sample_transformer(object_to_json(example))
|
|
@@ -216,12 +224,12 @@ class ExampleBuilder:
|
|
|
216
224
|
def get_anonymous(self, example: Any) -> JsonType:
|
|
217
225
|
return self._get_value(example)
|
|
218
226
|
|
|
219
|
-
def get_named(self, example: Any) ->
|
|
227
|
+
def get_named(self, example: Any) -> tuple[str, JsonType]:
|
|
220
228
|
value = self._get_value(example)
|
|
221
229
|
|
|
222
230
|
name: Optional[str] = None
|
|
223
231
|
|
|
224
|
-
if type(example).__str__ is not object.__str__:
|
|
232
|
+
if type(example).__str__ is not object.__str__: # type: ignore[comparison-overlap]
|
|
225
233
|
friendly_name = str(example)
|
|
226
234
|
if friendly_name.isprintable():
|
|
227
235
|
name = friendly_name
|
|
@@ -244,17 +252,17 @@ class ResponseOptions:
|
|
|
244
252
|
:param default_status_code: HTTP status code assigned to responses that have no mapping.
|
|
245
253
|
"""
|
|
246
254
|
|
|
247
|
-
type_descriptions:
|
|
248
|
-
examples: Optional[
|
|
249
|
-
status_catalog:
|
|
255
|
+
type_descriptions: dict[type, str]
|
|
256
|
+
examples: Optional[list[Any]]
|
|
257
|
+
status_catalog: dict[type, HTTPStatusCode]
|
|
250
258
|
default_status_code: HTTPStatusCode
|
|
251
259
|
|
|
252
260
|
|
|
253
261
|
@dataclass
|
|
254
262
|
class StatusResponse:
|
|
255
263
|
status_code: str
|
|
256
|
-
types:
|
|
257
|
-
examples:
|
|
264
|
+
types: list[type] = dataclasses.field(default_factory=list)
|
|
265
|
+
examples: list[Any] = dataclasses.field(default_factory=list)
|
|
258
266
|
|
|
259
267
|
|
|
260
268
|
class ResponseBuilder:
|
|
@@ -263,8 +271,8 @@ class ResponseBuilder:
|
|
|
263
271
|
def __init__(self, content_builder: ContentBuilder) -> None:
|
|
264
272
|
self.content_builder = content_builder
|
|
265
273
|
|
|
266
|
-
def _get_status_responses(self, options: ResponseOptions) ->
|
|
267
|
-
status_responses:
|
|
274
|
+
def _get_status_responses(self, options: ResponseOptions) -> dict[str, StatusResponse]:
|
|
275
|
+
status_responses: dict[str, StatusResponse] = {}
|
|
268
276
|
|
|
269
277
|
for response_type in options.type_descriptions.keys():
|
|
270
278
|
status_code = http_status_to_string(options.status_catalog.get(response_type, options.default_status_code))
|
|
@@ -283,12 +291,12 @@ class ResponseBuilder:
|
|
|
283
291
|
|
|
284
292
|
return dict(sorted(status_responses.items()))
|
|
285
293
|
|
|
286
|
-
def build_response(self, options: ResponseOptions) ->
|
|
294
|
+
def build_response(self, options: ResponseOptions) -> dict[str, Union[Response, ResponseRef]]:
|
|
287
295
|
"""
|
|
288
296
|
Groups responses that have the same status code.
|
|
289
297
|
"""
|
|
290
298
|
|
|
291
|
-
responses:
|
|
299
|
+
responses: dict[str, Union[Response, ResponseRef]] = {}
|
|
292
300
|
status_responses = self._get_status_responses(options)
|
|
293
301
|
for status_code, status_response in status_responses.items():
|
|
294
302
|
response_types = tuple(status_response.types)
|
|
@@ -315,9 +323,9 @@ class ResponseBuilder:
|
|
|
315
323
|
|
|
316
324
|
def _build_response(
|
|
317
325
|
self,
|
|
318
|
-
response_type: type,
|
|
326
|
+
response_type: Optional[type],
|
|
319
327
|
description: str,
|
|
320
|
-
examples: Optional[
|
|
328
|
+
examples: Optional[list[Any]] = None,
|
|
321
329
|
) -> Response:
|
|
322
330
|
"Creates a response subtree."
|
|
323
331
|
|
|
@@ -355,7 +363,7 @@ class Generator:
|
|
|
355
363
|
endpoint: type
|
|
356
364
|
options: Options
|
|
357
365
|
schema_builder: SchemaBuilder
|
|
358
|
-
responses:
|
|
366
|
+
responses: dict[str, Response]
|
|
359
367
|
|
|
360
368
|
def __init__(self, endpoint: type, options: Options) -> None:
|
|
361
369
|
self.endpoint = endpoint
|
|
@@ -379,17 +387,17 @@ class Generator:
|
|
|
379
387
|
description="\n\n".join(s for s in (title, description, definition) if s is not None),
|
|
380
388
|
)
|
|
381
389
|
|
|
382
|
-
def _build_extra_tag_groups(self, extra_types:
|
|
390
|
+
def _build_extra_tag_groups(self, extra_types: dict[str, list[type]]) -> dict[str, list[Tag]]:
|
|
383
391
|
"""
|
|
384
392
|
Creates a dictionary of tag group captions as keys, and tag lists as values.
|
|
385
393
|
|
|
386
394
|
:param extra_types: A dictionary of type categories and list of types in that category.
|
|
387
395
|
"""
|
|
388
396
|
|
|
389
|
-
extra_tags:
|
|
397
|
+
extra_tags: dict[str, list[Tag]] = {}
|
|
390
398
|
|
|
391
399
|
for category_name, category_items in extra_types.items():
|
|
392
|
-
tag_list:
|
|
400
|
+
tag_list: list[Tag] = []
|
|
393
401
|
|
|
394
402
|
for extra_type in category_items:
|
|
395
403
|
name = python_type_to_name(extra_type)
|
|
@@ -454,7 +462,7 @@ class Generator:
|
|
|
454
462
|
# success response types
|
|
455
463
|
if doc_string.returns is None and is_type_union(op.response_type):
|
|
456
464
|
# split union of return types into a list of response types
|
|
457
|
-
success_type_docstring:
|
|
465
|
+
success_type_docstring: dict[type, Docstring] = {typing.cast(type, item): parse_type(item) for item in unwrap_union_types(op.response_type)}
|
|
458
466
|
success_type_descriptions = {
|
|
459
467
|
item: doc_string.short_description for item, doc_string in success_type_docstring.items() if doc_string.short_description
|
|
460
468
|
}
|
|
@@ -477,7 +485,7 @@ class Generator:
|
|
|
477
485
|
|
|
478
486
|
# failure response types
|
|
479
487
|
if doc_string.raises:
|
|
480
|
-
exception_types:
|
|
488
|
+
exception_types: dict[type, str] = {item.raise_type: item.description for item in doc_string.raises.values()}
|
|
481
489
|
exception_examples = [example for example in response_examples if isinstance(example, Exception)]
|
|
482
490
|
|
|
483
491
|
if self.options.error_wrapper:
|
|
@@ -529,8 +537,8 @@ class Generator:
|
|
|
529
537
|
)
|
|
530
538
|
|
|
531
539
|
def generate(self) -> Document:
|
|
532
|
-
paths:
|
|
533
|
-
endpoint_classes:
|
|
540
|
+
paths: dict[str, PathItem] = {}
|
|
541
|
+
endpoint_classes: set[type] = set()
|
|
534
542
|
for op in get_endpoint_operations(self.endpoint, use_examples=self.options.use_examples):
|
|
535
543
|
endpoint_classes.add(op.defining_class)
|
|
536
544
|
|
|
@@ -555,7 +563,7 @@ class Generator:
|
|
|
555
563
|
else:
|
|
556
564
|
paths[route] = pathItem
|
|
557
565
|
|
|
558
|
-
operation_tags:
|
|
566
|
+
operation_tags: list[Tag] = []
|
|
559
567
|
for cls in endpoint_classes:
|
|
560
568
|
doc_string = parse_type(cls)
|
|
561
569
|
operation_tags.append(
|
|
@@ -570,14 +578,14 @@ class Generator:
|
|
|
570
578
|
type_tags = [self._build_type_tag(ref, schema) for ref, schema in self.schema_builder.schemas.items()]
|
|
571
579
|
|
|
572
580
|
# types that are emitted by events
|
|
573
|
-
event_tags:
|
|
581
|
+
event_tags: list[Tag] = []
|
|
574
582
|
events = get_endpoint_events(self.endpoint)
|
|
575
583
|
for ref, event_type in events.items():
|
|
576
584
|
event_schema = self.schema_builder.classdef_to_named_schema(ref, event_type)
|
|
577
585
|
event_tags.append(self._build_type_tag(ref, event_schema))
|
|
578
586
|
|
|
579
587
|
# types that are explicitly declared
|
|
580
|
-
extra_tag_groups:
|
|
588
|
+
extra_tag_groups: dict[str, list[Tag]] = {}
|
|
581
589
|
if self.options.extra_types is not None:
|
|
582
590
|
if isinstance(self.options.extra_types, list):
|
|
583
591
|
extra_tag_groups = self._build_extra_tag_groups({"AdditionalTypes": self.options.extra_types})
|
|
@@ -587,7 +595,7 @@ class Generator:
|
|
|
587
595
|
raise TypeError(f"type mismatch for collection of extra types: {type(self.options.extra_types)}")
|
|
588
596
|
|
|
589
597
|
# list all operations and types
|
|
590
|
-
tags:
|
|
598
|
+
tags: list[Tag] = []
|
|
591
599
|
tags.extend(operation_tags)
|
|
592
600
|
tags.extend(type_tags)
|
|
593
601
|
tags.extend(event_tags)
|
pyopenapi/metadata.py
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generate an OpenAPI specification from a Python class definition
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2025, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/pyopenapi
|
|
7
|
+
"""
|
|
8
|
+
|
|
1
9
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Any,
|
|
10
|
+
from typing import Any, Optional
|
|
3
11
|
|
|
4
12
|
|
|
5
13
|
@dataclass
|
|
@@ -15,5 +23,5 @@ class WebMethod:
|
|
|
15
23
|
|
|
16
24
|
route: Optional[str] = None
|
|
17
25
|
public: bool = False
|
|
18
|
-
request_examples: Optional[
|
|
19
|
-
response_examples: Optional[
|
|
26
|
+
request_examples: Optional[list[Any]] = None
|
|
27
|
+
response_examples: Optional[list[Any]] = None
|
pyopenapi/operations.py
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generate an OpenAPI specification from a Python class definition
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2025, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/pyopenapi
|
|
7
|
+
"""
|
|
8
|
+
|
|
1
9
|
import collections.abc
|
|
2
10
|
import enum
|
|
3
11
|
import inspect
|
|
4
12
|
import typing
|
|
5
13
|
import uuid
|
|
6
14
|
from dataclasses import dataclass
|
|
7
|
-
from typing import Any, Callable,
|
|
15
|
+
from typing import Any, Callable, Iterable, Iterator, Optional, Union
|
|
8
16
|
|
|
9
17
|
from strong_typing.inspection import get_signature, is_type_enum, is_type_optional, unwrap_optional_type
|
|
10
18
|
|
|
11
19
|
from .metadata import WebMethod
|
|
12
20
|
|
|
13
21
|
|
|
14
|
-
def split_prefix(s: str, sep: str, prefix: Union[str, Iterable[str]]) ->
|
|
22
|
+
def split_prefix(s: str, sep: str, prefix: Union[str, Iterable[str]]) -> tuple[Optional[str], str]:
|
|
15
23
|
"""
|
|
16
24
|
Recognizes a prefix at the beginning of a string.
|
|
17
25
|
|
|
@@ -34,11 +42,11 @@ def split_prefix(s: str, sep: str, prefix: Union[str, Iterable[str]]) -> Tuple[O
|
|
|
34
42
|
return None, s
|
|
35
43
|
|
|
36
44
|
|
|
37
|
-
def _get_annotation_type(annotation: Union[type, str], callable: Callable) -> type:
|
|
38
|
-
"Maps a
|
|
45
|
+
def _get_annotation_type(annotation: Union[type, str], callable: Callable[..., Any]) -> type:
|
|
46
|
+
"Maps a string (forward) reference to a type, as if using `from __future__ import annotations`."
|
|
39
47
|
|
|
40
48
|
if isinstance(annotation, str):
|
|
41
|
-
return eval(annotation, callable.__globals__)
|
|
49
|
+
return typing.cast(type, eval(annotation, callable.__globals__))
|
|
42
50
|
else:
|
|
43
51
|
return annotation
|
|
44
52
|
|
|
@@ -54,7 +62,7 @@ class HTTPMethod(enum.Enum):
|
|
|
54
62
|
PATCH = "PATCH"
|
|
55
63
|
|
|
56
64
|
|
|
57
|
-
OperationParameter =
|
|
65
|
+
OperationParameter = tuple[str, type]
|
|
58
66
|
|
|
59
67
|
|
|
60
68
|
class ValidationError(TypeError):
|
|
@@ -87,15 +95,15 @@ class EndpointOperation:
|
|
|
87
95
|
func_name: str
|
|
88
96
|
func_ref: Callable[..., Any]
|
|
89
97
|
route: Optional[str]
|
|
90
|
-
path_params:
|
|
91
|
-
query_params:
|
|
98
|
+
path_params: list[OperationParameter]
|
|
99
|
+
query_params: list[OperationParameter]
|
|
92
100
|
request_param: Optional[OperationParameter]
|
|
93
101
|
event_type: Optional[type]
|
|
94
102
|
response_type: type
|
|
95
103
|
http_method: HTTPMethod
|
|
96
104
|
public: bool
|
|
97
|
-
request_examples: Optional[
|
|
98
|
-
response_examples: Optional[
|
|
105
|
+
request_examples: Optional[list[Any]] = None
|
|
106
|
+
response_examples: Optional[list[Any]] = None
|
|
99
107
|
|
|
100
108
|
def get_route(self) -> str:
|
|
101
109
|
if self.route is not None:
|
|
@@ -108,9 +116,9 @@ class EndpointOperation:
|
|
|
108
116
|
|
|
109
117
|
|
|
110
118
|
class _FormatParameterExtractor:
|
|
111
|
-
"A visitor to
|
|
119
|
+
"A visitor to extract parameters in a format string."
|
|
112
120
|
|
|
113
|
-
keys:
|
|
121
|
+
keys: list[str]
|
|
114
122
|
|
|
115
123
|
def __init__(self) -> None:
|
|
116
124
|
self.keys = []
|
|
@@ -120,13 +128,13 @@ class _FormatParameterExtractor:
|
|
|
120
128
|
return None
|
|
121
129
|
|
|
122
130
|
|
|
123
|
-
def _get_route_parameters(route: str) ->
|
|
131
|
+
def _get_route_parameters(route: str) -> list[str]:
|
|
124
132
|
extractor = _FormatParameterExtractor()
|
|
125
133
|
route.format_map(extractor)
|
|
126
134
|
return extractor.keys
|
|
127
135
|
|
|
128
136
|
|
|
129
|
-
def _get_endpoint_functions(endpoint: type, prefixes:
|
|
137
|
+
def _get_endpoint_functions(endpoint: type, prefixes: list[str]) -> Iterator[tuple[str, str, str, Callable[..., Any]]]:
|
|
130
138
|
if not inspect.isclass(endpoint):
|
|
131
139
|
raise ValidationError(f"object is not a class type: {endpoint}")
|
|
132
140
|
|
|
@@ -151,7 +159,7 @@ def _get_defining_class(member_fn: str, derived_cls: type) -> type:
|
|
|
151
159
|
raise ValidationError(f"cannot find defining class for {member_fn} in {derived_cls}")
|
|
152
160
|
|
|
153
161
|
|
|
154
|
-
def get_endpoint_operations(endpoint: type, use_examples: bool = True) ->
|
|
162
|
+
def get_endpoint_operations(endpoint: type, use_examples: bool = True) -> list[EndpointOperation]:
|
|
155
163
|
"""
|
|
156
164
|
Extracts a list of member functions in a class eligible for HTTP interface binding.
|
|
157
165
|
|
|
@@ -247,7 +255,8 @@ def get_endpoint_operations(endpoint: type, use_examples: bool = True) -> List[E
|
|
|
247
255
|
if request_param is not None:
|
|
248
256
|
param = (param_name, param_type)
|
|
249
257
|
raise ValidationError(
|
|
250
|
-
f"only a single composite type is permitted in a signature but multiple composite types found in function '{func_name}':
|
|
258
|
+
f"only a single composite type is permitted in a signature but multiple composite types found in function '{func_name}': "
|
|
259
|
+
f"{request_param} and {param}"
|
|
251
260
|
)
|
|
252
261
|
|
|
253
262
|
# composite types are read from body
|
|
@@ -310,7 +319,7 @@ def get_endpoint_operations(endpoint: type, use_examples: bool = True) -> List[E
|
|
|
310
319
|
return result
|
|
311
320
|
|
|
312
321
|
|
|
313
|
-
def get_endpoint_events(endpoint: type) ->
|
|
322
|
+
def get_endpoint_events(endpoint: type) -> dict[str, type]:
|
|
314
323
|
results = {}
|
|
315
324
|
|
|
316
325
|
for decl in typing.get_type_hints(endpoint).values():
|
pyopenapi/options.py
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generate an OpenAPI specification from a Python class definition
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2025, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/pyopenapi
|
|
7
|
+
"""
|
|
8
|
+
|
|
1
9
|
import dataclasses
|
|
2
10
|
from dataclasses import dataclass
|
|
3
11
|
from http import HTTPStatus
|
|
4
|
-
from typing import Callable, ClassVar,
|
|
12
|
+
from typing import Callable, ClassVar, Optional, Union
|
|
5
13
|
|
|
6
|
-
from .specification import Info, SecurityScheme
|
|
14
|
+
from .specification import Info, SecurityScheme, Server
|
|
7
15
|
from .specification import SecuritySchemeAPI as SecuritySchemeAPI
|
|
8
16
|
from .specification import SecuritySchemeHTTP as SecuritySchemeHTTP
|
|
9
17
|
from .specification import SecuritySchemeOpenIDConnect as SecuritySchemeOpenIDConnect
|
|
10
|
-
from .specification import Server
|
|
11
18
|
|
|
12
19
|
HTTPStatusCode = Union[HTTPStatus, int, str]
|
|
13
20
|
|
|
@@ -30,17 +37,17 @@ class Options:
|
|
|
30
37
|
|
|
31
38
|
server: Server
|
|
32
39
|
info: Info
|
|
33
|
-
version:
|
|
40
|
+
version: tuple[int, int, int] = (3, 1, 0)
|
|
34
41
|
default_security_scheme: Optional[SecurityScheme] = None
|
|
35
|
-
extra_types: Union[
|
|
42
|
+
extra_types: Union[list[type], dict[str, list[type]], None] = None
|
|
36
43
|
use_examples: bool = True
|
|
37
|
-
success_responses:
|
|
38
|
-
error_responses:
|
|
44
|
+
success_responses: dict[type, HTTPStatusCode] = dataclasses.field(default_factory=dict)
|
|
45
|
+
error_responses: dict[type, HTTPStatusCode] = dataclasses.field(default_factory=dict)
|
|
39
46
|
error_wrapper: bool = False
|
|
40
47
|
property_description_fun: Optional[Callable[[type, str, str], str]] = None
|
|
41
|
-
captions: Optional[
|
|
48
|
+
captions: Optional[dict[str, str]] = None
|
|
42
49
|
|
|
43
|
-
default_captions: ClassVar[
|
|
50
|
+
default_captions: ClassVar[dict[str, str]] = {
|
|
44
51
|
"Operations": "Operations",
|
|
45
52
|
"Types": "Types",
|
|
46
53
|
"Events": "Events",
|
pyopenapi/proxy.py
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generate an OpenAPI specification from a Python class definition
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2025, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/pyopenapi
|
|
7
|
+
"""
|
|
8
|
+
|
|
1
9
|
import json
|
|
2
|
-
from typing import Any, Callable,
|
|
10
|
+
from typing import Any, Callable, Optional, TypeVar
|
|
3
11
|
|
|
4
12
|
import aiohttp
|
|
13
|
+
from strong_typing.inspection import get_signature
|
|
5
14
|
from strong_typing.serialization import json_to_object, object_to_json
|
|
6
15
|
|
|
7
|
-
from .operations import EndpointOperation, HTTPMethod, get_endpoint_operations
|
|
16
|
+
from .operations import EndpointOperation, HTTPMethod, get_endpoint_operations
|
|
8
17
|
|
|
9
18
|
|
|
10
19
|
async def make_request(
|
|
11
20
|
http_method: HTTPMethod,
|
|
12
21
|
server: str,
|
|
13
22
|
path: str,
|
|
14
|
-
query:
|
|
23
|
+
query: dict[str, str],
|
|
15
24
|
data: Optional[str],
|
|
16
|
-
) ->
|
|
25
|
+
) -> tuple[int, str]:
|
|
17
26
|
"Makes an asynchronous HTTP request and returns the response."
|
|
18
27
|
|
|
19
28
|
headers = {"Accept": "application/json"}
|
|
@@ -97,7 +106,7 @@ class OperationProxy:
|
|
|
97
106
|
try:
|
|
98
107
|
s = json.loads(response)
|
|
99
108
|
except json.JSONDecodeError:
|
|
100
|
-
raise ProxyInvokeError(f"response body is not well-formed JSON:\n{response}")
|
|
109
|
+
raise ProxyInvokeError(f"response body is not well-formed JSON:\n{response}") from None
|
|
101
110
|
|
|
102
111
|
return json_to_object(self.op.response_type, s)
|
|
103
112
|
else:
|
|
@@ -118,7 +127,7 @@ def _get_operation_proxy(op: EndpointOperation) -> Callable[..., Any]:
|
|
|
118
127
|
T = TypeVar("T")
|
|
119
128
|
|
|
120
129
|
|
|
121
|
-
def make_proxy_class(api:
|
|
130
|
+
def make_proxy_class(api: type[T]) -> type[T]:
|
|
122
131
|
"""
|
|
123
132
|
Creates a proxy class for calling an HTTP REST API.
|
|
124
133
|
|
pyopenapi/specification.py
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generate an OpenAPI specification from a Python class definition
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2025, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/pyopenapi
|
|
7
|
+
"""
|
|
8
|
+
|
|
1
9
|
import dataclasses
|
|
2
10
|
import enum
|
|
3
11
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Any, ClassVar,
|
|
12
|
+
from typing import Any, ClassVar, Optional, Union
|
|
5
13
|
|
|
6
|
-
from strong_typing.
|
|
7
|
-
from strong_typing.
|
|
14
|
+
from strong_typing.core import JsonType as JsonType
|
|
15
|
+
from strong_typing.core import Schema, StrictJsonType
|
|
8
16
|
|
|
9
17
|
URL = str
|
|
10
18
|
|
|
@@ -68,12 +76,12 @@ class Info:
|
|
|
68
76
|
class MediaType:
|
|
69
77
|
schema: Optional[SchemaOrRef] = None
|
|
70
78
|
example: Optional[Any] = None
|
|
71
|
-
examples: Optional[
|
|
79
|
+
examples: Optional[dict[str, Union["Example", ExampleRef]]] = None
|
|
72
80
|
|
|
73
81
|
|
|
74
82
|
@dataclass
|
|
75
83
|
class RequestBody:
|
|
76
|
-
content:
|
|
84
|
+
content: dict[str, MediaType]
|
|
77
85
|
description: Optional[str] = None
|
|
78
86
|
required: Optional[bool] = None
|
|
79
87
|
|
|
@@ -81,7 +89,7 @@ class RequestBody:
|
|
|
81
89
|
@dataclass
|
|
82
90
|
class Response:
|
|
83
91
|
description: str
|
|
84
|
-
content: Optional[
|
|
92
|
+
content: Optional[dict[str, MediaType]] = None
|
|
85
93
|
|
|
86
94
|
|
|
87
95
|
@enum.unique
|
|
@@ -104,15 +112,15 @@ class Parameter:
|
|
|
104
112
|
|
|
105
113
|
@dataclass
|
|
106
114
|
class Operation:
|
|
107
|
-
responses:
|
|
108
|
-
tags: Optional[
|
|
115
|
+
responses: dict[str, Union[Response, ResponseRef]]
|
|
116
|
+
tags: Optional[list[str]] = None
|
|
109
117
|
summary: Optional[str] = None
|
|
110
118
|
description: Optional[str] = None
|
|
111
119
|
operationId: Optional[str] = None
|
|
112
|
-
parameters: Optional[
|
|
120
|
+
parameters: Optional[list[Parameter]] = None
|
|
113
121
|
requestBody: Optional[RequestBody] = None
|
|
114
|
-
callbacks: Optional[
|
|
115
|
-
security: Optional[
|
|
122
|
+
callbacks: Optional[dict[str, "Callback"]] = None
|
|
123
|
+
security: Optional[list["SecurityRequirement"]] = None
|
|
116
124
|
|
|
117
125
|
|
|
118
126
|
@dataclass
|
|
@@ -138,7 +146,7 @@ class PathItem:
|
|
|
138
146
|
|
|
139
147
|
|
|
140
148
|
# maps run-time expressions such as "$request.body#/url" to path items
|
|
141
|
-
Callback =
|
|
149
|
+
Callback = dict[str, PathItem]
|
|
142
150
|
|
|
143
151
|
|
|
144
152
|
@dataclass
|
|
@@ -202,17 +210,17 @@ class SecuritySchemeOpenIDConnect(SecurityScheme):
|
|
|
202
210
|
|
|
203
211
|
@dataclass
|
|
204
212
|
class Components:
|
|
205
|
-
schemas: Optional[
|
|
206
|
-
responses: Optional[
|
|
207
|
-
parameters: Optional[
|
|
208
|
-
examples: Optional[
|
|
209
|
-
requestBodies: Optional[
|
|
210
|
-
securitySchemes: Optional[
|
|
211
|
-
callbacks: Optional[
|
|
213
|
+
schemas: Optional[dict[str, Schema]] = None
|
|
214
|
+
responses: Optional[dict[str, Response]] = None
|
|
215
|
+
parameters: Optional[dict[str, Parameter]] = None
|
|
216
|
+
examples: Optional[dict[str, Example]] = None
|
|
217
|
+
requestBodies: Optional[dict[str, RequestBody]] = None
|
|
218
|
+
securitySchemes: Optional[dict[str, SecurityScheme]] = None
|
|
219
|
+
callbacks: Optional[dict[str, Callback]] = None
|
|
212
220
|
|
|
213
221
|
|
|
214
222
|
SecurityScope = str
|
|
215
|
-
SecurityRequirement =
|
|
223
|
+
SecurityRequirement = dict[str, list[SecurityScope]]
|
|
216
224
|
|
|
217
225
|
|
|
218
226
|
@dataclass
|
|
@@ -231,7 +239,7 @@ class TagGroup:
|
|
|
231
239
|
"""
|
|
232
240
|
|
|
233
241
|
name: str
|
|
234
|
-
tags:
|
|
242
|
+
tags: list[str]
|
|
235
243
|
|
|
236
244
|
|
|
237
245
|
@dataclass
|
|
@@ -244,10 +252,10 @@ class Document:
|
|
|
244
252
|
|
|
245
253
|
openapi: str
|
|
246
254
|
info: Info
|
|
247
|
-
servers:
|
|
248
|
-
paths:
|
|
255
|
+
servers: list[Server]
|
|
256
|
+
paths: dict[str, PathItem]
|
|
249
257
|
jsonSchemaDialect: Optional[str] = None
|
|
250
258
|
components: Optional[Components] = None
|
|
251
|
-
security: Optional[
|
|
252
|
-
tags: Optional[
|
|
253
|
-
tagGroups: Optional[
|
|
259
|
+
security: Optional[list[SecurityRequirement]] = None
|
|
260
|
+
tags: Optional[list[Tag]] = None
|
|
261
|
+
tagGroups: Optional[list[TagGroup]] = None
|
pyopenapi/utility.py
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generate an OpenAPI specification from a Python class definition
|
|
3
|
+
|
|
4
|
+
Copyright 2022-2025, Levente Hunyadi
|
|
5
|
+
|
|
6
|
+
:see: https://github.com/hunyadi/pyopenapi
|
|
7
|
+
"""
|
|
8
|
+
|
|
1
9
|
import importlib.resources
|
|
2
10
|
import json
|
|
3
|
-
import sys
|
|
4
11
|
import typing
|
|
5
12
|
from typing import TextIO
|
|
6
13
|
|
|
7
|
-
from strong_typing.
|
|
14
|
+
from strong_typing.core import StrictJsonType
|
|
15
|
+
from strong_typing.serialization import object_to_json
|
|
8
16
|
|
|
9
17
|
from .generator import Generator
|
|
10
18
|
from .options import Options
|
|
@@ -94,12 +102,8 @@ class Specification:
|
|
|
94
102
|
:param pretty_print: Whether to use line indents to beautify the JSON string in the HTML file.
|
|
95
103
|
"""
|
|
96
104
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
html_template = html_template_file.read()
|
|
100
|
-
else:
|
|
101
|
-
with importlib.resources.open_text(__package__, "template.html", encoding="utf-8", errors="strict") as html_template_file:
|
|
102
|
-
html_template = html_template_file.read()
|
|
105
|
+
with importlib.resources.files(__package__).joinpath("template.html").open(encoding="utf-8", errors="strict") as html_template_file:
|
|
106
|
+
html_template = html_template_file.read()
|
|
103
107
|
|
|
104
108
|
html = html_template.replace(
|
|
105
109
|
"{ /* OPENAPI_SPECIFICATION */ }",
|
|
@@ -1,30 +1,32 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-openapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Generate an OpenAPI specification from a Python class definition
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Author-email: Levente Hunyadi <hunyadi@gmail.com>
|
|
6
|
+
Maintainer-email: Levente Hunyadi <hunyadi@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/hunyadi/pyopenapi
|
|
9
|
+
Project-URL: Source, https://github.com/hunyadi/pyopenapi
|
|
10
|
+
Keywords: openapi3,openapi,redoc,swagger,json-schema-generator,dataclasses,type-inspection
|
|
9
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
12
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
13
|
Classifier: Operating System :: OS Independent
|
|
13
14
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
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
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
21
|
Classifier: Topic :: Software Development :: Code Generators
|
|
21
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
23
|
Classifier: Typing :: Typed
|
|
23
|
-
Requires-Python: >=3.
|
|
24
|
+
Requires-Python: >=3.9
|
|
24
25
|
Description-Content-Type: text/markdown
|
|
25
26
|
License-File: LICENSE
|
|
26
|
-
Requires-Dist: aiohttp>=3.
|
|
27
|
-
Requires-Dist:
|
|
27
|
+
Requires-Dist: aiohttp>=3.12
|
|
28
|
+
Requires-Dist: json_strong_typing>=0.3.9
|
|
29
|
+
Dynamic: license-file
|
|
28
30
|
|
|
29
31
|
# Generate an OpenAPI specification from a Python class
|
|
30
32
|
|
|
@@ -35,7 +37,7 @@ Requires-Dist: json-strong-typing>=0.3.7
|
|
|
35
37
|
* supports standard and asynchronous functions (`async def`)
|
|
36
38
|
* maps function name prefixes such as `get_` or `create_` to HTTP GET, POST, PUT, DELETE, PATCH
|
|
37
39
|
* handles both simple and composite types (`int`, `str`, `Enum`, `@dataclass`)
|
|
38
|
-
* handles generic types (`
|
|
40
|
+
* handles generic types (`list[T]`, `dict[K, V]`, `Optional[T]`, `Union[T1, T2, T3]`)
|
|
39
41
|
* maps Python positional-only and keyword-only arguments (of simple types) to path and query parameters, respectively
|
|
40
42
|
* maps composite types to HTTP request body
|
|
41
43
|
* supports user-defined routes, request and response samples with decorator `@webmethod`
|
|
@@ -88,7 +90,7 @@ Let's take a look at the definition of a simple endpoint called `JobManagement`:
|
|
|
88
90
|
|
|
89
91
|
```python
|
|
90
92
|
class JobManagement:
|
|
91
|
-
def create_job(self, items:
|
|
93
|
+
def create_job(self, items: list[URL]) -> uuid.UUID:
|
|
92
94
|
...
|
|
93
95
|
|
|
94
96
|
def get_job(self, job_id: uuid.UUID, /, format: Format) -> Job:
|
|
@@ -127,7 +129,7 @@ The custom path must have placeholders for all positional-only parameters in the
|
|
|
127
129
|
|
|
128
130
|
### Documenting operations
|
|
129
131
|
|
|
130
|
-
Use Python ReST (ReStructured Text) doc-strings to attach documentation to operations:
|
|
132
|
+
Use Python ReST (ReStructured Text) doc-strings to attach documentation to operations:
|
|
131
133
|
|
|
132
134
|
```python
|
|
133
135
|
def get_job(self, job_id: uuid.UUID, /, format: Format) -> Job:
|
|
@@ -170,7 +172,7 @@ The Python objects in `request_examples` and `response_examples` are translated
|
|
|
170
172
|
|
|
171
173
|
### Mapping function name prefixes to HTTP verbs
|
|
172
174
|
|
|
173
|
-
The following table identifies which function name prefixes map to which HTTP verbs:
|
|
175
|
+
The following table identifies which function name prefixes map to which HTTP verbs:
|
|
174
176
|
|
|
175
177
|
| Prefix | HTTP verb |
|
|
176
178
|
| ------ | ----------- |
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
pyopenapi/__init__.py,sha256=6XdaFzQkGdBLTQj0lv-7FyuMN8gFpLKctsA1nIW4pSU,345
|
|
2
|
+
pyopenapi/__main__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
pyopenapi/decorators.py,sha256=wYZNlZ7diaT5tnr8BsdS7aYpgFTndhfknqbhrU3jR4g,2065
|
|
4
|
+
pyopenapi/generator.py,sha256=HclcfyADHymH3be5w3Xa39MHe0xLsU28kWJhCTLmrG4,23971
|
|
5
|
+
pyopenapi/metadata.py,sha256=-w5C5qiphvnuOkyh9QEUURg7OT-5UffRowjAwmZJkc8,916
|
|
6
|
+
pyopenapi/operations.py,sha256=oprIalF_1XD1-7AjfmJQQeC3xRLdB8oeLlMEhjO2wGc,13529
|
|
7
|
+
pyopenapi/options.py,sha256=S6RWhpZfWrrZBqXzIsPwlVKOkvaI7eyDDUBqGICTlZo,2906
|
|
8
|
+
pyopenapi/proxy.py,sha256=ExDvESQdULTqfnunmfRKtkdjGbcLt5v9yaHd2h9HHlo,4279
|
|
9
|
+
pyopenapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
pyopenapi/specification.py,sha256=ZKQbhl0mhf5ceHan-2ymzt-tba3MhMH-nGsFQ_07XBE,6114
|
|
11
|
+
pyopenapi/template.html,sha256=hmZsSnN3awQcwlN6u-j06qrw_qYzWqYBLC8uPt_x17A,1234
|
|
12
|
+
pyopenapi/utility.py,sha256=Cdub8VUrklyUvZ9cmahDmIYKq4d6X7IT--rMP_ogd_4,3553
|
|
13
|
+
python_openapi-0.2.0.dist-info/licenses/LICENSE,sha256=sNQ9jvMoMB8FfhB7JbkJYLr8SWSO6jrYvcS-mRL485w,1118
|
|
14
|
+
python_openapi-0.2.0.dist-info/METADATA,sha256=eAVEMvh6P1YF-HWODkAXv7p0N5mOFQJ_6mlUP5IRWSg,10499
|
|
15
|
+
python_openapi-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
16
|
+
python_openapi-0.2.0.dist-info/top_level.txt,sha256=3M9FA79QfjyZyTipZP5fDiMcrRdcbSl7lW-iaq5RIRQ,10
|
|
17
|
+
python_openapi-0.2.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
18
|
+
python_openapi-0.2.0.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
pyopenapi/__init__.py,sha256=4HS5YmQwTzehGw1lUPkZmlKqnL05kAx7aKRq3Q1Lt_4,2020
|
|
2
|
-
pyopenapi/__main__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
pyopenapi/generator.py,sha256=DVR4n06TmSghR14_9tLPX6yCX1_Arfbh2pU7UvBD7C8,23807
|
|
4
|
-
pyopenapi/metadata.py,sha256=RnAfH79SJVE7-3HWJmdXAm_Kc5L5-4gutyh6xOZc8Fs,766
|
|
5
|
-
pyopenapi/operations.py,sha256=b2wiW93chm9b3pOOXrk4wip1DdjLsB7tgzszyBg2RcU,13318
|
|
6
|
-
pyopenapi/options.py,sha256=uc-nnyGJeaI3qHWvtDzDz-JBInhETEySF-SplyOr9_w,2795
|
|
7
|
-
pyopenapi/proxy.py,sha256=xEO541yqA6ed8jCq01b339_JW1pDSrMN_vxKYk_ZhBg,4096
|
|
8
|
-
pyopenapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
pyopenapi/specification.py,sha256=qr9K9DyDIW6B-MkrAS26_op1Qj3eosNyD92XimtLRn4,5974
|
|
10
|
-
pyopenapi/template.html,sha256=hmZsSnN3awQcwlN6u-j06qrw_qYzWqYBLC8uPt_x17A,1234
|
|
11
|
-
pyopenapi/utility.py,sha256=ZLe1Ss0MGBRD7fL0uSu7nWqUlMIIfAK2supDbN5-uCU,3625
|
|
12
|
-
python_openapi-0.1.10.dist-info/LICENSE,sha256=sNQ9jvMoMB8FfhB7JbkJYLr8SWSO6jrYvcS-mRL485w,1118
|
|
13
|
-
python_openapi-0.1.10.dist-info/METADATA,sha256=_g7APIZO9ZbYMkYghCx0RoRzlIzhkWt2CU1KD5Gmcr8,10305
|
|
14
|
-
python_openapi-0.1.10.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
|
15
|
-
python_openapi-0.1.10.dist-info/top_level.txt,sha256=3M9FA79QfjyZyTipZP5fDiMcrRdcbSl7lW-iaq5RIRQ,10
|
|
16
|
-
python_openapi-0.1.10.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
17
|
-
python_openapi-0.1.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|