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
@@ -14,17 +14,11 @@ from typing import (
14
14
  Any,
15
15
  Callable,
16
16
  ClassVar,
17
- Dict,
18
17
  Generator,
19
18
  Iterable,
20
- List,
21
19
  NoReturn,
22
- Optional,
23
20
  Sequence,
24
- Tuple,
25
- Type,
26
21
  TypeVar,
27
- Union,
28
22
  )
29
23
  from urllib.parse import urlsplit
30
24
 
@@ -37,7 +31,7 @@ from requests.structures import CaseInsensitiveDict
37
31
  from ... import experimental, failures
38
32
  from ..._compat import MultipleFailures
39
33
  from ...auths import AuthStorage
40
- from ...generation import DataGenerationMethod
34
+ from ...generation import DataGenerationMethod, GenerationConfig
41
35
  from ...constants import HTTP_METHODS, NOT_SET
42
36
  from ...exceptions import (
43
37
  OperationSchemaError,
@@ -93,19 +87,19 @@ class BaseOpenAPISchema(BaseSchema):
93
87
  links_field: ClassVar[str] = ""
94
88
  header_required_field: ClassVar[str] = ""
95
89
  security: ClassVar[BaseSecurityProcessor] = None # type: ignore
96
- _operations_by_id: Dict[str, APIOperation] = field(init=False)
97
- _inline_reference_cache: Dict[str, Any] = field(default_factory=dict)
90
+ _operations_by_id: dict[str, APIOperation] = field(init=False)
91
+ _inline_reference_cache: dict[str, Any] = field(default_factory=dict)
98
92
  # Inline references cache can be populated from multiple threads, therefore we need some synchronisation to avoid
99
93
  # excessive resolving
100
94
  _inline_reference_cache_lock: RLock = field(default_factory=RLock)
101
- component_locations: ClassVar[Tuple[Tuple[str, ...], ...]] = ()
95
+ component_locations: ClassVar[tuple[tuple[str, ...], ...]] = ()
102
96
 
103
97
  @property
104
98
  def spec_version(self) -> str:
105
99
  raise NotImplementedError
106
100
 
107
101
  def get_stateful_tests(
108
- self, response: GenericResponse, operation: APIOperation, stateful: Optional[Stateful]
102
+ self, response: GenericResponse, operation: APIOperation, stateful: Stateful | None
109
103
  ) -> Sequence[StatefulTest]:
110
104
  if stateful == Stateful.links:
111
105
  return links.get_links(response, operation, field=self.links_field)
@@ -115,7 +109,7 @@ class BaseOpenAPISchema(BaseSchema):
115
109
  info = self.raw_schema["info"]
116
110
  return f"{self.__class__.__name__} for {info['title']} ({info['version']})"
117
111
 
118
- def _should_skip(self, method: str, definition: Dict[str, Any]) -> bool:
112
+ def _should_skip(self, method: str, definition: dict[str, Any]) -> bool:
119
113
  return (
120
114
  method not in HTTP_METHODS
121
115
  or should_skip_method(method, self.method)
@@ -124,13 +118,11 @@ class BaseOpenAPISchema(BaseSchema):
124
118
  or should_skip_by_operation_id(definition.get("operationId"), self.operation_id)
125
119
  )
126
120
 
127
- @property
128
- def operations_count(self) -> int:
121
+ def _operation_iter(self) -> Generator[dict[str, Any], None, None]:
129
122
  try:
130
123
  paths = self.raw_schema["paths"]
131
124
  except KeyError:
132
- return 0
133
- total = 0
125
+ return
134
126
  resolve = self.resolver.resolve
135
127
  for path, methods in paths.items():
136
128
  full_path = self.get_full_path(path)
@@ -145,14 +137,33 @@ class BaseOpenAPISchema(BaseSchema):
145
137
  for method, definition in resolved_methods.items():
146
138
  if self._should_skip(method, definition):
147
139
  continue
148
- total += 1
140
+ yield definition
149
141
  except SCHEMA_PARSING_ERRORS:
150
142
  # Ignore errors
151
143
  continue
144
+
145
+ @property
146
+ def operations_count(self) -> int:
147
+ total = 0
148
+ # Do not build a list from it
149
+ for _ in self._operation_iter():
150
+ total += 1
151
+ return total
152
+
153
+ @property
154
+ def links_count(self) -> int:
155
+ total = 0
156
+ for definition in self._operation_iter():
157
+ for response in definition.get("responses", {}).values():
158
+ if "$ref" in response:
159
+ _, response = self.resolver.resolve(response["$ref"])
160
+ defined_links = response.get(self.links_field)
161
+ if defined_links is not None:
162
+ total += len(defined_links)
152
163
  return total
153
164
 
154
165
  def get_all_operations(
155
- self, hooks: Optional[HookDispatcher] = None
166
+ self, hooks: HookDispatcher | None = None
156
167
  ) -> Generator[Result[APIOperation, OperationSchemaError], None, None]:
157
168
  """Iterate over all operations defined in the API.
158
169
 
@@ -221,7 +232,7 @@ class BaseOpenAPISchema(BaseSchema):
221
232
  except SCHEMA_PARSING_ERRORS as exc:
222
233
  yield self._into_err(exc, path, method)
223
234
 
224
- def _into_err(self, error: Exception, path: Optional[str], method: Optional[str]) -> Err[OperationSchemaError]:
235
+ def _into_err(self, error: Exception, path: str | None, method: str | None) -> Err[OperationSchemaError]:
225
236
  __tracebackhide__ = True
226
237
  try:
227
238
  full_path = self.get_full_path(path) if isinstance(path, str) else None
@@ -232,9 +243,9 @@ class BaseOpenAPISchema(BaseSchema):
232
243
  def _raise_invalid_schema(
233
244
  self,
234
245
  error: Exception,
235
- full_path: Optional[str] = None,
236
- path: Optional[str] = None,
237
- method: Optional[str] = None,
246
+ full_path: str | None = None,
247
+ path: str | None = None,
248
+ method: str | None = None,
238
249
  ) -> NoReturn:
239
250
  __tracebackhide__ = True
240
251
  if isinstance(error, jsonschema.exceptions.RefResolutionError):
@@ -257,8 +268,8 @@ class BaseOpenAPISchema(BaseSchema):
257
268
  raise NotImplementedError
258
269
 
259
270
  def collect_parameters(
260
- self, parameters: Iterable[Dict[str, Any]], definition: Dict[str, Any]
261
- ) -> List[OpenAPIParameter]:
271
+ self, parameters: Iterable[dict[str, Any]], definition: dict[str, Any]
272
+ ) -> list[OpenAPIParameter]:
262
273
  """Collect Open API parameters.
263
274
 
264
275
  They should be used uniformly during the generation step; therefore, we need to convert them into
@@ -266,7 +277,7 @@ class BaseOpenAPISchema(BaseSchema):
266
277
  """
267
278
  raise NotImplementedError
268
279
 
269
- def _resolve_methods(self, methods: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]:
280
+ def _resolve_methods(self, methods: dict[str, Any]) -> tuple[str, dict[str, Any]]:
270
281
  # We need to know a proper scope in what methods are.
271
282
  # It will allow us to provide a proper reference resolving in `response_schema_conformance` and avoid
272
283
  # recursion errors
@@ -278,7 +289,7 @@ class BaseOpenAPISchema(BaseSchema):
278
289
  self,
279
290
  path: str,
280
291
  method: str,
281
- parameters: List[OpenAPIParameter],
292
+ parameters: list[OpenAPIParameter],
282
293
  raw_definition: OperationDefinition,
283
294
  ) -> APIOperation:
284
295
  """Create JSON schemas for the query, body, etc from Swagger parameters definitions."""
@@ -304,19 +315,19 @@ class BaseOpenAPISchema(BaseSchema):
304
315
  self._resolver = InliningResolver(self.location or "", self.raw_schema)
305
316
  return self._resolver
306
317
 
307
- def get_content_types(self, operation: APIOperation, response: GenericResponse) -> List[str]:
318
+ def get_content_types(self, operation: APIOperation, response: GenericResponse) -> list[str]:
308
319
  """Content types available for this API operation."""
309
320
  raise NotImplementedError
310
321
 
311
- def get_strategies_from_examples(self, operation: APIOperation) -> List[SearchStrategy[Case]]:
322
+ def get_strategies_from_examples(self, operation: APIOperation) -> list[SearchStrategy[Case]]:
312
323
  """Get examples from the API operation."""
313
324
  raise NotImplementedError
314
325
 
315
- def get_security_requirements(self, operation: APIOperation) -> List[str]:
326
+ def get_security_requirements(self, operation: APIOperation) -> list[str]:
316
327
  """Get applied security requirements for the given API operation."""
317
328
  return self.security.get_security_requirements(self.raw_schema, operation)
318
329
 
319
- def get_response_schema(self, definition: Dict[str, Any], scope: str) -> Tuple[List[str], Optional[Dict[str, Any]]]:
330
+ def get_response_schema(self, definition: dict[str, Any], scope: str) -> tuple[list[str], dict[str, Any] | None]:
320
331
  """Extract response schema from `responses`."""
321
332
  raise NotImplementedError
322
333
 
@@ -326,7 +337,7 @@ class BaseOpenAPISchema(BaseSchema):
326
337
  self._operations_by_id = dict(self._group_operations_by_id())
327
338
  return self._operations_by_id[operation_id]
328
339
 
329
- def _group_operations_by_id(self) -> Generator[Tuple[str, APIOperation], None, None]:
340
+ def _group_operations_by_id(self) -> Generator[tuple[str, APIOperation], None, None]:
330
341
  for path, methods in self.raw_schema["paths"].items():
331
342
  scope, raw_methods = self._resolve_methods(methods)
332
343
  common_parameters = self.resolver.resolve_all(methods.get("parameters", []), RECURSION_DEPTH_LIMIT - 8)
@@ -365,9 +376,10 @@ class BaseOpenAPISchema(BaseSchema):
365
376
  def get_case_strategy(
366
377
  self,
367
378
  operation: APIOperation,
368
- hooks: Optional[HookDispatcher] = None,
369
- auth_storage: Optional[AuthStorage] = None,
379
+ hooks: HookDispatcher | None = None,
380
+ auth_storage: AuthStorage | None = None,
370
381
  data_generation_method: DataGenerationMethod = DataGenerationMethod.default(),
382
+ generation_config: GenerationConfig | None = None,
371
383
  **kwargs: Any,
372
384
  ) -> SearchStrategy:
373
385
  return get_case_strategy(
@@ -375,10 +387,11 @@ class BaseOpenAPISchema(BaseSchema):
375
387
  auth_storage=auth_storage,
376
388
  hooks=hooks,
377
389
  generator=data_generation_method,
390
+ generation_config=generation_config,
378
391
  **kwargs,
379
392
  )
380
393
 
381
- def get_parameter_serializer(self, operation: APIOperation, location: str) -> Optional[Callable]:
394
+ def get_parameter_serializer(self, operation: APIOperation, location: str) -> Callable | None:
382
395
  definitions = [item for item in operation.definition.resolved.get("parameters", []) if item["in"] == location]
383
396
  security_parameters = self.security.get_security_definitions_as_parameters(
384
397
  self.raw_schema, operation, self.resolver, location
@@ -390,10 +403,10 @@ class BaseOpenAPISchema(BaseSchema):
390
403
  return self._get_parameter_serializer(definitions)
391
404
  return None
392
405
 
393
- def _get_parameter_serializer(self, definitions: List[Dict[str, Any]]) -> Optional[Callable]:
406
+ def _get_parameter_serializer(self, definitions: list[dict[str, Any]]) -> Callable | None:
394
407
  raise NotImplementedError
395
408
 
396
- def _get_response_definitions(self, operation: APIOperation, response: GenericResponse) -> Optional[Dict[str, Any]]:
409
+ def _get_response_definitions(self, operation: APIOperation, response: GenericResponse) -> dict[str, Any] | None:
397
410
  try:
398
411
  responses = operation.definition.resolved["responses"]
399
412
  except KeyError as exc:
@@ -408,23 +421,23 @@ class BaseOpenAPISchema(BaseSchema):
408
421
  return responses["default"]
409
422
  return None
410
423
 
411
- def get_headers(self, operation: APIOperation, response: GenericResponse) -> Optional[Dict[str, Dict[str, Any]]]:
424
+ def get_headers(self, operation: APIOperation, response: GenericResponse) -> dict[str, dict[str, Any]] | None:
412
425
  definitions = self._get_response_definitions(operation, response)
413
426
  if not definitions:
414
427
  return None
415
428
  return definitions.get("headers")
416
429
 
417
- def as_state_machine(self) -> Type[APIStateMachine]:
430
+ def as_state_machine(self) -> type[APIStateMachine]:
418
431
  return create_state_machine(self)
419
432
 
420
433
  def add_link(
421
434
  self,
422
435
  source: APIOperation,
423
- target: Union[str, APIOperation],
424
- status_code: Union[str, int],
425
- parameters: Optional[Dict[str, str]] = None,
436
+ target: str | APIOperation,
437
+ status_code: str | int,
438
+ parameters: dict[str, str] | None = None,
426
439
  request_body: Any = None,
427
- name: Optional[str] = None,
440
+ name: str | None = None,
428
441
  ) -> None:
429
442
  """Add a new Open API link to the schema definition.
430
443
 
@@ -493,13 +506,13 @@ class BaseOpenAPISchema(BaseSchema):
493
506
  message += " Check if the requested API operation passes the filters in the schema."
494
507
  raise ValueError(message)
495
508
 
496
- def get_links(self, operation: APIOperation) -> Dict[str, Dict[str, Any]]:
497
- result: Dict[str, Dict[str, Any]] = defaultdict(dict)
509
+ def get_links(self, operation: APIOperation) -> dict[str, dict[str, Any]]:
510
+ result: dict[str, dict[str, Any]] = defaultdict(dict)
498
511
  for status_code, link in links.get_all_links(operation):
499
512
  result[status_code][link.name] = link
500
513
  return result
501
514
 
502
- def validate_response(self, operation: APIOperation, response: GenericResponse) -> None:
515
+ def validate_response(self, operation: APIOperation, response: GenericResponse) -> bool | None:
503
516
  responses = {str(key): value for key, value in operation.definition.raw.get("responses", {}).items()}
504
517
  status_code = str(response.status_code)
505
518
  if status_code in responses:
@@ -508,11 +521,11 @@ class BaseOpenAPISchema(BaseSchema):
508
521
  definition = responses["default"]
509
522
  else:
510
523
  # No response defined for the received response status code
511
- return
524
+ return None
512
525
  scopes, schema = self.get_response_schema(definition, operation.definition.scope)
513
526
  if not schema:
514
527
  # No schema to check against
515
- return
528
+ return None
516
529
  content_type = response.headers.get("Content-Type")
517
530
  errors = []
518
531
  if content_type is None:
@@ -528,7 +541,7 @@ class BaseOpenAPISchema(BaseSchema):
528
541
  errors.append(exc)
529
542
  if content_type and not is_json_media_type(content_type):
530
543
  _maybe_raise_one_or_more(errors)
531
- return
544
+ return None
532
545
  try:
533
546
  if isinstance(response, (requests.Response, httpx.Response)):
534
547
  data = json.loads(response.text)
@@ -563,15 +576,15 @@ class BaseOpenAPISchema(BaseSchema):
563
576
  return None # explicitly return None for mypy
564
577
 
565
578
  @property
566
- def rewritten_components(self) -> Dict[str, Any]:
579
+ def rewritten_components(self) -> dict[str, Any]:
567
580
  if not hasattr(self, "_rewritten_components"):
568
581
 
569
- def callback(_schema: Dict[str, Any], nullable_name: str) -> Dict[str, Any]:
582
+ def callback(_schema: dict[str, Any], nullable_name: str) -> dict[str, Any]:
570
583
  _schema = to_json_schema(_schema, nullable_name=nullable_name, copy=False)
571
584
  return self._rewrite_references(_schema, self.resolver)
572
585
 
573
586
  # Different spec versions allow different keywords to store possible reference targets
574
- components: Dict[str, Any] = {}
587
+ components: dict[str, Any] = {}
575
588
  for path in self.component_locations:
576
589
  schema = self.raw_schema
577
590
  target = components
@@ -624,7 +637,7 @@ class BaseOpenAPISchema(BaseSchema):
624
637
  stack.append(sub_item)
625
638
  return schema
626
639
 
627
- def _rewrite_references(self, schema: Dict[str, Any], resolver: InliningResolver) -> Dict[str, Any]:
640
+ def _rewrite_references(self, schema: dict[str, Any], resolver: InliningResolver) -> dict[str, Any]:
628
641
  """Rewrite references present in the schema.
629
642
 
630
643
  The idea is to resolve references, cache the result and replace these references with new ones
@@ -646,7 +659,7 @@ class BaseOpenAPISchema(BaseSchema):
646
659
  return schema
647
660
 
648
661
 
649
- def _maybe_raise_one_or_more(errors: List[Exception]) -> None:
662
+ def _maybe_raise_one_or_more(errors: list[Exception]) -> None:
650
663
  if not errors:
651
664
  return
652
665
  elif len(errors) == 1:
@@ -655,7 +668,7 @@ def _maybe_raise_one_or_more(errors: List[Exception]) -> None:
655
668
  raise MultipleFailures("\n\n".join(str(error) for error in errors), errors)
656
669
 
657
670
 
658
- def _make_reference_key(scopes: List[str], reference: str) -> str:
671
+ def _make_reference_key(scopes: list[str], reference: str) -> str:
659
672
  """A name under which the resolved reference data will be stored."""
660
673
  # Using a hexdigest is the simplest way to associate practically unique keys with each reference
661
674
  digest = sha1()
@@ -681,7 +694,7 @@ def in_scope(resolver: jsonschema.RefResolver, scope: str) -> Generator[None, No
681
694
 
682
695
 
683
696
  @contextmanager
684
- def in_scopes(resolver: jsonschema.RefResolver, scopes: List[str]) -> Generator[None, None, None]:
697
+ def in_scopes(resolver: jsonschema.RefResolver, scopes: list[str]) -> Generator[None, None, None]:
685
698
  """Push all available scopes into the resolver.
686
699
 
687
700
  There could be an additional scope change during a schema resolving in `get_response_schema`, so in total there
@@ -705,7 +718,7 @@ class SwaggerV20(BaseOpenAPISchema):
705
718
  examples_field = "x-examples"
706
719
  header_required_field = "x-required"
707
720
  security = SwaggerSecurityProcessor()
708
- component_locations: ClassVar[Tuple[Tuple[str, ...], ...]] = (("definitions",),)
721
+ component_locations: ClassVar[tuple[tuple[str, ...], ...]] = (("definitions",),)
709
722
  links_field = "x-links"
710
723
 
711
724
  @property
@@ -723,11 +736,11 @@ class SwaggerV20(BaseOpenAPISchema):
723
736
  return self.raw_schema.get("basePath", "/")
724
737
 
725
738
  def collect_parameters(
726
- self, parameters: Iterable[Dict[str, Any]], definition: Dict[str, Any]
727
- ) -> List[OpenAPIParameter]:
739
+ self, parameters: Iterable[dict[str, Any]], definition: dict[str, Any]
740
+ ) -> list[OpenAPIParameter]:
728
741
  # The main difference with Open API 3.0 is that it has `body` and `form` parameters that we need to handle
