schemathesis 4.1.4__py3-none-any.whl → 4.2.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 (70) hide show
  1. schemathesis/cli/commands/run/executor.py +1 -1
  2. schemathesis/cli/commands/run/handlers/base.py +28 -1
  3. schemathesis/cli/commands/run/handlers/cassettes.py +109 -137
  4. schemathesis/cli/commands/run/handlers/junitxml.py +5 -6
  5. schemathesis/cli/commands/run/handlers/output.py +7 -1
  6. schemathesis/cli/ext/fs.py +1 -1
  7. schemathesis/config/_diff_base.py +3 -1
  8. schemathesis/config/_operations.py +2 -0
  9. schemathesis/config/_phases.py +21 -4
  10. schemathesis/config/_projects.py +10 -2
  11. schemathesis/core/adapter.py +34 -0
  12. schemathesis/core/errors.py +29 -5
  13. schemathesis/core/jsonschema/__init__.py +13 -0
  14. schemathesis/core/jsonschema/bundler.py +163 -0
  15. schemathesis/{specs/openapi/constants.py → core/jsonschema/keywords.py} +0 -8
  16. schemathesis/core/jsonschema/references.py +122 -0
  17. schemathesis/core/jsonschema/types.py +41 -0
  18. schemathesis/core/media_types.py +6 -4
  19. schemathesis/core/parameters.py +37 -0
  20. schemathesis/core/transforms.py +25 -2
  21. schemathesis/core/validation.py +19 -0
  22. schemathesis/engine/context.py +1 -1
  23. schemathesis/engine/errors.py +11 -18
  24. schemathesis/engine/phases/stateful/_executor.py +1 -1
  25. schemathesis/engine/phases/unit/_executor.py +30 -13
  26. schemathesis/errors.py +4 -0
  27. schemathesis/filters.py +2 -2
  28. schemathesis/generation/coverage.py +87 -11
  29. schemathesis/generation/hypothesis/__init__.py +79 -2
  30. schemathesis/generation/hypothesis/builder.py +108 -70
  31. schemathesis/generation/meta.py +5 -14
  32. schemathesis/generation/overrides.py +17 -17
  33. schemathesis/pytest/lazy.py +1 -1
  34. schemathesis/pytest/plugin.py +1 -6
  35. schemathesis/schemas.py +22 -72
  36. schemathesis/specs/graphql/schemas.py +27 -16
  37. schemathesis/specs/openapi/_hypothesis.py +83 -68
  38. schemathesis/specs/openapi/adapter/__init__.py +10 -0
  39. schemathesis/specs/openapi/adapter/parameters.py +504 -0
  40. schemathesis/specs/openapi/adapter/protocol.py +57 -0
  41. schemathesis/specs/openapi/adapter/references.py +19 -0
  42. schemathesis/specs/openapi/adapter/responses.py +329 -0
  43. schemathesis/specs/openapi/adapter/security.py +141 -0
  44. schemathesis/specs/openapi/adapter/v2.py +28 -0
  45. schemathesis/specs/openapi/adapter/v3_0.py +28 -0
  46. schemathesis/specs/openapi/adapter/v3_1.py +28 -0
  47. schemathesis/specs/openapi/checks.py +99 -90
  48. schemathesis/specs/openapi/converter.py +114 -27
  49. schemathesis/specs/openapi/examples.py +210 -168
  50. schemathesis/specs/openapi/negative/__init__.py +12 -7
  51. schemathesis/specs/openapi/negative/mutations.py +68 -40
  52. schemathesis/specs/openapi/references.py +2 -175
  53. schemathesis/specs/openapi/schemas.py +142 -490
  54. schemathesis/specs/openapi/serialization.py +15 -7
  55. schemathesis/specs/openapi/stateful/__init__.py +17 -12
  56. schemathesis/specs/openapi/stateful/inference.py +13 -11
  57. schemathesis/specs/openapi/stateful/links.py +5 -20
  58. schemathesis/specs/openapi/types/__init__.py +3 -0
  59. schemathesis/specs/openapi/types/v3.py +68 -0
  60. schemathesis/specs/openapi/utils.py +1 -13
  61. schemathesis/transport/requests.py +3 -11
  62. schemathesis/transport/serialization.py +63 -27
  63. schemathesis/transport/wsgi.py +1 -8
  64. {schemathesis-4.1.4.dist-info → schemathesis-4.2.1.dist-info}/METADATA +2 -2
  65. {schemathesis-4.1.4.dist-info → schemathesis-4.2.1.dist-info}/RECORD +68 -53
  66. schemathesis/specs/openapi/parameters.py +0 -405
  67. schemathesis/specs/openapi/security.py +0 -162
  68. {schemathesis-4.1.4.dist-info → schemathesis-4.2.1.dist-info}/WHEEL +0 -0
  69. {schemathesis-4.1.4.dist-info → schemathesis-4.2.1.dist-info}/entry_points.txt +0 -0
  70. {schemathesis-4.1.4.dist-info → schemathesis-4.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,67 +1,57 @@
1
1
  from __future__ import annotations
2
2
 
3
- import itertools
4
3
  import string
5
- from collections import defaultdict
6
- from contextlib import ExitStack, contextmanager, suppress
7
- from dataclasses import dataclass, field
4
+ from contextlib import contextmanager, suppress
5
+ from dataclasses import dataclass
8
6
  from difflib import get_close_matches
9
- from hashlib import sha1
7
+ from functools import cached_property, lru_cache
10
8
  from json import JSONDecodeError
11
- from threading import RLock
12
9
  from types import SimpleNamespace
13
10
  from typing import (
14
11
  TYPE_CHECKING,
15
12
  Any,
16
13
  Callable,
17
- ClassVar,
18
14
  Generator,
19
- Iterable,
20
15
  Iterator,
21
16
  Mapping,
22
17
  NoReturn,
18
+ Sequence,
23
19
  cast,
24
20
  )
25
21
  from urllib.parse import urlsplit
26
22
 
27
23
  import jsonschema
28
24
  from packaging import version
29
- from requests.exceptions import InvalidHeader
30
25
  from requests.structures import CaseInsensitiveDict
31
- from requests.utils import check_header_validity
32
26
 
33
27
  from schemathesis.core import INJECTED_PATH_PARAMETER_KEY, NOT_SET, NotSet, Specification, deserialization, media_types
28
+ from schemathesis.core.adapter import OperationParameter, ResponsesContainer
34
29
  from schemathesis.core.compat import RefResolutionError
35
- from schemathesis.core.errors import InternalError, InvalidSchema, LoaderError, LoaderErrorKind, OperationNotFound
30
+ from schemathesis.core.errors import InfiniteRecursiveReference, InvalidSchema, OperationNotFound
36
31
  from schemathesis.core.failures import Failure, FailureGroup, MalformedJson
37
32
  from schemathesis.core.result import Err, Ok, Result
38
- from schemathesis.core.transforms import UNRESOLVABLE, deepclone, resolve_pointer, transform
39
33
  from schemathesis.core.transport import Response
40
- from schemathesis.core.validation import INVALID_HEADER_RE
41
34
  from schemathesis.generation.case import Case
42
35
  from schemathesis.generation.meta import CaseMetadata
43
36
  from schemathesis.openapi.checks import JsonSchemaError, MissingContentType
44
- from schemathesis.specs.openapi.stateful import links
45
- from schemathesis.specs.openapi.utils import expand_status_code
37
+ from schemathesis.specs.openapi import adapter
38
+ from schemathesis.specs.openapi.adapter import OpenApiResponses
39
+ from schemathesis.specs.openapi.adapter.parameters import (
40
+ COMBINED_FORM_DATA_MARKER,
41
+ OpenApiParameter,
42
+ OpenApiParameterSet,
43
+ )
44
+ from schemathesis.specs.openapi.adapter.protocol import SpecificationAdapter
45
+ from schemathesis.specs.openapi.adapter.security import OpenApiSecurityParameters
46
46
 
47
47
  from ...generation import GenerationMode
48
48
  from ...hooks import HookContext, HookDispatcher
49
49
  from ...schemas import APIOperation, APIOperationMap, ApiStatistic, BaseSchema, OperationDefinition
50
50
  from . import serialization
51
51
  from ._hypothesis import openapi_cases
52
- from .converter import to_json_schema, to_json_schema_recursive
53
52
  from .definitions import OPENAPI_30_VALIDATOR, OPENAPI_31_VALIDATOR, SWAGGER_20_VALIDATOR
54
53
  from .examples import get_strategies_from_examples
55
- from .parameters import (
56
- OpenAPI20Body,
57
- OpenAPI20CompositeBody,
58
- OpenAPI20Parameter,
59
- OpenAPI30Body,
60
- OpenAPI30Parameter,
61
- OpenAPIParameter,
62
- )
63
- from .references import RECURSION_DEPTH_LIMIT, ConvertingResolver, InliningResolver
64
- from .security import BaseSecurityProcessor, OpenAPISecurityProcessor, SwaggerSecurityProcessor
54
+ from .references import ReferenceResolver
65
55
  from .stateful import create_state_machine
66
56
 
67
57
  if TYPE_CHECKING:
@@ -72,24 +62,10 @@ if TYPE_CHECKING:
72
62
 
73
63
  HTTP_METHODS = frozenset({"get", "put", "post", "delete", "options", "head", "patch", "trace"})
74
64
  SCHEMA_ERROR_MESSAGE = "Ensure that the definition complies with the OpenAPI specification"
75
- SCHEMA_PARSING_ERRORS = (KeyError, AttributeError, RefResolutionError, InvalidSchema)
76
-
77
-
78
- def check_header(parameter: dict[str, Any]) -> None:
79
- name = parameter["name"]
80
- if not name:
81
- raise InvalidSchema("Header name should not be empty")
82
- if not name.isascii():
83
- # `urllib3` encodes header names to ASCII
84
- raise InvalidSchema(f"Header name should be ASCII: {name}")
85
- try:
86
- check_header_validity((name, ""))
87
- except InvalidHeader as exc:
88
- raise InvalidSchema(str(exc)) from None
89
- if bool(INVALID_HEADER_RE.search(name)):
90
- raise InvalidSchema(f"Invalid header name: {name}")
65
+ SCHEMA_PARSING_ERRORS = (KeyError, AttributeError, RefResolutionError, InvalidSchema, InfiniteRecursiveReference)
91
66
 
92
67
 
68
+ @lru_cache()
93
69
  def get_template_fields(template: str) -> set[str]:
94
70
  """Extract named placeholders from a string template."""
95
71
  try:
@@ -103,16 +79,7 @@ def get_template_fields(template: str) -> set[str]:
103
79
 
104
80
  @dataclass(eq=False, repr=False)
105
81
  class BaseOpenAPISchema(BaseSchema):
106
- nullable_name: ClassVar[str] = ""
107
- links_field: ClassVar[str] = ""
108
- header_required_field: ClassVar[str] = ""
109
- security: ClassVar[BaseSecurityProcessor] = None # type: ignore
110
- _inline_reference_cache: dict[str, Any] = field(default_factory=dict)
111
- # Inline references cache can be populated from multiple threads, therefore we need some synchronisation to avoid
112
- # excessive resolving
113
- _inline_reference_cache_lock: RLock = field(default_factory=RLock)
114
- component_locations: ClassVar[tuple[tuple[str, ...], ...]] = ()
115
- _path_parameter_template: ClassVar[dict[str, Any]] = None # type: ignore
82
+ adapter: SpecificationAdapter = None # type: ignore
116
83
 
117
84
  @property
118
85
  def specification(self) -> Specification:
@@ -125,6 +92,10 @@ class BaseOpenAPISchema(BaseSchema):
125
92
  def __iter__(self) -> Iterator[str]:
126
93
  return iter(self.raw_schema.get("paths", {}))
127
94
 
95
+ @cached_property
96
+ def default_media_types(self) -> list[str]:
97
+ raise NotImplementedError
98
+
128
99
  def _get_operation_map(self, path: str) -> APIOperationMap:
129
100
  path_item = self.raw_schema.get("paths", {})[path]
130
101
  with in_scope(self.resolver, self.location or ""):
@@ -158,8 +129,10 @@ class BaseOpenAPISchema(BaseSchema):
158
129
  method="",
159
130
  path="",
160
131
  label="",
161
- definition=OperationDefinition(raw=None, resolved=None, scope=""),
132
+ definition=OperationDefinition(raw=None),
162
133
  schema=None, # type: ignore
134
+ responses=None, # type: ignore
135
+ security=None, # type: ignore
163
136
  )
