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.
- schemathesis/__init__.py +27 -65
- schemathesis/auths.py +102 -82
- schemathesis/checks.py +126 -46
- schemathesis/cli/__init__.py +11 -1760
- schemathesis/cli/__main__.py +4 -0
- schemathesis/cli/commands/__init__.py +37 -0
- schemathesis/cli/commands/run/__init__.py +662 -0
- schemathesis/cli/commands/run/checks.py +80 -0
- schemathesis/cli/commands/run/context.py +117 -0
- schemathesis/cli/commands/run/events.py +35 -0
- schemathesis/cli/commands/run/executor.py +138 -0
- schemathesis/cli/commands/run/filters.py +194 -0
- schemathesis/cli/commands/run/handlers/__init__.py +46 -0
- schemathesis/cli/commands/run/handlers/base.py +18 -0
- schemathesis/cli/commands/run/handlers/cassettes.py +494 -0
- schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
- schemathesis/cli/commands/run/handlers/output.py +746 -0
- schemathesis/cli/commands/run/hypothesis.py +105 -0
- schemathesis/cli/commands/run/loaders.py +129 -0
- schemathesis/cli/{callbacks.py → commands/run/validation.py} +103 -174
- schemathesis/cli/constants.py +5 -52
- schemathesis/cli/core.py +17 -0
- schemathesis/cli/ext/fs.py +14 -0
- schemathesis/cli/ext/groups.py +55 -0
- schemathesis/cli/{options.py → ext/options.py} +39 -10
- schemathesis/cli/hooks.py +36 -0
- schemathesis/contrib/__init__.py +1 -3
- schemathesis/contrib/openapi/__init__.py +1 -3
- schemathesis/contrib/openapi/fill_missing_examples.py +3 -5
- schemathesis/core/__init__.py +58 -0
- schemathesis/core/compat.py +25 -0
- schemathesis/core/control.py +2 -0
- schemathesis/core/curl.py +58 -0
- schemathesis/core/deserialization.py +65 -0
- schemathesis/core/errors.py +370 -0
- schemathesis/core/failures.py +285 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/{_lazy_import.py → core/lazy_import.py} +1 -0
- schemathesis/core/loaders.py +104 -0
- schemathesis/core/marks.py +66 -0
- schemathesis/{transports/content_types.py → core/media_types.py} +17 -13
- schemathesis/core/output/__init__.py +69 -0
- schemathesis/core/output/sanitization.py +197 -0
- schemathesis/core/rate_limit.py +60 -0
- schemathesis/core/registries.py +31 -0
- schemathesis/{internal → core}/result.py +1 -1
- schemathesis/core/transforms.py +113 -0
- schemathesis/core/transport.py +108 -0
- schemathesis/core/validation.py +38 -0
- schemathesis/core/version.py +7 -0
- schemathesis/engine/__init__.py +30 -0
- schemathesis/engine/config.py +59 -0
- schemathesis/engine/context.py +119 -0
- schemathesis/engine/control.py +36 -0
- schemathesis/engine/core.py +157 -0
- schemathesis/engine/errors.py +394 -0
- schemathesis/engine/events.py +337 -0
- schemathesis/engine/phases/__init__.py +66 -0
- schemathesis/{runner → engine/phases}/probes.py +50 -67
- schemathesis/engine/phases/stateful/__init__.py +65 -0
- schemathesis/engine/phases/stateful/_executor.py +326 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +174 -0
- schemathesis/engine/phases/unit/_executor.py +321 -0
- schemathesis/engine/phases/unit/_pool.py +74 -0
- schemathesis/engine/recorder.py +241 -0
- schemathesis/errors.py +31 -0
- schemathesis/experimental/__init__.py +18 -14
- schemathesis/filters.py +103 -14
- schemathesis/generation/__init__.py +21 -37
- schemathesis/generation/case.py +190 -0
- schemathesis/generation/coverage.py +931 -0
- schemathesis/generation/hypothesis/__init__.py +30 -0
- schemathesis/generation/hypothesis/builder.py +585 -0
- schemathesis/generation/hypothesis/examples.py +50 -0
- schemathesis/generation/hypothesis/given.py +66 -0
- schemathesis/generation/hypothesis/reporting.py +14 -0
- schemathesis/generation/hypothesis/strategies.py +16 -0
- schemathesis/generation/meta.py +115 -0
- schemathesis/generation/modes.py +28 -0
- schemathesis/generation/overrides.py +96 -0
- schemathesis/generation/stateful/__init__.py +20 -0
- schemathesis/{stateful → generation/stateful}/state_machine.py +68 -81
- schemathesis/generation/targets.py +69 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +115 -0
- schemathesis/graphql/loaders.py +131 -0
- schemathesis/hooks.py +99 -67
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +412 -0
- schemathesis/openapi/generation/__init__.py +0 -0
- schemathesis/openapi/generation/filters.py +63 -0
- schemathesis/openapi/loaders.py +178 -0
- schemathesis/pytest/__init__.py +5 -0
- schemathesis/pytest/control_flow.py +7 -0
- schemathesis/pytest/lazy.py +273 -0
- schemathesis/pytest/loaders.py +12 -0
- schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +106 -127
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +537 -261
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/_cache.py +25 -0
- schemathesis/specs/graphql/nodes.py +1 -0
- schemathesis/specs/graphql/scalars.py +7 -5
- schemathesis/specs/graphql/schemas.py +215 -187
- schemathesis/specs/graphql/validation.py +11 -18
- schemathesis/specs/openapi/__init__.py +7 -1
- schemathesis/specs/openapi/_cache.py +122 -0
- schemathesis/specs/openapi/_hypothesis.py +146 -165
- schemathesis/specs/openapi/checks.py +565 -67
- schemathesis/specs/openapi/converter.py +33 -6
- schemathesis/specs/openapi/definitions.py +11 -18
- schemathesis/specs/openapi/examples.py +139 -23
- schemathesis/specs/openapi/expressions/__init__.py +37 -2
- schemathesis/specs/openapi/expressions/context.py +4 -6
- schemathesis/specs/openapi/expressions/extractors.py +23 -0
- schemathesis/specs/openapi/expressions/lexer.py +20 -18
- schemathesis/specs/openapi/expressions/nodes.py +38 -14
- schemathesis/specs/openapi/expressions/parser.py +26 -5
- schemathesis/specs/openapi/formats.py +45 -0
- schemathesis/specs/openapi/links.py +65 -165
- schemathesis/specs/openapi/media_types.py +32 -0
- schemathesis/specs/openapi/negative/__init__.py +7 -3
- schemathesis/specs/openapi/negative/mutations.py +24 -8
- schemathesis/specs/openapi/parameters.py +46 -30
- schemathesis/specs/openapi/patterns.py +137 -0
- schemathesis/specs/openapi/references.py +47 -57
- schemathesis/specs/openapi/schemas.py +478 -369
- schemathesis/specs/openapi/security.py +25 -7
- schemathesis/specs/openapi/serialization.py +11 -6
- schemathesis/specs/openapi/stateful/__init__.py +185 -73
- schemathesis/specs/openapi/utils.py +6 -1
- schemathesis/transport/__init__.py +104 -0
- schemathesis/transport/asgi.py +26 -0
- schemathesis/transport/prepare.py +99 -0
- schemathesis/transport/requests.py +221 -0
- schemathesis/{_xml.py → transport/serialization.py} +143 -28
- schemathesis/transport/wsgi.py +165 -0
- schemathesis-4.0.0a1.dist-info/METADATA +297 -0
- schemathesis-4.0.0a1.dist-info/RECORD +152 -0
- {schemathesis-3.25.6.dist-info → schemathesis-4.0.0a1.dist-info}/WHEEL +1 -1
- {schemathesis-3.25.6.dist-info → schemathesis-4.0.0a1.dist-info}/entry_points.txt +1 -1
- schemathesis/_compat.py +0 -74
- schemathesis/_dependency_versions.py +0 -17
- schemathesis/_hypothesis.py +0 -246
- schemathesis/_override.py +0 -49
- schemathesis/cli/cassettes.py +0 -375
- schemathesis/cli/context.py +0 -58
- schemathesis/cli/debug.py +0 -26
- schemathesis/cli/handlers.py +0 -16
- schemathesis/cli/junitxml.py +0 -43
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -790
- schemathesis/cli/output/short.py +0 -44
- schemathesis/cli/sanitization.py +0 -20
- schemathesis/code_samples.py +0 -149
- schemathesis/constants.py +0 -55
- schemathesis/contrib/openapi/formats/__init__.py +0 -9
- schemathesis/contrib/openapi/formats/uuid.py +0 -15
- schemathesis/contrib/unique_data.py +0 -41
- schemathesis/exceptions.py +0 -560
- schemathesis/extra/_aiohttp.py +0 -27
- schemathesis/extra/_flask.py +0 -10
- schemathesis/extra/_server.py +0 -17
- schemathesis/failures.py +0 -209
- schemathesis/fixups/__init__.py +0 -36
- schemathesis/fixups/fast_api.py +0 -41
- schemathesis/fixups/utf8_bom.py +0 -29
- schemathesis/graphql.py +0 -4
- schemathesis/internal/__init__.py +0 -7
- schemathesis/internal/copy.py +0 -13
- schemathesis/internal/datetime.py +0 -5
- schemathesis/internal/deprecation.py +0 -34
- schemathesis/internal/jsonschema.py +0 -35
- schemathesis/internal/transformation.py +0 -15
- schemathesis/internal/validation.py +0 -34
- schemathesis/lazy.py +0 -361
- schemathesis/loaders.py +0 -120
- schemathesis/models.py +0 -1234
- schemathesis/parameters.py +0 -86
- schemathesis/runner/__init__.py +0 -570
- schemathesis/runner/events.py +0 -329
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/core.py +0 -1035
- schemathesis/runner/impl/solo.py +0 -90
- schemathesis/runner/impl/threadpool.py +0 -400
- schemathesis/runner/serialization.py +0 -411
- schemathesis/sanitization.py +0 -248
- schemathesis/serializers.py +0 -323
- schemathesis/service/__init__.py +0 -18
- schemathesis/service/auth.py +0 -11
- schemathesis/service/ci.py +0 -201
- schemathesis/service/client.py +0 -100
- schemathesis/service/constants.py +0 -38
- schemathesis/service/events.py +0 -57
- schemathesis/service/hosts.py +0 -107
- schemathesis/service/metadata.py +0 -46
- schemathesis/service/models.py +0 -49
- schemathesis/service/report.py +0 -255
- schemathesis/service/serialization.py +0 -199
- schemathesis/service/usage.py +0 -65
- schemathesis/specs/graphql/loaders.py +0 -344
- schemathesis/specs/openapi/filters.py +0 -49
- schemathesis/specs/openapi/loaders.py +0 -667
- schemathesis/specs/openapi/stateful/links.py +0 -92
- schemathesis/specs/openapi/validation.py +0 -25
- schemathesis/stateful/__init__.py +0 -133
- schemathesis/targets.py +0 -45
- schemathesis/throttling.py +0 -41
- schemathesis/transports/__init__.py +0 -5
- schemathesis/transports/auth.py +0 -15
- schemathesis/transports/headers.py +0 -35
- schemathesis/transports/responses.py +0 -52
- schemathesis/types.py +0 -35
- schemathesis/utils.py +0 -169
- schemathesis-3.25.6.dist-info/METADATA +0 -356
- schemathesis-3.25.6.dist-info/RECORD +0 -134
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
- {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())
|