python-openapi 0.2.0__tar.gz → 0.3.0__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 (32) hide show
  1. {python_openapi-0.2.0 → python_openapi-0.3.0}/LICENSE +1 -1
  2. {python_openapi-0.2.0/python_openapi.egg-info → python_openapi-0.3.0}/PKG-INFO +16 -7
  3. {python_openapi-0.2.0 → python_openapi-0.3.0}/README.md +2 -2
  4. {python_openapi-0.2.0 → python_openapi-0.3.0}/pyopenapi/__init__.py +3 -3
  5. {python_openapi-0.2.0 → python_openapi-0.3.0}/pyopenapi/decorators.py +16 -9
  6. {python_openapi-0.2.0 → python_openapi-0.3.0}/pyopenapi/generator.py +60 -58
  7. {python_openapi-0.2.0 → python_openapi-0.3.0}/pyopenapi/metadata.py +7 -5
  8. {python_openapi-0.2.0 → python_openapi-0.3.0}/pyopenapi/operations.py +33 -27
  9. {python_openapi-0.2.0 → python_openapi-0.3.0}/pyopenapi/options.py +9 -9
  10. {python_openapi-0.2.0 → python_openapi-0.3.0}/pyopenapi/proxy.py +7 -6
  11. {python_openapi-0.2.0 → python_openapi-0.3.0}/pyopenapi/specification.py +74 -63
  12. {python_openapi-0.2.0 → python_openapi-0.3.0}/pyopenapi/utility.py +1 -1
  13. {python_openapi-0.2.0 → python_openapi-0.3.0}/pyproject.toml +17 -4
  14. {python_openapi-0.2.0 → python_openapi-0.3.0/python_openapi.egg-info}/PKG-INFO +16 -7
  15. {python_openapi-0.2.0 → python_openapi-0.3.0}/python_openapi.egg-info/SOURCES.txt +0 -1
  16. python_openapi-0.3.0/python_openapi.egg-info/requires.txt +13 -0
  17. python_openapi-0.3.0/setup.py +12 -0
  18. {python_openapi-0.2.0 → python_openapi-0.3.0}/tests/endpoint.py +10 -3
  19. {python_openapi-0.2.0 → python_openapi-0.3.0}/tests/test_openapi.py +6 -8
  20. {python_openapi-0.2.0 → python_openapi-0.3.0}/tests/test_proxy.py +2 -2
  21. python_openapi-0.2.0/pyopenapi/__main__.py +0 -0
  22. python_openapi-0.2.0/python_openapi.egg-info/requires.txt +0 -2
  23. python_openapi-0.2.0/setup.py +0 -4
  24. {python_openapi-0.2.0 → python_openapi-0.3.0}/MANIFEST.in +0 -0
  25. {python_openapi-0.2.0 → python_openapi-0.3.0}/pyopenapi/py.typed +0 -0
  26. {python_openapi-0.2.0 → python_openapi-0.3.0}/pyopenapi/template.html +0 -0
  27. {python_openapi-0.2.0 → python_openapi-0.3.0}/python_openapi.egg-info/dependency_links.txt +0 -0
  28. {python_openapi-0.2.0 → python_openapi-0.3.0}/python_openapi.egg-info/top_level.txt +0 -0
  29. {python_openapi-0.2.0 → python_openapi-0.3.0}/python_openapi.egg-info/zip-safe +0 -0
  30. {python_openapi-0.2.0 → python_openapi-0.3.0}/setup.cfg +0 -0
  31. {python_openapi-0.2.0 → python_openapi-0.3.0}/tests/__init__.py +0 -0
  32. {python_openapi-0.2.0 → python_openapi-0.3.0}/tests/endpoint.md +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021-2025 Levente Hunyadi
3
+ Copyright (c) 2021-2026 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.4
2
2
  Name: python-openapi
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Generate an OpenAPI specification from a Python class definition
5
5
  Author-email: Levente Hunyadi <hunyadi@gmail.com>
6
6
  Maintainer-email: Levente Hunyadi <hunyadi@gmail.com>