164
137
  ),
165
138
  ) -> bool:
@@ -173,7 +146,6 @@ class BaseOpenAPISchema(BaseSchema):
173
146
  operation.path = path
174
147
  operation.label = f"{method.upper()} {path}"
175
148
  operation.definition.raw = definition
176
- operation.definition.resolved = definition
177
149
  operation.schema = self
178
150
  return not self.filter_set.match(_ctx_cache)
179
151
 
@@ -187,7 +159,7 @@ class BaseOpenAPISchema(BaseSchema):
187
159
  resolve = self.resolver.resolve
188
160
  resolve_path_item = self._resolve_path_item
189
161
  should_skip = self._should_skip
190
- links_field = self.links_field
162
+ links_keyword = self.adapter.links_keyword
191
163
 
192
164
  # For operationId lookup
193
165
  selected_operations_by_id: set[str] = set()
@@ -214,7 +186,7 @@ class BaseOpenAPISchema(BaseSchema):
214
186
  for response in definition.get("responses", {}).values():
215
187
  if "$ref" in response:
216
188
  _, response = resolve(response["$ref"])
217
- defined_links = response.get(links_field)
189
+ defined_links = response.get(links_keyword)
218
190
  if defined_links is not None:
219
191
  statistic.links.total += len(defined_links)
