schemathesis 3.25.6__py3-none-any.whl → 4.0.0a1__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 (221) hide show
  1. schemathesis/__init__.py +27 -65
  2. schemathesis/auths.py +102 -82
  3. schemathesis/checks.py +126 -46
  4. schemathesis/cli/__init__.py +11 -1760
  5. schemathesis/cli/__main__.py +4 -0
  6. schemathesis/cli/commands/__init__.py +37 -0
  7. schemathesis/cli/commands/run/__init__.py +662 -0
  8. schemathesis/cli/commands/run/checks.py +80 -0
  9. schemathesis/cli/commands/run/context.py +117 -0
  10. schemathesis/cli/commands/run/events.py +35 -0
  11. schemathesis/cli/commands/run/executor.py +138 -0
  12. schemathesis/cli/commands/run/filters.py +194 -0
  13. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  14. schemathesis/cli/commands/run/handlers/base.py +18 -0
  15. schemathesis/cli/commands/run/handlers/cassettes.py +494 -0
  16. schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
  17. schemathesis/cli/commands/run/handlers/output.py +746 -0
  18. schemathesis/cli/commands/run/hypothesis.py +105 -0
  19. schemathesis/cli/commands/run/loaders.py +129 -0
  20. schemathesis/cli/{callbacks.py → commands/run/validation.py} +103 -174
  21. schemathesis/cli/constants.py +5 -52
  22. schemathesis/cli/core.py +17 -0
  23. schemathesis/cli/ext/fs.py +14 -0
  24. schemathesis/cli/ext/groups.py +55 -0
  25. schemathesis/cli/{options.py → ext/options.py} +39 -10
  26. schemathesis/cli/hooks.py +36 -0
  27. schemathesis/contrib/__init__.py +1 -3
  28. schemathesis/contrib/openapi/__init__.py +1 -3
  29. schemathesis/contrib/openapi/fill_missing_examples.py +3 -5
  30. schemathesis/core/__init__.py +58 -0
  31. schemathesis/core/compat.py +25 -0
  32. schemathesis/core/control.py +2 -0
  33. schemathesis/core/curl.py +58 -0
  34. schemathesis/core/deserialization.py +65 -0
  35. schemathesis/core/errors.py +370 -0
  36. schemathesis/core/failures.py +285 -0
  37. schemathesis/core/fs.py +19 -0
  38. schemathesis/{_lazy_import.py → core/lazy_import.py} +1 -0
  39. schemathesis/core/loaders.py +104 -0
  40. schemathesis/core/marks.py +66 -0
  41. schemathesis/{transports/content_types.py → core/media_types.py} +17 -13
  42. schemathesis/core/output/__init__.py +69 -0
  43. schemathesis/core/output/sanitization.py +197 -0
  44. schemathesis/core/rate_limit.py +60 -0
  45. schemathesis/core/registries.py +31 -0
  46. schemathesis/{internal → core}/result.py +1 -1
  47. schemathesis/core/transforms.py +113 -0
  48. schemathesis/core/transport.py +108 -0
  49. schemathesis/core/validation.py +38 -0
  50. schemathesis/core/version.py +7 -0
  51. schemathesis/engine/__init__.py +30 -0
  52. schemathesis/engine/config.py +59 -0
  53. schemathesis/engine/context.py +119 -0
  54. schemathesis/engine/control.py +36 -0
  55. schemathesis/engine/core.py +157 -0
  56. schemathesis/engine/errors.py +394 -0
  57. schemathesis/engine/events.py +337 -0
  58. schemathesis/engine/phases/__init__.py +66 -0
  59. schemathesis/{runner → engine/phases}/probes.py +50 -67
  60. schemathesis/engine/phases/stateful/__init__.py +65 -0
  61. schemathesis/engine/phases/stateful/_executor.py +326 -0
  62. schemathesis/engine/phases/stateful/context.py +85 -0
  63. schemathesis/engine/phases/unit/__init__.py +174 -0
  64. schemathesis/engine/phases/unit/_executor.py +321 -0
  65. schemathesis/engine/phases/unit/_pool.py +74 -0
  66. schemathesis/engine/recorder.py +241 -0
  67. schemathesis/errors.py +31 -0
  68. schemathesis/experimental/__init__.py +18 -14
  69. schemathesis/filters.py +103 -14
  70. schemathesis/generation/__init__.py +21 -37
  71. schemathesis/generation/case.py +190 -0
  72. schemathesis/generation/coverage.py +931 -0
  73. schemathesis/generation/hypothesis/__init__.py +30 -0
  74. schemathesis/generation/hypothesis/builder.py +585 -0
  75. schemathesis/generation/hypothesis/examples.py +50 -0
  76. schemathesis/generation/hypothesis/given.py +66 -0
  77. schemathesis/generation/hypothesis/reporting.py +14 -0
  78. schemathesis/generation/hypothesis/strategies.py +16 -0
  79. schemathesis/generation/meta.py +115 -0
  80. schemathesis/generation/modes.py +28 -0
  81. schemathesis/generation/overrides.py +96 -0
  82. schemathesis/generation/stateful/__init__.py +20 -0
  83. schemathesis/{stateful → generation/stateful}/state_machine.py +68 -81
  84. schemathesis/generation/targets.py +69 -0
  85. schemathesis/graphql/__init__.py +15 -0
  86. schemathesis/graphql/checks.py +115 -0
  87. schemathesis/graphql/loaders.py +131 -0
  88. schemathesis/hooks.py +99 -67
  89. schemathesis/openapi/__init__.py +13 -0
  90. schemathesis/openapi/checks.py +412 -0
  91. schemathesis/openapi/generation/__init__.py +0 -0
  92. schemathesis/openapi/generation/filters.py +63 -0
  93. schemathesis/openapi/loaders.py +178 -0
  94. schemathesis/pytest/__init__.py +5 -0
  95. schemathesis/pytest/control_flow.py +7 -0
  96. schemathesis/pytest/lazy.py +273 -0
  97. schemathesis/pytest/loaders.py +12 -0
  98. schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +106 -127
  99. schemathesis/python/__init__.py +0 -0
  100. schemathesis/python/asgi.py +12 -0
  101. schemathesis/python/wsgi.py +12 -0
  102. schemathesis/schemas.py +537 -261
  103. schemathesis/specs/graphql/__init__.py +0 -1
  104. schemathesis/specs/graphql/_cache.py +25 -0
  105. schemathesis/specs/graphql/nodes.py +1 -0
  106. schemathesis/specs/graphql/scalars.py +7 -5
  107. schemathesis/specs/graphql/schemas.py +215 -187
  108. schemathesis/specs/graphql/validation.py +11 -18
  109. schemathesis/specs/openapi/__init__.py +7 -1
  110. schemathesis/specs/openapi/_cache.py +122 -0
  111. schemathesis/specs/openapi/_hypothesis.py +146 -165
  112. schemathesis/specs/openapi/checks.py +565 -67
  113. schemathesis/specs/openapi/converter.py +33 -6
  114. schemathesis/specs/openapi/definitions.py +11 -18
  115. schemathesis/specs/openapi/examples.py +139 -23
  116. schemathesis/specs/openapi/expressions/__init__.py +37 -2
  117. schemathesis/specs/openapi/expressions/context.py +4 -6
  118. schemathesis/specs/openapi/expressions/extractors.py +23 -0
  119. schemathesis/specs/openapi/expressions/lexer.py +20 -18
  120. schemathesis/specs/openapi/expressions/nodes.py +38 -14
  121. schemathesis/specs/openapi/expressions/parser.py +26 -5
  122. schemathesis/specs/openapi/formats.py +45 -0
  123. schemathesis/specs/openapi/links.py +65 -165
  124. schemathesis/specs/openapi/media_types.py +32 -0
  125. schemathesis/specs/openapi/negative/__init__.py +7 -3
  126. schemathesis/specs/openapi/negative/mutations.py +24 -8
  127. schemathesis/specs/openapi/parameters.py +46 -30
  128. schemathesis/specs/openapi/patterns.py +137 -0
  129. schemathesis/specs/openapi/references.py +47 -57
  130. schemathesis/specs/openapi/schemas.py +478 -369
  131. schemathesis/specs/openapi/security.py +25 -7
  132. schemathesis/specs/openapi/serialization.py +11 -6
  133. schemathesis/specs/openapi/stateful/__init__.py +185 -73
  134. schemathesis/specs/openapi/utils.py +6 -1
  135. schemathesis/transport/__init__.py +104 -0
  136. schemathesis/transport/asgi.py +26 -0
  137. schemathesis/transport/prepare.py +99 -0
  138. schemathesis/transport/requests.py +221 -0
  139. schemathesis/{_xml.py → transport/serialization.py} +143 -28
  140. schemathesis/transport/wsgi.py +165 -0
  141. schemathesis-4.0.0a1.dist-info/METADATA +297 -0
  142. schemathesis-4.0.0a1.dist-info/RECORD +152 -0
  143. {schemathesis-3.25.6.dist-info → schemathesis-4.0.0a1.dist-info}/WHEEL +1 -1
  144. {schemathesis-3.25.6.dist-info → schemathesis-4.0.0a1.dist-info}/entry_points.txt +1 -1
  145. schemathesis/_compat.py +0 -74
  146. schemathesis/_dependency_versions.py +0 -17
  147. schemathesis/_hypothesis.py +0 -246
  148. schemathesis/_override.py +0 -49
  149. schemathesis/cli/cassettes.py +0 -375
  150. schemathesis/cli/context.py +0 -58
  151. schemathesis/cli/debug.py +0 -26
  152. schemathesis/cli/handlers.py +0 -16
  153. schemathesis/cli/junitxml.py +0 -43
  154. schemathesis/cli/output/__init__.py +0 -1
  155. schemathesis/cli/output/default.py +0 -790
  156. schemathesis/cli/output/short.py +0 -44
  157. schemathesis/cli/sanitization.py +0 -20
  158. schemathesis/code_samples.py +0 -149
  159. schemathesis/constants.py +0 -55
  160. schemathesis/contrib/openapi/formats/__init__.py +0 -9
  161. schemathesis/contrib/openapi/formats/uuid.py +0 -15
  162. schemathesis/contrib/unique_data.py +0 -41
  163. schemathesis/exceptions.py +0 -560
  164. schemathesis/extra/_aiohttp.py +0 -27
  165. schemathesis/extra/_flask.py +0 -10
  166. schemathesis/extra/_server.py +0 -17
  167. schemathesis/failures.py +0 -209
  168. schemathesis/fixups/__init__.py +0 -36
  169. schemathesis/fixups/fast_api.py +0 -41
  170. schemathesis/fixups/utf8_bom.py +0 -29
  171. schemathesis/graphql.py +0 -4
  172. schemathesis/internal/__init__.py +0 -7
  173. schemathesis/internal/copy.py +0 -13
  174. schemathesis/internal/datetime.py +0 -5
  175. schemathesis/internal/deprecation.py +0 -34
  176. schemathesis/internal/jsonschema.py +0 -35
  177. schemathesis/internal/transformation.py +0 -15
  178. schemathesis/internal/validation.py +0 -34
  179. schemathesis/lazy.py +0 -361
  180. schemathesis/loaders.py +0 -120
  181. schemathesis/models.py +0 -1234
  182. schemathesis/parameters.py +0 -86
  183. schemathesis/runner/__init__.py +0 -570
  184. schemathesis/runner/events.py +0 -329
  185. schemathesis/runner/impl/__init__.py +0 -3
  186. schemathesis/runner/impl/core.py +0 -1035
  187. schemathesis/runner/impl/solo.py +0 -90
  188. schemathesis/runner/impl/threadpool.py +0 -400
  189. schemathesis/runner/serialization.py +0 -411
  190. schemathesis/sanitization.py +0 -248
  191. schemathesis/serializers.py +0 -323
  192. schemathesis/service/__init__.py +0 -18
  193. schemathesis/service/auth.py +0 -11
  194. schemathesis/service/ci.py +0 -201
  195. schemathesis/service/client.py +0 -100
  196. schemathesis/service/constants.py +0 -38
  197. schemathesis/service/events.py +0 -57
  198. schemathesis/service/hosts.py +0 -107
  199. schemathesis/service/metadata.py +0 -46
  200. schemathesis/service/models.py +0 -49
  201. schemathesis/service/report.py +0 -255
  202. schemathesis/service/serialization.py +0 -199
  203. schemathesis/service/usage.py +0 -65
  204. schemathesis/specs/graphql/loaders.py +0 -344
  205. schemathesis/specs/openapi/filters.py +0 -49
  206. schemathesis/specs/openapi/loaders.py +0 -667
  207. schemathesis/specs/openapi/stateful/links.py +0 -92
  208. schemathesis/specs/openapi/validation.py +0 -25
  209. schemathesis/stateful/__init__.py +0 -133
  210. schemathesis/targets.py +0 -45
  211. schemathesis/throttling.py +0 -41
  212. schemathesis/transports/__init__.py +0 -5
  213. schemathesis/transports/auth.py +0 -15
  214. schemathesis/transports/headers.py +0 -35
  215. schemathesis/transports/responses.py +0 -52
  216. schemathesis/types.py +0 -35
  217. schemathesis/utils.py +0 -169
  218. schemathesis-3.25.6.dist-info/METADATA +0 -356
  219. schemathesis-3.25.6.dist-info/RECORD +0 -134
  220. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
  221. {schemathesis-3.25.6.dist-info → schemathesis-4.0.0a1.dist-info}/licenses/LICENSE +0 -0