@@ -12,20 +12,29 @@ Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.9
16
15
  Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
20
  Classifier: Programming Language :: Python :: 3 :: Only
21
21
  Classifier: Topic :: Software Development :: Code Generators
22
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Classifier: Typing :: Typed
24
- Requires-Python: >=3.9
24
+ Requires-Python: >=3.10
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
- Requires-Dist: aiohttp>=3.12
28
- Requires-Dist: json_strong_typing>=0.3.9
27
+ Requires-Dist: json_strong_typing>=0.4
28
+ Provides-Extra: proxy
29
+ Requires-Dist: aiohttp>=3.13; extra == "proxy"
30
+ Provides-Extra: dev
31
+ Requires-Dist: build; extra == "dev"
32
+ Requires-Dist: mypy; extra == "dev"
33
+ Requires-Dist: ruff; extra == "dev"
34
+ Requires-Dist: PyYAML; extra == "dev"
35
+ Requires-Dist: Pygments; extra == "dev"
36
+ Requires-Dist: types-PyYAML; extra == "dev"
37
+ Requires-Dist: types-Pygments; extra == "dev"
29
38
  Dynamic: license-file
30
39
 
31
40
  # Generate an OpenAPI specification from a Python class
@@ -37,7 +46,7 @@ Dynamic: license-file
37
46
  * supports standard and asynchronous functions (`async def`)
38
47
  * maps function name prefixes such as `get_` or `create_` to HTTP GET, POST, PUT, DELETE, PATCH
39
48
  * handles both simple and composite types (`int`, `str`, `Enum`, `@dataclass`)
40
- * handles generic types (`list[T]`, `dict[K, V]`, `Optional[T]`, `Union[T1, T2, T3]`)
49
+ * handles generic types (`list[T]`, `dict[K, V]`, `T | None`, `T1 | T2 | T3`)
41
50
  * maps Python positional-only and keyword-only arguments (of simple types) to path and query parameters, respectively
42
51
  * maps composite types to HTTP request body
43
52
  * supports user-defined routes, request and response samples with decorator `@webmethod`
@@ -162,7 +171,7 @@ OpenAPI supports specifying [examples](https://spec.openapis.org/oas/latest.html
162
171
  Teacher("Vacska", "Mati", "Négyszögletű Kerek Erdő"),
163
172
  ],
164
173
  )
165
- def get_member_by_name(self, family: str, given: str, /) -> Union[Student, Teacher]:
174
+ def get_member_by_name(self, family: str, given: str, /) -> Student | Teacher:
166
175
  ...
167
176
  ```
168
177
 
@@ -7,7 +7,7 @@
7
7
  * supports standard and asynchronous functions (`async def`)
8
8
  * maps function name prefixes such as `get_` or `create_` to HTTP GET, POST, PUT, DELETE, PATCH
9
9
  * handles both simple and composite types (`int`, `str`, `Enum`, `@dataclass`)
10
- * handles generic types (`list[T]`, `dict[K, V]`, `Optional[T]`, `Union[T1, T2, T3]`)
10
+ * handles generic types (`list[T]`, `dict[K, V]`, `T | None`, `T1 | T2 | T3`)
11
11
  * maps Python positional-only and keyword-only arguments (of simple types) to path and query parameters, respectively
12
12
  * maps composite types to HTTP request body
13
13
  * supports user-defined routes, request and response samples with decorator `@webmethod`
@@ -132,7 +132,7 @@ OpenAPI supports specifying [examples](https://spec.openapis.org/oas/latest.html
132
132
  Teacher("Vacska", "Mati", "Négyszögletű Kerek Erdő"),
133
133
  ],
134
134
  )
135
- def get_member_by_name(self, family: str, given: str, /) -> Union[Student, Teacher]:
135
+ def get_member_by_name(self, family: str, given: str, /) -> Student | Teacher:
136
136
  ...
137
137
  ```
138
138
 
