schemathesis 4.3.13__py3-none-any.whl → 4.3.15__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.
Potentially problematic release.
This version of schemathesis might be problematic. Click here for more details.
- schemathesis/checks.py +11 -0
- schemathesis/core/registries.py +3 -0
- schemathesis/engine/errors.py +14 -53
- schemathesis/engine/phases/unit/_executor.py +14 -3
- schemathesis/generation/hypothesis/builder.py +3 -0
- schemathesis/generation/hypothesis/examples.py +11 -5
- schemathesis/generation/hypothesis/reporting.py +272 -1
- schemathesis/openapi/checks.py +1 -1
- schemathesis/pytest/plugin.py +18 -2
- schemathesis/specs/graphql/schemas.py +6 -2
- schemathesis/specs/openapi/checks.py +2 -2
- schemathesis/specs/openapi/converter.py +18 -0
- schemathesis/transport/requests.py +3 -1
- {schemathesis-4.3.13.dist-info → schemathesis-4.3.15.dist-info}/METADATA +1 -1
- {schemathesis-4.3.13.dist-info → schemathesis-4.3.15.dist-info}/RECORD +18 -18
- {schemathesis-4.3.13.dist-info → schemathesis-4.3.15.dist-info}/WHEEL +0 -0
- {schemathesis-4.3.13.dist-info → schemathesis-4.3.15.dist-info}/entry_points.txt +0 -0
- {schemathesis-4.3.13.dist-info → schemathesis-4.3.15.dist-info}/licenses/LICENSE +0 -0
schemathesis/checks.py
CHANGED
|
@@ -187,3 +187,14 @@ def run_checks(
|
|
|
187
187
|
on_failure(name, collected, sub_failure)
|
|
188
188
|
|
|
189
189
|
return collected
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def __getattr__(name: str) -> Any:
|
|
193
|
+
try:
|
|
194
|
+
return CHECKS.get_one(name)
|
|
195
|
+
except KeyError:
|
|
196
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def __dir__() -> list[str]:
|
|
200
|
+
return sorted(list(globals().keys()) + CHECKS.get_all_names())
|
schemathesis/core/registries.py
CHANGED
|
@@ -26,6 +26,9 @@ class Registry(Generic[T]):
|
|
|
26
26
|
def get_all(self) -> list[T]:
|
|
27
27
|
return list(self._items.values())
|
|
28
28
|
|
|
29
|
+
def get_one(self, name: str) -> T:
|
|
30
|
+
return self._items[name]
|
|
31
|
+
|
|
29
32
|
def get_by_names(self, names: Sequence[str]) -> list[T]:
|
|
30
33
|
"""Get items by their names."""
|
|
31
34
|
return [self._items[name] for name in names]
|
schemathesis/engine/errors.py
CHANGED
|
@@ -12,6 +12,8 @@ from dataclasses import dataclass
|
|
|
12
12
|
from functools import cached_property
|
|
13
13
|
from typing import TYPE_CHECKING, Callable, Iterator, Sequence, cast
|
|
14
14
|
|
|
15
|
+
from hypothesis import HealthCheck
|
|
16
|
+
|
|
15
17
|
from schemathesis import errors
|
|
16
18
|
from schemathesis.core.errors import (
|
|
17
19
|
InfiniteRecursiveReference,
|
|
@@ -23,6 +25,7 @@ from schemathesis.core.errors import (
|
|
|
23
25
|
get_request_error_message,
|
|
24
26
|
split_traceback,
|
|
25
27
|
)
|
|
28
|
+
from schemathesis.generation.hypothesis.reporting import HEALTH_CHECK_ACTIONS, HEALTH_CHECK_TITLES
|
|
26
29
|
|
|
27
30
|
if TYPE_CHECKING:
|
|
28
31
|
import hypothesis.errors
|
|
@@ -119,18 +122,6 @@ class EngineErrorInfo:
|
|
|
119
122
|
scalar_name = scalar_name_from_error(self._error)
|
|
120
123
|
return f"Scalar type '{scalar_name}' is not recognized"
|
|
121
124
|
|
|
122
|
-
if self._kind == RuntimeErrorKind.HYPOTHESIS_HEALTH_CHECK_DATA_TOO_LARGE:
|
|
123
|
-
return HEALTH_CHECK_MESSAGE_DATA_TOO_LARGE
|
|
124
|
-
if self._kind == RuntimeErrorKind.HYPOTHESIS_HEALTH_CHECK_FILTER_TOO_MUCH:
|
|
125
|
-
return HEALTH_CHECK_MESSAGE_FILTER_TOO_MUCH
|
|
126
|
-
if self._kind == RuntimeErrorKind.HYPOTHESIS_HEALTH_CHECK_TOO_SLOW:
|
|
127
|
-
return HEALTH_CHECK_MESSAGE_TOO_SLOW
|
|
128
|
-
if self._kind == RuntimeErrorKind.HYPOTHESIS_HEALTH_CHECK_LARGE_BASE_EXAMPLE:
|
|
129
|
-
return HEALTH_CHECK_MESSAGE_LARGE_BASE_EXAMPLE
|
|
130
|
-
|
|
131
|
-
if self._kind == RuntimeErrorKind.HYPOTHESIS_UNSATISFIABLE:
|
|
132
|
-
return f"{self._error}. Possible reasons:"
|
|
133
|
-
|
|
134
125
|
if self._kind in (
|
|
135
126
|
RuntimeErrorKind.SCHEMA_INVALID_REGULAR_EXPRESSION,
|
|
136
127
|
RuntimeErrorKind.SCHEMA_GENERIC,
|
|
@@ -147,13 +138,6 @@ class EngineErrorInfo:
|
|
|
147
138
|
if isinstance(self._error, requests.RequestException):
|
|
148
139
|
return get_request_error_extras(self._error)
|
|
149
140
|
|
|
150
|
-
if self._kind == RuntimeErrorKind.HYPOTHESIS_UNSATISFIABLE:
|
|
151
|
-
return [
|
|
152
|
-
"- Contradictory schema constraints, such as a minimum value exceeding the maximum.",
|
|
153
|
-
"- Invalid schema definitions for headers or cookies, for example allowing for non-ASCII characters.",
|
|
154
|
-
"- Excessive schema complexity, which hinders parameter generation.",
|
|
155
|
-
]
|
|
156
|
-
|
|
157
141
|
return []
|
|
158
142
|
|
|
159
143
|
@cached_property
|
|
@@ -226,16 +210,9 @@ def scalar_name_from_error(exception: hypothesis.errors.InvalidArgument) -> str:
|
|
|
226
210
|
|
|
227
211
|
|
|
228
212
|
def extract_health_check_error(error: hypothesis.errors.FailedHealthCheck) -> hypothesis.HealthCheck | None:
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if match:
|
|
233
|
-
return {
|
|
234
|
-
"data_too_large": HealthCheck.data_too_large,
|
|
235
|
-
"filter_too_much": HealthCheck.filter_too_much,
|
|
236
|
-
"too_slow": HealthCheck.too_slow,
|
|
237
|
-
"large_base_example": HealthCheck.large_base_example,
|
|
238
|
-
}.get(match.group(1))
|
|
213
|
+
for key, title in HEALTH_CHECK_TITLES.items():
|
|
214
|
+
if title in str(error):
|
|
215
|
+
return key
|
|
239
216
|
return None
|
|
240
217
|
|
|
241
218
|
|
|
@@ -243,11 +220,17 @@ def get_runtime_error_suggestion(error_type: RuntimeErrorKind, bold: Callable[[s
|
|
|
243
220
|
"""Get a user-friendly suggestion for handling the error."""
|
|
244
221
|
|
|
245
222
|
def _format_health_check_suggestion(label: str) -> str:
|
|
246
|
-
|
|
223
|
+
base = {
|
|
224
|
+
"data_too_large": HEALTH_CHECK_ACTIONS[HealthCheck.data_too_large],
|
|
225
|
+
"filter_too_much": HEALTH_CHECK_ACTIONS[HealthCheck.filter_too_much],
|
|
226
|
+
"too_slow": HEALTH_CHECK_ACTIONS[HealthCheck.too_slow],
|
|
227
|
+
"large_base_example": HEALTH_CHECK_ACTIONS[HealthCheck.large_base_example],
|
|
228
|
+
}[label]
|
|
229
|
+
return f"{base} or bypass this health check using {bold(f'`--suppress-health-check={label}`')}."
|
|
247
230
|
|
|
248
231
|
return {
|
|
249
232
|
RuntimeErrorKind.CONNECTION_SSL: f"Bypass SSL verification with {bold('`--tls-verify=false`')}.",
|
|
250
|
-
RuntimeErrorKind.HYPOTHESIS_UNSATISFIABLE: "
|
|
233
|
+
RuntimeErrorKind.HYPOTHESIS_UNSATISFIABLE: "Review all parameters and request body schemas for conflicting constraints.",
|
|
251
234
|
RuntimeErrorKind.SCHEMA_NO_LINKS_FOUND: "Review your endpoint filters to include linked operations",
|
|
252
235
|
RuntimeErrorKind.SCHEMA_INVALID_REGULAR_EXPRESSION: "Ensure your regex is compatible with Python's syntax.\n"
|
|
253
236
|
"For guidance, visit: https://docs.python.org/3/library/re.html",
|
|
@@ -262,28 +245,6 @@ def get_runtime_error_suggestion(error_type: RuntimeErrorKind, bold: Callable[[s
|
|
|
262
245
|
}.get(error_type)
|
|
263
246
|
|
|
264
247
|
|
|
265
|
-
HEALTH_CHECK_MESSAGE_DATA_TOO_LARGE = """There's a notable occurrence of examples surpassing the maximum size limit.
|
|
266
|
-
Typically, generating excessively large examples can compromise the quality of test outcomes.
|
|
267
|
-
|
|
268
|
-
Consider revising the schema to more accurately represent typical use cases
|
|
269
|
-
or applying constraints to reduce the data size."""
|
|
270
|
-
HEALTH_CHECK_MESSAGE_FILTER_TOO_MUCH = """A significant number of generated examples are being filtered out, indicating
|
|
271
|
-
that the schema's constraints may be too complex.
|
|
272
|
-
|
|
273
|
-
This level of filtration can slow down testing and affect the distribution
|
|
274
|
-
of generated data. Review and simplify the schema constraints where
|
|
275
|
-
possible to mitigate this issue."""
|
|
276
|
-
HEALTH_CHECK_MESSAGE_TOO_SLOW = "Data generation is extremely slow. Consider reducing the complexity of the schema."
|
|
277
|
-
HEALTH_CHECK_MESSAGE_LARGE_BASE_EXAMPLE = """A health check has identified that the smallest example derived from the schema
|
|
278
|
-
is excessively large, potentially leading to inefficient test execution.
|
|
279
|
-
|
|
280
|
-
This is commonly due to schemas that specify large-scale data structures by
|
|
281
|
-
default, such as an array with an extensive number of elements.
|
|
282
|
-
|
|
283
|
-
Consider revising the schema to more accurately represent typical use cases
|
|
284
|
-
or applying constraints to reduce the data size."""
|
|
285
|
-
|
|
286
|
-
|
|
287
248
|
@enum.unique
|
|
288
249
|
class RuntimeErrorKind(str, enum.Enum):
|
|
289
250
|
"""Classification of runtime errors."""
|
|
@@ -54,7 +54,11 @@ from schemathesis.generation.hypothesis.builder import (
|
|
|
54
54
|
UnresolvableReferenceMark,
|
|
55
55
|
UnsatisfiableExampleMark,
|
|
56
56
|
)
|
|
57
|
-
from schemathesis.generation.hypothesis.reporting import
|
|
57
|
+
from schemathesis.generation.hypothesis.reporting import (
|
|
58
|
+
build_health_check_error,
|
|
59
|
+
build_unsatisfiable_error,
|
|
60
|
+
ignore_hypothesis_output,
|
|
61
|
+
)
|
|
58
62
|
|
|
59
63
|
if TYPE_CHECKING:
|
|
60
64
|
from schemathesis.schemas import APIOperation
|
|
@@ -162,10 +166,13 @@ def run_test(
|
|
|
162
166
|
status = Status.FAILURE
|
|
163
167
|
except BaseExceptionGroup:
|
|
164
168
|
status = Status.ERROR
|
|
169
|
+
except hypothesis.errors.FailedHealthCheck as exc:
|
|
170
|
+
status = Status.ERROR
|
|
171
|
+
yield non_fatal_error(build_health_check_error(operation, exc, with_tip=False))
|
|
165
172
|
except hypothesis.errors.Unsatisfiable:
|
|
166
173
|
# We need more clear error message here
|
|
167
174
|
status = Status.ERROR
|
|
168
|
-
yield non_fatal_error(
|
|
175
|
+
yield non_fatal_error(build_unsatisfiable_error(operation, with_tip=False))
|
|
169
176
|
except KeyboardInterrupt:
|
|
170
177
|
yield scenario_finished(Status.INTERRUPTED)
|
|
171
178
|
yield events.Interrupted(phase=phase)
|
|
@@ -211,7 +218,11 @@ def run_test(
|
|
|
211
218
|
# `hypothesis-jsonschema` emits a warning on invalid regular expression syntax
|
|
212
219
|
yield non_fatal_error(InvalidRegexPattern.from_hypothesis_jsonschema_message(message))
|
|
213
220
|
else:
|
|
214
|
-
|
|
221
|
+
health_check = build_health_check_error(operation, exc, with_tip=False)
|
|
222
|
+
if isinstance(health_check, hypothesis.errors.FailedHealthCheck):
|
|
223
|
+
yield non_fatal_error(health_check)
|
|
224
|
+
else:
|
|
225
|
+
yield non_fatal_error(exc)
|
|
215
226
|
except hypothesis.errors.DeadlineExceeded as exc:
|
|
216
227
|
status = Status.ERROR
|
|
217
228
|
yield non_fatal_error(DeadlineExceeded.from_exc(exc))
|
|
@@ -121,6 +121,8 @@ def create_test(
|
|
|
121
121
|
kwargs=config.given_kwargs,
|
|
122
122
|
)
|
|
123
123
|
|
|
124
|
+
ApiOperationMark.set(hypothesis_test, operation)
|
|
125
|
+
|
|
124
126
|
if config.seed is not None:
|
|
125
127
|
hypothesis_test = hypothesis.seed(config.seed)(hypothesis_test)
|
|
126
128
|
|
|
@@ -957,3 +959,4 @@ InvalidHeadersExampleMark = Mark[dict[str, str]](attr_name="invalid_example_head
|
|
|
957
959
|
MissingPathParameters = Mark[InvalidSchema](attr_name="missing_path_parameters")
|
|
958
960
|
InfiniteRecursiveReferenceMark = Mark[InfiniteRecursiveReference](attr_name="infinite_recursive_reference")
|
|
959
961
|
UnresolvableReferenceMark = Mark[UnresolvableReference](attr_name="unresolvable_reference")
|
|
962
|
+
ApiOperationMark = Mark[APIOperation](attr_name="api_operation")
|
|
@@ -28,17 +28,23 @@ def default_settings() -> settings:
|
|
|
28
28
|
T = TypeVar("T")
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def generate_one(strategy: st.SearchStrategy[T]) -> T: # type: ignore[type-var]
|
|
31
|
+
def generate_one(strategy: st.SearchStrategy[T], suppress_health_check: list | None = None) -> T: # type: ignore[type-var]
|
|
32
32
|
examples: list[T] = []
|
|
33
|
-
add_single_example(strategy, examples)
|
|
33
|
+
add_single_example(strategy, examples, suppress_health_check)
|
|
34
34
|
return examples[0]
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def add_single_example(
|
|
38
|
-
|
|
37
|
+
def add_single_example(
|
|
38
|
+
strategy: st.SearchStrategy[T], examples: list[T], suppress_health_check: list | None = None
|
|
39
|
+
) -> None:
|
|
40
|
+
from hypothesis import given, seed, settings
|
|
41
|
+
|
|
42
|
+
applied_settings = default_settings()
|
|
43
|
+
if suppress_health_check is not None:
|
|
44
|
+
applied_settings = settings(applied_settings, suppress_health_check=suppress_health_check)
|
|
39
45
|
|
|
40
46
|
@given(strategy) # type: ignore
|
|
41
|
-
@
|
|
47
|
+
@applied_settings # type: ignore
|
|
42
48
|
def example_generating_inner_function(ex: T) -> None:
|
|
43
49
|
examples.append(ex)
|
|
44
50
|
|
|
@@ -1,8 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from contextlib import contextmanager
|
|
2
|
-
from
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Generator
|
|
3
7
|
|
|
8
|
+
from hypothesis import HealthCheck
|
|
9
|
+
from hypothesis.errors import FailedHealthCheck, InvalidArgument, Unsatisfiable
|
|
4
10
|
from hypothesis.reporting import with_reporter
|
|
5
11
|
|
|
12
|
+
from schemathesis.config import OutputConfig
|
|
13
|
+
from schemathesis.core.jsonschema.types import JsonSchema
|
|
14
|
+
from schemathesis.core.output import truncate_json
|
|
15
|
+
from schemathesis.core.parameters import ParameterLocation
|
|
16
|
+
from schemathesis.generation.hypothesis.examples import generate_one
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from schemathesis.schemas import APIOperation
|
|
20
|
+
|
|
6
21
|
|
|
7
22
|
def ignore(_: str) -> None:
|
|
8
23
|
pass
|
|
@@ -12,3 +27,259 @@ def ignore(_: str) -> None:
|
|
|
12
27
|
def ignore_hypothesis_output() -> Generator:
|
|
13
28
|
with with_reporter(ignore): # type: ignore
|
|
14
29
|
yield
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
UNSATISFIABILITY_CAUSE = """ - Type mismatch (e.g., enum with strings but type: integer)
|
|
33
|
+
- Contradictory constraints (e.g., minimum > maximum)
|
|
34
|
+
- Regex that's too complex to generate values for"""
|
|
35
|
+
|
|
36
|
+
GENERIC_UNSATISFIABLE_MESSAGE = f"""Cannot generate test data for this operation
|
|
37
|
+
|
|
38
|
+
Unable to identify the specific parameter. Common causes:
|
|
39
|
+
{UNSATISFIABILITY_CAUSE}"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class UnsatisfiableParameter:
|
|
44
|
+
location: ParameterLocation
|
|
45
|
+
name: str
|
|
46
|
+
schema: JsonSchema
|
|
47
|
+
|
|
48
|
+
__slots__ = ("location", "name", "schema")
|
|
49
|
+
|
|
50
|
+
def get_error_message(self, config: OutputConfig) -> str:
|
|
51
|
+
formatted_schema = truncate_json(self.schema, config=config)
|
|
52
|
+
|
|
53
|
+
if self.location == ParameterLocation.BODY:
|
|
54
|
+
# For body, name is the media type
|
|
55
|
+
location = f"request body ({self.name})"
|
|
56
|
+
else:
|
|
57
|
+
location = f"{self.location.value} parameter '{self.name}'"
|
|
58
|
+
|
|
59
|
+
return f"""Cannot generate test data for {location}
|
|
60
|
+
Schema:
|
|
61
|
+
|
|
62
|
+
{formatted_schema}
|
|
63
|
+
|
|
64
|
+
This usually means:
|
|
65
|
+
{UNSATISFIABILITY_CAUSE}"""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def find_unsatisfiable_parameter(operation: APIOperation) -> UnsatisfiableParameter | None:
|
|
69
|
+
from hypothesis_jsonschema import from_schema
|
|
70
|
+
|
|
71
|
+
for location, container in (
|
|
72
|
+
(ParameterLocation.QUERY, operation.query),
|
|
73
|
+
(ParameterLocation.PATH, operation.path_parameters),
|
|
74
|
+
(ParameterLocation.HEADER, operation.headers),
|
|
75
|
+
(ParameterLocation.COOKIE, operation.cookies),
|
|
76
|
+
(ParameterLocation.BODY, operation.body),
|
|
77
|
+
):
|
|
78
|
+
for parameter in container:
|
|
79
|
+
try:
|
|
80
|
+
generate_one(from_schema(parameter.optimized_schema))
|
|
81
|
+
except Unsatisfiable:
|
|
82
|
+
if location == ParameterLocation.BODY:
|
|
83
|
+
name = parameter.media_type
|
|
84
|
+
else:
|
|
85
|
+
name = parameter.name
|
|
86
|
+
schema = unbundle_schema_refs(parameter.optimized_schema, parameter.name_to_uri)
|
|
87
|
+
return UnsatisfiableParameter(location=location, name=name, schema=schema)
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def unbundle_schema_refs(schema: JsonSchema | list[JsonSchema], name_to_uri: dict[str, str]) -> JsonSchema:
|
|
92
|
+
if isinstance(schema, dict):
|
|
93
|
+
result: dict[str, Any] = {}
|
|
94
|
+
for key, value in schema.items():
|
|
95
|
+
if key == "$ref" and isinstance(value, str) and value.startswith("#/x-bundled/"):
|
|
96
|
+
# Extract bundled name (e.g., "schema1" from "#/x-bundled/schema1")
|
|
97
|
+
bundled_name = value.split("/")[-1]
|
|
98
|
+
if bundled_name in name_to_uri:
|
|
99
|
+
original_uri = name_to_uri[bundled_name]
|
|
100
|
+
# Extract fragment after # (e.g., "#/components/schemas/ObjectType")
|
|
101
|
+
if "#" in original_uri:
|
|
102
|
+
result[key] = "#" + original_uri.split("#", 1)[1]
|
|
103
|
+
else:
|
|
104
|
+
# Fallback if no fragment
|
|
105
|
+
result[key] = value
|
|
106
|
+
else:
|
|
107
|
+
result[key] = value
|
|
108
|
+
elif key == "x-bundled" and isinstance(value, dict):
|
|
109
|
+
# Replace x-bundled with proper components/schemas structure
|
|
110
|
+
components: dict[str, dict[str, Any]] = {"schemas": {}}
|
|
111
|
+
for bundled_name, bundled_schema in value.items():
|
|
112
|
+
if bundled_name in name_to_uri:
|
|
113
|
+
original_uri = name_to_uri[bundled_name]
|
|
114
|
+
# Extract schema name (e.g., "ObjectType" from "...#/components/schemas/ObjectType")
|
|
115
|
+
if "#/components/schemas/" in original_uri:
|
|
116
|
+
schema_name = original_uri.split("#/components/schemas/")[1]
|
|
117
|
+
components["schemas"][schema_name] = unbundle_schema_refs(bundled_schema, name_to_uri)
|
|
118
|
+
else:
|
|
119
|
+
# Fallback: keep bundled name if URI doesn't match expected pattern
|
|
120
|
+
components["schemas"][bundled_name] = unbundle_schema_refs(bundled_schema, name_to_uri)
|
|
121
|
+
else:
|
|
122
|
+
components["schemas"][bundled_name] = unbundle_schema_refs(bundled_schema, name_to_uri)
|
|
123
|
+
result["components"] = components
|
|
124
|
+
elif isinstance(value, (dict, list)):
|
|
125
|
+
# Recursively process all other values
|
|
126
|
+
result[key] = unbundle_schema_refs(value, name_to_uri)
|
|
127
|
+
else:
|
|
128
|
+
result[key] = value
|
|
129
|
+
return result
|
|
130
|
+
elif isinstance(schema, list):
|
|
131
|
+
return [unbundle_schema_refs(item, name_to_uri) for item in schema] # type: ignore
|
|
132
|
+
return schema
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def build_unsatisfiable_error(operation: APIOperation, *, with_tip: bool) -> Unsatisfiable:
|
|
136
|
+
__tracebackhide__ = True
|
|
137
|
+
unsatisfiable = find_unsatisfiable_parameter(operation)
|
|
138
|
+
|
|
139
|
+
if unsatisfiable is not None:
|
|
140
|
+
message = unsatisfiable.get_error_message(operation.schema.config.output)
|
|
141
|
+
else:
|
|
142
|
+
message = GENERIC_UNSATISFIABLE_MESSAGE
|
|
143
|
+
|
|
144
|
+
if with_tip:
|
|
145
|
+
message += "\n\nTip: Review all parameters and request body schemas for conflicting constraints"
|
|
146
|
+
|
|
147
|
+
return Unsatisfiable(message)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
HEALTH_CHECK_CAUSES = {
|
|
151
|
+
HealthCheck.data_too_large: """ - Arrays with large minItems (e.g., minItems: 1000)
|
|
152
|
+
- Strings with large minLength (e.g., minLength: 10000)
|
|
153
|
+
- Deeply nested objects with many required properties""",
|
|
154
|
+
HealthCheck.filter_too_much: """ - Complex regex patterns that match few strings
|
|
155
|
+
- Multiple overlapping constraints (pattern + format + enum)""",
|
|
156
|
+
HealthCheck.too_slow: """ - Regex with excessive backtracking (e.g., (a+)+b)
|
|
157
|
+
- Many interdependent constraints
|
|
158
|
+
- Large combinatorial complexity""",
|
|
159
|
+
HealthCheck.large_base_example: """ - Arrays with large minimum size (e.g., minItems: 100)
|
|
160
|
+
- Many required properties with their own large minimums
|
|
161
|
+
- Nested structures that multiply size requirements""",
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
HEALTH_CHECK_ACTIONS = {
|
|
165
|
+
HealthCheck.data_too_large: "Reduce minItems, minLength, or size constraints to realistic values",
|
|
166
|
+
HealthCheck.filter_too_much: "Simplify constraints or widen acceptable value ranges",
|
|
167
|
+
HealthCheck.too_slow: "Simplify regex patterns or reduce constraint complexity",
|
|
168
|
+
HealthCheck.large_base_example: "Reduce minimum size requirements or number of required properties",
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
HEALTH_CHECK_TITLES = {
|
|
172
|
+
HealthCheck.data_too_large: "Generated examples exceed size limits",
|
|
173
|
+
HealthCheck.filter_too_much: "Too many generated examples are filtered out",
|
|
174
|
+
HealthCheck.too_slow: "Data generation is too slow",
|
|
175
|
+
HealthCheck.large_base_example: "Minimum possible example is too large",
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass
|
|
180
|
+
class SlowParameter:
|
|
181
|
+
"""Information about a parameter with slow or problematic data generation."""
|
|
182
|
+
|
|
183
|
+
location: ParameterLocation
|
|
184
|
+
name: str
|
|
185
|
+
schema: JsonSchema
|
|
186
|
+
original: HealthCheck
|
|
187
|
+
|
|
188
|
+
__slots__ = ("location", "name", "schema", "original")
|
|
189
|
+
|
|
190
|
+
def get_error_message(self, config: OutputConfig) -> str:
|
|
191
|
+
formatted_schema = truncate_json(self.schema, config=config)
|
|
192
|
+
if self.location == ParameterLocation.BODY:
|
|
193
|
+
# For body, name is the media type
|
|
194
|
+
location = f"request body ({self.name})"
|
|
195
|
+
else:
|
|
196
|
+
location = f"{self.location.value} parameter '{self.name}'"
|
|
197
|
+
title = HEALTH_CHECK_TITLES[self.original]
|
|
198
|
+
causes = HEALTH_CHECK_CAUSES[self.original]
|
|
199
|
+
|
|
200
|
+
return f"""{title} for {location}
|
|
201
|
+
Schema:
|
|
202
|
+
|
|
203
|
+
{formatted_schema}
|
|
204
|
+
|
|
205
|
+
This usually means:
|
|
206
|
+
{causes}"""
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _extract_health_check_reason(exc: FailedHealthCheck | InvalidArgument) -> HealthCheck | None:
|
|
210
|
+
message = str(exc).lower()
|
|
211
|
+
if "data_too_large" in message or "too large" in message:
|
|
212
|
+
return HealthCheck.data_too_large
|
|
213
|
+
elif "filter_too_much" in message or "filtered out" in message:
|
|
214
|
+
return HealthCheck.filter_too_much
|
|
215
|
+
elif "too_slow" in message or "too slow" in message:
|
|
216
|
+
return HealthCheck.too_slow
|
|
217
|
+
elif ("large_base_example" in message or "can never generate an example, because min_size" in message) or (
|
|
218
|
+
isinstance(exc, InvalidArgument)
|
|
219
|
+
and message.endswith("larger than hypothesis is designed to handle")
|
|
220
|
+
or "can never generate an example, because min_size is larger than hypothesis supports" in message
|
|
221
|
+
):
|
|
222
|
+
return HealthCheck.large_base_example
|
|
223
|
+
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def find_slow_parameter(operation: APIOperation, reason: HealthCheck) -> SlowParameter | None:
|
|
228
|
+
from hypothesis.errors import FailedHealthCheck
|
|
229
|
+
from hypothesis_jsonschema import from_schema
|
|
230
|
+
|
|
231
|
+
for location, container in (
|
|
232
|
+
(ParameterLocation.QUERY, operation.query),
|
|
233
|
+
(ParameterLocation.PATH, operation.path_parameters),
|
|
234
|
+
(ParameterLocation.HEADER, operation.headers),
|
|
235
|
+
(ParameterLocation.COOKIE, operation.cookies),
|
|
236
|
+
(ParameterLocation.BODY, operation.body),
|
|
237
|
+
):
|
|
238
|
+
for parameter in container:
|
|
239
|
+
try:
|
|
240
|
+
generate_one(from_schema(parameter.optimized_schema), suppress_health_check=[])
|
|
241
|
+
except (FailedHealthCheck, Unsatisfiable, InvalidArgument):
|
|
242
|
+
if location == ParameterLocation.BODY:
|
|
243
|
+
name = parameter.media_type
|
|
244
|
+
else:
|
|
245
|
+
name = parameter.name
|
|
246
|
+
|
|
247
|
+
schema = unbundle_schema_refs(parameter.optimized_schema, parameter.name_to_uri)
|
|
248
|
+
return SlowParameter(location=location, name=name, schema=schema, original=reason)
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _get_generic_health_check_message(reason: HealthCheck) -> str:
|
|
253
|
+
title = HEALTH_CHECK_TITLES[reason]
|
|
254
|
+
causes = HEALTH_CHECK_CAUSES[reason]
|
|
255
|
+
return f"{title} for this operation\n\nUnable to identify the specific parameter. Common causes:\n{causes}"
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class HealthCheckTipStyle(Enum):
|
|
259
|
+
DEFAULT = "default"
|
|
260
|
+
PYTEST = "pytest"
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def build_health_check_error(
|
|
264
|
+
operation: APIOperation,
|
|
265
|
+
original: FailedHealthCheck | InvalidArgument,
|
|
266
|
+
with_tip: bool,
|
|
267
|
+
tip_style: HealthCheckTipStyle = HealthCheckTipStyle.DEFAULT,
|
|
268
|
+
) -> FailedHealthCheck | InvalidArgument:
|
|
269
|
+
__tracebackhide__ = True
|
|
270
|
+
reason = _extract_health_check_reason(original)
|
|
271
|
+
if reason is None:
|
|
272
|
+
return original
|
|
273
|
+
slow_param = find_slow_parameter(operation, reason)
|
|
274
|
+
|
|
275
|
+
if slow_param is not None:
|
|
276
|
+
message = slow_param.get_error_message(operation.schema.config.output)
|
|
277
|
+
else:
|
|
278
|
+
message = _get_generic_health_check_message(reason)
|
|
279
|
+
|
|
280
|
+
if with_tip:
|
|
281
|
+
message += f"\n\nTip: {HEALTH_CHECK_ACTIONS[reason]}"
|
|
282
|
+
if tip_style == HealthCheckTipStyle.PYTEST:
|
|
283
|
+
message += f". You can disable this health check with @settings(suppress_health_check=[{reason!r}])"
|
|
284
|
+
|
|
285
|
+
return FailedHealthCheck(message)
|
schemathesis/openapi/checks.py
CHANGED
|
@@ -449,7 +449,7 @@ class UnsupportedMethodResponse(Failure):
|
|
|
449
449
|
allow_header_present: bool | None = None,
|
|
450
450
|
failure_reason: str, # "wrong_status" or "missing_allow_header"
|
|
451
451
|
message: str,
|
|
452
|
-
title: str = "Unsupported
|
|
452
|
+
title: str = "Unsupported methods",
|
|
453
453
|
case_id: str | None = None,
|
|
454
454
|
) -> None:
|
|
455
455
|
self.operation = operation
|
schemathesis/pytest/plugin.py
CHANGED
|
@@ -9,7 +9,7 @@ import pytest
|
|
|
9
9
|
from _pytest import nodes
|
|
10
10
|
from _pytest.config import hookimpl
|
|
11
11
|
from _pytest.python import Class, Function, FunctionDefinition, Metafunc, Module, PyCollector
|
|
12
|
-
from hypothesis.errors import InvalidArgument, Unsatisfiable
|
|
12
|
+
from hypothesis.errors import FailedHealthCheck, InvalidArgument, Unsatisfiable
|
|
13
13
|
from jsonschema.exceptions import SchemaError
|
|
14
14
|
|
|
15
15
|
from schemathesis.core.control import SkipTest
|
|
@@ -34,7 +34,12 @@ from schemathesis.generation.hypothesis.given import (
|
|
|
34
34
|
merge_given_args,
|
|
35
35
|
validate_given_args,
|
|
36
36
|
)
|
|
37
|
-
from schemathesis.generation.hypothesis.reporting import
|
|
37
|
+
from schemathesis.generation.hypothesis.reporting import (
|
|
38
|
+
HealthCheckTipStyle,
|
|
39
|
+
build_health_check_error,
|
|
40
|
+
build_unsatisfiable_error,
|
|
41
|
+
ignore_hypothesis_output,
|
|
42
|
+
)
|
|
38
43
|
from schemathesis.pytest.control_flow import fail_on_no_matches
|
|
39
44
|
from schemathesis.schemas import APIOperation
|
|
40
45
|
|
|
@@ -293,6 +298,7 @@ def pytest_pyfunc_call(pyfuncitem): # type:ignore
|
|
|
293
298
|
For example - kwargs validation is failed for some strategy.
|
|
294
299
|
"""
|
|
295
300
|
from schemathesis.generation.hypothesis.builder import (
|
|
301
|
+
ApiOperationMark,
|
|
296
302
|
InvalidHeadersExampleMark,
|
|
297
303
|
InvalidRegexMark,
|
|
298
304
|
MissingPathParameters,
|
|
@@ -327,6 +333,16 @@ def pytest_pyfunc_call(pyfuncitem): # type:ignore
|
|
|
327
333
|
if invalid_headers is not None:
|
|
328
334
|
raise InvalidHeadersExample.from_headers(invalid_headers) from None
|
|
329
335
|
pytest.skip(exc.args[0])
|
|
336
|
+
except FailedHealthCheck as exc:
|
|
337
|
+
operation = ApiOperationMark.get(pyfuncitem.obj)
|
|
338
|
+
assert operation is not None
|
|
339
|
+
raise build_health_check_error(
|
|
340
|
+
operation, exc, with_tip=True, tip_style=HealthCheckTipStyle.PYTEST
|
|
341
|
+
) from None
|
|
342
|
+
except Unsatisfiable:
|
|
343
|
+
operation = ApiOperationMark.get(pyfuncitem.obj)
|
|
344
|
+
assert operation is not None
|
|
345
|
+
raise build_unsatisfiable_error(operation, with_tip=True) from None
|
|
330
346
|
except SchemaError as exc:
|
|
331
347
|
raise InvalidRegexPattern.from_schema_error(exc, from_examples=False) from exc
|
|
332
348
|
|
|
@@ -19,9 +19,7 @@ from typing import (
|
|
|
19
19
|
)
|
|
20
20
|
from urllib.parse import urlsplit
|
|
21
21
|
|
|
22
|
-
import graphql
|
|
23
22
|
from hypothesis import strategies as st
|
|
24
|
-
from hypothesis_graphql import strategies as gql_st
|
|
25
23
|
from requests.structures import CaseInsensitiveDict
|
|
26
24
|
|
|
27
25
|
from schemathesis import auths
|
|
@@ -52,6 +50,7 @@ from schemathesis.schemas import (
|
|
|
52
50
|
from .scalars import CUSTOM_SCALARS, get_extra_scalar_strategies
|
|
53
51
|
|
|
54
52
|
if TYPE_CHECKING:
|
|
53
|
+
import graphql
|
|
55
54
|
from hypothesis.strategies import SearchStrategy
|
|
56
55
|
|
|
57
56
|
from schemathesis.auths import AuthStorage
|
|
@@ -143,6 +142,8 @@ class GraphQLSchema(BaseSchema):
|
|
|
143
142
|
|
|
144
143
|
@property
|
|
145
144
|
def client_schema(self) -> graphql.GraphQLSchema:
|
|
145
|
+
import graphql
|
|
146
|
+
|
|
146
147
|
if not hasattr(self, "_client_schema"):
|
|
147
148
|
self._client_schema = graphql.build_client_schema(self.raw_schema)
|
|
148
149
|
return self._client_schema
|
|
@@ -336,6 +337,9 @@ def graphql_cases(
|
|
|
336
337
|
media_type: str | None = None,
|
|
337
338
|
phase: TestPhase = TestPhase.FUZZING,
|
|
338
339
|
) -> Any:
|
|
340
|
+
import graphql
|
|
341
|
+
from hypothesis_graphql import strategies as gql_st
|
|
342
|
+
|
|
339
343
|
start = time.monotonic()
|
|
340
344
|
definition = cast(GraphQLOperationDefinition, operation.definition)
|
|
341
345
|
strategy_factory = {
|
|
@@ -320,7 +320,7 @@ def unsupported_method(ctx: CheckContext, response: Response, case: Case) -> boo
|
|
|
320
320
|
method=cast(str, response.request.method),
|
|
321
321
|
status_code=response.status_code,
|
|
322
322
|
failure_reason="wrong_status",
|
|
323
|
-
message=f"
|
|
323
|
+
message=f"Unsupported method {response.request.method} returned {response.status_code}, expected 405 Method Not Allowed\n\nReturn 405 for methods not listed in the OpenAPI spec",
|
|
324
324
|
)
|
|
325
325
|
|
|
326
326
|
allow_header = response.headers.get("allow")
|
|
@@ -331,7 +331,7 @@ def unsupported_method(ctx: CheckContext, response: Response, case: Case) -> boo
|
|
|
331
331
|
status_code=response.status_code,
|
|
332
332
|
allow_header_present=False,
|
|
333
333
|
failure_reason="missing_allow_header",
|
|
334
|
-
message=f"
|
|
334
|
+
message=f"{response.request.method} returned 405 without required `Allow` header\n\nAdd `Allow` header listing supported methods (required by RFC 9110)",
|
|
335
335
|
)
|
|
336
336
|
return None
|
|
337
337
|
|
|
@@ -93,6 +93,8 @@ def _to_json_schema(
|
|
|
93
93
|
# Read-only properties should not occur in requests
|
|
94
94
|
rewrite_properties(schema, is_read_only)
|
|
95
95
|
|
|
96
|
+
ensure_required_properties(schema)
|
|
97
|
+
|
|
96
98
|
for keyword, value in schema.items():
|
|
97
99
|
if keyword in IN_VALUE and isinstance(value, dict):
|
|
98
100
|
schema[keyword] = _to_json_schema(
|
|
@@ -121,6 +123,22 @@ def _to_json_schema(
|
|
|
121
123
|
return schema
|
|
122
124
|
|
|
123
125
|
|
|
126
|
+
def ensure_required_properties(schema: dict[str, Any]) -> None:
|
|
127
|
+
if schema.get("additionalProperties") is not False:
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
required = schema.get("required")
|
|
131
|
+
if not required or not isinstance(required, list):
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
properties = schema.setdefault("properties", {})
|
|
135
|
+
|
|
136
|
+
# Add missing required properties as empty schemas
|
|
137
|
+
for name in required:
|
|
138
|
+
if name not in properties:
|
|
139
|
+
properties[name] = {}
|
|
140
|
+
|
|
141
|
+
|
|
124
142
|
IN_VALUE = frozenset(
|
|
125
143
|
(
|
|
126
144
|
"additionalProperties",
|
|
@@ -114,7 +114,9 @@ class RequestsTransport(BaseTransport["requests.Session"]):
|
|
|
114
114
|
data.setdefault("cert", cert)
|
|
115
115
|
|
|
116
116
|
kwargs.pop("base_url", None)
|
|
117
|
-
|
|
117
|
+
for key, value in kwargs.items():
|
|
118
|
+
if key not in ("headers", "cookies", "params") or key not in data:
|
|
119
|
+
data[key] = value
|
|
118
120
|
data.setdefault("timeout", DEFAULT_RESPONSE_TIMEOUT)
|
|
119
121
|
|
|
120
122
|
current_session_headers: MutableMapping[str, Any] = {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: schemathesis
|
|
3
|
-
Version: 4.3.
|
|
3
|
+
Version: 4.3.15
|
|
4
4
|
Summary: Property-based testing framework for Open API and GraphQL based apps
|
|
5
5
|
Project-URL: Documentation, https://schemathesis.readthedocs.io/en/stable/
|
|
6
6
|
Project-URL: Changelog, https://github.com/schemathesis/schemathesis/blob/master/CHANGELOG.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
schemathesis/__init__.py,sha256=QqVUCBQr-RDEstgCZLsxzIa9HJslVSeijrm9gES4b_0,1423
|
|
2
2
|
schemathesis/auths.py,sha256=JdEwPRS9WKmPcxzGXYYz9pjlIUMQYCfif7ZJU0Kde-I,16400
|
|
3
|
-
schemathesis/checks.py,sha256=
|
|
3
|
+
schemathesis/checks.py,sha256=QaG8ErFCOSYe1GqJKjoiOesv2oZgFTedRUhBZUpTgR8,6879
|
|
4
4
|
schemathesis/errors.py,sha256=K3irHIZkrBH2-9LIjlgXlm8RNC41Nffd39ncfwagUvw,1053
|
|
5
5
|
schemathesis/filters.py,sha256=IevPA5A04GfRLLjmkFLZ0CLhjNO3RmpZq_yw6MqjLIA,13515
|
|
6
6
|
schemathesis/hooks.py,sha256=q2wqYNgpMCO8ImSBkbrWDSwN0BSELelqJMgAAgGvv2M,14836
|
|
@@ -62,7 +62,7 @@ schemathesis/core/marks.py,sha256=SH7jsVuNRJjx2gZN9Ze5MY01u7FJiHeO0iruzKi5rm4,21
|
|
|
62
62
|
schemathesis/core/media_types.py,sha256=VN1QhgfwBHpTw0CQquzAfhJstxXIXBzSSy0NZmAUcAo,2185
|
|
63
63
|
schemathesis/core/parameters.py,sha256=20pd4DLW1uXs61YuESKG1Fx9QJJQN3JY7CKnOJMgDb4,790
|
|
64
64
|
schemathesis/core/rate_limit.py,sha256=7tg9Znk11erTfw8-ANutjEmu7hbfUHZx_iEdkoaP174,1757
|
|
65
|
-
schemathesis/core/registries.py,sha256=
|
|
65
|
+
schemathesis/core/registries.py,sha256=ksWLxPcohKInH9DwB56KNK0AwGO0Gk5LR2z_Q7cRvps,884
|
|
66
66
|
schemathesis/core/result.py,sha256=d449YvyONjqjDs-A5DAPgtAI96iT753K8sU6_1HLo2Q,461
|
|
67
67
|
schemathesis/core/transforms.py,sha256=n_inn8Vb-CFiDUd9Z8E7JIp2kn2eAnxO8DD3OSwOTiM,4310
|
|
68
68
|
schemathesis/core/transport.py,sha256=LQcamAkFqJ0HuXQzepevAq2MCJW-uq5Nm-HE9yc7HMI,7503
|
|
@@ -79,7 +79,7 @@ schemathesis/engine/__init__.py,sha256=QaFE-FinaTAaarteADo2RRMJ-Sz6hZB9TzD5KjMin
|
|
|
79
79
|
schemathesis/engine/context.py,sha256=YaBfwTUyTCZaMq7-jtAKFQj-Eh1aQdbZ0UNcC5d_epU,5792
|
|
80
80
|
schemathesis/engine/control.py,sha256=FXzP8dxL47j1Giqpy2-Bsr_MdMw9YiATSK_UfpFwDtk,1348
|
|
81
81
|
schemathesis/engine/core.py,sha256=qlPHnZVq2RrUe93fOciXd1hC3E1gVyF2BIWMPMeLIj8,6655
|
|
82
|
-
schemathesis/engine/errors.py,sha256=
|
|
82
|
+
schemathesis/engine/errors.py,sha256=WVDITmwEgs1O59yrBtQX4_bWCNhdNKZ8DkJXYMRjUpY,16835
|
|
83
83
|
schemathesis/engine/events.py,sha256=jpCtMkWWfNe2jUeZh_Ly_wfZEF44EOodL-I_W4C9rgg,6594
|
|
84
84
|
schemathesis/engine/observations.py,sha256=T-5R8GeVIqvxpCMxc6vZ04UUxUTx3w7689r3Dc6bIcE,1416
|
|
85
85
|
schemathesis/engine/recorder.py,sha256=KWyWkGkZxIwSDU92jNWCJXU4G4E5WqfhLM6G1Yi7Jyo,8636
|
|
@@ -89,7 +89,7 @@ schemathesis/engine/phases/stateful/__init__.py,sha256=Lz1rgNqCfUSIz173XqCGsiMuU
|
|
|
89
89
|
schemathesis/engine/phases/stateful/_executor.py,sha256=yRpUJqKLTKMVRy7hEXPwmI23CtgGIprz341lCJwvTrU,15613
|
|
90
90
|
schemathesis/engine/phases/stateful/context.py,sha256=A7X1SLDOWFpCvFN9IiIeNVZM0emjqatmJL_k9UsO7vM,2946
|
|
91
91
|
schemathesis/engine/phases/unit/__init__.py,sha256=9dDcxyj887pktnE9YDIPNaR-vc7iqKQWIrFr77SbUTQ,8786
|
|
92
|
-
schemathesis/engine/phases/unit/_executor.py,sha256=
|
|
92
|
+
schemathesis/engine/phases/unit/_executor.py,sha256=9qlc0SEq-9M1mqszB-IaIuaqQYoGkSUD3ygTciUhBl0,17871
|
|
93
93
|
schemathesis/engine/phases/unit/_pool.py,sha256=iU0hdHDmohPnEv7_S1emcabuzbTf-Cznqwn0pGQ5wNQ,2480
|
|
94
94
|
schemathesis/generation/__init__.py,sha256=tvNO2FLiY8z3fZ_kL_QJhSgzXfnT4UqwSXMHCwfLI0g,645
|
|
95
95
|
schemathesis/generation/case.py,sha256=SLMw6zkzmeiZdaIij8_0tjTF70BrMlRSWREaqWii0uM,12508
|
|
@@ -99,17 +99,17 @@ schemathesis/generation/metrics.py,sha256=cZU5HdeAMcLFEDnTbNE56NuNq4P0N4ew-g1NEz
|
|
|
99
99
|
schemathesis/generation/modes.py,sha256=Q1fhjWr3zxabU5qdtLvKfpMFZJAwlW9pnxgenjeXTyU,481
|
|
100
100
|
schemathesis/generation/overrides.py,sha256=xI2djHsa42fzP32xpxgxO52INixKagf5DjDAWJYswM8,3890
|
|
101
101
|
schemathesis/generation/hypothesis/__init__.py,sha256=68BHULoXQC1WjFfw03ga5lvDGZ-c-J7H_fNEuUzFWRw,4976
|
|
102
|
-
schemathesis/generation/hypothesis/builder.py,sha256=
|
|
103
|
-
schemathesis/generation/hypothesis/examples.py,sha256=
|
|
102
|
+
schemathesis/generation/hypothesis/builder.py,sha256=N9UiJfwUplmrCLsROHQtirm1UPt_TDqUiLv4as2qAjU,38562
|
|
103
|
+
schemathesis/generation/hypothesis/examples.py,sha256=9fc3fQW1e0ZxJuHyyCGhPeyL_gCtb8h29XK-Yz-s1Vk,1716
|
|
104
104
|
schemathesis/generation/hypothesis/given.py,sha256=sTZR1of6XaHAPWtHx2_WLlZ50M8D5Rjux0GmWkWjDq4,2337
|
|
105
|
-
schemathesis/generation/hypothesis/reporting.py,sha256=
|
|
105
|
+
schemathesis/generation/hypothesis/reporting.py,sha256=d5vuv5KF4bgIoMV2qy_LwikfowK8BQkj8fjORMJT6PU,11481
|
|
106
106
|
schemathesis/generation/stateful/__init__.py,sha256=s7jiJEnguIj44IsRyMi8afs-8yjIUuBbzW58bH5CHjs,1042
|
|
107
107
|
schemathesis/generation/stateful/state_machine.py,sha256=CiVtpBEeotpNOUkYO3vJLKRe89gdT1kjguZ88vbfqs0,9500
|
|
108
108
|
schemathesis/graphql/__init__.py,sha256=_eO6MAPHGgiADVGRntnwtPxmuvk666sAh-FAU4cG9-0,326
|
|
109
109
|
schemathesis/graphql/checks.py,sha256=IADbxiZjgkBWrC5yzHDtohRABX6zKXk5w_zpWNwdzYo,3186
|
|
110
110
|
schemathesis/graphql/loaders.py,sha256=2tgG4HIvFmjHLr_KexVXnT8hSBM-dKG_fuXTZgE97So,9445
|
|
111
111
|
schemathesis/openapi/__init__.py,sha256=-KcsSAM19uOM0N5J4s-yTnQ1BFsptYhW1E51cEf6kVM,311
|
|
112
|
-
schemathesis/openapi/checks.py,sha256=
|
|
112
|
+
schemathesis/openapi/checks.py,sha256=icUnRTkfFNP0nOYCyaj7hFHO8gJic0XPKxP0xHkm0Qw,13062
|
|
113
113
|
schemathesis/openapi/loaders.py,sha256=aaCIf6P8R33l6DBNGD_99m_wruYOPR7ecyL5hT6UChg,10710
|
|
114
114
|
schemathesis/openapi/generation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
115
115
|
schemathesis/openapi/generation/filters.py,sha256=pY9cUZdL_kQK80Z2aylTOqqa12zmaYUlYC5BfYgeQMk,2395
|
|
@@ -117,7 +117,7 @@ schemathesis/pytest/__init__.py,sha256=7W0q-Thcw03IAQfXE_Mo8JPZpUdHJzfu85fjK1Zdf
|
|
|
117
117
|
schemathesis/pytest/control_flow.py,sha256=F8rAPsPeNv_sJiJgbZYtTpwKWjauZmqFUaKroY2GmQI,217
|
|
118
118
|
schemathesis/pytest/lazy.py,sha256=wP0sqcVFcD-OjDIFUpYdJdFQ-BY18CVyL0iB6eHiWRw,11088
|
|
119
119
|
schemathesis/pytest/loaders.py,sha256=Sbv8e5F77_x4amLP50iwubfm6kpOhx7LhLFGsVXW5Ys,925
|
|
120
|
-
schemathesis/pytest/plugin.py,sha256=
|
|
120
|
+
schemathesis/pytest/plugin.py,sha256=fAT76woft3Uak5kGPpt3I8n_LjnzBDjl1adHrho0dA4,14900
|
|
121
121
|
schemathesis/python/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
122
122
|
schemathesis/python/asgi.py,sha256=5PyvuTBaivvyPUEi3pwJni91K1kX5Zc0u9c6c1D8a1Q,287
|
|
123
123
|
schemathesis/python/wsgi.py,sha256=uShAgo_NChbfYaV1117e6UHp0MTg7jaR0Sy_to3Jmf8,219
|
|
@@ -125,12 +125,12 @@ schemathesis/specs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
|
125
125
|
schemathesis/specs/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
126
126
|
schemathesis/specs/graphql/nodes.py,sha256=bE3G1kNmqJ8OV4igBvIK-UORrkQA6Nofduf87O3TD9I,541
|
|
127
127
|
schemathesis/specs/graphql/scalars.py,sha256=6lew8mnwhrtg23leiEbG43mLGPLlRln8mClCY94XpDA,2680
|
|
128
|
-
schemathesis/specs/graphql/schemas.py,sha256=
|
|
128
|
+
schemathesis/specs/graphql/schemas.py,sha256=oNbluhfxpbxRWVIMoLn3_g-bUaVLCea29jOZiHN1MT0,14509
|
|
129
129
|
schemathesis/specs/graphql/validation.py,sha256=-W1Noc1MQmTb4RX-gNXMeU2qkgso4mzVfHxtdLkCPKM,1422
|
|
130
130
|
schemathesis/specs/openapi/__init__.py,sha256=C5HOsfuDJGq_3mv8CRBvRvb0Diy1p0BFdqyEXMS-loE,238
|
|
131
131
|
schemathesis/specs/openapi/_hypothesis.py,sha256=O8vN-koBjzBVZfpD3pmgIt6ecU4ddAPHOxTAORd23Lo,22642
|
|
132
|
-
schemathesis/specs/openapi/checks.py,sha256=
|
|
133
|
-
schemathesis/specs/openapi/converter.py,sha256=
|
|
132
|
+
schemathesis/specs/openapi/checks.py,sha256=tb2s8azZYcO__Jf13ONUstO1inXoZBlo3dD2uuABB24,31712
|
|
133
|
+
schemathesis/specs/openapi/converter.py,sha256=tnh08EDvmac5ONaCUmBwrblpWu43TaQ7qhJZeh5PJIc,6963
|
|
134
134
|
schemathesis/specs/openapi/definitions.py,sha256=8htclglV3fW6JPBqs59lgM4LnA25Mm9IptXBPb_qUT0,93949
|
|
135
135
|
schemathesis/specs/openapi/examples.py,sha256=uHV1HRqFhwpGNsBWHt7WmehyIyr8d-n-VeKKs4FRt2c,24475
|
|
136
136
|
schemathesis/specs/openapi/formats.py,sha256=4tYRdckauHxkJCmOhmdwDq_eOpHPaKloi89lzMPbPzw,3975
|
|
@@ -175,11 +175,11 @@ schemathesis/specs/openapi/types/v3.py,sha256=Vondr9Amk6JKCIM6i6RGcmTUjFfPgOOqzB
|
|
|
175
175
|
schemathesis/transport/__init__.py,sha256=6yg_RfV_9L0cpA6qpbH-SL9_3ggtHQji9CZrpIkbA6s,5321
|
|
176
176
|
schemathesis/transport/asgi.py,sha256=qTClt6oT_xUEWnRHokACN_uqCNNUZrRPT6YG0PjbElY,926
|
|
177
177
|
schemathesis/transport/prepare.py,sha256=erYXRaxpQokIDzaIuvt_csHcw72iHfCyNq8VNEzXd0o,4743
|
|
178
|
-
schemathesis/transport/requests.py,sha256=
|
|
178
|
+
schemathesis/transport/requests.py,sha256=jJAKqmsnlErU066WlUTIZHa3rx5lFtHWT43nm8CewUc,10717
|
|
179
179
|
schemathesis/transport/serialization.py,sha256=GwO6OAVTmL1JyKw7HiZ256tjV4CbrRbhQN0ep1uaZwI,11157
|
|
180
180
|
schemathesis/transport/wsgi.py,sha256=kQtasFre6pjdJWRKwLA_Qb-RyQHCFNpaey9ubzlFWKI,5907
|
|
181
|
-
schemathesis-4.3.
|
|
182
|
-
schemathesis-4.3.
|
|
183
|
-
schemathesis-4.3.
|
|
184
|
-
schemathesis-4.3.
|
|
185
|
-
schemathesis-4.3.
|
|
181
|
+
schemathesis-4.3.15.dist-info/METADATA,sha256=4huPYaRVN0uReWxrIBrV0LSn1mLHu91RxF-O-J9KIIo,8566
|
|
182
|
+
schemathesis-4.3.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
183
|
+
schemathesis-4.3.15.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
|
|
184
|
+
schemathesis-4.3.15.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
|
|
185
|
+
schemathesis-4.3.15.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|