schemathesis 3.15.4__py3-none-any.whl → 4.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- schemathesis/__init__.py +53 -25
- schemathesis/auths.py +507 -0
- schemathesis/checks.py +190 -25
- schemathesis/cli/__init__.py +27 -1219
- schemathesis/cli/__main__.py +4 -0
- schemathesis/cli/commands/__init__.py +133 -0
- schemathesis/cli/commands/data.py +10 -0
- schemathesis/cli/commands/run/__init__.py +602 -0
- schemathesis/cli/commands/run/context.py +228 -0
- schemathesis/cli/commands/run/events.py +60 -0
- schemathesis/cli/commands/run/executor.py +157 -0
- schemathesis/cli/commands/run/filters.py +53 -0
- schemathesis/cli/commands/run/handlers/__init__.py +46 -0
- schemathesis/cli/commands/run/handlers/base.py +45 -0
- schemathesis/cli/commands/run/handlers/cassettes.py +464 -0
- schemathesis/cli/commands/run/handlers/junitxml.py +60 -0
- schemathesis/cli/commands/run/handlers/output.py +1750 -0
- schemathesis/cli/commands/run/loaders.py +118 -0
- schemathesis/cli/commands/run/validation.py +256 -0
- schemathesis/cli/constants.py +5 -0
- schemathesis/cli/core.py +19 -0
- schemathesis/cli/ext/fs.py +16 -0
- schemathesis/cli/ext/groups.py +203 -0
- schemathesis/cli/ext/options.py +81 -0
- schemathesis/config/__init__.py +202 -0
- schemathesis/config/_auth.py +51 -0
- schemathesis/config/_checks.py +268 -0
- schemathesis/config/_diff_base.py +101 -0
- schemathesis/config/_env.py +21 -0
- schemathesis/config/_error.py +163 -0
- schemathesis/config/_generation.py +157 -0
- schemathesis/config/_health_check.py +24 -0
- schemathesis/config/_operations.py +335 -0
- schemathesis/config/_output.py +171 -0
- schemathesis/config/_parameters.py +19 -0
- schemathesis/config/_phases.py +253 -0
- schemathesis/config/_projects.py +543 -0
- schemathesis/config/_rate_limit.py +17 -0
- schemathesis/config/_report.py +120 -0
- schemathesis/config/_validator.py +9 -0
- schemathesis/config/_warnings.py +89 -0
- schemathesis/config/schema.json +975 -0
- schemathesis/core/__init__.py +72 -0
- schemathesis/core/adapter.py +34 -0
- schemathesis/core/compat.py +32 -0
- schemathesis/core/control.py +2 -0
- schemathesis/core/curl.py +100 -0
- schemathesis/core/deserialization.py +210 -0
- schemathesis/core/errors.py +588 -0
- schemathesis/core/failures.py +316 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/core/hooks.py +20 -0
- schemathesis/core/jsonschema/__init__.py +13 -0
- schemathesis/core/jsonschema/bundler.py +183 -0
- schemathesis/core/jsonschema/keywords.py +40 -0
- schemathesis/core/jsonschema/references.py +222 -0
- schemathesis/core/jsonschema/types.py +41 -0
- schemathesis/core/lazy_import.py +15 -0
- schemathesis/core/loaders.py +107 -0
- schemathesis/core/marks.py +66 -0
- schemathesis/core/media_types.py +79 -0
- schemathesis/core/output/__init__.py +46 -0
- schemathesis/core/output/sanitization.py +54 -0
- schemathesis/core/parameters.py +45 -0
- schemathesis/core/rate_limit.py +60 -0
- schemathesis/core/registries.py +34 -0
- schemathesis/core/result.py +27 -0
- schemathesis/core/schema_analysis.py +17 -0
- schemathesis/core/shell.py +203 -0
- schemathesis/core/transforms.py +144 -0
- schemathesis/core/transport.py +223 -0
- schemathesis/core/validation.py +73 -0
- schemathesis/core/version.py +7 -0
- schemathesis/engine/__init__.py +28 -0
- schemathesis/engine/context.py +152 -0
- schemathesis/engine/control.py +44 -0
- schemathesis/engine/core.py +201 -0
- schemathesis/engine/errors.py +446 -0
- schemathesis/engine/events.py +284 -0
- schemathesis/engine/observations.py +42 -0
- schemathesis/engine/phases/__init__.py +108 -0
- schemathesis/engine/phases/analysis.py +28 -0
- schemathesis/engine/phases/probes.py +172 -0
- schemathesis/engine/phases/stateful/__init__.py +68 -0
- schemathesis/engine/phases/stateful/_executor.py +364 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +220 -0
- schemathesis/engine/phases/unit/_executor.py +459 -0
- schemathesis/engine/phases/unit/_pool.py +82 -0
- schemathesis/engine/recorder.py +254 -0
- schemathesis/errors.py +47 -0
- schemathesis/filters.py +395 -0
- schemathesis/generation/__init__.py +25 -0
- schemathesis/generation/case.py +478 -0
- schemathesis/generation/coverage.py +1528 -0
- schemathesis/generation/hypothesis/__init__.py +121 -0
- schemathesis/generation/hypothesis/builder.py +992 -0
- schemathesis/generation/hypothesis/examples.py +56 -0
- schemathesis/generation/hypothesis/given.py +66 -0
- schemathesis/generation/hypothesis/reporting.py +285 -0
- schemathesis/generation/meta.py +227 -0
- schemathesis/generation/metrics.py +93 -0
- schemathesis/generation/modes.py +20 -0
- schemathesis/generation/overrides.py +127 -0
- schemathesis/generation/stateful/__init__.py +37 -0
- schemathesis/generation/stateful/state_machine.py +294 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +109 -0
- schemathesis/graphql/loaders.py +285 -0
- schemathesis/hooks.py +270 -91
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +467 -0
- schemathesis/openapi/generation/__init__.py +0 -0
- schemathesis/openapi/generation/filters.py +72 -0
- schemathesis/openapi/loaders.py +315 -0
- schemathesis/pytest/__init__.py +5 -0
- schemathesis/pytest/control_flow.py +7 -0
- schemathesis/pytest/lazy.py +341 -0
- schemathesis/pytest/loaders.py +36 -0
- schemathesis/pytest/plugin.py +357 -0
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +682 -257
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/nodes.py +26 -2
- schemathesis/specs/graphql/scalars.py +77 -12
- schemathesis/specs/graphql/schemas.py +367 -148
- schemathesis/specs/graphql/validation.py +33 -0
- schemathesis/specs/openapi/__init__.py +9 -1
- schemathesis/specs/openapi/_hypothesis.py +555 -318
- schemathesis/specs/openapi/adapter/__init__.py +10 -0
- schemathesis/specs/openapi/adapter/parameters.py +729 -0
- schemathesis/specs/openapi/adapter/protocol.py +59 -0
- schemathesis/specs/openapi/adapter/references.py +19 -0
- schemathesis/specs/openapi/adapter/responses.py +368 -0
- schemathesis/specs/openapi/adapter/security.py +144 -0
- schemathesis/specs/openapi/adapter/v2.py +30 -0
- schemathesis/specs/openapi/adapter/v3_0.py +30 -0
- schemathesis/specs/openapi/adapter/v3_1.py +30 -0
- schemathesis/specs/openapi/analysis.py +96 -0
- schemathesis/specs/openapi/checks.py +748 -82
- schemathesis/specs/openapi/converter.py +176 -37
- schemathesis/specs/openapi/definitions.py +599 -4
- schemathesis/specs/openapi/examples.py +581 -165
- schemathesis/specs/openapi/expressions/__init__.py +52 -5
- schemathesis/specs/openapi/expressions/extractors.py +25 -0
- schemathesis/specs/openapi/expressions/lexer.py +34 -31
- schemathesis/specs/openapi/expressions/nodes.py +97 -46
- schemathesis/specs/openapi/expressions/parser.py +35 -13
- schemathesis/specs/openapi/formats.py +122 -0
- schemathesis/specs/openapi/media_types.py +75 -0
- schemathesis/specs/openapi/negative/__init__.py +93 -73
- schemathesis/specs/openapi/negative/mutations.py +294 -103
- schemathesis/specs/openapi/negative/utils.py +0 -9
- schemathesis/specs/openapi/patterns.py +458 -0
- schemathesis/specs/openapi/references.py +60 -81
- schemathesis/specs/openapi/schemas.py +647 -666
- schemathesis/specs/openapi/serialization.py +53 -30
- schemathesis/specs/openapi/stateful/__init__.py +403 -68
- schemathesis/specs/openapi/stateful/control.py +87 -0
- schemathesis/specs/openapi/stateful/dependencies/__init__.py +232 -0
- schemathesis/specs/openapi/stateful/dependencies/inputs.py +428 -0
- schemathesis/specs/openapi/stateful/dependencies/models.py +341 -0
- schemathesis/specs/openapi/stateful/dependencies/naming.py +491 -0
- schemathesis/specs/openapi/stateful/dependencies/outputs.py +34 -0
- schemathesis/specs/openapi/stateful/dependencies/resources.py +339 -0
- schemathesis/specs/openapi/stateful/dependencies/schemas.py +447 -0
- schemathesis/specs/openapi/stateful/inference.py +254 -0
- schemathesis/specs/openapi/stateful/links.py +219 -78
- schemathesis/specs/openapi/types/__init__.py +3 -0
- schemathesis/specs/openapi/types/common.py +23 -0
- schemathesis/specs/openapi/types/v2.py +129 -0
- schemathesis/specs/openapi/types/v3.py +134 -0
- schemathesis/specs/openapi/utils.py +7 -6
- schemathesis/specs/openapi/warnings.py +75 -0
- schemathesis/transport/__init__.py +224 -0
- schemathesis/transport/asgi.py +26 -0
- schemathesis/transport/prepare.py +126 -0
- schemathesis/transport/requests.py +278 -0
- schemathesis/transport/serialization.py +329 -0
- schemathesis/transport/wsgi.py +175 -0
- schemathesis-4.4.2.dist-info/METADATA +213 -0
- schemathesis-4.4.2.dist-info/RECORD +192 -0
- {schemathesis-3.15.4.dist-info → schemathesis-4.4.2.dist-info}/WHEEL +1 -1
- schemathesis-4.4.2.dist-info/entry_points.txt +6 -0
- {schemathesis-3.15.4.dist-info → schemathesis-4.4.2.dist-info/licenses}/LICENSE +1 -1
- schemathesis/_compat.py +0 -57
- schemathesis/_hypothesis.py +0 -123
- schemathesis/auth.py +0 -214
- schemathesis/cli/callbacks.py +0 -240
- schemathesis/cli/cassettes.py +0 -351
- schemathesis/cli/context.py +0 -38
- schemathesis/cli/debug.py +0 -21
- schemathesis/cli/handlers.py +0 -11
- schemathesis/cli/junitxml.py +0 -41
- schemathesis/cli/options.py +0 -70
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -521
- schemathesis/cli/output/short.py +0 -40
- schemathesis/constants.py +0 -88
- schemathesis/exceptions.py +0 -257
- schemathesis/extra/_aiohttp.py +0 -27
- schemathesis/extra/_flask.py +0 -10
- schemathesis/extra/_server.py +0 -16
- schemathesis/extra/pytest_plugin.py +0 -251
- schemathesis/failures.py +0 -145
- schemathesis/fixups/__init__.py +0 -29
- schemathesis/fixups/fast_api.py +0 -30
- schemathesis/graphql.py +0 -5
- schemathesis/internal.py +0 -6
- schemathesis/lazy.py +0 -301
- schemathesis/models.py +0 -1113
- schemathesis/parameters.py +0 -91
- schemathesis/runner/__init__.py +0 -470
- schemathesis/runner/events.py +0 -242
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/core.py +0 -791
- schemathesis/runner/impl/solo.py +0 -85
- schemathesis/runner/impl/threadpool.py +0 -367
- schemathesis/runner/serialization.py +0 -206
- schemathesis/serializers.py +0 -253
- schemathesis/service/__init__.py +0 -18
- schemathesis/service/auth.py +0 -10
- schemathesis/service/client.py +0 -62
- schemathesis/service/constants.py +0 -25
- schemathesis/service/events.py +0 -39
- schemathesis/service/handler.py +0 -46
- schemathesis/service/hosts.py +0 -74
- schemathesis/service/metadata.py +0 -42
- schemathesis/service/models.py +0 -21
- schemathesis/service/serialization.py +0 -184
- schemathesis/service/worker.py +0 -39
- schemathesis/specs/graphql/loaders.py +0 -215
- schemathesis/specs/openapi/constants.py +0 -7
- schemathesis/specs/openapi/expressions/context.py +0 -12
- schemathesis/specs/openapi/expressions/pointers.py +0 -29
- schemathesis/specs/openapi/filters.py +0 -44
- schemathesis/specs/openapi/links.py +0 -303
- schemathesis/specs/openapi/loaders.py +0 -453
- schemathesis/specs/openapi/parameters.py +0 -430
- schemathesis/specs/openapi/security.py +0 -129
- schemathesis/specs/openapi/validation.py +0 -24
- schemathesis/stateful.py +0 -358
- schemathesis/targets.py +0 -32
- schemathesis/types.py +0 -38
- schemathesis/utils.py +0 -475
- schemathesis-3.15.4.dist-info/METADATA +0 -202
- schemathesis-3.15.4.dist-info/RECORD +0 -99
- schemathesis-3.15.4.dist-info/entry_points.txt +0 -7
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
schemathesis/exceptions.py
DELETED
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
from hashlib import sha1
|
|
2
|
-
from json import JSONDecodeError
|
|
3
|
-
from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, NoReturn, Optional, Tuple, Type, Union
|
|
4
|
-
|
|
5
|
-
import attr
|
|
6
|
-
import hypothesis.errors
|
|
7
|
-
import requests
|
|
8
|
-
from jsonschema import ValidationError
|
|
9
|
-
|
|
10
|
-
from .constants import SERIALIZERS_SUGGESTION_MESSAGE
|
|
11
|
-
from .failures import FailureContext
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from .utils import GenericResponse
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class CheckFailed(AssertionError):
|
|
18
|
-
"""Custom error type to distinguish from arbitrary AssertionError that may happen in the dependent libraries."""
|
|
19
|
-
|
|
20
|
-
__module__ = "builtins"
|
|
21
|
-
context: Optional[FailureContext]
|
|
22
|
-
causes: Optional[Tuple[Union["CheckFailed", AssertionError], ...]]
|
|
23
|
-
|
|
24
|
-
def __init__(
|
|
25
|
-
self,
|
|
26
|
-
*args: Any,
|
|
27
|
-
context: Optional[FailureContext] = None,
|
|
28
|
-
causes: Optional[Tuple[Union["CheckFailed", AssertionError], ...]] = None,
|
|
29
|
-
):
|
|
30
|
-
super().__init__(*args)
|
|
31
|
-
self.context = context
|
|
32
|
-
self.causes = causes
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def make_unique_by_key(
|
|
36
|
-
check_name: str, check_message: Optional[str], context: Optional[FailureContext]
|
|
37
|
-
) -> Tuple[Optional[str], ...]:
|
|
38
|
-
"""A key to distinguish different failed checks.
|
|
39
|
-
|
|
40
|
-
It is not only based on `FailureContext`, because the end-user may raise plain `AssertionError` in their custom
|
|
41
|
-
checks, and those won't have any context attached.
|
|
42
|
-
"""
|
|
43
|
-
if context is not None:
|
|
44
|
-
return context.unique_by_key(check_message)
|
|
45
|
-
return check_name, check_message
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def deduplicate_failed_checks(
|
|
49
|
-
checks: List[Union[CheckFailed, AssertionError]]
|
|
50
|
-
) -> Generator[Union[CheckFailed, AssertionError], None, None]:
|
|
51
|
-
"""Keep only unique failed checks."""
|
|
52
|
-
seen = set()
|
|
53
|
-
for check in checks:
|
|
54
|
-
check_message = check.args[0]
|
|
55
|
-
if isinstance(check, CheckFailed) and check.context is not None:
|
|
56
|
-
key = check.context.unique_by_key(check_message)
|
|
57
|
-
else:
|
|
58
|
-
key = check_message
|
|
59
|
-
if key not in seen:
|
|
60
|
-
yield check
|
|
61
|
-
seen.add(key)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
CACHE: Dict[Union[str, int], Type[CheckFailed]] = {}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def get_exception(name: str) -> Type[CheckFailed]:
|
|
68
|
-
"""Create a new exception class with provided name or fetch one from the cache."""
|
|
69
|
-
if name in CACHE:
|
|
70
|
-
exception_class = CACHE[name]
|
|
71
|
-
else:
|
|
72
|
-
exception_class = type(name, (CheckFailed,), {})
|
|
73
|
-
exception_class.__qualname__ = CheckFailed.__name__
|
|
74
|
-
exception_class.__name__ = CheckFailed.__name__
|
|
75
|
-
CACHE[name] = exception_class
|
|
76
|
-
return exception_class
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def _get_hashed_exception(prefix: str, message: str) -> Type[CheckFailed]:
|
|
80
|
-
"""Give different exceptions for different error messages."""
|
|
81
|
-
messages_digest = sha1(message.encode("utf-8")).hexdigest()
|
|
82
|
-
name = f"{prefix}{messages_digest}"
|
|
83
|
-
return get_exception(name)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def get_grouped_exception(prefix: str, *exceptions: AssertionError) -> Type[CheckFailed]:
|
|
87
|
-
# The prefix is needed to distinguish multiple operations with the same error messages
|
|
88
|
-
# that are coming from different operations
|
|
89
|
-
messages = [exception.args[0] for exception in exceptions]
|
|
90
|
-
message = "".join(messages)
|
|
91
|
-
return _get_hashed_exception("GroupedException", f"{prefix}{message}")
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def get_server_error(status_code: int) -> Type[CheckFailed]:
|
|
95
|
-
"""Return new exception for the Internal Server Error cases."""
|
|
96
|
-
name = f"ServerError{status_code}"
|
|
97
|
-
return get_exception(name)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def get_status_code_error(status_code: int) -> Type[CheckFailed]:
|
|
101
|
-
"""Return new exception for an unexpected status code."""
|
|
102
|
-
name = f"StatusCodeError{status_code}"
|
|
103
|
-
return get_exception(name)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def get_response_type_error(expected: str, received: str) -> Type[CheckFailed]:
|
|
107
|
-
"""Return new exception for an unexpected response type."""
|
|
108
|
-
name = f"SchemaValidationError{expected}_{received}"
|
|
109
|
-
return get_exception(name)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def get_malformed_media_type_error(media_type: str) -> Type[CheckFailed]:
|
|
113
|
-
name = f"MalformedMediaType{media_type}"
|
|
114
|
-
return get_exception(name)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def get_missing_content_type_error() -> Type[CheckFailed]:
|
|
118
|
-
"""Return new exception for a missing Content-Type header."""
|
|
119
|
-
return get_exception("MissingContentTypeError")
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def get_schema_validation_error(exception: ValidationError) -> Type[CheckFailed]:
|
|
123
|
-
"""Return new exception for schema validation error."""
|
|
124
|
-
return _get_hashed_exception("SchemaValidationError", str(exception))
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def get_response_parsing_error(exception: JSONDecodeError) -> Type[CheckFailed]:
|
|
128
|
-
"""Return new exception for response parsing error."""
|
|
129
|
-
return _get_hashed_exception("ResponseParsingError", str(exception))
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def get_headers_error(message: str) -> Type[CheckFailed]:
|
|
133
|
-
"""Return new exception for missing headers."""
|
|
134
|
-
return _get_hashed_exception("MissingHeadersError", message)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def get_timeout_error(deadline: Union[float, int]) -> Type[CheckFailed]:
|
|
138
|
-
"""Request took too long."""
|
|
139
|
-
return _get_hashed_exception("TimeoutError", str(deadline))
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
@attr.s(slots=True)
|
|
143
|
-
class InvalidSchema(Exception):
|
|
144
|
-
"""Schema associated with an API operation contains an error."""
|
|
145
|
-
|
|
146
|
-
__module__ = "builtins"
|
|
147
|
-
message: Optional[str] = attr.ib(default=None)
|
|
148
|
-
path: Optional[str] = attr.ib(default=None)
|
|
149
|
-
method: Optional[str] = attr.ib(default=None)
|
|
150
|
-
full_path: Optional[str] = attr.ib(default=None)
|
|
151
|
-
|
|
152
|
-
def as_failing_test_function(self) -> Callable:
|
|
153
|
-
"""Create a test function that will fail.
|
|
154
|
-
|
|
155
|
-
This approach allows us to use default pytest reporting style for operation-level schema errors.
|
|
156
|
-
"""
|
|
157
|
-
|
|
158
|
-
def actual_test(*args: Any, **kwargs: Any) -> NoReturn:
|
|
159
|
-
__tracebackhide__ = True # pylint: disable=unused-variable
|
|
160
|
-
raise self
|
|
161
|
-
|
|
162
|
-
return actual_test
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
class DeadlineExceeded(Exception):
|
|
166
|
-
"""Test took too long to run."""
|
|
167
|
-
|
|
168
|
-
__module__ = "builtins"
|
|
169
|
-
|
|
170
|
-
@classmethod
|
|
171
|
-
def from_exc(cls, exc: hypothesis.errors.DeadlineExceeded) -> "DeadlineExceeded":
|
|
172
|
-
runtime = exc.runtime.total_seconds() * 1000
|
|
173
|
-
deadline = exc.deadline.total_seconds() * 1000
|
|
174
|
-
return cls(
|
|
175
|
-
f"API response time is too slow! It took {runtime:.2f}ms, which exceeds the deadline of {deadline:.2f}ms.\n"
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
class SchemaLoadingError(ValueError):
|
|
180
|
-
"""Failed to load an API schema."""
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
class NonCheckError(Exception):
|
|
184
|
-
"""An error happened in side the runner, but is not related to failed checks.
|
|
185
|
-
|
|
186
|
-
Used primarily to not let Hypothesis consider the test as flaky or detect multiple failures as we handle it
|
|
187
|
-
on our side.
|
|
188
|
-
"""
|
|
189
|
-
|
|
190
|
-
__module__ = "builtins"
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
class InternalError(Exception):
|
|
194
|
-
"""Internal error in Schemathesis."""
|
|
195
|
-
|
|
196
|
-
__module__ = "builtins"
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
class SkipTest(BaseException):
|
|
200
|
-
"""Raises when a test should be skipped and return control to the execution engine (own Schemathesis' or pytest)."""
|
|
201
|
-
|
|
202
|
-
__module__ = "builtins"
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
SERIALIZATION_NOT_POSSIBLE_MESSAGE = (
|
|
206
|
-
f"Schemathesis can't serialize data to any of the defined media types: {{}} \n{SERIALIZERS_SUGGESTION_MESSAGE}"
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
SERIALIZATION_FOR_TYPE_IS_NOT_POSSIBLE_MESSAGE = (
|
|
210
|
-
f"Schemathesis can't serialize data to {{}} \n{SERIALIZERS_SUGGESTION_MESSAGE}"
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
class SerializationNotPossible(Exception):
|
|
215
|
-
"""Not possible to serialize to any of the media types defined for some API operation.
|
|
216
|
-
|
|
217
|
-
Usually, there is still `application/json` along with less common ones, but this error happens when there is no
|
|
218
|
-
media type that Schemathesis knows how to serialize data to.
|
|
219
|
-
"""
|
|
220
|
-
|
|
221
|
-
__module__ = "builtins"
|
|
222
|
-
|
|
223
|
-
@classmethod
|
|
224
|
-
def from_media_types(cls, *media_types: str) -> "SerializationNotPossible":
|
|
225
|
-
return cls(SERIALIZATION_NOT_POSSIBLE_MESSAGE.format(", ".join(media_types)))
|
|
226
|
-
|
|
227
|
-
@classmethod
|
|
228
|
-
def for_media_type(cls, media_type: str) -> "SerializationNotPossible":
|
|
229
|
-
return cls(SERIALIZATION_FOR_TYPE_IS_NOT_POSSIBLE_MESSAGE.format(media_type))
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
class InvalidRegularExpression(Exception):
|
|
233
|
-
__module__ = "builtins"
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
@attr.s # pragma: no mutate
|
|
237
|
-
class HTTPError(Exception):
|
|
238
|
-
response: "GenericResponse" = attr.ib() # pragma: no mutate
|
|
239
|
-
url: str = attr.ib() # pragma: no mutate
|
|
240
|
-
|
|
241
|
-
@classmethod
|
|
242
|
-
def raise_for_status(cls, response: requests.Response) -> None:
|
|
243
|
-
try:
|
|
244
|
-
response.raise_for_status()
|
|
245
|
-
except requests.HTTPError as exc:
|
|
246
|
-
raise cls(response=response, url=response.url) from exc
|
|
247
|
-
|
|
248
|
-
@classmethod
|
|
249
|
-
def check_response(cls, response: requests.Response, schema_path: str) -> None:
|
|
250
|
-
# Raising exception to provide unified behavior
|
|
251
|
-
# E.g. it will be handled in CLI - a proper error message will be shown
|
|
252
|
-
if 400 <= response.status_code < 600:
|
|
253
|
-
raise cls(response=response, url=schema_path)
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
class UsageError(Exception):
|
|
257
|
-
"""Incorrect usage of Schemathesis functions."""
|
schemathesis/extra/_aiohttp.py
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
from typing import Optional
|
|
3
|
-
|
|
4
|
-
from aiohttp import web
|
|
5
|
-
|
|
6
|
-
from . import _server
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def _run_server(app: web.Application, port: int) -> None:
|
|
10
|
-
"""Run the given app on the given port.
|
|
11
|
-
|
|
12
|
-
Intended to be called as a target for a separate thread.
|
|
13
|
-
NOTE. `aiohttp.web.run_app` works only in the main thread and can't be used here (or maybe can we some tuning)
|
|
14
|
-
"""
|
|
15
|
-
# Set a loop for a new thread (there is no by default for non-main threads)
|
|
16
|
-
loop = asyncio.new_event_loop()
|
|
17
|
-
asyncio.set_event_loop(loop)
|
|
18
|
-
runner = web.AppRunner(app)
|
|
19
|
-
loop.run_until_complete(runner.setup())
|
|
20
|
-
site = web.TCPSite(runner, "127.0.0.1", port)
|
|
21
|
-
loop.run_until_complete(site.start())
|
|
22
|
-
loop.run_forever()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def run_server(app: web.Application, port: Optional[int] = None, timeout: float = 0.05) -> int:
|
|
26
|
-
"""Start a thread with the given aiohttp application."""
|
|
27
|
-
return _server.run(_run_server, app=app, port=port, timeout=timeout)
|
schemathesis/extra/_flask.py
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
|
-
from flask import Flask
|
|
4
|
-
|
|
5
|
-
from . import _server
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def run_server(app: Flask, port: Optional[int] = None, timeout: float = 0.05) -> int:
|
|
9
|
-
"""Start a thread with the given aiohttp application."""
|
|
10
|
-
return _server.run(app.run, port=port, timeout=timeout)
|
schemathesis/extra/_server.py
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import threading
|
|
2
|
-
from time import sleep
|
|
3
|
-
from typing import Any, Callable, Optional
|
|
4
|
-
|
|
5
|
-
from aiohttp.test_utils import unused_port
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def run(target: Callable, port: Optional[int] = None, timeout: float = 0.05, **kwargs: Any) -> int:
|
|
9
|
-
"""Start a thread with the given aiohttp application."""
|
|
10
|
-
if port is None:
|
|
11
|
-
port = unused_port()
|
|
12
|
-
server_thread = threading.Thread(target=target, kwargs={"port": port, **kwargs})
|
|
13
|
-
server_thread.daemon = True
|
|
14
|
-
server_thread.start()
|
|
15
|
-
sleep(timeout)
|
|
16
|
-
return port
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
from contextlib import contextmanager
|
|
2
|
-
from functools import partial
|
|
3
|
-
from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, TypeVar, cast
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
from _pytest import fixtures, nodes
|
|
7
|
-
from _pytest.config import hookimpl
|
|
8
|
-
from _pytest.fixtures import FuncFixtureInfo
|
|
9
|
-
from _pytest.nodes import Node
|
|
10
|
-
from _pytest.python import Class, Function, FunctionDefinition, Metafunc, Module, PyCollector
|
|
11
|
-
from hypothesis import reporting
|
|
12
|
-
from hypothesis.errors import InvalidArgument
|
|
13
|
-
from hypothesis_jsonschema._canonicalise import HypothesisRefResolutionError
|
|
14
|
-
|
|
15
|
-
from .. import DataGenerationMethod
|
|
16
|
-
from .._hypothesis import create_test
|
|
17
|
-
from ..constants import IS_PYTEST_ABOVE_7, IS_PYTEST_ABOVE_54, RECURSIVE_REFERENCE_ERROR_MESSAGE
|
|
18
|
-
from ..exceptions import InvalidSchema, SkipTest
|
|
19
|
-
from ..models import APIOperation
|
|
20
|
-
from ..utils import (
|
|
21
|
-
PARAMETRIZE_MARKER,
|
|
22
|
-
Ok,
|
|
23
|
-
Result,
|
|
24
|
-
fail_on_no_matches,
|
|
25
|
-
get_given_args,
|
|
26
|
-
get_given_kwargs,
|
|
27
|
-
is_given_applied,
|
|
28
|
-
is_schemathesis_test,
|
|
29
|
-
merge_given_args,
|
|
30
|
-
validate_given_args,
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
T = TypeVar("T", bound=Node)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def create(cls: Type[T], *args: Any, **kwargs: Any) -> T:
|
|
37
|
-
if IS_PYTEST_ABOVE_54:
|
|
38
|
-
return cls.from_parent(*args, **kwargs) # type: ignore
|
|
39
|
-
return cls(*args, **kwargs)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class SchemathesisFunction(Function): # pylint: disable=too-many-ancestors
|
|
43
|
-
def __init__(
|
|
44
|
-
self,
|
|
45
|
-
*args: Any,
|
|
46
|
-
test_func: Callable,
|
|
47
|
-
test_name: Optional[str] = None,
|
|
48
|
-
data_generation_method: DataGenerationMethod,
|
|
49
|
-
**kwargs: Any,
|
|
50
|
-
) -> None:
|
|
51
|
-
super().__init__(*args, **kwargs)
|
|
52
|
-
self.test_function = test_func
|
|
53
|
-
self.test_name = test_name
|
|
54
|
-
self.data_generation_method = data_generation_method
|
|
55
|
-
|
|
56
|
-
if not IS_PYTEST_ABOVE_7:
|
|
57
|
-
# On pytest 7, `self.obj` is already `partial`
|
|
58
|
-
def _getobj(self) -> partial:
|
|
59
|
-
"""Tests defined as methods require `self` as the first argument.
|
|
60
|
-
|
|
61
|
-
This method is called only for this case.
|
|
62
|
-
"""
|
|
63
|
-
return partial(self.obj, self.parent.obj) # type: ignore
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class SchemathesisCase(PyCollector):
|
|
67
|
-
def __init__(self, test_function: Callable, *args: Any, **kwargs: Any) -> None:
|
|
68
|
-
self.given_kwargs: Optional[Dict[str, Any]]
|
|
69
|
-
given_args = get_given_args(test_function)
|
|
70
|
-
given_kwargs = get_given_kwargs(test_function)
|
|
71
|
-
|
|
72
|
-
def _init_with_valid_test(_test_function: Callable, _args: Tuple, _kwargs: Dict[str, Any]) -> None:
|
|
73
|
-
self.test_function = _test_function
|
|
74
|
-
self.is_invalid_test = False
|
|
75
|
-
self.given_kwargs = merge_given_args(test_function, _args, _kwargs)
|
|
76
|
-
|
|
77
|
-
if is_given_applied(test_function):
|
|
78
|
-
failing_test = validate_given_args(test_function, given_args, given_kwargs)
|
|
79
|
-
if failing_test is not None:
|
|
80
|
-
self.test_function = failing_test
|
|
81
|
-
self.is_invalid_test = True
|
|
82
|
-
self.given_kwargs = None
|
|
83
|
-
else:
|
|
84
|
-
_init_with_valid_test(test_function, given_args, given_kwargs)
|
|
85
|
-
else:
|
|
86
|
-
_init_with_valid_test(test_function, given_args, given_kwargs)
|
|
87
|
-
self.schemathesis_case = getattr(test_function, PARAMETRIZE_MARKER)
|
|
88
|
-
super().__init__(*args, **kwargs)
|
|
89
|
-
|
|
90
|
-
def _get_test_name(self, operation: APIOperation, data_generation_method: DataGenerationMethod) -> str:
|
|
91
|
-
return f"{self.name}[{operation.verbose_name}][{data_generation_method.as_short_name()}]"
|
|
92
|
-
|
|
93
|
-
def _gen_items(
|
|
94
|
-
self, result: Result[APIOperation, InvalidSchema], data_generation_method: DataGenerationMethod
|
|
95
|
-
) -> Generator[SchemathesisFunction, None, None]:
|
|
96
|
-
"""Generate all tests for the given API operation.
|
|
97
|
-
|
|
98
|
-
Could produce more than one test item if
|
|
99
|
-
parametrization is applied via ``pytest.mark.parametrize`` or ``pytest_generate_tests``.
|
|
100
|
-
|
|
101
|
-
This implementation is based on the original one in pytest, but with slight adjustments
|
|
102
|
-
to produce tests out of hypothesis ones.
|
|
103
|
-
"""
|
|
104
|
-
if isinstance(result, Ok):
|
|
105
|
-
operation = result.ok()
|
|
106
|
-
if self.is_invalid_test:
|
|
107
|
-
funcobj = self.test_function
|
|
108
|
-
else:
|
|
109
|
-
funcobj = create_test(
|
|
110
|
-
operation=operation,
|
|
111
|
-
test=self.test_function,
|
|
112
|
-
_given_kwargs=self.given_kwargs,
|
|
113
|
-
data_generation_method=data_generation_method,
|
|
114
|
-
)
|
|
115
|
-
name = self._get_test_name(operation, data_generation_method)
|
|
116
|
-
else:
|
|
117
|
-
error = result.err()
|
|
118
|
-
funcobj = error.as_failing_test_function()
|
|
119
|
-
name = self.name
|
|
120
|
-
# `full_path` is always available in this case
|
|
121
|
-
if error.method:
|
|
122
|
-
name += f"[{error.method.upper()} {error.full_path}]"
|
|
123
|
-
else:
|
|
124
|
-
name += f"[{error.full_path}]"
|
|
125
|
-
name += f"[{data_generation_method.as_short_name()}]"
|
|
126
|
-
|
|
127
|
-
cls = self._get_class_parent()
|
|
128
|
-
definition: FunctionDefinition = create(FunctionDefinition, name=self.name, parent=self.parent, callobj=funcobj)
|
|
129
|
-
fixturemanager = self.session._fixturemanager
|
|
130
|
-
fixtureinfo = fixturemanager.getfixtureinfo(definition, funcobj, cls)
|
|
131
|
-
|
|
132
|
-
metafunc = self._parametrize(cls, definition, fixtureinfo)
|
|
133
|
-
|
|
134
|
-
if isinstance(self.parent, Class):
|
|
135
|
-
# On pytest 7, Class collects the test methods directly, therefore
|
|
136
|
-
funcobj = partial(funcobj, self.parent.obj)
|
|
137
|
-
|
|
138
|
-
if not metafunc._calls:
|
|
139
|
-
yield create(
|
|
140
|
-
SchemathesisFunction,
|
|
141
|
-
name=name,
|
|
142
|
-
parent=self.parent,
|
|
143
|
-
callobj=funcobj,
|
|
144
|
-
fixtureinfo=fixtureinfo,
|
|
145
|
-
test_func=self.test_function,
|
|
146
|
-
originalname=self.name,
|
|
147
|
-
data_generation_method=data_generation_method,
|
|
148
|
-
)
|
|
149
|
-
else:
|
|
150
|
-
fixtures.add_funcarg_pseudo_fixture_def(self.parent, metafunc, fixturemanager) # type: ignore[arg-type]
|
|
151
|
-
fixtureinfo.prune_dependency_tree()
|
|
152
|
-
for callspec in metafunc._calls:
|
|
153
|
-
subname = f"{name}[{callspec.id}]"
|
|
154
|
-
yield create(
|
|
155
|
-
SchemathesisFunction,
|
|
156
|
-
name=subname,
|
|
157
|
-
parent=self.parent,
|
|
158
|
-
callspec=callspec,
|
|
159
|
-
callobj=funcobj,
|
|
160
|
-
fixtureinfo=fixtureinfo,
|
|
161
|
-
keywords={callspec.id: True},
|
|
162
|
-
originalname=name,
|
|
163
|
-
test_func=self.test_function,
|
|
164
|
-
data_generation_method=data_generation_method,
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
def _get_class_parent(self) -> Optional[Type]:
|
|
168
|
-
clscol = self.getparent(Class)
|
|
169
|
-
return clscol.obj if clscol else None
|
|
170
|
-
|
|
171
|
-
def _parametrize(
|
|
172
|
-
self, cls: Optional[Type], definition: FunctionDefinition, fixtureinfo: FuncFixtureInfo
|
|
173
|
-
) -> Metafunc:
|
|
174
|
-
parent = self.getparent(Module)
|
|
175
|
-
module = parent.obj if parent is not None else parent
|
|
176
|
-
kwargs = {"cls": cls, "module": module}
|
|
177
|
-
if IS_PYTEST_ABOVE_7:
|
|
178
|
-
# Avoiding `Metafunc` is quite problematic for now, as there are quite a lot of internals we rely on
|
|
179
|
-
kwargs["_ispytest"] = True
|
|
180
|
-
metafunc = Metafunc(definition, fixtureinfo, self.config, **kwargs)
|
|
181
|
-
methods = []
|
|
182
|
-
if hasattr(module, "pytest_generate_tests"):
|
|
183
|
-
methods.append(module.pytest_generate_tests)
|
|
184
|
-
if hasattr(cls, "pytest_generate_tests"):
|
|
185
|
-
cls = cast(Type, cls)
|
|
186
|
-
methods.append(cls().pytest_generate_tests)
|
|
187
|
-
self.ihook.pytest_generate_tests.call_extra(methods, {"metafunc": metafunc})
|
|
188
|
-
return metafunc
|
|
189
|
-
|
|
190
|
-
def collect(self) -> List[Function]: # type: ignore
|
|
191
|
-
"""Generate different test items for all API operations available in the given schema."""
|
|
192
|
-
try:
|
|
193
|
-
items = [
|
|
194
|
-
item
|
|
195
|
-
for data_generation_method in self.schemathesis_case.data_generation_methods
|
|
196
|
-
for operation in self.schemathesis_case.get_all_operations()
|
|
197
|
-
for item in self._gen_items(operation, data_generation_method)
|
|
198
|
-
]
|
|
199
|
-
if not items:
|
|
200
|
-
fail_on_no_matches(self.nodeid)
|
|
201
|
-
return items
|
|
202
|
-
except Exception:
|
|
203
|
-
pytest.fail("Error during collection")
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
@hookimpl(hookwrapper=True) # type:ignore # pragma: no mutate
|
|
207
|
-
def pytest_pycollect_makeitem(collector: nodes.Collector, name: str, obj: Any) -> Generator[None, Any, None]:
|
|
208
|
-
"""Switch to a different collector if the test is parametrized marked by schemathesis."""
|
|
209
|
-
outcome = yield
|
|
210
|
-
if is_schemathesis_test(obj):
|
|
211
|
-
outcome.force_result(create(SchemathesisCase, parent=collector, test_function=obj, name=name))
|
|
212
|
-
else:
|
|
213
|
-
outcome.get_result()
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
IGNORED_HYPOTHESIS_OUTPUT = ("Falsifying example",)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def hypothesis_reporter(value: str) -> None:
|
|
220
|
-
if value.startswith(IGNORED_HYPOTHESIS_OUTPUT):
|
|
221
|
-
return
|
|
222
|
-
reporting.default(value)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
@contextmanager
|
|
226
|
-
def skip_unnecessary_hypothesis_output() -> Generator:
|
|
227
|
-
"""Avoid printing Hypothesis output that is not necessary in Schemathesis' pytest plugin."""
|
|
228
|
-
with reporting.with_reporter(hypothesis_reporter): # type: ignore
|
|
229
|
-
yield
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
@hookimpl(hookwrapper=True) # pragma: no mutate
|
|
233
|
-
def pytest_pyfunc_call(pyfuncitem): # type:ignore
|
|
234
|
-
"""It is possible to have a Hypothesis exception in runtime.
|
|
235
|
-
|
|
236
|
-
For example - kwargs validation is failed for some strategy.
|
|
237
|
-
"""
|
|
238
|
-
if isinstance(pyfuncitem, SchemathesisFunction):
|
|
239
|
-
with skip_unnecessary_hypothesis_output():
|
|
240
|
-
outcome = yield
|
|
241
|
-
try:
|
|
242
|
-
outcome.get_result()
|
|
243
|
-
except InvalidArgument as exc:
|
|
244
|
-
raise InvalidSchema(exc.args[0]) from None
|
|
245
|
-
except HypothesisRefResolutionError:
|
|
246
|
-
pytest.skip(RECURSIVE_REFERENCE_ERROR_MESSAGE)
|
|
247
|
-
except SkipTest as exc:
|
|
248
|
-
pytest.skip(exc.args[0])
|
|
249
|
-
else:
|
|
250
|
-
outcome = yield
|
|
251
|
-
outcome.get_result()
|