@@ -1,14 +1,14 @@
1
1
  """
2
2
  Generate an OpenAPI specification from a Python class definition
3
3
 
4
- Copyright 2022-2025, Levente Hunyadi
4
+ Copyright 2021-2026, Levente Hunyadi
5
5
 
6
6
  :see: https://github.com/hunyadi/pyopenapi
7
7
  """
8
8
 
9
- __version__ = "0.2.0"
9
+ __version__ = "0.3.0"
10
10
  __author__ = "Levente Hunyadi"
11
- __copyright__ = "Copyright 2022-2025, Levente Hunyadi"
11
+ __copyright__ = "Copyright 2021-2026, Levente Hunyadi"
12
12
  __license__ = "MIT"
13
13
  __maintainer__ = "Levente Hunyadi"
14
14
  __status__ = "Production"
@@ -1,12 +1,12 @@
1
1
  """
2
2
  Generate an OpenAPI specification from a Python class definition
3
3
 
4
- Copyright 2022-2025, Levente Hunyadi
4
+ Copyright 2021-2026, Levente Hunyadi
5
5
 
6
6
  :see: https://github.com/hunyadi/pyopenapi
7
7
  """
8
8
 
9
- from typing import Any, Callable, Optional, TypeVar
9
+ from typing import Any, Callable, TypeVar
10
10
 
11
11
  from .metadata import WebMethod
12
12
  from .options import * # noqa: F403
@@ -16,12 +16,13 @@ F = TypeVar("F", bound=Callable[..., Any])
16
16
 
17
17
 
18
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,
19
+ route: str | None = None,
20
+ public: bool = False,
21
+ deprecated: bool = False,
22
+ request_example: Any | None = None,
23
+ response_example: Any | None = None,
24
+ request_examples: list[Any] | None = None,
25
+ response_examples: list[Any] | None = None,
25
26
  ) -> Callable[[F], F]:
26
27
  """
27
28
  Decorator that supplies additional metadata to an endpoint operation function.
@@ -45,7 +46,13 @@ def webmethod(
45
46
  response_examples = [response_example]
46
47
 
47
48
  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
+ cls.__webmethod__ = WebMethod( # type: ignore[attr-defined]
50
+ route=route,
51
+ public=public or False,
52
+ deprecated=deprecated or False,
53
+ request_examples=request_examples,
54
+ response_examples=response_examples,
55
+ )
49
56
  return cls
50
57
 
51
58
  return wrap
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Generate an OpenAPI specification from a Python class definition
3
3
 
4
- Copyright 2022-2025, Levente Hunyadi
4
+ Copyright 2021-2026, Levente Hunyadi
5
5
 
6
6
  :see: https://github.com/hunyadi/pyopenapi
7
7
  """
@@ -9,14 +9,16 @@ Copyright 2022-2025, Levente Hunyadi
9
9
  import dataclasses
10
10
  import hashlib
11
11
  import ipaddress
12
+ import operator
12
13
  import typing
13
14
  from dataclasses import dataclass
15
+ from functools import reduce
14
16
  from http import HTTPStatus
15
- from typing import Any, Callable, Optional, Union
17
+ from typing import Any, Callable
16
18
 
17
19
  from strong_typing.core import JsonType, Schema
18
20
  from strong_typing.docstring import Docstring, parse_type
19
- from strong_typing.inspection import is_generic_list, is_type_optional, is_type_union, unwrap_generic_list, unwrap_optional_type, unwrap_union_types
21
+ from strong_typing.inspection import is_type_optional, is_type_union, unwrap_optional_type, unwrap_union_types
20
22
  from strong_typing.name import python_type_to_name
21
23
  from strong_typing.schema import JsonSchemaGenerator, SchemaOptions, get_schema_identifier, register_schema
22
24
  from strong_typing.serialization import json_dump_string, object_to_json