220
192
  if is_selected:
@@ -263,24 +235,6 @@ class BaseOpenAPISchema(BaseSchema):
263
235
  except SCHEMA_PARSING_ERRORS:
264
236
  continue
265
237
 
266
- def _resolve_until_no_references(self, value: dict[str, Any]) -> dict[str, Any]:
267
- while "$ref" in value:
268
- _, value = self.resolver.resolve(value["$ref"])
269
- return value
270
-
271
- def _resolve_shared_parameters(self, path_item: Mapping[str, Any]) -> list[dict[str, Any]]:
272
- return self.resolver.resolve_all(path_item.get("parameters", []), RECURSION_DEPTH_LIMIT - 8)
273
-
274
- def _resolve_operation(self, operation: dict[str, Any]) -> dict[str, Any]:
275
- return self.resolver.resolve_all(operation, RECURSION_DEPTH_LIMIT - 8)
276
-
277
- def _collect_operation_parameters(
278
- self, path_item: Mapping[str, Any], operation: dict[str, Any]
279
- ) -> list[OpenAPIParameter]:
280
- shared_parameters = self._resolve_shared_parameters(path_item)
281
- parameters = operation.get("parameters", ())
282
- return self.collect_parameters(itertools.chain(parameters, shared_parameters), operation)
283
-
284
238
  def get_all_operations(self) -> Generator[Result[APIOperation, InvalidSchema], None, None]:
285
239
  """Iterate over all operations defined in the API.
286
240
 
@@ -311,10 +265,8 @@ class BaseOpenAPISchema(BaseSchema):
311
265
  # Optimization: local variables are faster than attribute access
312
266
  dispatch_hook = self.dispatch_hook
313
267
  resolve_path_item = self._resolve_path_item
314
- resolve_shared_parameters = self._resolve_shared_parameters
315
- resolve_operation = self._resolve_operation
316
268
  should_skip = self._should_skip
317
- collect_parameters = self.collect_parameters
269
+ iter_parameters = self._iter_parameters
318
270
  make_operation = self.make_operation
319
271
  for path, path_item in paths.items():
320
272
  method = None
@@ -322,22 +274,19 @@ class BaseOpenAPISchema(BaseSchema):
322
274
  dispatch_hook("before_process_path", context, path, path_item)
323
275
  scope, path_item = resolve_path_item(path_item)
324
276
  with in_scope(self.resolver, scope):
325
- shared_parameters = resolve_shared_parameters(path_item)
277
+ shared_parameters = path_item.get("parameters", [])
326
278
  for method, entry in path_item.items():
327
279
  if method not in HTTP_METHODS:
328
280
  continue
329
281
  try:
330
- resolved = resolve_operation(entry)
331
- if should_skip(path, method, resolved):
282
+ if should_skip(path, method, entry):
332
283
  continue
333
- parameters = resolved.get("parameters", ())
334
- parameters = collect_parameters(itertools.chain(parameters, shared_parameters), resolved)
284
+ parameters = iter_parameters(entry, shared_parameters)
335
285
  operation = make_operation(
336
286
  path,
337
287
  method,
338
288
  parameters,
339
289
  entry,
340
- resolved,
341
290
  scope,
342
291
  )
343
292
  yield Ok(operation)
@@ -360,6 +309,8 @@ class BaseOpenAPISchema(BaseSchema):
360
309
  method: str | None = None,
361
310
  ) -> NoReturn:
362
311
  __tracebackhide__ = True
312
+ if isinstance(error, InfiniteRecursiveReference):
313
+ raise InvalidSchema(str(error), path=path, method=method) from None
363
314
  if isinstance(error, RefResolutionError):
364
315
  raise InvalidSchema.from_reference_resolution_error(error, path=path, method=method) from None
365
316
  try:
@@ -377,15 +328,28 @@ class BaseOpenAPISchema(BaseSchema):
377
328
  def _validate(self) -> None:
378
329
  raise NotImplementedError
379
330
 
380
- def collect_parameters(
381
- self, parameters: Iterable[dict[str, Any]], definition: dict[str, Any]
382
- ) -> list[OpenAPIParameter]:
383
- """Collect Open API parameters.
331
+ def _iter_parameters(
332
+ self, definition: dict[str, Any], shared_parameters: Sequence[dict[str, Any]]
333
+ ) -> list[OperationParameter]:
334
+ return list(
335
+ self.adapter.iter_parameters(
336
+ definition, shared_parameters, self.default_media_types, self.resolver, self.adapter
337
+ )
338
+ )
384
339
 