729
742
  # differently.
730
- collected: List[OpenAPIParameter] = []
743
+ collected: list[OpenAPIParameter] = []
731
744
  # NOTE. The Open API 2.0 spec doesn't strictly imply having media types in the "consumes" keyword.
732
745
  # It is not enforced by the meta schema and has no "MUST" verb in the spec text.
733
746
  # Also, not every API has operations with payload (they might have only GET operations without payloads).
@@ -760,11 +773,11 @@ class SwaggerV20(BaseOpenAPISchema):
760
773
  )
761
774
  return collected
762
775
 
763
- def get_strategies_from_examples(self, operation: APIOperation) -> List[SearchStrategy[Case]]:
776
+ def get_strategies_from_examples(self, operation: APIOperation) -> list[SearchStrategy[Case]]:
764
777
  """Get examples from the API operation."""
765
778
  return get_strategies_from_examples(operation, self.examples_field)
766
779
 
767
- def get_response_schema(self, definition: Dict[str, Any], scope: str) -> Tuple[List[str], Optional[Dict[str, Any]]]:
780
+ def get_response_schema(self, definition: dict[str, Any], scope: str) -> tuple[list[str], dict[str, Any] | None]:
768
781
  scopes, definition = self.resolver.resolve_in_scope(fast_deepcopy(definition), scope)