@@ -36,7 +38,6 @@ from .specification import (
36
38
  RequestBody,
37
39
  Response,
38
40
  ResponseRef,
39
- SchemaOrRef,
40
41
  SchemaRef,
41
42
  Tag,
42
43
  TagGroup,
@@ -74,14 +75,13 @@ register_schema(
74
75
  def http_status_to_string(status_code: HTTPStatusCode) -> str:
75
76
  "Converts an HTTP status code to a string."
76
77
 
77
- if isinstance(status_code, HTTPStatus):
78
- return str(status_code.value)
79
- elif isinstance(status_code, int):
80
- return str(status_code)
81
- elif isinstance(status_code, str):
82
- return status_code
83
- else:
84
- raise TypeError("expected: HTTP status code")
78
+ match status_code:
79
+ case HTTPStatus():
80
+ return str(status_code.value)
81
+ case int():
82
+ return str(status_code)
83
+ case str():
84
+ return status_code
85
85
 
86
86
 
87
87
  class SchemaBuilder:
@@ -92,7 +92,7 @@ class SchemaBuilder:
92
92
  self.schema_generator = schema_generator
93
93
  self.schemas = {}
94
94
 
95
- def classdef_to_schema(self, typ: type) -> Schema:
95
+ def classdef_to_schema(self, typ: type[Any]) -> Schema:
96
96
  """
97
97
  Converts a type to a JSON schema.
98
98
  For nested types found in the type hierarchy, adds the type to the schema registry in the OpenAPI specification section `components`.
@@ -106,12 +106,12 @@ class SchemaBuilder:
106
106
 
107
107
  return type_schema
108
108
 
109
- def classdef_to_named_schema(self, name: str, typ: type) -> Schema:
109
+ def classdef_to_named_schema(self, name: str, typ: type[Any]) -> Schema:
110
110
  schema = self.classdef_to_schema(typ)
111
111
  self._add_ref(name, schema)
112
112
  return schema
113
113
 
114
- def classdef_to_ref(self, typ: type) -> SchemaOrRef:
114
+ def classdef_to_ref(self, typ: type[Any]) -> Schema | SchemaRef:
115
115
  """
116
116
  Converts a type to a JSON schema, and if possible, returns a schema reference.
117
117
  For composite types (such as classes), adds the type to the schema registry in the OpenAPI specification section `components`.
@@ -145,35 +145,35 @@ class SchemaBuilder:
145
145
 
146
146
  class ContentBuilder:
147
147
  schema_builder: SchemaBuilder
148
- schema_transformer: Optional[Callable[[SchemaOrRef], SchemaOrRef]]
149
- sample_transformer: Optional[Callable[[JsonType], JsonType]]
148
+ schema_transformer: Callable[[Schema | SchemaRef], Schema | SchemaRef] | None
149
+ sample_transformer: Callable[[JsonType], JsonType] | None
150
150
 
151
151
  def __init__(
152
152
  self,
153
153
  schema_builder: SchemaBuilder,
154
- schema_transformer: Optional[Callable[[SchemaOrRef], SchemaOrRef]] = None,
155
- sample_transformer: Optional[Callable[[JsonType], JsonType]] = None,
154
+ schema_transformer: Callable[[Schema | SchemaRef], Schema | SchemaRef] | None = None,
155
+ sample_transformer: Callable[[JsonType], JsonType] | None = None,
156
156
  ) -> None:
157
157
  self.schema_builder = schema_builder
158
158
  self.schema_transformer = schema_transformer
159
159
  self.sample_transformer = sample_transformer
160
160
 
161
- def build_content(self, payload_type: type, examples: Optional[list[Any]] = None) -> dict[str, MediaType]:
161
+ def build_content(self, payload_type: type[Any], examples: list[Any] | None = None) -> dict[str, MediaType]:
162
162
  "Creates the content subtree for a request or response."
163
163
 
164
- if is_generic_list(payload_type):
164
+ if typing.get_origin(payload_type) is list:
165
165
  media_type = "application/jsonl"
166
- item_type = unwrap_generic_list(payload_type)
166
+ (item_type,) = typing.get_args(payload_type) # unpack single tuple element
167
167
  else:
168
168
  media_type = "application/json"
169
169
  item_type = payload_type
170
170
 
171
171
  return {media_type: self.build_media_type(item_type, examples)}
172
172
 
173
- def build_media_type(self, item_type: type, examples: Optional[list[Any]] = None) -> MediaType:
173
+ def build_media_type(self, item_type: type[Any], examples: list[Any] | None = None) -> MediaType:
174
174
  schema = self.schema_builder.classdef_to_ref(item_type)
175
175
  if self.schema_transformer:
176
- schema_transformer: Callable[[SchemaOrRef], SchemaOrRef] = self.schema_transformer
176
+ schema_transformer: Callable[[Schema | SchemaRef], Schema | SchemaRef] = self.schema_transformer
177
177
  schema = schema_transformer(schema)
178
178
 
179
179
  if not examples:
@@ -187,12 +187,12 @@ class ContentBuilder:
187
187
  examples=self._build_examples(examples),
188
188
  )
189
189
 
190
- def _build_examples(self, examples: list[Any]) -> dict[str, Union[Example, ExampleRef]]:
190
+ def _build_examples(self, examples: list[Any]) -> dict[str, Example | ExampleRef]:
191
191
  "Creates a set of several examples for a media type."
192
192
 
193
193
  builder = ExampleBuilder(self.sample_transformer)
194
194
 
195
- results: dict[str, Union[Example, ExampleRef]] = {}
195
+ results: dict[str, Example | ExampleRef] = {}
196
196
  for example in examples:
197
197
  name, value = builder.get_named(example)
198
198
  results[name] = Example(value=value)
@@ -211,7 +211,7 @@ class ExampleBuilder:
211
211
 
212
212
  def __init__(
213
213
  self,
214
- sample_transformer: Optional[Callable[[JsonType], JsonType]] = None,
214
+ sample_transformer: Callable[[JsonType], JsonType] | None = None,
215
215
  ) -> None:
216
216
  if sample_transformer:
217
217
  self.sample_transformer = sample_transformer
@@ -227,7 +227,7 @@ class ExampleBuilder:
227
227
  def get_named(self, example: Any) -> tuple[str, JsonType]:
228
228
  value = self._get_value(example)
229
229
 
230
- name: Optional[str] = None
230
+ name: str | None = None
231
231
 
232
232
  if type(example).__str__ is not object.__str__: # type: ignore[comparison-overlap]
233
233
  friendly_name = str(example)
@@ -252,8 +252,8 @@ class ResponseOptions:
252
252
  :param default_status_code: HTTP status code assigned to responses that have no mapping.
253
253
  """
254
254
 
255
- type_descriptions: dict[type, str]
256
- examples: Optional[list[Any]]
255
+ type_descriptions: dict[type[Any], str]
256
+ examples: list[Any] | None
257
257
  status_catalog: dict[type, HTTPStatusCode]
258
258
  default_status_code: HTTPStatusCode
259
259
 
@@ -261,8 +261,8 @@ class ResponseOptions:
261
261
  @dataclass
262
262
  class StatusResponse:
263
263
  status_code: str
264
- types: list[type] = dataclasses.field(default_factory=list)
265
- examples: list[Any] = dataclasses.field(default_factory=list)
264
+ types: list[type[Any]] = dataclasses.field(default_factory=list[type[Any]])
265
+ examples: list[Any] = dataclasses.field(default_factory=list[Any])
266
266
 
267
267
 
268
268
  class ResponseBuilder:
@@ -291,20 +291,20 @@ class ResponseBuilder:
291
291
 
292
292
  return dict(sorted(status_responses.items()))
293
293
 
294
- def build_response(self, options: ResponseOptions) -> dict[str, Union[Response, ResponseRef]]:
294
+ def build_response(self, options: ResponseOptions) -> dict[str, Response | ResponseRef]:
295
295
  """
296
296
  Groups responses that have the same status code.
297
297
  """
298
298
 
299
- responses: dict[str, Union[Response, ResponseRef]] = {}
299
+ responses: dict[str, Response | ResponseRef] = {}
300
300
  status_responses = self._get_status_responses(options)
301
301
  for status_code, status_response in status_responses.items():
302
302
  response_types = tuple(status_response.types)
303
- if len(response_types) > 1:
304
- composite_response_type: type = Union[response_types] # type: ignore
303
+ composite_response_type: type[Any] | None
304
+ if len(response_types) > 0:
305
+ composite_response_type = reduce(operator.or_, response_types)
305
306
  else:
306
- (response_type,) = response_types
307
- composite_response_type = response_type
307
+ composite_response_type = None
308
308
 
309
309
  description = " **OR** ".join(
310
310
  filter(
@@ -323,9 +323,9 @@ class ResponseBuilder:
323
323
 
324
324
  def _build_response(
325
325
  self,
326
- response_type: Optional[type],
326
+ response_type: type[Any] | None,
327
327
  description: str,
328
- examples: Optional[list[Any]] = None,
328
+ examples: list[Any] | None = None,
329
329
  ) -> Response:
330
330
  "Creates a response subtree."
331
331
 
@@ -338,7 +338,7 @@ class ResponseBuilder:
338
338
  return Response(description=description)
339
339
 
340
340
 
341
- def schema_error_wrapper(schema: SchemaOrRef) -> Schema:
341
+ def schema_error_wrapper(schema: Schema | SchemaRef) -> Schema:
342
342
  "Wraps an error output schema into a top-level error schema."
343
343
 
344
344
  return {
@@ -360,12 +360,12 @@ def sample_error_wrapper(error: JsonType) -> JsonType:
360
360
 
361
361
 
362
362
  class Generator:
363
- endpoint: type
363
+ endpoint: type[Any]
364
364
  options: Options
365
365
  schema_builder: SchemaBuilder
366
366
  responses: dict[str, Response]
367
367
 
368
- def __init__(self, endpoint: type, options: Options) -> None:
368
+ def __init__(self, endpoint: type[Any], options: Options) -> None:
369
369
  self.endpoint = endpoint
370
370
  self.options = options
371
371
  schema_generator = JsonSchemaGenerator(
@@ -380,14 +380,14 @@ class Generator:
380
380
 
381
381
  def _build_type_tag(self, ref: str, schema: Schema) -> Tag:
382
382
  definition = f'<SchemaDefinition schemaRef="#/components/schemas/{ref}" />'
383
- title = typing.cast(str, schema.get("title"))
384
- description = typing.cast(str, schema.get("description"))
383
+ title = typing.cast(str | None, schema.get("title"))
384
+ description = typing.cast(str | None, schema.get("description"))
385
385
  return Tag(
386
386
  name=ref,
387
387
  description="\n\n".join(s for s in (title, description, definition) if s is not None),
388
388
  )
389
389
 
390
- def _build_extra_tag_groups(self, extra_types: dict[str, list[type]]) -> dict[str, list[Tag]]:
390
+ def _build_extra_tag_groups(self, extra_types: dict[str, list[type[Any]]]) -> dict[str, list[Tag]]:
391
391
  """
392
392
  Creates a dictionary of tag group captions as keys, and tag lists as values.
393
393
 
@@ -426,10 +426,10 @@ class Generator:
426
426
  ]
