schemathesis 4.3.14__py3-none-any.whl → 4.3.16__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 +12 -1
- schemathesis/cli/commands/run/handlers/cassettes.py +1 -2
- schemathesis/config/_error.py +1 -1
- schemathesis/core/errors.py +9 -0
- schemathesis/core/registries.py +3 -0
- schemathesis/engine/errors.py +13 -42
- schemathesis/engine/phases/unit/_executor.py +13 -2
- schemathesis/generation/hypothesis/examples.py +11 -5
- schemathesis/generation/hypothesis/reporting.py +141 -1
- schemathesis/openapi/checks.py +1 -1
- schemathesis/pytest/plugin.py +13 -2
- schemathesis/specs/openapi/checks.py +2 -2
- schemathesis/specs/openapi/converter.py +19 -14
- schemathesis/specs/openapi/examples.py +4 -4
- schemathesis/specs/openapi/references.py +31 -1
- schemathesis/transport/requests.py +3 -1
- {schemathesis-4.3.14.dist-info → schemathesis-4.3.16.dist-info}/METADATA +1 -1
- {schemathesis-4.3.14.dist-info → schemathesis-4.3.16.dist-info}/RECORD +21 -21
- {schemathesis-4.3.14.dist-info → schemathesis-4.3.16.dist-info}/WHEEL +0 -0
- {schemathesis-4.3.14.dist-info → schemathesis-4.3.16.dist-info}/entry_points.txt +0 -0
- {schemathesis-4.3.14.dist-info → schemathesis-4.3.16.dist-info}/licenses/LICENSE +0 -0
schemathesis/checks.py
CHANGED
|
@@ -93,7 +93,7 @@ CHECKS = Registry[CheckFunction]()
|
|
|
93
93
|
|
|
94
94
|
def load_all_checks() -> None:
|
|
95
95
|
# NOTE: Trigger registering all Open API checks
|
|
96
|
-
from schemathesis.specs.openapi.checks import status_code_conformance # noqa: F401
|
|
96
|
+
from schemathesis.specs.openapi.checks import status_code_conformance # noqa: F401
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
def check(func: CheckFunction) -> CheckFunction:
|
|
@@ -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())
|
|
@@ -123,8 +123,7 @@ def vcr_writer(output: TextOutput, config: ProjectConfig, queue: Queue) -> None:
|
|
|
123
123
|
current_id = 1
|
|
124
124
|
|
|
125
125
|
def write_header_values(stream: IO, values: list[str]) -> None:
|
|
126
|
-
for v in values
|
|
127
|
-
stream.write(f" - {json.dumps(v)}\n")
|
|
126
|
+
stream.writelines(f" - {json.dumps(v)}\n" for v in values)
|
|
128
127
|
|
|
129
128
|
if config.output.sanitization.enabled:
|
|
130
129
|
sanitization_keys = config.output.sanitization.keys_to_sanitize
|
schemathesis/config/_error.py
CHANGED
|
@@ -142,7 +142,7 @@ def _format_anyof_error(error: ValidationError) -> str:
|
|
|
142
142
|
)
|
|
143
143
|
elif list(error.schema_path) == ["properties", "workers", "anyOf"]:
|
|
144
144
|
return (
|
|
145
|
-
f"Invalid value for 'workers': {
|
|
145
|
+
f"Invalid value for 'workers': {error.instance!r}\n\n"
|
|
146
146
|
f"Expected either:\n"
|
|
147
147
|
f" - A positive integer (e.g., workers = 4)\n"
|
|
148
148
|
f' - The string "auto" for automatic detection (workers = "auto")'
|
schemathesis/core/errors.py
CHANGED
|
@@ -161,6 +161,8 @@ class InvalidSchema(SchemathesisError):
|
|
|
161
161
|
message += "\n File reference could not be resolved. Check that the file exists."
|
|
162
162
|
elif reference.startswith(("#/components", "#/definitions")):
|
|
163
163
|
message += "\n Component does not exist in the schema."
|
|
164
|
+
elif isinstance(error.__cause__, RemoteDocumentError):
|
|
165
|
+
message += f"\n {error.__cause__}"
|
|
164
166
|
return cls(message, path=path, method=method)
|
|
165
167
|
|
|
166
168
|
def as_failing_test_function(self) -> Callable:
|
|
@@ -176,6 +178,13 @@ class InvalidSchema(SchemathesisError):
|
|
|
176
178
|
return actual_test
|
|
177
179
|
|
|
178
180
|
|
|
181
|
+
class RemoteDocumentError(SchemathesisError):
|
|
182
|
+
"""Remote reference resolution failed.
|
|
183
|
+
|
|
184
|
+
This exception carries more context than the default one in `jsonschema`.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
|
|
179
188
|
class HookError(SchemathesisError):
|
|
180
189
|
"""Happens during hooks loading."""
|
|
181
190
|
|
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,15 +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
125
|
if self._kind in (
|
|
132
126
|
RuntimeErrorKind.SCHEMA_INVALID_REGULAR_EXPRESSION,
|
|
133
127
|
RuntimeErrorKind.SCHEMA_GENERIC,
|
|
@@ -216,16 +210,9 @@ def scalar_name_from_error(exception: hypothesis.errors.InvalidArgument) -> str:
|
|
|
216
210
|
|
|
217
211
|
|
|
218
212
|
def extract_health_check_error(error: hypothesis.errors.FailedHealthCheck) -> hypothesis.HealthCheck | None:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if match:
|
|
223
|
-
return {
|
|
224
|
-
"data_too_large": HealthCheck.data_too_large,
|
|
225
|
-
"filter_too_much": HealthCheck.filter_too_much,
|
|
226
|
-
"too_slow": HealthCheck.too_slow,
|
|
227
|
-
"large_base_example": HealthCheck.large_base_example,
|
|
228
|
-
}.get(match.group(1))
|
|
213
|
+
for key, title in HEALTH_CHECK_TITLES.items():
|
|
214
|
+
if title in str(error):
|
|
215
|
+
return key
|
|
229
216
|
return None
|
|
230
217
|
|
|
231
218
|
|
|
@@ -233,7 +220,13 @@ def get_runtime_error_suggestion(error_type: RuntimeErrorKind, bold: Callable[[s
|
|
|
233
220
|
"""Get a user-friendly suggestion for handling the error."""
|
|
234
221
|
|
|
235
222
|
def _format_health_check_suggestion(label: str) -> str:
|
|
236
|
-
|
|
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}`')}."
|
|
237
230
|
|
|
238
231
|
return {
|
|
239
232
|
RuntimeErrorKind.CONNECTION_SSL: f"Bypass SSL verification with {bold('`--tls-verify=false`')}.",
|
|
@@ -252,28 +245,6 @@ def get_runtime_error_suggestion(error_type: RuntimeErrorKind, bold: Callable[[s
|
|
|
252
245
|
}.get(error_type)
|
|
253
246
|
|
|
254
247
|
|
|
255
|
-
HEALTH_CHECK_MESSAGE_DATA_TOO_LARGE = """There's a notable occurrence of examples surpassing the maximum size limit.
|
|
256
|
-
Typically, generating excessively large examples can compromise the quality of test outcomes.
|
|
257
|
-
|
|
258
|
-
Consider revising the schema to more accurately represent typical use cases
|
|
259
|
-
or applying constraints to reduce the data size."""
|
|
260
|
-
HEALTH_CHECK_MESSAGE_FILTER_TOO_MUCH = """A significant number of generated examples are being filtered out, indicating
|
|
261
|
-
that the schema's constraints may be too complex.
|
|
262
|
-
|
|
263
|
-
This level of filtration can slow down testing and affect the distribution
|
|
264
|
-
of generated data. Review and simplify the schema constraints where
|
|
265
|
-
possible to mitigate this issue."""
|
|
266
|
-
HEALTH_CHECK_MESSAGE_TOO_SLOW = "Data generation is extremely slow. Consider reducing the complexity of the schema."
|
|
267
|
-
HEALTH_CHECK_MESSAGE_LARGE_BASE_EXAMPLE = """A health check has identified that the smallest example derived from the schema
|
|
268
|
-
is excessively large, potentially leading to inefficient test execution.
|
|
269
|
-
|
|
270
|
-
This is commonly due to schemas that specify large-scale data structures by
|
|
271
|
-
default, such as an array with an extensive number of elements.
|
|
272
|
-
|
|
273
|
-
Consider revising the schema to more accurately represent typical use cases
|
|
274
|
-
or applying constraints to reduce the data size."""
|
|
275
|
-
|
|
276
|
-
|
|
277
248
|
@enum.unique
|
|
278
249
|
class RuntimeErrorKind(str, enum.Enum):
|
|
279
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,6 +166,9 @@ 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
|
|
@@ -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))
|
|
@@ -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
|
|
|
@@ -2,9 +2,11 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from contextlib import contextmanager
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
+
from enum import Enum
|
|
5
6
|
from typing import TYPE_CHECKING, Any, Generator
|
|
6
7
|
|
|
7
|
-
from hypothesis
|
|
8
|
+
from hypothesis import HealthCheck
|
|
9
|
+
from hypothesis.errors import FailedHealthCheck, InvalidArgument, Unsatisfiable
|
|
8
10
|
from hypothesis.reporting import with_reporter
|
|
9
11
|
|
|
10
12
|
from schemathesis.config import OutputConfig
|
|
@@ -143,3 +145,141 @@ def build_unsatisfiable_error(operation: APIOperation, *, with_tip: bool) -> Uns
|
|
|
143
145
|
message += "\n\nTip: Review all parameters and request body schemas for conflicting constraints"
|
|
144
146
|
|
|
145
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
|
|
|
@@ -328,6 +333,12 @@ def pytest_pyfunc_call(pyfuncitem): # type:ignore
|
|
|
328
333
|
if invalid_headers is not None:
|
|
329
334
|
raise InvalidHeadersExample.from_headers(invalid_headers) from None
|
|
330
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
|
|
331
342
|
except Unsatisfiable:
|
|
332
343
|
operation = ApiOperationMark.get(pyfuncitem.obj)
|
|
333
344
|
assert operation is not None
|
|
@@ -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
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from itertools import chain
|
|
4
3
|
from typing import Any, Callable, overload
|
|
5
4
|
|
|
6
5
|
from schemathesis.core.jsonschema.bundler import BUNDLE_STORAGE_KEY
|
|
@@ -93,6 +92,8 @@ def _to_json_schema(
|
|
|
93
92
|
# Read-only properties should not occur in requests
|
|
94
93
|
rewrite_properties(schema, is_read_only)
|
|
95
94
|
|
|
95
|
+
ensure_required_properties(schema)
|
|
96
|
+
|
|
96
97
|
for keyword, value in schema.items():
|
|
97
98
|
if keyword in IN_VALUE and isinstance(value, dict):
|
|
98
99
|
schema[keyword] = _to_json_schema(
|
|
@@ -121,6 +122,22 @@ def _to_json_schema(
|
|
|
121
122
|
return schema
|
|
122
123
|
|
|
123
124
|
|
|
125
|
+
def ensure_required_properties(schema: dict[str, Any]) -> None:
|
|
126
|
+
if schema.get("additionalProperties") is not False:
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
required = schema.get("required")
|
|
130
|
+
if not required or not isinstance(required, list):
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
properties = schema.setdefault("properties", {})
|
|
134
|
+
|
|
135
|
+
# Add missing required properties as empty schemas
|
|
136
|
+
for name in required:
|
|
137
|
+
if name not in properties:
|
|
138
|
+
properties[name] = {}
|
|
139
|
+
|
|
140
|
+
|
|
124
141
|
IN_VALUE = frozenset(
|
|
125
142
|
(
|
|
126
143
|
"additionalProperties",
|
|
@@ -170,29 +187,17 @@ def update_pattern_in_schema(schema: dict[str, Any]) -> None:
|
|
|
170
187
|
|
|
171
188
|
def rewrite_properties(schema: dict[str, Any], predicate: Callable[[dict[str, Any]], bool]) -> None:
|
|
172
189
|
required = schema.get("required", [])
|
|
173
|
-
forbidden = []
|
|
174
190
|
for name, subschema in list(schema.get("properties", {}).items()):
|
|
175
191
|
if predicate(subschema):
|
|
176
192
|
if name in required:
|
|
177
193
|
required.remove(name)
|
|
178
|
-
|
|
179
|
-
forbidden.append(name)
|
|
180
|
-
if forbidden:
|
|
181
|
-
forbid_properties(schema, forbidden)
|
|
194
|
+
schema["properties"][name] = {"not": {}}
|
|
182
195
|
if not schema.get("required"):
|
|
183
196
|
schema.pop("required", None)
|
|
184
197
|
if not schema.get("properties"):
|
|
185
198
|
schema.pop("properties", None)
|
|
186
199
|
|
|
187
200
|
|
|
188
|
-
def forbid_properties(schema: dict[str, Any], forbidden: list[str]) -> None:
|
|
189
|
-
"""Explicitly forbid properties via the `not` keyword."""
|
|
190
|
-
not_schema = schema.setdefault("not", {})
|
|
191
|
-
already_forbidden = not_schema.setdefault("required", [])
|
|
192
|
-
already_forbidden.extend(forbidden)
|
|
193
|
-
not_schema["required"] = list(set(chain(already_forbidden, forbidden)))
|
|
194
|
-
|
|
195
|
-
|
|
196
201
|
def is_write_only(schema: dict[str, Any] | bool) -> bool:
|
|
197
202
|
if isinstance(schema, bool):
|
|
198
203
|
return False
|
|
@@ -242,7 +242,7 @@ def _expand_subschemas(
|
|
|
242
242
|
except InfiniteRecursiveReference:
|
|
243
243
|
return
|
|
244
244
|
|
|
245
|
-
yield
|
|
245
|
+
yield schema, current_path
|
|
246
246
|
|
|
247
247
|
if isinstance(schema, dict):
|
|
248
248
|
# For anyOf/oneOf, yield each alternative with the same path
|
|
@@ -250,10 +250,10 @@ def _expand_subschemas(
|
|
|
250
250
|
if key in schema:
|
|
251
251
|
for subschema in schema[key]:
|
|
252
252
|
# Each alternative starts with the current path
|
|
253
|
-
yield
|
|
253
|
+
yield subschema, current_path
|
|
254
254
|
|
|
255
255
|
# For allOf, merge all alternatives
|
|
256
|
-
if
|
|
256
|
+
if schema.get("allOf"):
|
|
257
257
|
subschema = deepclone(schema["allOf"][0])
|
|
258
258
|
try:
|
|
259
259
|
subschema, expanded_path = _resolve_bundled(subschema, resolver, current_path)
|
|
@@ -278,7 +278,7 @@ def _expand_subschemas(
|
|
|
278
278
|
else:
|
|
279
279
|
subschema[key] = value
|
|
280
280
|
|
|
281
|
-
yield
|
|
281
|
+
yield subschema, expanded_path
|
|
282
282
|
|
|
283
283
|
|
|
284
284
|
def extract_inner_examples(examples: dict[str, Any] | list, schema: BaseOpenAPISchema) -> Generator[Any, None, None]:
|
|
@@ -9,6 +9,7 @@ import requests
|
|
|
9
9
|
|
|
10
10
|
from schemathesis.core.compat import RefResolutionError, RefResolver
|
|
11
11
|
from schemathesis.core.deserialization import deserialize_yaml
|
|
12
|
+
from schemathesis.core.errors import RemoteDocumentError
|
|
12
13
|
from schemathesis.core.transport import DEFAULT_RESPONSE_TIMEOUT
|
|
13
14
|
|
|
14
15
|
|
|
@@ -30,10 +31,39 @@ def load_file_uri(location: str) -> dict[str, Any]:
|
|
|
30
31
|
return load_file_impl(location, urlopen)
|
|
31
32
|
|
|
32
33
|
|
|
34
|
+
_HTML_MARKERS = (b"<!doctype", b"<html", b"<head", b"<body")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _looks_like_html(content_type: str | None, body: bytes) -> bool:
|
|
38
|
+
if content_type and "html" in content_type.lower():
|
|
39
|
+
return True
|
|
40
|
+
head = body.lstrip()[:64].lower()
|
|
41
|
+
return any(head.startswith(m) for m in _HTML_MARKERS)
|
|
42
|
+
|
|
43
|
+
|
|
33
44
|
def load_remote_uri(uri: str) -> Any:
|
|
34
45
|
"""Load the resource and parse it as YAML / JSON."""
|
|
35
46
|
response = requests.get(uri, timeout=DEFAULT_RESPONSE_TIMEOUT)
|
|
36
|
-
|
|
47
|
+
content_type = response.headers.get("Content-Type", "")
|
|
48
|
+
body = response.content or b""
|
|
49
|
+
|
|
50
|
+
def _suffix() -> str:
|
|
51
|
+
return f"(HTTP {response.status_code}, Content-Type={content_type}, size={len(body)})"
|
|
52
|
+
|
|
53
|
+
if not (200 <= response.status_code < 300):
|
|
54
|
+
raise RemoteDocumentError(f"Failed to fetch {_suffix()}")
|
|
55
|
+
|
|
56
|
+
if _looks_like_html(content_type, body):
|
|
57
|
+
raise RemoteDocumentError(f"Expected YAML/JSON, got HTML {_suffix()}")
|
|
58
|
+
|
|
59
|
+
document = deserialize_yaml(response.content)
|
|
60
|
+
|
|
61
|
+
if not isinstance(document, (dict, list)):
|
|
62
|
+
raise RemoteDocumentError(
|
|
63
|
+
f"Remote document is parsed as {type(document).__name__}, but an object/array is expected {_suffix()}"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return document
|
|
37
67
|
|
|
38
68
|
|
|
39
69
|
JSONType = Union[None, bool, float, str, list, Dict[str, Any]]
|
|
@@ -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.16
|
|
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=F_lsC5JTUKm_ByvilBN_9IpbL4mJiidfLgS8ir2ZDoQ,6873
|
|
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
|
|
@@ -21,7 +21,7 @@ schemathesis/cli/commands/run/loaders.py,sha256=eRgP1ZPfhOfxR7iXQ_CfV9r_8jP1DN4t
|
|
|
21
21
|
schemathesis/cli/commands/run/validation.py,sha256=DQaMiBLN2tYT9hONvv8xnyPvNXZH768UlOdUxTd5kZs,9193
|
|
22
22
|
schemathesis/cli/commands/run/handlers/__init__.py,sha256=TPZ3KdGi8m0fjlN0GjA31MAXXn1qI7uU4FtiDwroXZI,1915
|
|
23
23
|
schemathesis/cli/commands/run/handlers/base.py,sha256=qUtDvtr3F6were_BznfnaPpMibGJMnQ5CA9aEzcIUBc,1306
|
|
24
|
-
schemathesis/cli/commands/run/handlers/cassettes.py,sha256=
|
|
24
|
+
schemathesis/cli/commands/run/handlers/cassettes.py,sha256=2sXW9jykEFw4HCv25ycRfRd8uw5VV1e26Cp14O7PVhs,19245
|
|
25
25
|
schemathesis/cli/commands/run/handlers/junitxml.py,sha256=qiFvM4-SlM67sep003SkLqPslzaEb4nOm3bkzw-DO-Q,2602
|
|
26
26
|
schemathesis/cli/commands/run/handlers/output.py,sha256=pPp5-lJP3Zir1sTA7fmlhc-u1Jn17enXZNUerQMr56M,64166
|
|
27
27
|
schemathesis/cli/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -33,7 +33,7 @@ schemathesis/config/_auth.py,sha256=83RLVPm97W2thbn-yi01Rt94YwOxLG_a5VoxhEfjUjs,
|
|
|
33
33
|
schemathesis/config/_checks.py,sha256=F0r16eSSiICvoiTUkNNOE2PH73EGd8bikoeZdME_3Yw,10763
|
|
34
34
|
schemathesis/config/_diff_base.py,sha256=U7wuE4480EjP3K16mfC528TP5q7Q5IwAZwZLqRIrS1E,4300
|
|
35
35
|
schemathesis/config/_env.py,sha256=8XfIyrnGNQuCDnfG0lwmKRFbasRUjgeQGBAMupsmtOU,613
|
|
36
|
-
schemathesis/config/_error.py,sha256=
|
|
36
|
+
schemathesis/config/_error.py,sha256=0QnXO7Zagr69aYsX46GHm5xcTDd_18m8pFQBb0SsYvY,5777
|
|
37
37
|
schemathesis/config/_generation.py,sha256=giWs4z17z9nRe_9Z3mAZ3LEoyh4hkcJnlAA6LSy6iEo,5210
|
|
38
38
|
schemathesis/config/_health_check.py,sha256=zC9inla5ibMBlEy5WyM4_TME7ju_KH3Bwfo21RI3Gks,561
|
|
39
39
|
schemathesis/config/_operations.py,sha256=JvfMkieYBkbEmZRb4cTvQLfvHQLhmsxa3GXzgjOtmFc,12383
|
|
@@ -52,7 +52,7 @@ schemathesis/core/compat.py,sha256=9BWCrFoqN2sJIaiht_anxe8kLjYMR7t0iiOkXqLRUZ8,1
|
|
|
52
52
|
schemathesis/core/control.py,sha256=IzwIc8HIAEMtZWW0Q0iXI7T1niBpjvcLlbuwOSmy5O8,130
|
|
53
53
|
schemathesis/core/curl.py,sha256=jrPL9KpNHteyJ6A1oxJRSkL5bfuBeuPs3xh9Z_ml2cE,1892
|
|
54
54
|
schemathesis/core/deserialization.py,sha256=qjXUPaz_mc1OSgXzTUSkC8tuVR8wgVQtb9g3CcAF6D0,2951
|
|
55
|
-
schemathesis/core/errors.py,sha256=
|
|
55
|
+
schemathesis/core/errors.py,sha256=NxuhMozUnC57BSsTKC95zJgSflZ1d01D3j6p8VyXhcw,20209
|
|
56
56
|
schemathesis/core/failures.py,sha256=yFpAxWdEnm0Ri8z8RqRI9H7vcLH5ztOeSIi4m4SGx5g,8996
|
|
57
57
|
schemathesis/core/fs.py,sha256=ItQT0_cVwjDdJX9IiI7EnU75NI2H3_DCEyyUjzg_BgI,472
|
|
58
58
|
schemathesis/core/hooks.py,sha256=qhbkkRSf8URJ4LKv2wmKRINKpquUOgxQzWBHKWRWo3Q,475
|
|
@@ -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
|
|
@@ -100,16 +100,16 @@ schemathesis/generation/modes.py,sha256=Q1fhjWr3zxabU5qdtLvKfpMFZJAwlW9pnxgenjeX
|
|
|
100
100
|
schemathesis/generation/overrides.py,sha256=xI2djHsa42fzP32xpxgxO52INixKagf5DjDAWJYswM8,3890
|
|
101
101
|
schemathesis/generation/hypothesis/__init__.py,sha256=68BHULoXQC1WjFfw03ga5lvDGZ-c-J7H_fNEuUzFWRw,4976
|
|
102
102
|
schemathesis/generation/hypothesis/builder.py,sha256=N9UiJfwUplmrCLsROHQtirm1UPt_TDqUiLv4as2qAjU,38562
|
|
103
|
-
schemathesis/generation/hypothesis/examples.py,sha256=
|
|
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
|
|
@@ -129,14 +129,14 @@ schemathesis/specs/graphql/schemas.py,sha256=oNbluhfxpbxRWVIMoLn3_g-bUaVLCea29jO
|
|
|
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=OlvGCCDAiIZS_osM9OQBvDGzwEToUZmVWwzQa4wQNws,6463
|
|
134
134
|
schemathesis/specs/openapi/definitions.py,sha256=8htclglV3fW6JPBqs59lgM4LnA25Mm9IptXBPb_qUT0,93949
|
|
135
|
-
schemathesis/specs/openapi/examples.py,sha256=
|
|
135
|
+
schemathesis/specs/openapi/examples.py,sha256=bNQVYJAb2LGtl_6josaNj5O4B0b_WaqtXVg1ZTvDDv8,24451
|
|
136
136
|
schemathesis/specs/openapi/formats.py,sha256=4tYRdckauHxkJCmOhmdwDq_eOpHPaKloi89lzMPbPzw,3975
|
|
137
137
|
schemathesis/specs/openapi/media_types.py,sha256=F5M6TKl0s6Z5X8mZpPsWDEdPBvxclKRcUOc41eEwKbo,2472
|
|
138
138
|
schemathesis/specs/openapi/patterns.py,sha256=GqPZEXMRdWENQxanWjBOalIZ2MQUjuxk21kmdiI703E,18027
|
|
139
|
-
schemathesis/specs/openapi/references.py,sha256=
|
|
139
|
+
schemathesis/specs/openapi/references.py,sha256=Jez2KpfsDIwXSvxZCmRQtlHk2X9UZ-JZkKAJaCPSLk8,2981
|
|
140
140
|
schemathesis/specs/openapi/schemas.py,sha256=ONFB8kMBrryZL_tKHWvxnBjyUHoHh_MAUqxjuVDc78c,34034
|
|
141
141
|
schemathesis/specs/openapi/serialization.py,sha256=RPNdadne5wdhsGmjSvgKLRF58wpzpRx3wura8PsHM3o,12152
|
|
142
142
|
schemathesis/specs/openapi/utils.py,sha256=XkOJT8qD-6uhq-Tmwxk_xYku1Gy5F9pKL3ldNg_DRZw,522
|
|
@@ -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.16.dist-info/METADATA,sha256=C--ciooZRdh7oAzk5hwFfccPqx1-G8QPHTTxbh_lZGw,8566
|
|
182
|
+
schemathesis-4.3.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
183
|
+
schemathesis-4.3.16.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
|
|
184
|
+
schemathesis-4.3.16.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
|
|
185
|
+
schemathesis-4.3.16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|