385
- They should be used uniformly during the generation step; therefore, we need to convert them into
386
- a spec-independent list of parameters.
387
- """
388
- raise NotImplementedError
340
+ def _parse_responses(self, definition: dict[str, Any], scope: str) -> OpenApiResponses:
341
+ responses = definition.get("responses", {})
342
+ return OpenApiResponses.from_definition(
343
+ definition=responses, resolver=self.resolver, scope=scope, adapter=self.adapter
344
+ )
345
+
346
+ def _parse_security(self, definition: dict[str, Any]) -> OpenApiSecurityParameters:
347
+ return OpenApiSecurityParameters.from_definition(
348
+ schema=self.raw_schema,
349
+ operation=definition,
350
+ resolver=self.resolver,
351
+ adapter=self.adapter,
352
+ )
389
353
 
390
354
  def _resolve_path_item(self, methods: dict[str, Any]) -> tuple[str, dict[str, Any]]:
391
355
  # The path item could be behind a reference
@@ -399,20 +363,27 @@ class BaseOpenAPISchema(BaseSchema):
399
363
  self,
400
364
  path: str,
401
365
  method: str,
402
- parameters: list[OpenAPIParameter],
403
- raw: dict[str, Any],
404
- resolved: dict[str, Any],
366
+ parameters: list[OperationParameter],
367
+ definition: dict[str, Any],
405
368
  scope: str,
406
369
  ) -> APIOperation:
407
370
  __tracebackhide__ = True
408
371
  base_url = self.get_base_url()
409
- operation: APIOperation[OpenAPIParameter] = APIOperation(
372
+ responses = self._parse_responses(definition, scope)
373
+ security = self._parse_security(definition)
374
+ operation: APIOperation[OperationParameter, ResponsesContainer, OpenApiSecurityParameters] = APIOperation(
410
375
  path=path,
411
376
  method=method,
412
- definition=OperationDefinition(raw, resolved, scope),
377
+ definition=OperationDefinition(definition),
413
378
  base_url=base_url,
414
379
  app=self.app,
415
380
  schema=self,
381
+ responses=responses,
382
+ security=security,
383
+ path_parameters=OpenApiParameterSet(),
384
+ query=OpenApiParameterSet(),
385
+ headers=OpenApiParameterSet(),
386
+ cookies=OpenApiParameterSet(),
416
387
  )
417
388
  for parameter in parameters:
418
389
  operation.add_parameter(parameter)
@@ -421,19 +392,28 @@ class BaseOpenAPISchema(BaseSchema):
421
392
  parameter.name for parameter in operation.path_parameters
422
393
  }
423
394
  for name in missing_parameter_names:
424
- definition = {"name": name, INJECTED_PATH_PARAMETER_KEY: True, **deepclone(self._path_parameter_template)}
425
- for parameter in self.collect_parameters([definition], resolved):
426
- operation.add_parameter(parameter)
395
+ operation.add_parameter(
396
+ self.adapter.build_path_parameter({"name": name, INJECTED_PATH_PARAMETER_KEY: True})
397
+ )
427
398
  config = self.config.generation_for(operation=operation)
428
399
  if config.with_security_parameters:
429
- self.security.process_definitions(self.raw_schema, operation, self.resolver)
400
+ for param in operation.security.iter_parameters():
401
+ param_name = param.get("name")
402
+ param_location = param.get("in")
403
+ if (
404
+ param_name is not None
405
+ and param_location is not None
406
+ and operation.get_parameter(name=param_name, location=param_location) is not None
407
+ ):
408
+ continue
409
+ operation.add_parameter(OpenApiParameter.from_definition(definition=param, adapter=self.adapter))
430
410
  self.dispatch_hook("before_init_operation", HookContext(operation=operation), operation)
431
411
  return operation
432
412
 
433
413
  @property
434
- def resolver(self) -> InliningResolver:
414
+ def resolver(self) -> ReferenceResolver:
435
415
  if not hasattr(self, "_resolver"):
436
- self._resolver = InliningResolver(self.location or "", self.raw_schema)
416
+ self._resolver = ReferenceResolver(self.location or "", self.raw_schema)
437
417
  return self._resolver
438
418
 
439
419
  def get_content_types(self, operation: APIOperation, response: Response) -> list[str]:
@@ -444,14 +424,6 @@ class BaseOpenAPISchema(BaseSchema):
444
424
  """Get examples from the API operation."""
445
425
  raise NotImplementedError
446
426
 
447
- def get_security_requirements(self, operation: APIOperation) -> list[str]:
448
- """Get applied security requirements for the given API operation."""
449
- return self.security.get_security_requirements(self.raw_schema, operation)
450
-
451
- def get_response_schema(self, definition: dict[str, Any], scope: str) -> tuple[list[str], dict[str, Any] | None]:
452
- """Extract response schema from `responses`."""
453
- raise NotImplementedError
454
-
455
427
  def get_operation_by_id(self, operation_id: str) -> APIOperation:
456
428
  """Get an `APIOperation` instance by its `operationId`."""
457
429
  resolve = self.resolver.resolve
@@ -467,9 +439,8 @@ class BaseOpenAPISchema(BaseSchema):
467
439
  if method not in HTTP_METHODS:
468
440
  continue
469
441
  if "operationId" in operation and operation["operationId"] == operation_id:
470
- resolved = self._resolve_operation(operation)
471
- parameters = self._collect_operation_parameters(path_item, resolved)
472
- return self.make_operation(path, method, parameters, operation, resolved, scope)
442
+ parameters = self._iter_parameters(operation, path_item.get("parameters", []))
443
+ return self.make_operation(path, method, parameters, operation, scope)
473
444
  self._on_missing_operation(operation_id, None, [])
474
445
 
475
446
  def get_operation_by_reference(self, reference: str) -> APIOperation:
@@ -480,12 +451,11 @@ class BaseOpenAPISchema(BaseSchema):
480
451
  scope, operation = self.resolver.resolve(reference)
481
452
  path, method = scope.rsplit("/", maxsplit=2)[-2:]
482
453
  path = path.replace("~1", "/").replace("~0", "~")
483
- with in_scope(self.resolver, scope):
484
- resolved = self._resolve_operation(operation)
485
454
  parent_ref, _ = reference.rsplit("/", maxsplit=1)
486
455
  _, path_item = self.resolver.resolve(parent_ref)
487
- parameters = self._collect_operation_parameters(path_item, resolved)
488
- return self.make_operation(path, method, parameters, operation, resolved, scope)
456
+ with in_scope(self.resolver, scope):
457
+ parameters = self._iter_parameters(operation, path_item.get("parameters", []))
458
+ return self.make_operation(path, method, parameters, operation, scope)
489
459
 
490
460
  def get_case_strategy(
491
461
  self,
@@ -507,10 +477,7 @@ class BaseOpenAPISchema(BaseSchema):
507
477
  definitions = [item.definition for item in operation.iter_parameters() if item.location == location]
508
478
  config = self.config.generation_for(operation=operation)
509
479
  if config.with_security_parameters:
510
- security_parameters = self.security.get_security_definitions_as_parameters(
511
- self.raw_schema, operation, self.resolver, location
512
- )
513
- security_parameters = [item for item in security_parameters if item["in"] == location]
480
+ security_parameters = [param for param in operation.security.iter_parameters() if param["in"] == location]
514
481
  if security_parameters:
515
482
  definitions.extend(security_parameters)
516
483
  if definitions:
@@ -520,64 +487,22 @@ class BaseOpenAPISchema(BaseSchema):
520
487
  def _get_parameter_serializer(self, definitions: list[dict[str, Any]]) -> Callable | None:
521
488
  raise NotImplementedError
522
489
 
523
- def _get_response_definitions(
524
- self, operation: APIOperation, response: Response
525
- ) -> tuple[list[str], dict[str, Any]] | None:
526
- try:
527
- responses = operation.definition.raw["responses"]
528
- except KeyError as exc:
529
- path = operation.path
530
- self._raise_invalid_schema(exc, path, operation.method)
531
- definition = _get_response_definition_by_status(response.status_code, responses)
532
- if definition is None:
533
- return None
534
- return self.resolver.resolve_in_scope(definition, operation.definition.scope)
535
-
536
- def get_headers(
537
- self, operation: APIOperation, response: Response
538
- ) -> tuple[list[str], dict[str, dict[str, Any]] | None] | None:
539
- resolved = self._get_response_definitions(operation, response)
540
- if not resolved:
541
- return None
542
- scopes, definitions = resolved
543
- return scopes, definitions.get("headers")
544
-
545
490
  def as_state_machine(self) -> type[APIStateMachine]:
546
491
  return create_state_machine(self)
547
492
 
548
- def get_links(self, operation: APIOperation) -> dict[str, dict[str, Any]]:
549
- result: dict[str, dict[str, Any]] = defaultdict(dict)
550
- for status_code, link in links.get_all_links(operation):
551
- if isinstance(link, Ok):
552
- name = link.ok().name
553
- else:
554
- name = link.err().name
555
- result[status_code][name] = link
556
-
557
- return result
558
-
559
493
  def get_tags(self, operation: APIOperation) -> list[str] | None:
560
494
  return operation.definition.raw.get("tags")
561
495
 
562
- @property
563
- def validator_cls(self) -> type[jsonschema.Validator]:
564
- if self.specification.version.startswith("3.1"):
565
- return jsonschema.Draft202012Validator
566
- return jsonschema.Draft4Validator
567
-
568
496
  def validate_response(self, operation: APIOperation, response: Response) -> bool | None:
569
497
  __tracebackhide__ = True
570
- responses = {str(key): value for key, value in operation.definition.raw.get("responses", {}).items()}
571
- definition = _get_response_definition_by_status(response.status_code, responses)
572
- if definition is None:
573
- # No response defined for the received response status code
574
- return None
575
- scopes, schema = self.get_response_schema(definition, operation.definition.scope)
576
- if not schema:
577
- # No schema to check against
498
+ definition = operation.responses.find_by_status_code(response.status_code)
499
+ if definition is None or definition.schema is None:
500
+ # No definition for the given HTTP response, or missing "schema" in the matching definition
578
501
  return None
579
- content_types = response.headers.get("content-type")
502
+
580
503
  failures: list[Failure] = []
504
+
505
+ content_types = response.headers.get("content-type")
581
506
  if content_types is None:
582
507
  all_media_types = self.get_content_types(operation, response)
583
508
  formatted_content_types = [f"\n- `{content_type}`" for content_type in all_media_types]
@@ -587,6 +512,7 @@ class BaseOpenAPISchema(BaseSchema):
587
512
  content_type = "application/json"
588
513
  else:
589
514
  content_type = content_types[0]
515
+
590
516
  try:
591
517
  data = deserialization.deserialize_response(response, content_type)
592
518
  except JSONDecodeError as exc:
@@ -607,146 +533,24 @@ class BaseOpenAPISchema(BaseSchema):
607
533
  )
608
534
  _maybe_raise_one_or_more(failures)
609
535
  return None
610
- with self._validating_response(scopes) as resolver:
611
- try:
612
- jsonschema.validate(
613
- data,
614
- schema,
615
- cls=self.validator_cls,
616
- resolver=resolver,
617
- # Use a recent JSON Schema format checker to get most of formats checked for older drafts as well
618
- format_checker=jsonschema.Draft202012Validator.FORMAT_CHECKER,
619
- )
620
- except jsonschema.SchemaError as exc:
621
- raise InvalidSchema.from_jsonschema_error(
622
- exc, path=operation.path, method=operation.method, config=self.config.output
623
- ) from exc
624
- except jsonschema.ValidationError as exc:
625
- failures.append(
626
- JsonSchemaError.from_exception(
627
- operation=operation.label,
628
- exc=exc,
629
- config=operation.schema.config.output,
630
- )
536
+
537
+ try:
538
+ definition.validator.validate(data)
539
+ except jsonschema.SchemaError as exc:
540
+ raise InvalidSchema.from_jsonschema_error(
541
+ exc, path=operation.path, method=operation.method, config=self.config.output
542
+ ) from exc
543
+ except jsonschema.ValidationError as exc:
544
+ failures.append(
545
+ JsonSchemaError.from_exception(
546
+ operation=operation.label,
547
+ exc=exc,
548
+ config=operation.schema.config.output,
631
549
  )
550
+ )
632
551
  _maybe_raise_one_or_more(failures)
633
552
  return None # explicitly return None for mypy
634
553
 
635
- @contextmanager
636
- def _validating_response(self, scopes: list[str]) -> Generator[ConvertingResolver, None, None]:
637
- resolver = ConvertingResolver(
638
- self.location or "", self.raw_schema, nullable_name=self.nullable_name, is_response_schema=True
639
- )
640
- with in_scopes(resolver, scopes):
641
- yield resolver
642
-
643
- @property
644
- def rewritten_components(self) -> dict[str, Any]:
645
- if not hasattr(self, "_rewritten_components"):
646
-
647
- def callback(_schema: dict[str, Any], nullable_name: str) -> dict[str, Any]:
648
- _schema = to_json_schema(_schema, nullable_name=nullable_name, copy=False)
649
- return self._rewrite_references(_schema, self.resolver)
650
-
651
- # Different spec versions allow different keywords to store possible reference targets
652
- components: dict[str, Any] = {}
653
- for path in self.component_locations:
654
- schema = self.raw_schema
655
- target = components
656
- for chunk in path:
657
- if chunk in schema:
658
- schema = schema[chunk]
659
- target = target.setdefault(chunk, {})
660
- else:
661
- break
662
- else:
663
- target.update(transform(deepclone(schema), callback, self.nullable_name))
664
- if self._inline_reference_cache:
665
- components[INLINED_REFERENCES_KEY] = self._inline_reference_cache
666
- self._rewritten_components = components
667
- return self._rewritten_components
668
-
669
- def prepare_schema(self, schema: Any) -> Any:
670
- """Inline Open API definitions.
671
-
672
- Inlining components helps `hypothesis-jsonschema` generate data that involves non-resolved references.
673
- """
674
- schema = deepclone(schema)
675
- schema = transform(schema, self._rewrite_references, self.resolver)
676
- # Only add definitions that are reachable from the schema via references
677
- stack = [schema]
678
- seen = set()
679
- while stack:
680
- item = stack.pop()
681
- if isinstance(item, dict):
682
- if "$ref" in item:
683
- reference = item["$ref"]
684
- if isinstance(reference, str) and reference.startswith("#/") and reference not in seen:
685
- seen.add(reference)
686
- # Resolve the component and add it to the proper place in the schema
687
- pointer = reference[1:]
688
- resolved = resolve_pointer(self.rewritten_components, pointer)
689
- if resolved is UNRESOLVABLE:
690
- raise LoaderError(
691
- LoaderErrorKind.OPEN_API_INVALID_SCHEMA,
692
- message=f"Unresolvable JSON pointer in the schema: {pointer}",
693
- )
694
- if isinstance(resolved, dict):
695
- container = schema
696
- for key in pointer.split("/")[1:]:
697
- container = container.setdefault(key, {})
698
- container.update(resolved)
699
- # Explore the resolved value too
700
- stack.append(resolved)
701
- # Still explore other values as they may have nested references in other keys
702
- for value in item.values():
703
- if isinstance(value, (dict, list)):
704
- stack.append(value)
705
- elif isinstance(item, list):
706
- for sub_item in item:
707
- if isinstance(sub_item, (dict, list)):
708
- stack.append(sub_item)
709
- return schema
710
-
711
- def _rewrite_references(self, schema: dict[str, Any], resolver: InliningResolver) -> dict[str, Any]:
712
- """Rewrite references present in the schema.
713
-
714
- The idea is to resolve references, cache the result and replace these references with new ones
715
- that point to a local path which is populated from this cache later on.
716
- """
717
- reference = schema.get("$ref")
718
- # If `$ref` is not a property name and should be processed
719
- if reference is not None and isinstance(reference, str) and not reference.startswith("#/"):
720
- key = _make_reference_key(resolver._scopes_stack, reference)
721
- with self._inline_reference_cache_lock:
722
- if key not in self._inline_reference_cache:
723
- with resolver.resolving(reference) as resolved:
724
- # Resolved object also may have references
725
- self._inline_reference_cache[key] = transform(
726
- resolved, lambda s: self._rewrite_references(s, resolver)
727
- )
728
- # Rewrite the reference with the new location
729
- schema["$ref"] = f"#/{INLINED_REFERENCES_KEY}/{key}"
730
- return schema
731
-
732
-
733
- def _get_response_definition_by_status(status_code: int, responses: dict[str, Any]) -> dict[str, Any] | None:
734
- # Cast to string, as integers are often there due to YAML deserialization
735
- responses = {str(status): definition for status, definition in responses.items()}
736
- if str(status_code) in responses:
737
- return responses[str(status_code)]
738
- # More specific should go first
739
- keys = sorted(responses, key=lambda k: k.count("X"))
740
- for key in keys:
741
- if key == "default":
742
- continue
743
- status_codes = expand_status_code(key)
744
- if status_code in status_codes:
745
- return responses[key]
746
- if "default" in responses:
747
- return responses["default"]
748
- return None
749
-
750
554
 
751
555
  def _maybe_raise_one_or_more(failures: list[Failure]) -> None:
752
556
  if not failures:
@@ -756,22 +560,6 @@ def _maybe_raise_one_or_more(failures: list[Failure]) -> None:
756
560
  raise FailureGroup(failures) from None
757
561
 
758
562
 
759
- def _make_reference_key(scopes: list[str], reference: str) -> str:
760
- """A name under which the resolved reference data will be stored."""
761
- # Using a hexdigest is the simplest way to associate practically unique keys with each reference
762
- digest = sha1()
763
- for scope in scopes:
764
- digest.update(scope.encode("utf-8"))
765
- # Separator to avoid collisions like this: ["a"], "bc" vs. ["ab"], "c". Otherwise, the resulting digest
766
- # will be the same for both cases
767
- digest.update(b"#")
768
- digest.update(reference.encode("utf-8"))
769
- return digest.hexdigest()
770
-
771
-
772
- INLINED_REFERENCES_KEY = "x-inlined"
773
-
774
-
775
563
  @contextmanager
776
564
  def in_scope(resolver: jsonschema.RefResolver, scope: str) -> Generator[None, None, None]:
777
565
  resolver.push_scope(scope)
@@ -781,20 +569,6 @@ def in_scope(resolver: jsonschema.RefResolver, scope: str) -> Generator[None, No
781
569
  resolver.pop_scope()
782
570
 
783
571
 
784
- @contextmanager
785
- def in_scopes(resolver: jsonschema.RefResolver, scopes: list[str]) -> Generator[None, None, None]:
786
- """Push all available scopes into the resolver.
787
-
788
- There could be an additional scope change during a schema resolving in `get_response_schema`, so in total there
789
- could be a stack of two scopes maximum. This context manager handles both cases (1 or 2 scope changes) in the same
790
- way.
791
- """
792
- with ExitStack() as stack:
793
- for scope in scopes:
794
- stack.enter_context(in_scope(resolver, scope))
795
- yield
796
-
797
-
798
572
  @dataclass
799
573
  class MethodMap(Mapping):
800
574
  """Container for accessing API operations.