427
427
 
428
428
  # parameters passed in URL component query string
429
- query_parameters = []
429
+ query_parameters: list[Parameter] = []
430
430
  for param_name, param_type in op.query_params:
431
431
  if is_type_optional(param_type):
432
- inner_type: type = unwrap_optional_type(param_type)
432
+ inner_type: type[Any] = unwrap_optional_type(param_type)
433
433
  required = False
434
434
  else:
435
435
  inner_type = param_type
@@ -462,7 +462,9 @@ class Generator:
462
462
  # success response types
463
463
  if doc_string.returns is None and is_type_union(op.response_type):
464
464
  # split union of return types into a list of response types
465
- success_type_docstring: dict[type, Docstring] = {typing.cast(type, item): parse_type(item) for item in unwrap_union_types(op.response_type)}
465
+ success_type_docstring: dict[type[Any], Docstring] = {
466
+ typing.cast(type[Any], item): parse_type(item) for item in unwrap_union_types(op.response_type)
467
+ }
466
468
  success_type_descriptions = {
467
469
  item: doc_string.short_description for item, doc_string in success_type_docstring.items() if doc_string.short_description
468
470
  }
@@ -533,12 +535,13 @@ class Generator:
533
535
  requestBody=requestBody,
534
536
  responses=responses,
