schemathesis 3.39.7__py3-none-any.whl → 4.0.0a2__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 (229) hide show
  1. schemathesis/__init__.py +27 -65
  2. schemathesis/auths.py +26 -68
  3. schemathesis/checks.py +130 -60
  4. schemathesis/cli/__init__.py +5 -2105
  5. schemathesis/cli/commands/__init__.py +37 -0
  6. schemathesis/cli/commands/run/__init__.py +662 -0
  7. schemathesis/cli/commands/run/checks.py +80 -0
  8. schemathesis/cli/commands/run/context.py +117 -0
  9. schemathesis/cli/commands/run/events.py +30 -0
  10. schemathesis/cli/commands/run/executor.py +141 -0
  11. schemathesis/cli/commands/run/filters.py +202 -0
  12. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  13. schemathesis/cli/commands/run/handlers/base.py +18 -0
  14. schemathesis/cli/{cassettes.py → commands/run/handlers/cassettes.py} +178 -247
  15. schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
  16. schemathesis/cli/commands/run/handlers/output.py +1368 -0
  17. schemathesis/cli/commands/run/hypothesis.py +105 -0
  18. schemathesis/cli/commands/run/loaders.py +129 -0
  19. schemathesis/cli/{callbacks.py → commands/run/validation.py} +59 -175
  20. schemathesis/cli/constants.py +5 -58
  21. schemathesis/cli/core.py +17 -0
  22. schemathesis/cli/ext/fs.py +14 -0
  23. schemathesis/cli/ext/groups.py +55 -0
  24. schemathesis/cli/{options.py → ext/options.py} +37 -16
  25. schemathesis/cli/hooks.py +36 -0
  26. schemathesis/contrib/__init__.py +1 -3
  27. schemathesis/contrib/openapi/__init__.py +1 -3
  28. schemathesis/contrib/openapi/fill_missing_examples.py +3 -7
  29. schemathesis/core/__init__.py +58 -0
  30. schemathesis/core/compat.py +25 -0
  31. schemathesis/core/control.py +2 -0
  32. schemathesis/core/curl.py +58 -0
  33. schemathesis/core/deserialization.py +65 -0
  34. schemathesis/core/errors.py +370 -0
  35. schemathesis/core/failures.py +315 -0
  36. schemathesis/core/fs.py +19 -0
  37. schemathesis/core/loaders.py +104 -0
  38. schemathesis/core/marks.py +66 -0
  39. schemathesis/{transports/content_types.py → core/media_types.py} +14 -12
  40. schemathesis/{internal/output.py → core/output/__init__.py} +1 -0
  41. schemathesis/core/output/sanitization.py +197 -0
  42. schemathesis/{throttling.py → core/rate_limit.py} +16 -17
  43. schemathesis/core/registries.py +31 -0
  44. schemathesis/core/transforms.py +113 -0
  45. schemathesis/core/transport.py +108 -0
  46. schemathesis/core/validation.py +38 -0
  47. schemathesis/core/version.py +7 -0
  48. schemathesis/engine/__init__.py +30 -0
  49. schemathesis/engine/config.py +59 -0
  50. schemathesis/engine/context.py +119 -0
  51. schemathesis/engine/control.py +36 -0
  52. schemathesis/engine/core.py +157 -0
  53. schemathesis/engine/errors.py +394 -0
  54. schemathesis/engine/events.py +243 -0
  55. schemathesis/engine/phases/__init__.py +66 -0
  56. schemathesis/{runner → engine/phases}/probes.py +49 -68
  57. schemathesis/engine/phases/stateful/__init__.py +66 -0
  58. schemathesis/engine/phases/stateful/_executor.py +301 -0
  59. schemathesis/engine/phases/stateful/context.py +85 -0
  60. schemathesis/engine/phases/unit/__init__.py +175 -0
  61. schemathesis/engine/phases/unit/_executor.py +322 -0
  62. schemathesis/engine/phases/unit/_pool.py +74 -0
  63. schemathesis/engine/recorder.py +246 -0
  64. schemathesis/errors.py +31 -0
  65. schemathesis/experimental/__init__.py +9 -40
  66. schemathesis/filters.py +7 -95
  67. schemathesis/generation/__init__.py +3 -3
  68. schemathesis/generation/case.py +190 -0
  69. schemathesis/generation/coverage.py +22 -22
  70. schemathesis/{_patches.py → generation/hypothesis/__init__.py} +15 -6
  71. schemathesis/generation/hypothesis/builder.py +585 -0
  72. schemathesis/generation/{_hypothesis.py → hypothesis/examples.py} +2 -11
  73. schemathesis/generation/hypothesis/given.py +66 -0
  74. schemathesis/generation/hypothesis/reporting.py +14 -0
  75. schemathesis/generation/hypothesis/strategies.py +16 -0
  76. schemathesis/generation/meta.py +115 -0
  77. schemathesis/generation/modes.py +28 -0
  78. schemathesis/generation/overrides.py +96 -0
  79. schemathesis/generation/stateful/__init__.py +20 -0
  80. schemathesis/{stateful → generation/stateful}/state_machine.py +84 -109
  81. schemathesis/generation/targets.py +69 -0
  82. schemathesis/graphql/__init__.py +15 -0
  83. schemathesis/graphql/checks.py +109 -0
  84. schemathesis/graphql/loaders.py +131 -0
  85. schemathesis/hooks.py +17 -62
  86. schemathesis/openapi/__init__.py +13 -0
  87. schemathesis/openapi/checks.py +387 -0
  88. schemathesis/openapi/generation/__init__.py +0 -0
  89. schemathesis/openapi/generation/filters.py +63 -0
  90. schemathesis/openapi/loaders.py +178 -0
  91. schemathesis/pytest/__init__.py +5 -0
  92. schemathesis/pytest/control_flow.py +7 -0
  93. schemathesis/pytest/lazy.py +273 -0
  94. schemathesis/pytest/loaders.py +12 -0
  95. schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +94 -107
  96. schemathesis/python/__init__.py +0 -0
  97. schemathesis/python/asgi.py +12 -0
  98. schemathesis/python/wsgi.py +12 -0
  99. schemathesis/schemas.py +456 -228
  100. schemathesis/specs/graphql/__init__.py +0 -1
  101. schemathesis/specs/graphql/_cache.py +1 -2
  102. schemathesis/specs/graphql/scalars.py +5 -3
  103. schemathesis/specs/graphql/schemas.py +122 -123
  104. schemathesis/specs/graphql/validation.py +11 -17
  105. schemathesis/specs/openapi/__init__.py +6 -1
  106. schemathesis/specs/openapi/_cache.py +1 -2
  107. schemathesis/specs/openapi/_hypothesis.py +97 -134
  108. schemathesis/specs/openapi/checks.py +238 -219
  109. schemathesis/specs/openapi/converter.py +4 -4
  110. schemathesis/specs/openapi/definitions.py +1 -1
  111. schemathesis/specs/openapi/examples.py +22 -20
  112. schemathesis/specs/openapi/expressions/__init__.py +11 -15
  113. schemathesis/specs/openapi/expressions/extractors.py +1 -4
  114. schemathesis/specs/openapi/expressions/nodes.py +33 -32
  115. schemathesis/specs/openapi/formats.py +3 -2
  116. schemathesis/specs/openapi/links.py +123 -299
  117. schemathesis/specs/openapi/media_types.py +10 -12
  118. schemathesis/specs/openapi/negative/__init__.py +2 -1
  119. schemathesis/specs/openapi/negative/mutations.py +3 -2
  120. schemathesis/specs/openapi/parameters.py +8 -6
  121. schemathesis/specs/openapi/patterns.py +1 -1
  122. schemathesis/specs/openapi/references.py +11 -51
  123. schemathesis/specs/openapi/schemas.py +177 -191
  124. schemathesis/specs/openapi/security.py +1 -1
  125. schemathesis/specs/openapi/serialization.py +10 -6
  126. schemathesis/specs/openapi/stateful/__init__.py +97 -91
  127. schemathesis/transport/__init__.py +104 -0
  128. schemathesis/transport/asgi.py +26 -0
  129. schemathesis/transport/prepare.py +99 -0
  130. schemathesis/transport/requests.py +221 -0
  131. schemathesis/{_xml.py → transport/serialization.py} +69 -7
  132. schemathesis/transport/wsgi.py +165 -0
  133. {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/METADATA +18 -14
  134. schemathesis-4.0.0a2.dist-info/RECORD +151 -0
  135. {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/entry_points.txt +1 -1
  136. schemathesis/_compat.py +0 -74
  137. schemathesis/_dependency_versions.py +0 -19
  138. schemathesis/_hypothesis.py +0 -559
  139. schemathesis/_override.py +0 -50
  140. schemathesis/_rate_limiter.py +0 -7
  141. schemathesis/cli/context.py +0 -75
  142. schemathesis/cli/debug.py +0 -27
  143. schemathesis/cli/handlers.py +0 -19
  144. schemathesis/cli/junitxml.py +0 -124
  145. schemathesis/cli/output/__init__.py +0 -1
  146. schemathesis/cli/output/default.py +0 -936
  147. schemathesis/cli/output/short.py +0 -59
  148. schemathesis/cli/reporting.py +0 -79
  149. schemathesis/cli/sanitization.py +0 -26
  150. schemathesis/code_samples.py +0 -151
  151. schemathesis/constants.py +0 -56
  152. schemathesis/contrib/openapi/formats/__init__.py +0 -9
  153. schemathesis/contrib/openapi/formats/uuid.py +0 -16
  154. schemathesis/contrib/unique_data.py +0 -41
  155. schemathesis/exceptions.py +0 -571
  156. schemathesis/extra/_aiohttp.py +0 -28
  157. schemathesis/extra/_flask.py +0 -13
  158. schemathesis/extra/_server.py +0 -18
  159. schemathesis/failures.py +0 -277
  160. schemathesis/fixups/__init__.py +0 -37
  161. schemathesis/fixups/fast_api.py +0 -41
  162. schemathesis/fixups/utf8_bom.py +0 -28
  163. schemathesis/generation/_methods.py +0 -44
  164. schemathesis/graphql.py +0 -3
  165. schemathesis/internal/__init__.py +0 -7
  166. schemathesis/internal/checks.py +0 -84
  167. schemathesis/internal/copy.py +0 -32
  168. schemathesis/internal/datetime.py +0 -5
  169. schemathesis/internal/deprecation.py +0 -38
  170. schemathesis/internal/diff.py +0 -15
  171. schemathesis/internal/extensions.py +0 -27
  172. schemathesis/internal/jsonschema.py +0 -36
  173. schemathesis/internal/transformation.py +0 -26
  174. schemathesis/internal/validation.py +0 -34
  175. schemathesis/lazy.py +0 -474
  176. schemathesis/loaders.py +0 -122
  177. schemathesis/models.py +0 -1341
  178. schemathesis/parameters.py +0 -90
  179. schemathesis/runner/__init__.py +0 -605
  180. schemathesis/runner/events.py +0 -389
  181. schemathesis/runner/impl/__init__.py +0 -3
  182. schemathesis/runner/impl/context.py +0 -104
  183. schemathesis/runner/impl/core.py +0 -1246
  184. schemathesis/runner/impl/solo.py +0 -80
  185. schemathesis/runner/impl/threadpool.py +0 -391
  186. schemathesis/runner/serialization.py +0 -544
  187. schemathesis/sanitization.py +0 -252
  188. schemathesis/serializers.py +0 -328
  189. schemathesis/service/__init__.py +0 -18
  190. schemathesis/service/auth.py +0 -11
  191. schemathesis/service/ci.py +0 -202
  192. schemathesis/service/client.py +0 -133
  193. schemathesis/service/constants.py +0 -38
  194. schemathesis/service/events.py +0 -61
  195. schemathesis/service/extensions.py +0 -224
  196. schemathesis/service/hosts.py +0 -111
  197. schemathesis/service/metadata.py +0 -71
  198. schemathesis/service/models.py +0 -258
  199. schemathesis/service/report.py +0 -255
  200. schemathesis/service/serialization.py +0 -173
  201. schemathesis/service/usage.py +0 -66
  202. schemathesis/specs/graphql/loaders.py +0 -364
  203. schemathesis/specs/openapi/expressions/context.py +0 -16
  204. schemathesis/specs/openapi/loaders.py +0 -708
  205. schemathesis/specs/openapi/stateful/statistic.py +0 -198
  206. schemathesis/specs/openapi/stateful/types.py +0 -14
  207. schemathesis/specs/openapi/validation.py +0 -26
  208. schemathesis/stateful/__init__.py +0 -147
  209. schemathesis/stateful/config.py +0 -97
  210. schemathesis/stateful/context.py +0 -135
  211. schemathesis/stateful/events.py +0 -274
  212. schemathesis/stateful/runner.py +0 -309
  213. schemathesis/stateful/sink.py +0 -68
  214. schemathesis/stateful/statistic.py +0 -22
  215. schemathesis/stateful/validation.py +0 -100
  216. schemathesis/targets.py +0 -77
  217. schemathesis/transports/__init__.py +0 -359
  218. schemathesis/transports/asgi.py +0 -7
  219. schemathesis/transports/auth.py +0 -38
  220. schemathesis/transports/headers.py +0 -36
  221. schemathesis/transports/responses.py +0 -57
  222. schemathesis/types.py +0 -44
  223. schemathesis/utils.py +0 -164
  224. schemathesis-3.39.7.dist-info/RECORD +0 -160
  225. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
  226. /schemathesis/{_lazy_import.py → core/lazy_import.py} +0 -0
  227. /schemathesis/{internal → core}/result.py +0 -0
  228. {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/WHEEL +0 -0
  229. {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/licenses/LICENSE +0 -0
@@ -19,43 +19,35 @@ from typing import (
19
19
  Iterator,
20
20
  Mapping,
21
21
  NoReturn,
22
- Sequence,
23
- TypeVar,
24
22
  cast,
25
23
  )
26
24
  from urllib.parse import urlsplit
27
25
 
28
26
  import jsonschema
29
27
  from packaging import version
28
+ from requests.exceptions import InvalidHeader
30
29
  from requests.structures import CaseInsensitiveDict
31
-
32
- from ... import experimental, failures
33
- from ..._compat import MultipleFailures
34
- from ..._override import CaseOverride, check_no_override_mark, set_override_mark
35
- from ...constants import HTTP_METHODS, NOT_SET
36
- from ...exceptions import (
37
- InternalError,
38
- OperationNotFound,
39
- OperationSchemaError,
40
- SchemaError,
41
- SchemaErrorType,
42
- get_missing_content_type_error,
43
- get_response_parsing_error,
44
- get_schema_validation_error,
45
- )
46
- from ...generation import DataGenerationMethod, GenerationConfig
47
- from ...hooks import GLOBAL_HOOK_DISPATCHER, HookContext, HookDispatcher, should_skip_operation
48
- from ...internal.copy import fast_deepcopy
49
- from ...internal.jsonschema import traverse_schema
50
- from ...internal.result import Err, Ok, Result
51
- from ...models import APIOperation, Case, OperationDefinition
52
- from ...schemas import APIOperationMap, BaseSchema
53
- from ...stateful import Stateful, StatefulTest
54
- from ...transports.content_types import is_json_media_type, parse_content_type
55
- from ...transports.responses import get_json
30
+ from requests.utils import check_header_validity
31
+
32
+ from schemathesis.core import NOT_SET, NotSet, Specification, media_types
33
+ from schemathesis.core.compat import RefResolutionError
34
+ from schemathesis.core.errors import InternalError, InvalidSchema, LoaderError, LoaderErrorKind, OperationNotFound
35
+ from schemathesis.core.failures import Failure, FailureGroup, MalformedJson
36
+ from schemathesis.core.result import Err, Ok, Result
37
+ from schemathesis.core.transforms import UNRESOLVABLE, deepclone, resolve_pointer, transform
38
+ from schemathesis.core.transport import Response
39
+ from schemathesis.core.validation import INVALID_HEADER_RE
40
+ from schemathesis.generation.case import Case
41
+ from schemathesis.generation.meta import CaseMetadata
42
+ from schemathesis.generation.overrides import Override, OverrideMark, check_no_override_mark
43
+ from schemathesis.openapi.checks import JsonSchemaError, MissingContentType
44
+
45
+ from ...generation import GenerationConfig, GenerationMode
46
+ from ...hooks import HookContext, HookDispatcher
47
+ from ...schemas import APIOperation, APIOperationMap, ApiStatistic, BaseSchema, OperationDefinition
56
48
  from . import links, serialization
57
49
  from ._cache import OperationCache
58
- from ._hypothesis import get_case_strategy
50
+ from ._hypothesis import openapi_cases
59
51
  from .converter import to_json_schema, to_json_schema_recursive
60
52
  from .definitions import OPENAPI_30_VALIDATOR, OPENAPI_31_VALIDATOR, SWAGGER_20_VALIDATOR
61
53
  from .examples import get_strategies_from_examples
@@ -67,13 +59,7 @@ from .parameters import (
67
59
  OpenAPI30Parameter,
68
60
  OpenAPIParameter,
69
61
  )
70
- from .references import (
71
- RECURSION_DEPTH_LIMIT,
72
- UNRESOLVABLE,
73
- ConvertingResolver,
74
- InliningResolver,
75
- resolve_pointer,
76
- )
62
+ from .references import RECURSION_DEPTH_LIMIT, ConvertingResolver, InliningResolver
77
63
  from .security import BaseSecurityProcessor, OpenAPISecurityProcessor, SwaggerSecurityProcessor
78
64
  from .stateful import create_state_machine
79
65
 
@@ -82,11 +68,25 @@ if TYPE_CHECKING:
82
68
 
83
69
  from ...auths import AuthStorage
84
70
  from ...stateful.state_machine import APIStateMachine
85
- from ...transports.responses import GenericResponse
86
- from ...types import Body, Cookies, FormData, GenericTest, Headers, NotSet, PathParameters, Query
87
71
 
72
+ HTTP_METHODS = frozenset({"get", "put", "post", "delete", "options", "head", "patch", "trace"})
88
73
  SCHEMA_ERROR_MESSAGE = "Ensure that the definition complies with the OpenAPI specification"
89
- SCHEMA_PARSING_ERRORS = (KeyError, AttributeError, jsonschema.exceptions.RefResolutionError)
74
+ SCHEMA_PARSING_ERRORS = (KeyError, AttributeError, RefResolutionError, InvalidSchema)
75
+
76
+
77
+ def check_header(parameter: dict[str, Any]) -> None:
78
+ name = parameter["name"]
79
+ if not name:
80
+ raise InvalidSchema("Header name should not be empty")
81
+ if not name.isascii():
82
+ # `urllib3` encodes header names to ASCII
83
+ raise InvalidSchema(f"Header name should be ASCII: {name}")
84
+ try:
85
+ check_header_validity((name, ""))
86
+ except InvalidHeader as exc:
87
+ raise InvalidSchema(str(exc)) from None
88
+ if bool(INVALID_HEADER_RE.search(name)):
89
+ raise InvalidSchema(f"Invalid header name: {name}")
90
90
 
91
91
 
92
92
  @dataclass(eq=False, repr=False)
@@ -103,16 +103,9 @@ class BaseOpenAPISchema(BaseSchema):
103
103
  component_locations: ClassVar[tuple[tuple[str, ...], ...]] = ()
104
104
 
105
105
  @property
106
- def spec_version(self) -> str:
106
+ def specification(self) -> Specification:
107
107
  raise NotImplementedError
108
108
 
109
- def get_stateful_tests(
110
- self, response: GenericResponse, operation: APIOperation, stateful: Stateful | None
111
- ) -> Sequence[StatefulTest]:
112
- if stateful == Stateful.links:
113
- return links.get_links(response, operation, field=self.links_field)
114
- return []
115
-
116
109
  def __repr__(self) -> str:
117
110
  info = self.raw_schema["info"]
118
111
  return f"<{self.__class__.__name__} for {info['title']} {info['version']}>"
@@ -152,7 +145,7 @@ class BaseOpenAPISchema(BaseSchema):
152
145
  operation=APIOperation(
153
146
  method="",
154
147
  path="",
155
- verbose_name="",
148
+ label="",
156
149
  definition=OperationDefinition(raw=None, resolved=None, scope=""),
157
150
  schema=None, # type: ignore
158
151
  )
@@ -167,12 +160,46 @@ class BaseOpenAPISchema(BaseSchema):
167
160
  operation = _ctx_cache.operation
168
161
  operation.method = method
169
162
  operation.path = path
170
- operation.verbose_name = f"{method.upper()} {path}"
163
+ operation.label = f"{method.upper()} {path}"
171
164
  operation.definition.raw = definition
172
165
  operation.definition.resolved = definition
173
166
  operation.schema = self
174
167
  return not self.filter_set.match(_ctx_cache)
175
168
 
169
+ def _measure_statistic(self) -> ApiStatistic:
170
+ statistic = ApiStatistic()
171
+ try:
172
+ paths = self.raw_schema["paths"]
173
+ except KeyError:
174
+ return statistic
175
+
176
+ resolve = self.resolver.resolve
177
+ should_skip = self._should_skip
178
+ links_field = self.links_field
179
+
180
+ for path, path_item in paths.items():
181
+ try:
182
+ if "$ref" in path_item:
183
+ _, path_item = resolve(path_item["$ref"])
184
+ for method, definition in path_item.items():
185
+ if method not in HTTP_METHODS:
186
+ continue
187
+ statistic.operations.total += 1
188
+ is_selected = not should_skip(path, method, definition)
189
+ if is_selected:
190
+ statistic.operations.selected += 1
191
+ for response in definition.get("responses", {}).values():
192
+ if "$ref" in response:
193
+ _, response = resolve(response["$ref"])
194
+ defined_links = response.get(links_field)
195
+ if defined_links is not None:
196
+ statistic.links.total += len(defined_links)
197
+ if is_selected:
198
+ statistic.links.selected = len(defined_links)
199
+ except SCHEMA_PARSING_ERRORS:
200
+ continue
201
+ return statistic
202
+
176
203
  def _operation_iter(self) -> Generator[dict[str, Any], None, None]:
177
204
  try:
178
205
  paths = self.raw_schema["paths"]
@@ -193,28 +220,6 @@ class BaseOpenAPISchema(BaseSchema):
193
220
  # Ignore errors
194
221
  continue
195
222
 
196
- @property
197
- def operations_count(self) -> int:
198
- total = 0
199
- # Do not build a list from it
200
- for _ in self._operation_iter():
201
- total += 1
202
- return total
203
-
204
- @property
205
- def links_count(self) -> int:
206
- total = 0
207
- resolve = self.resolver.resolve
208
- links_field = self.links_field
209
- for definition in self._operation_iter():
210
- for response in definition.get("responses", {}).values():
211
- if "$ref" in response:
212
- _, response = resolve(response["$ref"])
213
- defined_links = response.get(links_field)
214
- if defined_links is not None:
215
- total += len(defined_links)
216
- return total
217
-
218
223
  def override(
219
224
  self,
220
225
  *,
@@ -222,15 +227,15 @@ class BaseOpenAPISchema(BaseSchema):
222
227
  headers: dict[str, str] | None = None,
223
228
  cookies: dict[str, str] | None = None,
224
229
  path_parameters: dict[str, str] | None = None,
225
- ) -> Callable[[GenericTest], GenericTest]:
230
+ ) -> Callable[[Callable], Callable]:
226
231
  """Override Open API parameters with fixed values."""
227
232
 
228
- def _add_override(test: GenericTest) -> GenericTest:
233
+ def _add_override(test: Callable) -> Callable:
229
234
  check_no_override_mark(test)
230
- override = CaseOverride(
235
+ override = Override(
231
236
  query=query or {}, headers=headers or {}, cookies=cookies or {}, path_parameters=path_parameters or {}
232
237
  )
233
- set_override_mark(test, override)
238
+ OverrideMark.set(test, override)
234
239
  return test
235
240
 
236
241
  return _add_override
@@ -254,8 +259,8 @@ class BaseOpenAPISchema(BaseSchema):
254
259
  return self.collect_parameters(itertools.chain(parameters, shared_parameters), operation)
255
260
 
256
261
  def get_all_operations(
257
- self, hooks: HookDispatcher | None = None, generation_config: GenerationConfig | None = None
258
- ) -> Generator[Result[APIOperation, OperationSchemaError], None, None]:
262
+ self, generation_config: GenerationConfig | None = None
263
+ ) -> Generator[Result[APIOperation, InvalidSchema], None, None]:
259
264
  """Iterate over all operations defined in the API.
260
265
 
261
266
  Each yielded item is either `Ok` or `Err`, depending on the presence of errors during schema processing.
@@ -276,7 +281,7 @@ class BaseOpenAPISchema(BaseSchema):
276
281
  paths = self.raw_schema["paths"]
277
282
  except KeyError as exc:
278
283
  # This field is optional in Open API 3.1
279
- if version.parse(self.spec_version) >= version.parse("3.1"):
284
+ if version.parse(self.specification.version) >= version.parse("3.1"):
280
285
  return
281
286
  # Missing `paths` is not recoverable
282
287
  self._raise_invalid_schema(exc)
@@ -290,7 +295,6 @@ class BaseOpenAPISchema(BaseSchema):
290
295
  should_skip = self._should_skip
291
296
  collect_parameters = self.collect_parameters
292
297
  make_operation = self.make_operation
293
- hooks = self.hooks
294
298
  for path, path_item in paths.items():
295
299
  method = None
296
300
  try:
@@ -318,25 +322,18 @@ class BaseOpenAPISchema(BaseSchema):
318
322
  if generation_config
319
323
  else None,
320
324
  )
321
- context = HookContext(operation=operation)
322
- if (
323
- should_skip_operation(GLOBAL_HOOK_DISPATCHER, context)
324
- or should_skip_operation(hooks, context)
325
- or (hooks and should_skip_operation(hooks, context))
326
- ):
327
- continue
328
325
  yield Ok(operation)
329
326
  except SCHEMA_PARSING_ERRORS as exc:
330
327
  yield self._into_err(exc, path, method)
331
328
  except SCHEMA_PARSING_ERRORS as exc:
332
329
  yield self._into_err(exc, path, method)
333
330
 
334
- def _into_err(self, error: Exception, path: str | None, method: str | None) -> Err[OperationSchemaError]:
331
+ def _into_err(self, error: Exception, path: str | None, method: str | None) -> Err[InvalidSchema]:
335
332
  __tracebackhide__ = True
336
333
  try:
337
334
  full_path = self.get_full_path(path) if isinstance(path, str) else None
338
335
  self._raise_invalid_schema(error, full_path, path, method)
339
- except OperationSchemaError as exc:
336
+ except InvalidSchema as exc:
340
337
  return Err(exc)
341
338
 
342
339
  def _raise_invalid_schema(
@@ -347,17 +344,15 @@ class BaseOpenAPISchema(BaseSchema):
347
344
  method: str | None = None,
348
345
  ) -> NoReturn:
349
346
  __tracebackhide__ = True
350
- if isinstance(error, jsonschema.exceptions.RefResolutionError):
351
- raise OperationSchemaError.from_reference_resolution_error(
347
+ if isinstance(error, RefResolutionError):
348
+ raise InvalidSchema.from_reference_resolution_error(
352
349
  error, path=path, method=method, full_path=full_path
353
350
  ) from None
354
351
  try:
355
352
  self.validate()
356
353
  except jsonschema.ValidationError as exc:
357
- raise OperationSchemaError.from_jsonschema_error(
358
- exc, path=path, method=method, full_path=full_path
359
- ) from None
360
- raise OperationSchemaError(SCHEMA_ERROR_MESSAGE, path=path, method=method, full_path=full_path) from error
354
+ raise InvalidSchema.from_jsonschema_error(exc, path=path, method=method, full_path=full_path) from None
355
+ raise InvalidSchema(SCHEMA_ERROR_MESSAGE, path=path, method=method, full_path=full_path) from error
361
356
 
362
357
  def validate(self) -> None:
363
358
  with suppress(TypeError):
@@ -397,7 +392,7 @@ class BaseOpenAPISchema(BaseSchema):
397
392
  """Create JSON schemas for the query, body, etc from Swagger parameters definitions."""
398
393
  __tracebackhide__ = True
399
394
  base_url = self.get_base_url()
400
- operation: APIOperation[OpenAPIParameter, Case] = APIOperation(
395
+ operation: APIOperation[OpenAPIParameter] = APIOperation(
401
396
  path=path,
402
397
  method=method,
403
398
  definition=OperationDefinition(raw, resolved, scope),
@@ -423,13 +418,11 @@ class BaseOpenAPISchema(BaseSchema):
423
418
  self._resolver = InliningResolver(self.location or "", self.raw_schema)
424
419
  return self._resolver
425
420
 
426
- def get_content_types(self, operation: APIOperation, response: GenericResponse) -> list[str]:
421
+ def get_content_types(self, operation: APIOperation, response: Response) -> list[str]:
427
422
  """Content types available for this API operation."""
428
423
  raise NotImplementedError
429
424
 
430
- def get_strategies_from_examples(
431
- self, operation: APIOperation, as_strategy_kwargs: dict[str, Any] | None = None
432
- ) -> list[SearchStrategy[Case]]:
425
+ def get_strategies_from_examples(self, operation: APIOperation, **kwargs: Any) -> list[SearchStrategy[Case]]:
433
426
  """Get examples from the API operation."""
434
427
  raise NotImplementedError
435
428
 
@@ -521,15 +514,15 @@ class BaseOpenAPISchema(BaseSchema):
521
514
  operation: APIOperation,
522
515
  hooks: HookDispatcher | None = None,
523
516
  auth_storage: AuthStorage | None = None,
524
- data_generation_method: DataGenerationMethod = DataGenerationMethod.default(),
517
+ generation_mode: GenerationMode = GenerationMode.default(),
525
518
  generation_config: GenerationConfig | None = None,
526
519
  **kwargs: Any,
527
520
  ) -> SearchStrategy:
528
- return get_case_strategy(
521
+ return openapi_cases(
529
522
  operation=operation,
530
523
  auth_storage=auth_storage,
531
524
  hooks=hooks,
532
- generator=data_generation_method,
525
+ generation_mode=generation_mode,
533
526
  generation_config=generation_config or self.generation_config,
534
527
  **kwargs,
535
528
  )
@@ -551,12 +544,11 @@ class BaseOpenAPISchema(BaseSchema):
551
544
  raise NotImplementedError
552
545
 
553
546
  def _get_response_definitions(
554
- self, operation: APIOperation, response: GenericResponse
547
+ self, operation: APIOperation, response: Response
555
548
  ) -> tuple[list[str], dict[str, Any]] | None:
556
549
  try:
557
550
  responses = operation.definition.raw["responses"]
558
551
  except KeyError as exc:
559
- # Possible to get if `validate_schema=False` is passed during schema creation
560
552
  path = operation.path
561
553
  full_path = self.get_full_path(path) if isinstance(path, str) else None
562
554
  self._raise_invalid_schema(exc, full_path, path, operation.method)
@@ -568,7 +560,7 @@ class BaseOpenAPISchema(BaseSchema):
568
560
  return None
569
561
 
570
562
  def get_headers(
571
- self, operation: APIOperation, response: GenericResponse
563
+ self, operation: APIOperation, response: Response
572
564
  ) -> tuple[list[str], dict[str, dict[str, Any]] | None] | None:
573
565
  resolved = self._get_response_definitions(operation, response)
574
566
  if not resolved:
@@ -580,8 +572,8 @@ class BaseOpenAPISchema(BaseSchema):
580
572
  try:
581
573
  return create_state_machine(self)
582
574
  except OperationNotFound as exc:
583
- raise SchemaError(
584
- type=SchemaErrorType.OPEN_API_INVALID_SCHEMA,
575
+ raise LoaderError(
576
+ kind=LoaderErrorKind.OPEN_API_INVALID_SCHEMA,
585
577
  message=f"Invalid Open API link definition: Operation `{exc.item}` not found",
586
578
  ) from exc
587
579
 
@@ -609,7 +601,7 @@ class BaseOpenAPISchema(BaseSchema):
609
601
 
610
602
  .. code-block:: python
611
603
 
612
- schema = schemathesis.from_uri("http://0.0.0.0/schema.yaml")
604
+ schema = schemathesis.openapi.from_url("http://0.0.0.0/schema.yaml")
613
605
 
614
606
  schema.add_link(
615
607
  source=schema["/users/"]["POST"],
@@ -642,11 +634,11 @@ class BaseOpenAPISchema(BaseSchema):
642
634
 
643
635
  @property
644
636
  def validator_cls(self) -> type[jsonschema.Validator]:
645
- if self.spec_version.startswith("3.1") and experimental.OPEN_API_3_1.is_enabled:
637
+ if self.specification.version.startswith("3.1"):
646
638
  return jsonschema.Draft202012Validator
647
639
  return jsonschema.Draft4Validator
648
640
 
649
- def validate_response(self, operation: APIOperation, response: GenericResponse) -> bool | None:
641
+ def validate_response(self, operation: APIOperation, response: Response) -> bool | None:
650
642
  responses = {str(key): value for key, value in operation.definition.raw.get("responses", {}).items()}
651
643
  status_code = str(response.status_code)
652
644
  if status_code in responses:
@@ -660,32 +652,24 @@ class BaseOpenAPISchema(BaseSchema):
660
652
  if not schema:
661
653
  # No schema to check against
662
654
  return None
663
- content_type = response.headers.get("Content-Type")
664
- errors = []
665
- if content_type is None:
666
- media_types = self.get_content_types(operation, response)
667
- formatted_content_types = [f"\n- `{content_type}`" for content_type in media_types]
655
+ content_types = response.headers.get("content-type")
656
+ failures: list[Failure] = []
657
+ if content_types is None:
658
+ all_media_types = self.get_content_types(operation, response)
659
+ formatted_content_types = [f"\n- `{content_type}`" for content_type in all_media_types]
668
660
  message = f"The following media types are documented in the schema:{''.join(formatted_content_types)}"
669
- try:
670
- raise get_missing_content_type_error(operation.verbose_name)(
671
- failures.MissingContentType.title,
672
- context=failures.MissingContentType(message=message, media_types=media_types),
673
- )
674
- except Exception as exc:
675
- errors.append(exc)
676
- if content_type and not is_json_media_type(content_type):
677
- _maybe_raise_one_or_more(errors)
661
+ failures.append(MissingContentType(operation=operation.label, message=message, media_types=all_media_types))
662
+ content_type = None
663
+ else:
664
+ content_type = content_types[0]
665
+ if content_type and not media_types.is_json(content_type):
666
+ _maybe_raise_one_or_more(failures)
678
667
  return None
679
668
  try:
680
- data = get_json(response)
669
+ data = response.json()
681
670
  except JSONDecodeError as exc:
682
- exc_class = get_response_parsing_error(operation.verbose_name, exc)
683
- context = failures.JSONDecodeErrorContext.from_exception(exc)
684
- try:
685
- raise exc_class(context.title, context=context) from exc
686
- except Exception as exc:
687
- errors.append(exc)
688
- _maybe_raise_one_or_more(errors)
671
+ failures.append(MalformedJson.from_exception(operation=operation.label, exc=exc))
672
+ _maybe_raise_one_or_more(failures)
689
673
  with self._validating_response(scopes) as resolver:
690
674
  try:
691
675
  jsonschema.validate(
@@ -697,13 +681,14 @@ class BaseOpenAPISchema(BaseSchema):
697
681
  format_checker=jsonschema.Draft202012Validator.FORMAT_CHECKER,
698
682
  )
699
683
  except jsonschema.ValidationError as exc:
700
- exc_class = get_schema_validation_error(operation.verbose_name, exc)
701
- ctx = failures.ValidationErrorContext.from_exception(exc, output_config=operation.schema.output_config)
702
- try:
703
- raise exc_class(ctx.title, context=ctx) from exc
704
- except Exception as exc:
705
- errors.append(exc)
706
- _maybe_raise_one_or_more(errors)
684
+ failures.append(
685
+ JsonSchemaError.from_exception(
686
+ operation=operation.label,
687
+ exc=exc,
688
+ output_config=operation.schema.output_config,
689
+ )
690
+ )
691
+ _maybe_raise_one_or_more(failures)
707
692
  return None # explicitly return None for mypy
708
693
 
709
694
  @contextmanager
@@ -734,7 +719,7 @@ class BaseOpenAPISchema(BaseSchema):
734
719
  else:
735
720
  break
736
721
  else:
737
- target.update(traverse_schema(fast_deepcopy(schema), callback, self.nullable_name))
722
+ target.update(transform(deepclone(schema), callback, self.nullable_name))
738
723
  if self._inline_reference_cache:
739
724
  components[INLINED_REFERENCES_KEY] = self._inline_reference_cache
740
725
  self._rewritten_components = components
@@ -745,8 +730,8 @@ class BaseOpenAPISchema(BaseSchema):
745
730
 
746
731
  Inlining components helps `hypothesis-jsonschema` generate data that involves non-resolved references.
747
732
  """
748
- schema = fast_deepcopy(schema)
749
- schema = traverse_schema(schema, self._rewrite_references, self.resolver)
733
+ schema = deepclone(schema)
734
+ schema = transform(schema, self._rewrite_references, self.resolver)
750
735
  # Only add definitions that are reachable from the schema via references
751
736
  stack = [schema]
752
737
  seen = set()
@@ -761,8 +746,8 @@ class BaseOpenAPISchema(BaseSchema):
761
746
  pointer = reference[1:]
762
747
  resolved = resolve_pointer(self.rewritten_components, pointer)
763
748
  if resolved is UNRESOLVABLE:
764
- raise SchemaError(
765
- SchemaErrorType.OPEN_API_INVALID_SCHEMA,
749
+ raise LoaderError(
750
+ LoaderErrorKind.OPEN_API_INVALID_SCHEMA,
766
751
  message=f"Unresolvable JSON pointer in the schema: {pointer}",
767
752
  )
768
753
  if isinstance(resolved, dict):
@@ -796,7 +781,7 @@ class BaseOpenAPISchema(BaseSchema):
796
781
  if key not in self._inline_reference_cache:
797
782
  with resolver.resolving(reference) as resolved:
798
783
  # Resolved object also may have references
799
- self._inline_reference_cache[key] = traverse_schema(
784
+ self._inline_reference_cache[key] = transform(
800
785
  resolved, lambda s: self._rewrite_references(s, resolver)
801
786
  )
802
787
  # Rewrite the reference with the new location
@@ -804,12 +789,12 @@ class BaseOpenAPISchema(BaseSchema):
804
789
  return schema
805
790
 
806
791
 
807
- def _maybe_raise_one_or_more(errors: list[Exception]) -> None:
808
- if not errors:
792
+ def _maybe_raise_one_or_more(failures: list[Failure]) -> None:
793
+ if not failures:
809
794
  return
810
- if len(errors) == 1:
811
- raise errors[0]
812
- raise MultipleFailures("\n\n".join(str(error) for error in errors), errors)
795
+ if len(failures) == 1:
796
+ raise failures[0] from None
797
+ raise FailureGroup(failures) from None
813
798
 
814
799
 
815
800
  def _make_reference_key(scopes: list[str], reference: str) -> str:
@@ -898,17 +883,16 @@ class MethodMap(Mapping):
898
883
  def __getitem__(self, item: str) -> APIOperation:
899
884
  try:
900
885
  return self._init_operation(item)
901
- except KeyError as exc:
886
+ except LookupError as exc:
902
887
  available_methods = ", ".join(map(str.upper, self))
903
888
  message = f"Method `{item.upper()}` not found."
904
889
  if available_methods:
905
890
  message += f" Available methods: {available_methods}"
906
- raise KeyError(message) from exc
891
+ raise LookupError(message) from exc
907
892
 
908
893
 
909
894
  OPENAPI_20_DEFAULT_BODY_MEDIA_TYPE = "application/json"
910
895
  OPENAPI_20_DEFAULT_FORM_MEDIA_TYPE = "multipart/form-data"
911
- C = TypeVar("C", bound=Case)
912
896
 
913
897
 
914
898
  class SwaggerV20(BaseOpenAPISchema):
@@ -921,12 +905,9 @@ class SwaggerV20(BaseOpenAPISchema):
921
905
  links_field = "x-links"
922
906
 
923
907
  @property
924
- def spec_version(self) -> str:
925
- return self.raw_schema.get("swagger", "2.0")
926
-
927
- @property
928
- def verbose_name(self) -> str:
929
- return f"Swagger {self.spec_version}"
908
+ def specification(self) -> Specification:
909
+ version = self.raw_schema.get("swagger", "2.0")
910
+ return Specification.openapi(version=version)
930
911
 
931
912
  def _validate(self) -> None:
932
913
  SWAGGER_20_VALIDATOR.validate(self.raw_schema)
@@ -962,6 +943,8 @@ class SwaggerV20(BaseOpenAPISchema):
962
943
  for media_type in body_media_types:
963
944
  collected.append(OpenAPI20Body(definition=parameter, media_type=media_type))
964
945
  else:
946
+ if parameter["in"] in ("header", "cookie"):
947
+ check_header(parameter)
965
948
  collected.append(OpenAPI20Parameter(definition=parameter))
966
949
 
967
950
  if form_parameters:
@@ -972,11 +955,9 @@ class SwaggerV20(BaseOpenAPISchema):
972
955
  )
973
956
  return collected
974
957
 
975
- def get_strategies_from_examples(
976
- self, operation: APIOperation, as_strategy_kwargs: dict[str, Any] | None = None
977
- ) -> list[SearchStrategy[Case]]:
958
+ def get_strategies_from_examples(self, operation: APIOperation, **kwargs: Any) -> list[SearchStrategy[Case]]:
978
959
  """Get examples from the API operation."""
979
- return get_strategies_from_examples(operation, as_strategy_kwargs=as_strategy_kwargs)
960
+ return get_strategies_from_examples(operation, **kwargs)
980
961
 
981
962
  def get_response_schema(self, definition: dict[str, Any], scope: str) -> tuple[list[str], dict[str, Any] | None]:
982
963
  scopes, definition = self.resolver.resolve_in_scope(definition, scope)
@@ -989,7 +970,7 @@ class SwaggerV20(BaseOpenAPISchema):
989
970
  schema, self.nullable_name, is_response_schema=True, update_quantifiers=False
990
971
  )
991
972
 
992
- def get_content_types(self, operation: APIOperation, response: GenericResponse) -> list[str]:
973
+ def get_content_types(self, operation: APIOperation, response: Response) -> list[str]:
993
974
  produces = operation.definition.raw.get("produces", None)
994
975
  if produces:
995
976
  return produces
@@ -999,7 +980,7 @@ class SwaggerV20(BaseOpenAPISchema):
999
980
  return serialization.serialize_swagger2_parameters(definitions)
1000
981
 
1001
982
  def prepare_multipart(
1002
- self, form_data: FormData, operation: APIOperation
983
+ self, form_data: dict[str, Any], operation: APIOperation
1003
984
  ) -> tuple[list | None, dict[str, Any] | None]:
1004
985
  """Prepare form data for sending with `requests`.
1005
986
 
@@ -1040,27 +1021,30 @@ class SwaggerV20(BaseOpenAPISchema):
1040
1021
  def make_case(
1041
1022
  self,
1042
1023
  *,
1043
- case_cls: type[C],
1044
1024
  operation: APIOperation,
1045
- path_parameters: PathParameters | None = None,
1046
- headers: Headers | None = None,
1047
- cookies: Cookies | None = None,
1048
- query: Query | None = None,
1049
- body: Body | NotSet = NOT_SET,
1025
+ method: str | None = None,
1026
+ path: str | None = None,
1027
+ path_parameters: dict[str, Any] | None = None,
1028
+ headers: dict[str, Any] | None = None,
1029
+ cookies: dict[str, Any] | None = None,
1030
+ query: dict[str, Any] | None = None,
1031
+ body: list | dict[str, Any] | str | int | float | bool | bytes | NotSet = NOT_SET,
1050
1032
  media_type: str | None = None,
1051
- generation_time: float = 0.0,
1052
- ) -> C:
1033
+ meta: CaseMetadata | None = None,
1034
+ ) -> Case:
1053
1035
  if body is not NOT_SET and media_type is None:
1054
1036
  media_type = operation._get_default_media_type()
1055
- return case_cls(
1037
+ return Case(
1056
1038
  operation=operation,
1039
+ method=method or operation.method.upper(),
1040
+ path=path or operation.path,
1057
1041
  path_parameters=path_parameters,
1058
1042
  headers=CaseInsensitiveDict(headers) if headers is not None else headers,
1059
1043
  cookies=cookies,
1060
1044
  query=query,
1061
1045
  body=body,
1062
1046
  media_type=media_type,
1063
- generation_time=generation_time,
1047
+ meta=meta,
1064
1048
  )
1065
1049
 
1066
1050
  def _get_consumes_for_operation(self, definition: dict[str, Any]) -> list[str]:
@@ -1078,6 +1062,8 @@ class SwaggerV20(BaseOpenAPISchema):
1078
1062
 
1079
1063
  def _get_payload_schema(self, definition: dict[str, Any], media_type: str) -> dict[str, Any] | None:
1080
1064
  for parameter in definition.get("parameters", []):
1065
+ if "$ref" in parameter:
1066
+ _, parameter = self.resolver.resolve(parameter["$ref"])
1081
1067
  if parameter["in"] == "body":
1082
1068
  return parameter["schema"]
1083
1069
  return None
@@ -1093,15 +1079,12 @@ class OpenApi30(SwaggerV20):
1093
1079
  links_field = "links"
1094
1080
 
1095
1081
  @property
1096
- def spec_version(self) -> str:
1097
- return self.raw_schema["openapi"]
1098
-
1099
- @property
1100
- def verbose_name(self) -> str:
1101
- return f"Open API {self.spec_version}"
1082
+ def specification(self) -> Specification:
1083
+ version = self.raw_schema["openapi"]
1084
+ return Specification.openapi(version=version)
1102
1085
 
1103
1086
  def _validate(self) -> None:
1104
- if self.spec_version.startswith("3.1"):
1087
+ if self.specification.version.startswith("3.1"):
1105
1088
  # Currently we treat Open API 3.1 as 3.0 in some regard
1106
1089
  OPENAPI_31_VALIDATOR.validate(self.raw_schema)
1107
1090
  else:
@@ -1120,7 +1103,12 @@ class OpenApi30(SwaggerV20):
1120
1103
  self, parameters: Iterable[dict[str, Any]], definition: dict[str, Any]
1121
1104
  ) -> list[OpenAPIParameter]:
1122
1105
  # Open API 3.0 has the `requestBody` keyword, which may contain multiple different payload variants.
1123
- collected: list[OpenAPIParameter] = [OpenAPI30Parameter(definition=parameter) for parameter in parameters]
1106
+ collected: list[OpenAPIParameter] = []
1107
+
1108
+ for parameter in parameters:
1109
+ if parameter["in"] in ("header", "cookie"):
1110
+ check_header(parameter)
1111
+ collected.append(OpenAPI30Parameter(definition=parameter))
1124
1112
  if "requestBody" in definition:
1125
1113
  required = definition["requestBody"].get("required", False)
1126
1114
  description = definition["requestBody"].get("description")
@@ -1143,13 +1131,11 @@ class OpenApi30(SwaggerV20):
1143
1131
  )
1144
1132
  return scopes, None
1145
1133
 
1146
- def get_strategies_from_examples(
1147
- self, operation: APIOperation, as_strategy_kwargs: dict[str, Any] | None = None
1148
- ) -> list[SearchStrategy[Case]]:
1134
+ def get_strategies_from_examples(self, operation: APIOperation, **kwargs: Any) -> list[SearchStrategy[Case]]:
1149
1135
  """Get examples from the API operation."""
1150
- return get_strategies_from_examples(operation, as_strategy_kwargs=as_strategy_kwargs)
1136
+ return get_strategies_from_examples(operation, **kwargs)
1151
1137
 
1152
- def get_content_types(self, operation: APIOperation, response: GenericResponse) -> list[str]:
1138
+ def get_content_types(self, operation: APIOperation, response: Response) -> list[str]:
1153
1139
  resolved = self._get_response_definitions(operation, response)
1154
1140
  if not resolved:
1155
1141
  return []
@@ -1164,7 +1150,7 @@ class OpenApi30(SwaggerV20):
1164
1150
  return list(request_body["content"])
1165
1151
 
1166
1152
  def prepare_multipart(
1167
- self, form_data: FormData, operation: APIOperation
1153
+ self, form_data: dict[str, Any], operation: APIOperation
1168
1154
  ) -> tuple[list | None, dict[str, Any] | None]:
1169
1155
  """Prepare form data for sending with `requests`.
1170
1156
 
@@ -1182,7 +1168,7 @@ class OpenApi30(SwaggerV20):
1182
1168
  # Open API 3.0 requires media types to be present. We can get here only if the schema defines
1183
1169
  # the "multipart/form-data" media type, or any other more general media type that matches it (like `*/*`)
1184
1170
  for media_type, entry in content.items():
1185
- main, sub = parse_content_type(media_type)
1171
+ main, sub = media_types.parse(media_type)
1186
1172
  if main in ("*", "multipart") and sub in ("*", "form-data", "mixed"):
1187
1173
  schema = entry.get("schema")
1188
1174
  break
@@ -1206,8 +1192,8 @@ class OpenApi30(SwaggerV20):
1206
1192
  else:
1207
1193
  body = definition["requestBody"]
1208
1194
  if "content" in body:
1209
- main, sub = parse_content_type(media_type)
1195
+ main, sub = media_types.parse(media_type)
1210
1196
  for defined_media_type, item in body["content"].items():
1211
- if parse_content_type(defined_media_type) == (main, sub):
1197
+ if media_types.parse(defined_media_type) == (main, sub):
1212
1198
  return item["schema"]
1213
1199
  return None