schemathesis 3.39.7__py3-none-any.whl → 4.0.0a2__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 +27 -65
- schemathesis/auths.py +26 -68
- schemathesis/checks.py +130 -60
- schemathesis/cli/__init__.py +5 -2105
- schemathesis/cli/commands/__init__.py +37 -0
- schemathesis/cli/commands/run/__init__.py +662 -0
- schemathesis/cli/commands/run/checks.py +80 -0
- schemathesis/cli/commands/run/context.py +117 -0
- schemathesis/cli/commands/run/events.py +30 -0
- schemathesis/cli/commands/run/executor.py +141 -0
- schemathesis/cli/commands/run/filters.py +202 -0
- schemathesis/cli/commands/run/handlers/__init__.py +46 -0
- schemathesis/cli/commands/run/handlers/base.py +18 -0
- schemathesis/cli/{cassettes.py → commands/run/handlers/cassettes.py} +178 -247
- schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
- schemathesis/cli/commands/run/handlers/output.py +1368 -0
- schemathesis/cli/commands/run/hypothesis.py +105 -0
- schemathesis/cli/commands/run/loaders.py +129 -0
- schemathesis/cli/{callbacks.py → commands/run/validation.py} +59 -175
- schemathesis/cli/constants.py +5 -58
- schemathesis/cli/core.py +17 -0
- schemathesis/cli/ext/fs.py +14 -0
- schemathesis/cli/ext/groups.py +55 -0
- schemathesis/cli/{options.py → ext/options.py} +37 -16
- schemathesis/cli/hooks.py +36 -0
- schemathesis/contrib/__init__.py +1 -3
- schemathesis/contrib/openapi/__init__.py +1 -3
- schemathesis/contrib/openapi/fill_missing_examples.py +3 -7
- schemathesis/core/__init__.py +58 -0
- schemathesis/core/compat.py +25 -0
- schemathesis/core/control.py +2 -0
- schemathesis/core/curl.py +58 -0
- schemathesis/core/deserialization.py +65 -0
- schemathesis/core/errors.py +370 -0
- schemathesis/core/failures.py +315 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/core/loaders.py +104 -0
- schemathesis/core/marks.py +66 -0
- schemathesis/{transports/content_types.py → core/media_types.py} +14 -12
- schemathesis/{internal/output.py → core/output/__init__.py} +1 -0
- schemathesis/core/output/sanitization.py +197 -0
- schemathesis/{throttling.py → core/rate_limit.py} +16 -17
- schemathesis/core/registries.py +31 -0
- schemathesis/core/transforms.py +113 -0
- schemathesis/core/transport.py +108 -0
- schemathesis/core/validation.py +38 -0
- schemathesis/core/version.py +7 -0
- schemathesis/engine/__init__.py +30 -0
- schemathesis/engine/config.py +59 -0
- schemathesis/engine/context.py +119 -0
- schemathesis/engine/control.py +36 -0
- schemathesis/engine/core.py +157 -0
- schemathesis/engine/errors.py +394 -0
- schemathesis/engine/events.py +243 -0
- schemathesis/engine/phases/__init__.py +66 -0
- schemathesis/{runner → engine/phases}/probes.py +49 -68
- schemathesis/engine/phases/stateful/__init__.py +66 -0
- schemathesis/engine/phases/stateful/_executor.py +301 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +175 -0
- schemathesis/engine/phases/unit/_executor.py +322 -0
- schemathesis/engine/phases/unit/_pool.py +74 -0
- schemathesis/engine/recorder.py +246 -0
- schemathesis/errors.py +31 -0
- schemathesis/experimental/__init__.py +9 -40
- schemathesis/filters.py +7 -95
- schemathesis/generation/__init__.py +3 -3
- schemathesis/generation/case.py +190 -0
- schemathesis/generation/coverage.py +22 -22
- schemathesis/{_patches.py → generation/hypothesis/__init__.py} +15 -6
- schemathesis/generation/hypothesis/builder.py +585 -0
- schemathesis/generation/{_hypothesis.py → hypothesis/examples.py} +2 -11
- schemathesis/generation/hypothesis/given.py +66 -0
- schemathesis/generation/hypothesis/reporting.py +14 -0
- schemathesis/generation/hypothesis/strategies.py +16 -0
- schemathesis/generation/meta.py +115 -0
- schemathesis/generation/modes.py +28 -0
- schemathesis/generation/overrides.py +96 -0
- schemathesis/generation/stateful/__init__.py +20 -0
- schemathesis/{stateful → generation/stateful}/state_machine.py +84 -109
- schemathesis/generation/targets.py +69 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +109 -0
- schemathesis/graphql/loaders.py +131 -0
- schemathesis/hooks.py +17 -62
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +387 -0
- schemathesis/openapi/generation/__init__.py +0 -0
- schemathesis/openapi/generation/filters.py +63 -0
- schemathesis/openapi/loaders.py +178 -0
- schemathesis/pytest/__init__.py +5 -0
- schemathesis/pytest/control_flow.py +7 -0
- schemathesis/pytest/lazy.py +273 -0
- schemathesis/pytest/loaders.py +12 -0
- schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +94 -107
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +456 -228
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/_cache.py +1 -2
- schemathesis/specs/graphql/scalars.py +5 -3
- schemathesis/specs/graphql/schemas.py +122 -123
- schemathesis/specs/graphql/validation.py +11 -17
- schemathesis/specs/openapi/__init__.py +6 -1
- schemathesis/specs/openapi/_cache.py +1 -2
- schemathesis/specs/openapi/_hypothesis.py +97 -134
- schemathesis/specs/openapi/checks.py +238 -219
- schemathesis/specs/openapi/converter.py +4 -4
- schemathesis/specs/openapi/definitions.py +1 -1
- schemathesis/specs/openapi/examples.py +22 -20
- schemathesis/specs/openapi/expressions/__init__.py +11 -15
- schemathesis/specs/openapi/expressions/extractors.py +1 -4
- schemathesis/specs/openapi/expressions/nodes.py +33 -32
- schemathesis/specs/openapi/formats.py +3 -2
- schemathesis/specs/openapi/links.py +123 -299
- schemathesis/specs/openapi/media_types.py +10 -12
- schemathesis/specs/openapi/negative/__init__.py +2 -1
- schemathesis/specs/openapi/negative/mutations.py +3 -2
- schemathesis/specs/openapi/parameters.py +8 -6
- schemathesis/specs/openapi/patterns.py +1 -1
- schemathesis/specs/openapi/references.py +11 -51
- schemathesis/specs/openapi/schemas.py +177 -191
- schemathesis/specs/openapi/security.py +1 -1
- schemathesis/specs/openapi/serialization.py +10 -6
- schemathesis/specs/openapi/stateful/__init__.py +97 -91
- schemathesis/transport/__init__.py +104 -0
- schemathesis/transport/asgi.py +26 -0
- schemathesis/transport/prepare.py +99 -0
- schemathesis/transport/requests.py +221 -0
- schemathesis/{_xml.py → transport/serialization.py} +69 -7
- schemathesis/transport/wsgi.py +165 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/METADATA +18 -14
- schemathesis-4.0.0a2.dist-info/RECORD +151 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/entry_points.txt +1 -1
- schemathesis/_compat.py +0 -74
- schemathesis/_dependency_versions.py +0 -19
- schemathesis/_hypothesis.py +0 -559
- schemathesis/_override.py +0 -50
- schemathesis/_rate_limiter.py +0 -7
- schemathesis/cli/context.py +0 -75
- schemathesis/cli/debug.py +0 -27
- schemathesis/cli/handlers.py +0 -19
- schemathesis/cli/junitxml.py +0 -124
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -936
- schemathesis/cli/output/short.py +0 -59
- schemathesis/cli/reporting.py +0 -79
- schemathesis/cli/sanitization.py +0 -26
- schemathesis/code_samples.py +0 -151
- schemathesis/constants.py +0 -56
- schemathesis/contrib/openapi/formats/__init__.py +0 -9
- schemathesis/contrib/openapi/formats/uuid.py +0 -16
- schemathesis/contrib/unique_data.py +0 -41
- schemathesis/exceptions.py +0 -571
- schemathesis/extra/_aiohttp.py +0 -28
- schemathesis/extra/_flask.py +0 -13
- schemathesis/extra/_server.py +0 -18
- schemathesis/failures.py +0 -277
- schemathesis/fixups/__init__.py +0 -37
- schemathesis/fixups/fast_api.py +0 -41
- schemathesis/fixups/utf8_bom.py +0 -28
- schemathesis/generation/_methods.py +0 -44
- schemathesis/graphql.py +0 -3
- schemathesis/internal/__init__.py +0 -7
- schemathesis/internal/checks.py +0 -84
- schemathesis/internal/copy.py +0 -32
- schemathesis/internal/datetime.py +0 -5
- schemathesis/internal/deprecation.py +0 -38
- schemathesis/internal/diff.py +0 -15
- schemathesis/internal/extensions.py +0 -27
- schemathesis/internal/jsonschema.py +0 -36
- schemathesis/internal/transformation.py +0 -26
- schemathesis/internal/validation.py +0 -34
- schemathesis/lazy.py +0 -474
- schemathesis/loaders.py +0 -122
- schemathesis/models.py +0 -1341
- schemathesis/parameters.py +0 -90
- schemathesis/runner/__init__.py +0 -605
- schemathesis/runner/events.py +0 -389
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/context.py +0 -104
- schemathesis/runner/impl/core.py +0 -1246
- schemathesis/runner/impl/solo.py +0 -80
- schemathesis/runner/impl/threadpool.py +0 -391
- schemathesis/runner/serialization.py +0 -544
- schemathesis/sanitization.py +0 -252
- schemathesis/serializers.py +0 -328
- schemathesis/service/__init__.py +0 -18
- schemathesis/service/auth.py +0 -11
- schemathesis/service/ci.py +0 -202
- schemathesis/service/client.py +0 -133
- schemathesis/service/constants.py +0 -38
- schemathesis/service/events.py +0 -61
- schemathesis/service/extensions.py +0 -224
- schemathesis/service/hosts.py +0 -111
- schemathesis/service/metadata.py +0 -71
- schemathesis/service/models.py +0 -258
- schemathesis/service/report.py +0 -255
- schemathesis/service/serialization.py +0 -173
- schemathesis/service/usage.py +0 -66
- schemathesis/specs/graphql/loaders.py +0 -364
- schemathesis/specs/openapi/expressions/context.py +0 -16
- schemathesis/specs/openapi/loaders.py +0 -708
- schemathesis/specs/openapi/stateful/statistic.py +0 -198
- schemathesis/specs/openapi/stateful/types.py +0 -14
- schemathesis/specs/openapi/validation.py +0 -26
- schemathesis/stateful/__init__.py +0 -147
- schemathesis/stateful/config.py +0 -97
- schemathesis/stateful/context.py +0 -135
- schemathesis/stateful/events.py +0 -274
- schemathesis/stateful/runner.py +0 -309
- schemathesis/stateful/sink.py +0 -68
- schemathesis/stateful/statistic.py +0 -22
- schemathesis/stateful/validation.py +0 -100
- schemathesis/targets.py +0 -77
- schemathesis/transports/__init__.py +0 -359
- schemathesis/transports/asgi.py +0 -7
- schemathesis/transports/auth.py +0 -38
- schemathesis/transports/headers.py +0 -36
- schemathesis/transports/responses.py +0 -57
- schemathesis/types.py +0 -44
- schemathesis/utils.py +0 -164
- schemathesis-3.39.7.dist-info/RECORD +0 -160
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
- /schemathesis/{_lazy_import.py → core/lazy_import.py} +0 -0
- /schemathesis/{internal → core}/result.py +0 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/WHEEL +0 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/licenses/LICENSE +0 -0
@@ -19,43 +19,35 @@ from typing import (
|
|
19
19
|
Iterator,
|
20
20
|
Mapping,
|
21
21
|
NoReturn,
|
22
|
-
Sequence,
|
23
|
-
TypeVar,
|
24
22
|
cast,
|
25
23
|
)
|
26
24
|
from urllib.parse import urlsplit
|
27
25
|
|
28
26
|
import jsonschema
|
29
27
|
from packaging import version
|
28
|
+
from requests.exceptions import InvalidHeader
|
30
29
|
from requests.structures import CaseInsensitiveDict
|
31
|
-
|
32
|
-
|
33
|
-
from
|
34
|
-
from
|
35
|
-
from
|
36
|
-
from
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
from ...generation import
|
47
|
-
from ...hooks import
|
48
|
-
from ...
|
49
|
-
from ...internal.jsonschema import traverse_schema
|
50
|
-
from ...internal.result import Err, Ok, Result
|
51
|
-
from ...models import APIOperation, Case, OperationDefinition
|
52
|
-
from ...schemas import APIOperationMap, BaseSchema
|
53
|
-
from ...stateful import Stateful, StatefulTest
|
54
|
-
from ...transports.content_types import is_json_media_type, parse_content_type
|
55
|
-
from ...transports.responses import get_json
|
30
|
+
from requests.utils import check_header_validity
|
31
|
+
|
32
|
+
from schemathesis.core import NOT_SET, NotSet, Specification, media_types
|
33
|
+
from schemathesis.core.compat import RefResolutionError
|
34
|
+
from schemathesis.core.errors import InternalError, InvalidSchema, LoaderError, LoaderErrorKind, OperationNotFound
|
35
|
+
from schemathesis.core.failures import Failure, FailureGroup, MalformedJson
|
36
|
+
from schemathesis.core.result import Err, Ok, Result
|
37
|
+
from schemathesis.core.transforms import UNRESOLVABLE, deepclone, resolve_pointer, transform
|
38
|
+
from schemathesis.core.transport import Response
|
39
|
+
from schemathesis.core.validation import INVALID_HEADER_RE
|
40
|
+
from schemathesis.generation.case import Case
|
41
|
+
from schemathesis.generation.meta import CaseMetadata
|
42
|
+
from schemathesis.generation.overrides import Override, OverrideMark, check_no_override_mark
|
43
|
+
from schemathesis.openapi.checks import JsonSchemaError, MissingContentType
|
44
|
+
|
45
|
+
from ...generation import GenerationConfig, GenerationMode
|
46
|
+
from ...hooks import HookContext, HookDispatcher
|
47
|
+
from ...schemas import APIOperation, APIOperationMap, ApiStatistic, BaseSchema, OperationDefinition
|
56
48
|
from . import links, serialization
|
57
49
|
from ._cache import OperationCache
|
58
|
-
from ._hypothesis import
|
50
|
+
from ._hypothesis import openapi_cases
|
59
51
|
from .converter import to_json_schema, to_json_schema_recursive
|
60
52
|
from .definitions import OPENAPI_30_VALIDATOR, OPENAPI_31_VALIDATOR, SWAGGER_20_VALIDATOR
|
61
53
|
from .examples import get_strategies_from_examples
|
@@ -67,13 +59,7 @@ from .parameters import (
|
|
67
59
|
OpenAPI30Parameter,
|
68
60
|
OpenAPIParameter,
|
69
61
|
)
|
70
|
-
from .references import
|
71
|
-
RECURSION_DEPTH_LIMIT,
|
72
|
-
UNRESOLVABLE,
|
73
|
-
ConvertingResolver,
|
74
|
-
InliningResolver,
|
75
|
-
resolve_pointer,
|
76
|
-
)
|
62
|
+
from .references import RECURSION_DEPTH_LIMIT, ConvertingResolver, InliningResolver
|
77
63
|
from .security import BaseSecurityProcessor, OpenAPISecurityProcessor, SwaggerSecurityProcessor
|
78
64
|
from .stateful import create_state_machine
|
79
65
|
|
@@ -82,11 +68,25 @@ if TYPE_CHECKING:
|
|
82
68
|
|
83
69
|
from ...auths import AuthStorage
|
84
70
|
from ...stateful.state_machine import APIStateMachine
|
85
|
-
from ...transports.responses import GenericResponse
|
86
|
-
from ...types import Body, Cookies, FormData, GenericTest, Headers, NotSet, PathParameters, Query
|
87
71
|
|
72
|
+
HTTP_METHODS = frozenset({"get", "put", "post", "delete", "options", "head", "patch", "trace"})
|
88
73
|
SCHEMA_ERROR_MESSAGE = "Ensure that the definition complies with the OpenAPI specification"
|
89
|
-
SCHEMA_PARSING_ERRORS = (KeyError, AttributeError,
|
74
|
+
SCHEMA_PARSING_ERRORS = (KeyError, AttributeError, RefResolutionError, InvalidSchema)
|
75
|
+
|
76
|
+
|
77
|
+
def check_header(parameter: dict[str, Any]) -> None:
|
78
|
+
name = parameter["name"]
|
79
|
+
if not name:
|
80
|
+
raise InvalidSchema("Header name should not be empty")
|
81
|
+
if not name.isascii():
|
82
|
+
# `urllib3` encodes header names to ASCII
|
83
|
+
raise InvalidSchema(f"Header name should be ASCII: {name}")
|
84
|
+
try:
|
85
|
+
check_header_validity((name, ""))
|
86
|
+
except InvalidHeader as exc:
|
87
|
+
raise InvalidSchema(str(exc)) from None
|
88
|
+
if bool(INVALID_HEADER_RE.search(name)):
|
89
|
+
raise InvalidSchema(f"Invalid header name: {name}")
|
90
90
|
|
91
91
|
|
92
92
|
@dataclass(eq=False, repr=False)
|
@@ -103,16 +103,9 @@ class BaseOpenAPISchema(BaseSchema):
|
|
103
103
|
component_locations: ClassVar[tuple[tuple[str, ...], ...]] = ()
|
104
104
|
|
105
105
|
@property
|
106
|
-
def
|
106
|
+
def specification(self) -> Specification:
|
107
107
|
raise NotImplementedError
|
108
108
|
|
109
|
-
def get_stateful_tests(
|
110
|
-
self, response: GenericResponse, operation: APIOperation, stateful: Stateful | None
|
111
|
-
) -> Sequence[StatefulTest]:
|
112
|
-
if stateful == Stateful.links:
|
113
|
-
return links.get_links(response, operation, field=self.links_field)
|
114
|
-
return []
|
115
|
-
|
116
109
|
def __repr__(self) -> str:
|
117
110
|
info = self.raw_schema["info"]
|
118
111
|
return f"<{self.__class__.__name__} for {info['title']} {info['version']}>"
|
@@ -152,7 +145,7 @@ class BaseOpenAPISchema(BaseSchema):
|
|
152
145
|
operation=APIOperation(
|
153
146
|
method="",
|
154
147
|
path="",
|
155
|
-
|
148
|
+
label="",
|
156
149
|
definition=OperationDefinition(raw=None, resolved=None, scope=""),
|
157
150
|
schema=None, # type: ignore
|
158
151
|
)
|
@@ -167,12 +160,46 @@ class BaseOpenAPISchema(BaseSchema):
|
|
167
160
|
operation = _ctx_cache.operation
|
168
161
|
operation.method = method
|
169
162
|
operation.path = path
|
170
|
-
operation.
|
163
|
+
operation.label = f"{method.upper()} {path}"
|
171
164
|
operation.definition.raw = definition
|
172
165
|
operation.definition.resolved = definition
|
173
166
|
operation.schema = self
|
174
167
|
return not self.filter_set.match(_ctx_cache)
|
175
168
|
|
169
|
+
def _measure_statistic(self) -> ApiStatistic:
|
170
|
+
statistic = ApiStatistic()
|
171
|
+
try:
|
172
|
+
paths = self.raw_schema["paths"]
|
173
|
+
except KeyError:
|
174
|
+
return statistic
|
175
|
+
|
176
|
+
resolve = self.resolver.resolve
|
177
|
+
should_skip = self._should_skip
|
178
|
+
links_field = self.links_field
|
179
|
+
|
180
|
+
for path, path_item in paths.items():
|
181
|
+
try:
|
182
|
+
if "$ref" in path_item:
|
183
|
+
_, path_item = resolve(path_item["$ref"])
|
184
|
+
for method, definition in path_item.items():
|
185
|
+
if method not in HTTP_METHODS:
|
186
|
+
continue
|
187
|
+
statistic.operations.total += 1
|
188
|
+
is_selected = not should_skip(path, method, definition)
|
189
|
+
if is_selected:
|
190
|
+
statistic.operations.selected += 1
|
191
|
+
for response in definition.get("responses", {}).values():
|
192
|
+
if "$ref" in response:
|
193
|
+
_, response = resolve(response["$ref"])
|
194
|
+
defined_links = response.get(links_field)
|
195
|
+
if defined_links is not None:
|
196
|
+
statistic.links.total += len(defined_links)
|
197
|
+
if is_selected:
|
198
|
+
statistic.links.selected = len(defined_links)
|
199
|
+
except SCHEMA_PARSING_ERRORS:
|
200
|
+
continue
|
201
|
+
return statistic
|
202
|
+
|
176
203
|
def _operation_iter(self) -> Generator[dict[str, Any], None, None]:
|
177
204
|
try:
|
178
205
|
paths = self.raw_schema["paths"]
|
@@ -193,28 +220,6 @@ class BaseOpenAPISchema(BaseSchema):
|
|
193
220
|
# Ignore errors
|
194
221
|
continue
|
195
222
|
|
196
|
-
@property
|
197
|
-
def operations_count(self) -> int:
|
198
|
-
total = 0
|
199
|
-
# Do not build a list from it
|
200
|
-
for _ in self._operation_iter():
|
201
|
-
total += 1
|
202
|
-
return total
|
203
|
-
|
204
|
-
@property
|
205
|
-
def links_count(self) -> int:
|
206
|
-
total = 0
|
207
|
-
resolve = self.resolver.resolve
|
208
|
-
links_field = self.links_field
|
209
|
-
for definition in self._operation_iter():
|
210
|
-
for response in definition.get("responses", {}).values():
|
211
|
-
if "$ref" in response:
|
212
|
-
_, response = resolve(response["$ref"])
|
213
|
-
defined_links = response.get(links_field)
|
214
|
-
if defined_links is not None:
|
215
|
-
total += len(defined_links)
|
216
|
-
return total
|
217
|
-
|
218
223
|
def override(
|
219
224
|
self,
|
220
225
|
*,
|
@@ -222,15 +227,15 @@ class BaseOpenAPISchema(BaseSchema):
|
|
222
227
|
headers: dict[str, str] | None = None,
|
223
228
|
cookies: dict[str, str] | None = None,
|
224
229
|
path_parameters: dict[str, str] | None = None,
|
225
|
-
) -> Callable[[
|
230
|
+
) -> Callable[[Callable], Callable]:
|
226
231
|
"""Override Open API parameters with fixed values."""
|
227
232
|
|
228
|
-
def _add_override(test:
|
233
|
+
def _add_override(test: Callable) -> Callable:
|
229
234
|
check_no_override_mark(test)
|
230
|
-
override =
|
235
|
+
override = Override(
|
231
236
|
query=query or {}, headers=headers or {}, cookies=cookies or {}, path_parameters=path_parameters or {}
|
232
237
|
)
|
233
|
-
|
238
|
+
OverrideMark.set(test, override)
|
234
239
|
return test
|
235
240
|
|
236
241
|
return _add_override
|
@@ -254,8 +259,8 @@ class BaseOpenAPISchema(BaseSchema):
|
|
254
259
|
return self.collect_parameters(itertools.chain(parameters, shared_parameters), operation)
|
255
260
|
|
256
261
|
def get_all_operations(
|
257
|
-
self,
|
258
|
-
) -> Generator[Result[APIOperation,
|
262
|
+
self, generation_config: GenerationConfig | None = None
|
263
|
+
) -> Generator[Result[APIOperation, InvalidSchema], None, None]:
|
259
264
|
"""Iterate over all operations defined in the API.
|
260
265
|
|
261
266
|
Each yielded item is either `Ok` or `Err`, depending on the presence of errors during schema processing.
|
@@ -276,7 +281,7 @@ class BaseOpenAPISchema(BaseSchema):
|
|
276
281
|
paths = self.raw_schema["paths"]
|
277
282
|
except KeyError as exc:
|
278
283
|
# This field is optional in Open API 3.1
|
279
|
-
if version.parse(self.
|
284
|
+
if version.parse(self.specification.version) >= version.parse("3.1"):
|
280
285
|
return
|
281
286
|
# Missing `paths` is not recoverable
|
282
287
|
self._raise_invalid_schema(exc)
|
@@ -290,7 +295,6 @@ class BaseOpenAPISchema(BaseSchema):
|
|
290
295
|
should_skip = self._should_skip
|
291
296
|
collect_parameters = self.collect_parameters
|
292
297
|
make_operation = self.make_operation
|
293
|
-
hooks = self.hooks
|
294
298
|
for path, path_item in paths.items():
|
295
299
|
method = None
|
296
300
|
try:
|
@@ -318,25 +322,18 @@ class BaseOpenAPISchema(BaseSchema):
|
|
318
322
|
if generation_config
|
319
323
|
else None,
|
320
324
|
)
|
321
|
-
context = HookContext(operation=operation)
|
322
|
-
if (
|
323
|
-
should_skip_operation(GLOBAL_HOOK_DISPATCHER, context)
|
324
|
-
or should_skip_operation(hooks, context)
|
325
|
-
or (hooks and should_skip_operation(hooks, context))
|
326
|
-
):
|
327
|
-
continue
|
328
325
|
yield Ok(operation)
|
329
326
|
except SCHEMA_PARSING_ERRORS as exc:
|
330
327
|
yield self._into_err(exc, path, method)
|
331
328
|
except SCHEMA_PARSING_ERRORS as exc:
|
332
329
|
yield self._into_err(exc, path, method)
|
333
330
|
|
334
|
-
def _into_err(self, error: Exception, path: str | None, method: str | None) -> Err[
|
331
|
+
def _into_err(self, error: Exception, path: str | None, method: str | None) -> Err[InvalidSchema]:
|
335
332
|
__tracebackhide__ = True
|
336
333
|
try:
|
337
334
|
full_path = self.get_full_path(path) if isinstance(path, str) else None
|
338
335
|
self._raise_invalid_schema(error, full_path, path, method)
|
339
|
-
except
|
336
|
+
except InvalidSchema as exc:
|
340
337
|
return Err(exc)
|
341
338
|
|
342
339
|
def _raise_invalid_schema(
|
@@ -347,17 +344,15 @@ class BaseOpenAPISchema(BaseSchema):
|
|
347
344
|
method: str | None = None,
|
348
345
|
) -> NoReturn:
|
349
346
|
__tracebackhide__ = True
|
350
|
-
if isinstance(error,
|
351
|
-
raise
|
347
|
+
if isinstance(error, RefResolutionError):
|
348
|
+
raise InvalidSchema.from_reference_resolution_error(
|
352
349
|
error, path=path, method=method, full_path=full_path
|
353
350
|
) from None
|
354
351
|
try:
|
355
352
|
self.validate()
|
356
353
|
except jsonschema.ValidationError as exc:
|
357
|
-
raise
|
358
|
-
|
359
|
-
) from None
|
360
|
-
raise OperationSchemaError(SCHEMA_ERROR_MESSAGE, path=path, method=method, full_path=full_path) from error
|
354
|
+
raise InvalidSchema.from_jsonschema_error(exc, path=path, method=method, full_path=full_path) from None
|
355
|
+
raise InvalidSchema(SCHEMA_ERROR_MESSAGE, path=path, method=method, full_path=full_path) from error
|
361
356
|
|
362
357
|
def validate(self) -> None:
|
363
358
|
with suppress(TypeError):
|
@@ -397,7 +392,7 @@ class BaseOpenAPISchema(BaseSchema):
|
|
397
392
|
"""Create JSON schemas for the query, body, etc from Swagger parameters definitions."""
|
398
393
|
__tracebackhide__ = True
|
399
394
|
base_url = self.get_base_url()
|
400
|
-
operation: APIOperation[OpenAPIParameter
|
395
|
+
operation: APIOperation[OpenAPIParameter] = APIOperation(
|
401
396
|
path=path,
|
402
397
|
method=method,
|
403
398
|
definition=OperationDefinition(raw, resolved, scope),
|
@@ -423,13 +418,11 @@ class BaseOpenAPISchema(BaseSchema):
|
|
423
418
|
self._resolver = InliningResolver(self.location or "", self.raw_schema)
|
424
419
|
return self._resolver
|
425
420
|
|
426
|
-
def get_content_types(self, operation: APIOperation, response:
|
421
|
+
def get_content_types(self, operation: APIOperation, response: Response) -> list[str]:
|
427
422
|
"""Content types available for this API operation."""
|
428
423
|
raise NotImplementedError
|
429
424
|
|
430
|
-
def get_strategies_from_examples(
|
431
|
-
self, operation: APIOperation, as_strategy_kwargs: dict[str, Any] | None = None
|
432
|
-
) -> list[SearchStrategy[Case]]:
|
425
|
+
def get_strategies_from_examples(self, operation: APIOperation, **kwargs: Any) -> list[SearchStrategy[Case]]:
|
433
426
|
"""Get examples from the API operation."""
|
434
427
|
raise NotImplementedError
|
435
428
|
|
@@ -521,15 +514,15 @@ class BaseOpenAPISchema(BaseSchema):
|
|
521
514
|
operation: APIOperation,
|
522
515
|
hooks: HookDispatcher | None = None,
|
523
516
|
auth_storage: AuthStorage | None = None,
|
524
|
-
|
517
|
+
generation_mode: GenerationMode = GenerationMode.default(),
|
525
518
|
generation_config: GenerationConfig | None = None,
|
526
519
|
**kwargs: Any,
|
527
520
|
) -> SearchStrategy:
|
528
|
-
return
|
521
|
+
return openapi_cases(
|
529
522
|
operation=operation,
|
530
523
|
auth_storage=auth_storage,
|
531
524
|
hooks=hooks,
|
532
|
-
|
525
|
+
generation_mode=generation_mode,
|
533
526
|
generation_config=generation_config or self.generation_config,
|
534
527
|
**kwargs,
|
535
528
|
)
|
@@ -551,12 +544,11 @@ class BaseOpenAPISchema(BaseSchema):
|
|
551
544
|
raise NotImplementedError
|
552
545
|
|
553
546
|
def _get_response_definitions(
|
554
|
-
self, operation: APIOperation, response:
|
547
|
+
self, operation: APIOperation, response: Response
|
555
548
|
) -> tuple[list[str], dict[str, Any]] | None:
|
556
549
|
try:
|
557
550
|
responses = operation.definition.raw["responses"]
|
558
551
|
except KeyError as exc:
|
559
|
-
# Possible to get if `validate_schema=False` is passed during schema creation
|
560
552
|
path = operation.path
|
561
553
|
full_path = self.get_full_path(path) if isinstance(path, str) else None
|
562
554
|
self._raise_invalid_schema(exc, full_path, path, operation.method)
|
@@ -568,7 +560,7 @@ class BaseOpenAPISchema(BaseSchema):
|
|
568
560
|
return None
|
569
561
|
|
570
562
|
def get_headers(
|
571
|
-
self, operation: APIOperation, response:
|
563
|
+
self, operation: APIOperation, response: Response
|
572
564
|
) -> tuple[list[str], dict[str, dict[str, Any]] | None] | None:
|
573
565
|
resolved = self._get_response_definitions(operation, response)
|
574
566
|
if not resolved:
|
@@ -580,8 +572,8 @@ class BaseOpenAPISchema(BaseSchema):
|
|
580
572
|
try:
|
581
573
|
return create_state_machine(self)
|
582
574
|
except OperationNotFound as exc:
|
583
|
-
raise
|
584
|
-
|
575
|
+
raise LoaderError(
|
576
|
+
kind=LoaderErrorKind.OPEN_API_INVALID_SCHEMA,
|
585
577
|
message=f"Invalid Open API link definition: Operation `{exc.item}` not found",
|
586
578
|
) from exc
|
587
579
|
|
@@ -609,7 +601,7 @@ class BaseOpenAPISchema(BaseSchema):
|
|
609
601
|
|
610
602
|
.. code-block:: python
|
611
603
|
|
612
|
-
schema = schemathesis.
|
604
|
+
schema = schemathesis.openapi.from_url("http://0.0.0.0/schema.yaml")
|
613
605
|
|
614
606
|
schema.add_link(
|
615
607
|
source=schema["/users/"]["POST"],
|
@@ -642,11 +634,11 @@ class BaseOpenAPISchema(BaseSchema):
|
|
642
634
|
|
643
635
|
@property
|
644
636
|
def validator_cls(self) -> type[jsonschema.Validator]:
|
645
|
-
if self.
|
637
|
+
if self.specification.version.startswith("3.1"):
|
646
638
|
return jsonschema.Draft202012Validator
|
647
639
|
return jsonschema.Draft4Validator
|
648
640
|
|
649
|
-
def validate_response(self, operation: APIOperation, response:
|
641
|
+
def validate_response(self, operation: APIOperation, response: Response) -> bool | None:
|
650
642
|
responses = {str(key): value for key, value in operation.definition.raw.get("responses", {}).items()}
|
651
643
|
status_code = str(response.status_code)
|
652
644
|
if status_code in responses:
|
@@ -660,32 +652,24 @@ class BaseOpenAPISchema(BaseSchema):
|
|
660
652
|
if not schema:
|
661
653
|
# No schema to check against
|
662
654
|
return None
|
663
|
-
|
664
|
-
|
665
|
-
if
|
666
|
-
|
667
|
-
formatted_content_types = [f"\n- `{content_type}`" for content_type in
|
655
|
+
content_types = response.headers.get("content-type")
|
656
|
+
failures: list[Failure] = []
|
657
|
+
if content_types is None:
|
658
|
+
all_media_types = self.get_content_types(operation, response)
|
659
|
+
formatted_content_types = [f"\n- `{content_type}`" for content_type in all_media_types]
|
668
660
|
message = f"The following media types are documented in the schema:{''.join(formatted_content_types)}"
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
errors.append(exc)
|
676
|
-
if content_type and not is_json_media_type(content_type):
|
677
|
-
_maybe_raise_one_or_more(errors)
|
661
|
+
failures.append(MissingContentType(operation=operation.label, message=message, media_types=all_media_types))
|
662
|
+
content_type = None
|
663
|
+
else:
|
664
|
+
content_type = content_types[0]
|
665
|
+
if content_type and not media_types.is_json(content_type):
|
666
|
+
_maybe_raise_one_or_more(failures)
|
678
667
|
return None
|
679
668
|
try:
|
680
|
-
data =
|
669
|
+
data = response.json()
|
681
670
|
except JSONDecodeError as exc:
|
682
|
-
|
683
|
-
|
684
|
-
try:
|
685
|
-
raise exc_class(context.title, context=context) from exc
|
686
|
-
except Exception as exc:
|
687
|
-
errors.append(exc)
|
688
|
-
_maybe_raise_one_or_more(errors)
|
671
|
+
failures.append(MalformedJson.from_exception(operation=operation.label, exc=exc))
|
672
|
+
_maybe_raise_one_or_more(failures)
|
689
673
|
with self._validating_response(scopes) as resolver:
|
690
674
|
try:
|
691
675
|
jsonschema.validate(
|
@@ -697,13 +681,14 @@ class BaseOpenAPISchema(BaseSchema):
|
|
697
681
|
format_checker=jsonschema.Draft202012Validator.FORMAT_CHECKER,
|
698
682
|
)
|
699
683
|
except jsonschema.ValidationError as exc:
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
684
|
+
failures.append(
|
685
|
+
JsonSchemaError.from_exception(
|
686
|
+
operation=operation.label,
|
687
|
+
exc=exc,
|
688
|
+
output_config=operation.schema.output_config,
|
689
|
+
)
|
690
|
+
)
|
691
|
+
_maybe_raise_one_or_more(failures)
|
707
692
|
return None # explicitly return None for mypy
|
708
693
|
|
709
694
|
@contextmanager
|
@@ -734,7 +719,7 @@ class BaseOpenAPISchema(BaseSchema):
|
|
734
719
|
else:
|
735
720
|
break
|
736
721
|
else:
|
737
|
-
target.update(
|
722
|
+
target.update(transform(deepclone(schema), callback, self.nullable_name))
|
738
723
|
if self._inline_reference_cache:
|
739
724
|
components[INLINED_REFERENCES_KEY] = self._inline_reference_cache
|
740
725
|
self._rewritten_components = components
|
@@ -745,8 +730,8 @@ class BaseOpenAPISchema(BaseSchema):
|
|
745
730
|
|
746
731
|
Inlining components helps `hypothesis-jsonschema` generate data that involves non-resolved references.
|
747
732
|
"""
|
748
|
-
schema =
|
749
|
-
schema =
|
733
|
+
schema = deepclone(schema)
|
734
|
+
schema = transform(schema, self._rewrite_references, self.resolver)
|
750
735
|
# Only add definitions that are reachable from the schema via references
|
751
736
|
stack = [schema]
|
752
737
|
seen = set()
|
@@ -761,8 +746,8 @@ class BaseOpenAPISchema(BaseSchema):
|
|
761
746
|
pointer = reference[1:]
|
762
747
|
resolved = resolve_pointer(self.rewritten_components, pointer)
|
763
748
|
if resolved is UNRESOLVABLE:
|
764
|
-
raise
|
765
|
-
|
749
|
+
raise LoaderError(
|
750
|
+
LoaderErrorKind.OPEN_API_INVALID_SCHEMA,
|
766
751
|
message=f"Unresolvable JSON pointer in the schema: {pointer}",
|
767
752
|
)
|
768
753
|
if isinstance(resolved, dict):
|
@@ -796,7 +781,7 @@ class BaseOpenAPISchema(BaseSchema):
|
|
796
781
|
if key not in self._inline_reference_cache:
|
797
782
|
with resolver.resolving(reference) as resolved:
|
798
783
|
# Resolved object also may have references
|
799
|
-
self._inline_reference_cache[key] =
|
784
|
+
self._inline_reference_cache[key] = transform(
|
800
785
|
resolved, lambda s: self._rewrite_references(s, resolver)
|
801
786
|
)
|
802
787
|
# Rewrite the reference with the new location
|
@@ -804,12 +789,12 @@ class BaseOpenAPISchema(BaseSchema):
|
|
804
789
|
return schema
|
805
790
|
|
806
791
|
|
807
|
-
def _maybe_raise_one_or_more(
|
808
|
-
if not
|
792
|
+
def _maybe_raise_one_or_more(failures: list[Failure]) -> None:
|
793
|
+
if not failures:
|
809
794
|
return
|
810
|
-
if len(
|
811
|
-
raise
|
812
|
-
raise
|
795
|
+
if len(failures) == 1:
|
796
|
+
raise failures[0] from None
|
797
|
+
raise FailureGroup(failures) from None
|
813
798
|
|
814
799
|
|
815
800
|
def _make_reference_key(scopes: list[str], reference: str) -> str:
|
@@ -898,17 +883,16 @@ class MethodMap(Mapping):
|
|
898
883
|
def __getitem__(self, item: str) -> APIOperation:
|
899
884
|
try:
|
900
885
|
return self._init_operation(item)
|
901
|
-
except
|
886
|
+
except LookupError as exc:
|
902
887
|
available_methods = ", ".join(map(str.upper, self))
|
903
888
|
message = f"Method `{item.upper()}` not found."
|
904
889
|
if available_methods:
|
905
890
|
message += f" Available methods: {available_methods}"
|
906
|
-
raise
|
891
|
+
raise LookupError(message) from exc
|
907
892
|
|
908
893
|
|
909
894
|
OPENAPI_20_DEFAULT_BODY_MEDIA_TYPE = "application/json"
|
910
895
|
OPENAPI_20_DEFAULT_FORM_MEDIA_TYPE = "multipart/form-data"
|
911
|
-
C = TypeVar("C", bound=Case)
|
912
896
|
|
913
897
|
|
914
898
|
class SwaggerV20(BaseOpenAPISchema):
|
@@ -921,12 +905,9 @@ class SwaggerV20(BaseOpenAPISchema):
|
|
921
905
|
links_field = "x-links"
|
922
906
|
|
923
907
|
@property
|
924
|
-
def
|
925
|
-
|
926
|
-
|
927
|
-
@property
|
928
|
-
def verbose_name(self) -> str:
|
929
|
-
return f"Swagger {self.spec_version}"
|
908
|
+
def specification(self) -> Specification:
|
909
|
+
version = self.raw_schema.get("swagger", "2.0")
|
910
|
+
return Specification.openapi(version=version)
|
930
911
|
|
931
912
|
def _validate(self) -> None:
|
932
913
|
SWAGGER_20_VALIDATOR.validate(self.raw_schema)
|
@@ -962,6 +943,8 @@ class SwaggerV20(BaseOpenAPISchema):
|
|
962
943
|
for media_type in body_media_types:
|
963
944
|
collected.append(OpenAPI20Body(definition=parameter, media_type=media_type))
|
964
945
|
else:
|
946
|
+
if parameter["in"] in ("header", "cookie"):
|
947
|
+
check_header(parameter)
|
965
948
|
collected.append(OpenAPI20Parameter(definition=parameter))
|
966
949
|
|
967
950
|
if form_parameters:
|
@@ -972,11 +955,9 @@ class SwaggerV20(BaseOpenAPISchema):
|
|
972
955
|
)
|
973
956
|
return collected
|
974
957
|
|
975
|
-
def get_strategies_from_examples(
|
976
|
-
self, operation: APIOperation, as_strategy_kwargs: dict[str, Any] | None = None
|
977
|
-
) -> list[SearchStrategy[Case]]:
|
958
|
+
def get_strategies_from_examples(self, operation: APIOperation, **kwargs: Any) -> list[SearchStrategy[Case]]:
|
978
959
|
"""Get examples from the API operation."""
|
979
|
-
return get_strategies_from_examples(operation,
|
960
|
+
return get_strategies_from_examples(operation, **kwargs)
|
980
961
|
|
981
962
|
def get_response_schema(self, definition: dict[str, Any], scope: str) -> tuple[list[str], dict[str, Any] | None]:
|
982
963
|
scopes, definition = self.resolver.resolve_in_scope(definition, scope)
|
@@ -989,7 +970,7 @@ class SwaggerV20(BaseOpenAPISchema):
|
|
989
970
|
schema, self.nullable_name, is_response_schema=True, update_quantifiers=False
|
990
971
|
)
|
991
972
|
|
992
|
-
def get_content_types(self, operation: APIOperation, response:
|
973
|
+
def get_content_types(self, operation: APIOperation, response: Response) -> list[str]:
|
993
974
|
produces = operation.definition.raw.get("produces", None)
|
994
975
|
if produces:
|
995
976
|
return produces
|
@@ -999,7 +980,7 @@ class SwaggerV20(BaseOpenAPISchema):
|
|
999
980
|
return serialization.serialize_swagger2_parameters(definitions)
|
1000
981
|
|
1001
982
|
def prepare_multipart(
|
1002
|
-
self, form_data:
|
983
|
+
self, form_data: dict[str, Any], operation: APIOperation
|
1003
984
|
) -> tuple[list | None, dict[str, Any] | None]:
|
1004
985
|
"""Prepare form data for sending with `requests`.
|
1005
986
|
|
@@ -1040,27 +1021,30 @@ class SwaggerV20(BaseOpenAPISchema):
|
|
1040
1021
|
def make_case(
|
1041
1022
|
self,
|
1042
1023
|
*,
|
1043
|
-
case_cls: type[C],
|
1044
1024
|
operation: APIOperation,
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1025
|
+
method: str | None = None,
|
1026
|
+
path: str | None = None,
|
1027
|
+
path_parameters: dict[str, Any] | None = None,
|
1028
|
+
headers: dict[str, Any] | None = None,
|
1029
|
+
cookies: dict[str, Any] | None = None,
|
1030
|
+
query: dict[str, Any] | None = None,
|
1031
|
+
body: list | dict[str, Any] | str | int | float | bool | bytes | NotSet = NOT_SET,
|
1050
1032
|
media_type: str | None = None,
|
1051
|
-
|
1052
|
-
) ->
|
1033
|
+
meta: CaseMetadata | None = None,
|
1034
|
+
) -> Case:
|
1053
1035
|
if body is not NOT_SET and media_type is None:
|
1054
1036
|
media_type = operation._get_default_media_type()
|
1055
|
-
return
|
1037
|
+
return Case(
|
1056
1038
|
operation=operation,
|
1039
|
+
method=method or operation.method.upper(),
|
1040
|
+
path=path or operation.path,
|
1057
1041
|
path_parameters=path_parameters,
|
1058
1042
|
headers=CaseInsensitiveDict(headers) if headers is not None else headers,
|
1059
1043
|
cookies=cookies,
|
1060
1044
|
query=query,
|
1061
1045
|
body=body,
|
1062
1046
|
media_type=media_type,
|
1063
|
-
|
1047
|
+
meta=meta,
|
1064
1048
|
)
|
1065
1049
|
|
1066
1050
|
def _get_consumes_for_operation(self, definition: dict[str, Any]) -> list[str]:
|
@@ -1078,6 +1062,8 @@ class SwaggerV20(BaseOpenAPISchema):
|
|
1078
1062
|
|
1079
1063
|
def _get_payload_schema(self, definition: dict[str, Any], media_type: str) -> dict[str, Any] | None:
|
1080
1064
|
for parameter in definition.get("parameters", []):
|
1065
|
+
if "$ref" in parameter:
|
1066
|
+
_, parameter = self.resolver.resolve(parameter["$ref"])
|
1081
1067
|
if parameter["in"] == "body":
|
1082
1068
|
return parameter["schema"]
|
1083
1069
|
return None
|
@@ -1093,15 +1079,12 @@ class OpenApi30(SwaggerV20):
|
|
1093
1079
|
links_field = "links"
|
1094
1080
|
|
1095
1081
|
@property
|
1096
|
-
def
|
1097
|
-
|
1098
|
-
|
1099
|
-
@property
|
1100
|
-
def verbose_name(self) -> str:
|
1101
|
-
return f"Open API {self.spec_version}"
|
1082
|
+
def specification(self) -> Specification:
|
1083
|
+
version = self.raw_schema["openapi"]
|
1084
|
+
return Specification.openapi(version=version)
|
1102
1085
|
|
1103
1086
|
def _validate(self) -> None:
|
1104
|
-
if self.
|
1087
|
+
if self.specification.version.startswith("3.1"):
|
1105
1088
|
# Currently we treat Open API 3.1 as 3.0 in some regard
|
1106
1089
|
OPENAPI_31_VALIDATOR.validate(self.raw_schema)
|
1107
1090
|
else:
|
@@ -1120,7 +1103,12 @@ class OpenApi30(SwaggerV20):
|
|
1120
1103
|
self, parameters: Iterable[dict[str, Any]], definition: dict[str, Any]
|
1121
1104
|
) -> list[OpenAPIParameter]:
|
1122
1105
|
# Open API 3.0 has the `requestBody` keyword, which may contain multiple different payload variants.
|
1123
|
-
collected: list[OpenAPIParameter] = [
|
1106
|
+
collected: list[OpenAPIParameter] = []
|
1107
|
+
|
1108
|
+
for parameter in parameters:
|
1109
|
+
if parameter["in"] in ("header", "cookie"):
|
1110
|
+
check_header(parameter)
|
1111
|
+
collected.append(OpenAPI30Parameter(definition=parameter))
|
1124
1112
|
if "requestBody" in definition:
|
1125
1113
|
required = definition["requestBody"].get("required", False)
|
1126
1114
|
description = definition["requestBody"].get("description")
|
@@ -1143,13 +1131,11 @@ class OpenApi30(SwaggerV20):
|
|
1143
1131
|
)
|
1144
1132
|
return scopes, None
|
1145
1133
|
|
1146
|
-
def get_strategies_from_examples(
|
1147
|
-
self, operation: APIOperation, as_strategy_kwargs: dict[str, Any] | None = None
|
1148
|
-
) -> list[SearchStrategy[Case]]:
|
1134
|
+
def get_strategies_from_examples(self, operation: APIOperation, **kwargs: Any) -> list[SearchStrategy[Case]]:
|
1149
1135
|
"""Get examples from the API operation."""
|
1150
|
-
return get_strategies_from_examples(operation,
|
1136
|
+
return get_strategies_from_examples(operation, **kwargs)
|
1151
1137
|
|
1152
|
-
def get_content_types(self, operation: APIOperation, response:
|
1138
|
+
def get_content_types(self, operation: APIOperation, response: Response) -> list[str]:
|
1153
1139
|
resolved = self._get_response_definitions(operation, response)
|
1154
1140
|
if not resolved:
|
1155
1141
|
return []
|
@@ -1164,7 +1150,7 @@ class OpenApi30(SwaggerV20):
|
|
1164
1150
|
return list(request_body["content"])
|
1165
1151
|
|
1166
1152
|
def prepare_multipart(
|
1167
|
-
self, form_data:
|
1153
|
+
self, form_data: dict[str, Any], operation: APIOperation
|
1168
1154
|
) -> tuple[list | None, dict[str, Any] | None]:
|
1169
1155
|
"""Prepare form data for sending with `requests`.
|
1170
1156
|
|
@@ -1182,7 +1168,7 @@ class OpenApi30(SwaggerV20):
|
|
1182
1168
|
# Open API 3.0 requires media types to be present. We can get here only if the schema defines
|
1183
1169
|
# the "multipart/form-data" media type, or any other more general media type that matches it (like `*/*`)
|
1184
1170
|
for media_type, entry in content.items():
|
1185
|
-
main, sub =
|
1171
|
+
main, sub = media_types.parse(media_type)
|
1186
1172
|
if main in ("*", "multipart") and sub in ("*", "form-data", "mixed"):
|
1187
1173
|
schema = entry.get("schema")
|
1188
1174
|
break
|
@@ -1206,8 +1192,8 @@ class OpenApi30(SwaggerV20):
|
|
1206
1192
|
else:
|
1207
1193
|
body = definition["requestBody"]
|
1208
1194
|
if "content" in body:
|
1209
|
-
main, sub =
|
1195
|
+
main, sub = media_types.parse(media_type)
|
1210
1196
|
for defined_media_type, item in body["content"].items():
|
1211
|
-
if
|
1197
|
+
if media_types.parse(defined_media_type) == (main, sub):
|
1212
1198
|
return item["schema"]
|
1213
1199
|
return None
|