769
782
  schema = definition.get("schema")
770
783
  if not schema:
@@ -773,18 +786,18 @@ class SwaggerV20(BaseOpenAPISchema):
773
786
  # because it is not converted
774
787
  return scopes, to_json_schema_recursive(schema, self.nullable_name, is_response_schema=True)
775
788
 
776
- def get_content_types(self, operation: APIOperation, response: GenericResponse) -> List[str]:
789
+ def get_content_types(self, operation: APIOperation, response: GenericResponse) -> list[str]:
777
790
  produces = operation.definition.raw.get("produces", None)
778
791
  if produces:
779
792
  return produces
780
793
  return self.raw_schema.get("produces", [])
781
794
 
782
- def _get_parameter_serializer(self, definitions: List[Dict[str, Any]]) -> Optional[Callable]:
795
+ def _get_parameter_serializer(self, definitions: list[dict[str, Any]]) -> Callable | None:
783
796
  return serialization.serialize_swagger2_parameters(definitions)
784
797
 
785
798
  def prepare_multipart(
786
799
  self, form_data: FormData, operation: APIOperation
787
- ) -> Tuple[Optional[List], Optional[Dict[str, Any]]]:
800
+ ) -> tuple[list | None, dict[str, Any] | None]:
788
801
  """Prepare form data for sending with `requests`.
789
802
 
790
803
  :param form_data: Raw generated data as a dictionary.
@@ -818,20 +831,20 @@ class SwaggerV20(BaseOpenAPISchema):
818
831
  # `None` is the default value for `files` and `data` arguments in `requests.request`
819
832
  return files or None, data or None
820
833
 
821
- def get_request_payload_content_types(self, operation: APIOperation) -> List[str]:
834
+ def get_request_payload_content_types(self, operation: APIOperation) -> list[str]:
822
835
  return self._get_consumes_for_operation(operation.definition.resolved)
823
836
 
824
837
  def make_case(
825
838
  self,
826
839
  *,
827
- case_cls: Type[C],
840
+ case_cls: type[C],
828
841
  operation: APIOperation,
829
- path_parameters: Optional[PathParameters] = None,
830
- headers: Optional[Headers] = None,
831
- cookies: Optional[Cookies] = None,
832
- query: Optional[Query] = None,
833
- body: Union[Body, NotSet] = NOT_SET,
834
- media_type: Optional[str] = None,
842
+ path_parameters: PathParameters | None = None,
843
+ headers: Headers | None = None,
844
+ cookies: Cookies | None = None,
845
+ query: Query | None = None,
846
+ body: Body | NotSet = NOT_SET,
847
+ media_type: str | None = None,
835
848
  ) -> C:
836
849
  if body is not NOT_SET and media_type is None:
837
850
  # If the user wants to send payload, then there should be a media type, otherwise the payload is ignored
@@ -856,7 +869,7 @@ class SwaggerV20(BaseOpenAPISchema):
856
869
  media_type=media_type,
857
870
  )
858
871
 
859
- def _get_consumes_for_operation(self, definition: Dict[str, Any]) -> List[str]:
872
+ def _get_consumes_for_operation(self, definition: dict[str, Any]) -> list[str]:
860
873
  """Get the `consumes` value for the given API operation.
