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
schemathesis/schemas.py CHANGED
@@ -1,72 +1,52 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Mapping
4
- from contextlib import nullcontext
5
4
  from dataclasses import dataclass, field
6
- from functools import lru_cache
5
+ from functools import cached_property, lru_cache, partial
6
+ from itertools import chain
7
7
  from typing import (
8
8
  TYPE_CHECKING,
9
9
  Any,
10
10
  Callable,
11
- ContextManager,
12
11
  Generator,
13
- Iterable,
12
+ Generic,
14
13
  Iterator,
15
14
  NoReturn,
16
- Sequence,
17
15
  TypeVar,
18
16
  )
19
- from urllib.parse import quote, unquote, urljoin, urlparse, urlsplit, urlunsplit
17
+ from urllib.parse import quote, unquote, urljoin, urlsplit, urlunsplit
18
+
19
+ from schemathesis import transport
20
+ from schemathesis.core import NOT_SET, NotSet
21
+ from schemathesis.core.errors import IncorrectUsage, InvalidSchema
22
+ from schemathesis.core.output import OutputConfig
23
+ from schemathesis.core.rate_limit import build_limiter
24
+ from schemathesis.core.result import Ok, Result
25
+ from schemathesis.core.transport import Response
26
+ from schemathesis.generation import GenerationConfig, GenerationMode
27
+ from schemathesis.generation.case import Case
28
+ from schemathesis.generation.hypothesis import strategies
29
+ from schemathesis.generation.hypothesis.given import GivenInput, given_proxy
30
+ from schemathesis.generation.meta import CaseMetadata
31
+ from schemathesis.hooks import HookDispatcherMark
20
32
 
21
- from ._dependency_versions import IS_PYRATE_LIMITER_ABOVE_3
22
- from ._hypothesis import create_test
23
33
  from .auths import AuthStorage
24
- from .code_samples import CodeSampleStyle
25
- from .constants import NOT_SET
26
- from .exceptions import OperationSchemaError, UsageError
27
34
  from .filters import (
28
35
  FilterSet,
29
36
  FilterValue,
30
37
  MatcherFunc,
31
38
  RegexValue,
32
- filter_set_from_components,
33
39
  is_deprecated,
34
40
  )
35
- from .generation import (
36
- DEFAULT_DATA_GENERATION_METHODS,
37
- DataGenerationMethod,
38
- DataGenerationMethodInput,
39
- GenerationConfig,
40
- combine_strategies,
41
- )
42
- from .hooks import HookContext, HookDispatcher, HookScope, dispatch, to_filterable_hook
43
- from .internal.deprecation import warn_filtration_arguments
44
- from .internal.output import OutputConfig
45
- from .internal.result import Ok, Result
46
- from .models import APIOperation, Case
47
- from .utils import PARAMETRIZE_MARKER, GivenInput, given_proxy
41
+ from .hooks import GLOBAL_HOOK_DISPATCHER, HookContext, HookDispatcher, HookScope, dispatch, to_filterable_hook
48
42
 
49
43
  if TYPE_CHECKING:
50
- import hypothesis
51
44
  from hypothesis.strategies import SearchStrategy
52
45
  from pyrate_limiter import Limiter
46
+ from typing_extensions import Self
53
47
 
54
- from .stateful import Stateful, StatefulTest
55
- from .stateful.state_machine import APIStateMachine
56
- from .transports import Transport
57
- from .transports.responses import GenericResponse
58
- from .types import (
59
- Body,
60
- Cookies,
61
- Filter,
62
- FormData,
63
- GenericTest,
64
- Headers,
65
- NotSet,
66
- PathParameters,
67
- Query,
68
- Specification,
69
- )
48
+ from schemathesis.core import Specification
49
+ from schemathesis.generation.stateful.state_machine import APIStateMachine
70
50
 
71
51
 
72
52
  C = TypeVar("C", bound=Case)
@@ -77,31 +57,73 @@ def get_full_path(base_path: str, path: str) -> str:
77
57
  return unquote(urljoin(base_path, quote(path.lstrip("/"))))
78
58
 
79
59
 
60
+ @dataclass
61
+ class FilteredCount:
62
+ """Count of total items and those passing filters."""
63
+
64
+ total: int
65
+ selected: int
66
+
67
+ __slots__ = ("total", "selected")
68
+
69
+ def __init__(self) -> None:
70
+ self.total = 0
71
+ self.selected = 0
72
+
73
+
74
+ @dataclass
75
+ class ApiStatistic:
76
+ """Statistics about API operations and links."""
77
+
78
+ operations: FilteredCount
79
+ links: FilteredCount
80
+
81
+ __slots__ = ("operations", "links")
82
+
83
+ def __init__(self) -> None:
84
+ self.operations = FilteredCount()
85
+ self.links = FilteredCount()
86
+
87
+
88
+ @dataclass
89
+ class ApiOperationsCount:
90
+ """Statistics about API operations."""
91
+
92
+ total: int
93
+ selected: int
94
+
95
+ __slots__ = ("total", "selected")
96
+
97
+ def __init__(self) -> None:
98
+ self.total = 0
99
+ self.selected = 0
100
+
101
+
80
102
  @dataclass(eq=False)
81
103
  class BaseSchema(Mapping):
82
104
  raw_schema: dict[str, Any]
83
- transport: Transport
84
- specification: Specification
85
105
  location: str | None = None
86
106
  base_url: str | None = None
87
107
  filter_set: FilterSet = field(default_factory=FilterSet)
88
108
  app: Any = None
89
109
  hooks: HookDispatcher = field(default_factory=lambda: HookDispatcher(scope=HookScope.SCHEMA))
90
110
  auth: AuthStorage = field(default_factory=AuthStorage)
91
- test_function: GenericTest | None = None
92
- validate_schema: bool = True
93
- data_generation_methods: list[DataGenerationMethod] = field(
94
- default_factory=lambda: list(DEFAULT_DATA_GENERATION_METHODS)
95
- )
111
+ test_function: Callable | None = None
96
112
  generation_config: GenerationConfig = field(default_factory=GenerationConfig)
97
113
  output_config: OutputConfig = field(default_factory=OutputConfig)
98
- code_sample_style: CodeSampleStyle = CodeSampleStyle.default()
99
114
  rate_limiter: Limiter | None = None
100
- sanitize_output: bool = True
101
115
 
102
116
  def __post_init__(self) -> None:
103
117
  self.hook = to_filterable_hook(self.hooks) # type: ignore[method-assign]
104
118
 
119
+ @property
120
+ def specification(self) -> Specification:
121
+ raise NotImplementedError
122
+
123
+ @property
124
+ def transport(self) -> transport.BaseTransport:
125
+ return transport.get(self.app)
126
+
105
127
  def _repr_pretty_(self, *args: Any, **kwargs: Any) -> None: ...
106
128
 
107
129
  def include(
@@ -191,15 +213,11 @@ class BaseSchema(Mapping):
191
213
  raise NotImplementedError
192
214
 
193
215
  def __len__(self) -> int:
194
- return self.operations_count
216
+ return self.statistic.operations.total
195
217
 
196
218
  def hook(self, hook: str | Callable) -> Callable:
197
219
  return self.hooks.register(hook)
198
220
 
199
- @property
200
- def verbose_name(self) -> str:
201
- raise NotImplementedError
202
-
203
221
  def get_full_path(self, path: str) -> str:
204
222
  """Compute full path for the given path."""
205
223
  return get_full_path(self.base_path, path)
@@ -234,22 +252,19 @@ class BaseSchema(Mapping):
234
252
  def validate(self) -> None:
235
253
  raise NotImplementedError
236
254
 
237
- @property
238
- def operations_count(self) -> int:
239
- raise NotImplementedError
255
+ @cached_property
256
+ def statistic(self) -> ApiStatistic:
257
+ return self._measure_statistic()
240
258
 
241
- @property
242
- def links_count(self) -> int:
259
+ def _measure_statistic(self) -> ApiStatistic:
243
260
  raise NotImplementedError
244
261
 
245
262
  def get_all_operations(
246
- self, hooks: HookDispatcher | None = None, generation_config: GenerationConfig | None = None
247
- ) -> Generator[Result[APIOperation, OperationSchemaError], None, None]:
263
+ self, generation_config: GenerationConfig | None = None
264
+ ) -> Generator[Result[APIOperation, InvalidSchema], None, None]:
248
265
  raise NotImplementedError
249
266
 
250
- def get_strategies_from_examples(
251
- self, operation: APIOperation, as_strategy_kwargs: dict[str, Any] | None = None
252
- ) -> list[SearchStrategy[Case]]:
267
+ def get_strategies_from_examples(self, operation: APIOperation, **kwargs: Any) -> list[SearchStrategy[Case]]:
253
268
  """Get examples from the API operation."""
254
269
  raise NotImplementedError
255
270
 
@@ -257,85 +272,20 @@ class BaseSchema(Mapping):
257
272
  """Get applied security requirements for the given API operation."""
258
273
  raise NotImplementedError
259
274
 
260
- def get_stateful_tests(
261
- self, response: GenericResponse, operation: APIOperation, stateful: Stateful | None
262
- ) -> Sequence[StatefulTest]:
263
- """Get a list of additional tests, that should be executed after this response from the API operation."""
264
- raise NotImplementedError
265
-
266
275
  def get_parameter_serializer(self, operation: APIOperation, location: str) -> Callable | None:
267
276
  """Get a function that serializes parameters for the given location."""
268
277
  raise NotImplementedError
269
278
 
270
- def get_all_tests(
271
- self,
272
- func: Callable,
273
- settings: hypothesis.settings | None = None,
274
- generation_config: GenerationConfig | None = None,
275
- seed: int | None = None,
276
- as_strategy_kwargs: dict[str, Any] | Callable[[APIOperation], dict[str, Any]] | None = None,
277
- hooks: HookDispatcher | None = None,
278
- _given_kwargs: dict[str, GivenInput] | None = None,
279
- ) -> Generator[Result[tuple[APIOperation, Callable], OperationSchemaError], None, None]:
280
- """Generate all operations and Hypothesis tests for them."""
281
- for result in self.get_all_operations(hooks=hooks, generation_config=generation_config):
282
- if isinstance(result, Ok):
283
- operation = result.ok()
284
- _as_strategy_kwargs: dict[str, Any] | None
285
- if callable(as_strategy_kwargs):
286
- _as_strategy_kwargs = as_strategy_kwargs(operation)
287
- else:
288
- _as_strategy_kwargs = as_strategy_kwargs
289
- test = create_test(
290
- operation=operation,
291
- test=func,
292
- settings=settings,
293
- seed=seed,
294
- data_generation_methods=self.data_generation_methods,
295
- generation_config=generation_config,
296
- as_strategy_kwargs=_as_strategy_kwargs,
297
- _given_kwargs=_given_kwargs,
298
- )
299
- yield Ok((operation, test))
300
- else:
301
- yield result
302
-
303
- def parametrize(
304
- self,
305
- method: Filter | None = NOT_SET,
306
- endpoint: Filter | None = NOT_SET,
307
- tag: Filter | None = NOT_SET,
308
- operation_id: Filter | None = NOT_SET,
309
- validate_schema: bool | NotSet = NOT_SET,
310
- skip_deprecated_operations: bool | NotSet = NOT_SET,
311
- data_generation_methods: Iterable[DataGenerationMethod] | NotSet = NOT_SET,
312
- code_sample_style: str | NotSet = NOT_SET,
313
- ) -> Callable:
279
+ def parametrize(self) -> Callable:
314
280
  """Mark a test function as a parametrized one."""
315
- _code_sample_style = (
316
- CodeSampleStyle.from_str(code_sample_style) if isinstance(code_sample_style, str) else code_sample_style
317
- )
318
281
 
319
- for name in ("method", "endpoint", "tag", "operation_id", "skip_deprecated_operations"):
320
- value = locals()[name]
321
- if value is not NOT_SET:
322
- warn_filtration_arguments(name)
282
+ def wrapper(func: Callable) -> Callable:
283
+ from schemathesis.pytest.plugin import SchemaHandleMark
323
284
 
324
- filter_set = filter_set_from_components(
325
- include=True,
326
- method=method,
327
- endpoint=endpoint,
328
- tag=tag,
329
- operation_id=operation_id,
330
- skip_deprecated_operations=skip_deprecated_operations,
331
- parent=self.filter_set,
332
- )
333
-
334
- def wrapper(func: GenericTest) -> GenericTest:
335
- if hasattr(func, PARAMETRIZE_MARKER):
285
+ if SchemaHandleMark.is_set(func):
336
286
 
337
287
  def wrapped_test(*_: Any, **__: Any) -> NoReturn:
338
- raise UsageError(
288
+ raise IncorrectUsage(
339
289
  f"You have applied `parametrize` to the `{func.__name__}` test more than once, which "
340
290
  "overrides the previous decorator. "
341
291
  "The `parametrize` decorator could be applied to the same function at most once."
@@ -343,14 +293,8 @@ class BaseSchema(Mapping):
343
293
 
344
294
  return wrapped_test
345
295
  HookDispatcher.add_dispatcher(func)
346
- cloned = self.clone(
347
- test_function=func,
348
- validate_schema=validate_schema,
349
- data_generation_methods=data_generation_methods,
350
- filter_set=filter_set,
351
- code_sample_style=_code_sample_style, # type: ignore
352
- )
353
- setattr(func, PARAMETRIZE_MARKER, cloned)
296
+ cloned = self.clone(test_function=func)
297
+ SchemaHandleMark.set(func, cloned)
354
298
  return func
355
299
 
356
300
  return wrapper
@@ -360,65 +304,29 @@ class BaseSchema(Mapping):
360
304
  return given_proxy(*args, **kwargs)
361
305
 
362
306
  def clone(
363
- self,
364
- *,
365
- base_url: str | None | NotSet = NOT_SET,
366
- test_function: GenericTest | None = None,
367
- app: Any = NOT_SET,
368
- hooks: HookDispatcher | NotSet = NOT_SET,
369
- auth: AuthStorage | NotSet = NOT_SET,
370
- validate_schema: bool | NotSet = NOT_SET,
371
- data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET,
372
- generation_config: GenerationConfig | NotSet = NOT_SET,
373
- output_config: OutputConfig | NotSet = NOT_SET,
374
- code_sample_style: CodeSampleStyle | NotSet = NOT_SET,
375
- rate_limiter: Limiter | None = NOT_SET,
376
- sanitize_output: bool | NotSet | None = NOT_SET,
377
- filter_set: FilterSet | None = None,
307
+ self, *, test_function: Callable | NotSet = NOT_SET, filter_set: FilterSet | NotSet = NOT_SET
378
308
  ) -> BaseSchema:
379
- if base_url is NOT_SET:
380
- base_url = self.base_url
381
- if app is NOT_SET:
382
- app = self.app
383
- if validate_schema is NOT_SET:
384
- validate_schema = self.validate_schema
385
- if filter_set is None:
386
- filter_set = self.filter_set
387
- if hooks is NOT_SET:
388
- hooks = self.hooks
389
- if auth is NOT_SET:
390
- auth = self.auth
391
- if data_generation_methods is NOT_SET:
392
- data_generation_methods = self.data_generation_methods
393
- if generation_config is NOT_SET:
394
- generation_config = self.generation_config
395
- if output_config is NOT_SET:
396
- output_config = self.output_config
397
- if code_sample_style is NOT_SET:
398
- code_sample_style = self.code_sample_style
399
- if rate_limiter is NOT_SET:
400
- rate_limiter = self.rate_limiter
401
- if sanitize_output is NOT_SET:
402
- sanitize_output = self.sanitize_output
309
+ if isinstance(test_function, NotSet):
310
+ _test_function = self.test_function
311
+ else:
312
+ _test_function = test_function
313
+ if isinstance(filter_set, NotSet):
314
+ _filter_set = self.filter_set
315
+ else:
316
+ _filter_set = filter_set
403
317
 
404
318
  return self.__class__(
405
319
  self.raw_schema,
406
- specification=self.specification,
407
320
  location=self.location,
408
- base_url=base_url, # type: ignore
409
- app=app,
410
- hooks=hooks, # type: ignore
411
- auth=auth, # type: ignore
412
- test_function=test_function,
413
- validate_schema=validate_schema, # type: ignore
414
- data_generation_methods=data_generation_methods, # type: ignore
415
- generation_config=generation_config, # type: ignore
416
- output_config=output_config, # type: ignore
417
- code_sample_style=code_sample_style, # type: ignore
418
- rate_limiter=rate_limiter, # type: ignore
419
- sanitize_output=sanitize_output, # type: ignore
420
- filter_set=filter_set, # type: ignore
421
- transport=self.transport,
321
+ base_url=self.base_url,
322
+ app=self.app,
323
+ hooks=self.hooks,
324
+ auth=self.auth,
325
+ test_function=_test_function,
326
+ generation_config=self.generation_config,
327
+ output_config=self.output_config,
328
+ rate_limiter=self.rate_limiter,
329
+ filter_set=_filter_set,
422
330
  )
423
331
 
424
332
  def get_local_hook_dispatcher(self) -> HookDispatcher | None:
@@ -426,7 +334,7 @@ class BaseSchema(Mapping):
426
334
  # It might be not present when it is used without pytest via `APIOperation.as_strategy()`
427
335
  if self.test_function is not None:
428
336
  # Might be missing it in case of `LazySchema` usage
429
- return getattr(self.test_function, "_schemathesis_hooks", None) # type: ignore
337
+ return HookDispatcherMark.get(self.test_function)
430
338
  return None
431
339
 
432
340
  def dispatch_hook(self, name: str, context: HookContext, *args: Any, **kwargs: Any) -> None:
@@ -438,7 +346,7 @@ class BaseSchema(Mapping):
438
346
  local_dispatcher.dispatch(name, context, *args, **kwargs)
439
347
 
440
348
  def prepare_multipart(
441
- self, form_data: FormData, operation: APIOperation
349
+ self, form_data: dict[str, Any], operation: APIOperation
442
350
  ) -> tuple[list | None, dict[str, Any] | None]:
443
351
  """Split content of `form_data` into files & data.
444
352
 
@@ -452,15 +360,17 @@ class BaseSchema(Mapping):
452
360
  def make_case(
453
361
  self,
454
362
  *,
455
- case_cls: type[C],
456
363
  operation: APIOperation,
457
- path_parameters: PathParameters | None = None,
458
- headers: Headers | None = None,
459
- cookies: Cookies | None = None,
460
- query: Query | None = None,
461
- body: Body | NotSet = NOT_SET,
364
+ method: str | None = None,
365
+ path: str | None = None,
366
+ path_parameters: dict[str, Any] | None = None,
367
+ headers: dict[str, Any] | None = None,
368
+ cookies: dict[str, Any] | None = None,
369
+ query: dict[str, Any] | None = None,
370
+ body: list | dict[str, Any] | str | int | float | bool | bytes | NotSet = NOT_SET,
462
371
  media_type: str | None = None,
463
- ) -> C:
372
+ meta: CaseMetadata | None = None,
373
+ ) -> Case:
464
374
  raise NotImplementedError
465
375
 
466
376
  def get_case_strategy(
@@ -468,7 +378,7 @@ class BaseSchema(Mapping):
468
378
  operation: APIOperation,
469
379
  hooks: HookDispatcher | None = None,
470
380
  auth_storage: AuthStorage | None = None,
471
- data_generation_method: DataGenerationMethod = DataGenerationMethod.default(),
381
+ generation_mode: GenerationMode = GenerationMode.default(),
472
382
  generation_config: GenerationConfig | None = None,
473
383
  **kwargs: Any,
474
384
  ) -> SearchStrategy:
@@ -484,22 +394,12 @@ class BaseSchema(Mapping):
484
394
  def get_tags(self, operation: APIOperation) -> list[str] | None:
485
395
  raise NotImplementedError
486
396
 
487
- def validate_response(self, operation: APIOperation, response: GenericResponse) -> bool | None:
397
+ def validate_response(self, operation: APIOperation, response: Response) -> bool | None:
488
398
  raise NotImplementedError
489
399
 
490
400
  def prepare_schema(self, schema: Any) -> Any:
491
401
  raise NotImplementedError
492
402
 
493
- def ratelimit(self) -> ContextManager:
494
- """Limit the rate of sending generated requests."""
495
- label = urlparse(self.base_url).netloc
496
- if self.rate_limiter is not None:
497
- if IS_PYRATE_LIMITER_ABOVE_3:
498
- self.rate_limiter.try_acquire(label)
499
- else:
500
- return self.rate_limiter.ratelimit(label, delay=True, max_delay=0)
501
- return nullcontext()
502
-
503
403
  def _get_payload_schema(self, definition: dict[str, Any], media_type: str) -> dict[str, Any] | None:
504
404
  raise NotImplementedError
505
405
 
@@ -507,23 +407,50 @@ class BaseSchema(Mapping):
507
407
  self,
508
408
  hooks: HookDispatcher | None = None,
509
409
  auth_storage: AuthStorage | None = None,
510
- data_generation_method: DataGenerationMethod = DataGenerationMethod.default(),
410
+ generation_mode: GenerationMode = GenerationMode.default(),
511
411
  generation_config: GenerationConfig | None = None,
512
412
  **kwargs: Any,
513
413
  ) -> SearchStrategy:
514
414
  """Build a strategy for generating test cases for all defined API operations."""
515
- strategies = [
415
+ _strategies = [
516
416
  operation.ok().as_strategy(
517
417
  hooks=hooks,
518
418
  auth_storage=auth_storage,
519
- data_generation_method=data_generation_method,
419
+ generation_mode=generation_mode,
520
420
  generation_config=generation_config,
521
421
  **kwargs,
522
422
  )
523
- for operation in self.get_all_operations(hooks=hooks)
423
+ for operation in self.get_all_operations()
524
424
  if isinstance(operation, Ok)
525
425
  ]
526
- return combine_strategies(strategies)
426
+ return strategies.combine(_strategies)
427
+
428
+ def configure(
429
+ self,
430
+ *,
431
+ base_url: str | None | NotSet = NOT_SET,
432
+ location: str | None | NotSet = NOT_SET,
433
+ rate_limit: str | None | NotSet = NOT_SET,
434
+ generation: GenerationConfig | NotSet = NOT_SET,
435
+ output: OutputConfig | NotSet = NOT_SET,
436
+ app: Any | NotSet = NOT_SET,
437
+ ) -> Self:
438
+ if not isinstance(base_url, NotSet):
439
+ self.base_url = base_url
440
+ if not isinstance(location, NotSet):
441
+ self.location = location
442
+ if not isinstance(rate_limit, NotSet):
443
+ if isinstance(rate_limit, str):
444
+ self.rate_limiter = build_limiter(rate_limit)
445
+ else:
446
+ self.rate_limiter = None
447
+ if not isinstance(generation, NotSet):
448
+ self.generation_config = generation
449
+ if not isinstance(output, NotSet):
450
+ self.output_config = output
451
+ if not isinstance(app, NotSet):
452
+ self.app = app
453
+ return self
527
454
 
528
455
 
529
456
  @dataclass
@@ -544,19 +471,320 @@ class APIOperationMap(Mapping):
544
471
  self,
545
472
  hooks: HookDispatcher | None = None,
546
473
  auth_storage: AuthStorage | None = None,
547
- data_generation_method: DataGenerationMethod = DataGenerationMethod.default(),
474
+ generation_mode: GenerationMode = GenerationMode.default(),
548
475
  generation_config: GenerationConfig | None = None,
549
476
  **kwargs: Any,
550
477
  ) -> SearchStrategy:
551
478
  """Build a strategy for generating test cases for all API operations defined in this subset."""
552
- strategies = [
479
+ _strategies = [
553
480
  operation.as_strategy(
554
481
  hooks=hooks,
555
482
  auth_storage=auth_storage,
556
- data_generation_method=data_generation_method,
483
+ generation_mode=generation_mode,
557
484
  generation_config=generation_config,
558
485
  **kwargs,
559
486
  )
560
487
  for operation in self._data.values()
561
488
  ]
562
- return combine_strategies(strategies)
489
+ return strategies.combine(_strategies)
490
+
491
+
492
+ @dataclass(eq=False)
493
+ class Parameter:
494
+ """A logically separate parameter bound to a location (e.g., to "query string").
495
+
496
+ For example, if the API requires multiple headers to be present, each header is presented as a separate
497
+ `Parameter` instance.
498
+ """
499
+
500
+ # The parameter definition in the language acceptable by the API
501
+ definition: Any
502
+
503
+ @property
504
+ def location(self) -> str:
505
+ """Where this parameter is located.
506
+
507
+ E.g. "query" or "body"
508
+ """
509
+ raise NotImplementedError
510
+
511
+ @property
512
+ def name(self) -> str:
513
+ """Parameter name."""
514
+ raise NotImplementedError
515
+
516
+ @property
517
+ def is_required(self) -> bool:
518
+ """Whether the parameter is required for a successful API call."""
519
+ raise NotImplementedError
520
+
521
+ def serialize(self, operation: APIOperation) -> str:
522
+ """Get parameter's string representation."""
523
+ raise NotImplementedError
524
+
525
+
526
+ P = TypeVar("P", bound=Parameter)
527
+
528
+
529
+ @dataclass
530
+ class ParameterSet(Generic[P]):
531
+ """A set of parameters for the same location."""
532
+
533
+ items: list[P] = field(default_factory=list)
534
+
535
+ def _repr_pretty_(self, *args: Any, **kwargs: Any) -> None: ...
536
+
537
+ def add(self, parameter: P) -> None:
538
+ """Add a new parameter."""
539
+ self.items.append(parameter)
540
+
541
+ def get(self, name: str) -> P | None:
542
+ for parameter in self:
543
+ if parameter.name == name:
544
+ return parameter
545
+ return None
546
+
547
+ def contains(self, name: str) -> bool:
548
+ return self.get(name) is not None
549
+
550
+ def __contains__(self, item: str) -> bool:
551
+ return self.contains(item)
552
+
553
+ def __bool__(self) -> bool:
554
+ return bool(self.items)
555
+
556
+ def __iter__(self) -> Generator[P, None, None]:
557
+ yield from iter(self.items)
558
+
559
+ def __len__(self) -> int:
560
+ return len(self.items)
561
+
562
+ def __getitem__(self, item: int) -> P:
563
+ return self.items[item]
564
+
565
+
566
+ class PayloadAlternatives(ParameterSet[P]):
567
+ """A set of alternative payloads."""
568
+
569
+
570
+ D = TypeVar("D", bound=dict)
571
+
572
+
573
+ @dataclass(repr=False)
574
+ class OperationDefinition(Generic[D]):
575
+ """A wrapper to store not resolved API operation definitions.
576
+
577
+ To prevent recursion errors we need to store definitions without resolving references. But operation definitions
578
+ itself can be behind a reference (when there is a ``$ref`` in ``paths`` values), therefore we need to store this
579
+ scope change to have a proper reference resolving later.
580
+ """
581
+
582
+ raw: D
583
+ resolved: D
584
+ scope: str
585
+
586
+ __slots__ = ("raw", "resolved", "scope")
587
+
588
+ def _repr_pretty_(self, *args: Any, **kwargs: Any) -> None: ...
589
+
590
+
591
+ @dataclass(eq=False)
592
+ class APIOperation(Generic[P]):
593
+ """A single operation defined in an API.
594
+
595
+ You can get one via a ``schema`` instance.
596
+
597
+ .. code-block:: python
598
+
599
+ # Get the POST /items operation
600
+ operation = schema["/items"]["POST"]
601
+
602
+ """
603
+
604
+ # `path` does not contain `basePath`
605
+ # Example <scheme>://<host>/<basePath>/users - "/users" is path
606
+ # https://swagger.io/docs/specification/2-0/api-host-and-base-path/
607
+ path: str
608
+ method: str
609
+ definition: OperationDefinition = field(repr=False)
610
+ schema: BaseSchema
611
+ label: str = None # type: ignore
612
+ app: Any = None
613
+ base_url: str | None = None
614
+ path_parameters: ParameterSet[P] = field(default_factory=ParameterSet)
615
+ headers: ParameterSet[P] = field(default_factory=ParameterSet)
616
+ cookies: ParameterSet[P] = field(default_factory=ParameterSet)
617
+ query: ParameterSet[P] = field(default_factory=ParameterSet)
618
+ body: PayloadAlternatives[P] = field(default_factory=PayloadAlternatives)
619
+
620
+ def __post_init__(self) -> None:
621
+ if self.label is None:
622
+ self.label = f"{self.method.upper()} {self.full_path}" # type: ignore
623
+
624
+ @property
625
+ def full_path(self) -> str:
626
+ return self.schema.get_full_path(self.path)
627
+
628
+ @property
629
+ def links(self) -> dict[str, dict[str, Any]]:
630
+ return self.schema.get_links(self)
631
+
632
+ @property
633
+ def tags(self) -> list[str] | None:
634
+ return self.schema.get_tags(self)
635
+
636
+ def iter_parameters(self) -> Iterator[P]:
637
+ """Iterate over all operation's parameters."""
638
+ return chain(self.path_parameters, self.headers, self.cookies, self.query)
639
+
640
+ def _lookup_container(self, location: str) -> ParameterSet[P] | PayloadAlternatives[P] | None:
641
+ return {
642
+ "path": self.path_parameters,
643
+ "header": self.headers,
644
+ "cookie": self.cookies,
645
+ "query": self.query,
646
+ "body": self.body,
647
+ }.get(location)
648
+
649
+ def add_parameter(self, parameter: P) -> None:
650
+ """Add a new processed parameter to an API operation.
651
+
652
+ :param parameter: A parameter that will be used with this operation.
653
+ :rtype: None
654
+ """
655
+ # If the parameter has a typo, then by default, there will be an error from `jsonschema` earlier.
656
+ # But if the user wants to skip schema validation, we choose to ignore a malformed parameter.
657
+ # In this case, we still might generate some tests for an API operation, but without this parameter,
658
+ # which is better than skip the whole operation from testing.
659
+ container = self._lookup_container(parameter.location)
660
+ if container is not None:
661
+ container.add(parameter)
662
+
663
+ def get_parameter(self, name: str, location: str) -> P | None:
664
+ container = self._lookup_container(location)
665
+ if container is not None:
666
+ return container.get(name)
667
+ return None
668
+
669
+ def as_strategy(
670
+ self,
671
+ hooks: HookDispatcher | None = None,
672
+ auth_storage: AuthStorage | None = None,
673
+ generation_mode: GenerationMode = GenerationMode.default(),
674
+ generation_config: GenerationConfig | None = None,
675
+ **kwargs: Any,
676
+ ) -> SearchStrategy[Case]:
677
+ """Turn this API operation into a Hypothesis strategy."""
678
+ strategy = self.schema.get_case_strategy(
679
+ self, hooks, auth_storage, generation_mode, generation_config=generation_config, **kwargs
680
+ )
681
+
682
+ def _apply_hooks(dispatcher: HookDispatcher, _strategy: SearchStrategy[Case]) -> SearchStrategy[Case]:
683
+ context = HookContext(self)
684
+ for hook in dispatcher.get_all_by_name("before_generate_case"):
685
+ _strategy = hook(context, _strategy)
686
+ for hook in dispatcher.get_all_by_name("filter_case"):
687
+ hook = partial(hook, context)
688
+ _strategy = _strategy.filter(hook)
689
+ for hook in dispatcher.get_all_by_name("map_case"):
690
+ hook = partial(hook, context)
691
+ _strategy = _strategy.map(hook)
692
+ for hook in dispatcher.get_all_by_name("flatmap_case"):
693
+ hook = partial(hook, context)
694
+ _strategy = _strategy.flatmap(hook)
695
+ return _strategy
696
+
697
+ strategy = _apply_hooks(GLOBAL_HOOK_DISPATCHER, strategy)
698
+ strategy = _apply_hooks(self.schema.hooks, strategy)
699
+ if hooks is not None:
700
+ strategy = _apply_hooks(hooks, strategy)
701
+ return strategy
702
+
703
+ def get_security_requirements(self) -> list[str]:
704
+ return self.schema.get_security_requirements(self)
705
+
706
+ def get_strategies_from_examples(self, **kwargs: Any) -> list[SearchStrategy[Case]]:
707
+ """Get examples from the API operation."""
708
+ kwargs.setdefault("generation_config", self.schema.generation_config)
709
+ return self.schema.get_strategies_from_examples(self, **kwargs)
710
+
711
+ def get_parameter_serializer(self, location: str) -> Callable | None:
712
+ """Get a function that serializes parameters for the given location.
713
+
714
+ It handles serializing data into various `collectionFormat` options and similar.
715
+ Note that payload is handled by this function - it is handled by serializers.
716
+ """
717
+ return self.schema.get_parameter_serializer(self, location)
718
+
719
+ def prepare_multipart(self, form_data: dict[str, Any]) -> tuple[list | None, dict[str, Any] | None]:
720
+ return self.schema.prepare_multipart(form_data, self)
721
+
722
+ def get_request_payload_content_types(self) -> list[str]:
723
+ return self.schema.get_request_payload_content_types(self)
724
+
725
+ def _get_default_media_type(self) -> str:
726
+ # If the user wants to send payload, then there should be a media type, otherwise the payload is ignored
727
+ media_types = self.get_request_payload_content_types()
728
+ if len(media_types) == 1:
729
+ # The only available option
730
+ return media_types[0]
731
+ media_types_repr = ", ".join(media_types)
732
+ raise IncorrectUsage(
733
+ "Can not detect appropriate media type. "
734
+ "You can either specify one of the defined media types "
735
+ f"or pass any other media type available for serialization. Defined media types: {media_types_repr}"
736
+ )
737
+
738
+ def Case(
739
+ self,
740
+ *,
741
+ method: str | None = None,
742
+ path_parameters: dict[str, Any] | None = None,
743
+ headers: dict[str, Any] | None = None,
744
+ cookies: dict[str, Any] | None = None,
745
+ query: dict[str, Any] | None = None,
746
+ body: list | dict[str, Any] | str | int | float | bool | bytes | NotSet = NOT_SET,
747
+ media_type: str | None = None,
748
+ meta: CaseMetadata | None = None,
749
+ ) -> Case:
750
+ """Create a new example for this API operation.
751
+
752
+ The main use case is constructing Case instances completely manually, without data generation.
753
+ """
754
+ return self.schema.make_case(
755
+ operation=self,
756
+ method=method,
757
+ path_parameters=path_parameters,
758
+ headers=headers,
759
+ cookies=cookies,
760
+ query=query,
761
+ body=body,
762
+ media_type=media_type,
763
+ meta=meta,
764
+ )
765
+
766
+ @property
767
+ def operation_reference(self) -> str:
768
+ path = self.path.replace("~", "~0").replace("/", "~1")
769
+ return f"#/paths/{path}/{self.method}"
770
+
771
+ def validate_response(self, response: Response) -> bool | None:
772
+ """Validate API response for conformance.
773
+
774
+ :raises FailureGroup: If the response does not conform to the API schema.
775
+ """
776
+ return self.schema.validate_response(self, response)
777
+
778
+ def is_response_valid(self, response: Response) -> bool:
779
+ """Validate API response for conformance."""
780
+ try:
781
+ self.validate_response(response)
782
+ return True
783
+ except AssertionError:
784
+ return False
785
+
786
+ def get_raw_payload_schema(self, media_type: str) -> dict[str, Any] | None:
787
+ return self.schema._get_payload_schema(self.definition.raw, media_type)
788
+
789
+ def get_resolved_payload_schema(self, media_type: str) -> dict[str, Any] | None:
790
+ return self.schema._get_payload_schema(self.definition.resolved, media_type)