schemathesis 4.0.0a10__py3-none-any.whl → 4.0.0a12__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 +29 -30
- schemathesis/auths.py +65 -24
- schemathesis/checks.py +73 -39
- schemathesis/cli/commands/__init__.py +51 -3
- schemathesis/cli/commands/data.py +10 -0
- schemathesis/cli/commands/run/__init__.py +163 -274
- schemathesis/cli/commands/run/context.py +8 -4
- schemathesis/cli/commands/run/events.py +11 -1
- schemathesis/cli/commands/run/executor.py +70 -78
- schemathesis/cli/commands/run/filters.py +15 -165
- schemathesis/cli/commands/run/handlers/cassettes.py +105 -104
- schemathesis/cli/commands/run/handlers/junitxml.py +5 -4
- schemathesis/cli/commands/run/handlers/output.py +195 -121
- schemathesis/cli/commands/run/loaders.py +35 -50
- schemathesis/cli/commands/run/validation.py +52 -162
- schemathesis/cli/core.py +5 -3
- schemathesis/cli/ext/fs.py +7 -5
- schemathesis/cli/ext/options.py +0 -21
- schemathesis/config/__init__.py +189 -0
- schemathesis/config/_auth.py +51 -0
- schemathesis/config/_checks.py +268 -0
- schemathesis/config/_diff_base.py +99 -0
- schemathesis/config/_env.py +21 -0
- schemathesis/config/_error.py +156 -0
- schemathesis/config/_generation.py +149 -0
- schemathesis/config/_health_check.py +24 -0
- schemathesis/config/_operations.py +327 -0
- schemathesis/config/_output.py +171 -0
- schemathesis/config/_parameters.py +19 -0
- schemathesis/config/_phases.py +187 -0
- schemathesis/config/_projects.py +523 -0
- schemathesis/config/_rate_limit.py +17 -0
- schemathesis/config/_report.py +120 -0
- schemathesis/config/_validator.py +9 -0
- schemathesis/config/_warnings.py +25 -0
- schemathesis/config/schema.json +885 -0
- schemathesis/core/__init__.py +2 -0
- schemathesis/core/compat.py +16 -9
- schemathesis/core/errors.py +24 -4
- schemathesis/core/failures.py +6 -7
- schemathesis/core/hooks.py +20 -0
- schemathesis/core/output/__init__.py +14 -37
- schemathesis/core/output/sanitization.py +3 -146
- schemathesis/core/transport.py +36 -1
- schemathesis/core/validation.py +16 -0
- schemathesis/engine/__init__.py +2 -4
- schemathesis/engine/context.py +42 -43
- schemathesis/engine/core.py +7 -5
- schemathesis/engine/errors.py +60 -1
- schemathesis/engine/events.py +10 -2
- schemathesis/engine/phases/__init__.py +10 -0
- schemathesis/engine/phases/probes.py +11 -8
- schemathesis/engine/phases/stateful/__init__.py +2 -1
- schemathesis/engine/phases/stateful/_executor.py +104 -46
- schemathesis/engine/phases/stateful/context.py +2 -2
- schemathesis/engine/phases/unit/__init__.py +23 -15
- schemathesis/engine/phases/unit/_executor.py +110 -21
- schemathesis/engine/phases/unit/_pool.py +1 -1
- schemathesis/errors.py +2 -0
- schemathesis/filters.py +2 -3
- schemathesis/generation/__init__.py +5 -33
- schemathesis/generation/case.py +6 -3
- schemathesis/generation/coverage.py +154 -124
- schemathesis/generation/hypothesis/builder.py +70 -20
- schemathesis/generation/meta.py +3 -3
- schemathesis/generation/metrics.py +93 -0
- schemathesis/generation/modes.py +0 -8
- schemathesis/generation/overrides.py +37 -1
- schemathesis/generation/stateful/__init__.py +4 -0
- schemathesis/generation/stateful/state_machine.py +9 -1
- schemathesis/graphql/loaders.py +159 -16
- schemathesis/hooks.py +62 -35
- schemathesis/openapi/checks.py +12 -8
- schemathesis/openapi/generation/filters.py +10 -8
- schemathesis/openapi/loaders.py +142 -17
- schemathesis/pytest/lazy.py +2 -5
- schemathesis/pytest/loaders.py +24 -0
- schemathesis/pytest/plugin.py +33 -2
- schemathesis/schemas.py +21 -66
- schemathesis/specs/graphql/scalars.py +37 -3
- schemathesis/specs/graphql/schemas.py +23 -18
- schemathesis/specs/openapi/_hypothesis.py +26 -28
- schemathesis/specs/openapi/checks.py +37 -36
- schemathesis/specs/openapi/examples.py +4 -3
- schemathesis/specs/openapi/formats.py +32 -5
- schemathesis/specs/openapi/media_types.py +44 -1
- schemathesis/specs/openapi/negative/__init__.py +2 -2
- schemathesis/specs/openapi/patterns.py +46 -16
- schemathesis/specs/openapi/references.py +2 -3
- schemathesis/specs/openapi/schemas.py +19 -22
- schemathesis/specs/openapi/stateful/__init__.py +12 -6
- schemathesis/transport/__init__.py +54 -16
- schemathesis/transport/prepare.py +38 -13
- schemathesis/transport/requests.py +12 -9
- schemathesis/transport/wsgi.py +11 -12
- {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/METADATA +50 -97
- schemathesis-4.0.0a12.dist-info/RECORD +164 -0
- schemathesis/cli/commands/run/checks.py +0 -79
- schemathesis/cli/commands/run/hypothesis.py +0 -78
- schemathesis/cli/commands/run/reports.py +0 -72
- schemathesis/cli/hooks.py +0 -36
- schemathesis/contrib/__init__.py +0 -9
- schemathesis/contrib/openapi/__init__.py +0 -9
- schemathesis/contrib/openapi/fill_missing_examples.py +0 -20
- schemathesis/engine/config.py +0 -59
- schemathesis/experimental/__init__.py +0 -72
- schemathesis/generation/targets.py +0 -69
- schemathesis-4.0.0a10.dist-info/RECORD +0 -153
- {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/WHEEL +0 -0
- {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/entry_points.txt +0 -0
- {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/licenses/LICENSE +0 -0
@@ -14,6 +14,7 @@ from schemathesis.engine.recorder import ScenarioRecorder
|
|
14
14
|
from schemathesis.generation import GenerationMode
|
15
15
|
from schemathesis.generation.case import Case
|
16
16
|
from schemathesis.generation.hypothesis import strategies
|
17
|
+
from schemathesis.generation.stateful import STATEFUL_TESTS_LABEL
|
17
18
|
from schemathesis.generation.stateful.state_machine import APIStateMachine, StepInput, StepOutput, _normalize_name
|
18
19
|
from schemathesis.schemas import APIOperation
|
19
20
|
from schemathesis.specs.openapi.stateful.control import TransitionController
|
@@ -32,7 +33,7 @@ class OpenAPIStateMachine(APIStateMachine):
|
|
32
33
|
_transitions: ApiTransitions
|
33
34
|
|
34
35
|
def __init__(self) -> None:
|
35
|
-
self.recorder = ScenarioRecorder(label=
|
36
|
+
self.recorder = ScenarioRecorder(label=STATEFUL_TESTS_LABEL)
|
36
37
|
self.control = TransitionController(self._transitions)
|
37
38
|
super().__init__()
|
38
39
|
|
@@ -162,22 +163,25 @@ def create_state_machine(schema: BaseOpenAPISchema) -> type[APIStateMachine]:
|
|
162
163
|
f"{link.source.label} -> {link.status_code} -> {link.name} -> {target.label}"
|
163
164
|
)
|
164
165
|
assert name not in rules, name
|
166
|
+
config = schema.config.generation_for(operation=target, phase="stateful")
|
165
167
|
rules[name] = precondition(is_transition_allowed(bundle_name, link.source.label, target.label))(
|
166
168
|
transition(
|
167
169
|
name=name,
|
168
170
|
target=catch_all,
|
169
171
|
input=bundles[bundle_name].flatmap(
|
170
|
-
into_step_input(target=target, link=link, modes=
|
172
|
+
into_step_input(target=target, link=link, modes=config.modes)
|
171
173
|
),
|
172
174
|
)
|
173
175
|
)
|
174
176
|
if target.label in roots.reliable or (not roots.reliable and target.label in roots.fallback):
|
175
177
|
name = _normalize_name(f"RANDOM -> {target.label}")
|
176
|
-
|
177
|
-
|
178
|
+
config = schema.config.generation_for(operation=target, phase="stateful")
|
179
|
+
if len(config.modes) == 1:
|
180
|
+
case_strategy = target.as_strategy(generation_mode=config.modes[0], __is_stateful_phase=True)
|
178
181
|
else:
|
179
182
|
_strategies = {
|
180
|
-
method: target.as_strategy(generation_mode=method)
|
183
|
+
method: target.as_strategy(generation_mode=method, __is_stateful_phase=True)
|
184
|
+
for method in config.modes
|
181
185
|
}
|
182
186
|
|
183
187
|
@st.composite # type: ignore[misc]
|
@@ -262,7 +266,9 @@ def into_step_input(
|
|
262
266
|
and not link.merge_body
|
263
267
|
):
|
264
268
|
kwargs["body"] = transition.request_body.value.ok()
|
265
|
-
cases = strategies.combine(
|
269
|
+
cases = strategies.combine(
|
270
|
+
[target.as_strategy(generation_mode=mode, __is_stateful_phase=True, **kwargs) for mode in modes]
|
271
|
+
)
|
266
272
|
case = draw(cases)
|
267
273
|
if (
|
268
274
|
transition.request_body is not None
|
@@ -2,11 +2,15 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from dataclasses import dataclass
|
4
4
|
from inspect import iscoroutinefunction
|
5
|
-
from typing import Any, Callable, Generic, Iterator, TypeVar
|
5
|
+
from typing import TYPE_CHECKING, Any, Callable, Generic, Iterator, TypeVar, Union
|
6
6
|
|
7
7
|
from schemathesis.core import media_types
|
8
8
|
from schemathesis.core.errors import SerializationNotPossible
|
9
9
|
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
from schemathesis.core.transport import Response
|
12
|
+
from schemathesis.generation.case import Case
|
13
|
+
|
10
14
|
|
11
15
|
def get(app: Any) -> BaseTransport:
|
12
16
|
"""Get transport to send the data to the application."""
|
@@ -23,41 +27,43 @@ def get(app: Any) -> BaseTransport:
|
|
23
27
|
return WSGI_TRANSPORT
|
24
28
|
|
25
29
|
|
26
|
-
C = TypeVar("C", contravariant=True)
|
27
|
-
R = TypeVar("R", covariant=True)
|
28
30
|
S = TypeVar("S", contravariant=True)
|
29
31
|
|
30
32
|
|
31
33
|
@dataclass
|
32
|
-
class SerializationContext
|
33
|
-
"""
|
34
|
+
class SerializationContext:
|
35
|
+
"""Context object passed to serializer functions.
|
36
|
+
|
37
|
+
It provides access to the generated test case and any related metadata.
|
38
|
+
"""
|
34
39
|
|
35
|
-
case:
|
40
|
+
case: Case
|
41
|
+
"""The generated test case."""
|
36
42
|
|
37
43
|
__slots__ = ("case",)
|
38
44
|
|
39
45
|
|
40
|
-
Serializer = Callable[[SerializationContext
|
46
|
+
Serializer = Callable[[SerializationContext, Any], Any]
|
41
47
|
|
42
48
|
|
43
|
-
class BaseTransport(Generic[
|
49
|
+
class BaseTransport(Generic[S]):
|
44
50
|
"""Base implementation with serializer registration."""
|
45
51
|
|
46
52
|
def __init__(self) -> None:
|
47
|
-
self._serializers: dict[str, Serializer
|
53
|
+
self._serializers: dict[str, Serializer] = {}
|
48
54
|
|
49
|
-
def serialize_case(self, case:
|
55
|
+
def serialize_case(self, case: Case, **kwargs: Any) -> dict[str, Any]:
|
50
56
|
"""Prepare the case for sending."""
|
51
57
|
raise NotImplementedError
|
52
58
|
|
53
|
-
def send(self, case:
|
59
|
+
def send(self, case: Case, *, session: S | None = None, **kwargs: Any) -> Response:
|
54
60
|
"""Send the case using this transport."""
|
55
61
|
raise NotImplementedError
|
56
62
|
|
57
|
-
def serializer(self, *media_types: str) -> Callable[[Serializer
|
63
|
+
def serializer(self, *media_types: str) -> Callable[[Serializer], Serializer]:
|
58
64
|
"""Register a serializer for given media types."""
|
59
65
|
|
60
|
-
def decorator(func: Serializer
|
66
|
+
def decorator(func: Serializer) -> Serializer:
|
61
67
|
for media_type in media_types:
|
62
68
|
self._serializers[media_type] = func
|
63
69
|
return func
|
@@ -71,10 +77,10 @@ class BaseTransport(Generic[C, R, S]):
|
|
71
77
|
def _copy_serializers_from(self, transport: BaseTransport) -> None:
|
72
78
|
self._serializers.update(transport._serializers)
|
73
79
|
|
74
|
-
def get_first_matching_media_type(self, media_type: str) -> tuple[str, Serializer
|
80
|
+
def get_first_matching_media_type(self, media_type: str) -> tuple[str, Serializer] | None:
|
75
81
|
return next(self.get_matching_media_types(media_type), None)
|
76
82
|
|
77
|
-
def get_matching_media_types(self, media_type: str) -> Iterator[tuple[str, Serializer
|
83
|
+
def get_matching_media_types(self, media_type: str) -> Iterator[tuple[str, Serializer]]:
|
78
84
|
"""Get all registered media types matching the given media type."""
|
79
85
|
if media_type == "*/*":
|
80
86
|
# Shortcut to avoid comparing all values
|
@@ -96,9 +102,41 @@ class BaseTransport(Generic[C, R, S]):
|
|
96
102
|
if main in ("*", target_main) and sub in ("*", target_sub):
|
97
103
|
yield registered_media_type, serializer
|
98
104
|
|
99
|
-
def _get_serializer(self, input_media_type: str) -> Serializer
|
105
|
+
def _get_serializer(self, input_media_type: str) -> Serializer:
|
100
106
|
pair = self.get_first_matching_media_type(input_media_type)
|
101
107
|
if pair is None:
|
102
108
|
# This media type is set manually. Otherwise, it should have been rejected during the data generation
|
103
109
|
raise SerializationNotPossible.for_media_type(input_media_type)
|
104
110
|
return pair[1]
|
111
|
+
|
112
|
+
|
113
|
+
_Serializer = Callable[[SerializationContext, Any], Union[bytes, None]]
|
114
|
+
|
115
|
+
|
116
|
+
def serializer(*media_types: str) -> Callable[[_Serializer], None]:
|
117
|
+
"""Register a serializer for specified media types on HTTP, ASGI, and WSGI transports.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
*media_types: One or more MIME types (e.g., "application/json") this serializer handles.
|
121
|
+
|
122
|
+
Returns:
|
123
|
+
A decorator that wraps a function taking `(ctx: SerializationContext, value: Any)` and returning `bytes` for serialized body and `None` for omitting request body.
|
124
|
+
|
125
|
+
"""
|
126
|
+
|
127
|
+
def register(func: _Serializer) -> None:
|
128
|
+
from schemathesis.transport.asgi import ASGI_TRANSPORT
|
129
|
+
from schemathesis.transport.requests import REQUESTS_TRANSPORT
|
130
|
+
from schemathesis.transport.wsgi import WSGI_TRANSPORT
|
131
|
+
|
132
|
+
@ASGI_TRANSPORT.serializer(*media_types)
|
133
|
+
@REQUESTS_TRANSPORT.serializer(*media_types)
|
134
|
+
@WSGI_TRANSPORT.serializer(*media_types)
|
135
|
+
def inner(ctx: SerializationContext, value: Any) -> dict[str, bytes]:
|
136
|
+
result = {}
|
137
|
+
serialized = func(ctx, value)
|
138
|
+
if serialized is not None:
|
139
|
+
result["data"] = serialized
|
140
|
+
return result
|
141
|
+
|
142
|
+
return register
|
@@ -1,12 +1,15 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
from functools import lru_cache
|
3
4
|
from typing import TYPE_CHECKING, Any, Mapping, cast
|
4
5
|
from urllib.parse import quote, unquote, urljoin, urlsplit, urlunsplit
|
5
6
|
|
7
|
+
from schemathesis.config import SanitizationConfig
|
6
8
|
from schemathesis.core import SCHEMATHESIS_TEST_CASE_HEADER, NotSet
|
7
9
|
from schemathesis.core.errors import InvalidSchema
|
8
10
|
from schemathesis.core.output.sanitization import sanitize_url, sanitize_value
|
9
11
|
from schemathesis.core.transport import USER_AGENT
|
12
|
+
from schemathesis.generation.meta import CoveragePhaseData
|
10
13
|
|
11
14
|
if TYPE_CHECKING:
|
12
15
|
from requests import PreparedRequest
|
@@ -15,15 +18,37 @@ if TYPE_CHECKING:
|
|
15
18
|
from schemathesis.generation.case import Case
|
16
19
|
|
17
20
|
|
18
|
-
|
19
|
-
|
21
|
+
@lru_cache()
|
22
|
+
def get_default_headers() -> CaseInsensitiveDict:
|
23
|
+
from requests.utils import default_headers
|
24
|
+
|
25
|
+
headers = default_headers()
|
26
|
+
headers["User-Agent"] = USER_AGENT
|
27
|
+
return headers
|
28
|
+
|
20
29
|
|
21
|
-
|
30
|
+
def prepare_headers(case: Case, headers: dict[str, str] | None = None) -> CaseInsensitiveDict:
|
31
|
+
default_headers = get_default_headers().copy()
|
32
|
+
if case.headers:
|
33
|
+
default_headers.update(case.headers)
|
34
|
+
default_headers.setdefault(SCHEMATHESIS_TEST_CASE_HEADER, case.id)
|
22
35
|
if headers:
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
return
|
36
|
+
default_headers.update(headers)
|
37
|
+
for header in get_exclude_headers(case):
|
38
|
+
default_headers.pop(header, None)
|
39
|
+
return default_headers
|
40
|
+
|
41
|
+
|
42
|
+
def get_exclude_headers(case: Case) -> list[str]:
|
43
|
+
if (
|
44
|
+
case.meta is not None
|
45
|
+
and isinstance(case.meta.phase.data, CoveragePhaseData)
|
46
|
+
and case.meta.phase.data.description.startswith("Missing")
|
47
|
+
and case.meta.phase.data.description.endswith("at header")
|
48
|
+
and case.meta.phase.data.parameter is not None
|
49
|
+
):
|
50
|
+
return [case.meta.phase.data.parameter]
|
51
|
+
return []
|
27
52
|
|
28
53
|
|
29
54
|
def prepare_url(case: Case, base_url: str | None) -> str:
|
@@ -81,19 +106,19 @@ def prepare_path(path: str, parameters: dict[str, Any] | None) -> str:
|
|
81
106
|
raise InvalidSchema(f"Malformed path template: `{path}`\n\n {exc}") from exc
|
82
107
|
|
83
108
|
|
84
|
-
def prepare_request(case: Case, headers: Mapping[str, Any] | None,
|
109
|
+
def prepare_request(case: Case, headers: Mapping[str, Any] | None, *, config: SanitizationConfig) -> PreparedRequest:
|
85
110
|
import requests
|
86
111
|
|
87
112
|
from schemathesis.transport.requests import REQUESTS_TRANSPORT
|
88
113
|
|
89
114
|
base_url = normalize_base_url(case.operation.base_url)
|
90
115
|
kwargs = REQUESTS_TRANSPORT.serialize_case(case, base_url=base_url, headers=headers)
|
91
|
-
if
|
92
|
-
kwargs["url"] = sanitize_url(kwargs["url"])
|
93
|
-
sanitize_value(kwargs["headers"])
|
116
|
+
if config.enabled:
|
117
|
+
kwargs["url"] = sanitize_url(kwargs["url"], config=config)
|
118
|
+
sanitize_value(kwargs["headers"], config=config)
|
94
119
|
if kwargs["cookies"]:
|
95
|
-
sanitize_value(kwargs["cookies"])
|
120
|
+
sanitize_value(kwargs["cookies"], config=config)
|
96
121
|
if kwargs["params"]:
|
97
|
-
sanitize_value(kwargs["params"])
|
122
|
+
sanitize_value(kwargs["params"], config=config)
|
98
123
|
|
99
124
|
return requests.Request(**kwargs).prepare()
|
@@ -21,7 +21,7 @@ if TYPE_CHECKING:
|
|
21
21
|
from schemathesis.generation.case import Case
|
22
22
|
|
23
23
|
|
24
|
-
class RequestsTransport(BaseTransport["
|
24
|
+
class RequestsTransport(BaseTransport["requests.Session"]):
|
25
25
|
def serialize_case(self, case: Case, **kwargs: Any) -> dict[str, Any]:
|
26
26
|
base_url = kwargs.get("base_url")
|
27
27
|
headers = kwargs.get("headers")
|
@@ -92,6 +92,7 @@ class RequestsTransport(BaseTransport["Case", Response, "requests.Session"]):
|
|
92
92
|
if session is None:
|
93
93
|
validate_vanilla_requests_kwargs(data)
|
94
94
|
session = requests.Session()
|
95
|
+
session.headers = {}
|
95
96
|
close_session = True
|
96
97
|
else:
|
97
98
|
close_session = False
|
@@ -99,7 +100,9 @@ class RequestsTransport(BaseTransport["Case", Response, "requests.Session"]):
|
|
99
100
|
verify = data.get("verify", True)
|
100
101
|
|
101
102
|
try:
|
102
|
-
|
103
|
+
config = case.operation.schema.config
|
104
|
+
rate_limit = config.rate_limit_for(operation=case.operation)
|
105
|
+
with ratelimit(rate_limit, config.base_url):
|
103
106
|
response = session.request(**data) # type: ignore
|
104
107
|
return Response.from_requests(response, verify=verify)
|
105
108
|
finally:
|
@@ -133,14 +136,14 @@ REQUESTS_TRANSPORT = RequestsTransport()
|
|
133
136
|
|
134
137
|
|
135
138
|
@REQUESTS_TRANSPORT.serializer("application/json", "text/json")
|
136
|
-
def json_serializer(ctx: SerializationContext
|
139
|
+
def json_serializer(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
137
140
|
return serialize_json(value)
|
138
141
|
|
139
142
|
|
140
143
|
@REQUESTS_TRANSPORT.serializer(
|
141
144
|
"text/yaml", "text/x-yaml", "text/vnd.yaml", "text/yml", "application/yaml", "application/x-yaml"
|
142
145
|
)
|
143
|
-
def yaml_serializer(ctx: SerializationContext
|
146
|
+
def yaml_serializer(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
144
147
|
return serialize_yaml(value)
|
145
148
|
|
146
149
|
|
@@ -186,7 +189,7 @@ def _encode_multipart(value: Any, boundary: str) -> bytes:
|
|
186
189
|
|
187
190
|
|
188
191
|
@REQUESTS_TRANSPORT.serializer("multipart/form-data", "multipart/mixed")
|
189
|
-
def multipart_serializer(ctx: SerializationContext
|
192
|
+
def multipart_serializer(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
190
193
|
if isinstance(value, bytes):
|
191
194
|
return {"data": value}
|
192
195
|
if isinstance(value, dict):
|
@@ -202,7 +205,7 @@ def multipart_serializer(ctx: SerializationContext[Case], value: Any) -> dict[st
|
|
202
205
|
|
203
206
|
|
204
207
|
@REQUESTS_TRANSPORT.serializer("application/xml", "text/xml")
|
205
|
-
def xml_serializer(ctx: SerializationContext
|
208
|
+
def xml_serializer(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
206
209
|
media_type = ctx.case.media_type
|
207
210
|
|
208
211
|
assert media_type is not None
|
@@ -214,17 +217,17 @@ def xml_serializer(ctx: SerializationContext[Case], value: Any) -> dict[str, Any
|
|
214
217
|
|
215
218
|
|
216
219
|
@REQUESTS_TRANSPORT.serializer("application/x-www-form-urlencoded")
|
217
|
-
def urlencoded_serializer(ctx: SerializationContext
|
220
|
+
def urlencoded_serializer(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
218
221
|
return {"data": value}
|
219
222
|
|
220
223
|
|
221
224
|
@REQUESTS_TRANSPORT.serializer("text/plain")
|
222
|
-
def text_serializer(ctx: SerializationContext
|
225
|
+
def text_serializer(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
223
226
|
if isinstance(value, bytes):
|
224
227
|
return {"data": value}
|
225
228
|
return {"data": str(value).encode("utf8")}
|
226
229
|
|
227
230
|
|
228
231
|
@REQUESTS_TRANSPORT.serializer("application/octet-stream")
|
229
|
-
def binary_serializer(ctx: SerializationContext
|
232
|
+
def binary_serializer(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
230
233
|
return {"data": serialize_binary(value)}
|
schemathesis/transport/wsgi.py
CHANGED
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
|
|
19
19
|
import werkzeug
|
20
20
|
|
21
21
|
|
22
|
-
class WSGITransport(BaseTransport["
|
22
|
+
class WSGITransport(BaseTransport["werkzeug.Client"]):
|
23
23
|
def serialize_case(self, case: Case, **kwargs: Any) -> dict[str, Any]:
|
24
24
|
headers = kwargs.get("headers")
|
25
25
|
params = kwargs.get("params")
|
@@ -75,10 +75,9 @@ class WSGITransport(BaseTransport["Case", Response, "werkzeug.Client"]):
|
|
75
75
|
client = session or wsgi.get_client(application)
|
76
76
|
cookies = {**(case.cookies or {}), **(cookies or {})}
|
77
77
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
):
|
78
|
+
config = case.operation.schema.config
|
79
|
+
rate_limit = config.rate_limit_for(operation=case.operation)
|
80
|
+
with cookie_handler(client, cookies), ratelimit(rate_limit, config.base_url):
|
82
81
|
start = time.monotonic()
|
83
82
|
response = client.open(**data)
|
84
83
|
elapsed = time.monotonic() - start
|
@@ -120,24 +119,24 @@ WSGI_TRANSPORT = WSGITransport()
|
|
120
119
|
|
121
120
|
|
122
121
|
@WSGI_TRANSPORT.serializer("application/json", "text/json")
|
123
|
-
def json_serializer(ctx: SerializationContext
|
122
|
+
def json_serializer(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
124
123
|
return serialize_json(value)
|
125
124
|
|
126
125
|
|
127
126
|
@WSGI_TRANSPORT.serializer(
|
128
127
|
"text/yaml", "text/x-yaml", "text/vnd.yaml", "text/yml", "application/yaml", "application/x-yaml"
|
129
128
|
)
|
130
|
-
def yaml_serializer(ctx: SerializationContext
|
129
|
+
def yaml_serializer(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
131
130
|
return serialize_yaml(value)
|
132
131
|
|
133
132
|
|
134
133
|
@WSGI_TRANSPORT.serializer("multipart/form-data", "multipart/mixed")
|
135
|
-
def multipart_serializer(ctx: SerializationContext
|
134
|
+
def multipart_serializer(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
136
135
|
return {"data": value}
|
137
136
|
|
138
137
|
|
139
138
|
@WSGI_TRANSPORT.serializer("application/xml", "text/xml")
|
140
|
-
def xml_serializer(ctx: SerializationContext
|
139
|
+
def xml_serializer(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
141
140
|
media_type = ctx.case.media_type
|
142
141
|
|
143
142
|
assert media_type is not None
|
@@ -149,17 +148,17 @@ def xml_serializer(ctx: SerializationContext[Case], value: Any) -> dict[str, Any
|
|
149
148
|
|
150
149
|
|
151
150
|
@WSGI_TRANSPORT.serializer("application/x-www-form-urlencoded")
|
152
|
-
def urlencoded_serializer(ctx: SerializationContext
|
151
|
+
def urlencoded_serializer(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
153
152
|
return {"data": value}
|
154
153
|
|
155
154
|
|
156
155
|
@WSGI_TRANSPORT.serializer("text/plain")
|
157
|
-
def text_serializer(ctx: SerializationContext
|
156
|
+
def text_serializer(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
158
157
|
if isinstance(value, bytes):
|
159
158
|
return {"data": value}
|
160
159
|
return {"data": str(value)}
|
161
160
|
|
162
161
|
|
163
162
|
@WSGI_TRANSPORT.serializer("application/octet-stream")
|
164
|
-
def binary_serializer(ctx: SerializationContext
|
163
|
+
def binary_serializer(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
165
164
|
return {"data": serialize_binary(value)}
|