861
874
 
862
875
  :param definition: Raw API operation definition.
@@ -869,7 +882,7 @@ class SwaggerV20(BaseOpenAPISchema):
869
882
  consumes = global_consumes
870
883
  return consumes
871
884
 
872
- def _get_payload_schema(self, definition: Dict[str, Any], media_type: str) -> Optional[Dict[str, Any]]:
885
+ def _get_payload_schema(self, definition: dict[str, Any], media_type: str) -> dict[str, Any] | None:
873
886
  for parameter in definition.get("parameters", []):
874
887
  if parameter["in"] == "body":
875
888
  return parameter["schema"]
@@ -910,10 +923,10 @@ class OpenApi30(SwaggerV20):
910
923
  return "/"
911
924
 
912
925
  def collect_parameters(
913
- self, parameters: Iterable[Dict[str, Any]], definition: Dict[str, Any]
914
- ) -> List[OpenAPIParameter]:
926
+ self, parameters: Iterable[dict[str, Any]], definition: dict[str, Any]
927
+ ) -> list[OpenAPIParameter]:
915
928
  # Open API 3.0 has the `requestBody` keyword, which may contain multiple different payload variants.
916
- collected: List[OpenAPIParameter] = [OpenAPI30Parameter(definition=parameter) for parameter in parameters]
929
+ collected: list[OpenAPIParameter] = [OpenAPI30Parameter(definition=parameter) for parameter in parameters]
917
930
  if "requestBody" in definition:
918
931
  required = definition["requestBody"].get("required", False)
919
932
  description = definition["requestBody"].get("description")
@@ -923,7 +936,7 @@ class OpenApi30(SwaggerV20):
923
936
  )
924
937
  return collected
925
938
 
926
- def get_response_schema(self, definition: Dict[str, Any], scope: str) -> Tuple[List[str], Optional[Dict[str, Any]]]:
939
+ def get_response_schema(self, definition: dict[str, Any], scope: str) -> tuple[list[str], dict[str, Any] | None]:
927
940
  scopes, definition = self.resolver.resolve_in_scope(fast_deepcopy(definition), scope)
928
941
  options = iter(definition.get("content", {}).values())
929
942
  option = next(options, None)
@@ -934,25 +947,25 @@ class OpenApi30(SwaggerV20):
934
947
  return scopes, to_json_schema_recursive(option["schema"], self.nullable_name, is_response_schema=True)
935
948
  return scopes, None
936
949
 
937
- def get_strategies_from_examples(self, operation: APIOperation) -> List[SearchStrategy[Case]]:
950
+ def get_strategies_from_examples(self, operation: APIOperation) -> list[SearchStrategy[Case]]:
938
951
  """Get examples from the API operation."""
939
952
  return get_strategies_from_examples(operation, self.examples_field)
