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
@@ -1,328 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import re
4
- import time
5
- from dataclasses import dataclass
6
- from functools import lru_cache
7
- from typing import TYPE_CHECKING, Any, ClassVar
8
-
9
- from hypothesis.errors import InvalidDefinition
10
- from hypothesis.stateful import RuleBasedStateMachine
11
-
12
- from .._dependency_versions import HYPOTHESIS_HAS_STATEFUL_NAMING_IMPROVEMENTS
13
- from ..constants import NO_LINKS_ERROR_MESSAGE, NOT_SET
14
- from ..exceptions import UsageError
15
- from ..internal.checks import CheckFunction
16
- from ..models import APIOperation, Case
17
- from .config import _default_hypothesis_settings_factory
18
- from .runner import StatefulTestRunner, StatefulTestRunnerConfig
19
- from .sink import StateMachineSink
20
-
21
- if TYPE_CHECKING:
22
- import hypothesis
23
- from requests.structures import CaseInsensitiveDict
24
-
25
- from ..schemas import BaseSchema
26
- from ..transports.responses import GenericResponse
27
- from .statistic import TransitionStats
28
-
29
-
30
- @dataclass
31
- class StepResult:
32
- """Output from a single transition of a state machine."""
33
-
34
- response: GenericResponse
35
- case: Case
36
- elapsed: float
37
-
38
-
39
- def _normalize_name(name: str) -> str:
40
- return re.sub(r"\W|^(?=\d)", "_", name).replace("__", "_")
41
-
42
-
43
- class APIStateMachine(RuleBasedStateMachine):
44
- """The base class for state machines generated from API schemas.
45
-
46
- Exposes additional extension points in the testing process.
47
- """
48
-
49
- # This is a convenience attribute, which happened to clash with `RuleBasedStateMachine` instance level attribute
50
- # They don't interfere, since it is properly overridden on the Hypothesis side, but it is likely that this
51
- # attribute will be renamed in the future
52
- bundles: ClassVar[dict[str, CaseInsensitiveDict]] # type: ignore
53
- schema: BaseSchema
54
- # A template for transition statistics that can be filled with data from the state machine during its execution
55
- _transition_stats_template: ClassVar[TransitionStats]
56
-
57
- def __init__(self) -> None:
58
- try:
59
- super().__init__() # type: ignore
60
- except InvalidDefinition as exc:
61
- if "defines no rules" in str(exc):
62
- raise UsageError(NO_LINKS_ERROR_MESSAGE) from None
63
- raise
64
- self.setup()
65
-
66
- @classmethod
67
- @lru_cache
68
- def _to_test_case(cls) -> type:
69
- from . import run_state_machine_as_test
70
-
71
- class StateMachineTestCase(RuleBasedStateMachine.TestCase):
72
- settings = _default_hypothesis_settings_factory()
73
-
74
- def runTest(self) -> None:
75
- run_state_machine_as_test(cls, settings=self.settings)
76
-
77
- runTest.is_hypothesis_test = True # type: ignore[attr-defined]
78
-
79
- StateMachineTestCase.__name__ = cls.__name__ + ".TestCase"
80
- StateMachineTestCase.__qualname__ = cls.__qualname__ + ".TestCase"
81
- return StateMachineTestCase
82
-
83
- def _pretty_print(self, value: Any) -> str:
84
- if isinstance(value, Case):
85
- # State machines suppose to be reproducible, hence it is OK to get kwargs here
86
- kwargs = self.get_call_kwargs(value)
87
- return _print_case(value, kwargs)
88
- return super()._pretty_print(value) # type: ignore
89
-
90
- if HYPOTHESIS_HAS_STATEFUL_NAMING_IMPROVEMENTS:
91
-
92
- def _new_name(self, target: str) -> str:
93
- target = _normalize_name(target)
94
- return super()._new_name(target) # type: ignore
95
-
96
- def _get_target_for_result(self, result: StepResult) -> str | None:
97
- raise NotImplementedError
98
-
99
- def _add_result_to_targets(self, targets: tuple[str, ...], result: StepResult | None) -> None:
100
- if result is None:
101
- return
102
- target = self._get_target_for_result(result)
103
- if target is not None:
104
- super()._add_result_to_targets((target,), result)
105
-
106
- @classmethod
107
- def format_rules(cls) -> str:
108
- raise NotImplementedError
109
-
110
- @classmethod
111
- def run(cls, *, settings: hypothesis.settings | None = None) -> None:
112
- """Run state machine as a test."""
113
- from . import run_state_machine_as_test
114
-
115
- return run_state_machine_as_test(cls, settings=settings)
116
-
117
- @classmethod
118
- def runner(cls, *, config: StatefulTestRunnerConfig | None = None) -> StatefulTestRunner:
119
- """Create a runner for this state machine."""
120
- from .runner import StatefulTestRunnerConfig
121
-
122
- return StatefulTestRunner(cls, config=config or StatefulTestRunnerConfig())
123
-
124
- @classmethod
125
- def sink(cls) -> StateMachineSink:
126
- """Create a sink to collect events into."""
127
- return StateMachineSink(transitions=cls._transition_stats_template.copy())
128
-
129
- def setup(self) -> None:
130
- """Hook method that runs unconditionally in the beginning of each test scenario.
131
-
132
- Does nothing by default.
133
- """
134
-
135
- def teardown(self) -> None:
136
- pass
137
-
138
- # To provide the return type in the rendered documentation
139
- teardown.__doc__ = RuleBasedStateMachine.teardown.__doc__
140
-
141
- def transform(self, result: StepResult, direction: Direction, case: Case) -> Case:
142
- raise NotImplementedError
143
-
144
- def _step(self, case: Case, previous: StepResult | None = None, link: Direction | None = None) -> StepResult | None:
145
- # This method is a proxy that is used under the hood during the state machine initialization.
146
- # The whole point of having it is to make it possible to override `step`; otherwise, custom "step" is ignored.
147
- # It happens because, at the point of initialization, the final class is not yet created.
148
- __tracebackhide__ = True
149
- if previous is not None and link is not None:
150
- return self.step(case, (previous, link))
151
- return self.step(case, None)
152
-
153
- def step(self, case: Case, previous: tuple[StepResult, Direction] | None = None) -> StepResult | None:
154
- """A single state machine step.
155
-
156
- :param Case case: Generated test case data that should be sent in an API call to the tested API operation.
157
- :param previous: Optional result from the previous step and the direction in which this step should be done.
158
-
159
- Schemathesis prepares data, makes a call and validates the received response.
160
- It is the most high-level point to extend the testing process. You probably don't need it in most cases.
161
- """
162
- from ..specs.openapi.checks import use_after_free
163
-
164
- __tracebackhide__ = True
165
- if previous is not None:
166
- result, direction = previous
167
- case = self.transform(result, direction, case)
168
- self.before_call(case)
169
- kwargs = self.get_call_kwargs(case)
170
- start = time.monotonic()
171
- response = self.call(case, **kwargs)
172
- self._transport_kwargs = kwargs
173
- elapsed = time.monotonic() - start
174
- self.after_call(response, case)
175
- self.validate_response(response, case, additional_checks=(use_after_free,))
176
- return self.store_result(response, case, elapsed)
177
-
178
- def before_call(self, case: Case) -> None:
179
- """Hook method for modifying the case data before making a request.
180
-
181
- :param Case case: Generated test case data that should be sent in an API call to the tested API operation.
182
-
183
- Use it if you want to inject static data, for example,
184
- a query parameter that should always be used in API calls:
185
-
186
- .. code-block:: python
187
-
188
- class APIWorkflow(schema.as_state_machine()):
189
- def before_call(self, case):
190
- case.query = case.query or {}
191
- case.query["test"] = "true"
192
-
193
- You can also modify data only for some operations:
194
-
195
- .. code-block:: python
196
-
197
- class APIWorkflow(schema.as_state_machine()):
198
- def before_call(self, case):
199
- if case.method == "PUT" and case.path == "/items":
200
- case.body["is_fake"] = True
201
- """
202
-
203
- def after_call(self, response: GenericResponse, case: Case) -> None:
204
- """Hook method for additional actions with case or response instances.
205
-
206
- :param response: Response from the application under test.
207
- :param Case case: Generated test case data that should be sent in an API call to the tested API operation.
208
-
209
- For example, you can log all response statuses by using this hook:
210
-
211
- .. code-block:: python
212
-
213
- import logging
214
-
215
- logger = logging.getLogger(__file__)
216
- logger.setLevel(logging.INFO)
217
-
218
-
219
- class APIWorkflow(schema.as_state_machine()):
220
- def after_call(self, response, case):
221
- logger.info(
222
- "%s %s -> %d",
223
- case.method,
224
- case.path,
225
- response.status_code,
226
- )
227
-
228
-
229
- # POST /users/ -> 201
230
- # GET /users/{user_id} -> 200
231
- # PATCH /users/{user_id} -> 200
232
- # GET /users/{user_id} -> 200
233
- # PATCH /users/{user_id} -> 500
234
- """
235
-
236
- def call(self, case: Case, **kwargs: Any) -> GenericResponse:
237
- """Make a request to the API.
238
-
239
- :param Case case: Generated test case data that should be sent in an API call to the tested API operation.
240
- :param kwargs: Keyword arguments that will be passed to the appropriate ``case.call_*`` method.
241
- :return: Response from the application under test.
242
-
243
- Note that WSGI/ASGI applications are detected automatically in this method. Depending on the result of this
244
- detection the state machine will call the ``call`` method.
245
-
246
- Usually, you don't need to override this method unless you are building a different state machine on top of this
247
- one and want to customize the transport layer itself.
248
- """
249
- return case.call(**kwargs)
250
-
251
- def get_call_kwargs(self, case: Case) -> dict[str, Any]:
252
- """Create custom keyword arguments that will be passed to the :meth:`Case.call` method.
253
-
254
- Mostly they are proxied to the :func:`requests.request` call.
255
-
256
- :param Case case: Generated test case data that should be sent in an API call to the tested API operation.
257
-
258
- .. code-block:: python
259
-
260
- class APIWorkflow(schema.as_state_machine()):
261
- def get_call_kwargs(self, case):
262
- return {"verify": False}
263
-
264
- The above example disables the server's TLS certificate verification.
265
- """
266
- return {}
267
-
268
- def validate_response(
269
- self, response: GenericResponse, case: Case, additional_checks: tuple[CheckFunction, ...] = ()
270
- ) -> None:
271
- """Validate an API response.
272
-
273
- :param response: Response from the application under test.
274
- :param Case case: Generated test case data that should be sent in an API call to the tested API operation.
275
- :param additional_checks: A list of checks that will be run together with the default ones.
276
- :raises CheckFailed: If any of the supplied checks failed.
277
-
278
- If you need to change the default checks or provide custom validation rules, you can do it here.
279
-
280
- .. code-block:: python
281
-
282
- def my_check(response, case):
283
- ... # some assertions
284
-
285
-
286
- class APIWorkflow(schema.as_state_machine()):
287
- def validate_response(self, response, case):
288
- case.validate_response(response, checks=(my_check,))
289
-
290
- The state machine from the example above will execute only the ``my_check`` check instead of all
291
- available checks.
292
-
293
- Each check function should accept ``response`` as the first argument and ``case`` as the second one and raise
294
- ``AssertionError`` if the check fails.
295
-
296
- **Note** that it is preferred to pass check functions as an argument to ``case.validate_response``.
297
- In this case, all checks will be executed, and you'll receive a grouped exception that contains results from
298
- all provided checks rather than only the first encountered exception.
299
- """
300
- __tracebackhide__ = True
301
- case.validate_response(response, additional_checks=additional_checks, transport_kwargs=self._transport_kwargs)
302
-
303
- def store_result(self, response: GenericResponse, case: Case, elapsed: float) -> StepResult:
304
- return StepResult(response, case, elapsed)
305
-
306
-
307
- def _print_case(case: Case, kwargs: dict[str, Any]) -> str:
308
- from requests.structures import CaseInsensitiveDict
309
-
310
- operation = f"state.schema['{case.operation.path}']['{case.operation.method.upper()}']"
311
- headers = case.headers or CaseInsensitiveDict()
312
- headers.update(kwargs.get("headers", {}))
313
- case.headers = headers
314
- data = [
315
- f"{name}={getattr(case, name)!r}"
316
- for name in ("path_parameters", "headers", "cookies", "query", "body", "media_type")
317
- if getattr(case, name) not in (None, NOT_SET)
318
- ]
319
- return f"{operation}.make_case({', '.join(data)})"
320
-
321
-
322
- class Direction:
323
- name: str
324
- status_code: str
325
- operation: APIOperation
326
-
327
- def set_data(self, case: Case, elapsed: float, **kwargs: Any) -> None:
328
- raise NotImplementedError
@@ -1,22 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from typing import TYPE_CHECKING
5
-
6
- if TYPE_CHECKING:
7
- from . import events
8
-
9
-
10
- @dataclass
11
- class TransitionStats:
12
- """Statistic for transitions in a state machine."""
13
-
14
- def consume(self, event: events.StatefulEvent) -> None:
15
- raise NotImplementedError
16
-
17
- def copy(self) -> TransitionStats:
18
- """Create a copy of the statistic."""
19
- raise NotImplementedError
20
-
21
- def to_formatted_table(self, width: int) -> str:
22
- raise NotImplementedError
@@ -1,100 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING
4
-
5
- from ..exceptions import CheckFailed, get_grouped_exception
6
- from ..internal.checks import CheckContext
7
-
8
- if TYPE_CHECKING:
9
- from ..failures import FailureContext
10
- from ..internal.checks import CheckFunction
11
- from ..models import Case
12
- from ..transports.responses import GenericResponse
13
- from .context import RunnerContext
14
-
15
-
16
- def validate_response(
17
- *,
18
- response: GenericResponse,
19
- case: Case,
20
- runner_ctx: RunnerContext,
21
- check_ctx: CheckContext,
22
- checks: tuple[CheckFunction, ...],
23
- additional_checks: tuple[CheckFunction, ...] = (),
24
- max_response_time: int | None = None,
25
- ) -> None:
26
- """Validate the response against the provided checks."""
27
- from .._compat import MultipleFailures
28
- from ..checks import _make_max_response_time_failure_message
29
- from ..failures import ResponseTimeExceeded
30
- from ..models import Check, Status
31
-
32
- exceptions: list[CheckFailed | AssertionError] = []
33
- check_results = runner_ctx.checks_for_step
34
-
35
- def _on_failure(exc: CheckFailed | AssertionError, message: str, context: FailureContext | None) -> None:
36
- exceptions.append(exc)
37
- if runner_ctx.is_seen_in_suite(exc):
38
- return
39
- failed_check = Check(
40
- name=name,
41
- value=Status.failure,
42
- response=response,
43
- elapsed=response.elapsed.total_seconds(),
44
- example=copied_case,
45
- message=message,
46
- context=context,
47
- request=None,
48
- )
49
- runner_ctx.add_failed_check(failed_check)
50
- check_results.append(failed_check)
51
- runner_ctx.mark_as_seen_in_suite(exc)
52
-
53
- def _on_passed(_name: str, _case: Case) -> None:
54
- passed_check = Check(
55
- name=_name,
56
- value=Status.success,
57
- response=response,
58
- elapsed=response.elapsed.total_seconds(),
59
- example=_case,
60
- request=None,
61
- )
62
- check_results.append(passed_check)
63
-
64
- for check in tuple(checks) + tuple(additional_checks):
65
- name = check.__name__
66
- copied_case = case.partial_deepcopy()
67
- try:
68
- skip_check = check(check_ctx, response, copied_case)
69
- if not skip_check:
70
- _on_passed(name, copied_case)
71
- except CheckFailed as exc:
72
- if runner_ctx.is_seen_in_run(exc):
73
- continue
74
- _on_failure(exc, str(exc), exc.context)
75
- except AssertionError as exc:
76
- if runner_ctx.is_seen_in_run(exc):
77
- continue
78
- _on_failure(exc, str(exc) or f"Custom check failed: `{name}`", None)
79
- except MultipleFailures as exc:
80
- for subexc in exc.exceptions:
81
- if runner_ctx.is_seen_in_run(subexc):
82
- continue
83
- _on_failure(subexc, str(subexc), subexc.context)
84
-
85
- if max_response_time:
86
- elapsed_time = response.elapsed.total_seconds() * 1000
87
- if elapsed_time > max_response_time:
88
- message = _make_max_response_time_failure_message(elapsed_time, max_response_time)
89
- context = ResponseTimeExceeded(message=message, elapsed=elapsed_time, deadline=max_response_time)
90
- try:
91
- raise AssertionError(message)
92
- except AssertionError as _exc:
93
- if not runner_ctx.is_seen_in_run(_exc):
94
- _on_failure(_exc, message, context)
95
- else:
96
- _on_passed("max_response_time", case)
97
-
98
- # Raise a grouped exception so Hypothesis can properly deduplicate it against the other failures
99
- if exceptions:
100
- raise get_grouped_exception(case.operation.verbose_name, *exceptions)(causes=tuple(exceptions))
schemathesis/targets.py DELETED
@@ -1,77 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Callable
5
-
6
- if TYPE_CHECKING:
7
- from .models import Case
8
- from .transports.responses import GenericResponse
9
-
10
-
11
- @dataclass
12
- class TargetContext:
13
- """Context for targeted testing.
14
-
15
- :ivar Case case: Generated example that is being processed.
16
- :ivar GenericResponse response: API response.
17
- :ivar float response_time: API response time.
18
- """
19
-
20
- case: Case
21
- response: GenericResponse
22
- response_time: float
23
-
24
-
25
- def response_time(context: TargetContext) -> float:
26
- return context.response_time
27
-
28
-
29
- Target = Callable[[TargetContext], float]
30
- DEFAULT_TARGETS = ()
31
- OPTIONAL_TARGETS = (response_time,)
32
- ALL_TARGETS: tuple[Target, ...] = DEFAULT_TARGETS + OPTIONAL_TARGETS
33
-
34
-
35
- @dataclass
36
- class TargetMetricCollector:
37
- """Collect multiple observations for target metrics."""
38
-
39
- targets: list[Target]
40
- observations: dict[str, list[int | float]] = field(init=False)
41
-
42
- def __post_init__(self) -> None:
43
- self.observations = {target.__name__: [] for target in self.targets}
44
-
45
- def reset(self) -> None:
46
- """Reset all collected observations."""
47
- for target in self.targets:
48
- self.observations[target.__name__].clear()
49
-
50
- def store(self, case: Case, response: GenericResponse) -> None:
51
- """Calculate target metrics & store them."""
52
- context = TargetContext(case=case, response=response, response_time=response.elapsed.total_seconds())
53
- for target in self.targets:
54
- self.observations[target.__name__].append(target(context))
55
-
56
- def maximize(self) -> None:
57
- """Give feedback to the Hypothesis engine, so it maximizes the aggregated metrics."""
58
- import hypothesis
59
-
60
- for target in self.targets:
61
- # Currently aggregation is just a sum
62
- metric = sum(self.observations[target.__name__])
63
- hypothesis.target(metric, label=target.__name__)
64
-
65
-
66
- def register(target: Target) -> Target:
67
- """Register a new testing target for schemathesis CLI.
68
-
69
- :param target: A function that will be called to calculate a metric passed to ``hypothesis.target``.
70
- """
71
- from . import cli
72
-
73
- global ALL_TARGETS
74
-
75
- ALL_TARGETS += (target,)
76
- cli.TARGETS_TYPE.choices += (target.__name__,) # type: ignore
77
- return target