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.
Files changed (26) hide show
  1. {python-openapi-0.1.8 → python_openapi-0.1.10}/LICENSE +1 -1
  2. {python-openapi-0.1.8 → python_openapi-0.1.10}/PKG-INFO +6 -1
  3. {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/__init__.py +6 -10
  4. {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/generator.py +153 -133
  5. {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/operations.py +19 -45
  6. {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/options.py +12 -15
  7. {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/proxy.py +11 -22
  8. python_openapi-0.1.10/pyopenapi/py.typed +0 -0
  9. {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/specification.py +14 -11
  10. {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/utility.py +21 -22
  11. {python-openapi-0.1.8 → python_openapi-0.1.10}/pyproject.toml +3 -0
  12. {python-openapi-0.1.8 → python_openapi-0.1.10}/python_openapi.egg-info/PKG-INFO +6 -1
  13. {python-openapi-0.1.8 → python_openapi-0.1.10}/python_openapi.egg-info/SOURCES.txt +4 -1
  14. python_openapi-0.1.10/python_openapi.egg-info/requires.txt +2 -0
  15. {python-openapi-0.1.8 → python_openapi-0.1.10}/setup.cfg +8 -3
  16. python_openapi-0.1.10/tests/test_openapi.py +163 -0
  17. python_openapi-0.1.10/tests/test_proxy.py +70 -0
  18. python-openapi-0.1.8/python_openapi.egg-info/requires.txt +0 -2
  19. {python-openapi-0.1.8 → python_openapi-0.1.10}/README.md +0 -0
  20. {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/__main__.py +0 -0
  21. {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/metadata.py +0 -0
  22. {python-openapi-0.1.8 → python_openapi-0.1.10}/pyopenapi/template.html +0 -0
  23. {python-openapi-0.1.8 → python_openapi-0.1.10}/python_openapi.egg-info/dependency_links.txt +0 -0
  24. {python-openapi-0.1.8 → python_openapi-0.1.10}/python_openapi.egg-info/top_level.txt +0 -0
  25. {python-openapi-0.1.8 → python_openapi-0.1.10}/python_openapi.egg-info/zip-safe +0 -0
  26. {python-openapi-0.1.8 → python_openapi-0.1.10}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021-2022 Levente Hunyadi
3
+ Copyright (c) 2021-2025 Levente Hunyadi
4
4
  Copyright (c) 2021-2022 Instructure Inc.
5
5
 
6
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-openapi
3
- Version: 0.1.8
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.8"
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
- from typing import Any, Dict, Set, Union
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
- JsonSchemaGenerator,
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
- EndpointOperation,
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
- SchemaOrRef = Union[Schema, SchemaRef]
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 # type: ignore
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
- self, examples: Optional[List[Any]] = None
167
- ) -> Optional[Dict[str, Union[Example, ExampleRef]]]:
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
- if examples is None:
170
- return None
194
+ def _build_example(self, example: Any) -> Any:
195
+ "Creates a single example for a media type."
171
196
 
172
- if self.sample_transformer:
173
- sample_transformer: Callable[[JsonType], JsonType] = self.sample_transformer # type: ignore
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
- return {
178
- str(example): Example(value=sample_transformer(object_to_json(example)))
179
- for example in examples
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
- extra_tags[category_name] = tag_list
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: parse_type(item).short_description
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
- item.raise_type: item.description for item in doc_string.raises.values()
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 = lambda schema: {
446
- "type": "object",
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="3.1.0",
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(