@@ -824,13 +598,12 @@ class MethodMap(Mapping):
824
598
  schema = cast(BaseOpenAPISchema, self._parent._schema)
825
599
  path = self._path
826
600
  scope = self._scope
827
- schema.resolver.push_scope(scope)
828
- try:
829
- resolved = schema._resolve_operation(operation)
830
- finally:
831
- schema.resolver.pop_scope()
832
- parameters = schema._collect_operation_parameters(self._path_item, resolved)
833
- return schema.make_operation(path, method, parameters, operation, resolved, scope)
601
+ with in_scope(schema.resolver, scope):
602
+ try:
603
+ parameters = schema._iter_parameters(operation, self._path_item.get("parameters", []))
604
+ except SCHEMA_PARSING_ERRORS as exc:
605
+ schema._raise_invalid_schema(exc, path, method)
606
+ return schema.make_operation(path, method, parameters, operation, scope)
834
607
 
835
608
  def __getitem__(self, item: str) -> APIOperation:
836
609
  try:
@@ -843,86 +616,30 @@ class MethodMap(Mapping):
843
616
  raise LookupError(message) from exc
844
617
 
845
618
 
846
- OPENAPI_20_DEFAULT_BODY_MEDIA_TYPE = "application/json"
847
- OPENAPI_20_DEFAULT_FORM_MEDIA_TYPE = "multipart/form-data"
848
-
849
-
850
619
  class SwaggerV20(BaseOpenAPISchema):