schemathesis/lazy.py DELETED
@@ -1,361 +0,0 @@
1
- from __future__ import annotations
2
- from dataclasses import dataclass, field
3
- from inspect import signature
4
- from typing import Any, Callable, Generator
5
-
6
- import pytest
7
- from _pytest.fixtures import FixtureRequest
8
- from hypothesis.core import HypothesisHandle
9
- from hypothesis.errors import Flaky
10
- from hypothesis.internal.escalation import format_exception, get_trimmed_traceback
11
- from hypothesis.internal.reflection import impersonate
12
- from pyrate_limiter import Limiter
13
- from pytest_subtests import SubTests, nullcontext
14
-
15
- from ._compat import MultipleFailures, get_interesting_origin
16
- from ._override import check_no_override_mark, CaseOverride, set_override_mark, get_override_from_mark
17
- from .auths import AuthStorage
18
- from .code_samples import CodeSampleStyle
19
- from .constants import FLAKY_FAILURE_MESSAGE, NOT_SET
20
- from .generation import DataGenerationMethodInput, GenerationConfig
21
- from .exceptions import CheckFailed, OperationSchemaError, SkipTest, get_grouped_exception
22
- from .hooks import HookDispatcher, HookScope
23
- from .internal.result import Ok
24
- from .models import APIOperation
25
- from .schemas import BaseSchema
26
- from .types import Filter, GenericTest, NotSet
27
- from .utils import (
28
- GivenInput,
29
- fail_on_no_matches,
30
- get_given_args,
31
- get_given_kwargs,
32
- given_proxy,
33
- is_given_applied,
34
- merge_given_args,
35
- validate_given_args,
36
- )
37
-
38
-
39
- @dataclass
40
- class LazySchema:
41
- fixture_name: str
42
- base_url: str | None | NotSet = NOT_SET
43
- method: Filter | None = NOT_SET
44
- endpoint: Filter | None = NOT_SET
45
- tag: Filter | None = NOT_SET
46
- operation_id: Filter | None = NOT_SET
47
- app: Any = NOT_SET
48
- hooks: HookDispatcher = field(default_factory=lambda: HookDispatcher(scope=HookScope.SCHEMA))
49
- auth: AuthStorage = field(default_factory=AuthStorage)
50
- validate_schema: bool = True
51
- skip_deprecated_operations: bool = False
52
- data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET
53
- generation_config: GenerationConfig | NotSet = NOT_SET
54
- code_sample_style: CodeSampleStyle = CodeSampleStyle.default()
55
- rate_limiter: Limiter | None = None
56
- sanitize_output: bool = True
57
-
58
- def hook(self, hook: str | Callable) -> Callable:
59
- return self.hooks.register(hook)
60
-
61
- def parametrize(
62
- self,
63
- method: Filter | None = NOT_SET,
64
- endpoint: Filter | None = NOT_SET,
65
- tag: Filter | None = NOT_SET,
66
- operation_id: Filter | None = NOT_SET,
67
- validate_schema: bool | NotSet = NOT_SET,
68
- skip_deprecated_operations: bool | NotSet = NOT_SET,
69
- data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET,
70
- generation_config: GenerationConfig | NotSet = NOT_SET,
71
- code_sample_style: str | NotSet = NOT_SET,
72
- ) -> Callable:
73
- if method is NOT_SET:
74
- method = self.method
75
- if endpoint is NOT_SET:
76
- endpoint = self.endpoint
77
- if tag is NOT_SET:
78
- tag = self.tag
79
- if operation_id is NOT_SET:
80
- operation_id = self.operation_id
81
- if data_generation_methods is NOT_SET:
82
- data_generation_methods = self.data_generation_methods
83
- if generation_config is NOT_SET:
84
- generation_config = self.generation_config
85
- if isinstance(code_sample_style, str):
86
- _code_sample_style = CodeSampleStyle.from_str(code_sample_style)
87
- else:
88
- _code_sample_style = self.code_sample_style
89
-
90
- def wrapper(test: Callable) -> Callable:
91
- if is_given_applied(test):
92
- # The user wrapped the test function with `@schema.given`
93
- # These args & kwargs go as extra to the underlying test generator
94
- given_args = get_given_args(test)
95
- given_kwargs = get_given_kwargs(test)
96
- test_function = validate_given_args(test, given_args, given_kwargs)
97
- if test_function is not None:
98
- return test_function
99
- given_kwargs = merge_given_args(test, given_args, given_kwargs)
100
- del given_args
101
- else:
102
- given_kwargs = {}
103
-
104
- def wrapped_test(request: FixtureRequest) -> None:
105
- """The actual test, which is executed by pytest."""
106
- __tracebackhide__ = True
107
- if hasattr(wrapped_test, "_schemathesis_hooks"):
108
- test._schemathesis_hooks = wrapped_test._schemathesis_hooks # type: ignore
109
- schema = get_schema(
110
- request=request,
111
- name=self.fixture_name,
112
- base_url=self.base_url,
113
- method=method,
114
- endpoint=endpoint,
115
- tag=tag,
116
- operation_id=operation_id,
117
- hooks=self.hooks,
118
- auth=self.auth if self.auth.providers is not None else NOT_SET,
119
- test_function=test,
120
- validate_schema=validate_schema,
121
- skip_deprecated_operations=skip_deprecated_operations,
122
- data_generation_methods=data_generation_methods,
123
- generation_config=generation_config,
124
- code_sample_style=_code_sample_style,
125
- app=self.app,
126
- rate_limiter=self.rate_limiter,
127
- sanitize_output=self.sanitize_output,
128
- )
129
- fixtures = get_fixtures(test, request, given_kwargs)
130
- # Changing the node id is required for better reporting - the method and path will appear there
131
- node_id = request.node._nodeid
132
- settings = getattr(wrapped_test, "_hypothesis_internal_use_settings", None)
133
-
134
- as_strategy_kwargs: Callable[[APIOperation], dict[str, Any]] | None = None
135
-
136
- override = get_override_from_mark(test)
137
- if override is not None:
138
-
139
- def as_strategy_kwargs(_operation: APIOperation) -> dict[str, Any]:
140
- nonlocal override
141
-
142
- return {
143
- location: entry for location, entry in override.for_operation(_operation).items() if entry
144
- }
145
-
146
- tests = list(
147
- schema.get_all_tests(
148
- test,
149
- settings,
150
- hooks=self.hooks,
151
- as_strategy_kwargs=as_strategy_kwargs,
152
- _given_kwargs=given_kwargs,
153
- )
154
- )
155
- if not tests:
156
- fail_on_no_matches(node_id)
157
- request.session.testscollected += len(tests)
158
- suspend_capture_ctx = _get_capturemanager(request)
159
- subtests = SubTests(request.node.ihook, suspend_capture_ctx, request)
160
- for result in tests:
161
- if isinstance(result, Ok):
162
- operation, sub_test = result.ok()
163
- subtests.item._nodeid = _get_node_name(node_id, operation)
164
- run_subtest(operation, fixtures, sub_test, subtests)
165
- else:
166
- _schema_error(subtests, result.err(), node_id)
167
- subtests.item._nodeid = node_id
168
-
169
- wrapped_test = pytest.mark.usefixtures(self.fixture_name)(wrapped_test)
170
- _copy_marks(test, wrapped_test)
171
-
172
- # Needed to prevent a failure when settings are applied to the test function
173
- wrapped_test.is_hypothesis_test = True # type: ignore
174
- wrapped_test.hypothesis = HypothesisHandle(test, wrapped_test, given_kwargs) # type: ignore
175
-
176
- return wrapped_test
177
-
178
- return wrapper
179
-
180
- def given(self, *args: GivenInput, **kwargs: GivenInput) -> Callable:
181
- return given_proxy(*args, **kwargs)
182
-
183
- def override(
184
- self,
185
- *,
186
- query: dict[str, str] | None = None,
187
- headers: dict[str, str] | None = None,
188
- cookies: dict[str, str] | None = None,
189
- path_parameters: dict[str, str] | None = None,
190
- ) -> Callable[[GenericTest], GenericTest]:
191
- """Override Open API parameters with fixed values."""
192
-
193
- def _add_override(test: GenericTest) -> GenericTest:
194
- check_no_override_mark(test)
195
- override = CaseOverride(
196
- query=query or {}, headers=headers or {}, cookies=cookies or {}, path_parameters=path_parameters or {}
197
- )
198
- set_override_mark(test, override)
199
- return test
200
-
201
- return _add_override
202
-
203
-
204
- def _copy_marks(source: Callable, target: Callable) -> None:
205
- marks = getattr(source, "pytestmark", [])
206
- # Pytest adds this attribute in `usefixtures`
207
- target.pytestmark.extend(marks) # type: ignore
208
-
209
-
210
- def _get_capturemanager(request: FixtureRequest) -> Generator:
211
- capturemanager = request.node.config.pluginmanager.get_plugin("capturemanager")
212
- if capturemanager is not None:
213
- return capturemanager.global_and_fixture_disabled
214
- return nullcontext
215
-
216
-
217
- def _get_node_name(node_id: str, operation: APIOperation) -> str:
218
- """Make a test node name. For example: test_api[GET /users]."""
219
- return f"{node_id}[{operation.method.upper()} {operation.full_path}]"
220
-
221
-
222
- def _get_partial_node_name(node_id: str, **kwargs: Any) -> str:
223
- """Make a test node name for failing tests caused by schema errors."""
224
- name = node_id
225
- if "method" in kwargs:
226
- name += f"[{kwargs['method']} {kwargs['path']}]"
227
- else:
228
- name += f"[{kwargs['path']}]"
229
- return name
230
-
231
-
232
- def run_subtest(
233
- operation: APIOperation,
234
- fixtures: dict[str, Any],
235
- sub_test: Callable,
236
- subtests: SubTests,
237
- ) -> None:
238
- """Run the given subtest with pytest fixtures."""
239
- __tracebackhide__ = True
240
-
241
- # Deduplicate found checks in case of Hypothesis finding multiple of them
242
- failed_checks = {}
243
- exceptions = []
244
- inner_test = sub_test.hypothesis.inner_test # type: ignore
245
-
246
- @impersonate(inner_test) # type: ignore
247
- def collecting_wrapper(*args: Any, **kwargs: Any) -> None:
248
- __tracebackhide__ = True
249
- try:
250
- inner_test(*args, **kwargs)
251
- except CheckFailed as failed:
252
- failed_checks[failed.__class__] = failed
253
- raise failed
254
- except Exception as exception:
255
- # Deduplicate it later, as it is more costly than for `CheckFailed`
256
- exceptions.append(exception)
257
- raise
258
-
259
- def get_exception_class() -> type[CheckFailed]:
260
- return get_grouped_exception("Lazy", *failed_checks.values())
261
-
262
- sub_test.hypothesis.inner_test = collecting_wrapper # type: ignore
263
-
264
- with subtests.test(verbose_name=operation.verbose_name):
265
- try:
266
- sub_test(**fixtures)
267
- except SkipTest as exc:
268
- pytest.skip(exc.args[0])
269
- except (MultipleFailures, CheckFailed) as exc:
270
- # Hypothesis doesn't report the underlying failures in these circumstances, hence we display them manually
271
- exc_class = get_exception_class()
272
- failures = "".join(f"{SEPARATOR} {failure.args[0]}" for failure in failed_checks.values())
273
- unique_exceptions = {get_interesting_origin(exception): exception for exception in exceptions}
274
- total_problems = len(failed_checks) + len(unique_exceptions)
275
- if total_problems == 1:
276
- raise
277
- message = f"Schemathesis found {total_problems} distinct sets of failures.{failures}"
278
- for exception in unique_exceptions.values():
279
- # Non-check exceptions
280
- message += f"{SEPARATOR}\n\n"
281
- tb = get_trimmed_traceback(exception)
282
- message += format_exception(exception, tb)
283
- raise exc_class(message, causes=tuple(failed_checks.values())).with_traceback(exc.__traceback__) from None
284
- except Flaky as exc:
285
- exc_class = get_exception_class()
286
- failure = next(iter(failed_checks.values()))
287
- message = f"{FLAKY_FAILURE_MESSAGE}{failure}"
288
- # The outer frame is the one for user's test function, take it as the root one
289
- traceback = exc.__traceback__.tb_next
290
- # The next one comes from Hypothesis internals - remove it
291
- traceback.tb_next = None
292
- raise exc_class(message, causes=tuple(failed_checks.values())).with_traceback(traceback) from None
293
-
294
-
295
- SEPARATOR = "\n===================="
296
-
297
-
298
- def _schema_error(subtests: SubTests, error: OperationSchemaError, node_id: str) -> None:
299
- """Run a failing test, that will show the underlying problem."""
300
- sub_test = error.as_failing_test_function()
301
- # `full_path` is always available in this case
302
- kwargs = {"path": error.full_path}
303
- if error.method:
304
- kwargs["method"] = error.method.upper()
305
- subtests.item._nodeid = _get_partial_node_name(node_id, **kwargs)
306
- __tracebackhide__ = True
307
- with subtests.test(**kwargs):
308
- sub_test()
309
-
310
-
311
- def get_schema(
312
- *,
313
- request: FixtureRequest,
314
- name: str,
315
- base_url: str | None | NotSet = None,
316
- method: Filter | None = None,
317
- endpoint: Filter | None = None,
318
- tag: Filter | None = None,
319
- operation_id: Filter | None = None,
320
- app: Any = None,
321
- test_function: GenericTest,
322
- hooks: HookDispatcher,
323
- auth: AuthStorage | NotSet,
324
- validate_schema: bool | NotSet = NOT_SET,
325
- skip_deprecated_operations: bool | NotSet = NOT_SET,
326
- data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET,
327
- generation_config: GenerationConfig | NotSet = NOT_SET,
328
- code_sample_style: CodeSampleStyle,
329
- rate_limiter: Limiter | None,
330
- sanitize_output: bool,
331
- ) -> BaseSchema:
332
- """Loads a schema from the fixture."""
333
- schema = request.getfixturevalue(name)
334
- if not isinstance(schema, BaseSchema):
335
- raise ValueError(f"The given schema must be an instance of BaseSchema, got: {type(schema)}")
336
- return schema.clone(
337
- base_url=base_url,
338
- method=method,
339
- endpoint=endpoint,
340
- tag=tag,
341
- operation_id=operation_id,
342
- app=app,
343
- test_function=test_function,
344
- hooks=schema.hooks.merge(hooks),
345
- auth=auth,
346
- validate_schema=validate_schema,
347
- skip_deprecated_operations=skip_deprecated_operations,
348
- data_generation_methods=data_generation_methods,
349
- generation_config=generation_config,
350
- code_sample_style=code_sample_style,
351
- rate_limiter=rate_limiter,
352
- sanitize_output=sanitize_output,
353
- )
354
-
355
-
356
- def get_fixtures(func: Callable, request: FixtureRequest, given_kwargs: dict[str, Any]) -> dict[str, Any]:
357
- """Load fixtures, needed for the test function."""
358
- sig = signature(func)
359
- return {
360
- name: request.getfixturevalue(name) for name in sig.parameters if name != "case" and name not in given_kwargs
361
- }
schemathesis/loaders.py DELETED
@@ -1,120 +0,0 @@
1
- from __future__ import annotations
2
- import re
3
- import sys
4
- from functools import lru_cache
5
- from typing import Callable, TypeVar, TYPE_CHECKING, TextIO, Any, BinaryIO
6
-
7
- from .exceptions import SchemaError, SchemaErrorType, extract_requests_exception_details
8
-
9
- if TYPE_CHECKING:
10
- from .transports.responses import GenericResponse
11
- import yaml
12
-
13
- R = TypeVar("R", bound="GenericResponse")
14
-
15
-
16
- def load_schema_from_url(loader: Callable[[], R]) -> R:
17
- import requests
18
-
19
- try:
20
- response = loader()
21
- except requests.RequestException as exc:
22
- url = exc.request.url if exc.request is not None else None
23
- if isinstance(exc, requests.exceptions.SSLError):
24
- type_ = SchemaErrorType.CONNECTION_SSL
25
- elif isinstance(exc, requests.exceptions.ConnectionError):
26
- type_ = SchemaErrorType.CONNECTION_OTHER
27
- else:
28
- type_ = SchemaErrorType.NETWORK_OTHER
29
- message, extras = extract_requests_exception_details(exc)
30
- raise SchemaError(message=message, type=type_, url=url, response=exc.response, extras=extras) from exc
31
- _raise_for_status(response)
32
- return response
33
-
34
-
35
- def _raise_for_status(response: GenericResponse) -> None:
36
- from .transports.responses import get_reason
37
-
38
- status_code = response.status_code
39
- reason = get_reason(status_code)
40
- if status_code >= 500:
41
- message = f"Failed to load schema due to server error (HTTP {status_code} {reason})"
42
- type_ = SchemaErrorType.HTTP_SERVER_ERROR
43
- elif status_code >= 400:
44
- message = f"Failed to load schema due to client error (HTTP {status_code} {reason})"
45
- if status_code == 403:
46
- type_ = SchemaErrorType.HTTP_FORBIDDEN
47
- elif status_code == 404:
48
- type_ = SchemaErrorType.HTTP_NOT_FOUND
49
- else:
50
- type_ = SchemaErrorType.HTTP_CLIENT_ERROR
51
- else:
52
- return None
53
- raise SchemaError(message=message, type=type_, url=response.request.url, response=response, extras=[])
54
-
55
-
56
- def load_app(path: str) -> Any:
57
- """Import an application from a string."""
58
- path, name = (re.split(r":(?![\\/])", path, maxsplit=1) + [""])[:2]
59
- __import__(path)
60
- # accessing the module from sys.modules returns a proper module, while `__import__`
61
- # may return a parent module (system dependent)
62
- module = sys.modules[path]
63
- return getattr(module, name)
64
-
65
-
66
- @lru_cache
67
- def get_yaml_loader() -> type[yaml.SafeLoader]:
68
- """Create a YAML loader, that doesn't parse specific tokens into Python objects."""
69
- import yaml
70
-
71
- try:
72
- from yaml import CSafeLoader as SafeLoader
73
- except ImportError:
74
- from yaml import SafeLoader # type: ignore
75
-
76
- cls: type[yaml.SafeLoader] = type("YAMLLoader", (SafeLoader,), {})
77
- cls.yaml_implicit_resolvers = {
78
- key: [(tag, regexp) for tag, regexp in mapping if tag != "tag:yaml.org,2002:timestamp"]
79
- for key, mapping in cls.yaml_implicit_resolvers.copy().items()
80
- }
81
-
82
- # Fix pyyaml scientific notation parse bug
83
- # See PR: https://github.com/yaml/pyyaml/pull/174 for upstream fix
84
- cls.add_implicit_resolver( # type: ignore
85
- "tag:yaml.org,2002:float",
86
- re.compile(
87
- r"""^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+]?[0-9]+)?
88
- |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
89
- |\.[0-9_]+(?:[eE][-+]?[0-9]+)?
90
- |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*
91
- |[-+]?\.(?:inf|Inf|INF)
92
- |\.(?:nan|NaN|NAN))$""",
93
- re.X,
94
- ),
95
- list("-+0123456789."),
96
- )
97
-
98
- def construct_mapping(self: SafeLoader, node: yaml.Node, deep: bool = False) -> dict[str, Any]:
99
- if isinstance(node, yaml.MappingNode):
100
- self.flatten_mapping(node) # type: ignore
101
- mapping = {}
102
- for key_node, value_node in node.value:
103
- # If the key has a tag different from `str` - use its string value.
104
- # With this change all integer keys or YAML 1.1 boolean-ish values like "on" / "off" will not be cast to
105
- # a different type
106
- if key_node.tag != "tag:yaml.org,2002:str":
107
- key = key_node.value
108
- else:
109
- key = self.construct_object(key_node, deep) # type: ignore
110
- mapping[key] = self.construct_object(value_node, deep) # type: ignore
111
- return mapping
112
-
113
- cls.construct_mapping = construct_mapping # type: ignore
114
- return cls
115
-
116
-
117
- def load_yaml(stream: str | bytes | TextIO | BinaryIO) -> Any:
118
- import yaml
119
-
120
- return yaml.load(stream, get_yaml_loader())