schemathesis 3.21.2__py3-none-any.whl → 3.22.1__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 +1 -1
- schemathesis/_compat.py +2 -18
- schemathesis/_dependency_versions.py +1 -6
- schemathesis/_hypothesis.py +15 -12
- schemathesis/_lazy_import.py +3 -2
- schemathesis/_xml.py +12 -11
- schemathesis/auths.py +88 -81
- schemathesis/checks.py +4 -4
- schemathesis/cli/__init__.py +202 -171
- schemathesis/cli/callbacks.py +29 -32
- schemathesis/cli/cassettes.py +25 -25
- schemathesis/cli/context.py +18 -12
- schemathesis/cli/junitxml.py +2 -2
- schemathesis/cli/options.py +10 -11
- schemathesis/cli/output/default.py +64 -34
- schemathesis/code_samples.py +10 -10
- schemathesis/constants.py +1 -1
- schemathesis/contrib/unique_data.py +2 -2
- schemathesis/exceptions.py +55 -42
- schemathesis/extra/_aiohttp.py +2 -2
- schemathesis/extra/_flask.py +2 -2
- schemathesis/extra/_server.py +3 -2
- schemathesis/extra/pytest_plugin.py +10 -10
- schemathesis/failures.py +16 -16
- schemathesis/filters.py +40 -41
- schemathesis/fixups/__init__.py +4 -3
- schemathesis/fixups/fast_api.py +5 -4
- schemathesis/generation/__init__.py +16 -4
- schemathesis/hooks.py +25 -25
- schemathesis/internal/jsonschema.py +4 -3
- schemathesis/internal/transformation.py +3 -2
- schemathesis/lazy.py +39 -31
- schemathesis/loaders.py +8 -8
- schemathesis/models.py +128 -126
- schemathesis/parameters.py +6 -5
- schemathesis/runner/__init__.py +107 -81
- schemathesis/runner/events.py +37 -26
- schemathesis/runner/impl/core.py +86 -81
- schemathesis/runner/impl/solo.py +19 -15
- schemathesis/runner/impl/threadpool.py +40 -22
- schemathesis/runner/serialization.py +67 -40
- schemathesis/sanitization.py +18 -20
- schemathesis/schemas.py +83 -72
- schemathesis/serializers.py +39 -30
- schemathesis/service/ci.py +20 -21
- schemathesis/service/client.py +29 -9
- schemathesis/service/constants.py +1 -0
- schemathesis/service/events.py +2 -2
- schemathesis/service/hosts.py +8 -7
- schemathesis/service/metadata.py +5 -0
- schemathesis/service/models.py +22 -4
- schemathesis/service/report.py +15 -15
- schemathesis/service/serialization.py +23 -27
- schemathesis/service/usage.py +8 -7
- schemathesis/specs/graphql/loaders.py +31 -24
- schemathesis/specs/graphql/nodes.py +3 -2
- schemathesis/specs/graphql/scalars.py +26 -2
- schemathesis/specs/graphql/schemas.py +38 -34
- schemathesis/specs/openapi/_hypothesis.py +62 -44
- schemathesis/specs/openapi/checks.py +10 -10
- schemathesis/specs/openapi/converter.py +10 -9
- schemathesis/specs/openapi/definitions.py +2 -2
- schemathesis/specs/openapi/examples.py +22 -21
- schemathesis/specs/openapi/expressions/nodes.py +5 -4
- schemathesis/specs/openapi/expressions/parser.py +7 -6
- schemathesis/specs/openapi/filters.py +6 -6
- schemathesis/specs/openapi/formats.py +2 -2
- schemathesis/specs/openapi/links.py +19 -21
- schemathesis/specs/openapi/loaders.py +133 -78
- schemathesis/specs/openapi/negative/__init__.py +16 -11
- schemathesis/specs/openapi/negative/mutations.py +11 -10
- schemathesis/specs/openapi/parameters.py +20 -19
- schemathesis/specs/openapi/references.py +21 -20
- schemathesis/specs/openapi/schemas.py +97 -84
- schemathesis/specs/openapi/security.py +25 -24
- schemathesis/specs/openapi/serialization.py +20 -23
- schemathesis/specs/openapi/stateful/__init__.py +12 -11
- schemathesis/specs/openapi/stateful/links.py +7 -7
- schemathesis/specs/openapi/utils.py +4 -3
- schemathesis/specs/openapi/validation.py +3 -2
- schemathesis/stateful/__init__.py +15 -16
- schemathesis/stateful/state_machine.py +9 -9
- schemathesis/targets.py +3 -3
- schemathesis/throttling.py +2 -2
- schemathesis/transports/auth.py +2 -2
- schemathesis/transports/content_types.py +5 -0
- schemathesis/transports/headers.py +3 -2
- schemathesis/transports/responses.py +1 -1
- schemathesis/utils.py +7 -10
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/METADATA +12 -13
- schemathesis-3.22.1.dist-info/RECORD +130 -0
- schemathesis-3.21.2.dist-info/RECORD +0 -130
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/WHEEL +0 -0
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
from typing import overload, Dict, Union, Any, List, Callable
|
|
2
3
|
|
|
3
4
|
JsonValue = Union[Dict[str, Any], List, str, float, int]
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
@overload
|
|
7
|
-
def traverse_schema(schema:
|
|
8
|
+
def traverse_schema(schema: dict[str, Any], callback: Callable, *args: Any, **kwargs: Any) -> dict[str, Any]:
|
|
8
9
|
pass
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@overload
|
|
12
|
-
def traverse_schema(schema:
|
|
13
|
+
def traverse_schema(schema: list, callback: Callable, *args: Any, **kwargs: Any) -> list:
|
|
13
14
|
pass
|
|
14
15
|
|
|
15
16
|
|
|
@@ -23,7 +24,7 @@ def traverse_schema(schema: float, callback: Callable, *args: Any, **kwargs: Any
|
|
|
23
24
|
pass
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
def traverse_schema(schema: JsonValue, callback: Callable[...,
|
|
27
|
+
def traverse_schema(schema: JsonValue, callback: Callable[..., dict[str, Any]], *args: Any, **kwargs: Any) -> JsonValue:
|
|
27
28
|
"""Apply callback recursively to the given schema."""
|
|
28
29
|
if isinstance(schema, dict):
|
|
29
30
|
schema = callback(schema, *args, **kwargs)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Any
|
|
2
3
|
|
|
3
4
|
|
|
4
|
-
def merge_recursively(a:
|
|
5
|
+
def merge_recursively(a: dict[str, Any], b: dict[str, Any]) -> dict[str, Any]:
|
|
5
6
|
"""Merge two dictionaries recursively."""
|
|
6
7
|
for key in b:
|
|
7
8
|
if key in a:
|
schemathesis/lazy.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
from dataclasses import dataclass, field
|
|
2
3
|
from inspect import signature
|
|
3
|
-
from typing import Any, Callable,
|
|
4
|
+
from typing import Any, Callable, Generator
|
|
4
5
|
|
|
5
6
|
import pytest
|
|
6
7
|
from _pytest.fixtures import FixtureRequest
|
|
@@ -15,7 +16,7 @@ from ._compat import MultipleFailures, get_interesting_origin
|
|
|
15
16
|
from .auths import AuthStorage
|
|
16
17
|
from .code_samples import CodeSampleStyle
|
|
17
18
|
from .constants import FLAKY_FAILURE_MESSAGE, NOT_SET
|
|
18
|
-
from .generation import DataGenerationMethodInput
|
|
19
|
+
from .generation import DataGenerationMethodInput, GenerationConfig
|
|
19
20
|
from .exceptions import CheckFailed, OperationSchemaError, SkipTest, get_grouped_exception
|
|
20
21
|
from .hooks import HookDispatcher, HookScope
|
|
21
22
|
from .internal.result import Ok
|
|
@@ -37,34 +38,36 @@ from .utils import (
|
|
|
37
38
|
@dataclass
|
|
38
39
|
class LazySchema:
|
|
39
40
|
fixture_name: str
|
|
40
|
-
base_url:
|
|
41
|
-
method:
|
|
42
|
-
endpoint:
|
|
43
|
-
tag:
|
|
44
|
-
operation_id:
|
|
41
|
+
base_url: str | None | NotSet = NOT_SET
|
|
42
|
+
method: Filter | None = NOT_SET
|
|
43
|
+
endpoint: Filter | None = NOT_SET
|
|
44
|
+
tag: Filter | None = NOT_SET
|
|
45
|
+
operation_id: Filter | None = NOT_SET
|
|
45
46
|
app: Any = NOT_SET
|
|
46
47
|
hooks: HookDispatcher = field(default_factory=lambda: HookDispatcher(scope=HookScope.SCHEMA))
|
|
47
48
|
auth: AuthStorage = field(default_factory=AuthStorage)
|
|
48
49
|
validate_schema: bool = True
|
|
49
50
|
skip_deprecated_operations: bool = False
|
|
50
|
-
data_generation_methods:
|
|
51
|
+
data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET
|
|
52
|
+
generation_config: GenerationConfig | NotSet = NOT_SET
|
|
51
53
|
code_sample_style: CodeSampleStyle = CodeSampleStyle.default()
|
|
52
|
-
rate_limiter:
|
|
54
|
+
rate_limiter: Limiter | None = None
|
|
53
55
|
sanitize_output: bool = True
|
|
54
56
|
|
|
55
|
-
def hook(self, hook:
|
|
57
|
+
def hook(self, hook: str | Callable) -> Callable:
|
|
56
58
|
return self.hooks.register(hook)
|
|
57
59
|
|
|
58
60
|
def parametrize(
|
|
59
61
|
self,
|
|
60
|
-
method:
|
|
61
|
-
endpoint:
|
|
62
|
-
tag:
|
|
63
|
-
operation_id:
|
|
64
|
-
validate_schema:
|
|
65
|
-
skip_deprecated_operations:
|
|
66
|
-
data_generation_methods:
|
|
67
|
-
|
|
62
|
+
method: Filter | None = NOT_SET,
|
|
63
|
+
endpoint: Filter | None = NOT_SET,
|
|
64
|
+
tag: Filter | None = NOT_SET,
|
|
65
|
+
operation_id: Filter | None = NOT_SET,
|
|
66
|
+
validate_schema: bool | NotSet = NOT_SET,
|
|
67
|
+
skip_deprecated_operations: bool | NotSet = NOT_SET,
|
|
68
|
+
data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET,
|
|
69
|
+
generation_config: GenerationConfig | NotSet = NOT_SET,
|
|
70
|
+
code_sample_style: str | NotSet = NOT_SET,
|
|
68
71
|
) -> Callable:
|
|
69
72
|
if method is NOT_SET:
|
|
70
73
|
method = self.method
|
|
@@ -76,6 +79,8 @@ class LazySchema:
|
|
|
76
79
|
operation_id = self.operation_id
|
|
77
80
|
if data_generation_methods is NOT_SET:
|
|
78
81
|
data_generation_methods = self.data_generation_methods
|
|
82
|
+
if generation_config is NOT_SET:
|
|
83
|
+
generation_config = self.generation_config
|
|
79
84
|
if isinstance(code_sample_style, str):
|
|
80
85
|
_code_sample_style = CodeSampleStyle.from_str(code_sample_style)
|
|
81
86
|
else:
|
|
@@ -114,6 +119,7 @@ class LazySchema:
|
|
|
114
119
|
validate_schema=validate_schema,
|
|
115
120
|
skip_deprecated_operations=skip_deprecated_operations,
|
|
116
121
|
data_generation_methods=data_generation_methods,
|
|
122
|
+
generation_config=generation_config,
|
|
117
123
|
code_sample_style=_code_sample_style,
|
|
118
124
|
app=self.app,
|
|
119
125
|
rate_limiter=self.rate_limiter,
|
|
@@ -183,7 +189,7 @@ def _get_partial_node_name(node_id: str, **kwargs: Any) -> str:
|
|
|
183
189
|
|
|
184
190
|
def run_subtest(
|
|
185
191
|
operation: APIOperation,
|
|
186
|
-
fixtures:
|
|
192
|
+
fixtures: dict[str, Any],
|
|
187
193
|
sub_test: Callable,
|
|
188
194
|
subtests: SubTests,
|
|
189
195
|
) -> None:
|
|
@@ -208,7 +214,7 @@ def run_subtest(
|
|
|
208
214
|
exceptions.append(exception)
|
|
209
215
|
raise
|
|
210
216
|
|
|
211
|
-
def get_exception_class() ->
|
|
217
|
+
def get_exception_class() -> type[CheckFailed]:
|
|
212
218
|
return get_grouped_exception("Lazy", *failed_checks.values())
|
|
213
219
|
|
|
214
220
|
sub_test.hypothesis.inner_test = collecting_wrapper # type: ignore
|
|
@@ -264,20 +270,21 @@ def get_schema(
|
|
|
264
270
|
*,
|
|
265
271
|
request: FixtureRequest,
|
|
266
272
|
name: str,
|
|
267
|
-
base_url:
|
|
268
|
-
method:
|
|
269
|
-
endpoint:
|
|
270
|
-
tag:
|
|
271
|
-
operation_id:
|
|
273
|
+
base_url: str | None | NotSet = None,
|
|
274
|
+
method: Filter | None = None,
|
|
275
|
+
endpoint: Filter | None = None,
|
|
276
|
+
tag: Filter | None = None,
|
|
277
|
+
operation_id: Filter | None = None,
|
|
272
278
|
app: Any = None,
|
|
273
279
|
test_function: GenericTest,
|
|
274
280
|
hooks: HookDispatcher,
|
|
275
|
-
auth:
|
|
276
|
-
validate_schema:
|
|
277
|
-
skip_deprecated_operations:
|
|
278
|
-
data_generation_methods:
|
|
281
|
+
auth: AuthStorage | NotSet,
|
|
282
|
+
validate_schema: bool | NotSet = NOT_SET,
|
|
283
|
+
skip_deprecated_operations: bool | NotSet = NOT_SET,
|
|
284
|
+
data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET,
|
|
285
|
+
generation_config: GenerationConfig | NotSet = NOT_SET,
|
|
279
286
|
code_sample_style: CodeSampleStyle,
|
|
280
|
-
rate_limiter:
|
|
287
|
+
rate_limiter: Limiter | None,
|
|
281
288
|
sanitize_output: bool,
|
|
282
289
|
) -> BaseSchema:
|
|
283
290
|
"""Loads a schema from the fixture."""
|
|
@@ -297,13 +304,14 @@ def get_schema(
|
|
|
297
304
|
validate_schema=validate_schema,
|
|
298
305
|
skip_deprecated_operations=skip_deprecated_operations,
|
|
299
306
|
data_generation_methods=data_generation_methods,
|
|
307
|
+
generation_config=generation_config,
|
|
300
308
|
code_sample_style=code_sample_style,
|
|
301
309
|
rate_limiter=rate_limiter,
|
|
302
310
|
sanitize_output=sanitize_output,
|
|
303
311
|
)
|
|
304
312
|
|
|
305
313
|
|
|
306
|
-
def get_fixtures(func: Callable, request: FixtureRequest, given_kwargs:
|
|
314
|
+
def get_fixtures(func: Callable, request: FixtureRequest, given_kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
307
315
|
"""Load fixtures, needed for the test function."""
|
|
308
316
|
sig = signature(func)
|
|
309
317
|
return {
|
schemathesis/loaders.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
import re
|
|
3
3
|
import sys
|
|
4
4
|
from functools import lru_cache
|
|
5
|
-
from typing import Callable, TypeVar,
|
|
5
|
+
from typing import Callable, TypeVar, TYPE_CHECKING, TextIO, Any, BinaryIO
|
|
6
6
|
|
|
7
7
|
from .exceptions import SchemaError, SchemaErrorType, extract_requests_exception_details
|
|
8
8
|
|
|
@@ -19,7 +19,7 @@ def load_schema_from_url(loader: Callable[[], R]) -> R:
|
|
|
19
19
|
try:
|
|
20
20
|
response = loader()
|
|
21
21
|
except requests.RequestException as exc:
|
|
22
|
-
|
|
22
|
+
url = exc.request.url if exc.request is not None else None
|
|
23
23
|
if isinstance(exc, requests.exceptions.SSLError):
|
|
24
24
|
type_ = SchemaErrorType.CONNECTION_SSL
|
|
25
25
|
elif isinstance(exc, requests.exceptions.ConnectionError):
|
|
@@ -27,12 +27,12 @@ def load_schema_from_url(loader: Callable[[], R]) -> R:
|
|
|
27
27
|
else:
|
|
28
28
|
type_ = SchemaErrorType.NETWORK_OTHER
|
|
29
29
|
message, extras = extract_requests_exception_details(exc)
|
|
30
|
-
raise SchemaError(message=message, type=type_, url=
|
|
30
|
+
raise SchemaError(message=message, type=type_, url=url, response=exc.response, extras=extras) from exc
|
|
31
31
|
_raise_for_status(response)
|
|
32
32
|
return response
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def _raise_for_status(response:
|
|
35
|
+
def _raise_for_status(response: GenericResponse) -> None:
|
|
36
36
|
from .transports.responses import get_reason
|
|
37
37
|
|
|
38
38
|
status_code = response.status_code
|
|
@@ -63,8 +63,8 @@ def load_app(path: str) -> Any:
|
|
|
63
63
|
return getattr(module, name)
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
@lru_cache
|
|
67
|
-
def get_yaml_loader() ->
|
|
66
|
+
@lru_cache
|
|
67
|
+
def get_yaml_loader() -> type[yaml.SafeLoader]:
|
|
68
68
|
"""Create a YAML loader, that doesn't parse specific tokens into Python objects."""
|
|
69
69
|
import yaml
|
|
70
70
|
|
|
@@ -73,7 +73,7 @@ def get_yaml_loader() -> Type[yaml.SafeLoader]:
|
|
|
73
73
|
except ImportError:
|
|
74
74
|
from yaml import SafeLoader # type: ignore
|
|
75
75
|
|
|
76
|
-
cls:
|
|
76
|
+
cls: type[yaml.SafeLoader] = type("YAMLLoader", (SafeLoader,), {})
|
|
77
77
|
cls.yaml_implicit_resolvers = {
|
|
78
78
|
key: [(tag, regexp) for tag, regexp in mapping if tag != "tag:yaml.org,2002:timestamp"]
|
|
79
79
|
for key, mapping in cls.yaml_implicit_resolvers.copy().items()
|
|
@@ -95,7 +95,7 @@ def get_yaml_loader() -> Type[yaml.SafeLoader]:
|
|
|
95
95
|
list("-+0123456789."),
|
|
96
96
|
)
|
|
97
97
|
|
|
98
|
-
def construct_mapping(self: SafeLoader, node: yaml.Node, deep: bool = False) ->
|
|
98
|
+
def construct_mapping(self: SafeLoader, node: yaml.Node, deep: bool = False) -> dict[str, Any]:
|
|
99
99
|
if isinstance(node, yaml.MappingNode):
|
|
100
100
|
self.flatten_mapping(node) # type: ignore
|
|
101
101
|
mapping = {}
|