schemathesis 3.15.4__py3-none-any.whl → 4.4.2__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 (251) hide show
  1. schemathesis/__init__.py +53 -25
  2. schemathesis/auths.py +507 -0
  3. schemathesis/checks.py +190 -25
  4. schemathesis/cli/__init__.py +27 -1219
  5. schemathesis/cli/__main__.py +4 -0
  6. schemathesis/cli/commands/__init__.py +133 -0
  7. schemathesis/cli/commands/data.py +10 -0
  8. schemathesis/cli/commands/run/__init__.py +602 -0
  9. schemathesis/cli/commands/run/context.py +228 -0
  10. schemathesis/cli/commands/run/events.py +60 -0
  11. schemathesis/cli/commands/run/executor.py +157 -0
  12. schemathesis/cli/commands/run/filters.py +53 -0
  13. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  14. schemathesis/cli/commands/run/handlers/base.py +45 -0
  15. schemathesis/cli/commands/run/handlers/cassettes.py +464 -0
  16. schemathesis/cli/commands/run/handlers/junitxml.py +60 -0
  17. schemathesis/cli/commands/run/handlers/output.py +1750 -0
  18. schemathesis/cli/commands/run/loaders.py +118 -0
  19. schemathesis/cli/commands/run/validation.py +256 -0
  20. schemathesis/cli/constants.py +5 -0
  21. schemathesis/cli/core.py +19 -0
  22. schemathesis/cli/ext/fs.py +16 -0
  23. schemathesis/cli/ext/groups.py +203 -0
  24. schemathesis/cli/ext/options.py +81 -0
  25. schemathesis/config/__init__.py +202 -0
  26. schemathesis/config/_auth.py +51 -0
  27. schemathesis/config/_checks.py +268 -0
  28. schemathesis/config/_diff_base.py +101 -0
  29. schemathesis/config/_env.py +21 -0
  30. schemathesis/config/_error.py +163 -0
  31. schemathesis/config/_generation.py +157 -0
  32. schemathesis/config/_health_check.py +24 -0
  33. schemathesis/config/_operations.py +335 -0
  34. schemathesis/config/_output.py +171 -0
  35. schemathesis/config/_parameters.py +19 -0
  36. schemathesis/config/_phases.py +253 -0
  37. schemathesis/config/_projects.py +543 -0
  38. schemathesis/config/_rate_limit.py +17 -0
  39. schemathesis/config/_report.py +120 -0
  40. schemathesis/config/_validator.py +9 -0
  41. schemathesis/config/_warnings.py +89 -0
  42. schemathesis/config/schema.json +975 -0
  43. schemathesis/core/__init__.py +72 -0
  44. schemathesis/core/adapter.py +34 -0
  45. schemathesis/core/compat.py +32 -0
  46. schemathesis/core/control.py +2 -0
  47. schemathesis/core/curl.py +100 -0
  48. schemathesis/core/deserialization.py +210 -0
  49. schemathesis/core/errors.py +588 -0
  50. schemathesis/core/failures.py +316 -0
  51. schemathesis/core/fs.py +19 -0
  52. schemathesis/core/hooks.py +20 -0
  53. schemathesis/core/jsonschema/__init__.py +13 -0
  54. schemathesis/core/jsonschema/bundler.py +183 -0
  55. schemathesis/core/jsonschema/keywords.py +40 -0
  56. schemathesis/core/jsonschema/references.py +222 -0
  57. schemathesis/core/jsonschema/types.py +41 -0
  58. schemathesis/core/lazy_import.py +15 -0
  59. schemathesis/core/loaders.py +107 -0
  60. schemathesis/core/marks.py +66 -0
  61. schemathesis/core/media_types.py +79 -0
  62. schemathesis/core/output/__init__.py +46 -0
  63. schemathesis/core/output/sanitization.py +54 -0
  64. schemathesis/core/parameters.py +45 -0
  65. schemathesis/core/rate_limit.py +60 -0
  66. schemathesis/core/registries.py +34 -0
  67. schemathesis/core/result.py +27 -0
  68. schemathesis/core/schema_analysis.py +17 -0
  69. schemathesis/core/shell.py +203 -0
  70. schemathesis/core/transforms.py +144 -0
  71. schemathesis/core/transport.py +223 -0
  72. schemathesis/core/validation.py +73 -0
  73. schemathesis/core/version.py +7 -0
  74. schemathesis/engine/__init__.py +28 -0
  75. schemathesis/engine/context.py +152 -0
  76. schemathesis/engine/control.py +44 -0
  77. schemathesis/engine/core.py +201 -0
  78. schemathesis/engine/errors.py +446 -0
  79. schemathesis/engine/events.py +284 -0
  80. schemathesis/engine/observations.py +42 -0
  81. schemathesis/engine/phases/__init__.py +108 -0
  82. schemathesis/engine/phases/analysis.py +28 -0
  83. schemathesis/engine/phases/probes.py +172 -0
  84. schemathesis/engine/phases/stateful/__init__.py +68 -0
  85. schemathesis/engine/phases/stateful/_executor.py +364 -0
  86. schemathesis/engine/phases/stateful/context.py +85 -0
  87. schemathesis/engine/phases/unit/__init__.py +220 -0
  88. schemathesis/engine/phases/unit/_executor.py +459 -0
  89. schemathesis/engine/phases/unit/_pool.py +82 -0
  90. schemathesis/engine/recorder.py +254 -0
  91. schemathesis/errors.py +47 -0
  92. schemathesis/filters.py +395 -0
  93. schemathesis/generation/__init__.py +25 -0
  94. schemathesis/generation/case.py +478 -0
  95. schemathesis/generation/coverage.py +1528 -0
  96. schemathesis/generation/hypothesis/__init__.py +121 -0
  97. schemathesis/generation/hypothesis/builder.py +992 -0
  98. schemathesis/generation/hypothesis/examples.py +56 -0
  99. schemathesis/generation/hypothesis/given.py +66 -0
  100. schemathesis/generation/hypothesis/reporting.py +285 -0
  101. schemathesis/generation/meta.py +227 -0
  102. schemathesis/generation/metrics.py +93 -0
  103. schemathesis/generation/modes.py +20 -0
  104. schemathesis/generation/overrides.py +127 -0
  105. schemathesis/generation/stateful/__init__.py +37 -0
  106. schemathesis/generation/stateful/state_machine.py +294 -0
  107. schemathesis/graphql/__init__.py +15 -0
  108. schemathesis/graphql/checks.py +109 -0
  109. schemathesis/graphql/loaders.py +285 -0
  110. schemathesis/hooks.py +270 -91
  111. schemathesis/openapi/__init__.py +13 -0
  112. schemathesis/openapi/checks.py +467 -0
  113. schemathesis/openapi/generation/__init__.py +0 -0
  114. schemathesis/openapi/generation/filters.py +72 -0
  115. schemathesis/openapi/loaders.py +315 -0
  116. schemathesis/pytest/__init__.py +5 -0
  117. schemathesis/pytest/control_flow.py +7 -0
  118. schemathesis/pytest/lazy.py +341 -0
  119. schemathesis/pytest/loaders.py +36 -0
  120. schemathesis/pytest/plugin.py +357 -0
  121. schemathesis/python/__init__.py +0 -0
  122. schemathesis/python/asgi.py +12 -0
  123. schemathesis/python/wsgi.py +12 -0
  124. schemathesis/schemas.py +682 -257
  125. schemathesis/specs/graphql/__init__.py +0 -1
  126. schemathesis/specs/graphql/nodes.py +26 -2
  127. schemathesis/specs/graphql/scalars.py +77 -12
  128. schemathesis/specs/graphql/schemas.py +367 -148
  129. schemathesis/specs/graphql/validation.py +33 -0
  130. schemathesis/specs/openapi/__init__.py +9 -1
  131. schemathesis/specs/openapi/_hypothesis.py +555 -318
  132. schemathesis/specs/openapi/adapter/__init__.py +10 -0
  133. schemathesis/specs/openapi/adapter/parameters.py +729 -0
  134. schemathesis/specs/openapi/adapter/protocol.py +59 -0
  135. schemathesis/specs/openapi/adapter/references.py +19 -0
  136. schemathesis/specs/openapi/adapter/responses.py +368 -0
  137. schemathesis/specs/openapi/adapter/security.py +144 -0
  138. schemathesis/specs/openapi/adapter/v2.py +30 -0
  139. schemathesis/specs/openapi/adapter/v3_0.py +30 -0
  140. schemathesis/specs/openapi/adapter/v3_1.py +30 -0
  141. schemathesis/specs/openapi/analysis.py +96 -0
  142. schemathesis/specs/openapi/checks.py +748 -82
  143. schemathesis/specs/openapi/converter.py +176 -37
  144. schemathesis/specs/openapi/definitions.py +599 -4
  145. schemathesis/specs/openapi/examples.py +581 -165
  146. schemathesis/specs/openapi/expressions/__init__.py +52 -5
  147. schemathesis/specs/openapi/expressions/extractors.py +25 -0
  148. schemathesis/specs/openapi/expressions/lexer.py +34 -31
  149. schemathesis/specs/openapi/expressions/nodes.py +97 -46
  150. schemathesis/specs/openapi/expressions/parser.py +35 -13
  151. schemathesis/specs/openapi/formats.py +122 -0
  152. schemathesis/specs/openapi/media_types.py +75 -0
  153. schemathesis/specs/openapi/negative/__init__.py +93 -73
  154. schemathesis/specs/openapi/negative/mutations.py +294 -103
  155. schemathesis/specs/openapi/negative/utils.py +0 -9
  156. schemathesis/specs/openapi/patterns.py +458 -0
  157. schemathesis/specs/openapi/references.py +60 -81
  158. schemathesis/specs/openapi/schemas.py +647 -666
  159. schemathesis/specs/openapi/serialization.py +53 -30
  160. schemathesis/specs/openapi/stateful/__init__.py +403 -68
  161. schemathesis/specs/openapi/stateful/control.py +87 -0
  162. schemathesis/specs/openapi/stateful/dependencies/__init__.py +232 -0
  163. schemathesis/specs/openapi/stateful/dependencies/inputs.py +428 -0
  164. schemathesis/specs/openapi/stateful/dependencies/models.py +341 -0
  165. schemathesis/specs/openapi/stateful/dependencies/naming.py +491 -0
  166. schemathesis/specs/openapi/stateful/dependencies/outputs.py +34 -0
  167. schemathesis/specs/openapi/stateful/dependencies/resources.py +339 -0
  168. schemathesis/specs/openapi/stateful/dependencies/schemas.py +447 -0
  169. schemathesis/specs/openapi/stateful/inference.py +254 -0
  170. schemathesis/specs/openapi/stateful/links.py +219 -78
  171. schemathesis/specs/openapi/types/__init__.py +3 -0
  172. schemathesis/specs/openapi/types/common.py +23 -0
  173. schemathesis/specs/openapi/types/v2.py +129 -0
  174. schemathesis/specs/openapi/types/v3.py +134 -0
  175. schemathesis/specs/openapi/utils.py +7 -6
  176. schemathesis/specs/openapi/warnings.py +75 -0
  177. schemathesis/transport/__init__.py +224 -0
  178. schemathesis/transport/asgi.py +26 -0
  179. schemathesis/transport/prepare.py +126 -0
  180. schemathesis/transport/requests.py +278 -0
  181. schemathesis/transport/serialization.py +329 -0
  182. schemathesis/transport/wsgi.py +175 -0
  183. schemathesis-4.4.2.dist-info/METADATA +213 -0
  184. schemathesis-4.4.2.dist-info/RECORD +192 -0
  185. {schemathesis-3.15.4.dist-info → schemathesis-4.4.2.dist-info}/WHEEL +1 -1
  186. schemathesis-4.4.2.dist-info/entry_points.txt +6 -0
  187. {schemathesis-3.15.4.dist-info → schemathesis-4.4.2.dist-info/licenses}/LICENSE +1 -1
  188. schemathesis/_compat.py +0 -57
  189. schemathesis/_hypothesis.py +0 -123
  190. schemathesis/auth.py +0 -214
  191. schemathesis/cli/callbacks.py +0 -240
  192. schemathesis/cli/cassettes.py +0 -351
  193. schemathesis/cli/context.py +0 -38
  194. schemathesis/cli/debug.py +0 -21
  195. schemathesis/cli/handlers.py +0 -11
  196. schemathesis/cli/junitxml.py +0 -41
  197. schemathesis/cli/options.py +0 -70
  198. schemathesis/cli/output/__init__.py +0 -1
  199. schemathesis/cli/output/default.py +0 -521
  200. schemathesis/cli/output/short.py +0 -40
  201. schemathesis/constants.py +0 -88
  202. schemathesis/exceptions.py +0 -257
  203. schemathesis/extra/_aiohttp.py +0 -27
  204. schemathesis/extra/_flask.py +0 -10
  205. schemathesis/extra/_server.py +0 -16
  206. schemathesis/extra/pytest_plugin.py +0 -251
  207. schemathesis/failures.py +0 -145
  208. schemathesis/fixups/__init__.py +0 -29
  209. schemathesis/fixups/fast_api.py +0 -30
  210. schemathesis/graphql.py +0 -5
  211. schemathesis/internal.py +0 -6
  212. schemathesis/lazy.py +0 -301
  213. schemathesis/models.py +0 -1113
  214. schemathesis/parameters.py +0 -91
  215. schemathesis/runner/__init__.py +0 -470
  216. schemathesis/runner/events.py +0 -242
  217. schemathesis/runner/impl/__init__.py +0 -3
  218. schemathesis/runner/impl/core.py +0 -791
  219. schemathesis/runner/impl/solo.py +0 -85
  220. schemathesis/runner/impl/threadpool.py +0 -367
  221. schemathesis/runner/serialization.py +0 -206
  222. schemathesis/serializers.py +0 -253
  223. schemathesis/service/__init__.py +0 -18
  224. schemathesis/service/auth.py +0 -10
  225. schemathesis/service/client.py +0 -62
  226. schemathesis/service/constants.py +0 -25
  227. schemathesis/service/events.py +0 -39
  228. schemathesis/service/handler.py +0 -46
  229. schemathesis/service/hosts.py +0 -74
  230. schemathesis/service/metadata.py +0 -42
  231. schemathesis/service/models.py +0 -21
  232. schemathesis/service/serialization.py +0 -184
  233. schemathesis/service/worker.py +0 -39
  234. schemathesis/specs/graphql/loaders.py +0 -215
  235. schemathesis/specs/openapi/constants.py +0 -7
  236. schemathesis/specs/openapi/expressions/context.py +0 -12
  237. schemathesis/specs/openapi/expressions/pointers.py +0 -29
  238. schemathesis/specs/openapi/filters.py +0 -44
  239. schemathesis/specs/openapi/links.py +0 -303
  240. schemathesis/specs/openapi/loaders.py +0 -453
  241. schemathesis/specs/openapi/parameters.py +0 -430
  242. schemathesis/specs/openapi/security.py +0 -129
  243. schemathesis/specs/openapi/validation.py +0 -24
  244. schemathesis/stateful.py +0 -358
  245. schemathesis/targets.py +0 -32
  246. schemathesis/types.py +0 -38
  247. schemathesis/utils.py +0 -475
  248. schemathesis-3.15.4.dist-info/METADATA +0 -202
  249. schemathesis-3.15.4.dist-info/RECORD +0 -99
  250. schemathesis-3.15.4.dist-info/entry_points.txt +0 -7
  251. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from schemathesis.pytest.lazy import LazySchema
