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/failures.py
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Optional, Tuple, 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
|
-
def unique_by_key(self, check_message: Optional[str]) -> Tuple[str, ...]:
|
|
17
|
-
"""A key to distinguish different failure contexts."""
|
|
18
|
-
return (check_message or self.message,)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@attr.s(slots=True, repr=False)
|
|
22
|
-
class ValidationErrorContext(FailureContext):
|
|
23
|
-
"""Additional information about JSON Schema validation errors."""
|
|
24
|
-
|
|
25
|
-
validation_message: str = attr.ib()
|
|
26
|
-
schema_path: List[Union[str, int]] = attr.ib()
|
|
27
|
-
schema: Union[Dict[str, Any], bool] = attr.ib()
|
|
28
|
-
instance_path: List[Union[str, int]] = attr.ib()
|
|
29
|
-
instance: Union[None, bool, float, str, list, Dict[str, Any]] = attr.ib()
|
|
30
|
-
title: str = attr.ib(default="Non-conforming response payload")
|
|
31
|
-
message: str = attr.ib(default="Response does not conform to the defined schema")
|
|
32
|
-
type: str = attr.ib(default="json_schema")
|
|
33
|
-
|
|
34
|
-
def unique_by_key(self, check_message: Optional[str]) -> Tuple[str, ...]:
|
|
35
|
-
# Deduplicate by JSON Schema path. All errors that happened on this sub-schema will be deduplicated
|
|
36
|
-
return ("/".join(map(str, self.schema_path)),)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@attr.s(slots=True, repr=False)
|
|
40
|
-
class JSONDecodeErrorContext(FailureContext):
|
|
41
|
-
"""Failed to decode JSON."""
|
|
42
|
-
|
|
43
|
-
validation_message: str = attr.ib()
|
|
44
|
-
document: str = attr.ib()
|
|
45
|
-
position: int = attr.ib()
|
|
46
|
-
lineno: int = attr.ib()
|
|
47
|
-
colno: int = attr.ib()
|
|
48
|
-
title: str = attr.ib(default="JSON deserialization error")
|
|
49
|
-
message: str = attr.ib(default="Response is not a valid JSON")
|
|
50
|
-
type: str = attr.ib(default="json_decode")
|
|
51
|
-
|
|
52
|
-
def unique_by_key(self, check_message: Optional[str]) -> Tuple[str, ...]:
|
|
53
|
-
# Treat different JSON decoding failures as the same issue
|
|
54
|
-
# Payloads often contain dynamic data and distinguishing it by the error location still would not be sufficient
|
|
55
|
-
# as it may be different on different dynamic payloads
|
|
56
|
-
return (self.title,)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@attr.s(slots=True, repr=False)
|
|
60
|
-
class ServerError(FailureContext):
|
|
61
|
-
status_code: int = attr.ib()
|
|
62
|
-
title: str = attr.ib(default="Internal server error")
|
|
63
|
-
message: str = attr.ib(default="Server got itself in trouble")
|
|
64
|
-
type: str = attr.ib(default="server_error")
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@attr.s(slots=True, repr=False)
|
|
68
|
-
class MissingContentType(FailureContext):
|
|
69
|
-
"""Content type header is missing."""
|
|
70
|
-
|
|
71
|
-
media_types: List[str] = attr.ib()
|
|
72
|
-
title: str = attr.ib(default="Missing Content-Type header")
|
|
73
|
-
message: str = attr.ib(default="Response is missing the `Content-Type` header")
|
|
74
|
-
type: str = attr.ib(default="missing_content_type")
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
@attr.s(slots=True, repr=False)
|
|
78
|
-
class UndefinedContentType(FailureContext):
|
|
79
|
-
"""Response has Content-Type that is not defined in the schema."""
|
|
80
|
-
|
|
81
|
-
content_type: str = attr.ib()
|
|
82
|
-
defined_content_types: List[str] = attr.ib()
|
|
83
|
-
title: str = attr.ib(default="Undefined Content-Type")
|
|
84
|
-
message: str = attr.ib(default="Response has `Content-Type` that is not declared in the schema")
|
|
85
|
-
type: str = attr.ib(default="undefined_content_type")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@attr.s(slots=True, repr=False)
|
|
89
|
-
class UndefinedStatusCode(FailureContext):
|
|
90
|
-
"""Response has a status code that is not defined in the schema."""
|
|
91
|
-
|
|
92
|
-
# Response's status code
|
|
93
|
-
status_code: int = attr.ib()
|
|
94
|
-
# Status codes as defined in schema
|
|
95
|
-
defined_status_codes: List[str] = attr.ib()
|
|
96
|
-
# Defined status code with expanded wildcards
|
|
97
|
-
allowed_status_codes: List[int] = attr.ib()
|
|
98
|
-
title: str = attr.ib(default="Undefined status code")
|
|
99
|
-
message: str = attr.ib(default="Response has a status code that is not declared in the schema")
|
|
100
|
-
type: str = attr.ib(default="undefined_status_code")
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@attr.s(slots=True, repr=False)
|
|
104
|
-
class MissingHeaders(FailureContext):
|
|
105
|
-
"""Some required headers are missing."""
|
|
106
|
-
|
|
107
|
-
missing_headers: List[str] = attr.ib()
|
|
108
|
-
title: str = attr.ib(default="Missing required headers")
|
|
109
|
-
message: str = attr.ib(default="Response is missing headers required by the schema")
|
|
110
|
-
type: str = attr.ib(default="missing_headers")
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
@attr.s(slots=True, repr=False)
|
|
114
|
-
class MalformedMediaType(FailureContext):
|
|
115
|
-
"""Media type name is malformed.
|
|
116
|
-
|
|
117
|
-
Example: `application-json` instead of `application/json`
|
|
118
|
-
"""
|
|
119
|
-
|
|
120
|
-
actual: str = attr.ib()
|
|
121
|
-
defined: str = attr.ib()
|
|
122
|
-
title: str = attr.ib(default="Malformed media type name")
|
|
123
|
-
message: str = attr.ib(default="Media type name is not valid")
|
|
124
|
-
type: str = attr.ib(default="malformed_media_type")
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
@attr.s(slots=True, repr=False)
|
|
128
|
-
class ResponseTimeExceeded(FailureContext):
|
|
129
|
-
"""Response took longer than expected."""
|
|
130
|
-
|
|
131
|
-
elapsed: float = attr.ib()
|
|
132
|
-
deadline: int = attr.ib()
|
|
133
|
-
title: str = attr.ib(default="Response time exceeded")
|
|
134
|
-
message: str = attr.ib(default="Response time exceeds the deadline")
|
|
135
|
-
type: str = attr.ib(default="response_time_exceeded")
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
@attr.s(slots=True, repr=False)
|
|
139
|
-
class RequestTimeout(FailureContext):
|
|
140
|
-
"""Request took longer than timeout."""
|
|
141
|
-
|
|
142
|
-
timeout: int = attr.ib()
|
|
143
|
-
title: str = attr.ib(default="Request timeout")
|
|
144
|
-
message: str = attr.ib(default="The request timed out")
|
|
145
|
-
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
|
schemathesis/graphql.py
DELETED
schemathesis/internal.py
DELETED
schemathesis/lazy.py
DELETED
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
from inspect import signature
|
|
2
|
-
from typing import Any, Callable, Dict, Generator, Optional, Type, Union
|
|
3
|
-
|
|
4
|
-
import attr
|
|
5
|
-
import pytest
|
|
6
|
-
from _pytest.fixtures import FixtureRequest
|
|
7
|
-
from hypothesis.core import HypothesisHandle
|
|
8
|
-
from hypothesis.errors import Flaky, MultipleFailures
|
|
9
|
-
from hypothesis.internal.escalation import format_exception, get_interesting_origin, get_trimmed_traceback
|
|
10
|
-
from hypothesis.internal.reflection import impersonate
|
|
11
|
-
from pytest_subtests import SubTests, nullcontext
|
|
12
|
-
|
|
13
|
-
from .auth import AuthStorage
|
|
14
|
-
from .constants import FLAKY_FAILURE_MESSAGE, CodeSampleStyle, DataGenerationMethod
|
|
15
|
-
from .exceptions import CheckFailed, InvalidSchema, SkipTest, get_grouped_exception
|
|
16
|
-
from .hooks import HookDispatcher, HookScope
|
|
17
|
-
from .models import APIOperation
|
|
18
|
-
from .schemas import BaseSchema
|
|
19
|
-
from .types import DataGenerationMethodInput, Filter, GenericTest, NotSet
|
|
20
|
-
from .utils import (
|
|
21
|
-
NOT_SET,
|
|
22
|
-
GivenInput,
|
|
23
|
-
Ok,
|
|
24
|
-
fail_on_no_matches,
|
|
25
|
-
get_given_args,
|
|
26
|
-
get_given_kwargs,
|
|
27
|
-
given_proxy,
|
|
28
|
-
is_given_applied,
|
|
29
|
-
merge_given_args,
|
|
30
|
-
validate_given_args,
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@attr.s(slots=True) # pragma: no mutate
|
|
35
|
-
class LazySchema:
|
|
36
|
-
fixture_name: str = attr.ib() # pragma: no mutate
|
|
37
|
-
base_url: Union[Optional[str], NotSet] = attr.ib(default=NOT_SET) # pragma: no mutate
|
|
38
|
-
method: Optional[Filter] = attr.ib(default=NOT_SET) # pragma: no mutate
|
|
39
|
-
endpoint: Optional[Filter] = attr.ib(default=NOT_SET) # pragma: no mutate
|
|
40
|
-
tag: Optional[Filter] = attr.ib(default=NOT_SET) # pragma: no mutate
|
|
41
|
-
operation_id: Optional[Filter] = attr.ib(default=NOT_SET) # pragma: no mutate
|
|
42
|
-
app: Any = attr.ib(default=NOT_SET) # pragma: no mutate
|
|
43
|
-
hooks: HookDispatcher = attr.ib(factory=lambda: HookDispatcher(scope=HookScope.SCHEMA)) # pragma: no mutate
|
|
44
|
-
auth: AuthStorage = attr.ib(factory=AuthStorage) # pragma: no mutate
|
|
45
|
-
validate_schema: bool = attr.ib(default=True) # pragma: no mutate
|
|
46
|
-
skip_deprecated_operations: bool = attr.ib(default=False) # pragma: no mutate
|
|
47
|
-
data_generation_methods: Union[DataGenerationMethodInput, NotSet] = attr.ib(default=NOT_SET)
|
|
48
|
-
code_sample_style: CodeSampleStyle = attr.ib(default=CodeSampleStyle.default()) # pragma: no mutate
|
|
49
|
-
|
|
50
|
-
def parametrize(
|
|
51
|
-
self,
|
|
52
|
-
method: Optional[Filter] = NOT_SET,
|
|
53
|
-
endpoint: Optional[Filter] = NOT_SET,
|
|
54
|
-
tag: Optional[Filter] = NOT_SET,
|
|
55
|
-
operation_id: Optional[Filter] = NOT_SET,
|
|
56
|
-
validate_schema: Union[bool, NotSet] = NOT_SET,
|
|
57
|
-
skip_deprecated_operations: Union[bool, NotSet] = NOT_SET,
|
|
58
|
-
data_generation_methods: Union[DataGenerationMethodInput, NotSet] = NOT_SET,
|
|
59
|
-
code_sample_style: Union[str, NotSet] = NOT_SET,
|
|
60
|
-
) -> Callable:
|
|
61
|
-
if method is NOT_SET:
|
|
62
|
-
method = self.method
|
|
63
|
-
if endpoint is NOT_SET:
|
|
64
|
-
endpoint = self.endpoint
|
|
65
|
-
if tag is NOT_SET:
|
|
66
|
-
tag = self.tag
|
|
67
|
-
if operation_id is NOT_SET:
|
|
68
|
-
operation_id = self.operation_id
|
|
69
|
-
if data_generation_methods is NOT_SET:
|
|
70
|
-
data_generation_methods = self.data_generation_methods
|
|
71
|
-
if isinstance(code_sample_style, str):
|
|
72
|
-
_code_sample_style = CodeSampleStyle.from_str(code_sample_style)
|
|
73
|
-
else:
|
|
74
|
-
_code_sample_style = self.code_sample_style
|
|
75
|
-
|
|
76
|
-
def wrapper(test: Callable) -> Callable:
|
|
77
|
-
if is_given_applied(test):
|
|
78
|
-
# The user wrapped the test function with `@schema.given`
|
|
79
|
-
# These args & kwargs go as extra to the underlying test generator
|
|
80
|
-
given_args = get_given_args(test)
|
|
81
|
-
given_kwargs = get_given_kwargs(test)
|
|
82
|
-
test_function = validate_given_args(test, given_args, given_kwargs)
|
|
83
|
-
if test_function is not None:
|
|
84
|
-
return test_function
|
|
85
|
-
given_kwargs = merge_given_args(test, given_args, given_kwargs)
|
|
86
|
-
del given_args
|
|
87
|
-
else:
|
|
88
|
-
given_kwargs = {}
|
|
89
|
-
|
|
90
|
-
def wrapped_test(request: FixtureRequest) -> None:
|
|
91
|
-
"""The actual test, which is executed by pytest."""
|
|
92
|
-
__tracebackhide__ = True # pylint: disable=unused-variable
|
|
93
|
-
if hasattr(wrapped_test, "_schemathesis_hooks"):
|
|
94
|
-
test._schemathesis_hooks = wrapped_test._schemathesis_hooks # type: ignore
|
|
95
|
-
schema = get_schema(
|
|
96
|
-
request=request,
|
|
97
|
-
name=self.fixture_name,
|
|
98
|
-
base_url=self.base_url,
|
|
99
|
-
method=method,
|
|
100
|
-
endpoint=endpoint,
|
|
101
|
-
tag=tag,
|
|
102
|
-
operation_id=operation_id,
|
|
103
|
-
hooks=self.hooks,
|
|
104
|
-
auth=self.auth if self.auth.provider is not None else NOT_SET,
|
|
105
|
-
test_function=test,
|
|
106
|
-
validate_schema=validate_schema,
|
|
107
|
-
skip_deprecated_operations=skip_deprecated_operations,
|
|
108
|
-
data_generation_methods=data_generation_methods,
|
|
109
|
-
code_sample_style=_code_sample_style,
|
|
110
|
-
app=self.app,
|
|
111
|
-
)
|
|
112
|
-
fixtures = get_fixtures(test, request, given_kwargs)
|
|
113
|
-
# Changing the node id is required for better reporting - the method and path will appear there
|
|
114
|
-
node_id = request.node._nodeid
|
|
115
|
-
settings = getattr(wrapped_test, "_hypothesis_internal_use_settings", None)
|
|
116
|
-
tests = list(schema.get_all_tests(test, settings, _given_kwargs=given_kwargs))
|
|
117
|
-
if not tests:
|
|
118
|
-
fail_on_no_matches(node_id)
|
|
119
|
-
request.session.testscollected += len(tests)
|
|
120
|
-
suspend_capture_ctx = _get_capturemanager(request)
|
|
121
|
-
subtests = SubTests(request.node.ihook, suspend_capture_ctx, request)
|
|
122
|
-
for result, data_generation_method in tests:
|
|
123
|
-
if isinstance(result, Ok):
|
|
124
|
-
operation, sub_test = result.ok()
|
|
125
|
-
subtests.item._nodeid = _get_node_name(node_id, operation, data_generation_method)
|
|
126
|
-
run_subtest(operation, data_generation_method, fixtures, sub_test, subtests)
|
|
127
|
-
else:
|
|
128
|
-
_schema_error(subtests, result.err(), node_id, data_generation_method)
|
|
129
|
-
subtests.item._nodeid = node_id
|
|
130
|
-
|
|
131
|
-
wrapped_test = pytest.mark.usefixtures(self.fixture_name)(wrapped_test)
|
|
132
|
-
_copy_marks(test, wrapped_test)
|
|
133
|
-
|
|
134
|
-
# Needed to prevent a failure when settings are applied to the test function
|
|
135
|
-
wrapped_test.is_hypothesis_test = True # type: ignore
|
|
136
|
-
wrapped_test.hypothesis = HypothesisHandle(test, wrapped_test, given_kwargs) # type: ignore
|
|
137
|
-
|
|
138
|
-
return wrapped_test
|
|
139
|
-
|
|
140
|
-
return wrapper
|
|
141
|
-
|
|
142
|
-
def given(self, *args: GivenInput, **kwargs: GivenInput) -> Callable:
|
|
143
|
-
return given_proxy(*args, **kwargs)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def _copy_marks(source: Callable, target: Callable) -> None:
|
|
147
|
-
marks = getattr(source, "pytestmark", [])
|
|
148
|
-
# Pytest adds this attribute in `usefixtures`
|
|
149
|
-
target.pytestmark.extend(marks) # type: ignore
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def _get_capturemanager(request: FixtureRequest) -> Generator:
|
|
153
|
-
capturemanager = request.node.config.pluginmanager.get_plugin("capturemanager")
|
|
154
|
-
if capturemanager is not None:
|
|
155
|
-
return capturemanager.global_and_fixture_disabled
|
|
156
|
-
return nullcontext
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def _get_node_name(node_id: str, operation: APIOperation, data_generation_method: DataGenerationMethod) -> str:
|
|
160
|
-
"""Make a test node name. For example: test_api[GET /users]."""
|
|
161
|
-
return f"{node_id}[{operation.method.upper()} {operation.full_path}][{data_generation_method.as_short_name()}]"
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def _get_partial_node_name(node_id: str, data_generation_method: DataGenerationMethod, **kwargs: Any) -> str:
|
|
165
|
-
"""Make a test node name for failing tests caused by schema errors."""
|
|
166
|
-
name = node_id
|
|
167
|
-
if "method" in kwargs:
|
|
168
|
-
name += f"[{kwargs['method']} {kwargs['path']}]"
|
|
169
|
-
else:
|
|
170
|
-
name += f"[{kwargs['path']}]"
|
|
171
|
-
name += f"[{data_generation_method.as_short_name()}]"
|
|
172
|
-
return name
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def run_subtest(
|
|
176
|
-
operation: APIOperation,
|
|
177
|
-
data_generation_method: DataGenerationMethod,
|
|
178
|
-
fixtures: Dict[str, Any],
|
|
179
|
-
sub_test: Callable,
|
|
180
|
-
subtests: SubTests,
|
|
181
|
-
) -> None:
|
|
182
|
-
"""Run the given subtest with pytest fixtures."""
|
|
183
|
-
__tracebackhide__ = True # pylint: disable=unused-variable
|
|
184
|
-
|
|
185
|
-
# Deduplicate found checks in case of Hypothesis finding multiple of them
|
|
186
|
-
failed_checks = {}
|
|
187
|
-
exceptions = []
|
|
188
|
-
inner_test = sub_test.hypothesis.inner_test # type: ignore
|
|
189
|
-
|
|
190
|
-
@impersonate(inner_test) # type: ignore
|
|
191
|
-
def collecting_wrapper(*args: Any, **kwargs: Any) -> None:
|
|
192
|
-
__tracebackhide__ = True # pylint: disable=unused-variable
|
|
193
|
-
try:
|
|
194
|
-
inner_test(*args, **kwargs)
|
|
195
|
-
except CheckFailed as failed:
|
|
196
|
-
failed_checks[failed.__class__] = failed
|
|
197
|
-
raise failed
|
|
198
|
-
except Exception as exception:
|
|
199
|
-
# Deduplicate it later, as it is more costly than for `CheckFailed`
|
|
200
|
-
exceptions.append(exception)
|
|
201
|
-
raise
|
|
202
|
-
|
|
203
|
-
def get_exception_class() -> Type[CheckFailed]:
|
|
204
|
-
return get_grouped_exception("Lazy", *failed_checks.values())
|
|
205
|
-
|
|
206
|
-
sub_test.hypothesis.inner_test = collecting_wrapper # type: ignore
|
|
207
|
-
|
|
208
|
-
with subtests.test(
|
|
209
|
-
verbose_name=operation.verbose_name, data_generation_method=data_generation_method.as_short_name()
|
|
210
|
-
):
|
|
211
|
-
try:
|
|
212
|
-
sub_test(**fixtures)
|
|
213
|
-
except SkipTest as exc:
|
|
214
|
-
pytest.skip(exc.args[0])
|
|
215
|
-
except MultipleFailures as exc:
|
|
216
|
-
# Hypothesis doesn't report the underlying failures in these circumstances, hence we display them manually
|
|
217
|
-
exc_class = get_exception_class()
|
|
218
|
-
failures = "".join(f"{SEPARATOR} {failure.args[0]}" for failure in failed_checks.values())
|
|
219
|
-
unique_exceptions = {get_interesting_origin(exception): exception for exception in exceptions}
|
|
220
|
-
message = (
|
|
221
|
-
f"Schemathesis found {len(failed_checks) + len(unique_exceptions)} distinct sets of failures.{failures}"
|
|
222
|
-
)
|
|
223
|
-
for exception in unique_exceptions.values():
|
|
224
|
-
# Non-check exceptions
|
|
225
|
-
message += f"{SEPARATOR}\n\n"
|
|
226
|
-
tb = get_trimmed_traceback(exception)
|
|
227
|
-
message += format_exception(exception, tb)
|
|
228
|
-
raise exc_class(message, causes=tuple(failed_checks.values())).with_traceback(exc.__traceback__) from None
|
|
229
|
-
except Flaky as exc:
|
|
230
|
-
exc_class = get_exception_class()
|
|
231
|
-
failure = next(iter(failed_checks.values()))
|
|
232
|
-
message = f"{FLAKY_FAILURE_MESSAGE}{failure}"
|
|
233
|
-
# The outer frame is the one for user's test function, take it as the root one
|
|
234
|
-
traceback = exc.__traceback__.tb_next
|
|
235
|
-
# The next one comes from Hypothesis internals - remove it
|
|
236
|
-
traceback.tb_next = None
|
|
237
|
-
raise exc_class(message, causes=tuple(failed_checks.values())).with_traceback(traceback) from None
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
SEPARATOR = "\n===================="
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
def _schema_error(
|
|
244
|
-
subtests: SubTests, error: InvalidSchema, node_id: str, data_generation_method: DataGenerationMethod
|
|
245
|
-
) -> None:
|
|
246
|
-
"""Run a failing test, that will show the underlying problem."""
|
|
247
|
-
sub_test = error.as_failing_test_function()
|
|
248
|
-
# `full_path` is always available in this case
|
|
249
|
-
kwargs = {"path": error.full_path}
|
|
250
|
-
if error.method:
|
|
251
|
-
kwargs["method"] = error.method.upper()
|
|
252
|
-
subtests.item._nodeid = _get_partial_node_name(node_id, data_generation_method, **kwargs)
|
|
253
|
-
with subtests.test(**kwargs):
|
|
254
|
-
sub_test()
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def get_schema(
|
|
258
|
-
*,
|
|
259
|
-
request: FixtureRequest,
|
|
260
|
-
name: str,
|
|
261
|
-
base_url: Union[Optional[str], NotSet] = None,
|
|
262
|
-
method: Optional[Filter] = None,
|
|
263
|
-
endpoint: Optional[Filter] = None,
|
|
264
|
-
tag: Optional[Filter] = None,
|
|
265
|
-
operation_id: Optional[Filter] = None,
|
|
266
|
-
app: Any = None,
|
|
267
|
-
test_function: GenericTest,
|
|
268
|
-
hooks: HookDispatcher,
|
|
269
|
-
auth: Union[AuthStorage, NotSet],
|
|
270
|
-
validate_schema: Union[bool, NotSet] = NOT_SET,
|
|
271
|
-
skip_deprecated_operations: Union[bool, NotSet] = NOT_SET,
|
|
272
|
-
data_generation_methods: Union[DataGenerationMethodInput, NotSet] = NOT_SET,
|
|
273
|
-
code_sample_style: CodeSampleStyle,
|
|
274
|
-
) -> BaseSchema:
|
|
275
|
-
"""Loads a schema from the fixture."""
|
|
276
|
-
schema = request.getfixturevalue(name)
|
|
277
|
-
if not isinstance(schema, BaseSchema):
|
|
278
|
-
raise ValueError(f"The given schema must be an instance of BaseSchema, got: {type(schema)}")
|
|
279
|
-
return schema.clone(
|
|
280
|
-
base_url=base_url,
|
|
281
|
-
method=method,
|
|
282
|
-
endpoint=endpoint,
|
|
283
|
-
tag=tag,
|
|
284
|
-
operation_id=operation_id,
|
|
285
|
-
app=app,
|
|
286
|
-
test_function=test_function,
|
|
287
|
-
hooks=schema.hooks.merge(hooks),
|
|
288
|
-
auth=auth,
|
|
289
|
-
validate_schema=validate_schema,
|
|
290
|
-
skip_deprecated_operations=skip_deprecated_operations,
|
|
291
|
-
data_generation_methods=data_generation_methods,
|
|
292
|
-
code_sample_style=code_sample_style,
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
def get_fixtures(func: Callable, request: FixtureRequest, given_kwargs: Dict[str, Any]) -> Dict[str, Any]:
|
|
297
|
-
"""Load fixtures, needed for the test function."""
|
|
298
|
-
sig = signature(func)
|
|
299
|
-
return {
|
|
300
|
-
name: request.getfixturevalue(name) for name in sig.parameters if name != "case" and name not in given_kwargs
|
|
301
|
-
}
|