535
537
  callbacks=callbacks,
538
+ deprecated=op.deprecated,
536
539
  security=[] if op.public else None,
537
540
  )
538
541
 
539
542
  def generate(self) -> Document:
540
543
  paths: dict[str, PathItem] = {}
541
- endpoint_classes: set[type] = set()
544
+ endpoint_classes: set[type[Any]] = set()
542
545
  for op in get_endpoint_operations(self.endpoint, use_examples=self.options.use_examples):
543
546
  endpoint_classes.add(op.defining_class)
544
547
 
@@ -565,7 +568,7 @@ class Generator:
565
568
 
566
569
  operation_tags: list[Tag] = []
567
570
  for cls in endpoint_classes:
568
- doc_string = parse_type(cls)
571
+ doc_string = parse_type(cls) # type: ignore[arg-type]
569
572
  operation_tags.append(
570
573
  Tag(
571
574
  name=cls.__name__,
@@ -587,12 +590,11 @@ class Generator:
587
590
  # types that are explicitly declared
588
591
  extra_tag_groups: dict[str, list[Tag]] = {}
589
592
  if self.options.extra_types is not None:
590
- if isinstance(self.options.extra_types, list):
591
- extra_tag_groups = self._build_extra_tag_groups({"AdditionalTypes": self.options.extra_types})
592
- elif isinstance(self.options.extra_types, dict):
593
- extra_tag_groups = self._build_extra_tag_groups(self.options.extra_types)
594
- else:
595
- raise TypeError(f"type mismatch for collection of extra types: {type(self.options.extra_types)}")
593
+ match self.options.extra_types:
594
+ case list():
595
+ extra_tag_groups = self._build_extra_tag_groups({"AdditionalTypes": self.options.extra_types})
596
+ case dict():
597
+ extra_tag_groups = self._build_extra_tag_groups(self.options.extra_types)
596
598
 
597
599
  # list all operations and types
598
600
  tags: list[Tag] = []
@@ -602,7 +604,7 @@ class Generator:
602
604
  for extra_tag_group in extra_tag_groups.values():
603
605
  tags.extend(extra_tag_group)
604
606
 
605
- tag_groups = []
607
+ tag_groups: list[TagGroup] = []
606
608
  if operation_tags:
607
609
  tag_groups.append(
608
610
  TagGroup(
@@ -1,13 +1,13 @@
1
1
  """
2
2
  Generate an OpenAPI specification from a Python class definition
3
3
 
4
- Copyright 2022-2025, Levente Hunyadi
4
+ Copyright 2021-2026, Levente Hunyadi
5
5
 
6
6
  :see: https://github.com/hunyadi/pyopenapi
7
7
  """
8
8
 
9
9
  from dataclasses import dataclass
10
- from typing import Any, Optional
10
+ from typing import Any
11
11
 
12
12
 
13
13
  @dataclass
@@ -17,11 +17,13 @@ class WebMethod:
17
17
 
18
18
  :param route: The URL path pattern associated with this operation which path parameters are substituted into.
19
19
  :param public: True if the operation can be invoked without prior authentication.
20
+ :param deprecated: True if consumers should refrain from using the operation.
20
21
  :param request_examples: Sample requests that the operation might take. Pass a list of objects, not JSON.
21
22
  :param response_examples: Sample responses that the operation might produce. Pass a list of objects, not JSON.
22
23
  """
23
24
 
24
- route: Optional[str] = None
25
+ route: str | None = None
25
26
  public: bool = False
26
- request_examples: Optional[list[Any]] = None
27
- response_examples: Optional[list[Any]] = None
27
+ deprecated: bool = False
28
+ request_examples: list[Any] | None = None
29
+ response_examples: list[Any] | None = None