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,6 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
import json
|
|
2
3
|
from dataclasses import dataclass
|
|
3
|
-
from typing import Any, ClassVar,
|
|
4
|
+
from typing import Any, ClassVar, Iterable
|
|
4
5
|
|
|
5
6
|
from ...exceptions import OperationSchemaError
|
|
6
7
|
from ...models import APIOperation
|
|
@@ -15,10 +16,10 @@ class OpenAPIParameter(Parameter):
|
|
|
15
16
|
example_field: ClassVar[str]
|
|
16
17
|
examples_field: ClassVar[str]
|
|
17
18
|
nullable_field: ClassVar[str]
|
|
18
|
-
supported_jsonschema_keywords: ClassVar[
|
|
19
|
+
supported_jsonschema_keywords: ClassVar[tuple[str, ...]]
|
|
19
20
|
|
|
20
21
|
@property
|
|
21
|
-
def description(self) ->
|
|
22
|
+
def description(self) -> str | None:
|
|
22
23
|
"""A brief parameter description."""
|
|
23
24
|
return self.definition.get("description")
|
|
24
25
|
|
|
@@ -87,12 +88,12 @@ class OpenAPIParameter(Parameter):
|
|
|
87
88
|
"""
|
|
88
89
|
return self.definition.get("schema", {}).get("example")
|
|
89
90
|
|
|
90
|
-
def as_json_schema(self, operation: APIOperation) ->
|
|
91
|
+
def as_json_schema(self, operation: APIOperation) -> dict[str, Any]:
|
|
91
92
|
"""Convert parameter's definition to JSON Schema."""
|
|
92
93
|
schema = self.from_open_api_to_json_schema(operation, self.definition)
|
|
93
94
|
return self.transform_keywords(schema)
|
|
94
95
|
|
|
95
|
-
def transform_keywords(self, schema:
|
|
96
|
+
def transform_keywords(self, schema: dict[str, Any]) -> dict[str, Any]:
|
|
96
97
|
"""Transform Open API specific keywords into JSON Schema compatible form."""
|
|
97
98
|
definition = to_json_schema_recursive(schema, self.nullable_field)
|
|
98
99
|
# Headers are strings, but it is not always explicitly defined in the schema. By preparing them properly, we
|
|
@@ -106,7 +107,7 @@ class OpenAPIParameter(Parameter):
|
|
|
106
107
|
definition.setdefault("type", "string")
|
|
107
108
|
return definition
|
|
108
109
|
|
|
109
|
-
def from_open_api_to_json_schema(self, operation: APIOperation, open_api_schema:
|
|
110
|
+
def from_open_api_to_json_schema(self, operation: APIOperation, open_api_schema: dict[str, Any]) -> dict[str, Any]:
|
|
110
111
|
"""Convert Open API's `Schema` to JSON Schema."""
|
|
111
112
|
return {
|
|
112
113
|
key: value
|
|
@@ -137,7 +138,7 @@ class OpenAPI20Parameter(OpenAPIParameter):
|
|
|
137
138
|
# is required, which is not relevant because these parameters are later constructed
|
|
138
139
|
# into an "object" schema, and the value of this keyword is used there.
|
|
139
140
|
# The following keywords are relevant only for non-body parameters.
|
|
140
|
-
supported_jsonschema_keywords: ClassVar[
|
|
141
|
+
supported_jsonschema_keywords: ClassVar[tuple[str, ...]] = (
|
|
141
142
|
"$ref",
|
|
142
143
|
"type", # only as a string
|
|
143
144
|
"format",
|
|
@@ -211,7 +212,7 @@ class OpenAPI30Parameter(OpenAPIParameter):
|
|
|
211
212
|
def is_header(self) -> bool:
|
|
212
213
|
return self.location in ("header", "cookie")
|
|
213
214
|
|
|
214
|
-
def from_open_api_to_json_schema(self, operation: APIOperation, open_api_schema:
|
|
215
|
+
def from_open_api_to_json_schema(self, operation: APIOperation, open_api_schema: dict[str, Any]) -> dict[str, Any]:
|
|
215
216
|
open_api_schema = get_parameter_schema(operation, open_api_schema)
|
|
216
217
|
return super().from_open_api_to_json_schema(operation, open_api_schema)
|
|
217
218
|
|
|
@@ -263,7 +264,7 @@ class OpenAPI20Body(OpenAPIBody, OpenAPI20Parameter):
|
|
|
263
264
|
# NOTE. For Open API 2.0 bodies, we still give `x-example` precedence over the schema-level `example` field to keep
|
|
264
265
|
# the precedence rules consistent.
|
|
265
266
|
|
|
266
|
-
def as_json_schema(self, operation: APIOperation) ->
|
|
267
|
+
def as_json_schema(self, operation: APIOperation) -> dict[str, Any]:
|
|
267
268
|
"""Convert body definition to JSON Schema."""
|
|
268
269
|
# `schema` is required in Open API 2.0 when the `in` keyword is `body`
|
|
269
270
|
schema = self.definition["schema"]
|
|
@@ -290,14 +291,14 @@ class OpenAPI30Body(OpenAPIBody, OpenAPI30Parameter):
|
|
|
290
291
|
# The `required` keyword is located above the schema for concrete media-type;
|
|
291
292
|
# Therefore, it is passed here explicitly
|
|
292
293
|
required: bool = False
|
|
293
|
-
description:
|
|
294
|
+
description: str | None = None
|
|
294
295
|
|
|
295
|
-
def as_json_schema(self, operation: APIOperation) ->
|
|
296
|
+
def as_json_schema(self, operation: APIOperation) -> dict[str, Any]:
|
|
296
297
|
"""Convert body definition to JSON Schema."""
|
|
297
298
|
schema = get_media_type_schema(self.definition)
|
|
298
299
|
return self.transform_keywords(schema)
|
|
299
300
|
|
|
300
|
-
def transform_keywords(self, schema:
|
|
301
|
+
def transform_keywords(self, schema: dict[str, Any]) -> dict[str, Any]:
|
|
301
302
|
definition = super().transform_keywords(schema)
|
|
302
303
|
if self.is_form:
|
|
303
304
|
# It significantly reduces the "filtering" part of data generation.
|
|
@@ -318,17 +319,17 @@ class OpenAPI30Body(OpenAPIBody, OpenAPI30Parameter):
|
|
|
318
319
|
class OpenAPI20CompositeBody(OpenAPIBody, OpenAPI20Parameter):
|
|
319
320
|
"""A special container to abstract over multiple `formData` parameters."""
|
|
320
321
|
|
|
321
|
-
definition:
|
|
322
|
+
definition: list[OpenAPI20Parameter]
|
|
322
323
|
|
|
323
324
|
@classmethod
|
|
324
|
-
def from_parameters(cls, *parameters:
|
|
325
|
+
def from_parameters(cls, *parameters: dict[str, Any], media_type: str) -> OpenAPI20CompositeBody:
|
|
325
326
|
return cls(
|
|
326
327
|
definition=[OpenAPI20Parameter(parameter) for parameter in parameters],
|
|
327
328
|
media_type=media_type,
|
|
328
329
|
)
|
|
329
330
|
|
|
330
331
|
@property
|
|
331
|
-
def description(self) ->
|
|
332
|
+
def description(self) -> str | None:
|
|
332
333
|
return None
|
|
333
334
|
|
|
334
335
|
@property
|
|
@@ -344,12 +345,12 @@ class OpenAPI20CompositeBody(OpenAPIBody, OpenAPI20Parameter):
|
|
|
344
345
|
def _schema_example(self) -> Any:
|
|
345
346
|
return {parameter.name: parameter._schema_example for parameter in self.definition if parameter._schema_example}
|
|
346
347
|
|
|
347
|
-
def as_json_schema(self, operation: APIOperation) ->
|
|
348
|
+
def as_json_schema(self, operation: APIOperation) -> dict[str, Any]:
|
|
348
349
|
"""The composite body is transformed into an "object" JSON Schema."""
|
|
349
350
|
return parameters_to_json_schema(operation, self.definition)
|
|
350
351
|
|
|
351
352
|
|
|
352
|
-
def parameters_to_json_schema(operation: APIOperation, parameters: Iterable[OpenAPIParameter]) ->
|
|
353
|
+
def parameters_to_json_schema(operation: APIOperation, parameters: Iterable[OpenAPIParameter]) -> dict[str, Any]:
|
|
353
354
|
"""Create an "object" JSON schema from a list of Open API parameters.
|
|
354
355
|
|
|
355
356
|
:param List[OpenAPIParameter] parameters: A list of Open API parameters related to the same location. All of
|
|
@@ -402,7 +403,7 @@ MISSING_SCHEMA_OR_CONTENT_MESSAGE = (
|
|
|
402
403
|
)
|
|
403
404
|
|
|
404
405
|
|
|
405
|
-
def get_parameter_schema(operation: APIOperation, data:
|
|
406
|
+
def get_parameter_schema(operation: APIOperation, data: dict[str, Any]) -> dict[str, Any]:
|
|
406
407
|
"""Extract `schema` from Open API 3.0 `Parameter`."""
|
|
407
408
|
# In Open API 3.0, there could be "schema" or "content" field. They are mutually exclusive.
|
|
408
409
|
if "schema" in data:
|
|
@@ -423,7 +424,7 @@ def get_parameter_schema(operation: APIOperation, data: Dict[str, Any]) -> Dict[
|
|
|
423
424
|
return get_media_type_schema(media_type_object)
|
|
424
425
|
|
|
425
426
|
|
|
426
|
-
def get_media_type_schema(definition:
|
|
427
|
+
def get_media_type_schema(definition: dict[str, Any]) -> dict[str, Any]:
|
|
427
428
|
"""Extract `schema` from Open API 3.0 `MediaType`."""
|
|
428
429
|
# The `schema` keyword is optional, and we treat it as the payload could be any value of the specified media type
|
|
429
430
|
# Note, the main reason to have this function is to have an explicit name for the action we're doing.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
from functools import lru_cache
|
|
2
|
-
from typing import Any, Callable, Dict,
|
|
3
|
+
from typing import Any, Callable, Dict, Union, overload
|
|
3
4
|
from urllib.request import urlopen
|
|
4
5
|
|
|
5
6
|
import jsonschema
|
|
@@ -16,20 +17,20 @@ from .utils import get_type
|
|
|
16
17
|
RECURSION_DEPTH_LIMIT = 100
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
def load_file_impl(location: str, opener: Callable) ->
|
|
20
|
+
def load_file_impl(location: str, opener: Callable) -> dict[str, Any]:
|
|
20
21
|
"""Load a schema from the given file."""
|
|
21
22
|
with opener(location) as fd:
|
|
22
23
|
return load_yaml(fd)
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
@lru_cache
|
|
26
|
-
def load_file(location: str) ->
|
|
26
|
+
@lru_cache
|
|
27
|
+
def load_file(location: str) -> dict[str, Any]:
|
|
27
28
|
"""Load a schema from the given file."""
|
|
28
29
|
return load_file_impl(location, open)
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
@lru_cache
|
|
32
|
-
def load_file_uri(location: str) ->
|
|
32
|
+
@lru_cache
|
|
33
|
+
def load_file_uri(location: str) -> dict[str, Any]:
|
|
33
34
|
"""Load a schema from the given file uri."""
|
|
34
35
|
return load_file_impl(location, urlopen)
|
|
35
36
|
|
|
@@ -53,11 +54,11 @@ class InliningResolver(jsonschema.RefResolver):
|
|
|
53
54
|
super().__init__(*args, **kwargs)
|
|
54
55
|
|
|
55
56
|
@overload
|
|
56
|
-
def resolve_all(self, item:
|
|
57
|
+
def resolve_all(self, item: dict[str, Any], recursion_level: int = 0) -> dict[str, Any]:
|
|
57
58
|
pass
|
|
58
59
|
|
|
59
60
|
@overload
|
|
60
|
-
def resolve_all(self, item:
|
|
61
|
+
def resolve_all(self, item: list, recursion_level: int = 0) -> list:
|
|
61
62
|
pass
|
|
62
63
|
|
|
63
64
|
def resolve_all(self, item: JSONType, recursion_level: int = 0) -> JSONType:
|
|
@@ -79,7 +80,7 @@ class InliningResolver(jsonschema.RefResolver):
|
|
|
79
80
|
return [self.resolve_all(sub_item, recursion_level) for sub_item in item]
|
|
80
81
|
return item
|
|
81
82
|
|
|
82
|
-
def resolve_in_scope(self, definition:
|
|
83
|
+
def resolve_in_scope(self, definition: dict[str, Any], scope: str) -> tuple[list[str], dict[str, Any]]:
|
|
83
84
|
scopes = [scope]
|
|
84
85
|
# if there is `$ref` then we have a scope change that should be used during validation later to
|
|
85
86
|
# resolve nested references correctly
|
|
@@ -105,7 +106,7 @@ class ConvertingResolver(InliningResolver):
|
|
|
105
106
|
self.nullable_name = nullable_name
|
|
106
107
|
self.is_response_schema = is_response_schema
|
|
107
108
|
|
|
108
|
-
def resolve(self, ref: str) ->
|
|
109
|
+
def resolve(self, ref: str) -> tuple[str, Any]:
|
|
109
110
|
url, document = super().resolve(ref)
|
|
110
111
|
document = to_json_schema_recursive(
|
|
111
112
|
document, nullable_name=self.nullable_name, is_response_schema=self.is_response_schema
|
|
@@ -113,14 +114,14 @@ class ConvertingResolver(InliningResolver):
|
|
|
113
114
|
return url, document
|
|
114
115
|
|
|
115
116
|
|
|
116
|
-
def remove_optional_references(schema:
|
|
117
|
+
def remove_optional_references(schema: dict[str, Any]) -> None:
|
|
117
118
|
"""Remove optional parts of the schema that contain references.
|
|
118
119
|
|
|
119
120
|
It covers only the most popular cases, as removing all optional parts is complicated.
|
|
120
121
|
We might fall back to filtering out invalid cases in the future.
|
|
121
122
|
"""
|
|
122
123
|
|
|
123
|
-
def clean_properties(s:
|
|
124
|
+
def clean_properties(s: dict[str, Any]) -> None:
|
|
124
125
|
properties = s["properties"]
|
|
125
126
|
required = s.get("required", [])
|
|
126
127
|
for name, value in list(properties.items()):
|
|
@@ -132,7 +133,7 @@ def remove_optional_references(schema: Dict[str, Any]) -> None:
|
|
|
132
133
|
else:
|
|
133
134
|
stack.append(value)
|
|
134
135
|
|
|
135
|
-
def clean_items(s:
|
|
136
|
+
def clean_items(s: dict[str, Any]) -> None:
|
|
136
137
|
items = s["items"]
|
|
137
138
|
min_items = s.get("minItems", 0)
|
|
138
139
|
if not min_items:
|
|
@@ -141,25 +142,25 @@ def remove_optional_references(schema: Dict[str, Any]) -> None:
|
|
|
141
142
|
if isinstance(items, list) and any_ref(items):
|
|
142
143
|
force_empty_list(s)
|
|
143
144
|
|
|
144
|
-
def clean_additional_properties(s:
|
|
145
|
+
def clean_additional_properties(s: dict[str, Any]) -> None:
|
|
145
146
|
additional_properties = s["additionalProperties"]
|
|
146
147
|
if isinstance(additional_properties, dict) and "$ref" in additional_properties:
|
|
147
148
|
s["additionalProperties"] = False
|
|
148
149
|
|
|
149
|
-
def force_empty_list(s:
|
|
150
|
+
def force_empty_list(s: dict[str, Any]) -> None:
|
|
150
151
|
del s["items"]
|
|
151
152
|
s["maxItems"] = 0
|
|
152
153
|
|
|
153
|
-
def any_ref(i:
|
|
154
|
+
def any_ref(i: list[dict[str, Any]]) -> bool:
|
|
154
155
|
return any("$ref" in item for item in i)
|
|
155
156
|
|
|
156
|
-
def contains_ref(s:
|
|
157
|
+
def contains_ref(s: dict[str, Any]) -> bool:
|
|
157
158
|
if "$ref" in s:
|
|
158
159
|
return True
|
|
159
160
|
i = s.get("items")
|
|
160
161
|
return (isinstance(i, dict) and "$ref" in i) or isinstance(i, list) and any_ref(i)
|
|
161
162
|
|
|
162
|
-
def can_elide(s:
|
|
163
|
+
def can_elide(s: dict[str, Any]) -> bool:
|
|
163
164
|
# Whether this schema could be dropped from a list of schemas
|
|
164
165
|
type_ = get_type(s)
|
|
165
166
|
if type_ == ["object"]:
|
|
@@ -168,7 +169,7 @@ def remove_optional_references(schema: Dict[str, Any]) -> None:
|
|
|
168
169
|
# Has at least one keyword -> should not be removed
|
|
169
170
|
return not any(k in ALL_KEYWORDS for k in s)
|
|
170
171
|
|
|
171
|
-
def on_single_item_combinators(s:
|
|
172
|
+
def on_single_item_combinators(s: dict[str, Any]) -> list[str]:
|
|
172
173
|
# Schema example:
|
|
173
174
|
# {
|
|
174
175
|
# "type": "object",
|
|
@@ -203,7 +204,7 @@ def remove_optional_references(schema: Dict[str, Any]) -> None:
|
|
|
203
204
|
del definition[k]
|
|
204
205
|
|
|
205
206
|
|
|
206
|
-
def resolve_pointer(document: Any, pointer: str) ->
|
|
207
|
+
def resolve_pointer(document: Any, pointer: str) -> dict | list | str | int | float | None:
|
|
207
208
|
"""Implementation is adapted from Rust's `serde-json` crate.
|
|
208
209
|
|
|
209
210
|
Ref: https://github.com/serde-rs/json/blob/master/src/value/mod.rs#L751
|