851
- nullable_name = "x-nullable"
852
- example_field = "x-example"
853
- examples_field = "x-examples"
854
- header_required_field = "x-required"
855
- security = SwaggerSecurityProcessor()
856
- component_locations: ClassVar[tuple[tuple[str, ...], ...]] = (("definitions",),)
857
- links_field = "x-links"
858
- _path_parameter_template = {"in": "path", "required": True, "type": "string"}
620
+ def __post_init__(self) -> None:
621
+ self.adapter = adapter.v2
622
+ super().__post_init__()
859
623
 
860
624
  @property
861
625
  def specification(self) -> Specification:
862
626
  version = self.raw_schema.get("swagger", "2.0")
863
627
  return Specification.openapi(version=version)
864
628
 
629
+ @cached_property
630
+ def default_media_types(self) -> list[str]:
631
+ return self.raw_schema.get("consumes", [])
632
+
865
633
  def _validate(self) -> None:
866
634
  SWAGGER_20_VALIDATOR.validate(self.raw_schema)
867
635
 
868
636
  def _get_base_path(self) -> str:
869
637
  return self.raw_schema.get("basePath", "/")
870
638
 
871
- def collect_parameters(
872
- self, parameters: Iterable[dict[str, Any]], definition: dict[str, Any]
873
- ) -> list[OpenAPIParameter]:
874
- # The main difference with Open API 3.0 is that it has `body` and `form` parameters that we need to handle
875
- # differently.
876
- collected: list[OpenAPIParameter] = []
877
- # NOTE. The Open API 2.0 spec doesn't strictly imply having media types in the "consumes" keyword.
878
- # It is not enforced by the meta schema and has no "MUST" verb in the spec text.
879
- # Also, not every API has operations with payload (they might have only GET operations without payloads).
880
- # For these reasons, it might be (and often is) absent, and we need to provide the proper media type in case
881
- # we have operations with a payload.
882
- media_types = self._get_consumes_for_operation(definition)
883
- # For `in=body` parameters, we imply `application/json` as the default media type because it is the most common.
884
- body_media_types = media_types or (OPENAPI_20_DEFAULT_BODY_MEDIA_TYPE,)
885
- # If an API operation has parameters with `in=formData`, Schemathesis should know how to serialize it.
886
- # We can't be 100% sure what media type is expected by the server and chose `multipart/form-data` as
887
- # the default because it is broader since it allows us to upload files.
888
- form_data_media_types = media_types or (OPENAPI_20_DEFAULT_FORM_MEDIA_TYPE,)
889
-
890
- form_parameters = []
891
- for parameter in parameters:
892
- if parameter["in"] == "formData":
893
- # We need to gather form parameters first before creating a composite parameter for them
894
- form_parameters.append(parameter)
895
- elif parameter["in"] == "body":
896
- for media_type in body_media_types:
897
- collected.append(OpenAPI20Body(definition=parameter, media_type=media_type))
898
- else:
899
- if parameter["in"] in ("header", "cookie"):
900
- check_header(parameter)
901
- collected.append(OpenAPI20Parameter(definition=parameter))
902
-
903
- if form_parameters:
904
- for media_type in form_data_media_types:
905
- collected.append(
906
- # Individual `formData` parameters are joined into a single "composite" one.
907
- OpenAPI20CompositeBody.from_parameters(*form_parameters, media_type=media_type)
908
- )
909
- return collected
910
-
911
639
  def get_strategies_from_examples(self, operation: APIOperation, **kwargs: Any) -> list[SearchStrategy[Case]]:
912
640
  """Get examples from the API operation."""
913
641
  return get_strategies_from_examples(operation, **kwargs)
914
642
 
915
- def get_response_schema(self, definition: dict[str, Any], scope: str) -> tuple[list[str], dict[str, Any] | None]:
916
- scopes, definition = self.resolver.resolve_in_scope(definition, scope)
917
- schema = definition.get("schema")
918
- if not schema:
919
- return scopes, None
920
- # Extra conversion to JSON Schema is needed here if there was one $ref in the input
921
- # because it is not converted
922
- return scopes, to_json_schema_recursive(
923
- schema, self.nullable_name, is_response_schema=True, update_quantifiers=False
924
- )
925
-
926
643
  def get_content_types(self, operation: APIOperation, response: Response) -> list[str]:
927
644
  produces = operation.definition.raw.get("produces", None)
928
645
  if produces:
@@ -944,9 +661,8 @@ class SwaggerV20(BaseOpenAPISchema):
944
661
  known_fields: dict[str, dict] = {}
945
662
 
946
663
  for parameter in operation.body:
947
- if isinstance(parameter, OpenAPI20CompositeBody):
948
- for form_parameter in parameter.definition:
949
- known_fields[form_parameter.name] = form_parameter.definition
664
+ if COMBINED_FORM_DATA_MARKER in parameter.definition:
665
+ known_fields.update(parameter.definition["schema"].get("properties", {}))
950
666
 
951
667
  def add_file(name: str, value: Any) -> None:
952
668
  if isinstance(value, list):
@@ -1007,30 +723,24 @@ class SwaggerV20(BaseOpenAPISchema):
1007
723
  consumes = global_consumes
1008
724
  return consumes
1009
725
 
1010
- def _get_payload_schema(self, definition: dict[str, Any], media_type: str) -> dict[str, Any] | None:
1011
- for parameter in definition.get("parameters", []):
1012
- if "$ref" in parameter:
1013
- _, parameter = self.resolver.resolve(parameter["$ref"])
1014
- if parameter["in"] == "body":
1015
- return parameter["schema"]
1016
- return None
1017
-
1018
726
 
1019
727
  class OpenApi30(SwaggerV20):