940
953
 
941
- def get_content_types(self, operation: APIOperation, response: GenericResponse) -> List[str]:
954
+ def get_content_types(self, operation: APIOperation, response: GenericResponse) -> list[str]:
942
955
  definitions = self._get_response_definitions(operation, response)
943
956
  if not definitions:
944
957
  return []
945
958
  return list(definitions.get("content", {}).keys())
946
959
 
947
- def _get_parameter_serializer(self, definitions: List[Dict[str, Any]]) -> Optional[Callable]:
960
+ def _get_parameter_serializer(self, definitions: list[dict[str, Any]]) -> Callable | None:
948
961
  return serialization.serialize_openapi3_parameters(definitions)
949
962
 
950
- def get_request_payload_content_types(self, operation: APIOperation) -> List[str]:
963
+ def get_request_payload_content_types(self, operation: APIOperation) -> list[str]:
951
964
  return list(operation.definition.resolved["requestBody"]["content"].keys())
952
965
 
953
966
  def prepare_multipart(
954
967
  self, form_data: FormData, operation: APIOperation
955
- ) -> Tuple[Optional[List], Optional[Dict[str, Any]]]:
968
+ ) -> tuple[list | None, dict[str, Any] | None]:
956
969
  """Prepare form data for sending with `requests`.
957
970
 
958
971
  :param form_data: Raw generated data as a dictionary.
@@ -975,7 +988,7 @@ class OpenApi30(SwaggerV20):
975
988
  # `None` is the default value for `files` and `data` arguments in `requests.request`
976
989
  return files or None, None
977
990
 
978
- def _get_payload_schema(self, definition: Dict[str, Any], media_type: str) -> Optional[Dict[str, Any]]:
991
+ def _get_payload_schema(self, definition: dict[str, Any], media_type: str) -> dict[str, Any] | None:
979
992
  if "requestBody" in definition:
980
993
  if "$ref" in definition["requestBody"]:
981
994
  body = self.resolver.resolve_all(definition["requestBody"], RECURSION_DEPTH_LIMIT)
@@ -1,6 +1,7 @@
1
1
  """Processing of ``securityDefinitions`` or ``securitySchemes`` keywords."""
2
+ from __future__ import annotations
2
3
  from dataclasses import dataclass
3
- from typing import Any, ClassVar, Dict, Generator, List, Tuple, Type
4
+ from typing import Any, ClassVar, Generator
4
5
 
5
6
  from jsonschema import RefResolver
6
7
 
@@ -10,11 +11,11 @@ from .parameters import OpenAPI20Parameter, OpenAPI30Parameter, OpenAPIParameter
10
11
 
11
12
  @dataclass
12
13
  class BaseSecurityProcessor:
13
- api_key_locations: ClassVar[Tuple[str, ...]] = ("header", "query")
14
+ api_key_locations: ClassVar[tuple[str, ...]] = ("header", "query")
14
15
  http_security_name: ClassVar[str] = "basic"
15
- parameter_cls: ClassVar[Type[OpenAPIParameter]] = OpenAPI20Parameter
16
+ parameter_cls: ClassVar[type[OpenAPIParameter]] = OpenAPI20Parameter
16
17
 
17
- def process_definitions(self, schema: Dict[str, Any], operation: APIOperation, resolver: RefResolver) -> None:
18
+ def process_definitions(self, schema: dict[str, Any], operation: APIOperation, resolver: RefResolver) -> None:
18
19
  """Add relevant security parameters to data generation."""
19
20
  __tracebackhide__ = True
20
21
  for definition in self._get_active_definitions(schema, operation, resolver):
@@ -28,7 +29,7 @@ class BaseSecurityProcessor:
28
29
  self.process_http_security_definition(definition, operation)
29
30
 
30
31
  @staticmethod
31
- def get_security_requirements(schema: Dict[str, Any], operation: APIOperation) -> List[str]:
32
+ def get_security_requirements(schema: dict[str, Any], operation: APIOperation) -> list[str]:
32
33
  """Get applied security requirements for the given API operation."""
33
34
  # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object
34
35
  # > This definition overrides any declared top-level security.
@@ -42,8 +43,8 @@ class BaseSecurityProcessor:
42
43
  return [key for requirement in requirements for key in requirement]
43
44
 
44
45
  def _get_active_definitions(
45
- self, schema: Dict[str, Any], operation: APIOperation, resolver: RefResolver
46
- ) -> Generator[Dict[str, Any], None, None]:
46
+ self, schema: dict[str, Any], operation: APIOperation, resolver: RefResolver
47
+ ) -> Generator[dict[str, Any], None, None]:
47
48
  """Get only security definitions active for the given API operation."""
48
49
  definitions = self.get_security_definitions(schema, resolver)
49
50
  requirements = self.get_security_requirements(schema, operation)
@@ -51,12 +52,12 @@ class BaseSecurityProcessor:
51
52
  if name in requirements:
52
53
  yield definition
53
54
 
54
- def get_security_definitions(self, schema: Dict[str, Any], resolver: RefResolver) -> Dict[str, Any]:
55
+ def get_security_definitions(self, schema: dict[str, Any], resolver: RefResolver) -> dict[str, Any]:
55
56
  return schema.get("securityDefinitions", {})
56
57
 
57
58
  def get_security_definitions_as_parameters(
58
- self, schema: Dict[str, Any], operation: APIOperation, resolver: RefResolver, location: str
59
- ) -> List[Dict[str, Any]]:
59
+ self, schema: dict[str, Any], operation: APIOperation, resolver: RefResolver, location: str
60
+ ) -> list[dict[str, Any]]:
60
61
  """Security definitions converted to OAS parameters.