7
+
8
+
9
+ def from_fixture(name: str) -> LazySchema:
10
+ """Create a lazy schema loader that resolves a pytest fixture at test runtime.
11
+
12
+ Args:
13
+ name: Name of the pytest fixture that returns a schema object
14
+
15
+ Example:
16
+ ```python
17
+ import pytest
18
+ import schemathesis
19
+
20
+ @pytest.fixture
21
+ def api_schema():
22
+ return schemathesis.openapi.from_url("https://api.example.com/openapi.json")
23
+
24
+ # Create lazy schema from fixture
25
+ schema = schemathesis.pytest.from_fixture("api_schema")
26
+
27
+ # Use with parametrize to generate tests
28
+ @schema.parametrize()
29
+ def test_api(case):
30
+ case.call_and_validate()
31
+ ```
32
+
33
+ """
34
+ from schemathesis.pytest.lazy import LazySchema
35
+
36
+ return LazySchema(name)
@@ -0,0 +1,357 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import unittest
5
+ from functools import partial
6
+ from typing import TYPE_CHECKING, Any, Callable, Generator, Type, cast
7
+
8
+ import pytest
9
+ from _pytest import nodes
10
+ from _pytest.config import hookimpl
11
+ from _pytest.python import Class, Function, FunctionDefinition, Metafunc, Module, PyCollector
12
+ from hypothesis.errors import FailedHealthCheck, InvalidArgument, Unsatisfiable
13
+ from jsonschema.exceptions import SchemaError
14
+
15
+ from schemathesis.core.control import SkipTest
16
+ from schemathesis.core.errors import (
17
+ SERIALIZERS_SUGGESTION_MESSAGE,
18
+ IncorrectUsage,
19
+ InvalidHeadersExample,
20
+ InvalidRegexPattern,
21
+ InvalidSchema,
22
+ SchemathesisError,
23
+ SerializationNotPossible,
24
+ format_exception,
25
+ )
26
+ from schemathesis.core.failures import FailureGroup
27
+ from schemathesis.core.marks import Mark
28
+ from schemathesis.core.result import Ok, Result
29
+ from schemathesis.generation import overrides
30
+ from schemathesis.generation.hypothesis.given import (
31
+ GivenArgsMark,
32
+ GivenKwargsMark,
33
+ is_given_applied,
34
+ merge_given_args,
35
+ validate_given_args,
36
+ )
37
+ from schemathesis.generation.hypothesis.reporting import (
38
+ HealthCheckTipStyle,
39
+ build_health_check_error,
40
+ build_unsatisfiable_error,
41
+ ignore_hypothesis_output,
42
+ )
43
+ from schemathesis.pytest.control_flow import fail_on_no_matches
44
+ from schemathesis.schemas import APIOperation
45
+
46
+ if TYPE_CHECKING:
47
+ from _pytest.fixtures import FuncFixtureInfo
48
+
49
+ from schemathesis.schemas import BaseSchema
50
+
51
+ GIVEN_AND_EXPLICIT_EXAMPLES_ERROR_MESSAGE = (
52
+ "Unsupported test setup. Tests using `@schema.given` cannot be combined with explicit schema examples in the same "
53
+ "function. Separate these tests into distinct functions to avoid conflicts."
54
+ )
55
+
56
+
57
+ def _is_schema(value: object) -> bool:
58
+ from schemathesis.schemas import BaseSchema
59
+
60
+ return isinstance(value, BaseSchema)
61
+
62
+
63
+ SchemaHandleMark = Mark["BaseSchema"](attr_name="schema", check=_is_schema)
64
+
65
+
66
+ class SchemathesisFunction(Function):
67
+ def __init__(
68
+ self,
69
+ *args: Any,
70
+ test_func: Callable,
71
+ test_name: str | None = None,
72
+ **kwargs: Any,
73
+ ) -> None:
74
+ super().__init__(*args, **kwargs)
75
+ self.test_function = test_func
76
+ self.test_name = test_name
77
+
78
+
79
+ class SchemathesisCase(PyCollector):
80
+ def __init__(self, test_function: Callable, schema: BaseSchema, *args: Any, **kwargs: Any) -> None:
81
+ self.given_kwargs: dict[str, Any]
82
+ given_args = GivenArgsMark.get(test_function)
83
+ given_kwargs = GivenKwargsMark.get(test_function)
84
+
85
+ assert given_args is not None
86
+ assert given_kwargs is not None
87
+
88
+ def _init_with_valid_test(_test_function: Callable, _args: tuple, _kwargs: dict[str, Any]) -> None:
89
+ self.test_function = _test_function
90
+ self.is_invalid_test = False
91
+ self.given_kwargs = merge_given_args(test_function, _args, _kwargs)
92
+
93
+ if is_given_applied(test_function):
94
+ failing_test = validate_given_args(test_function, given_args, given_kwargs)
95
+ if failing_test is not None:
96
+ self.test_function = failing_test
97
+ self.is_invalid_test = True
98
+ self.given_kwargs = {}
99
+ else:
100
+ _init_with_valid_test(test_function, given_args, given_kwargs)
101
+ else:
102
+ _init_with_valid_test(test_function, given_args, given_kwargs)
103
+ self.schema = schema
104
+ super().__init__(*args, **kwargs)
105
+
106
+ def _get_test_name(self, operation: APIOperation) -> str:
107
+ return f"{self.name}[{operation.label}]"
108
+
109
+ def _gen_items(self, result: Result[APIOperation, InvalidSchema]) -> Generator[SchemathesisFunction, None, None]:
110
+ """Generate all tests for the given API operation.
111
+
112
+ Could produce more than one test item if
113
+ parametrization is applied via ``pytest.mark.parametrize`` or ``pytest_generate_tests``.
114
+
115
+ This implementation is based on the original one in pytest, but with slight adjustments
116
+ to produce tests out of hypothesis ones.
117
+ """
118
+ from schemathesis.checks import load_all_checks
119
+ from schemathesis.generation.hypothesis.builder import (
120
+ HypothesisTestConfig,
121
+ HypothesisTestMode,
122
+ create_test,
123
+ make_async_test,
124
+ )
125
+
126
+ load_all_checks()
127
+
128
+ is_trio_test = False
129
+ for mark in getattr(self.test_function, "pytestmark", []):
130
+ if mark.name == "trio":
131
+ is_trio_test = True
132
+ break
133
+
134
+ if isinstance(result, Ok):
135
+ operation = result.ok()
136
+ if self.is_invalid_test:
137
+ funcobj = self.test_function
138
+ else:
139
+ as_strategy_kwargs = {}
140
+
141
+ auth = self.schema.config.auth_for(operation=operation)
142
+ if auth is not None:
143
+ from requests.auth import _basic_auth_str
144
+
145
+ as_strategy_kwargs["headers"] = {"Authorization": _basic_auth_str(*auth)}
146
+ headers = self.schema.config.headers_for(operation=operation)
147
+ if headers:
148
+ as_strategy_kwargs["headers"] = headers
149
+
150
+ override = overrides.for_operation(operation=operation, config=self.schema.config)
151
+ if override is not None:
152
+ for location, entry in override.items():
153
+ if entry:
154
+ as_strategy_kwargs[location.container_name] = entry
155
+ modes = []
156
+ phases = self.schema.config.phases_for(operation=operation)
157
+ if phases.examples.enabled:
158
+ modes.append(HypothesisTestMode.EXAMPLES)
159
+ if phases.fuzzing.enabled:
160
+ modes.append(HypothesisTestMode.FUZZING)
161
+ if phases.coverage.enabled:
162
+ modes.append(HypothesisTestMode.COVERAGE)
163
+
164
+ funcobj = create_test(
165
+ operation=operation,
166
+ test_func=self.test_function,
167
+ config=HypothesisTestConfig(
168
+ modes=modes,
169
+ settings=self.schema.config.get_hypothesis_settings(operation=operation),
170
+ given_kwargs=self.given_kwargs,
171
+ project=self.schema.config,
172
+ as_strategy_kwargs=as_strategy_kwargs,
173
+ ),
174
+ )
175
+ if inspect.iscoroutinefunction(self.test_function):
176
+ # `pytest-trio` expects a coroutine function
177
+ if is_trio_test:
178
+ funcobj.hypothesis.inner_test = self.test_function # type: ignore[attr-defined]
179
+ else:
180
+ funcobj.hypothesis.inner_test = make_async_test(self.test_function) # type: ignore[attr-defined]
181
+ name = self._get_test_name(operation)
182
+ else:
183
+ error = result.err()
184
+ funcobj = error.as_failing_test_function()
185
+ name = self.name
186
+ if error.method:
187
+ name += f"[{error.method.upper()} {error.path}]"
188
+ else:
189
+ name += f"[{error.path}]"
190
+
191
+ cls = self._get_class_parent()
192
+ definition: FunctionDefinition = FunctionDefinition.from_parent(
193
+ name=self.name, parent=self.parent, callobj=funcobj
194
+ )
195
+ fixturemanager = self.session._fixturemanager
196
+ fixtureinfo = fixturemanager.getfixtureinfo(definition, funcobj, cls)
197
+
198
+ metafunc = self._parametrize(cls, definition, fixtureinfo)
199
+
200
+ if isinstance(self.parent, Class):
201
+ # On pytest 7, Class collects the test methods directly, therefore
202
+ funcobj = partial(funcobj, self.parent.obj)
203
+
204
+ if not metafunc._calls:
205
+ yield SchemathesisFunction.from_parent(
206
+ name=name,
207
+ parent=self.parent,
208
+ callobj=funcobj,
209
+ fixtureinfo=fixtureinfo,
210
+ test_func=self.test_function,
211
+ originalname=self.name,
212
+ )
213
+ else:
214
+ fixtureinfo.prune_dependency_tree()
215
+ for callspec in metafunc._calls:
216
+ subname = f"{name}[{callspec.id}]"
217
+ yield SchemathesisFunction.from_parent(
218
+ self.parent,
219
+ name=subname,
220
+ callspec=callspec,
221
+ callobj=funcobj,
222
+ fixtureinfo=fixtureinfo,
223
+ keywords={callspec.id: True},
224
+ originalname=name,
225
+ test_func=self.test_function,
226
+ )
227
+
228
+ def _get_class_parent(self) -> type | None:
229
+ clscol = self.getparent(Class)
230
+ return clscol.obj if clscol else None
231
+
232
+ def _parametrize(self, cls: type | None, definition: FunctionDefinition, fixtureinfo: FuncFixtureInfo) -> Metafunc:
233
+ parent = self.getparent(Module)
234
+ module = parent.obj if parent is not None else parent
235
+ # Avoiding `Metafunc` is quite problematic for now, as there are quite a lot of internals we rely on
236
+ metafunc = Metafunc(definition, fixtureinfo, self.config, cls=cls, module=module, _ispytest=True)
237
+ methods = []
238
+ if module is not None and hasattr(module, "pytest_generate_tests"):
239
+ methods.append(module.pytest_generate_tests)
240
+ if hasattr(cls, "pytest_generate_tests"):
241
+ cls = cast(Type, cls)
242
+ methods.append(cls().pytest_generate_tests)
243
+ self.ihook.pytest_generate_tests.call_extra(methods, {"metafunc": metafunc})
244
+ return metafunc
245
+
246
+ def collect(self) -> list[Function]: # type: ignore[return]
247
+ """Generate different test items for all API operations available in the given schema."""
248
+ try:
249
+ items = [item for operation in self.schema.get_all_operations() for item in self._gen_items(operation)]
250
+ if not items:
251
+ fail_on_no_matches(self.nodeid)
252
+ return items
253
+ except Exception:
254
+ pytest.fail("Error during collection")
255
+
256
+
257
+ @hookimpl(hookwrapper=True) # type: ignore[misc]
258
+ def pytest_pycollect_makeitem(collector: nodes.Collector, name: str, obj: Any) -> Generator[None, Any, None]:
259
+ """Switch to a different collector if the test is parametrized marked by schemathesis."""
260
+ outcome = yield
261
+ try:
262
+ schema = SchemaHandleMark.get(obj)
263
+ assert schema is not None
264
+ outcome.force_result(SchemathesisCase.from_parent(collector, test_function=obj, name=name, schema=schema))
265
+ except Exception:
266
+ outcome.get_result()
267
+
268
+
269
+ @pytest.hookimpl(tryfirst=True) # type: ignore[misc]
270
+ def pytest_exception_interact(node: Function, call: pytest.CallInfo, report: pytest.TestReport) -> None:
271
+ if call.excinfo:
272
+ if issubclass(call.excinfo.type, SchemathesisError) and hasattr(call.excinfo.value, "__notes__"):
273
+ # Hypothesis adds quite a lot of additional debug information which is not that helpful in Schemathesis
274
+ call.excinfo.value.__notes__.clear()
275
+ report.longrepr = "".join(format_exception(call.excinfo.value))
276
+ if call.excinfo.type is FailureGroup:
277
+ tb_entries = list(call.excinfo.traceback)
278
+ total_frames = len(tb_entries)
279
+
280
+ # Keep internal Schemathesis frames + one extra one from the caller
281
+ skip_frames = 0
282
+ for i in range(total_frames - 1, -1, -1):
283
+ entry = tb_entries[i]
284
+
285
+ if not str(entry.path).endswith("schemathesis/generation/case.py"):
286
+ skip_frames = i
287
+ break
288
+
289
+ report.longrepr = "".join(
290
+ format_exception(call.excinfo.value, with_traceback=True, skip_frames=skip_frames)
291
+ )
292
+
293
+
294
+ @hookimpl(wrapper=True)
295
+ def pytest_pyfunc_call(pyfuncitem): # type: ignore[no-untyped-def]
296
+ """It is possible to have a Hypothesis exception in runtime.
297
+
298
+ For example - kwargs validation is failed for some strategy.
299
+ """
300
+ from schemathesis.generation.hypothesis.builder import (
301
+ ApiOperationMark,
302
+ InvalidHeadersExampleMark,
303
+ InvalidRegexMark,
304
+ MissingPathParameters,
305
+ NonSerializableMark,
306
+ UnsatisfiableExampleMark,
307
+ )
308
+
309
+ __tracebackhide__ = True
310
+ if isinstance(pyfuncitem, SchemathesisFunction):
311
+ try:
312
+ with ignore_hypothesis_output():
313
+ yield
314
+ except InvalidArgument as exc:
315
+ if "Inconsistent args" in str(exc) and "@example()" in str(exc):
316
+ raise IncorrectUsage(GIVEN_AND_EXPLICIT_EXAMPLES_ERROR_MESSAGE) from None
317
+ raise InvalidSchema(exc.args[0]) from None
318
+ except (SkipTest, unittest.SkipTest) as exc:
319
+ if UnsatisfiableExampleMark.is_set(pyfuncitem.obj):
320
+ raise Unsatisfiable("Failed to generate test cases from examples for this API operation") from None
321
+ non_serializable = NonSerializableMark.get(pyfuncitem.obj)
322
+ if non_serializable is not None:
323
+ media_types = ", ".join(non_serializable.media_types)
324
+ raise SerializationNotPossible(
325
+ "Failed to generate test cases from examples for this API operation because of"
326
+ f" unsupported payload media types: {media_types}\n{SERIALIZERS_SUGGESTION_MESSAGE}",
327
+ media_types=non_serializable.media_types,
328
+ ) from None
329
+ invalid_regex = InvalidRegexMark.get(pyfuncitem.obj)
330
+ if invalid_regex is not None:
331
+ raise InvalidRegexPattern.from_schema_error(invalid_regex, from_examples=True) from None
332
+ invalid_headers = InvalidHeadersExampleMark.get(pyfuncitem.obj)
333
+ if invalid_headers is not None:
334
+ raise InvalidHeadersExample.from_headers(invalid_headers) from None
335
+ pytest.skip(exc.args[0])
336
+ except FailedHealthCheck as exc:
337
+ operation = ApiOperationMark.get(pyfuncitem.obj)
338
+ assert operation is not None
339
+ raise build_health_check_error(
340
+ operation, exc, with_tip=True, tip_style=HealthCheckTipStyle.PYTEST
341
+ ) from None
342
+ except Unsatisfiable:
343
+ operation = ApiOperationMark.get(pyfuncitem.obj)
344
+ assert operation is not None
345
+ raise build_unsatisfiable_error(operation, with_tip=True) from None
346
+ except SchemaError as exc:
347
+ raise InvalidRegexPattern.from_schema_error(exc, from_examples=False) from exc
348
+
349
+ invalid_headers = InvalidHeadersExampleMark.get(pyfuncitem.obj)
350
+ if invalid_headers is not None:
351
+ raise InvalidHeadersExample.from_headers(invalid_headers) from None
352
+
353
+ missing_path_parameters = MissingPathParameters.get(pyfuncitem.obj)
354
+ if missing_path_parameters:
355
+ raise missing_path_parameters from None
356
+ else:
357
+ yield
File without changes
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ if TYPE_CHECKING:
6
+ from starlette_testclient import TestClient as ASGIClient
7
+
8
+
9
+ def get_client(app: Any) -> ASGIClient:
10
+ from starlette_testclient import TestClient as ASGIClient
11
+
12
+ return ASGIClient(app)
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ if TYPE_CHECKING:
6
+ from werkzeug import Client
7
+
8
+
9
+ def get_client(app: Any) -> Client:
10
+ from werkzeug import Client
11
+
12
+ return Client(app)