schemathesis 3.39.7__py3-none-any.whl → 4.0.0a2__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 +26 -68
- schemathesis/checks.py +130 -60
- schemathesis/cli/__init__.py +5 -2105
- 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 +30 -0
- schemathesis/cli/commands/run/executor.py +141 -0
- schemathesis/cli/commands/run/filters.py +202 -0
- schemathesis/cli/commands/run/handlers/__init__.py +46 -0
- schemathesis/cli/commands/run/handlers/base.py +18 -0
- schemathesis/cli/{cassettes.py → commands/run/handlers/cassettes.py} +178 -247
- schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
- schemathesis/cli/commands/run/handlers/output.py +1368 -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} +59 -175
- schemathesis/cli/constants.py +5 -58
- 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} +37 -16
- 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 -7
- 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 +315 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/core/loaders.py +104 -0
- schemathesis/core/marks.py +66 -0
- schemathesis/{transports/content_types.py → core/media_types.py} +14 -12
- schemathesis/{internal/output.py → core/output/__init__.py} +1 -0
- schemathesis/core/output/sanitization.py +197 -0
- schemathesis/{throttling.py → core/rate_limit.py} +16 -17
- schemathesis/core/registries.py +31 -0
- 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 +243 -0
- schemathesis/engine/phases/__init__.py +66 -0
- schemathesis/{runner → engine/phases}/probes.py +49 -68
- schemathesis/engine/phases/stateful/__init__.py +66 -0
- schemathesis/engine/phases/stateful/_executor.py +301 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +175 -0
- schemathesis/engine/phases/unit/_executor.py +322 -0
- schemathesis/engine/phases/unit/_pool.py +74 -0
- schemathesis/engine/recorder.py +246 -0
- schemathesis/errors.py +31 -0
- schemathesis/experimental/__init__.py +9 -40
- schemathesis/filters.py +7 -95
- schemathesis/generation/__init__.py +3 -3
- schemathesis/generation/case.py +190 -0
- schemathesis/generation/coverage.py +22 -22
- schemathesis/{_patches.py → generation/hypothesis/__init__.py} +15 -6
- schemathesis/generation/hypothesis/builder.py +585 -0
- schemathesis/generation/{_hypothesis.py → hypothesis/examples.py} +2 -11
- 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 +84 -109
- schemathesis/generation/targets.py +69 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +109 -0
- schemathesis/graphql/loaders.py +131 -0
- schemathesis/hooks.py +17 -62
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +387 -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} +94 -107
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +456 -228
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/_cache.py +1 -2
- schemathesis/specs/graphql/scalars.py +5 -3
- schemathesis/specs/graphql/schemas.py +122 -123
- schemathesis/specs/graphql/validation.py +11 -17
- schemathesis/specs/openapi/__init__.py +6 -1
- schemathesis/specs/openapi/_cache.py +1 -2
- schemathesis/specs/openapi/_hypothesis.py +97 -134
- schemathesis/specs/openapi/checks.py +238 -219
- schemathesis/specs/openapi/converter.py +4 -4
- schemathesis/specs/openapi/definitions.py +1 -1
- schemathesis/specs/openapi/examples.py +22 -20
- schemathesis/specs/openapi/expressions/__init__.py +11 -15
- schemathesis/specs/openapi/expressions/extractors.py +1 -4
- schemathesis/specs/openapi/expressions/nodes.py +33 -32
- schemathesis/specs/openapi/formats.py +3 -2
- schemathesis/specs/openapi/links.py +123 -299
- schemathesis/specs/openapi/media_types.py +10 -12
- schemathesis/specs/openapi/negative/__init__.py +2 -1
- schemathesis/specs/openapi/negative/mutations.py +3 -2
- schemathesis/specs/openapi/parameters.py +8 -6
- schemathesis/specs/openapi/patterns.py +1 -1
- schemathesis/specs/openapi/references.py +11 -51
- schemathesis/specs/openapi/schemas.py +177 -191
- schemathesis/specs/openapi/security.py +1 -1
- schemathesis/specs/openapi/serialization.py +10 -6
- schemathesis/specs/openapi/stateful/__init__.py +97 -91
- 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} +69 -7
- schemathesis/transport/wsgi.py +165 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/METADATA +18 -14
- schemathesis-4.0.0a2.dist-info/RECORD +151 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/entry_points.txt +1 -1
- schemathesis/_compat.py +0 -74
- schemathesis/_dependency_versions.py +0 -19
- schemathesis/_hypothesis.py +0 -559
- schemathesis/_override.py +0 -50
- schemathesis/_rate_limiter.py +0 -7
- schemathesis/cli/context.py +0 -75
- schemathesis/cli/debug.py +0 -27
- schemathesis/cli/handlers.py +0 -19
- schemathesis/cli/junitxml.py +0 -124
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -936
- schemathesis/cli/output/short.py +0 -59
- schemathesis/cli/reporting.py +0 -79
- schemathesis/cli/sanitization.py +0 -26
- schemathesis/code_samples.py +0 -151
- schemathesis/constants.py +0 -56
- schemathesis/contrib/openapi/formats/__init__.py +0 -9
- schemathesis/contrib/openapi/formats/uuid.py +0 -16
- schemathesis/contrib/unique_data.py +0 -41
- schemathesis/exceptions.py +0 -571
- schemathesis/extra/_aiohttp.py +0 -28
- schemathesis/extra/_flask.py +0 -13
- schemathesis/extra/_server.py +0 -18
- schemathesis/failures.py +0 -277
- schemathesis/fixups/__init__.py +0 -37
- schemathesis/fixups/fast_api.py +0 -41
- schemathesis/fixups/utf8_bom.py +0 -28
- schemathesis/generation/_methods.py +0 -44
- schemathesis/graphql.py +0 -3
- schemathesis/internal/__init__.py +0 -7
- schemathesis/internal/checks.py +0 -84
- schemathesis/internal/copy.py +0 -32
- schemathesis/internal/datetime.py +0 -5
- schemathesis/internal/deprecation.py +0 -38
- schemathesis/internal/diff.py +0 -15
- schemathesis/internal/extensions.py +0 -27
- schemathesis/internal/jsonschema.py +0 -36
- schemathesis/internal/transformation.py +0 -26
- schemathesis/internal/validation.py +0 -34
- schemathesis/lazy.py +0 -474
- schemathesis/loaders.py +0 -122
- schemathesis/models.py +0 -1341
- schemathesis/parameters.py +0 -90
- schemathesis/runner/__init__.py +0 -605
- schemathesis/runner/events.py +0 -389
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/context.py +0 -104
- schemathesis/runner/impl/core.py +0 -1246
- schemathesis/runner/impl/solo.py +0 -80
- schemathesis/runner/impl/threadpool.py +0 -391
- schemathesis/runner/serialization.py +0 -544
- schemathesis/sanitization.py +0 -252
- schemathesis/serializers.py +0 -328
- schemathesis/service/__init__.py +0 -18
- schemathesis/service/auth.py +0 -11
- schemathesis/service/ci.py +0 -202
- schemathesis/service/client.py +0 -133
- schemathesis/service/constants.py +0 -38
- schemathesis/service/events.py +0 -61
- schemathesis/service/extensions.py +0 -224
- schemathesis/service/hosts.py +0 -111
- schemathesis/service/metadata.py +0 -71
- schemathesis/service/models.py +0 -258
- schemathesis/service/report.py +0 -255
- schemathesis/service/serialization.py +0 -173
- schemathesis/service/usage.py +0 -66
- schemathesis/specs/graphql/loaders.py +0 -364
- schemathesis/specs/openapi/expressions/context.py +0 -16
- schemathesis/specs/openapi/loaders.py +0 -708
- schemathesis/specs/openapi/stateful/statistic.py +0 -198
- schemathesis/specs/openapi/stateful/types.py +0 -14
- schemathesis/specs/openapi/validation.py +0 -26
- schemathesis/stateful/__init__.py +0 -147
- schemathesis/stateful/config.py +0 -97
- schemathesis/stateful/context.py +0 -135
- schemathesis/stateful/events.py +0 -274
- schemathesis/stateful/runner.py +0 -309
- schemathesis/stateful/sink.py +0 -68
- schemathesis/stateful/statistic.py +0 -22
- schemathesis/stateful/validation.py +0 -100
- schemathesis/targets.py +0 -77
- schemathesis/transports/__init__.py +0 -359
- schemathesis/transports/asgi.py +0 -7
- schemathesis/transports/auth.py +0 -38
- schemathesis/transports/headers.py +0 -36
- schemathesis/transports/responses.py +0 -57
- schemathesis/types.py +0 -44
- schemathesis/utils.py +0 -164
- schemathesis-3.39.7.dist-info/RECORD +0 -160
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
- /schemathesis/{_lazy_import.py → core/lazy_import.py} +0 -0
- /schemathesis/{internal → core}/result.py +0 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/WHEEL +0 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/licenses/LICENSE +0 -0
schemathesis/__init__.py
CHANGED
@@ -1,82 +1,44 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from
|
4
|
-
|
5
|
-
from . import
|
6
|
-
from .
|
7
|
-
from .
|
8
|
-
from .generation import
|
9
|
-
from .
|
10
|
-
from .
|
3
|
+
from schemathesis import auths, contrib, engine, errors, experimental, graphql, hooks, openapi, pytest, python
|
4
|
+
from schemathesis.checks import CheckContext, CheckFunction, check
|
5
|
+
from schemathesis.core.output import OutputConfig, sanitization
|
6
|
+
from schemathesis.core.transport import Response
|
7
|
+
from schemathesis.core.version import SCHEMATHESIS_VERSION
|
8
|
+
from schemathesis.generation import GenerationConfig, GenerationMode, HeaderConfig
|
9
|
+
from schemathesis.generation.case import Case
|
10
|
+
from schemathesis.generation.targets import TargetContext, TargetFunction, target
|
11
11
|
|
12
12
|
__version__ = SCHEMATHESIS_VERSION
|
13
13
|
|
14
|
-
# Default loaders
|
15
|
-
from_aiohttp = openapi.from_aiohttp
|
16
|
-
from_asgi = openapi.from_asgi
|
17
|
-
from_dict = openapi.from_dict
|
18
|
-
from_file = openapi.from_file
|
19
|
-
from_path = openapi.from_path
|
20
|
-
from_pytest_fixture = openapi.from_pytest_fixture
|
21
|
-
from_uri = openapi.from_uri
|
22
|
-
from_wsgi = openapi.from_wsgi
|
23
|
-
|
24
14
|
# Public API
|
25
15
|
auth = auths.GLOBAL_AUTH_STORAGE
|
26
|
-
check = checks.register
|
27
16
|
hook = hooks.register
|
28
|
-
serializer = serializers.register
|
29
|
-
target = targets.register
|
30
|
-
|
31
|
-
# Backward compatibility
|
32
|
-
register_check = checks.register
|
33
|
-
register_target = targets.register
|
34
|
-
register_string_format = openapi.format
|
35
17
|
|
36
18
|
__all__ = [
|
37
|
-
"auths",
|
38
|
-
"checks",
|
39
|
-
"experimental",
|
40
|
-
"contrib",
|
41
|
-
"fixups",
|
42
|
-
"graphql",
|
43
|
-
"hooks",
|
44
|
-
"runner",
|
45
|
-
"serializers",
|
46
|
-
"targets",
|
47
|
-
"DataGenerationMethod",
|
48
|
-
"SCHEMATHESIS_VERSION",
|
49
19
|
"Case",
|
50
|
-
"
|
20
|
+
"CheckContext",
|
21
|
+
"CheckFunction",
|
22
|
+
"GenerationMode",
|
23
|
+
"GenerationConfig",
|
24
|
+
"HeaderConfig",
|
25
|
+
"OutputConfig",
|
26
|
+
"Response",
|
27
|
+
"TargetContext",
|
28
|
+
"TargetFunction",
|
51
29
|
"__version__",
|
52
|
-
"from_aiohttp",
|
53
|
-
"from_asgi",
|
54
|
-
"from_dict",
|
55
|
-
"from_file",
|
56
|
-
"from_path",
|
57
|
-
"from_pytest_fixture",
|
58
|
-
"from_uri",
|
59
|
-
"from_wsgi",
|
60
30
|
"auth",
|
61
31
|
"check",
|
32
|
+
"contrib",
|
33
|
+
"engine",
|
34
|
+
"errors",
|
35
|
+
"experimental",
|
36
|
+
"graphql",
|
62
37
|
"hook",
|
63
|
-
"
|
38
|
+
"hooks",
|
39
|
+
"openapi",
|
40
|
+
"pytest",
|
41
|
+
"python",
|
42
|
+
"sanitization",
|
64
43
|
"target",
|
65
|
-
"register_check",
|
66
|
-
"register_target",
|
67
|
-
"register_string_format",
|
68
44
|
]
|
69
|
-
|
70
|
-
|
71
|
-
def _load_generic_response() -> Any:
|
72
|
-
from .transports.responses import GenericResponse
|
73
|
-
|
74
|
-
return GenericResponse
|
75
|
-
|
76
|
-
|
77
|
-
_imports = {"GenericResponse": _load_generic_response}
|
78
|
-
|
79
|
-
|
80
|
-
def __getattr__(name: str) -> Any:
|
81
|
-
# Some modules are relatively heavy, hence load them lazily to improve startup time for CLI
|
82
|
-
return lazy_import(__name__, name, _imports, globals())
|
schemathesis/auths.py
CHANGED
@@ -2,10 +2,8 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
import inspect
|
6
5
|
import threading
|
7
6
|
import time
|
8
|
-
import warnings
|
9
7
|
from dataclasses import dataclass, field
|
10
8
|
from typing import (
|
11
9
|
TYPE_CHECKING,
|
@@ -19,17 +17,18 @@ from typing import (
|
|
19
17
|
runtime_checkable,
|
20
18
|
)
|
21
19
|
|
22
|
-
from .
|
23
|
-
from .
|
20
|
+
from schemathesis.core.errors import IncorrectUsage
|
21
|
+
from schemathesis.core.marks import Mark
|
22
|
+
from schemathesis.filters import FilterSet, FilterValue, MatcherFunc, attach_filter_chain
|
23
|
+
from schemathesis.generation.case import Case
|
24
24
|
|
25
25
|
if TYPE_CHECKING:
|
26
26
|
import requests.auth
|
27
27
|
|
28
|
-
from .
|
29
|
-
from .types import GenericTest
|
28
|
+
from schemathesis.schemas import APIOperation
|
30
29
|
|
31
30
|
DEFAULT_REFRESH_INTERVAL = 300
|
32
|
-
|
31
|
+
AuthStorageMark = Mark["AuthStorage"](attr_name="auth_storage")
|
33
32
|
Auth = TypeVar("Auth")
|
34
33
|
|
35
34
|
|
@@ -111,7 +110,7 @@ class CachingAuthProvider(Generic[Auth]):
|
|
111
110
|
# Another thread updated the cache
|
112
111
|
return cache_entry.data
|
113
112
|
# We know that optional auth is possible only inside a higher-level wrapper
|
114
|
-
data: Auth =
|
113
|
+
data: Auth = self.provider.get(case, context) # type: ignore[assignment]
|
115
114
|
self._set_cache_entry(data, case, context)
|
116
115
|
return data
|
117
116
|
return cache_entry.data
|
@@ -152,8 +151,7 @@ class KeyedCachingAuthProvider(CachingAuthProvider[Auth]):
|
|
152
151
|
class FilterableRegisterAuth(Protocol):
|
153
152
|
"""Protocol that adds filters to the return value of `register`."""
|
154
153
|
|
155
|
-
def __call__(self, provider_class: type[AuthProvider]) -> type[AuthProvider]:
|
156
|
-
pass
|
154
|
+
def __call__(self, provider_class: type[AuthProvider]) -> type[AuthProvider]: ...
|
157
155
|
|
158
156
|
def apply_to(
|
159
157
|
self,
|
@@ -165,8 +163,7 @@ class FilterableRegisterAuth(Protocol):
|
|
165
163
|
method_regex: str | None = None,
|
166
164
|
path: FilterValue | None = None,
|
167
165
|
path_regex: str | None = None,
|
168
|
-
) -> FilterableRegisterAuth:
|
169
|
-
pass
|
166
|
+
) -> FilterableRegisterAuth: ...
|
170
167
|
|
171
168
|
def skip_for(
|
172
169
|
self,
|
@@ -178,15 +175,13 @@ class FilterableRegisterAuth(Protocol):
|
|
178
175
|
method_regex: str | None = None,
|
179
176
|
path: FilterValue | None = None,
|
180
177
|
path_regex: str | None = None,
|
181
|
-
) -> FilterableRegisterAuth:
|
182
|
-
pass
|
178
|
+
) -> FilterableRegisterAuth: ...
|
183
179
|
|
184
180
|
|
185
181
|
class FilterableApplyAuth(Protocol):
|
186
182
|
"""Protocol that adds filters to the return value of `apply`."""
|
187
183
|
|
188
|
-
def __call__(self, test:
|
189
|
-
pass
|
184
|
+
def __call__(self, test: Callable) -> Callable: ...
|
190
185
|
|
191
186
|
def apply_to(
|
192
187
|
self,
|
@@ -198,8 +193,7 @@ class FilterableApplyAuth(Protocol):
|
|
198
193
|
method_regex: str | None = None,
|
199
194
|
path: FilterValue | None = None,
|
200
195
|
path_regex: str | None = None,
|
201
|
-
) -> FilterableApplyAuth:
|
202
|
-
pass
|
196
|
+
) -> FilterableApplyAuth: ...
|
203
197
|
|
204
198
|
def skip_for(
|
205
199
|
self,
|
@@ -211,8 +205,7 @@ class FilterableApplyAuth(Protocol):
|
|
211
205
|
method_regex: str | None = None,
|
212
206
|
path: FilterValue | None = None,
|
213
207
|
path_regex: str | None = None,
|
214
|
-
) -> FilterableApplyAuth:
|
215
|
-
pass
|
208
|
+
) -> FilterableApplyAuth: ...
|
216
209
|
|
217
210
|
|
218
211
|
class FilterableRequestsAuth(Protocol):
|
@@ -228,8 +221,7 @@ class FilterableRequestsAuth(Protocol):
|
|
228
221
|
method_regex: str | None = None,
|
229
222
|
path: FilterValue | None = None,
|
230
223
|
path_regex: str | None = None,
|
231
|
-
) -> FilterableRequestsAuth:
|
232
|
-
pass
|
224
|
+
) -> FilterableRequestsAuth: ...
|
233
225
|
|
234
226
|
def skip_for(
|
235
227
|
self,
|
@@ -241,8 +233,7 @@ class FilterableRequestsAuth(Protocol):
|
|
241
233
|
method_regex: str | None = None,
|
242
234
|
path: FilterValue | None = None,
|
243
235
|
path_regex: str | None = None,
|
244
|
-
) -> FilterableRequestsAuth:
|
245
|
-
pass
|
236
|
+
) -> FilterableRequestsAuth: ...
|
246
237
|
|
247
238
|
|
248
239
|
@dataclass
|
@@ -254,7 +245,7 @@ class SelectiveAuthProvider(Generic[Auth]):
|
|
254
245
|
|
255
246
|
def get(self, case: Case, context: AuthContext) -> Auth | None:
|
256
247
|
if self.filter_set.match(context):
|
257
|
-
return
|
248
|
+
return self.provider.get(case, context)
|
258
249
|
return None
|
259
250
|
|
260
251
|
def set(self, case: Case, data: Auth, context: AuthContext) -> None:
|
@@ -278,8 +269,7 @@ class AuthStorage(Generic[Auth]):
|
|
278
269
|
*,
|
279
270
|
refresh_interval: int | None = DEFAULT_REFRESH_INTERVAL,
|
280
271
|
cache_by_key: CacheKeyFunction | None = None,
|
281
|
-
) -> FilterableRegisterAuth:
|
282
|
-
pass
|
272
|
+
) -> FilterableRegisterAuth: ...
|
283
273
|
|
284
274
|
@overload
|
285
275
|
def __call__(
|
@@ -288,8 +278,7 @@ class AuthStorage(Generic[Auth]):
|
|
288
278
|
*,
|
289
279
|
refresh_interval: int | None = DEFAULT_REFRESH_INTERVAL,
|
290
280
|
cache_by_key: CacheKeyFunction | None = None,
|
291
|
-
) -> FilterableApplyAuth:
|
292
|
-
pass
|
281
|
+
) -> FilterableApplyAuth: ...
|
293
282
|
|
294
283
|
def __call__(
|
295
284
|
self,
|
@@ -307,8 +296,7 @@ class AuthStorage(Generic[Auth]):
|
|
307
296
|
filter_set = FilterSet()
|
308
297
|
self.providers.append(SelectiveAuthProvider(provider=RequestsAuth(auth), filter_set=filter_set))
|
309
298
|
|
310
|
-
class _FilterableRequestsAuth:
|
311
|
-
pass
|
299
|
+
class _FilterableRequestsAuth: ...
|
312
300
|
|
313
301
|
attach_filter_chain(_FilterableRequestsAuth, "apply_to", filter_set.include)
|
314
302
|
attach_filter_chain(_FilterableRequestsAuth, "skip_for", filter_set.exclude)
|
@@ -418,8 +406,11 @@ class AuthStorage(Generic[Auth]):
|
|
418
406
|
"""
|
419
407
|
filter_set = FilterSet()
|
420
408
|
|
421
|
-
def wrapper(test:
|
422
|
-
|
409
|
+
def wrapper(test: Callable) -> Callable:
|
410
|
+
if AuthStorageMark.is_set(test):
|
411
|
+
raise IncorrectUsage(f"`{test.__name__}` has already been decorated with `apply`.")
|
412
|
+
auth_storage = self.__class__()
|
413
|
+
AuthStorageMark.set(test, auth_storage)
|
423
414
|
auth_storage._set_provider(
|
424
415
|
provider_class=provider_class,
|
425
416
|
refresh_interval=refresh_interval,
|
@@ -433,46 +424,18 @@ class AuthStorage(Generic[Auth]):
|
|
433
424
|
|
434
425
|
return wrapper # type: ignore[return-value]
|
435
426
|
|
436
|
-
@classmethod
|
437
|
-
def add_auth_storage(cls, test: GenericTest) -> AuthStorage:
|
438
|
-
"""Attach a new auth storage instance to the test if it is not already present."""
|
439
|
-
if not hasattr(test, AUTH_STORAGE_ATTRIBUTE_NAME):
|
440
|
-
setattr(test, AUTH_STORAGE_ATTRIBUTE_NAME, cls())
|
441
|
-
else:
|
442
|
-
raise UsageError(f"`{test.__name__}` has already been decorated with `apply`.")
|
443
|
-
return getattr(test, AUTH_STORAGE_ATTRIBUTE_NAME)
|
444
|
-
|
445
427
|
def set(self, case: Case, context: AuthContext) -> None:
|
446
428
|
"""Set authentication data on a generated test case."""
|
447
429
|
if not self.is_defined:
|
448
|
-
raise
|
430
|
+
raise IncorrectUsage("No auth provider is defined.")
|
449
431
|
for provider in self.providers:
|
450
|
-
data: Auth | None =
|
432
|
+
data: Auth | None = provider.get(case, context)
|
451
433
|
if data is not None:
|
452
434
|
provider.set(case, data, context)
|
453
435
|
case._has_explicit_auth = True
|
454
436
|
break
|
455
437
|
|
456
438
|
|
457
|
-
def _provider_get(auth_provider: AuthProvider, case: Case, context: AuthContext) -> Auth | None:
|
458
|
-
# A shim to provide a compatibility layer between previously used convention for `AuthProvider.get`
|
459
|
-
# where it used to accept a single `context` argument
|
460
|
-
method = auth_provider.get
|
461
|
-
parameters = inspect.signature(method).parameters
|
462
|
-
if len(parameters) == 1:
|
463
|
-
# Old calling convention
|
464
|
-
warnings.warn(
|
465
|
-
"The method 'get' of your AuthProvider is using the old calling convention, "
|
466
|
-
"which is deprecated and will be removed in Schemathesis 4.0. "
|
467
|
-
"Please update it to accept both 'case' and 'context' as arguments.",
|
468
|
-
DeprecationWarning,
|
469
|
-
stacklevel=1,
|
470
|
-
)
|
471
|
-
return method(context) # type: ignore
|
472
|
-
# New calling convention
|
473
|
-
return method(case, context)
|
474
|
-
|
475
|
-
|
476
439
|
def set_on_case(case: Case, context: AuthContext, auth_storage: AuthStorage | None) -> None:
|
477
440
|
"""Set authentication data on this case.
|
478
441
|
|
@@ -486,11 +449,6 @@ def set_on_case(case: Case, context: AuthContext, auth_storage: AuthStorage | No
|
|
486
449
|
GLOBAL_AUTH_STORAGE.set(case, context)
|
487
450
|
|
488
451
|
|
489
|
-
def get_auth_storage_from_test(test: GenericTest) -> AuthStorage | None:
|
490
|
-
"""Extract the currently attached auth storage from a test function."""
|
491
|
-
return getattr(test, AUTH_STORAGE_ATTRIBUTE_NAME, None)
|
492
|
-
|
493
|
-
|
494
452
|
# Global auth API
|
495
453
|
GLOBAL_AUTH_STORAGE: AuthStorage = AuthStorage()
|
496
454
|
register = GLOBAL_AUTH_STORAGE.register
|
schemathesis/checks.py
CHANGED
@@ -1,80 +1,150 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import json
|
4
|
-
from typing import TYPE_CHECKING
|
5
|
-
|
6
|
-
from . import
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
status_code_conformance,
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator, Optional
|
5
|
+
|
6
|
+
from schemathesis.core.failures import (
|
7
|
+
CustomFailure,
|
8
|
+
Failure,
|
9
|
+
FailureGroup,
|
10
|
+
MalformedJson,
|
11
|
+
MaxResponseTimeConfig,
|
12
|
+
ResponseTimeExceeded,
|
13
|
+
ServerError,
|
15
14
|
)
|
15
|
+
from schemathesis.core.registries import Registry
|
16
|
+
from schemathesis.core.transport import Response
|
17
|
+
from schemathesis.generation.overrides import Override
|
16
18
|
|
17
19
|
if TYPE_CHECKING:
|
18
|
-
from .
|
19
|
-
from .models import Case
|
20
|
-
from .transports.responses import GenericResponse
|
20
|
+
from requests.models import CaseInsensitiveDict
|
21
21
|
|
22
|
+
from schemathesis.engine.recorder import ScenarioRecorder
|
23
|
+
from schemathesis.generation.case import Case
|
22
24
|
|
23
|
-
|
25
|
+
CheckFunction = Callable[["CheckContext", "Response", "Case"], Optional[bool]]
|
26
|
+
ChecksConfig = dict[CheckFunction, Any]
|
27
|
+
|
28
|
+
|
29
|
+
class CheckContext:
|
30
|
+
"""Context for Schemathesis checks.
|
31
|
+
|
32
|
+
Provides access to broader test execution data beyond individual test cases.
|
33
|
+
"""
|
34
|
+
|
35
|
+
override: Override | None
|
36
|
+
auth: tuple[str, str] | None
|
37
|
+
headers: CaseInsensitiveDict | None
|
38
|
+
config: ChecksConfig
|
39
|
+
transport_kwargs: dict[str, Any] | None
|
40
|
+
recorder: ScenarioRecorder | None
|
41
|
+
|
42
|
+
__slots__ = ("override", "auth", "headers", "config", "transport_kwargs", "recorder")
|
43
|
+
|
44
|
+
def __init__(
|
45
|
+
self,
|
46
|
+
override: Override | None,
|
47
|
+
auth: tuple[str, str] | None,
|
48
|
+
headers: CaseInsensitiveDict | None,
|
49
|
+
config: ChecksConfig,
|
50
|
+
transport_kwargs: dict[str, Any] | None,
|
51
|
+
recorder: ScenarioRecorder | None = None,
|
52
|
+
) -> None:
|
53
|
+
self.override = override
|
54
|
+
self.auth = auth
|
55
|
+
self.headers = headers
|
56
|
+
self.config = config
|
57
|
+
self.transport_kwargs = transport_kwargs
|
58
|
+
self.recorder = recorder
|
59
|
+
|
60
|
+
def find_parent(self, *, case_id: str) -> Case | None:
|
61
|
+
if self.recorder is not None:
|
62
|
+
return self.recorder.find_parent(case_id=case_id)
|
63
|
+
return None
|
64
|
+
|
65
|
+
def find_related(self, *, case_id: str) -> Iterator[Case]:
|
66
|
+
if self.recorder is not None:
|
67
|
+
yield from self.recorder.find_related(case_id=case_id)
|
68
|
+
|
69
|
+
def find_response(self, *, case_id: str) -> Response | None:
|
70
|
+
if self.recorder is not None:
|
71
|
+
return self.recorder.find_response(case_id=case_id)
|
72
|
+
return None
|
73
|
+
|
74
|
+
def record_case(self, *, parent_id: str, case: Case) -> None:
|
75
|
+
if self.recorder is not None:
|
76
|
+
self.recorder.record_case(parent_id=parent_id, transition=None, case=case)
|
77
|
+
|
78
|
+
def record_response(self, *, case_id: str, response: Response) -> None:
|
79
|
+
if self.recorder is not None:
|
80
|
+
self.recorder.record_response(case_id=case_id, response=response)
|
81
|
+
|
82
|
+
|
83
|
+
CHECKS = Registry[CheckFunction]()
|
84
|
+
check = CHECKS.register
|
85
|
+
|
86
|
+
|
87
|
+
@check
|
88
|
+
def not_a_server_error(ctx: CheckContext, response: Response, case: Case) -> bool | None:
|
24
89
|
"""A check to verify that the response is not a server-side error."""
|
25
|
-
from .specs.graphql.schemas import
|
90
|
+
from .specs.graphql.schemas import GraphQLSchema
|
26
91
|
from .specs.graphql.validation import validate_graphql_response
|
27
|
-
from .transports.responses import get_json
|
28
92
|
|
29
93
|
status_code = response.status_code
|
30
94
|
if status_code >= 500:
|
31
|
-
|
32
|
-
|
33
|
-
if isinstance(case, GraphQLCase):
|
95
|
+
raise ServerError(operation=case.operation.label, status_code=status_code)
|
96
|
+
if isinstance(case.operation.schema, GraphQLSchema):
|
34
97
|
try:
|
35
|
-
data =
|
36
|
-
validate_graphql_response(data)
|
98
|
+
data = response.json()
|
99
|
+
validate_graphql_response(case, data)
|
37
100
|
except json.JSONDecodeError as exc:
|
38
|
-
|
39
|
-
context = failures.JSONDecodeErrorContext.from_exception(exc)
|
40
|
-
raise exc_class(context.title, context=context) from exc
|
101
|
+
raise MalformedJson.from_exception(operation=case.operation.label, exc=exc) from None
|
41
102
|
return None
|
42
103
|
|
43
104
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
ignored_auth,
|
56
|
-
)
|
57
|
-
ALL_CHECKS: tuple[CheckFunction, ...] = DEFAULT_CHECKS + OPTIONAL_CHECKS
|
58
|
-
|
59
|
-
|
60
|
-
def register(check: CheckFunction) -> CheckFunction:
|
61
|
-
"""Register a new check for schemathesis CLI.
|
62
|
-
|
63
|
-
:param check: A function to validate API responses.
|
64
|
-
|
65
|
-
.. code-block:: python
|
66
|
-
|
67
|
-
@schemathesis.check
|
68
|
-
def new_check(ctx, response, case):
|
69
|
-
# some awesome assertions!
|
70
|
-
...
|
71
|
-
"""
|
72
|
-
from . import cli
|
73
|
-
from .internal.checks import wrap_check
|
105
|
+
def max_response_time(ctx: CheckContext, response: Response, case: Case) -> bool | None:
|
106
|
+
config = ctx.config.get(max_response_time, MaxResponseTimeConfig())
|
107
|
+
elapsed = response.elapsed
|
108
|
+
if elapsed > config.limit:
|
109
|
+
raise ResponseTimeExceeded(
|
110
|
+
operation=case.operation.label,
|
111
|
+
message=f"Actual: {elapsed:.2f}ms\nLimit: {config.limit * 1000:.2f}ms",
|
112
|
+
elapsed=elapsed,
|
113
|
+
deadline=config.limit,
|
114
|
+
)
|
115
|
+
return None
|
74
116
|
|
75
|
-
_check = wrap_check(check)
|
76
|
-
global ALL_CHECKS
|
77
117
|
|
78
|
-
|
79
|
-
|
80
|
-
|
118
|
+
def run_checks(
|
119
|
+
*,
|
120
|
+
case: Case,
|
121
|
+
response: Response,
|
122
|
+
ctx: CheckContext,
|
123
|
+
checks: Iterable[CheckFunction],
|
124
|
+
on_failure: Callable[[str, set[Failure], Failure], None],
|
125
|
+
on_success: Callable[[str, Case], None] | None = None,
|
126
|
+
) -> set[Failure]:
|
127
|
+
"""Run a set of checks against a response."""
|
128
|
+
collected: set[Failure] = set()
|
129
|
+
|
130
|
+
for check in checks:
|
131
|
+
name = check.__name__
|
132
|
+
try:
|
133
|
+
skip_check = check(ctx, response, case)
|
134
|
+
if not skip_check and on_success:
|
135
|
+
on_success(name, case)
|
136
|
+
except Failure as failure:
|
137
|
+
on_failure(name, collected, failure.with_traceback(None))
|
138
|
+
except AssertionError as exc:
|
139
|
+
custom_failure = CustomFailure(
|
140
|
+
operation=case.operation.label,
|
141
|
+
title=f"Custom check failed: `{name}`",
|
142
|
+
message=str(exc),
|
143
|
+
exception=exc,
|
144
|
+
)
|
145
|
+
on_failure(name, collected, custom_failure)
|
146
|
+
except FailureGroup as group:
|
147
|
+
for sub_failure in group.exceptions:
|
148
|
+
on_failure(name, collected, sub_failure)
|
149
|
+
|
150
|
+
return collected
|