61
62
 
62
63
  We need it to get proper serialization that will be applied on generated values. For this case it is only
@@ -68,45 +69,45 @@ class BaseSecurityProcessor:
68
69
  if self._is_match(definition, location)
69
70
  ]
70
71
 
71
- def process_api_key_security_definition(self, definition: Dict[str, Any], operation: APIOperation) -> None:
72
+ def process_api_key_security_definition(self, definition: dict[str, Any], operation: APIOperation) -> None:
72
73
  parameter = self.parameter_cls(self._make_api_key_parameter(definition))
73
74
  operation.add_parameter(parameter)
74
75
 
75
- def process_http_security_definition(self, definition: Dict[str, Any], operation: APIOperation) -> None:
76
+ def process_http_security_definition(self, definition: dict[str, Any], operation: APIOperation) -> None:
76
77
  if definition["type"] == self.http_security_name:
77
78
  parameter = self.parameter_cls(self._make_http_auth_parameter(definition))
78
79
  operation.add_parameter(parameter)
79
80
 
80
- def _is_match(self, definition: Dict[str, Any], location: str) -> bool:
81
+ def _is_match(self, definition: dict[str, Any], location: str) -> bool:
81
82
  return (definition["type"] == "apiKey" and location in self.api_key_locations) or (
82
83
  definition["type"] == self.http_security_name and location == "header"
83
84
  )
