schemathesis 3.13.0__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 -1016
- 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 +683 -247
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/nodes.py +27 -0
- schemathesis/specs/graphql/scalars.py +86 -0
- schemathesis/specs/graphql/schemas.py +395 -123
- schemathesis/specs/graphql/validation.py +33 -0
- schemathesis/specs/openapi/__init__.py +9 -1
- schemathesis/specs/openapi/_hypothesis.py +578 -317
- 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 +753 -74
- 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 +117 -68
- schemathesis/specs/openapi/negative/mutations.py +294 -104
- schemathesis/specs/openapi/negative/utils.py +3 -6
- schemathesis/specs/openapi/patterns.py +458 -0
- schemathesis/specs/openapi/references.py +60 -81
- schemathesis/specs/openapi/schemas.py +648 -650
- schemathesis/specs/openapi/serialization.py +53 -30
- schemathesis/specs/openapi/stateful/__init__.py +404 -69
- 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.13.0.dist-info → schemathesis-4.4.2.dist-info}/WHEEL +1 -1
- schemathesis-4.4.2.dist-info/entry_points.txt +6 -0
- {schemathesis-3.13.0.dist-info → schemathesis-4.4.2.dist-info/licenses}/LICENSE +1 -1
- schemathesis/_compat.py +0 -41
- schemathesis/_hypothesis.py +0 -115
- schemathesis/cli/callbacks.py +0 -188
- schemathesis/cli/cassettes.py +0 -253
- schemathesis/cli/context.py +0 -36
- schemathesis/cli/debug.py +0 -21
- schemathesis/cli/handlers.py +0 -11
- schemathesis/cli/junitxml.py +0 -41
- schemathesis/cli/options.py +0 -51
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -508
- schemathesis/cli/output/short.py +0 -40
- schemathesis/constants.py +0 -79
- schemathesis/exceptions.py +0 -207
- schemathesis/extra/_aiohttp.py +0 -27
- schemathesis/extra/_flask.py +0 -10
- schemathesis/extra/_server.py +0 -16
- schemathesis/extra/pytest_plugin.py +0 -216
- schemathesis/failures.py +0 -131
- schemathesis/fixups/__init__.py +0 -29
- schemathesis/fixups/fast_api.py +0 -30
- schemathesis/lazy.py +0 -227
- schemathesis/models.py +0 -1041
- schemathesis/parameters.py +0 -88
- schemathesis/runner/__init__.py +0 -460
- schemathesis/runner/events.py +0 -240
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/core.py +0 -755
- schemathesis/runner/impl/solo.py +0 -85
- schemathesis/runner/impl/threadpool.py +0 -367
- schemathesis/runner/serialization.py +0 -189
- schemathesis/serializers.py +0 -233
- schemathesis/service/__init__.py +0 -3
- schemathesis/service/client.py +0 -46
- schemathesis/service/constants.py +0 -12
- schemathesis/service/events.py +0 -39
- schemathesis/service/handler.py +0 -39
- schemathesis/service/models.py +0 -7
- schemathesis/service/serialization.py +0 -153
- schemathesis/service/worker.py +0 -40
- 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 -302
- schemathesis/specs/openapi/loaders.py +0 -453
- schemathesis/specs/openapi/parameters.py +0 -413
- schemathesis/specs/openapi/security.py +0 -129
- schemathesis/specs/openapi/validation.py +0 -24
- schemathesis/stateful.py +0 -349
- schemathesis/targets.py +0 -32
- schemathesis/types.py +0 -38
- schemathesis/utils.py +0 -436
- schemathesis-3.13.0.dist-info/METADATA +0 -202
- schemathesis-3.13.0.dist-info/RECORD +0 -91
- schemathesis-3.13.0.dist-info/entry_points.txt +0 -6
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
schemathesis/exceptions.py
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
from hashlib import sha1
|
|
2
|
-
from json import JSONDecodeError
|
|
3
|
-
from typing import TYPE_CHECKING, Any, Callable, Dict, NoReturn, Optional, 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
|
-
|
|
23
|
-
def __init__(self, *args: Any, context: Optional[FailureContext] = None):
|
|
24
|
-
super().__init__(*args)
|
|
25
|
-
self.context = context
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
CACHE: Dict[Union[str, int], Type[CheckFailed]] = {}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def get_exception(name: str) -> Type[CheckFailed]:
|
|
32
|
-
"""Create a new exception class with provided name or fetch one from the cache."""
|
|
33
|
-
if name in CACHE:
|
|
34
|
-
exception_class = CACHE[name]
|
|
35
|
-
else:
|
|
36
|
-
exception_class = type(name, (CheckFailed,), {})
|
|
37
|
-
exception_class.__qualname__ = CheckFailed.__name__
|
|
38
|
-
exception_class.__name__ = CheckFailed.__name__
|
|
39
|
-
CACHE[name] = exception_class
|
|
40
|
-
return exception_class
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _get_hashed_exception(prefix: str, message: str) -> Type[CheckFailed]:
|
|
44
|
-
"""Give different exceptions for different error messages."""
|
|
45
|
-
messages_digest = sha1(message.encode("utf-8")).hexdigest()
|
|
46
|
-
name = f"{prefix}{messages_digest}"
|
|
47
|
-
return get_exception(name)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def get_grouped_exception(prefix: str, *exceptions: AssertionError) -> Type[CheckFailed]:
|
|
51
|
-
# The prefix is needed to distinguish multiple operations with the same error messages
|
|
52
|
-
# that are coming from different operations
|
|
53
|
-
messages = [exception.args[0] for exception in exceptions]
|
|
54
|
-
message = "".join(messages)
|
|
55
|
-
return _get_hashed_exception("GroupedException", f"{prefix}{message}")
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def get_server_error(status_code: int) -> Type[CheckFailed]:
|
|
59
|
-
"""Return new exception for the Internal Server Error cases."""
|
|
60
|
-
name = f"ServerError{status_code}"
|
|
61
|
-
return get_exception(name)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def get_status_code_error(status_code: int) -> Type[CheckFailed]:
|
|
65
|
-
"""Return new exception for an unexpected status code."""
|
|
66
|
-
name = f"StatusCodeError{status_code}"
|
|
67
|
-
return get_exception(name)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def get_response_type_error(expected: str, received: str) -> Type[CheckFailed]:
|
|
71
|
-
"""Return new exception for an unexpected response type."""
|
|
72
|
-
name = f"SchemaValidationError{expected}_{received}"
|
|
73
|
-
return get_exception(name)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def get_malformed_media_type_error(media_type: str) -> Type[CheckFailed]:
|
|
77
|
-
name = f"MalformedMediaType{media_type}"
|
|
78
|
-
return get_exception(name)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def get_missing_content_type_error() -> Type[CheckFailed]:
|
|
82
|
-
"""Return new exception for a missing Content-Type header."""
|
|
83
|
-
return get_exception("MissingContentTypeError")
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def get_schema_validation_error(exception: ValidationError) -> Type[CheckFailed]:
|
|
87
|
-
"""Return new exception for schema validation error."""
|
|
88
|
-
return _get_hashed_exception("SchemaValidationError", str(exception))
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def get_response_parsing_error(exception: JSONDecodeError) -> Type[CheckFailed]:
|
|
92
|
-
"""Return new exception for response parsing error."""
|
|
93
|
-
return _get_hashed_exception("ResponseParsingError", str(exception))
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def get_headers_error(message: str) -> Type[CheckFailed]:
|
|
97
|
-
"""Return new exception for missing headers."""
|
|
98
|
-
return _get_hashed_exception("MissingHeadersError", message)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def get_timeout_error(deadline: Union[float, int]) -> Type[CheckFailed]:
|
|
102
|
-
"""Request took too long."""
|
|
103
|
-
return _get_hashed_exception("TimeoutError", str(deadline))
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
@attr.s(slots=True)
|
|
107
|
-
class InvalidSchema(Exception):
|
|
108
|
-
"""Schema associated with an API operation contains an error."""
|
|
109
|
-
|
|
110
|
-
__module__ = "builtins"
|
|
111
|
-
message: Optional[str] = attr.ib(default=None)
|
|
112
|
-
path: Optional[str] = attr.ib(default=None)
|
|
113
|
-
method: Optional[str] = attr.ib(default=None)
|
|
114
|
-
full_path: Optional[str] = attr.ib(default=None)
|
|
115
|
-
|
|
116
|
-
def as_failing_test_function(self) -> Callable:
|
|
117
|
-
"""Create a test function that will fail.
|
|
118
|
-
|
|
119
|
-
This approach allows us to use default pytest reporting style for operation-level schema errors.
|
|
120
|
-
"""
|
|
121
|
-
|
|
122
|
-
def actual_test(*args: Any, **kwargs: Any) -> NoReturn:
|
|
123
|
-
__tracebackhide__ = True # pylint: disable=unused-variable
|
|
124
|
-
raise self
|
|
125
|
-
|
|
126
|
-
return actual_test
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
class DeadlineExceeded(Exception):
|
|
130
|
-
"""Test took too long to run."""
|
|
131
|
-
|
|
132
|
-
__module__ = "builtins"
|
|
133
|
-
|
|
134
|
-
@classmethod
|
|
135
|
-
def from_exc(cls, exc: hypothesis.errors.DeadlineExceeded) -> "DeadlineExceeded":
|
|
136
|
-
runtime = exc.runtime.total_seconds() * 1000
|
|
137
|
-
deadline = exc.deadline.total_seconds() * 1000
|
|
138
|
-
return cls(
|
|
139
|
-
f"API response time is too slow! It took {runtime:.2f}ms, which exceeds the deadline of {deadline:.2f}ms.\n"
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
class SchemaLoadingError(ValueError):
|
|
144
|
-
"""Failed to load an API schema."""
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
class NonCheckError(Exception):
|
|
148
|
-
"""An error happened in side the runner, but is not related to failed checks.
|
|
149
|
-
|
|
150
|
-
Used primarily to not let Hypothesis to consider the test as flaky or detect multiple failures as we handle it
|
|
151
|
-
on our side.
|
|
152
|
-
"""
|
|
153
|
-
|
|
154
|
-
__module__ = "builtins"
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
class InternalError(Exception):
|
|
158
|
-
"""Internal error in Schemathesis."""
|
|
159
|
-
|
|
160
|
-
__module__ = "builtins"
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
SERIALIZATION_NOT_POSSIBLE_MESSAGE = (
|
|
164
|
-
f"Schemathesis can't serialize data to any of the defined media types: {{}} \n{SERIALIZERS_SUGGESTION_MESSAGE}"
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
class SerializationNotPossible(Exception):
|
|
169
|
-
"""Not possible to serialize to any of the media types defined for some API operation.
|
|
170
|
-
|
|
171
|
-
Usually, there is still `application/json` along with less common ones, but this error happens when there is no
|
|
172
|
-
media type that Schemathesis knows how to serialize data to.
|
|
173
|
-
"""
|
|
174
|
-
|
|
175
|
-
__module__ = "builtins"
|
|
176
|
-
|
|
177
|
-
@classmethod
|
|
178
|
-
def from_media_types(cls, *media_types: str) -> "SerializationNotPossible":
|
|
179
|
-
return cls(SERIALIZATION_NOT_POSSIBLE_MESSAGE.format(", ".join(media_types)))
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
class InvalidRegularExpression(Exception):
|
|
183
|
-
__module__ = "builtins"
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
@attr.s # pragma: no mutate
|
|
187
|
-
class HTTPError(Exception):
|
|
188
|
-
response: "GenericResponse" = attr.ib() # pragma: no mutate
|
|
189
|
-
url: str = attr.ib() # pragma: no mutate
|
|
190
|
-
|
|
191
|
-
@classmethod
|
|
192
|
-
def raise_for_status(cls, response: requests.Response) -> None:
|
|
193
|
-
try:
|
|
194
|
-
response.raise_for_status()
|
|
195
|
-
except requests.HTTPError as exc:
|
|
196
|
-
raise cls(response=response, url=response.url) from exc
|
|
197
|
-
|
|
198
|
-
@classmethod
|
|
199
|
-
def check_response(cls, response: requests.Response, schema_path: str) -> None:
|
|
200
|
-
# Raising exception to provide unified behavior
|
|
201
|
-
# E.g. it will be handled in CLI - a proper error message will be shown
|
|
202
|
-
if 400 <= response.status_code < 600:
|
|
203
|
-
raise cls(response=response, url=schema_path)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
class UsageError(Exception):
|
|
207
|
-
"""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,216 +0,0 @@
|
|
|
1
|
-
from functools import partial
|
|
2
|
-
from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, TypeVar, cast
|
|
3
|
-
|
|
4
|
-
import pytest
|
|
5
|
-
from _pytest import fixtures, nodes
|
|
6
|
-
from _pytest.config import hookimpl
|
|
7
|
-
from _pytest.fixtures import FuncFixtureInfo
|
|
8
|
-
from _pytest.nodes import Node
|
|
9
|
-
from _pytest.python import Class, Function, FunctionDefinition, Metafunc, Module, PyCollector
|
|
10
|
-
from hypothesis.errors import InvalidArgument
|
|
11
|
-
from hypothesis_jsonschema._canonicalise import HypothesisRefResolutionError
|
|
12
|
-
|
|
13
|
-
from .. import DataGenerationMethod
|
|
14
|
-
from .._hypothesis import create_test
|
|
15
|
-
from ..constants import IS_PYTEST_ABOVE_54, RECURSIVE_REFERENCE_ERROR_MESSAGE
|
|
16
|
-
from ..exceptions import InvalidSchema
|
|
17
|
-
from ..models import APIOperation
|
|
18
|
-
from ..utils import (
|
|
19
|
-
PARAMETRIZE_MARKER,
|
|
20
|
-
Ok,
|
|
21
|
-
Result,
|
|
22
|
-
fail_on_no_matches,
|
|
23
|
-
get_given_args,
|
|
24
|
-
get_given_kwargs,
|
|
25
|
-
is_given_applied,
|
|
26
|
-
is_schemathesis_test,
|
|
27
|
-
merge_given_args,
|
|
28
|
-
validate_given_args,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
T = TypeVar("T", bound=Node)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def create(cls: Type[T], *args: Any, **kwargs: Any) -> T:
|
|
35
|
-
if IS_PYTEST_ABOVE_54:
|
|
36
|
-
return cls.from_parent(*args, **kwargs) # type: ignore
|
|
37
|
-
return cls(*args, **kwargs)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class SchemathesisFunction(Function): # pylint: disable=too-many-ancestors
|
|
41
|
-
def __init__(
|
|
42
|
-
self,
|
|
43
|
-
*args: Any,
|
|
44
|
-
test_func: Callable,
|
|
45
|
-
test_name: Optional[str] = None,
|
|
46
|
-
data_generation_method: DataGenerationMethod,
|
|
47
|
-
**kwargs: Any,
|
|
48
|
-
) -> None:
|
|
49
|
-
super().__init__(*args, **kwargs)
|
|
50
|
-
self.test_function = test_func
|
|
51
|
-
self.test_name = test_name
|
|
52
|
-
self.data_generation_method = data_generation_method
|
|
53
|
-
|
|
54
|
-
def _getobj(self) -> partial:
|
|
55
|
-
"""Tests defined as methods require `self` as the first argument.
|
|
56
|
-
|
|
57
|
-
This method is called only for this case.
|
|
58
|
-
"""
|
|
59
|
-
return partial(self.obj, self.parent.obj) # type: ignore
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class SchemathesisCase(PyCollector):
|
|
63
|
-
def __init__(self, test_function: Callable, *args: Any, **kwargs: Any) -> None:
|
|
64
|
-
self.given_kwargs: Optional[Dict[str, Any]]
|
|
65
|
-
given_args = get_given_args(test_function)
|
|
66
|
-
given_kwargs = get_given_kwargs(test_function)
|
|
67
|
-
|
|
68
|
-
def _init_with_valid_test(_test_function: Callable, _args: Tuple, _kwargs: Dict[str, Any]) -> None:
|
|
69
|
-
self.test_function = _test_function
|
|
70
|
-
self.is_invalid_test = False
|
|
71
|
-
self.given_kwargs = merge_given_args(test_function, _args, _kwargs)
|
|
72
|
-
|
|
73
|
-
if is_given_applied(test_function):
|
|
74
|
-
failing_test = validate_given_args(test_function, given_args, given_kwargs)
|
|
75
|
-
if failing_test is not None:
|
|
76
|
-
self.test_function = failing_test
|
|
77
|
-
self.is_invalid_test = True
|
|
78
|
-
self.given_kwargs = None
|
|
79
|
-
else:
|
|
80
|
-
_init_with_valid_test(test_function, given_args, given_kwargs)
|
|
81
|
-
else:
|
|
82
|
-
_init_with_valid_test(test_function, given_args, given_kwargs)
|
|
83
|
-
self.schemathesis_case = getattr(test_function, PARAMETRIZE_MARKER)
|
|
84
|
-
super().__init__(*args, **kwargs)
|
|
85
|
-
|
|
86
|
-
def _get_test_name(self, operation: APIOperation, data_generation_method: DataGenerationMethod) -> str:
|
|
87
|
-
return f"{self.name}[{operation.verbose_name}][{data_generation_method.as_short_name()}]"
|
|
88
|
-
|
|
89
|
-
def _gen_items(
|
|
90
|
-
self, result: Result[APIOperation, InvalidSchema], data_generation_method: DataGenerationMethod
|
|
91
|
-
) -> Generator[SchemathesisFunction, None, None]:
|
|
92
|
-
"""Generate all tests for the given API operation.
|
|
93
|
-
|
|
94
|
-
Could produce more than one test item if
|
|
95
|
-
parametrization is applied via ``pytest.mark.parametrize`` or ``pytest_generate_tests``.
|
|
96
|
-
|
|
97
|
-
This implementation is based on the original one in pytest, but with slight adjustments
|
|
98
|
-
to produce tests out of hypothesis ones.
|
|
99
|
-
"""
|
|
100
|
-
if isinstance(result, Ok):
|
|
101
|
-
operation = result.ok()
|
|
102
|
-
if self.is_invalid_test:
|
|
103
|
-
funcobj = self.test_function
|
|
104
|
-
else:
|
|
105
|
-
funcobj = create_test(
|
|
106
|
-
operation=operation,
|
|
107
|
-
test=self.test_function,
|
|
108
|
-
_given_kwargs=self.given_kwargs,
|
|
109
|
-
data_generation_method=data_generation_method,
|
|
110
|
-
)
|
|
111
|
-
name = self._get_test_name(operation, data_generation_method)
|
|
112
|
-
else:
|
|
113
|
-
error = result.err()
|
|
114
|
-
funcobj = error.as_failing_test_function()
|
|
115
|
-
name = self.name
|
|
116
|
-
# `full_path` is always available in this case
|
|
117
|
-
if error.method:
|
|
118
|
-
name += f"[{error.method.upper()} {error.full_path}]"
|
|
119
|
-
else:
|
|
120
|
-
name += f"[{error.full_path}]"
|
|
121
|
-
name += f"[{data_generation_method.as_short_name()}]"
|
|
122
|
-
|
|
123
|
-
cls = self._get_class_parent()
|
|
124
|
-
definition: FunctionDefinition = create(FunctionDefinition, name=self.name, parent=self.parent, callobj=funcobj)
|
|
125
|
-
fixturemanager = self.session._fixturemanager
|
|
126
|
-
fixtureinfo = fixturemanager.getfixtureinfo(definition, funcobj, cls)
|
|
127
|
-
|
|
128
|
-
metafunc = self._parametrize(cls, definition, fixtureinfo)
|
|
129
|
-
|
|
130
|
-
if not metafunc._calls:
|
|
131
|
-
yield create(
|
|
132
|
-
SchemathesisFunction,
|
|
133
|
-
name=name,
|
|
134
|
-
parent=self.parent,
|
|
135
|
-
callobj=funcobj,
|
|
136
|
-
fixtureinfo=fixtureinfo,
|
|
137
|
-
test_func=self.test_function,
|
|
138
|
-
originalname=self.name,
|
|
139
|
-
data_generation_method=data_generation_method,
|
|
140
|
-
)
|
|
141
|
-
else:
|
|
142
|
-
fixtures.add_funcarg_pseudo_fixture_def(self.parent, metafunc, fixturemanager) # type: ignore[arg-type]
|
|
143
|
-
fixtureinfo.prune_dependency_tree()
|
|
144
|
-
for callspec in metafunc._calls:
|
|
145
|
-
subname = f"{name}[{callspec.id}]"
|
|
146
|
-
yield create(
|
|
147
|
-
SchemathesisFunction,
|
|
148
|
-
name=subname,
|
|
149
|
-
parent=self.parent,
|
|
150
|
-
callspec=callspec,
|
|
151
|
-
callobj=funcobj,
|
|
152
|
-
fixtureinfo=fixtureinfo,
|
|
153
|
-
keywords={callspec.id: True},
|
|
154
|
-
originalname=name,
|
|
155
|
-
test_func=self.test_function,
|
|
156
|
-
data_generation_method=data_generation_method,
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
def _get_class_parent(self) -> Optional[Type]:
|
|
160
|
-
clscol = self.getparent(Class)
|
|
161
|
-
return clscol.obj if clscol else None
|
|
162
|
-
|
|
163
|
-
def _parametrize(
|
|
164
|
-
self, cls: Optional[Type], definition: FunctionDefinition, fixtureinfo: FuncFixtureInfo
|
|
165
|
-
) -> Metafunc:
|
|
166
|
-
parent = self.getparent(Module)
|
|
167
|
-
module = parent.obj if parent is not None else parent
|
|
168
|
-
metafunc = Metafunc(definition, fixtureinfo, self.config, cls=cls, module=module)
|
|
169
|
-
methods = []
|
|
170
|
-
if hasattr(module, "pytest_generate_tests"):
|
|
171
|
-
methods.append(module.pytest_generate_tests)
|
|
172
|
-
if hasattr(cls, "pytest_generate_tests"):
|
|
173
|
-
cls = cast(Type, cls)
|
|
174
|
-
methods.append(cls().pytest_generate_tests)
|
|
175
|
-
self.ihook.pytest_generate_tests.call_extra(methods, {"metafunc": metafunc})
|
|
176
|
-
return metafunc
|
|
177
|
-
|
|
178
|
-
def collect(self) -> List[Function]: # type: ignore
|
|
179
|
-
"""Generate different test items for all API operations available in the given schema."""
|
|
180
|
-
try:
|
|
181
|
-
items = [
|
|
182
|
-
item
|
|
183
|
-
for data_generation_method in self.schemathesis_case.data_generation_methods
|
|
184
|
-
for operation in self.schemathesis_case.get_all_operations()
|
|
185
|
-
for item in self._gen_items(operation, data_generation_method)
|
|
186
|
-
]
|
|
187
|
-
if not items:
|
|
188
|
-
fail_on_no_matches(self.nodeid)
|
|
189
|
-
return items
|
|
190
|
-
except Exception:
|
|
191
|
-
pytest.fail("Error during collection")
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
@hookimpl(hookwrapper=True) # type:ignore # pragma: no mutate
|
|
195
|
-
def pytest_pycollect_makeitem(collector: nodes.Collector, name: str, obj: Any) -> Generator[None, Any, None]:
|
|
196
|
-
"""Switch to a different collector if the test is parametrized marked by schemathesis."""
|
|
197
|
-
outcome = yield
|
|
198
|
-
if is_schemathesis_test(obj):
|
|
199
|
-
outcome.force_result(create(SchemathesisCase, parent=collector, test_function=obj, name=name))
|
|
200
|
-
else:
|
|
201
|
-
outcome.get_result()
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
@hookimpl(hookwrapper=True) # pragma: no mutate
|
|
205
|
-
def pytest_pyfunc_call(pyfuncitem): # type:ignore
|
|
206
|
-
"""It is possible to have a Hypothesis exception in runtime.
|
|
207
|
-
|
|
208
|
-
For example - kwargs validation is failed for some strategy.
|
|
209
|
-
"""
|
|
210
|
-
outcome = yield
|
|
211
|
-
try:
|
|
212
|
-
outcome.get_result()
|
|
213
|
-
except InvalidArgument as exc:
|
|
214
|
-
raise InvalidSchema(exc.args[0]) from None
|
|
215
|
-
except HypothesisRefResolutionError:
|
|
216
|
-
pytest.skip(RECURSIVE_REFERENCE_ERROR_MESSAGE)
|
schemathesis/failures.py
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Union
|
|
2
|
-
|
|
3
|
-
import attr
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@attr.s(slots=True, repr=False) # pragma: no mutate
|
|
7
|
-
class FailureContext:
|
|
8
|
-
"""Additional data specific to certain failure kind."""
|
|
9
|
-
|
|
10
|
-
# Short description of what happened
|
|
11
|
-
title: str
|
|
12
|
-
# A longer one
|
|
13
|
-
message: str
|
|
14
|
-
type: str
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@attr.s(slots=True, repr=False)
|
|
18
|
-
class ValidationErrorContext(FailureContext):
|
|
19
|
-
"""Additional information about JSON Schema validation errors."""
|
|
20
|
-
|
|
21
|
-
validation_message: str = attr.ib()
|
|
22
|
-
schema_path: List[Union[str, int]] = attr.ib()
|
|
23
|
-
schema: Union[Dict[str, Any], bool] = attr.ib()
|
|
24
|
-
instance_path: List[Union[str, int]] = attr.ib()
|
|
25
|
-
instance: Union[None, bool, float, str, list, Dict[str, Any]] = attr.ib()
|
|
26
|
-
title: str = attr.ib(default="Non-conforming response payload")
|
|
27
|
-
message: str = attr.ib(default="Response does not conform to the defined schema")
|
|
28
|
-
type: str = attr.ib(default="json_schema")
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@attr.s(slots=True, repr=False)
|
|
32
|
-
class JSONDecodeErrorContext(FailureContext):
|
|
33
|
-
"""Failed to decode JSON."""
|
|
34
|
-
|
|
35
|
-
validation_message: str = attr.ib()
|
|
36
|
-
document: str = attr.ib()
|
|
37
|
-
position: int = attr.ib()
|
|
38
|
-
lineno: int = attr.ib()
|
|
39
|
-
colno: int = attr.ib()
|
|
40
|
-
title: str = attr.ib(default="JSON deserialization error")
|
|
41
|
-
message: str = attr.ib(default="Response is not a valid JSON")
|
|
42
|
-
type: str = attr.ib(default="json_decode")
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@attr.s(slots=True, repr=False)
|
|
46
|
-
class ServerError(FailureContext):
|
|
47
|
-
status_code: int = attr.ib()
|
|
48
|
-
title: str = attr.ib(default="Internal server error")
|
|
49
|
-
message: str = attr.ib(default="Server got itself in trouble")
|
|
50
|
-
type: str = attr.ib(default="server_error")
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@attr.s(slots=True, repr=False)
|
|
54
|
-
class MissingContentType(FailureContext):
|
|
55
|
-
"""Content type header is missing."""
|
|
56
|
-
|
|
57
|
-
media_types: List[str] = attr.ib()
|
|
58
|
-
title: str = attr.ib(default="Missing Content-Type header")
|
|
59
|
-
message: str = attr.ib(default="Response is missing the `Content-Type` header")
|
|
60
|
-
type: str = attr.ib(default="missing_content_type")
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
@attr.s(slots=True, repr=False)
|
|
64
|
-
class UndefinedContentType(FailureContext):
|
|
65
|
-
"""Response has Content-Type that is not defined in the schema."""
|
|
66
|
-
|
|
67
|
-
content_type: str = attr.ib()
|
|
68
|
-
defined_content_types: List[str] = attr.ib()
|
|
69
|
-
title: str = attr.ib(default="Undefined Content-Type")
|
|
70
|
-
message: str = attr.ib(default="Response has `Content-Type` that is not declared in the schema")
|
|
71
|
-
type: str = attr.ib(default="undefined_content_type")
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
@attr.s(slots=True, repr=False)
|
|
75
|
-
class UndefinedStatusCode(FailureContext):
|
|
76
|
-
"""Response has a status code that is not defined in the schema."""
|
|
77
|
-
|
|
78
|
-
# Response's status code
|
|
79
|
-
status_code: int = attr.ib()
|
|
80
|
-
# Status codes as defined in schema
|
|
81
|
-
defined_status_codes: List[str] = attr.ib()
|
|
82
|
-
# Defined status code with expanded wildcards
|
|
83
|
-
allowed_status_codes: List[int] = attr.ib()
|
|
84
|
-
title: str = attr.ib(default="Undefined status code")
|
|
85
|
-
message: str = attr.ib(default="Response has a status code that is not declared in the schema")
|
|
86
|
-
type: str = attr.ib(default="undefined_status_code")
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@attr.s(slots=True, repr=False)
|
|
90
|
-
class MissingHeaders(FailureContext):
|
|
91
|
-
"""Some required headers are missing."""
|
|
92
|
-
|
|
93
|
-
missing_headers: List[str] = attr.ib()
|
|
94
|
-
title: str = attr.ib(default="Missing required headers")
|
|
95
|
-
message: str = attr.ib(default="Response is missing headers required by the schema")
|
|
96
|
-
type: str = attr.ib(default="missing_headers")
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
@attr.s(slots=True, repr=False)
|
|
100
|
-
class MalformedMediaType(FailureContext):
|
|
101
|
-
"""Media type name is malformed.
|
|
102
|
-
|
|
103
|
-
Example: `application-json` instead of `application/json`
|
|
104
|
-
"""
|
|
105
|
-
|
|
106
|
-
actual: str = attr.ib()
|
|
107
|
-
defined: str = attr.ib()
|
|
108
|
-
title: str = attr.ib(default="Malformed media type name")
|
|
109
|
-
message: str = attr.ib(default="Media type name is not valid")
|
|
110
|
-
type: str = attr.ib(default="malformed_media_type")
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
@attr.s(slots=True, repr=False)
|
|
114
|
-
class ResponseTimeExceeded(FailureContext):
|
|
115
|
-
"""Response took longer than expected."""
|
|
116
|
-
|
|
117
|
-
elapsed: float = attr.ib()
|
|
118
|
-
deadline: int = attr.ib()
|
|
119
|
-
title: str = attr.ib(default="Response time exceeded")
|
|
120
|
-
message: str = attr.ib(default="Response time exceeds the deadline")
|
|
121
|
-
type: str = attr.ib(default="response_time_exceeded")
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@attr.s(slots=True, repr=False)
|
|
125
|
-
class RequestTimeout(FailureContext):
|
|
126
|
-
"""Request took longer than timeout."""
|
|
127
|
-
|
|
128
|
-
timeout: int = attr.ib()
|
|
129
|
-
title: str = attr.ib(default="Request timeout")
|
|
130
|
-
message: str = attr.ib(default="The request timed out")
|
|
131
|
-
type: str = attr.ib(default="request_timeout")
|
schemathesis/fixups/__init__.py
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from typing import Iterable, Optional
|
|
2
|
-
|
|
3
|
-
from . import fast_api
|
|
4
|
-
|
|
5
|
-
ALL_FIXUPS = {"fast_api": fast_api}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def install(fixups: Optional[Iterable[str]] = None) -> None:
|
|
9
|
-
"""Install fixups.
|
|
10
|
-
|
|
11
|
-
Without the first argument installs all available fixups.
|
|
12
|
-
|
|
13
|
-
:param fixups: Names of fixups to install.
|
|
14
|
-
"""
|
|
15
|
-
fixups = fixups or list(ALL_FIXUPS.keys())
|
|
16
|
-
for name in fixups:
|
|
17
|
-
ALL_FIXUPS[name].install() # type: ignore
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def uninstall(fixups: Optional[Iterable[str]] = None) -> None:
|
|
21
|
-
"""Uninstall fixups.
|
|
22
|
-
|
|
23
|
-
Without the first argument uninstalls all available fixups.
|
|
24
|
-
|
|
25
|
-
:param fixups: Names of fixups to uninstall.
|
|
26
|
-
"""
|
|
27
|
-
fixups = fixups or list(ALL_FIXUPS.keys())
|
|
28
|
-
for name in fixups:
|
|
29
|
-
ALL_FIXUPS[name].uninstall() # type: ignore
|
schemathesis/fixups/fast_api.py
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
from typing import Any, Dict
|
|
2
|
-
|
|
3
|
-
from ..hooks import HookContext, register, unregister
|
|
4
|
-
from ..utils import traverse_schema
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def install() -> None:
|
|
8
|
-
register(before_load_schema)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def uninstall() -> None:
|
|
12
|
-
unregister(before_load_schema)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def before_load_schema(context: HookContext, schema: Dict[str, Any]) -> None:
|
|
16
|
-
traverse_schema(schema, _handle_boundaries)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _handle_boundaries(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
20
|
-
"""Convert Draft 7 keywords to Draft 4 compatible versions.
|
|
21
|
-
|
|
22
|
-
FastAPI uses ``pydantic``, which generates Draft 7 compatible schemas.
|
|
23
|
-
"""
|
|
24
|
-
for boundary_name, boundary_exclusive_name in (("maximum", "exclusiveMaximum"), ("minimum", "exclusiveMinimum")):
|
|
25
|
-
value = schema.get(boundary_exclusive_name)
|
|
26
|
-
# `bool` check is needed, since in Python `True` is an instance of `int`
|
|
27
|
-
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
|
28
|
-
schema[boundary_exclusive_name] = True
|
|
29
|
-
schema[boundary_name] = value
|
|
30
|
-
return schema
|