schemathesis 3.39.7__py3-none-any.whl → 4.0.0a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- schemathesis/__init__.py +27 -65
- schemathesis/auths.py +26 -68
- schemathesis/checks.py +130 -60
- schemathesis/cli/__init__.py +5 -2105
- schemathesis/cli/commands/__init__.py +37 -0
- schemathesis/cli/commands/run/__init__.py +662 -0
- schemathesis/cli/commands/run/checks.py +80 -0
- schemathesis/cli/commands/run/context.py +117 -0
- schemathesis/cli/commands/run/events.py +30 -0
- schemathesis/cli/commands/run/executor.py +141 -0
- schemathesis/cli/commands/run/filters.py +202 -0
- schemathesis/cli/commands/run/handlers/__init__.py +46 -0
- schemathesis/cli/commands/run/handlers/base.py +18 -0
- schemathesis/cli/{cassettes.py → commands/run/handlers/cassettes.py} +178 -247
- schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
- schemathesis/cli/commands/run/handlers/output.py +1368 -0
- schemathesis/cli/commands/run/hypothesis.py +105 -0
- schemathesis/cli/commands/run/loaders.py +129 -0
- schemathesis/cli/{callbacks.py → commands/run/validation.py} +59 -175
- schemathesis/cli/constants.py +5 -58
- schemathesis/cli/core.py +17 -0
- schemathesis/cli/ext/fs.py +14 -0
- schemathesis/cli/ext/groups.py +55 -0
- schemathesis/cli/{options.py → ext/options.py} +37 -16
- schemathesis/cli/hooks.py +36 -0
- schemathesis/contrib/__init__.py +1 -3
- schemathesis/contrib/openapi/__init__.py +1 -3
- schemathesis/contrib/openapi/fill_missing_examples.py +3 -7
- schemathesis/core/__init__.py +58 -0
- schemathesis/core/compat.py +25 -0
- schemathesis/core/control.py +2 -0
- schemathesis/core/curl.py +58 -0
- schemathesis/core/deserialization.py +65 -0
- schemathesis/core/errors.py +370 -0
- schemathesis/core/failures.py +315 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/core/loaders.py +104 -0
- schemathesis/core/marks.py +66 -0
- schemathesis/{transports/content_types.py → core/media_types.py} +14 -12
- schemathesis/{internal/output.py → core/output/__init__.py} +1 -0
- schemathesis/core/output/sanitization.py +197 -0
- schemathesis/{throttling.py → core/rate_limit.py} +16 -17
- schemathesis/core/registries.py +31 -0
- schemathesis/core/transforms.py +113 -0
- schemathesis/core/transport.py +108 -0
- schemathesis/core/validation.py +38 -0
- schemathesis/core/version.py +7 -0
- schemathesis/engine/__init__.py +30 -0
- schemathesis/engine/config.py +59 -0
- schemathesis/engine/context.py +119 -0
- schemathesis/engine/control.py +36 -0
- schemathesis/engine/core.py +157 -0
- schemathesis/engine/errors.py +394 -0
- schemathesis/engine/events.py +243 -0
- schemathesis/engine/phases/__init__.py +66 -0
- schemathesis/{runner → engine/phases}/probes.py +49 -68
- schemathesis/engine/phases/stateful/__init__.py +66 -0
- schemathesis/engine/phases/stateful/_executor.py +301 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +175 -0
- schemathesis/engine/phases/unit/_executor.py +322 -0
- schemathesis/engine/phases/unit/_pool.py +74 -0
- schemathesis/engine/recorder.py +246 -0
- schemathesis/errors.py +31 -0
- schemathesis/experimental/__init__.py +9 -40
- schemathesis/filters.py +7 -95
- schemathesis/generation/__init__.py +3 -3
- schemathesis/generation/case.py +190 -0
- schemathesis/generation/coverage.py +22 -22
- schemathesis/{_patches.py → generation/hypothesis/__init__.py} +15 -6
- schemathesis/generation/hypothesis/builder.py +585 -0
- schemathesis/generation/{_hypothesis.py → hypothesis/examples.py} +2 -11
- schemathesis/generation/hypothesis/given.py +66 -0
- schemathesis/generation/hypothesis/reporting.py +14 -0
- schemathesis/generation/hypothesis/strategies.py +16 -0
- schemathesis/generation/meta.py +115 -0
- schemathesis/generation/modes.py +28 -0
- schemathesis/generation/overrides.py +96 -0
- schemathesis/generation/stateful/__init__.py +20 -0
- schemathesis/{stateful → generation/stateful}/state_machine.py +84 -109
- schemathesis/generation/targets.py +69 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +109 -0
- schemathesis/graphql/loaders.py +131 -0
- schemathesis/hooks.py +17 -62
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +387 -0
- schemathesis/openapi/generation/__init__.py +0 -0
- schemathesis/openapi/generation/filters.py +63 -0
- schemathesis/openapi/loaders.py +178 -0
- schemathesis/pytest/__init__.py +5 -0
- schemathesis/pytest/control_flow.py +7 -0
- schemathesis/pytest/lazy.py +273 -0
- schemathesis/pytest/loaders.py +12 -0
- schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +94 -107
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +456 -228
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/_cache.py +1 -2
- schemathesis/specs/graphql/scalars.py +5 -3
- schemathesis/specs/graphql/schemas.py +122 -123
- schemathesis/specs/graphql/validation.py +11 -17
- schemathesis/specs/openapi/__init__.py +6 -1
- schemathesis/specs/openapi/_cache.py +1 -2
- schemathesis/specs/openapi/_hypothesis.py +97 -134
- schemathesis/specs/openapi/checks.py +238 -219
- schemathesis/specs/openapi/converter.py +4 -4
- schemathesis/specs/openapi/definitions.py +1 -1
- schemathesis/specs/openapi/examples.py +22 -20
- schemathesis/specs/openapi/expressions/__init__.py +11 -15
- schemathesis/specs/openapi/expressions/extractors.py +1 -4
- schemathesis/specs/openapi/expressions/nodes.py +33 -32
- schemathesis/specs/openapi/formats.py +3 -2
- schemathesis/specs/openapi/links.py +123 -299
- schemathesis/specs/openapi/media_types.py +10 -12
- schemathesis/specs/openapi/negative/__init__.py +2 -1
- schemathesis/specs/openapi/negative/mutations.py +3 -2
- schemathesis/specs/openapi/parameters.py +8 -6
- schemathesis/specs/openapi/patterns.py +1 -1
- schemathesis/specs/openapi/references.py +11 -51
- schemathesis/specs/openapi/schemas.py +177 -191
- schemathesis/specs/openapi/security.py +1 -1
- schemathesis/specs/openapi/serialization.py +10 -6
- schemathesis/specs/openapi/stateful/__init__.py +97 -91
- schemathesis/transport/__init__.py +104 -0
- schemathesis/transport/asgi.py +26 -0
- schemathesis/transport/prepare.py +99 -0
- schemathesis/transport/requests.py +221 -0
- schemathesis/{_xml.py → transport/serialization.py} +69 -7
- schemathesis/transport/wsgi.py +165 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/METADATA +18 -14
- schemathesis-4.0.0a2.dist-info/RECORD +151 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/entry_points.txt +1 -1
- schemathesis/_compat.py +0 -74
- schemathesis/_dependency_versions.py +0 -19
- schemathesis/_hypothesis.py +0 -559
- schemathesis/_override.py +0 -50
- schemathesis/_rate_limiter.py +0 -7
- schemathesis/cli/context.py +0 -75
- schemathesis/cli/debug.py +0 -27
- schemathesis/cli/handlers.py +0 -19
- schemathesis/cli/junitxml.py +0 -124
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -936
- schemathesis/cli/output/short.py +0 -59
- schemathesis/cli/reporting.py +0 -79
- schemathesis/cli/sanitization.py +0 -26
- schemathesis/code_samples.py +0 -151
- schemathesis/constants.py +0 -56
- schemathesis/contrib/openapi/formats/__init__.py +0 -9
- schemathesis/contrib/openapi/formats/uuid.py +0 -16
- schemathesis/contrib/unique_data.py +0 -41
- schemathesis/exceptions.py +0 -571
- schemathesis/extra/_aiohttp.py +0 -28
- schemathesis/extra/_flask.py +0 -13
- schemathesis/extra/_server.py +0 -18
- schemathesis/failures.py +0 -277
- schemathesis/fixups/__init__.py +0 -37
- schemathesis/fixups/fast_api.py +0 -41
- schemathesis/fixups/utf8_bom.py +0 -28
- schemathesis/generation/_methods.py +0 -44
- schemathesis/graphql.py +0 -3
- schemathesis/internal/__init__.py +0 -7
- schemathesis/internal/checks.py +0 -84
- schemathesis/internal/copy.py +0 -32
- schemathesis/internal/datetime.py +0 -5
- schemathesis/internal/deprecation.py +0 -38
- schemathesis/internal/diff.py +0 -15
- schemathesis/internal/extensions.py +0 -27
- schemathesis/internal/jsonschema.py +0 -36
- schemathesis/internal/transformation.py +0 -26
- schemathesis/internal/validation.py +0 -34
- schemathesis/lazy.py +0 -474
- schemathesis/loaders.py +0 -122
- schemathesis/models.py +0 -1341
- schemathesis/parameters.py +0 -90
- schemathesis/runner/__init__.py +0 -605
- schemathesis/runner/events.py +0 -389
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/context.py +0 -104
- schemathesis/runner/impl/core.py +0 -1246
- schemathesis/runner/impl/solo.py +0 -80
- schemathesis/runner/impl/threadpool.py +0 -391
- schemathesis/runner/serialization.py +0 -544
- schemathesis/sanitization.py +0 -252
- schemathesis/serializers.py +0 -328
- schemathesis/service/__init__.py +0 -18
- schemathesis/service/auth.py +0 -11
- schemathesis/service/ci.py +0 -202
- schemathesis/service/client.py +0 -133
- schemathesis/service/constants.py +0 -38
- schemathesis/service/events.py +0 -61
- schemathesis/service/extensions.py +0 -224
- schemathesis/service/hosts.py +0 -111
- schemathesis/service/metadata.py +0 -71
- schemathesis/service/models.py +0 -258
- schemathesis/service/report.py +0 -255
- schemathesis/service/serialization.py +0 -173
- schemathesis/service/usage.py +0 -66
- schemathesis/specs/graphql/loaders.py +0 -364
- schemathesis/specs/openapi/expressions/context.py +0 -16
- schemathesis/specs/openapi/loaders.py +0 -708
- schemathesis/specs/openapi/stateful/statistic.py +0 -198
- schemathesis/specs/openapi/stateful/types.py +0 -14
- schemathesis/specs/openapi/validation.py +0 -26
- schemathesis/stateful/__init__.py +0 -147
- schemathesis/stateful/config.py +0 -97
- schemathesis/stateful/context.py +0 -135
- schemathesis/stateful/events.py +0 -274
- schemathesis/stateful/runner.py +0 -309
- schemathesis/stateful/sink.py +0 -68
- schemathesis/stateful/statistic.py +0 -22
- schemathesis/stateful/validation.py +0 -100
- schemathesis/targets.py +0 -77
- schemathesis/transports/__init__.py +0 -359
- schemathesis/transports/asgi.py +0 -7
- schemathesis/transports/auth.py +0 -38
- schemathesis/transports/headers.py +0 -36
- schemathesis/transports/responses.py +0 -57
- schemathesis/types.py +0 -44
- schemathesis/utils.py +0 -164
- schemathesis-3.39.7.dist-info/RECORD +0 -160
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
- /schemathesis/{_lazy_import.py → core/lazy_import.py} +0 -0
- /schemathesis/{internal → core}/result.py +0 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/WHEEL +0 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/licenses/LICENSE +0 -0
@@ -3,8 +3,8 @@ from __future__ import annotations
|
|
3
3
|
from itertools import chain
|
4
4
|
from typing import Any, Callable
|
5
5
|
|
6
|
-
from
|
7
|
-
|
6
|
+
from schemathesis.core.transforms import deepclone, transform
|
7
|
+
|
8
8
|
from .patterns import update_quantifier
|
9
9
|
|
10
10
|
|
@@ -22,7 +22,7 @@ def to_json_schema(
|
|
22
22
|
See a recursive version below.
|
23
23
|
"""
|
24
24
|
if copy:
|
25
|
-
schema =
|
25
|
+
schema = deepclone(schema)
|
26
26
|
if schema.get(nullable_name) is True:
|
27
27
|
del schema[nullable_name]
|
28
28
|
schema = {"anyOf": [schema, {"type": "null"}]}
|
@@ -94,7 +94,7 @@ def is_read_only(schema: dict[str, Any] | bool) -> bool:
|
|
94
94
|
def to_json_schema_recursive(
|
95
95
|
schema: dict[str, Any], nullable_name: str, is_response_schema: bool = False, update_quantifiers: bool = True
|
96
96
|
) -> dict[str, Any]:
|
97
|
-
return
|
97
|
+
return transform(
|
98
98
|
schema,
|
99
99
|
to_json_schema,
|
100
100
|
nullable_name=nullable_name,
|
@@ -9,11 +9,15 @@ from typing import TYPE_CHECKING, Any, Generator, Iterator, Union, cast
|
|
9
9
|
import requests
|
10
10
|
from hypothesis_jsonschema import from_schema
|
11
11
|
|
12
|
-
from
|
13
|
-
from
|
14
|
-
from
|
15
|
-
from
|
16
|
-
from .
|
12
|
+
from schemathesis.core.transforms import deepclone
|
13
|
+
from schemathesis.core.transport import DEFAULT_RESPONSE_TIMEOUT
|
14
|
+
from schemathesis.generation import GenerationConfig
|
15
|
+
from schemathesis.generation.case import Case
|
16
|
+
from schemathesis.generation.hypothesis import examples
|
17
|
+
from schemathesis.generation.meta import TestPhase
|
18
|
+
from schemathesis.schemas import APIOperation
|
19
|
+
|
20
|
+
from ._hypothesis import get_default_format_strategies, openapi_cases
|
17
21
|
from .constants import LOCATION_TO_CONTAINER
|
18
22
|
from .formats import STRING_FORMATS
|
19
23
|
from .parameters import OpenAPIBody, OpenAPIParameter
|
@@ -21,8 +25,6 @@ from .parameters import OpenAPIBody, OpenAPIParameter
|
|
21
25
|
if TYPE_CHECKING:
|
22
26
|
from hypothesis.strategies import SearchStrategy
|
23
27
|
|
24
|
-
from ...generation import GenerationConfig
|
25
|
-
|
26
28
|
|
27
29
|
@dataclass
|
28
30
|
class ParameterExample:
|
@@ -45,7 +47,7 @@ Example = Union[ParameterExample, BodyExample]
|
|
45
47
|
|
46
48
|
|
47
49
|
def get_strategies_from_examples(
|
48
|
-
operation: APIOperation[OpenAPIParameter
|
50
|
+
operation: APIOperation[OpenAPIParameter], **kwargs: Any
|
49
51
|
) -> list[SearchStrategy[Case]]:
|
50
52
|
"""Build a set of strategies that generate test cases based on explicit examples in the schema."""
|
51
53
|
maps = {}
|
@@ -68,15 +70,15 @@ def get_strategies_from_examples(
|
|
68
70
|
examples = list(extract_top_level(operation))
|
69
71
|
# Add examples from parameter's schemas
|
70
72
|
examples.extend(extract_from_schemas(operation))
|
71
|
-
as_strategy_kwargs = as_strategy_kwargs or {}
|
72
|
-
as_strategy_kwargs["phase"] = TestPhase.EXPLICIT
|
73
73
|
return [
|
74
|
-
|
74
|
+
openapi_cases(operation=operation, **{**parameters, **kwargs, "phase": TestPhase.EXPLICIT}).map(
|
75
|
+
serialize_components
|
76
|
+
)
|
75
77
|
for parameters in produce_combinations(examples)
|
76
78
|
]
|
77
79
|
|
78
80
|
|
79
|
-
def extract_top_level(operation: APIOperation[OpenAPIParameter
|
81
|
+
def extract_top_level(operation: APIOperation[OpenAPIParameter]) -> Generator[Example, None, None]:
|
80
82
|
"""Extract top-level parameter examples from `examples` & `example` fields."""
|
81
83
|
responses = find_in_responses(operation)
|
82
84
|
for parameter in operation.iter_parameters():
|
@@ -144,7 +146,7 @@ def _expand_subschemas(schema: dict[str, Any] | bool) -> Generator[dict[str, Any
|
|
144
146
|
for subschema in schema[key]:
|
145
147
|
yield subschema
|
146
148
|
if "allOf" in schema:
|
147
|
-
subschema =
|
149
|
+
subschema = deepclone(schema["allOf"][0])
|
148
150
|
for sub in schema["allOf"][1:]:
|
149
151
|
if isinstance(sub, dict):
|
150
152
|
for key, value in sub.items():
|
@@ -162,7 +164,7 @@ def _expand_subschemas(schema: dict[str, Any] | bool) -> Generator[dict[str, Any
|
|
162
164
|
|
163
165
|
|
164
166
|
def _find_parameter_examples_definition(
|
165
|
-
operation: APIOperation[OpenAPIParameter
|
167
|
+
operation: APIOperation[OpenAPIParameter], parameter_name: str, field_name: str
|
166
168
|
) -> dict[str, Any]:
|
167
169
|
"""Find the original, unresolved `examples` definition of a parameter."""
|
168
170
|
from .schemas import BaseOpenAPISchema
|
@@ -180,13 +182,13 @@ def _find_parameter_examples_definition(
|
|
180
182
|
|
181
183
|
|
182
184
|
def _find_request_body_examples_definition(
|
183
|
-
operation: APIOperation[OpenAPIParameter
|
185
|
+
operation: APIOperation[OpenAPIParameter], alternative: OpenAPIBody
|
184
186
|
) -> dict[str, Any]:
|
185
187
|
"""Find the original, unresolved `examples` definition of a request body variant."""
|
186
188
|
from .schemas import BaseOpenAPISchema
|
187
189
|
|
188
190
|
schema = cast(BaseOpenAPISchema, operation.schema)
|
189
|
-
if schema.
|
191
|
+
if schema.specification.version == "2.0":
|
190
192
|
raw_schema = schema.raw_schema
|
191
193
|
path_data = raw_schema["paths"][operation.path]
|
192
194
|
parameters = chain(path_data[operation.method].get("parameters", []), path_data.get("parameters", []))
|
@@ -222,12 +224,12 @@ def extract_inner_examples(
|
|
222
224
|
@lru_cache
|
223
225
|
def load_external_example(url: str) -> bytes:
|
224
226
|
"""Load examples the `externalValue` keyword."""
|
225
|
-
response = requests.get(url, timeout=DEFAULT_RESPONSE_TIMEOUT
|
227
|
+
response = requests.get(url, timeout=DEFAULT_RESPONSE_TIMEOUT)
|
226
228
|
response.raise_for_status()
|
227
229
|
return response.content
|
228
230
|
|
229
231
|
|
230
|
-
def extract_from_schemas(operation: APIOperation[OpenAPIParameter
|
232
|
+
def extract_from_schemas(operation: APIOperation[OpenAPIParameter]) -> Generator[Example, None, None]:
|
231
233
|
"""Extract examples from parameters' schema definitions."""
|
232
234
|
for parameter in operation.iter_parameters():
|
233
235
|
schema = parameter.as_json_schema(operation)
|
@@ -244,7 +246,7 @@ def extract_from_schemas(operation: APIOperation[OpenAPIParameter, Case]) -> Gen
|
|
244
246
|
|
245
247
|
|
246
248
|
def extract_from_schema(
|
247
|
-
operation: APIOperation[OpenAPIParameter
|
249
|
+
operation: APIOperation[OpenAPIParameter],
|
248
250
|
schema: dict[str, Any],
|
249
251
|
example_field_name: str,
|
250
252
|
examples_field_name: str,
|
@@ -306,7 +308,7 @@ def _generate_single_example(
|
|
306
308
|
allow_x00=generation_config.allow_x00,
|
307
309
|
codec=generation_config.codec,
|
308
310
|
)
|
309
|
-
return
|
311
|
+
return examples.generate_one(strategy)
|
310
312
|
|
311
313
|
|
312
314
|
def produce_combinations(examples: list[Example]) -> Generator[dict[str, Any], None, None]:
|
@@ -8,42 +8,38 @@ from __future__ import annotations
|
|
8
8
|
import json
|
9
9
|
from typing import Any
|
10
10
|
|
11
|
+
from schemathesis.generation.stateful.state_machine import StepOutput
|
12
|
+
|
11
13
|
from . import lexer, nodes, parser
|
12
|
-
from .context import ExpressionContext
|
13
14
|
|
14
|
-
__all__ = [
|
15
|
-
"lexer",
|
16
|
-
"nodes",
|
17
|
-
"parser",
|
18
|
-
"ExpressionContext",
|
19
|
-
]
|
15
|
+
__all__ = ["lexer", "nodes", "parser"]
|
20
16
|
|
21
17
|
|
22
|
-
def evaluate(expr: Any,
|
18
|
+
def evaluate(expr: Any, output: StepOutput, evaluate_nested: bool = False) -> Any:
|
23
19
|
"""Evaluate runtime expression in context."""
|
24
20
|
if isinstance(expr, (dict, list)) and evaluate_nested:
|
25
|
-
return _evaluate_nested(expr,
|
21
|
+
return _evaluate_nested(expr, output)
|
26
22
|
if not isinstance(expr, str):
|
27
23
|
# Can be a non-string constant
|
28
24
|
return expr
|
29
|
-
parts = [node.evaluate(
|
25
|
+
parts = [node.evaluate(output) for node in parser.parse(expr)]
|
30
26
|
if len(parts) == 1:
|
31
27
|
return parts[0] # keep the return type the same as the internal value type
|
32
28
|
# otherwise, concatenate into a string
|
33
29
|
return "".join(str(part) for part in parts if part is not None)
|
34
30
|
|
35
31
|
|
36
|
-
def _evaluate_nested(expr: dict[str, Any] | list,
|
32
|
+
def _evaluate_nested(expr: dict[str, Any] | list, output: StepOutput) -> Any:
|
37
33
|
if isinstance(expr, dict):
|
38
34
|
return {
|
39
|
-
_evaluate_object_key(key,
|
35
|
+
_evaluate_object_key(key, output): evaluate(value, output, evaluate_nested=True)
|
40
36
|
for key, value in expr.items()
|
41
37
|
}
|
42
|
-
return [evaluate(item,
|
38
|
+
return [evaluate(item, output, evaluate_nested=True) for item in expr]
|
43
39
|
|
44
40
|
|
45
|
-
def _evaluate_object_key(key: str,
|
46
|
-
evaluated = evaluate(key,
|
41
|
+
def _evaluate_object_key(key: str, output: StepOutput) -> Any:
|
42
|
+
evaluated = evaluate(key, output)
|
47
43
|
if isinstance(evaluated, str):
|
48
44
|
return evaluated
|
49
45
|
if isinstance(evaluated, bool):
|
@@ -4,14 +4,15 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
from dataclasses import dataclass
|
6
6
|
from enum import Enum, unique
|
7
|
-
from typing import TYPE_CHECKING, Any
|
7
|
+
from typing import TYPE_CHECKING, Any, cast
|
8
8
|
|
9
9
|
from requests.structures import CaseInsensitiveDict
|
10
10
|
|
11
|
-
from
|
11
|
+
from schemathesis.core.transforms import UNRESOLVABLE, resolve_pointer
|
12
|
+
from schemathesis.generation.stateful.state_machine import StepOutput
|
13
|
+
from schemathesis.transport.requests import REQUESTS_TRANSPORT
|
12
14
|
|
13
15
|
if TYPE_CHECKING:
|
14
|
-
from .context import ExpressionContext
|
15
16
|
from .extractors import Extractor
|
16
17
|
|
17
18
|
|
@@ -19,7 +20,7 @@ if TYPE_CHECKING:
|
|
19
20
|
class Node:
|
20
21
|
"""Generic expression node."""
|
21
22
|
|
22
|
-
def evaluate(self,
|
23
|
+
def evaluate(self, output: StepOutput) -> str:
|
23
24
|
raise NotImplementedError
|
24
25
|
|
25
26
|
|
@@ -38,7 +39,7 @@ class String(Node):
|
|
38
39
|
|
39
40
|
value: str
|
40
41
|
|
41
|
-
def evaluate(self,
|
42
|
+
def evaluate(self, output: StepOutput) -> str:
|
42
43
|
"""String tokens are passed as they are.
|
43
44
|
|
44
45
|
``foo{$request.path.id}``
|
@@ -52,24 +53,29 @@ class String(Node):
|
|
52
53
|
class URL(Node):
|
53
54
|
"""A node for `$url` expression."""
|
54
55
|
|
55
|
-
def evaluate(self,
|
56
|
-
|
56
|
+
def evaluate(self, output: StepOutput) -> str:
|
57
|
+
import requests
|
58
|
+
|
59
|
+
base_url = output.case.operation.base_url or "http://127.0.0.1"
|
60
|
+
kwargs = REQUESTS_TRANSPORT.serialize_case(output.case, base_url=base_url)
|
61
|
+
prepared = requests.Request(**kwargs).prepare()
|
62
|
+
return cast(str, prepared.url)
|
57
63
|
|
58
64
|
|
59
65
|
@dataclass
|
60
66
|
class Method(Node):
|
61
67
|
"""A node for `$method` expression."""
|
62
68
|
|
63
|
-
def evaluate(self,
|
64
|
-
return
|
69
|
+
def evaluate(self, output: StepOutput) -> str:
|
70
|
+
return output.case.operation.method.upper()
|
65
71
|
|
66
72
|
|
67
73
|
@dataclass
|
68
74
|
class StatusCode(Node):
|
69
75
|
"""A node for `$statusCode` expression."""
|
70
76
|
|
71
|
-
def evaluate(self,
|
72
|
-
return str(
|
77
|
+
def evaluate(self, output: StepOutput) -> str:
|
78
|
+
return str(output.response.status_code)
|
73
79
|
|
74
80
|
|
75
81
|
@dataclass
|
@@ -80,11 +86,11 @@ class NonBodyRequest(Node):
|
|
80
86
|
parameter: str
|
81
87
|
extractor: Extractor | None = None
|
82
88
|
|
83
|
-
def evaluate(self,
|
89
|
+
def evaluate(self, output: StepOutput) -> str:
|
84
90
|
container: dict | CaseInsensitiveDict = {
|
85
|
-
"query":
|
86
|
-
"path":
|
87
|
-
"header":
|
91
|
+
"query": output.case.query,
|
92
|
+
"path": output.case.path_parameters,
|
93
|
+
"header": output.case.headers,
|
88
94
|
}[self.location] or {}
|
89
95
|
if self.location == "header":
|
90
96
|
container = CaseInsensitiveDict(container)
|
@@ -102,12 +108,12 @@ class BodyRequest(Node):
|
|
102
108
|
|
103
109
|
pointer: str | None = None
|
104
110
|
|
105
|
-
def evaluate(self,
|
106
|
-
document =
|
111
|
+
def evaluate(self, output: StepOutput) -> Any:
|
112
|
+
document = output.case.body
|
107
113
|
if self.pointer is None:
|
108
114
|
return document
|
109
|
-
resolved =
|
110
|
-
if resolved is
|
115
|
+
resolved = resolve_pointer(document, self.pointer[1:])
|
116
|
+
if resolved is UNRESOLVABLE:
|
111
117
|
return None
|
112
118
|
return resolved
|
113
119
|
|
@@ -119,13 +125,13 @@ class HeaderResponse(Node):
|
|
119
125
|
parameter: str
|
120
126
|
extractor: Extractor | None = None
|
121
127
|
|
122
|
-
def evaluate(self,
|
123
|
-
value =
|
128
|
+
def evaluate(self, output: StepOutput) -> str:
|
129
|
+
value = output.response.headers.get(self.parameter.lower())
|
124
130
|
if value is None:
|
125
131
|
return ""
|
126
132
|
if self.extractor is not None:
|
127
|
-
return self.extractor.extract(value) or ""
|
128
|
-
return value
|
133
|
+
return self.extractor.extract(value[0]) or ""
|
134
|
+
return value[0]
|
129
135
|
|
130
136
|
|
131
137
|
@dataclass
|
@@ -134,17 +140,12 @@ class BodyResponse(Node):
|
|
134
140
|
|
135
141
|
pointer: str | None = None
|
136
142
|
|
137
|
-
def evaluate(self,
|
138
|
-
|
139
|
-
|
140
|
-
if isinstance(context.response, WSGIResponse):
|
141
|
-
document = context.response.json
|
142
|
-
else:
|
143
|
-
document = context.response.json()
|
143
|
+
def evaluate(self, output: StepOutput) -> Any:
|
144
|
+
document = output.response.json()
|
144
145
|
if self.pointer is None:
|
145
146
|
# We need the parsed document - data will be serialized before sending to the application
|
146
147
|
return document
|
147
|
-
resolved =
|
148
|
-
if resolved is
|
148
|
+
resolved = resolve_pointer(document, self.pointer[1:])
|
149
|
+
if resolved is UNRESOLVABLE:
|
149
150
|
return None
|
150
151
|
return resolved
|
@@ -5,6 +5,8 @@ from base64 import b64encode
|
|
5
5
|
from functools import lru_cache
|
6
6
|
from typing import TYPE_CHECKING
|
7
7
|
|
8
|
+
from schemathesis.transport.serialization import Binary
|
9
|
+
|
8
10
|
if TYPE_CHECKING:
|
9
11
|
from hypothesis import strategies as st
|
10
12
|
|
@@ -54,8 +56,6 @@ def get_default_format_strategies() -> dict[str, st.SearchStrategy]:
|
|
54
56
|
from hypothesis import strategies as st
|
55
57
|
from requests.auth import _basic_auth_str
|
56
58
|
|
57
|
-
from ...serializers import Binary
|
58
|
-
|
59
59
|
def make_basic_auth_str(item: tuple[str, str]) -> str:
|
60
60
|
return _basic_auth_str(*item)
|
61
61
|
|
@@ -67,6 +67,7 @@ def get_default_format_strategies() -> dict[str, st.SearchStrategy]:
|
|
67
67
|
return {
|
68
68
|
"binary": st.binary().map(Binary),
|
69
69
|
"byte": st.binary().map(lambda x: b64encode(x).decode()),
|
70
|
+
"uuid": st.uuids().map(str),
|
70
71
|
# RFC 7230, Section 3.2.6
|
71
72
|
"_header_name": st.text(
|
72
73
|
min_size=1, alphabet=st.sampled_from("!#$%&'*+-.^_`|~" + string.digits + string.ascii_letters)
|