84
85
 
85
- def _to_parameter(self, definition: Dict[str, Any]) -> Dict[str, Any]:
86
+ def _to_parameter(self, definition: dict[str, Any]) -> dict[str, Any]:
86
87
  func = {
87
88
  "apiKey": self._make_api_key_parameter,
88
89
  self.http_security_name: self._make_http_auth_parameter,
89
90
  }[definition["type"]]
90
91
  return func(definition)
91
92
 
92
- def _make_http_auth_parameter(self, definition: Dict[str, Any]) -> Dict[str, Any]:
93
+ def _make_http_auth_parameter(self, definition: dict[str, Any]) -> dict[str, Any]:
93
94
  schema = make_auth_header_schema(definition)
94
95
  return make_auth_header(**schema)
95
96
 
96
- def _make_api_key_parameter(self, definition: Dict[str, Any]) -> Dict[str, Any]:
97
+ def _make_api_key_parameter(self, definition: dict[str, Any]) -> dict[str, Any]:
97
98
  return make_api_key_schema(definition, type="string")
98
99
 
99
100
 
100
- def make_auth_header_schema(definition: Dict[str, Any]) -> Dict[str, str]:
101
+ def make_auth_header_schema(definition: dict[str, Any]) -> dict[str, str]:
101
102
  schema = definition.get("scheme", "basic").lower()
102
103
  return {"type": "string", "format": f"_{schema}_auth"}
103
104
 
104
105
 
105
- def make_auth_header(**kwargs: Any) -> Dict[str, Any]:
106
+ def make_auth_header(**kwargs: Any) -> dict[str, Any]:
106
107
  return {"name": "Authorization", "in": "header", "required": True, **kwargs}
107
108
 
108
109
 
109
- def make_api_key_schema(definition: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]:
110
+ def make_api_key_schema(definition: dict[str, Any], **kwargs: Any) -> dict[str, Any]:
110
111
  return {"name": definition["name"], "required": True, "in": definition["in"], **kwargs}
111
112
 
112
113
 
@@ -115,11 +116,11 @@ SwaggerSecurityProcessor = BaseSecurityProcessor
115
116
 
116
117
  @dataclass
117
118
  class OpenAPISecurityProcessor(BaseSecurityProcessor):
118
- api_key_locations: ClassVar[Tuple[str, ...]] = ("header", "cookie", "query")
119
+ api_key_locations: ClassVar[tuple[str, ...]] = ("header", "cookie", "query")
119
120
  http_security_name: ClassVar[str] = "http"
120
- parameter_cls: ClassVar[Type[OpenAPIParameter]] = OpenAPI30Parameter
121
+ parameter_cls: ClassVar[type[OpenAPIParameter]] = OpenAPI30Parameter
121
122
 
122
- def get_security_definitions(self, schema: Dict[str, Any], resolver: RefResolver) -> Dict[str, Any]:
123
+ def get_security_definitions(self, schema: dict[str, Any], resolver: RefResolver) -> dict[str, Any]:
123
124
  """In Open API 3 security definitions are located in ``components`` and may have references inside."""
124
125
  components = schema.get("components", {})
125
126
  security_schemes = components.get("securitySchemes", {})
@@ -127,9 +128,9 @@ class OpenAPISecurityProcessor(BaseSecurityProcessor):
127
128
  return resolver.resolve(security_schemes["$ref"])[1]
128
129
  return security_schemes
129
130
 
130
- def _make_http_auth_parameter(self, definition: Dict[str, Any]) -> Dict[str, Any]:
131
+ def _make_http_auth_parameter(self, definition: dict[str, Any]) -> dict[str, Any]:
131
132
  schema = make_auth_header_schema(definition)
132
133
  return make_auth_header(schema=schema)
133
134
 
134
- def _make_api_key_parameter(self, definition: Dict[str, Any]) -> Dict[str, Any]:
135
+ def _make_api_key_parameter(self, definition: dict[str, Any]) -> dict[str, Any]:
135
136
  return make_api_key_schema(definition, schema={"type": "string"})