schemathesis 3.21.2__py3-none-any.whl → 3.22.1__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.
Files changed (95) hide show
  1. schemathesis/__init__.py +1 -1
  2. schemathesis/_compat.py +2 -18
  3. schemathesis/_dependency_versions.py +1 -6
  4. schemathesis/_hypothesis.py +15 -12
  5. schemathesis/_lazy_import.py +3 -2
  6. schemathesis/_xml.py +12 -11
  7. schemathesis/auths.py +88 -81
  8. schemathesis/checks.py +4 -4
  9. schemathesis/cli/__init__.py +202 -171
  10. schemathesis/cli/callbacks.py +29 -32
  11. schemathesis/cli/cassettes.py +25 -25
  12. schemathesis/cli/context.py +18 -12
  13. schemathesis/cli/junitxml.py +2 -2
  14. schemathesis/cli/options.py +10 -11
  15. schemathesis/cli/output/default.py +64 -34
  16. schemathesis/code_samples.py +10 -10
  17. schemathesis/constants.py +1 -1
  18. schemathesis/contrib/unique_data.py +2 -2
  19. schemathesis/exceptions.py +55 -42
  20. schemathesis/extra/_aiohttp.py +2 -2
  21. schemathesis/extra/_flask.py +2 -2
  22. schemathesis/extra/_server.py +3 -2
  23. schemathesis/extra/pytest_plugin.py +10 -10
  24. schemathesis/failures.py +16 -16
  25. schemathesis/filters.py +40 -41
  26. schemathesis/fixups/__init__.py +4 -3
  27. schemathesis/fixups/fast_api.py +5 -4
  28. schemathesis/generation/__init__.py +16 -4
  29. schemathesis/hooks.py +25 -25
  30. schemathesis/internal/jsonschema.py +4 -3
  31. schemathesis/internal/transformation.py +3 -2
  32. schemathesis/lazy.py +39 -31
  33. schemathesis/loaders.py +8 -8
  34. schemathesis/models.py +128 -126
  35. schemathesis/parameters.py +6 -5
  36. schemathesis/runner/__init__.py +107 -81
  37. schemathesis/runner/events.py +37 -26
  38. schemathesis/runner/impl/core.py +86 -81
  39. schemathesis/runner/impl/solo.py +19 -15
  40. schemathesis/runner/impl/threadpool.py +40 -22
  41. schemathesis/runner/serialization.py +67 -40
  42. schemathesis/sanitization.py +18 -20
  43. schemathesis/schemas.py +83 -72
  44. schemathesis/serializers.py +39 -30
  45. schemathesis/service/ci.py +20 -21
  46. schemathesis/service/client.py +29 -9
  47. schemathesis/service/constants.py +1 -0
  48. schemathesis/service/events.py +2 -2
  49. schemathesis/service/hosts.py +8 -7
  50. schemathesis/service/metadata.py +5 -0
  51. schemathesis/service/models.py +22 -4
  52. schemathesis/service/report.py +15 -15
  53. schemathesis/service/serialization.py +23 -27
  54. schemathesis/service/usage.py +8 -7
  55. schemathesis/specs/graphql/loaders.py +31 -24
  56. schemathesis/specs/graphql/nodes.py +3 -2
  57. schemathesis/specs/graphql/scalars.py +26 -2
  58. schemathesis/specs/graphql/schemas.py +38 -34
  59. schemathesis/specs/openapi/_hypothesis.py +62 -44
  60. schemathesis/specs/openapi/checks.py +10 -10
  61. schemathesis/specs/openapi/converter.py +10 -9
  62. schemathesis/specs/openapi/definitions.py +2 -2
  63. schemathesis/specs/openapi/examples.py +22 -21
  64. schemathesis/specs/openapi/expressions/nodes.py +5 -4
  65. schemathesis/specs/openapi/expressions/parser.py +7 -6
  66. schemathesis/specs/openapi/filters.py +6 -6
  67. schemathesis/specs/openapi/formats.py +2 -2
  68. schemathesis/specs/openapi/links.py +19 -21
  69. schemathesis/specs/openapi/loaders.py +133 -78
  70. schemathesis/specs/openapi/negative/__init__.py +16 -11
  71. schemathesis/specs/openapi/negative/mutations.py +11 -10
  72. schemathesis/specs/openapi/parameters.py +20 -19
  73. schemathesis/specs/openapi/references.py +21 -20
  74. schemathesis/specs/openapi/schemas.py +97 -84
  75. schemathesis/specs/openapi/security.py +25 -24
  76. schemathesis/specs/openapi/serialization.py +20 -23
  77. schemathesis/specs/openapi/stateful/__init__.py +12 -11
  78. schemathesis/specs/openapi/stateful/links.py +7 -7
  79. schemathesis/specs/openapi/utils.py +4 -3
  80. schemathesis/specs/openapi/validation.py +3 -2
  81. schemathesis/stateful/__init__.py +15 -16
  82. schemathesis/stateful/state_machine.py +9 -9
  83. schemathesis/targets.py +3 -3
  84. schemathesis/throttling.py +2 -2
  85. schemathesis/transports/auth.py +2 -2
  86. schemathesis/transports/content_types.py +5 -0
  87. schemathesis/transports/headers.py +3 -2
  88. schemathesis/transports/responses.py +1 -1
  89. schemathesis/utils.py +7 -10
  90. {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/METADATA +12 -13
  91. schemathesis-3.22.1.dist-info/RECORD +130 -0
  92. schemathesis-3.21.2.dist-info/RECORD +0 -130
  93. {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/WHEEL +0 -0
  94. {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/entry_points.txt +0 -0
  95. {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/licenses/LICENSE +0 -0
@@ -4,7 +4,7 @@ import os
4
4
  import re
5
5
  import traceback
6
6
  from contextlib import contextmanager
7
- from typing import Dict, Generator, List, Optional, Tuple, Union, TYPE_CHECKING
7
+ from typing import Generator, TYPE_CHECKING
8
8
  from urllib.parse import urlparse
9
9
 
10
10
  import click
@@ -77,7 +77,7 @@ class SchemaInputKind(enum.Enum):
77
77
  NAME = 4
78
78
 
79
79
 
80
- def parse_schema_kind(schema: str, app: Optional[str]) -> SchemaInputKind:
80
+ def parse_schema_kind(schema: str, app: str | None) -> SchemaInputKind:
81
81
  """Detect what kind the input schema is."""
82
82
  try:
83
83
  netloc = urlparse(schema).netloc
@@ -99,21 +99,20 @@ def validate_schema(
99
99
  schema: str,
100
100
  kind: SchemaInputKind,
101
101
  *,
102
- base_url: Optional[str],
102
+ base_url: str | None,
103
103
  dry_run: bool,
104
- app: Optional[str],
105
- api_name: Optional[str],
104
+ app: str | None,
105
+ api_name: str | None,
106
106
  ) -> None:
107
107
  if kind == SchemaInputKind.URL:
108
108
  validate_url(schema)
109
109
  if kind == SchemaInputKind.PATH:
110
- # Base URL is required if it is not a dry run
111
- if app is None and base_url is None and not dry_run:
110
+ if app is None:
112
111
  if not file_exists(schema):
113
- message = FILE_DOES_NOT_EXIST_MESSAGE
114
- else:
115
- message = MISSING_BASE_URL_MESSAGE
116
- raise click.UsageError(message)
112
+ raise click.UsageError(FILE_DOES_NOT_EXIST_MESSAGE)
113
+ # Base URL is required if it is not a dry run
114
+ if base_url is None and not dry_run:
115
+ raise click.UsageError(MISSING_BASE_URL_MESSAGE)
117
116
  if kind == SchemaInputKind.NAME:
118
117
  if api_name is not None:
119
118
  raise click.UsageError(f"Got unexpected extra argument ({api_name})")
@@ -138,9 +137,7 @@ def validate_base_url(ctx: click.core.Context, param: click.core.Parameter, raw_
138
137
  return raw_value
139
138
 
140
139
 
141
- def validate_rate_limit(
142
- ctx: click.core.Context, param: click.core.Parameter, raw_value: Optional[str]
143
- ) -> Optional[str]:
140
+ def validate_rate_limit(ctx: click.core.Context, param: click.core.Parameter, raw_value: str | None) -> str | None:
144
141
  if raw_value is None:
145
142
  return raw_value
146
143
  try:
@@ -150,7 +147,7 @@ def validate_rate_limit(
150
147
  raise click.UsageError(exc.args[0]) from exc
151
148
 
152
149
 
153
- def validate_app(ctx: click.core.Context, param: click.core.Parameter, raw_value: Optional[str]) -> Optional[str]:
150
+ def validate_app(ctx: click.core.Context, param: click.core.Parameter, raw_value: str | None) -> str | None:
154
151
  if raw_value is None:
155
152
  return raw_value
156
153
  try:
@@ -176,8 +173,8 @@ def validate_app(ctx: click.core.Context, param: click.core.Parameter, raw_value
176
173
 
177
174
 
178
175
  def validate_hypothesis_database(
179
- ctx: click.core.Context, param: click.core.Parameter, raw_value: Optional[str]
180
- ) -> Optional[str]:
176
+ ctx: click.core.Context, param: click.core.Parameter, raw_value: str | None
177
+ ) -> str | None:
181
178
  if raw_value is None:
182
179
  return raw_value
183
180
  if ctx.params.get("hypothesis_derandomize"):
@@ -186,8 +183,8 @@ def validate_hypothesis_database(
186
183
 
187
184
 
188
185
  def validate_auth(
189
- ctx: click.core.Context, param: click.core.Parameter, raw_value: Optional[str]
190
- ) -> Optional[Tuple[str, str]]:
186
+ ctx: click.core.Context, param: click.core.Parameter, raw_value: str | None
187
+ ) -> tuple[str, str] | None:
191
188
  if raw_value is not None:
192
189
  with reraise_format_error(raw_value):
193
190
  user, password = tuple(raw_value.split(":"))
@@ -202,8 +199,8 @@ def validate_auth(
202
199
 
203
200
 
204
201
  def validate_headers(
205
- ctx: click.core.Context, param: click.core.Parameter, raw_value: Tuple[str, ...]
206
- ) -> Dict[str, str]:
202
+ ctx: click.core.Context, param: click.core.Parameter, raw_value: tuple[str, ...]
203
+ ) -> dict[str, str]:
207
204
  headers = {}
208
205
  for header in raw_value:
209
206
  with reraise_format_error(header):
@@ -222,7 +219,7 @@ def validate_headers(
222
219
  return headers
223
220
 
224
221
 
225
- def validate_regex(ctx: click.core.Context, param: click.core.Parameter, raw_value: Tuple[str, ...]) -> Tuple[str, ...]:
222
+ def validate_regex(ctx: click.core.Context, param: click.core.Parameter, raw_value: tuple[str, ...]) -> tuple[str, ...]:
226
223
  for value in raw_value:
227
224
  try:
228
225
  re.compile(value)
@@ -232,8 +229,8 @@ def validate_regex(ctx: click.core.Context, param: click.core.Parameter, raw_val
232
229
 
233
230
 
234
231
  def validate_request_cert_key(
235
- ctx: click.core.Context, param: click.core.Parameter, raw_value: Optional[str]
236
- ) -> Optional[str]:
232
+ ctx: click.core.Context, param: click.core.Parameter, raw_value: str | None
233
+ ) -> str | None:
237
234
  if raw_value is not None and "request_cert" not in ctx.params:
238
235
  raise click.UsageError(MISSING_REQUEST_CERT_MESSAGE)
239
236
  return raw_value
@@ -246,8 +243,8 @@ def validate_preserve_exact_body_bytes(ctx: click.core.Context, param: click.cor
246
243
 
247
244
 
248
245
  def convert_verbosity(
249
- ctx: click.core.Context, param: click.core.Parameter, value: Optional[str]
250
- ) -> Optional[hypothesis.Verbosity]:
246
+ ctx: click.core.Context, param: click.core.Parameter, value: str | None
247
+ ) -> hypothesis.Verbosity | None:
251
248
  import hypothesis
252
249
 
253
250
  if value is None:
@@ -255,15 +252,15 @@ def convert_verbosity(
255
252
  return hypothesis.Verbosity[value]
256
253
 
257
254
 
258
- def convert_stateful(ctx: click.core.Context, param: click.core.Parameter, value: str) -> Optional[Stateful]:
255
+ def convert_stateful(ctx: click.core.Context, param: click.core.Parameter, value: str) -> Stateful | None:
259
256
  if value == "none":
260
257
  return None
261
258
  return Stateful[value]
262
259
 
263
260
 
264
261
  def convert_experimental(
265
- ctx: click.core.Context, param: click.core.Parameter, value: Tuple[str, ...]
266
- ) -> List[experimental.Experiment]:
262
+ ctx: click.core.Context, param: click.core.Parameter, value: tuple[str, ...]
263
+ ) -> list[experimental.Experiment]:
267
264
  return [
268
265
  feature
269
266
  for feature in experimental.GLOBAL_EXPERIMENTS.available
@@ -271,7 +268,7 @@ def convert_experimental(
271
268
  ]
272
269
 
273
270
 
274
- def convert_checks(ctx: click.core.Context, param: click.core.Parameter, value: Tuple[List[str]]) -> List[str]:
271
+ def convert_checks(ctx: click.core.Context, param: click.core.Parameter, value: tuple[list[str]]) -> list[str]:
275
272
  return sum(value, [])
276
273
 
277
274
 
@@ -281,7 +278,7 @@ def convert_code_sample_style(ctx: click.core.Context, param: click.core.Paramet
281
278
 
282
279
  def convert_data_generation_method(
283
280
  ctx: click.core.Context, param: click.core.Parameter, value: str
284
- ) -> List[DataGenerationMethod]:
281
+ ) -> list[DataGenerationMethod]:
285
282
  if value == "all":
286
283
  return DataGenerationMethod.all()
287
284
  return [DataGenerationMethod[value]]
@@ -307,7 +304,7 @@ def convert_hosts_file(ctx: click.core.Context, param: click.core.Parameter, val
307
304
  return value
308
305
 
309
306
 
310
- def convert_boolean_string(ctx: click.core.Context, param: click.core.Parameter, value: str) -> Union[str, bool]:
307
+ def convert_boolean_string(ctx: click.core.Context, param: click.core.Parameter, value: str) -> str | bool:
311
308
  if value.lower() in TRUE_VALUES:
312
309
  return True
313
310
  if value.lower() in FALSE_VALUES:
@@ -6,7 +6,7 @@ import sys
6
6
  import threading
7
7
  from dataclasses import dataclass, field
8
8
  from queue import Queue
9
- from typing import IO, Any, Dict, Generator, Iterator, List, Optional, cast, TYPE_CHECKING
9
+ from typing import IO, Any, Generator, Iterator, cast, TYPE_CHECKING
10
10
 
11
11
  from ..constants import SCHEMATHESIS_VERSION
12
12
  from ..runner import events
@@ -93,7 +93,7 @@ class Process:
93
93
  correlation_id: str
94
94
  thread_id: int
95
95
  data_generation_method: DataGenerationMethod
96
- interactions: List[SerializedInteraction]
96
+ interactions: list[SerializedInteraction]
97
97
 
98
98
 
99
99
  @dataclass
@@ -123,16 +123,16 @@ def worker(file_handle: click.utils.LazyFile, preserve_exact_body_bytes: bool, q
123
123
  current_id = 1
124
124
  stream = file_handle.open()
125
125
 
126
- def format_header_values(values: List[str]) -> str:
126
+ def format_header_values(values: list[str]) -> str:
127
127
  return "\n".join(f" - {json.dumps(v)}" for v in values)
128
128
 
129
- def format_headers(headers: Dict[str, List[str]]) -> str:
129
+ def format_headers(headers: dict[str, list[str]]) -> str:
130
130
  return "\n".join(f' "{name}":\n{format_header_values(values)}' for name, values in headers.items())
131
131
 
132
- def format_check_message(message: Optional[str]) -> str:
132
+ def format_check_message(message: str | None) -> str:
133
133
  return "~" if message is None else f"{repr(message)}"
134
134
 
135
- def format_checks(checks: List[SerializedCheck]) -> str:
135
+ def format_checks(checks: list[SerializedCheck]) -> str:
136
136
  return "\n".join(
137
137
  f" - name: '{check.name}'\n status: '{check.value.name.upper()}'\n message: {format_check_message(check.message)}"
138
138
  for check in checks
@@ -278,18 +278,18 @@ def write_double_quoted(stream: IO, text: str) -> None:
278
278
 
279
279
  @dataclass
280
280
  class Replayed:
281
- interaction: Dict[str, Any]
281
+ interaction: dict[str, Any]
282
282
  response: requests.Response
283
283
 
284
284
 
285
285
  def replay(
286
- cassette: Dict[str, Any],
287
- id_: Optional[str] = None,
288
- status: Optional[str] = None,
289
- uri: Optional[str] = None,
290
- method: Optional[str] = None,
286
+ cassette: dict[str, Any],
287
+ id_: str | None = None,
288
+ status: str | None = None,
289
+ uri: str | None = None,
290
+ method: str | None = None,
291
291
  request_tls_verify: bool = True,
292
- request_cert: Optional[RequestCert] = None,
292
+ request_cert: RequestCert | None = None,
293
293
  ) -> Generator[Replayed, None, None]:
294
294
  """Replay saved interactions."""
295
295
  import requests
@@ -304,26 +304,26 @@ def replay(
304
304
 
305
305
 
306
306
  def filter_cassette(
307
- interactions: List[Dict[str, Any]],
308
- id_: Optional[str] = None,
309
- status: Optional[str] = None,
310
- uri: Optional[str] = None,
311
- method: Optional[str] = None,
312
- ) -> Iterator[Dict[str, Any]]:
307
+ interactions: list[dict[str, Any]],
308
+ id_: str | None = None,
309
+ status: str | None = None,
310
+ uri: str | None = None,
311
+ method: str | None = None,
312
+ ) -> Iterator[dict[str, Any]]:
313
313
  filters = []
314
314
 
315
- def id_filter(item: Dict[str, Any]) -> bool:
315
+ def id_filter(item: dict[str, Any]) -> bool:
316
316
  return item["id"] == id_
317
317
 
318
- def status_filter(item: Dict[str, Any]) -> bool:
318
+ def status_filter(item: dict[str, Any]) -> bool:
319
319
  status_ = cast(str, status)
320
320
  return item["status"].upper() == status_.upper()
321
321
 
322
- def uri_filter(item: Dict[str, Any]) -> bool:
322
+ def uri_filter(item: dict[str, Any]) -> bool:
323
323
  uri_ = cast(str, uri)
324
324
  return bool(re.search(uri_, item["request"]["uri"]))
325
325
 
326
- def method_filter(item: Dict[str, Any]) -> bool:
326
+ def method_filter(item: dict[str, Any]) -> bool:
327
327
  method_ = cast(str, method)
328
328
  return bool(re.search(method_, item["request"]["method"]))
329
329
 
@@ -339,13 +339,13 @@ def filter_cassette(
339
339
  if method is not None:
340
340
  filters.append(method_filter)
341
341
 
342
- def is_match(interaction: Dict[str, Any]) -> bool:
342
+ def is_match(interaction: dict[str, Any]) -> bool:
343
343
  return all(filter_(interaction) for filter_ in filters)
344
344
 
345
345
  return filter(is_match, interactions)
346
346
 
347
347
 
348
- def get_prepared_request(data: Dict[str, Any]) -> requests.PreparedRequest:
348
+ def get_prepared_request(data: dict[str, Any]) -> requests.PreparedRequest:
349
349
  """Create a `requests.PreparedRequest` from a serialized one."""
350
350
  from requests.structures import CaseInsensitiveDict
351
351
  from requests.cookies import RequestsCookieJar
@@ -3,9 +3,10 @@ import os
3
3
  import shutil
4
4
  from dataclasses import dataclass, field
5
5
  from queue import Queue
6
- from typing import List, Optional, Union, TYPE_CHECKING
6
+ from typing import TYPE_CHECKING
7
7
 
8
8
  from ..code_samples import CodeSampleStyle
9
+ from ..internal.deprecation import deprecated_property
9
10
  from ..runner.serialization import SerializedTestResult
10
11
 
11
12
  if TYPE_CHECKING:
@@ -21,7 +22,7 @@ class ServiceReportContext:
21
22
  @dataclass
22
23
  class FileReportContext:
23
24
  queue: Queue
24
- filename: Optional[str] = None
25
+ filename: str | None = None
25
26
 
26
27
 
27
28
  @dataclass
@@ -29,21 +30,26 @@ class ExecutionContext:
29
30
  """Storage for the current context of the execution."""
30
31
 
31
32
  hypothesis_settings: hypothesis.settings
32
- hypothesis_output: List[str] = field(default_factory=list)
33
+ hypothesis_output: list[str] = field(default_factory=list)
33
34
  workers_num: int = 1
34
- rate_limit: Optional[str] = None
35
- show_errors_tracebacks: bool = False
36
- wait_for_schema: Optional[float] = None
35
+ rate_limit: str | None = None
36
+ show_trace: bool = False
37
+ wait_for_schema: float | None = None
37
38
  validate_schema: bool = True
38
39
  operations_processed: int = 0
39
- # It is set in runtime, from a `Initialized` event
40
- operations_count: Optional[int] = None
40
+ # It is set in runtime, from the `Initialized` event
41
+ operations_count: int | None = None
42
+ seed: int | None = None
41
43
  current_line_length: int = 0
42
44
  terminal_size: os.terminal_size = field(default_factory=shutil.get_terminal_size)
43
- results: List[SerializedTestResult] = field(default_factory=list)
44
- cassette_path: Optional[str] = None
45
- junit_xml_file: Optional[str] = None
45
+ results: list[SerializedTestResult] = field(default_factory=list)
46
+ cassette_path: str | None = None
47
+ junit_xml_file: str | None = None
46
48
  is_interrupted: bool = False
47
49
  verbosity: int = 0
48
50
  code_sample_style: CodeSampleStyle = CodeSampleStyle.default()
49
- report: Optional[Union[ServiceReportContext, FileReportContext]] = None
51
+ report: ServiceReportContext | FileReportContext | None = None
52
+
53
+ @deprecated_property(removed_in="4.0", replacement="show_trace")
54
+ def show_errors_tracebacks(self) -> bool:
55
+ return self.show_trace
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
  import platform
3
3
  from dataclasses import dataclass, field
4
- from typing import List, TYPE_CHECKING
4
+ from typing import TYPE_CHECKING
5
5
 
6
6
  from junit_xml import TestCase, TestSuite, to_xml_report_file
7
7
 
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
19
19
  @dataclass
20
20
  class JunitXMLHandler(EventHandler):
21
21
  file_handle: LazyFile
22
- test_cases: List = field(default_factory=list)
22
+ test_cases: list = field(default_factory=list)
23
23
 
24
24
  def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
25
25
  if isinstance(event, events.AfterExecution):
@@ -1,5 +1,6 @@
1
+ from __future__ import annotations
1
2
  from enum import Enum
2
- from typing import Any, List, NoReturn, Optional, Set, Tuple, Type, Union
3
+ from typing import Any, NoReturn
3
4
 
4
5
  import click
5
6
 
@@ -19,12 +20,12 @@ class CustomHelpMessageChoice(click.Choice):
19
20
 
20
21
 
21
22
  class BaseCsvChoice(click.Choice):
22
- def parse_value(self, value: str) -> Tuple[List[str], Set[str]]:
23
+ def parse_value(self, value: str) -> tuple[list[str], set[str]]:
23
24
  selected = [item for item in value.split(",") if item]
24
25
  invalid_options = set(selected) - set(self.choices)
25
26
  return selected, invalid_options
26
27
 
27
- def fail_on_invalid_options(self, invalid_options: Set[str], selected: List[str]) -> NoReturn:
28
+ def fail_on_invalid_options(self, invalid_options: set[str], selected: list[str]) -> NoReturn:
28
29
  # Sort to keep the error output consistent with the passed values
29
30
  sorted_options = ", ".join(sorted(invalid_options, key=selected.index))
30
31
  available_options = ", ".join(self.choices)
@@ -32,13 +33,13 @@ class BaseCsvChoice(click.Choice):
32
33
 
33
34
 
34
35
  class CsvEnumChoice(BaseCsvChoice):
35
- def __init__(self, choices: Type[Enum]):
36
+ def __init__(self, choices: type[Enum]):
36
37
  self.enum = choices
37
38
  super().__init__(tuple(el.name for el in choices))
38
39
 
39
40
  def convert( # type: ignore[return]
40
- self, value: str, param: Optional[click.core.Parameter], ctx: Optional[click.core.Context]
41
- ) -> List[Enum]:
41
+ self, value: str, param: click.core.Parameter | None, ctx: click.core.Context | None
42
+ ) -> list[Enum]:
42
43
  selected, invalid_options = self.parse_value(value)
43
44
  if not invalid_options and selected:
44
45
  return [self.enum[item] for item in selected]
@@ -46,9 +47,7 @@ class CsvEnumChoice(BaseCsvChoice):
46
47
 
47
48
 
48
49
  class CsvChoice(BaseCsvChoice):
49
- def convert(
50
- self, value: str, param: Optional[click.core.Parameter], ctx: Optional[click.core.Context]
51
- ) -> List[str]:
50
+ def convert(self, value: str, param: click.core.Parameter | None, ctx: click.core.Context | None) -> list[str]:
52
51
  selected, invalid_options = self.parse_value(value)
53
52
  if not invalid_options and selected:
54
53
  return selected
@@ -57,8 +56,8 @@ class CsvChoice(BaseCsvChoice):
57
56
 
58
57
  class OptionalInt(click.types.IntRange):
59
58
  def convert( # type: ignore
60
- self, value: str, param: Optional[click.core.Parameter], ctx: Optional[click.core.Context]
61
- ) -> Union[int, NotSet]:
59
+ self, value: str, param: click.core.Parameter | None, ctx: click.core.Context | None
60
+ ) -> int | NotSet:
62
61
  if value.lower() == "none":
63
62
  return NOT_SET
64
63
  try: