schemathesis 3.39.16__py3-none-any.whl → 4.0.0__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 (255) hide show
  1. schemathesis/__init__.py +41 -79
  2. schemathesis/auths.py +111 -122
  3. schemathesis/checks.py +169 -60
  4. schemathesis/cli/__init__.py +15 -2117
  5. schemathesis/cli/commands/__init__.py +85 -0
  6. schemathesis/cli/commands/data.py +10 -0
  7. schemathesis/cli/commands/run/__init__.py +590 -0
  8. schemathesis/cli/commands/run/context.py +204 -0
  9. schemathesis/cli/commands/run/events.py +60 -0
  10. schemathesis/cli/commands/run/executor.py +157 -0
  11. schemathesis/cli/commands/run/filters.py +53 -0
  12. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  13. schemathesis/cli/commands/run/handlers/base.py +18 -0
  14. schemathesis/cli/commands/run/handlers/cassettes.py +474 -0
  15. schemathesis/cli/commands/run/handlers/junitxml.py +55 -0
  16. schemathesis/cli/commands/run/handlers/output.py +1628 -0
  17. schemathesis/cli/commands/run/loaders.py +114 -0
  18. schemathesis/cli/commands/run/validation.py +246 -0
  19. schemathesis/cli/constants.py +5 -58
  20. schemathesis/cli/core.py +19 -0
  21. schemathesis/cli/ext/fs.py +16 -0
  22. schemathesis/cli/ext/groups.py +84 -0
  23. schemathesis/cli/{options.py → ext/options.py} +36 -34
  24. schemathesis/config/__init__.py +189 -0
  25. schemathesis/config/_auth.py +51 -0
  26. schemathesis/config/_checks.py +268 -0
  27. schemathesis/config/_diff_base.py +99 -0
  28. schemathesis/config/_env.py +21 -0
  29. schemathesis/config/_error.py +156 -0
  30. schemathesis/config/_generation.py +149 -0
  31. schemathesis/config/_health_check.py +24 -0
  32. schemathesis/config/_operations.py +327 -0
  33. schemathesis/config/_output.py +171 -0
  34. schemathesis/config/_parameters.py +19 -0
  35. schemathesis/config/_phases.py +187 -0
  36. schemathesis/config/_projects.py +527 -0
  37. schemathesis/config/_rate_limit.py +17 -0
  38. schemathesis/config/_report.py +120 -0
  39. schemathesis/config/_validator.py +9 -0
  40. schemathesis/config/_warnings.py +25 -0
  41. schemathesis/config/schema.json +885 -0
  42. schemathesis/core/__init__.py +67 -0
  43. schemathesis/core/compat.py +32 -0
  44. schemathesis/core/control.py +2 -0
  45. schemathesis/core/curl.py +58 -0
  46. schemathesis/core/deserialization.py +65 -0
  47. schemathesis/core/errors.py +459 -0
  48. schemathesis/core/failures.py +315 -0
  49. schemathesis/core/fs.py +19 -0
  50. schemathesis/core/hooks.py +20 -0
  51. schemathesis/core/loaders.py +104 -0
  52. schemathesis/core/marks.py +66 -0
  53. schemathesis/{transports/content_types.py → core/media_types.py} +14 -12
  54. schemathesis/core/output/__init__.py +46 -0
  55. schemathesis/core/output/sanitization.py +54 -0
  56. schemathesis/{throttling.py → core/rate_limit.py} +16 -17
  57. schemathesis/core/registries.py +31 -0
  58. schemathesis/core/transforms.py +113 -0
  59. schemathesis/core/transport.py +223 -0
  60. schemathesis/core/validation.py +54 -0
  61. schemathesis/core/version.py +7 -0
  62. schemathesis/engine/__init__.py +28 -0
  63. schemathesis/engine/context.py +118 -0
  64. schemathesis/engine/control.py +36 -0
  65. schemathesis/engine/core.py +169 -0
  66. schemathesis/engine/errors.py +464 -0
  67. schemathesis/engine/events.py +258 -0
  68. schemathesis/engine/phases/__init__.py +88 -0
  69. schemathesis/{runner → engine/phases}/probes.py +52 -68
  70. schemathesis/engine/phases/stateful/__init__.py +68 -0
  71. schemathesis/engine/phases/stateful/_executor.py +356 -0
  72. schemathesis/engine/phases/stateful/context.py +85 -0
  73. schemathesis/engine/phases/unit/__init__.py +212 -0
  74. schemathesis/engine/phases/unit/_executor.py +416 -0
  75. schemathesis/engine/phases/unit/_pool.py +82 -0
  76. schemathesis/engine/recorder.py +247 -0
  77. schemathesis/errors.py +43 -0
  78. schemathesis/filters.py +17 -98
  79. schemathesis/generation/__init__.py +5 -33
  80. schemathesis/generation/case.py +317 -0
  81. schemathesis/generation/coverage.py +282 -175
  82. schemathesis/generation/hypothesis/__init__.py +36 -0
  83. schemathesis/generation/hypothesis/builder.py +800 -0
  84. schemathesis/generation/{_hypothesis.py → hypothesis/examples.py} +2 -11
  85. schemathesis/generation/hypothesis/given.py +66 -0
  86. schemathesis/generation/hypothesis/reporting.py +14 -0
  87. schemathesis/generation/hypothesis/strategies.py +16 -0
  88. schemathesis/generation/meta.py +115 -0
  89. schemathesis/generation/metrics.py +93 -0
  90. schemathesis/generation/modes.py +20 -0
  91. schemathesis/generation/overrides.py +116 -0
  92. schemathesis/generation/stateful/__init__.py +37 -0
  93. schemathesis/generation/stateful/state_machine.py +278 -0
  94. schemathesis/graphql/__init__.py +15 -0
  95. schemathesis/graphql/checks.py +109 -0
  96. schemathesis/graphql/loaders.py +284 -0
  97. schemathesis/hooks.py +80 -101
  98. schemathesis/openapi/__init__.py +13 -0
  99. schemathesis/openapi/checks.py +455 -0
  100. schemathesis/openapi/generation/__init__.py +0 -0
  101. schemathesis/openapi/generation/filters.py +72 -0
  102. schemathesis/openapi/loaders.py +313 -0
  103. schemathesis/pytest/__init__.py +5 -0
  104. schemathesis/pytest/control_flow.py +7 -0
  105. schemathesis/pytest/lazy.py +281 -0
  106. schemathesis/pytest/loaders.py +36 -0
  107. schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +128 -108
  108. schemathesis/python/__init__.py +0 -0
  109. schemathesis/python/asgi.py +12 -0
  110. schemathesis/python/wsgi.py +12 -0
  111. schemathesis/schemas.py +537 -273
  112. schemathesis/specs/graphql/__init__.py +0 -1
  113. schemathesis/specs/graphql/_cache.py +1 -2
  114. schemathesis/specs/graphql/scalars.py +42 -6
  115. schemathesis/specs/graphql/schemas.py +141 -137
  116. schemathesis/specs/graphql/validation.py +11 -17
  117. schemathesis/specs/openapi/__init__.py +6 -1
  118. schemathesis/specs/openapi/_cache.py +1 -2
  119. schemathesis/specs/openapi/_hypothesis.py +142 -156
  120. schemathesis/specs/openapi/checks.py +368 -257
  121. schemathesis/specs/openapi/converter.py +4 -4
  122. schemathesis/specs/openapi/definitions.py +1 -1
  123. schemathesis/specs/openapi/examples.py +23 -21
  124. schemathesis/specs/openapi/expressions/__init__.py +31 -19
  125. schemathesis/specs/openapi/expressions/extractors.py +1 -4
  126. schemathesis/specs/openapi/expressions/lexer.py +1 -1
  127. schemathesis/specs/openapi/expressions/nodes.py +36 -41
  128. schemathesis/specs/openapi/expressions/parser.py +1 -1
  129. schemathesis/specs/openapi/formats.py +35 -7
  130. schemathesis/specs/openapi/media_types.py +53 -12
  131. schemathesis/specs/openapi/negative/__init__.py +7 -4
  132. schemathesis/specs/openapi/negative/mutations.py +6 -5
  133. schemathesis/specs/openapi/parameters.py +7 -10
  134. schemathesis/specs/openapi/patterns.py +94 -31
  135. schemathesis/specs/openapi/references.py +12 -53
  136. schemathesis/specs/openapi/schemas.py +233 -307
  137. schemathesis/specs/openapi/security.py +1 -1
  138. schemathesis/specs/openapi/serialization.py +12 -6
  139. schemathesis/specs/openapi/stateful/__init__.py +268 -133
  140. schemathesis/specs/openapi/stateful/control.py +87 -0
  141. schemathesis/specs/openapi/stateful/links.py +209 -0
  142. schemathesis/transport/__init__.py +142 -0
  143. schemathesis/transport/asgi.py +26 -0
  144. schemathesis/transport/prepare.py +124 -0
  145. schemathesis/transport/requests.py +244 -0
  146. schemathesis/{_xml.py → transport/serialization.py} +69 -11
  147. schemathesis/transport/wsgi.py +171 -0
  148. schemathesis-4.0.0.dist-info/METADATA +204 -0
  149. schemathesis-4.0.0.dist-info/RECORD +164 -0
  150. {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/entry_points.txt +1 -1
  151. {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/licenses/LICENSE +1 -1
  152. schemathesis/_compat.py +0 -74
  153. schemathesis/_dependency_versions.py +0 -19
  154. schemathesis/_hypothesis.py +0 -717
  155. schemathesis/_override.py +0 -50
  156. schemathesis/_patches.py +0 -21
  157. schemathesis/_rate_limiter.py +0 -7
  158. schemathesis/cli/callbacks.py +0 -466
  159. schemathesis/cli/cassettes.py +0 -561
  160. schemathesis/cli/context.py +0 -75
  161. schemathesis/cli/debug.py +0 -27
  162. schemathesis/cli/handlers.py +0 -19
  163. schemathesis/cli/junitxml.py +0 -124
  164. schemathesis/cli/output/__init__.py +0 -1
  165. schemathesis/cli/output/default.py +0 -920
  166. schemathesis/cli/output/short.py +0 -59
  167. schemathesis/cli/reporting.py +0 -79
  168. schemathesis/cli/sanitization.py +0 -26
  169. schemathesis/code_samples.py +0 -151
  170. schemathesis/constants.py +0 -54
  171. schemathesis/contrib/__init__.py +0 -11
  172. schemathesis/contrib/openapi/__init__.py +0 -11
  173. schemathesis/contrib/openapi/fill_missing_examples.py +0 -24
  174. schemathesis/contrib/openapi/formats/__init__.py +0 -9
  175. schemathesis/contrib/openapi/formats/uuid.py +0 -16
  176. schemathesis/contrib/unique_data.py +0 -41
  177. schemathesis/exceptions.py +0 -571
  178. schemathesis/experimental/__init__.py +0 -109
  179. schemathesis/extra/_aiohttp.py +0 -28
  180. schemathesis/extra/_flask.py +0 -13
  181. schemathesis/extra/_server.py +0 -18
  182. schemathesis/failures.py +0 -284
  183. schemathesis/fixups/__init__.py +0 -37
  184. schemathesis/fixups/fast_api.py +0 -41
  185. schemathesis/fixups/utf8_bom.py +0 -28
  186. schemathesis/generation/_methods.py +0 -44
  187. schemathesis/graphql.py +0 -3
  188. schemathesis/internal/__init__.py +0 -7
  189. schemathesis/internal/checks.py +0 -86
  190. schemathesis/internal/copy.py +0 -32
  191. schemathesis/internal/datetime.py +0 -5
  192. schemathesis/internal/deprecation.py +0 -37
  193. schemathesis/internal/diff.py +0 -15
  194. schemathesis/internal/extensions.py +0 -27
  195. schemathesis/internal/jsonschema.py +0 -36
  196. schemathesis/internal/output.py +0 -68
  197. schemathesis/internal/transformation.py +0 -26
  198. schemathesis/internal/validation.py +0 -34
  199. schemathesis/lazy.py +0 -474
  200. schemathesis/loaders.py +0 -122
  201. schemathesis/models.py +0 -1341
  202. schemathesis/parameters.py +0 -90
  203. schemathesis/runner/__init__.py +0 -605
  204. schemathesis/runner/events.py +0 -389
  205. schemathesis/runner/impl/__init__.py +0 -3
  206. schemathesis/runner/impl/context.py +0 -88
  207. schemathesis/runner/impl/core.py +0 -1280
  208. schemathesis/runner/impl/solo.py +0 -80
  209. schemathesis/runner/impl/threadpool.py +0 -391
  210. schemathesis/runner/serialization.py +0 -544
  211. schemathesis/sanitization.py +0 -252
  212. schemathesis/serializers.py +0 -328
  213. schemathesis/service/__init__.py +0 -18
  214. schemathesis/service/auth.py +0 -11
  215. schemathesis/service/ci.py +0 -202
  216. schemathesis/service/client.py +0 -133
  217. schemathesis/service/constants.py +0 -38
  218. schemathesis/service/events.py +0 -61
  219. schemathesis/service/extensions.py +0 -224
  220. schemathesis/service/hosts.py +0 -111
  221. schemathesis/service/metadata.py +0 -71
  222. schemathesis/service/models.py +0 -258
  223. schemathesis/service/report.py +0 -255
  224. schemathesis/service/serialization.py +0 -173
  225. schemathesis/service/usage.py +0 -66
  226. schemathesis/specs/graphql/loaders.py +0 -364
  227. schemathesis/specs/openapi/expressions/context.py +0 -16
  228. schemathesis/specs/openapi/links.py +0 -389
  229. schemathesis/specs/openapi/loaders.py +0 -707
  230. schemathesis/specs/openapi/stateful/statistic.py +0 -198
  231. schemathesis/specs/openapi/stateful/types.py +0 -14
  232. schemathesis/specs/openapi/validation.py +0 -26
  233. schemathesis/stateful/__init__.py +0 -147
  234. schemathesis/stateful/config.py +0 -97
  235. schemathesis/stateful/context.py +0 -135
  236. schemathesis/stateful/events.py +0 -274
  237. schemathesis/stateful/runner.py +0 -309
  238. schemathesis/stateful/sink.py +0 -68
  239. schemathesis/stateful/state_machine.py +0 -328
  240. schemathesis/stateful/statistic.py +0 -22
  241. schemathesis/stateful/validation.py +0 -100
  242. schemathesis/targets.py +0 -77
  243. schemathesis/transports/__init__.py +0 -369
  244. schemathesis/transports/asgi.py +0 -7
  245. schemathesis/transports/auth.py +0 -38
  246. schemathesis/transports/headers.py +0 -36
  247. schemathesis/transports/responses.py +0 -57
  248. schemathesis/types.py +0 -44
  249. schemathesis/utils.py +0 -164
  250. schemathesis-3.39.16.dist-info/METADATA +0 -293
  251. schemathesis-3.39.16.dist-info/RECORD +0 -160
  252. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
  253. /schemathesis/{_lazy_import.py → core/lazy_import.py} +0 -0
  254. /schemathesis/{internal → core}/result.py +0 -0
  255. {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/WHEEL +0 -0
schemathesis/hooks.py CHANGED
@@ -2,26 +2,26 @@ from __future__ import annotations
2
2
 
3
3
  import inspect
4
4
  from collections import defaultdict
5
- from copy import deepcopy
6
5
  from dataclasses import dataclass, field
7
6
  from enum import Enum, unique
8
7
  from functools import partial
9
8
  from typing import TYPE_CHECKING, Any, Callable, ClassVar, cast
10
9
 
11
- from .filters import FilterSet, attach_filter_chain
12
- from .internal.deprecation import deprecated_property
10
+ from schemathesis.core.marks import Mark
11
+ from schemathesis.core.transport import Response
12
+ from schemathesis.filters import FilterSet, attach_filter_chain
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from hypothesis import strategies as st
16
16
 
17
- from .models import APIOperation, Case
18
- from .schemas import BaseSchema
19
- from .transports.responses import GenericResponse
20
- from .types import GenericTest
17
+ from schemathesis.generation.case import Case
18
+ from schemathesis.schemas import APIOperation, BaseSchema
19
+
20
+ HookDispatcherMark = Mark["HookDispatcher"](attr_name="hook_dispatcher")
21
21
 
22
22
 
23
23
  @unique
24
- class HookScope(Enum):
24
+ class HookScope(int, Enum):
25
25
  GLOBAL = 1
26
26
  SCHEMA = 2
27
27
  TEST = 3
@@ -37,17 +37,15 @@ class RegisteredHook:
37
37
 
38
38
  @dataclass
39
39
  class HookContext:
40
- """A context that is passed to some hook functions.
40
+ """A context that is passed to some hook functions."""
41
41
 
42
- :ivar Optional[APIOperation] operation: API operation that is currently being processed.
43
- Might be absent in some cases.
44
- """
42
+ operation: APIOperation | None
43
+ """API operation that is currently being processed."""
45
44
 
46
- operation: APIOperation | None = None
45
+ __slots__ = ("operation",)
47
46
 
48
- @deprecated_property(removed_in="4.0", replacement="`operation`")
49
- def endpoint(self) -> APIOperation | None:
50
- return self.operation
47
+ def __init__(self, *, operation: APIOperation | None = None) -> None:
48
+ self.operation = operation
51
49
 
52
50
 
53
51
  def to_filterable_hook(dispatcher: HookDispatcher) -> Callable:
@@ -114,60 +112,29 @@ class HookDispatcher:
114
112
  _specs: ClassVar[dict[str, RegisteredHook]] = {}
115
113
 
116
114
  def __post_init__(self) -> None:
117
- self.register = to_filterable_hook(self) # type: ignore[method-assign]
118
-
119
- def register(self, hook: str | Callable) -> Callable:
120
- """Register a new hook.
121
-
122
- :param hook: Either a hook function or a string.
123
-
124
- Can be used as a decorator in two forms.
125
- Without arguments for registering hooks and autodetecting their names:
126
-
127
- .. code-block:: python
115
+ self.hook = to_filterable_hook(self) # type: ignore[method-assign]
128
116
 
129
- @schemathesis.hook
130
- def before_generate_query(context, strategy):
131
- ...
132
-
133
- With a hook name as the first argument:
134
-
135
- .. code-block:: python
136
-
137
- @schemathesis.hook("before_generate_query")
138
- def hook(context, strategy):
139
- ...
140
- """
117
+ def hook(self, hook: str | Callable) -> Callable:
141
118
  raise NotImplementedError
142
119
 
143
- def merge(self, other: HookDispatcher) -> HookDispatcher:
144
- """Merge two dispatches together.
145
-
146
- The resulting dispatcher will call the `self` hooks first.
147
- """
148
- all_hooks = deepcopy(self._hooks)
149
- for name, hooks in other._hooks.items():
150
- all_hooks[name].extend(hooks)
151
- instance = self.__class__(scope=self.scope)
152
- instance._hooks = all_hooks
153
- return instance
154
-
155
120
  def apply(self, hook: Callable, *, name: str | None = None) -> Callable[[Callable], Callable]:
156
121
  """Register hook to run only on one test function.
157
122
 
158
- :param hook: A hook function.
159
- :param Optional[str] name: A hook name.
160
-
161
- .. code-block:: python
123
+ Args:
124
+ hook: A hook function.
125
+ name: A hook name.
162
126
 
163
- def before_generate_query(context, strategy):
127
+ Example:
128
+ ```python
129
+ def filter_query(ctx, value):
164
130
  ...
165
131
 
166
132
 
167
- @schema.hooks.apply(before_generate_query)
133
+ @schema.hooks.apply(filter_query)
168
134
  @schema.parametrize()
169
135
  def test_api(case):
170
136
  ...
137
+ ```
171
138
 
172
139
  """
173
140
  if name is None:
@@ -175,7 +142,7 @@ class HookDispatcher:
175
142
  else:
176
143
  hook_name = name
177
144
 
178
- def decorator(func: GenericTest) -> GenericTest:
145
+ def decorator(func: Callable) -> Callable:
179
146
  dispatcher = self.add_dispatcher(func)
180
147
  dispatcher.register_hook_with_name(hook, hook_name)
181
148
  return func
@@ -183,11 +150,13 @@ class HookDispatcher:
183
150
  return decorator
184
151
 
185
152
  @classmethod
186
- def add_dispatcher(cls, func: GenericTest) -> HookDispatcher:
153
+ def add_dispatcher(cls, func: Callable) -> HookDispatcher:
187
154
  """Attach a new dispatcher instance to the test if it is not already present."""
188
- if not hasattr(func, "_schemathesis_hooks"):
189
- func._schemathesis_hooks = cls(scope=HookScope.TEST) # type: ignore
190
- return func._schemathesis_hooks # type: ignore
155
+ if not HookDispatcherMark.is_set(func):
156
+ HookDispatcherMark.set(func, cls(scope=HookScope.TEST))
157
+ dispatcher = HookDispatcherMark.get(func)
158
+ assert dispatcher is not None
159
+ return dispatcher
191
160
 
192
161
  def register_hook_with_name(self, hook: Callable, name: str) -> Callable:
193
162
  """A helper for hooks registration."""
@@ -226,19 +195,10 @@ class HookDispatcher:
226
195
  f"Hook '{name}' takes {len(spec.signature.parameters)} arguments but {len(signature.parameters)} is defined"
227
196
  )
228
197
 
229
- def collect_statistic(self) -> dict[str, int]:
230
- return {name: len(hooks) for name, hooks in self._hooks.items()}
231
-
232
198
  def get_all_by_name(self, name: str) -> list[Callable]:
233
199
  """Get a list of hooks registered for a name."""
234
200
  return self._hooks.get(name, [])
235
201
 
236
- def is_installed(self, name: str, needle: Callable) -> bool:
237
- for hook in self.get_all_by_name(name):
238
- if hook is needle:
239
- return True
240
- return False
241
-
242
202
  def apply_to_container(
243
203
  self, strategy: st.SearchStrategy, container: str, context: HookContext
244
204
  ) -> st.SearchStrategy:
@@ -271,10 +231,7 @@ class HookDispatcher:
271
231
  hook(context, *args, **kwargs)
272
232
 
273
233
  def unregister(self, hook: Callable) -> None:
274
- """Unregister a specific hook.
275
-
276
- :param hook: A hook function to unregister.
277
- """
234
+ """Unregister a specific hook."""
278
235
  # It removes this function from all places
279
236
  for hooks in self._hooks.values():
280
237
  hooks[:] = [item for item in hooks if item is not hook]
@@ -307,19 +264,12 @@ def apply_to_all_dispatchers(
307
264
  return strategy
308
265
 
309
266
 
310
- def should_skip_operation(dispatcher: HookDispatcher, context: HookContext) -> bool:
311
- for hook in dispatcher.get_all_by_name("filter_operations"):
312
- if not hook(context):
313
- return True
314
- return False
315
-
316
-
317
267
  def validate_filterable_hook(hook: str | Callable) -> None:
318
268
  if callable(hook):
319
269
  name = hook.__name__
320
270
  else:
321
271
  name = hook
322
- if name in ("before_process_path", "before_load_schema", "after_load_schema", "after_init_cli_run_handlers"):
272
+ if name in ("before_process_path", "before_load_schema", "after_load_schema"):
323
273
  raise ValueError(f"Filters are not applicable to this hook: `{name}`")
324
274
 
325
275
 
@@ -373,11 +323,6 @@ def before_process_path(context: HookContext, path: str, methods: dict[str, Any]
373
323
  """Called before API path is processed."""
374
324
 
375
325
 
376
- @all_scopes
377
- def filter_operations(context: HookContext) -> bool | None:
378
- """Decide whether testing of this particular API operation should be skipped or not."""
379
-
380
-
381
326
  @HookDispatcher.register_spec([HookScope.GLOBAL])
382
327
  def before_load_schema(context: HookContext, raw_schema: dict[str, Any]) -> None:
383
328
  """Called before schema instance is created."""
@@ -402,15 +347,7 @@ def before_init_operation(context: HookContext, operation: APIOperation) -> None
402
347
 
403
348
 
404
349
  @HookDispatcher.register_spec([HookScope.GLOBAL])
405
- def add_case(context: HookContext, case: Case, response: GenericResponse) -> Case | None:
406
- """Creates an additional test per API operation. If this hook returns None, no additional test created.
407
-
408
- Called with a copy of the original case object and the server's response to the original case.
409
- """
410
-
411
-
412
- @HookDispatcher.register_spec([HookScope.GLOBAL])
413
- def before_call(context: HookContext, case: Case) -> None:
350
+ def before_call(context: HookContext, case: Case, **kwargs: Any) -> None:
414
351
  """Called before every network call in CLI tests.
415
352
 
416
353
  Use cases:
@@ -420,7 +357,7 @@ def before_call(context: HookContext, case: Case) -> None:
420
357
 
421
358
 
422
359
  @HookDispatcher.register_spec([HookScope.GLOBAL])
423
- def after_call(context: HookContext, case: Case, response: GenericResponse) -> None:
360
+ def after_call(context: HookContext, case: Case, response: Response) -> None:
424
361
  """Called after every network call in CLI tests.
425
362
 
426
363
  Note that you need to modify the response in-place.
@@ -434,8 +371,50 @@ def after_call(context: HookContext, case: Case, response: GenericResponse) -> N
434
371
  GLOBAL_HOOK_DISPATCHER = HookDispatcher(scope=HookScope.GLOBAL)
435
372
  dispatch = GLOBAL_HOOK_DISPATCHER.dispatch
436
373
  get_all_by_name = GLOBAL_HOOK_DISPATCHER.get_all_by_name
437
- is_installed = GLOBAL_HOOK_DISPATCHER.is_installed
438
- collect_statistic = GLOBAL_HOOK_DISPATCHER.collect_statistic
439
- register = GLOBAL_HOOK_DISPATCHER.register
440
374
  unregister = GLOBAL_HOOK_DISPATCHER.unregister
441
375
  unregister_all = GLOBAL_HOOK_DISPATCHER.unregister_all
376
+
377
+
378
+ def hook(hook: str | Callable) -> Callable:
379
+ """Register a new hook.
380
+
381
+ Args:
382
+ hook: Either a hook function (autodetecting its name) or a string matching one of the supported hook names.
383
+
384
+ Example:
385
+ Can be used as a decorator in two ways:
386
+
387
+ 1. Without arguments (auto-detect the hook name from the function name):
388
+
389
+ ```python
390
+ @schemathesis.hook
391
+ def filter_query(ctx, query):
392
+ \"\"\"Skip cases where query is None or invalid\"\"\"
393
+ return query and "user_id" in query
394
+
395
+ @schemathesis.hook
396
+ def before_call(ctx, case):
397
+ \"\"\"Modify headers before sending each request\"\"\"
398
+ if case.headers is None:
399
+ case.headers = {}
400
+ case.headers["X-Test-Mode"] = "true"
401
+ return None
402
+ ```
403
+
404
+ 2. With an explicit hook name as the first argument:
405
+
406
+ ```python
407
+ @schemathesis.hook("map_headers")
408
+ def add_custom_header(ctx, headers):
409
+ \"\"\"Inject a test header into every request\"\"\"
410
+ if headers is None:
411
+ headers = {}
412
+ headers["X-Custom"] = "value"
413
+ return headers
414
+ ```
415
+
416
+ """
417
+ return GLOBAL_HOOK_DISPATCHER.hook(hook)
418
+
419
+
420
+ hook.__dict__ = GLOBAL_HOOK_DISPATCHER.hook.__dict__
@@ -0,0 +1,13 @@
1
+ from schemathesis.openapi.loaders import from_asgi, from_dict, from_file, from_path, from_url, from_wsgi
2
+ from schemathesis.specs.openapi import format, media_type
3
+
4
+ __all__ = [
5
+ "from_url",
6
+ "from_asgi",
7
+ "from_wsgi",
8
+ "from_file",
9
+ "from_path",
10
+ "from_dict",
11
+ "format",
12
+ "media_type",
13
+ ]