1020
- nullable_name = "nullable"
1021
- example_field = "example"
1022
- examples_field = "examples"
1023
- header_required_field = "required"
1024
- security = OpenAPISecurityProcessor()
1025
- component_locations = (("components", "schemas"),)
1026
- links_field = "links"
1027
- _path_parameter_template = {"in": "path", "required": True, "schema": {"type": "string"}}
728
+ def __post_init__(self) -> None:
729
+ if self.specification.version.startswith("3.1"):
730
+ self.adapter = adapter.v3_1
731
+ else:
732
+ self.adapter = adapter.v3_0
733
+ BaseOpenAPISchema.__post_init__(self)
1028
734
 
1029
735
  @property
1030
736
  def specification(self) -> Specification:
1031
737
  version = self.raw_schema["openapi"]
1032
738
  return Specification.openapi(version=version)
1033
739
 
740
+ @cached_property
741
+ def default_media_types(self) -> list[str]:
742
+ return []
743
+
1034
744
  def _validate(self) -> None:
1035
745
  if self.specification.version.startswith("3.1"):
1036
746
  # Currently we treat Open API 3.1 as 3.0 in some regard
@@ -1047,79 +757,34 @@ class OpenApi30(SwaggerV20):
1047
757
  return urlsplit(url).path
1048
758
  return "/"
1049
759
 
1050
- def collect_parameters(
1051
- self, parameters: Iterable[dict[str, Any]], definition: dict[str, Any]
1052
- ) -> list[OpenAPIParameter]:
1053
- # Open API 3.0 has the `requestBody` keyword, which may contain multiple different payload variants.
1054
- collected: list[OpenAPIParameter] = []
1055
-
1056
- for parameter in parameters:
1057
- if parameter["in"] in ("header", "cookie"):
1058
- check_header(parameter)
1059
- collected.append(OpenAPI30Parameter(definition=parameter))
1060
- if "requestBody" in definition:
1061
- required = definition["requestBody"].get("required", False)
1062
- description = definition["requestBody"].get("description")
1063
- for media_type, content in definition["requestBody"]["content"].items():
1064
- collected.append(
1065
- OpenAPI30Body(content, description=description, media_type=media_type, required=required)
1066
- )
1067
- return collected
1068
-
1069
- def get_response_schema(self, definition: dict[str, Any], scope: str) -> tuple[list[str], dict[str, Any] | None]:
1070
- scopes, definition = self.resolver.resolve_in_scope(definition, scope)
1071
- options = iter(definition.get("content", {}).values())
1072
- option = next(options, None)
1073
- # "schema" is an optional key in the `MediaType` object
1074
- if option and "schema" in option:
1075
- # Extra conversion to JSON Schema is needed here if there was one $ref in the input
1076
- # because it is not converted
1077
- return scopes, to_json_schema_recursive(
1078
- option["schema"], self.nullable_name, is_response_schema=True, update_quantifiers=False
1079
- )
1080
- return scopes, None
1081
-
1082
760
  def get_strategies_from_examples(self, operation: APIOperation, **kwargs: Any) -> list[SearchStrategy[Case]]:
1083
761
  """Get examples from the API operation."""
1084
762
  return get_strategies_from_examples(operation, **kwargs)
1085
763
 
1086
764
  def get_content_types(self, operation: APIOperation, response: Response) -> list[str]:
1087
- resolved = self._get_response_definitions(operation, response)
1088
- if not resolved:
765
+ definition = operation.responses.find_by_status_code(response.status_code)
766
+ if definition is None:
1089
767
  return []
1090
- _, definitions = resolved
1091
- return list(definitions.get("content", {}).keys())
768
+ return list(definition.definition.get("content", {}).keys())
1092
769
 
1093
770
  def _get_parameter_serializer(self, definitions: list[dict[str, Any]]) -> Callable | None:
1094
771
  return serialization.serialize_openapi3_parameters(definitions)
1095
772
 
1096
773
  def get_request_payload_content_types(self, operation: APIOperation) -> list[str]:
1097
- request_body = self._resolve_until_no_references(operation.definition.raw["requestBody"])
1098
- return list(request_body["content"])
774
+ return [body.media_type for body in operation.body]
1099
775
 
1100
776
  def prepare_multipart(
1101
777
  self, form_data: dict[str, Any], operation: APIOperation
1102
778
  ) -> tuple[list | None, dict[str, Any] | None]:
1103
779
  files = []
1104
- definition = operation.definition.raw
1105
- if "$ref" in definition["requestBody"]:
1106
- self.resolver.push_scope(operation.definition.scope)
1107
- try:
1108
- body = self.resolver.resolve_all(definition["requestBody"], RECURSION_DEPTH_LIMIT)
1109
- finally:
1110
- self.resolver.pop_scope()
1111
- else:
1112
- body = definition["requestBody"]
1113
- content = body["content"]
1114
780
  # Open API 3.0 requires media types to be present. We can get here only if the schema defines
1115
781
  # the "multipart/form-data" media type, or any other more general media type that matches it (like `*/*`)
1116
- for media_type, entry in content.items():
1117
- main, sub = media_types.parse(media_type)
782
+ schema = {}
783
+ for body in operation.body:
784
+ main, sub = media_types.parse(body.media_type)
1118
785
  if main in ("*", "multipart") and sub in ("*", "form-data", "mixed"):
1119
- schema = entry.get("schema")
786
+ schema = body.definition.get("schema")
1120
787
  break
1121
- else:
1122
- raise InternalError("No 'multipart/form-data' media type found in the schema")
1123
788
  for name, value in form_data.items():
1124
789
  property_schema = (schema or {}).get("properties", {}).get(name)
1125
790
  if property_schema:
@@ -1135,16 +800,3 @@ class OpenApi30(SwaggerV20):
1135
800
  files.append((name, (None, value)))
1136
801
  # `None` is the default value for `files` and `data` arguments in `requests.request`
1137
802
  return files or None, None
1138
-
1139
- def _get_payload_schema(self, definition: dict[str, Any], media_type: str) -> dict[str, Any] | None:
1140
- if "requestBody" in definition:
1141
- if "$ref" in definition["requestBody"]:
1142
- body = self.resolver.resolve_all(definition["requestBody"], RECURSION_DEPTH_LIMIT)
1143
- else:
1144
- body = definition["requestBody"]
1145
- if "content" in body:
1146
- main, sub = media_types.parse(media_type)
1147
- for defined_media_type, item in body["content"].items():
1148
- if media_types.parse(defined_media_type) == (main, sub):
1149
- return item["schema"]
1150
- return None