schemathesis 3.39.15__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 +238 -308
  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.15.dist-info → schemathesis-4.0.0.dist-info}/entry_points.txt +1 -1
  151. {schemathesis-3.39.15.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 -712
  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.15.dist-info/METADATA +0 -293
  251. schemathesis-3.39.15.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.15.dist-info → schemathesis-4.0.0.dist-info}/WHEEL +0 -0
@@ -1,49 +1,61 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
3
4
  import unittest
4
- from contextlib import contextmanager
5
5
  from functools import partial
6
6
  from typing import TYPE_CHECKING, Any, Callable, Generator, Type, cast
7
7
 
8
8
  import pytest
9
- from _pytest import fixtures, nodes
9
+ from _pytest import nodes
10
10
  from _pytest.config import hookimpl
11
11
  from _pytest.python import Class, Function, FunctionDefinition, Metafunc, Module, PyCollector
12
- from hypothesis import reporting
13
12
  from hypothesis.errors import InvalidArgument, Unsatisfiable
14
13
  from jsonschema.exceptions import SchemaError
15
14
 
16
- from .._dependency_versions import IS_PYTEST_ABOVE_7, IS_PYTEST_ABOVE_8
17
- from .._override import get_override_from_mark
18
- from ..constants import (
19
- GIVEN_AND_EXPLICIT_EXAMPLES_ERROR_MESSAGE,
15
+ from schemathesis.core.control import SkipTest
16
+ from schemathesis.core.errors import (
20
17
  RECURSIVE_REFERENCE_ERROR_MESSAGE,
21
18
  SERIALIZERS_SUGGESTION_MESSAGE,
22
- )
23
- from ..exceptions import (
19
+ IncorrectUsage,
24
20
  InvalidHeadersExample,
25
- InvalidRegularExpression,
26
- OperationSchemaError,
21
+ InvalidRegexPattern,
22
+ InvalidSchema,
27
23
  SerializationNotPossible,
28
- SkipTest,
29
- UsageError,
24
+ format_exception,
30
25
  )
31
- from ..internal.result import Ok, Result
32
- from ..utils import (
33
- PARAMETRIZE_MARKER,
34
- fail_on_no_matches,
35
- get_given_args,
36
- get_given_kwargs,
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,
37
33
  is_given_applied,
38
- is_schemathesis_test,
39
34
  merge_given_args,
40
35
  validate_given_args,
41
36
  )
37
+ from schemathesis.generation.hypothesis.reporting import ignore_hypothesis_output
38
+ from schemathesis.pytest.control_flow import fail_on_no_matches
39
+ from schemathesis.schemas import APIOperation
42
40
 
43
41
  if TYPE_CHECKING:
44
42
  from _pytest.fixtures import FuncFixtureInfo
45
43
 
46
- from ..models import APIOperation
44
+ from schemathesis.schemas import BaseSchema
45
+
46
+ GIVEN_AND_EXPLICIT_EXAMPLES_ERROR_MESSAGE = (
47
+ "Unsupported test setup. Tests using `@schema.given` cannot be combined with explicit schema examples in the same "
48
+ "function. Separate these tests into distinct functions to avoid conflicts."
49
+ )
50
+
51
+
52
+ def _is_schema(value: object) -> bool:
53
+ from schemathesis.schemas import BaseSchema
54
+
55
+ return isinstance(value, BaseSchema)
56
+
57
+
58
+ SchemaHandleMark = Mark["BaseSchema"](attr_name="schema", check=_is_schema)
47
59
 
48
60
 
49
61
  class SchemathesisFunction(Function):
@@ -58,21 +70,15 @@ class SchemathesisFunction(Function):
58
70
  self.test_function = test_func
59
71
  self.test_name = test_name
60
72
 
61
- if not IS_PYTEST_ABOVE_7:
62
- # On pytest 7, `self.obj` is already `partial`
63
- def _getobj(self) -> partial:
64
- """Tests defined as methods require `self` as the first argument.
65
-
66
- This method is called only for this case.
67
- """
68
- return partial(self.obj, self.parent.obj) # type: ignore
69
-
70
73
 
71
74
  class SchemathesisCase(PyCollector):
72
- def __init__(self, test_function: Callable, *args: Any, **kwargs: Any) -> None:
73
- self.given_kwargs: dict[str, Any] | None
74
- given_args = get_given_args(test_function)
75
- given_kwargs = get_given_kwargs(test_function)
75
+ def __init__(self, test_function: Callable, schema: BaseSchema, *args: Any, **kwargs: Any) -> None:
76
+ self.given_kwargs: dict[str, Any]
77
+ given_args = GivenArgsMark.get(test_function)
78
+ given_kwargs = GivenKwargsMark.get(test_function)
79
+
80
+ assert given_args is not None
81
+ assert given_kwargs is not None
76
82
 
77
83
  def _init_with_valid_test(_test_function: Callable, _args: tuple, _kwargs: dict[str, Any]) -> None:
78
84
  self.test_function = _test_function
@@ -84,20 +90,18 @@ class SchemathesisCase(PyCollector):
84
90
  if failing_test is not None:
85
91
  self.test_function = failing_test
86
92
  self.is_invalid_test = True
87
- self.given_kwargs = None
93
+ self.given_kwargs = {}
88
94
  else:
89
95
  _init_with_valid_test(test_function, given_args, given_kwargs)
90
96
  else:
91
97
  _init_with_valid_test(test_function, given_args, given_kwargs)
92
- self.schemathesis_case = getattr(test_function, PARAMETRIZE_MARKER)
98
+ self.schema = schema
93
99
  super().__init__(*args, **kwargs)
94
100
 
95
101
  def _get_test_name(self, operation: APIOperation) -> str:
96
- return f"{self.name}[{operation.verbose_name}]"
102
+ return f"{self.name}[{operation.label}]"
97
103
 
98
- def _gen_items(
99
- self, result: Result[APIOperation, OperationSchemaError]
100
- ) -> Generator[SchemathesisFunction, None, None]:
104
+ def _gen_items(self, result: Result[APIOperation, InvalidSchema]) -> Generator[SchemathesisFunction, None, None]:
101
105
  """Generate all tests for the given API operation.
102
106
 
103
107
  Could produce more than one test item if
@@ -106,7 +110,15 @@ class SchemathesisCase(PyCollector):
106
110
  This implementation is based on the original one in pytest, but with slight adjustments
107
111
  to produce tests out of hypothesis ones.
108
112
  """
109
- from .._hypothesis import create_test
113
+ from schemathesis.checks import load_all_checks
114
+ from schemathesis.generation.hypothesis.builder import (
115
+ HypothesisTestConfig,
116
+ HypothesisTestMode,
117
+ create_test,
118
+ make_async_test,
119
+ )
120
+
121
+ load_all_checks()
110
122
 
111
123
  is_trio_test = False
112
124
  for mark in getattr(self.test_function, "pytestmark", []):
@@ -119,34 +131,57 @@ class SchemathesisCase(PyCollector):
119
131
  if self.is_invalid_test:
120
132
  funcobj = self.test_function
121
133
  else:
122
- override = get_override_from_mark(self.test_function)
123
- as_strategy_kwargs: dict | None
134
+ as_strategy_kwargs = {}
135
+
136
+ auth = self.schema.config.auth_for(operation=operation)
137
+ if auth is not None:
138
+ from requests.auth import _basic_auth_str
139
+
140
+ as_strategy_kwargs["headers"] = {"Authorization": _basic_auth_str(*auth)}
141
+ headers = self.schema.config.headers_for(operation=operation)
142
+ if headers:
143
+ as_strategy_kwargs["headers"] = headers
144
+
145
+ override = overrides.for_operation(operation=operation, config=self.schema.config)
124
146
  if override is not None:
125
- as_strategy_kwargs = {}
126
- for location, entry in override.for_operation(operation).items():
147
+ for location, entry in override.items():
127
148
  if entry:
128
149
  as_strategy_kwargs[location] = entry
129
- else:
130
- as_strategy_kwargs = None
150
+ modes = []
151
+ phases = self.schema.config.phases_for(operation=operation)
152
+ if phases.examples.enabled:
153
+ modes.append(HypothesisTestMode.EXAMPLES)
154
+ if phases.fuzzing.enabled:
155
+ modes.append(HypothesisTestMode.FUZZING)
156
+ if phases.coverage.enabled:
157
+ modes.append(HypothesisTestMode.COVERAGE)
158
+
131
159
  funcobj = create_test(
132
160
  operation=operation,
133
- test=self.test_function,
134
- _given_kwargs=self.given_kwargs,
135
- data_generation_methods=self.schemathesis_case.data_generation_methods,
136
- generation_config=self.schemathesis_case.generation_config,
137
- as_strategy_kwargs=as_strategy_kwargs,
138
- keep_async_fn=is_trio_test,
161
+ test_func=self.test_function,
162
+ config=HypothesisTestConfig(
163
+ modes=modes,
164
+ settings=self.schema.config.get_hypothesis_settings(operation=operation),
165
+ given_kwargs=self.given_kwargs,
166
+ project=self.schema.config,
167
+ as_strategy_kwargs=as_strategy_kwargs,
168
+ ),
139
169
  )
170
+ if asyncio.iscoroutinefunction(self.test_function):
171
+ # `pytest-trio` expects a coroutine function
172
+ if is_trio_test:
173
+ funcobj.hypothesis.inner_test = self.test_function # type: ignore
174
+ else:
175
+ funcobj.hypothesis.inner_test = make_async_test(self.test_function) # type: ignore
140
176
  name = self._get_test_name(operation)
141
177
  else:
142
178
  error = result.err()
143
179
  funcobj = error.as_failing_test_function()
144
180
  name = self.name
145
- # `full_path` is always available in this case
146
181
  if error.method:
147
- name += f"[{error.method.upper()} {error.full_path}]"
182
+ name += f"[{error.method.upper()} {error.path}]"
148
183
  else:
149
- name += f"[{error.full_path}]"
184
+ name += f"[{error.path}]"
150
185
 
151
186
  cls = self._get_class_parent()
152
187
  definition: FunctionDefinition = FunctionDefinition.from_parent(
@@ -171,8 +206,6 @@ class SchemathesisCase(PyCollector):
171
206
  originalname=self.name,
172
207
  )
173
208
  else:
174
- if not IS_PYTEST_ABOVE_8:
175
- fixtures.add_funcarg_pseudo_fixture_def(self.parent, metafunc, fixturemanager) # type: ignore[arg-type]
176
209
  fixtureinfo.prune_dependency_tree()
177
210
  for callspec in metafunc._calls:
178
211
  subname = f"{name}[{callspec.id}]"
@@ -194,11 +227,8 @@ class SchemathesisCase(PyCollector):
194
227
  def _parametrize(self, cls: type | None, definition: FunctionDefinition, fixtureinfo: FuncFixtureInfo) -> Metafunc:
195
228
  parent = self.getparent(Module)
196
229
  module = parent.obj if parent is not None else parent
197
- kwargs = {"cls": cls, "module": module}
198
- if IS_PYTEST_ABOVE_7:
199
- # Avoiding `Metafunc` is quite problematic for now, as there are quite a lot of internals we rely on
200
- kwargs["_ispytest"] = True
201
- metafunc = Metafunc(definition, fixtureinfo, self.config, **kwargs)
230
+ # Avoiding `Metafunc` is quite problematic for now, as there are quite a lot of internals we rely on
231
+ metafunc = Metafunc(definition, fixtureinfo, self.config, cls=cls, module=module, _ispytest=True)
202
232
  methods = []
203
233
  if module is not None and hasattr(module, "pytest_generate_tests"):
204
234
  methods.append(module.pytest_generate_tests)
@@ -211,13 +241,7 @@ class SchemathesisCase(PyCollector):
211
241
  def collect(self) -> list[Function]: # type: ignore
212
242
  """Generate different test items for all API operations available in the given schema."""
213
243
  try:
214
- items = [
215
- item
216
- for operation in self.schemathesis_case.get_all_operations(
217
- hooks=getattr(self.test_function, "_schemathesis_hooks", None)
218
- )
219
- for item in self._gen_items(operation)
220
- ]
244
+ items = [item for operation in self.schema.get_all_operations() for item in self._gen_items(operation)]
221
245
  if not items:
222
246
  fail_on_no_matches(self.nodeid)
223
247
  return items
@@ -225,34 +249,34 @@ class SchemathesisCase(PyCollector):
225
249
  pytest.fail("Error during collection")
226
250
 
227
251
 
228
- @hookimpl(hookwrapper=True) # type:ignore # pragma: no mutate
252
+ @hookimpl(hookwrapper=True) # type:ignore
229
253
  def pytest_pycollect_makeitem(collector: nodes.Collector, name: str, obj: Any) -> Generator[None, Any, None]:
230
254
  """Switch to a different collector if the test is parametrized marked by schemathesis."""
231
255
  outcome = yield
232
- if is_schemathesis_test(obj):
233
- outcome.force_result(SchemathesisCase.from_parent(collector, test_function=obj, name=name))
234
- else:
256
+ try:
257
+ schema = SchemaHandleMark.get(obj)
258
+ assert schema is not None
259
+ outcome.force_result(SchemathesisCase.from_parent(collector, test_function=obj, name=name, schema=schema))
260
+ except Exception:
235
261
  outcome.get_result()
236
262
 
237
263
 
238
- IGNORED_HYPOTHESIS_OUTPUT = ("Falsifying example",)
239
-
240
-
241
- def _should_ignore_entry(value: str) -> bool:
242
- return value.startswith(IGNORED_HYPOTHESIS_OUTPUT)
264
+ @pytest.hookimpl(tryfirst=True) # type: ignore[misc]
265
+ def pytest_exception_interact(node: Function, call: pytest.CallInfo, report: pytest.TestReport) -> None:
266
+ if call.excinfo and call.excinfo.type is FailureGroup:
267
+ tb_entries = list(call.excinfo.traceback)
268
+ total_frames = len(tb_entries)
243
269
 
270
+ # Keep internal Schemathesis frames + one extra one from the caller
271
+ skip_frames = 0
272
+ for i in range(total_frames - 1, -1, -1):
273
+ entry = tb_entries[i]
244
274
 
245
- def hypothesis_reporter(value: str) -> None:
246
- if _should_ignore_entry(value):
247
- return
248
- reporting.default(value)
249
-
275
+ if not str(entry.path).endswith("schemathesis/generation/case.py"):
276
+ skip_frames = i
277
+ break
250
278
 
251
- @contextmanager
252
- def skip_unnecessary_hypothesis_output() -> Generator:
253
- """Avoid printing Hypothesis output that is not necessary in Schemathesis' pytest plugin."""
254
- with reporting.with_reporter(hypothesis_reporter): # type: ignore
255
- yield
279
+ report.longrepr = "".join(format_exception(call.excinfo.value, with_traceback=True, skip_frames=skip_frames))
256
280
 
257
281
 
258
282
  @hookimpl(wrapper=True)
@@ -263,28 +287,28 @@ def pytest_pyfunc_call(pyfuncitem): # type:ignore
263
287
  """
264
288
  from hypothesis_jsonschema._canonicalise import HypothesisRefResolutionError
265
289
 
266
- from .._hypothesis import (
267
- get_invalid_example_headers_mark,
268
- get_invalid_regex_mark,
269
- get_non_serializable_mark,
270
- has_unsatisfied_example_mark,
290
+ from schemathesis.generation.hypothesis.builder import (
291
+ InvalidHeadersExampleMark,
292
+ InvalidRegexMark,
293
+ NonSerializableMark,
294
+ UnsatisfiableExampleMark,
271
295
  )
272
296
 
273
297
  __tracebackhide__ = True
274
298
  if isinstance(pyfuncitem, SchemathesisFunction):
275
299
  try:
276
- with skip_unnecessary_hypothesis_output():
300
+ with ignore_hypothesis_output():
277
301
  yield
278
302
  except InvalidArgument as exc:
279
303
  if "Inconsistent args" in str(exc) and "@example()" in str(exc):
280
- raise UsageError(GIVEN_AND_EXPLICIT_EXAMPLES_ERROR_MESSAGE) from None
281
- raise OperationSchemaError(exc.args[0]) from None
304
+ raise IncorrectUsage(GIVEN_AND_EXPLICIT_EXAMPLES_ERROR_MESSAGE) from None
305
+ raise InvalidSchema(exc.args[0]) from None
282
306
  except HypothesisRefResolutionError:
283
307
  pytest.skip(RECURSIVE_REFERENCE_ERROR_MESSAGE)
284
308
  except (SkipTest, unittest.SkipTest) as exc:
285
- if has_unsatisfied_example_mark(pyfuncitem.obj):
309
+ if UnsatisfiableExampleMark.is_set(pyfuncitem.obj):
286
310
  raise Unsatisfiable("Failed to generate test cases from examples for this API operation") from None
287
- non_serializable = get_non_serializable_mark(pyfuncitem.obj)
311
+ non_serializable = NonSerializableMark.get(pyfuncitem.obj)
288
312
  if non_serializable is not None:
289
313
  media_types = ", ".join(non_serializable.media_types)
290
314
  raise SerializationNotPossible(
@@ -292,20 +316,16 @@ def pytest_pyfunc_call(pyfuncitem): # type:ignore
292
316
  f" unsupported payload media types: {media_types}\n{SERIALIZERS_SUGGESTION_MESSAGE}",
293
317
  media_types=non_serializable.media_types,
294
318
  ) from None
295
- invalid_regex = get_invalid_regex_mark(pyfuncitem.obj)
319
+ invalid_regex = InvalidRegexMark.get(pyfuncitem.obj)
296
320
  if invalid_regex is not None:
297
- raise InvalidRegularExpression.from_schema_error(invalid_regex, from_examples=True) from None
298
- invalid_headers = get_invalid_example_headers_mark(pyfuncitem.obj)
321
+ raise InvalidRegexPattern.from_schema_error(invalid_regex, from_examples=True) from None
322
+ invalid_headers = InvalidHeadersExampleMark.get(pyfuncitem.obj)
299
323
  if invalid_headers is not None:
300
324
  raise InvalidHeadersExample.from_headers(invalid_headers) from None
301
325
  pytest.skip(exc.args[0])
302
326
  except SchemaError as exc:
303
- raise InvalidRegularExpression.from_schema_error(exc, from_examples=False) from exc
304
- except Exception as exc:
305
- if hasattr(exc, "__notes__"):
306
- exc.__notes__ = [note for note in exc.__notes__ if not _should_ignore_entry(note)] # type: ignore
307
- raise
308
- invalid_headers = get_invalid_example_headers_mark(pyfuncitem.obj)
327
+ raise InvalidRegexPattern.from_schema_error(exc, from_examples=False) from exc
328
+ invalid_headers = InvalidHeadersExampleMark.get(pyfuncitem.obj)
309
329
  if invalid_headers is not None:
310
330
  raise InvalidHeadersExample.from_headers(invalid_headers) from None
311
331
  else:
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)