schemathesis 3.37.0__py3-none-any.whl → 3.38.0__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/_hypothesis.py +18 -8
- schemathesis/_patches.py +21 -0
- schemathesis/cli/__init__.py +1 -1
- schemathesis/cli/cassettes.py +6 -0
- schemathesis/extra/pytest_plugin.py +1 -1
- schemathesis/generation/_hypothesis.py +2 -0
- schemathesis/generation/coverage.py +257 -59
- schemathesis/hooks.py +4 -0
- schemathesis/internal/checks.py +4 -2
- schemathesis/internal/diff.py +15 -0
- schemathesis/models.py +65 -3
- schemathesis/parameters.py +5 -0
- schemathesis/runner/impl/context.py +10 -1
- schemathesis/runner/impl/core.py +14 -4
- schemathesis/runner/serialization.py +6 -3
- schemathesis/serializers.py +3 -0
- schemathesis/service/extensions.py +1 -1
- schemathesis/service/metadata.py +3 -3
- schemathesis/specs/openapi/_hypothesis.py +7 -46
- schemathesis/specs/openapi/checks.py +7 -2
- schemathesis/specs/openapi/converter.py +27 -11
- schemathesis/specs/openapi/formats.py +44 -0
- schemathesis/specs/openapi/links.py +4 -0
- schemathesis/specs/openapi/negative/mutations.py +5 -0
- schemathesis/specs/openapi/parameters.py +21 -14
- schemathesis/specs/openapi/schemas.py +6 -2
- schemathesis/stateful/context.py +1 -1
- schemathesis/stateful/runner.py +6 -2
- schemathesis/transports/__init__.py +4 -0
- schemathesis/utils.py +6 -4
- {schemathesis-3.37.0.dist-info → schemathesis-3.38.0.dist-info}/METADATA +2 -1
- {schemathesis-3.37.0.dist-info → schemathesis-3.38.0.dist-info}/RECORD +35 -33
- {schemathesis-3.37.0.dist-info → schemathesis-3.38.0.dist-info}/WHEEL +0 -0
- {schemathesis-3.37.0.dist-info → schemathesis-3.38.0.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.37.0.dist-info → schemathesis-3.38.0.dist-info}/licenses/LICENSE +0 -0
schemathesis/_hypothesis.py
CHANGED
|
@@ -6,15 +6,16 @@ import asyncio
|
|
|
6
6
|
import json
|
|
7
7
|
import warnings
|
|
8
8
|
from copy import copy
|
|
9
|
+
from functools import wraps
|
|
9
10
|
from typing import TYPE_CHECKING, Any, Callable, Generator, Mapping
|
|
10
11
|
|
|
11
12
|
import hypothesis
|
|
12
13
|
from hypothesis import Phase
|
|
13
14
|
from hypothesis.errors import HypothesisWarning, Unsatisfiable
|
|
14
15
|
from hypothesis.internal.entropy import deterministic_PRNG
|
|
15
|
-
from hypothesis.internal.reflection import proxies
|
|
16
16
|
from jsonschema.exceptions import SchemaError
|
|
17
17
|
|
|
18
|
+
from . import _patches
|
|
18
19
|
from .auths import get_auth_storage_from_test
|
|
19
20
|
from .constants import DEFAULT_DEADLINE, NOT_SET
|
|
20
21
|
from .exceptions import OperationSchemaError, SerializationNotPossible
|
|
@@ -29,11 +30,13 @@ from .types import NotSet
|
|
|
29
30
|
if TYPE_CHECKING:
|
|
30
31
|
from .utils import GivenInput
|
|
31
32
|
|
|
32
|
-
# Forcefully initializes Hypothesis' global PRNG to avoid races that
|
|
33
|
+
# Forcefully initializes Hypothesis' global PRNG to avoid races that initialize it
|
|
33
34
|
# if e.g. Schemathesis CLI is used with multiple workers
|
|
34
35
|
with deterministic_PRNG():
|
|
35
36
|
pass
|
|
36
37
|
|
|
38
|
+
_patches.install()
|
|
39
|
+
|
|
37
40
|
|
|
38
41
|
def create_test(
|
|
39
42
|
*,
|
|
@@ -72,7 +75,7 @@ def create_test(
|
|
|
72
75
|
# tests in multiple threads because Hypothesis stores some internal attributes on function objects and re-writing
|
|
73
76
|
# them from different threads may lead to unpredictable side-effects.
|
|
74
77
|
|
|
75
|
-
@
|
|
78
|
+
@wraps(test)
|
|
76
79
|
def test_function(*args: Any, **kwargs: Any) -> Any:
|
|
77
80
|
__tracebackhide__ = True
|
|
78
81
|
return test(*args, **kwargs)
|
|
@@ -220,7 +223,6 @@ def _iter_coverage_cases(
|
|
|
220
223
|
from .specs.openapi.constants import LOCATION_TO_CONTAINER
|
|
221
224
|
from .specs.openapi.examples import find_in_responses, find_matching_in_responses
|
|
222
225
|
|
|
223
|
-
ctx = coverage.CoverageContext(data_generation_methods=data_generation_methods)
|
|
224
226
|
meta = GenerationMetadata(
|
|
225
227
|
query=None,
|
|
226
228
|
path_parameters=None,
|
|
@@ -229,15 +231,18 @@ def _iter_coverage_cases(
|
|
|
229
231
|
body=None,
|
|
230
232
|
phase=TestPhase.COVERAGE,
|
|
231
233
|
description=None,
|
|
234
|
+
location=None,
|
|
232
235
|
)
|
|
233
236
|
generators: dict[tuple[str, str], Generator[coverage.GeneratedValue, None, None]] = {}
|
|
234
237
|
template: dict[str, Any] = {}
|
|
235
238
|
responses = find_in_responses(operation)
|
|
236
239
|
for parameter in operation.iter_parameters():
|
|
237
|
-
schema = parameter.as_json_schema(operation)
|
|
240
|
+
schema = parameter.as_json_schema(operation, update_quantifiers=False)
|
|
238
241
|
for value in find_matching_in_responses(responses, parameter.name):
|
|
239
242
|
schema.setdefault("examples", []).append(value)
|
|
240
|
-
gen = coverage.cover_schema_iter(
|
|
243
|
+
gen = coverage.cover_schema_iter(
|
|
244
|
+
coverage.CoverageContext(data_generation_methods=data_generation_methods), schema
|
|
245
|
+
)
|
|
241
246
|
value = next(gen, NOT_SET)
|
|
242
247
|
if isinstance(value, NotSet):
|
|
243
248
|
continue
|
|
@@ -251,13 +256,15 @@ def _iter_coverage_cases(
|
|
|
251
256
|
generators[(location, name)] = gen
|
|
252
257
|
if operation.body:
|
|
253
258
|
for body in operation.body:
|
|
254
|
-
schema = body.as_json_schema(operation)
|
|
259
|
+
schema = body.as_json_schema(operation, update_quantifiers=False)
|
|
255
260
|
# Definition could be a list for Open API 2.0
|
|
256
261
|
definition = body.definition if isinstance(body.definition, dict) else {}
|
|
257
262
|
examples = [example["value"] for example in definition.get("examples", {}).values() if "value" in example]
|
|
258
263
|
if examples:
|
|
259
264
|
schema.setdefault("examples", []).extend(examples)
|
|
260
|
-
gen = coverage.cover_schema_iter(
|
|
265
|
+
gen = coverage.cover_schema_iter(
|
|
266
|
+
coverage.CoverageContext(data_generation_methods=data_generation_methods), schema
|
|
267
|
+
)
|
|
261
268
|
value = next(gen, NOT_SET)
|
|
262
269
|
if isinstance(value, NotSet):
|
|
263
270
|
continue
|
|
@@ -268,12 +275,14 @@ def _iter_coverage_cases(
|
|
|
268
275
|
case.data_generation_method = value.data_generation_method
|
|
269
276
|
case.meta = copy(meta)
|
|
270
277
|
case.meta.description = value.description
|
|
278
|
+
case.meta.location = value.location
|
|
271
279
|
yield case
|
|
272
280
|
for next_value in gen:
|
|
273
281
|
case = operation.make_case(**{**template, "body": next_value.value, "media_type": body.media_type})
|
|
274
282
|
case.data_generation_method = next_value.data_generation_method
|
|
275
283
|
case.meta = copy(meta)
|
|
276
284
|
case.meta.description = next_value.description
|
|
285
|
+
case.meta.location = next_value.location
|
|
277
286
|
yield case
|
|
278
287
|
elif DataGenerationMethod.positive in data_generation_methods:
|
|
279
288
|
case = operation.make_case(**template)
|
|
@@ -292,6 +301,7 @@ def _iter_coverage_cases(
|
|
|
292
301
|
case.data_generation_method = value.data_generation_method
|
|
293
302
|
case.meta = copy(meta)
|
|
294
303
|
case.meta.description = value.description
|
|
304
|
+
case.meta.location = value.location
|
|
295
305
|
yield case
|
|
296
306
|
|
|
297
307
|
|
schemathesis/_patches.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""A set of performance-related patches."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def install() -> None:
|
|
7
|
+
from hypothesis.internal.reflection import is_first_param_referenced_in_function
|
|
8
|
+
from hypothesis.strategies._internal import core
|
|
9
|
+
from hypothesis_jsonschema import _from_schema, _resolve
|
|
10
|
+
|
|
11
|
+
from .internal.copy import fast_deepcopy
|
|
12
|
+
|
|
13
|
+
# This one is used a lot, and under the hood it re-parses the AST of the same function
|
|
14
|
+
def _is_first_param_referenced_in_function(f: Any) -> bool:
|
|
15
|
+
if f.__name__ == "from_object_schema" and f.__module__ == "hypothesis_jsonschema._from_schema":
|
|
16
|
+
return True
|
|
17
|
+
return is_first_param_referenced_in_function(f)
|
|
18
|
+
|
|
19
|
+
core.is_first_param_referenced_in_function = _is_first_param_referenced_in_function # type: ignore
|
|
20
|
+
_resolve.deepcopy = fast_deepcopy # type: ignore
|
|
21
|
+
_from_schema.deepcopy = fast_deepcopy # type: ignore
|
schemathesis/cli/__init__.py
CHANGED
|
@@ -36,7 +36,7 @@ from ..filters import FilterSet, expression_to_filter_function, is_deprecated
|
|
|
36
36
|
from ..fixups import ALL_FIXUPS
|
|
37
37
|
from ..generation import DEFAULT_DATA_GENERATION_METHODS, DataGenerationMethod
|
|
38
38
|
from ..hooks import GLOBAL_HOOK_DISPATCHER, HookContext, HookDispatcher, HookScope
|
|
39
|
-
from ..internal.checks import CheckConfig
|
|
39
|
+
from ..internal.checks import CheckConfig
|
|
40
40
|
from ..internal.datetime import current_datetime
|
|
41
41
|
from ..internal.output import OutputConfig
|
|
42
42
|
from ..internal.validation import file_exists
|
schemathesis/cli/cassettes.py
CHANGED
|
@@ -244,6 +244,12 @@ http_interactions:"""
|
|
|
244
244
|
write_double_quoted(stream, interaction.description)
|
|
245
245
|
else:
|
|
246
246
|
stream.write("null")
|
|
247
|
+
stream.write("\n location: ")
|
|
248
|
+
|
|
249
|
+
if interaction.location is not None:
|
|
250
|
+
write_double_quoted(stream, interaction.location)
|
|
251
|
+
else:
|
|
252
|
+
stream.write("null")
|
|
247
253
|
stream.write(
|
|
248
254
|
f"""
|
|
249
255
|
phase: {phase}
|
|
@@ -200,7 +200,7 @@ class SchemathesisCase(PyCollector):
|
|
|
200
200
|
kwargs["_ispytest"] = True
|
|
201
201
|
metafunc = Metafunc(definition, fixtureinfo, self.config, **kwargs)
|
|
202
202
|
methods = []
|
|
203
|
-
if hasattr(module, "pytest_generate_tests"):
|
|
203
|
+
if module is not None and hasattr(module, "pytest_generate_tests"):
|
|
204
204
|
methods.append(module.pytest_generate_tests)
|
|
205
205
|
if hasattr(cls, "pytest_generate_tests"):
|
|
206
206
|
cls = cast(Type, cls)
|
|
@@ -43,6 +43,8 @@ def add_single_example(strategy: st.SearchStrategy[T], examples: list[T]) -> Non
|
|
|
43
43
|
def example_generating_inner_function(ex: T) -> None:
|
|
44
44
|
examples.append(ex)
|
|
45
45
|
|
|
46
|
+
example_generating_inner_function._hypothesis_internal_database_key = b"" # type: ignore
|
|
47
|
+
|
|
46
48
|
if SCHEMATHESIS_BENCHMARK_SEED is not None:
|
|
47
49
|
example_generating_inner_function = seed(SCHEMATHESIS_BENCHMARK_SEED)(example_generating_inner_function)
|
|
48
50
|
|