schemathesis 3.21.2__py3-none-any.whl → 3.22.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- schemathesis/__init__.py +1 -1
- schemathesis/_compat.py +2 -18
- schemathesis/_dependency_versions.py +1 -6
- schemathesis/_hypothesis.py +15 -12
- schemathesis/_lazy_import.py +3 -2
- schemathesis/_xml.py +12 -11
- schemathesis/auths.py +88 -81
- schemathesis/checks.py +4 -4
- schemathesis/cli/__init__.py +202 -171
- schemathesis/cli/callbacks.py +29 -32
- schemathesis/cli/cassettes.py +25 -25
- schemathesis/cli/context.py +18 -12
- schemathesis/cli/junitxml.py +2 -2
- schemathesis/cli/options.py +10 -11
- schemathesis/cli/output/default.py +64 -34
- schemathesis/code_samples.py +10 -10
- schemathesis/constants.py +1 -1
- schemathesis/contrib/unique_data.py +2 -2
- schemathesis/exceptions.py +55 -42
- schemathesis/extra/_aiohttp.py +2 -2
- schemathesis/extra/_flask.py +2 -2
- schemathesis/extra/_server.py +3 -2
- schemathesis/extra/pytest_plugin.py +10 -10
- schemathesis/failures.py +16 -16
- schemathesis/filters.py +40 -41
- schemathesis/fixups/__init__.py +4 -3
- schemathesis/fixups/fast_api.py +5 -4
- schemathesis/generation/__init__.py +16 -4
- schemathesis/hooks.py +25 -25
- schemathesis/internal/jsonschema.py +4 -3
- schemathesis/internal/transformation.py +3 -2
- schemathesis/lazy.py +39 -31
- schemathesis/loaders.py +8 -8
- schemathesis/models.py +128 -126
- schemathesis/parameters.py +6 -5
- schemathesis/runner/__init__.py +107 -81
- schemathesis/runner/events.py +37 -26
- schemathesis/runner/impl/core.py +86 -81
- schemathesis/runner/impl/solo.py +19 -15
- schemathesis/runner/impl/threadpool.py +40 -22
- schemathesis/runner/serialization.py +67 -40
- schemathesis/sanitization.py +18 -20
- schemathesis/schemas.py +83 -72
- schemathesis/serializers.py +39 -30
- schemathesis/service/ci.py +20 -21
- schemathesis/service/client.py +29 -9
- schemathesis/service/constants.py +1 -0
- schemathesis/service/events.py +2 -2
- schemathesis/service/hosts.py +8 -7
- schemathesis/service/metadata.py +5 -0
- schemathesis/service/models.py +22 -4
- schemathesis/service/report.py +15 -15
- schemathesis/service/serialization.py +23 -27
- schemathesis/service/usage.py +8 -7
- schemathesis/specs/graphql/loaders.py +31 -24
- schemathesis/specs/graphql/nodes.py +3 -2
- schemathesis/specs/graphql/scalars.py +26 -2
- schemathesis/specs/graphql/schemas.py +38 -34
- schemathesis/specs/openapi/_hypothesis.py +62 -44
- schemathesis/specs/openapi/checks.py +10 -10
- schemathesis/specs/openapi/converter.py +10 -9
- schemathesis/specs/openapi/definitions.py +2 -2
- schemathesis/specs/openapi/examples.py +22 -21
- schemathesis/specs/openapi/expressions/nodes.py +5 -4
- schemathesis/specs/openapi/expressions/parser.py +7 -6
- schemathesis/specs/openapi/filters.py +6 -6
- schemathesis/specs/openapi/formats.py +2 -2
- schemathesis/specs/openapi/links.py +19 -21
- schemathesis/specs/openapi/loaders.py +133 -78
- schemathesis/specs/openapi/negative/__init__.py +16 -11
- schemathesis/specs/openapi/negative/mutations.py +11 -10
- schemathesis/specs/openapi/parameters.py +20 -19
- schemathesis/specs/openapi/references.py +21 -20
- schemathesis/specs/openapi/schemas.py +97 -84
- schemathesis/specs/openapi/security.py +25 -24
- schemathesis/specs/openapi/serialization.py +20 -23
- schemathesis/specs/openapi/stateful/__init__.py +12 -11
- schemathesis/specs/openapi/stateful/links.py +7 -7
- schemathesis/specs/openapi/utils.py +4 -3
- schemathesis/specs/openapi/validation.py +3 -2
- schemathesis/stateful/__init__.py +15 -16
- schemathesis/stateful/state_machine.py +9 -9
- schemathesis/targets.py +3 -3
- schemathesis/throttling.py +2 -2
- schemathesis/transports/auth.py +2 -2
- schemathesis/transports/content_types.py +5 -0
- schemathesis/transports/headers.py +3 -2
- schemathesis/transports/responses.py +1 -1
- schemathesis/utils.py +7 -10
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/METADATA +12 -13
- schemathesis-3.22.1.dist-info/RECORD +130 -0
- schemathesis-3.21.2.dist-info/RECORD +0 -130
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/WHEEL +0 -0
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
from contextlib import suppress
|
|
2
3
|
from functools import lru_cache
|
|
3
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, Generator
|
|
4
5
|
|
|
5
6
|
import requests
|
|
6
7
|
from hypothesis.strategies import SearchStrategy
|
|
@@ -11,7 +12,7 @@ from ._hypothesis import PARAMETERS, get_case_strategy
|
|
|
11
12
|
from .constants import LOCATION_TO_CONTAINER
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
def get_object_example_from_properties(object_schema:
|
|
15
|
+
def get_object_example_from_properties(object_schema: dict[str, Any]) -> dict[str, Any]:
|
|
15
16
|
return {
|
|
16
17
|
prop_name: prop["example"]
|
|
17
18
|
for prop_name, prop in object_schema.get("properties", {}).items()
|
|
@@ -19,7 +20,7 @@ def get_object_example_from_properties(object_schema: Dict[str, Any]) -> Dict[st
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
@lru_cache
|
|
23
|
+
@lru_cache
|
|
23
24
|
def load_external_example(url: str) -> bytes:
|
|
24
25
|
"""Load examples the `externalValue` keyword."""
|
|
25
26
|
response = requests.get(url, timeout=DEFAULT_RESPONSE_TIMEOUT / 1000)
|
|
@@ -27,7 +28,7 @@ def load_external_example(url: str) -> bytes:
|
|
|
27
28
|
return response.content
|
|
28
29
|
|
|
29
30
|
|
|
30
|
-
def get_examples(examples:
|
|
31
|
+
def get_examples(examples: dict[str, Any]) -> Generator[Any, None, None]:
|
|
31
32
|
for example in examples.values():
|
|
32
33
|
# IDEA: report when it is not a dictionary
|
|
33
34
|
if isinstance(example, dict):
|
|
@@ -39,7 +40,7 @@ def get_examples(examples: Dict[str, Any]) -> Generator[Any, None, None]:
|
|
|
39
40
|
yield load_external_example(example["externalValue"])
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
def get_parameter_examples(operation_definition:
|
|
43
|
+
def get_parameter_examples(operation_definition: dict[str, Any], examples_field: str) -> list[dict[str, Any]]:
|
|
43
44
|
"""Gets parameter examples from OAS3 `examples` keyword or `x-examples` for Swagger 2."""
|
|
44
45
|
return [
|
|
45
46
|
{
|
|
@@ -52,8 +53,8 @@ def get_parameter_examples(operation_definition: Dict[str, Any], examples_field:
|
|
|
52
53
|
]
|
|
53
54
|
|
|
54
55
|
|
|
55
|
-
def get_parameter_example_from_properties(operation_definition:
|
|
56
|
-
static_parameters:
|
|
56
|
+
def get_parameter_example_from_properties(operation_definition: dict[str, Any]) -> dict[str, Any]:
|
|
57
|
+
static_parameters: dict[str, Any] = {}
|
|
57
58
|
for parameter in operation_definition.get("parameters", []):
|
|
58
59
|
parameter_schema = parameter["schema"] if "schema" in parameter else parameter
|
|
59
60
|
example = get_object_example_from_properties(parameter_schema)
|
|
@@ -69,7 +70,7 @@ def get_parameter_example_from_properties(operation_definition: Dict[str, Any])
|
|
|
69
70
|
return static_parameters
|
|
70
71
|
|
|
71
72
|
|
|
72
|
-
def get_request_body_examples(operation_definition:
|
|
73
|
+
def get_request_body_examples(operation_definition: dict[str, Any], examples_field: str) -> dict[str, Any]:
|
|
73
74
|
"""Gets request body examples from OAS3 `examples` keyword or `x-examples` for Swagger 2."""
|
|
74
75
|
# NOTE. `requestBody` is OAS3-specific. How should it work with OAS2?
|
|
75
76
|
request_bodies_items = operation_definition.get("requestBody", {}).get("content", {}).items()
|
|
@@ -84,8 +85,8 @@ def get_request_body_examples(operation_definition: Dict[str, Any], examples_fie
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
|
|
87
|
-
def get_request_body_example_from_properties(operation_definition:
|
|
88
|
-
static_parameters:
|
|
88
|
+
def get_request_body_example_from_properties(operation_definition: dict[str, Any]) -> dict[str, Any]:
|
|
89
|
+
static_parameters: dict[str, Any] = {}
|
|
89
90
|
request_bodies_items = operation_definition.get("requestBody", {}).get("content", {}).items()
|
|
90
91
|
if request_bodies_items:
|
|
91
92
|
_, request_body_schema = next(iter(request_bodies_items))
|
|
@@ -96,7 +97,7 @@ def get_request_body_example_from_properties(operation_definition: Dict[str, Any
|
|
|
96
97
|
return static_parameters
|
|
97
98
|
|
|
98
99
|
|
|
99
|
-
def get_static_parameters_from_example(operation: APIOperation) ->
|
|
100
|
+
def get_static_parameters_from_example(operation: APIOperation) -> dict[str, Any]:
|
|
100
101
|
static_parameters = {}
|
|
101
102
|
for name in PARAMETERS:
|
|
102
103
|
parameters = getattr(operation, name)
|
|
@@ -106,7 +107,7 @@ def get_static_parameters_from_example(operation: APIOperation) -> Dict[str, Any
|
|
|
106
107
|
return static_parameters
|
|
107
108
|
|
|
108
109
|
|
|
109
|
-
def get_static_parameters_from_examples(operation: APIOperation, examples_field: str) ->
|
|
110
|
+
def get_static_parameters_from_examples(operation: APIOperation, examples_field: str) -> list[dict[str, Any]]:
|
|
110
111
|
"""Get static parameters from OpenAPI examples keyword."""
|
|
111
112
|
operation_definition = operation.definition.resolved
|
|
112
113
|
return merge_examples(
|
|
@@ -115,7 +116,7 @@ def get_static_parameters_from_examples(operation: APIOperation, examples_field:
|
|
|
115
116
|
)
|
|
116
117
|
|
|
117
118
|
|
|
118
|
-
def get_static_parameters_from_properties(operation: APIOperation) ->
|
|
119
|
+
def get_static_parameters_from_properties(operation: APIOperation) -> dict[str, Any]:
|
|
119
120
|
operation_definition = operation.definition.resolved
|
|
120
121
|
return {
|
|
121
122
|
**get_parameter_example_from_properties(operation_definition),
|
|
@@ -125,7 +126,7 @@ def get_static_parameters_from_properties(operation: APIOperation) -> Dict[str,
|
|
|
125
126
|
|
|
126
127
|
def get_strategies_from_examples(
|
|
127
128
|
operation: APIOperation, examples_field: str = "examples"
|
|
128
|
-
) ->
|
|
129
|
+
) -> list[SearchStrategy[Case]]:
|
|
129
130
|
maps = {}
|
|
130
131
|
for location, container in LOCATION_TO_CONTAINER.items():
|
|
131
132
|
serializer = operation.get_parameter_serializer(location)
|
|
@@ -155,12 +156,12 @@ def get_strategies_from_examples(
|
|
|
155
156
|
|
|
156
157
|
|
|
157
158
|
def merge_examples(
|
|
158
|
-
parameter_examples:
|
|
159
|
-
) ->
|
|
159
|
+
parameter_examples: list[dict[str, Any]], request_body_examples: dict[str, Any]
|
|
160
|
+
) -> list[dict[str, Any]]:
|
|
160
161
|
"""Create list of static parameter objects from the parameter and request body examples."""
|
|
161
162
|
static_parameter_list = []
|
|
162
163
|
for idx in range(num_examples(parameter_examples, request_body_examples)):
|
|
163
|
-
static_parameters:
|
|
164
|
+
static_parameters: dict[str, Any] = {}
|
|
164
165
|
for parameter in parameter_examples:
|
|
165
166
|
container = static_parameters.setdefault(parameter["type"], {})
|
|
166
167
|
container[parameter["name"]] = parameter["examples"][min(idx, len(parameter["examples"]) - 1)]
|
|
@@ -172,18 +173,18 @@ def merge_examples(
|
|
|
172
173
|
return static_parameter_list
|
|
173
174
|
|
|
174
175
|
|
|
175
|
-
def static_parameters_union(sp_1:
|
|
176
|
+
def static_parameters_union(sp_1: dict[str, Any], sp_2: dict[str, Any]) -> list[dict[str, Any]]:
|
|
176
177
|
"""Fill missing parameters in each static parameter dict with parameters provided in the other dict."""
|
|
177
178
|
full_static_parameters = (_static_parameters_union(sp_1, sp_2), _static_parameters_union(sp_2, sp_1))
|
|
178
179
|
return [static_parameter for static_parameter in full_static_parameters if static_parameter]
|
|
179
180
|
|
|
180
181
|
|
|
181
|
-
def _static_parameters_union(base_obj:
|
|
182
|
+
def _static_parameters_union(base_obj: dict[str, Any], fill_obj: dict[str, Any]) -> dict[str, Any]:
|
|
182
183
|
"""Fill base_obj with parameter examples in fill_obj that were not in base_obj."""
|
|
183
184
|
if not base_obj:
|
|
184
185
|
return {}
|
|
185
186
|
|
|
186
|
-
full_static_parameters:
|
|
187
|
+
full_static_parameters: dict[str, Any] = {**base_obj}
|
|
187
188
|
|
|
188
189
|
for parameter_type, examples in fill_obj.items():
|
|
189
190
|
if parameter_type not in full_static_parameters:
|
|
@@ -197,7 +198,7 @@ def _static_parameters_union(base_obj: Dict[str, Any], fill_obj: Dict[str, Any])
|
|
|
197
198
|
return full_static_parameters
|
|
198
199
|
|
|
199
200
|
|
|
200
|
-
def num_examples(parameter_examples:
|
|
201
|
+
def num_examples(parameter_examples: list[dict[str, Any]], request_body_examples: dict[str, Any]) -> int:
|
|
201
202
|
max_parameter_examples = (
|
|
202
203
|
max(len(parameter["examples"]) for parameter in parameter_examples) if parameter_examples else 0
|
|
203
204
|
)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Expression nodes description and evaluation logic."""
|
|
2
|
+
from __future__ import annotations
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
from enum import Enum, unique
|
|
4
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
7
|
from requests.structures import CaseInsensitiveDict
|
|
7
8
|
|
|
@@ -74,7 +75,7 @@ class NonBodyRequest(Node):
|
|
|
74
75
|
parameter: str
|
|
75
76
|
|
|
76
77
|
def evaluate(self, context: ExpressionContext) -> str:
|
|
77
|
-
container:
|
|
78
|
+
container: dict | CaseInsensitiveDict = {
|
|
78
79
|
"query": context.case.query,
|
|
79
80
|
"path": context.case.path_parameters,
|
|
80
81
|
"header": context.case.headers,
|
|
@@ -88,7 +89,7 @@ class NonBodyRequest(Node):
|
|
|
88
89
|
class BodyRequest(Node):
|
|
89
90
|
"""A node for `$request` expressions where location is `body`."""
|
|
90
91
|
|
|
91
|
-
pointer:
|
|
92
|
+
pointer: str | None = None
|
|
92
93
|
|
|
93
94
|
def evaluate(self, context: ExpressionContext) -> Any:
|
|
94
95
|
document = context.case.body
|
|
@@ -111,7 +112,7 @@ class HeaderResponse(Node):
|
|
|
111
112
|
class BodyResponse(Node):
|
|
112
113
|
"""A node for `$response.body` expressions."""
|
|
113
114
|
|
|
114
|
-
pointer:
|
|
115
|
+
pointer: str | None = None
|
|
115
116
|
|
|
116
117
|
def evaluate(self, context: ExpressionContext) -> Any:
|
|
117
118
|
from ....transports.responses import WSGIResponse
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
from functools import lru_cache
|
|
2
|
-
from typing import Generator
|
|
3
|
+
from typing import Generator
|
|
3
4
|
|
|
4
5
|
from . import lexer, nodes
|
|
5
6
|
from .errors import RuntimeExpressionError, UnknownToken
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
@lru_cache
|
|
9
|
-
def parse(expr: str) ->
|
|
9
|
+
@lru_cache
|
|
10
|
+
def parse(expr: str) -> list[nodes.Node]:
|
|
10
11
|
"""Parse lexical tokens into concrete expression nodes."""
|
|
11
12
|
return list(_parse(expr))
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
def _parse(expr: str) -> Generator[nodes.Node, None, None]:
|
|
15
16
|
tokens = lexer.tokenize(expr)
|
|
16
|
-
brackets_stack:
|
|
17
|
+
brackets_stack: list[str] = []
|
|
17
18
|
for token in tokens:
|
|
18
19
|
if token.is_string or token.is_dot:
|
|
19
20
|
yield nodes.String(token.value)
|
|
@@ -46,7 +47,7 @@ def _parse_variable(tokens: lexer.TokenGenerator, token: lexer.Token, expr: str)
|
|
|
46
47
|
raise UnknownToken(token.value)
|
|
47
48
|
|
|
48
49
|
|
|
49
|
-
def _parse_request(tokens: lexer.TokenGenerator, expr: str) ->
|
|
50
|
+
def _parse_request(tokens: lexer.TokenGenerator, expr: str) -> nodes.BodyRequest | nodes.NonBodyRequest:
|
|
50
51
|
skip_dot(tokens, "$request")
|
|
51
52
|
location = next(tokens)
|
|
52
53
|
if location.value in ("query", "path", "header"):
|
|
@@ -63,7 +64,7 @@ def _parse_request(tokens: lexer.TokenGenerator, expr: str) -> Union[nodes.BodyR
|
|
|
63
64
|
raise RuntimeExpressionError(f"Invalid expression: {expr}")
|
|
64
65
|
|
|
65
66
|
|
|
66
|
-
def _parse_response(tokens: lexer.TokenGenerator, expr: str) ->
|
|
67
|
+
def _parse_response(tokens: lexer.TokenGenerator, expr: str) -> nodes.HeaderResponse | nodes.BodyResponse:
|
|
67
68
|
skip_dot(tokens, "$response")
|
|
68
69
|
location = next(tokens)
|
|
69
70
|
if location.value == "header":
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
import re
|
|
2
|
-
from typing import List, Optional, Union, Set, Tuple
|
|
3
3
|
|
|
4
4
|
from ...types import Filter
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def should_skip_method(method: str, pattern:
|
|
7
|
+
def should_skip_method(method: str, pattern: Filter | None) -> bool:
|
|
8
8
|
if pattern is None:
|
|
9
9
|
return False
|
|
10
10
|
patterns = _ensure_tuple(pattern)
|
|
11
11
|
return method.upper() not in map(str.upper, patterns)
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def should_skip_endpoint(endpoint: str, pattern:
|
|
14
|
+
def should_skip_endpoint(endpoint: str, pattern: Filter | None) -> bool:
|
|
15
15
|
if pattern is None:
|
|
16
16
|
return False
|
|
17
17
|
return not _match_any_pattern(endpoint, pattern)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def should_skip_by_tag(tags:
|
|
20
|
+
def should_skip_by_tag(tags: list[str] | None, pattern: Filter | None) -> bool:
|
|
21
21
|
if pattern is None:
|
|
22
22
|
return False
|
|
23
23
|
if not tags:
|
|
@@ -26,7 +26,7 @@ def should_skip_by_tag(tags: Optional[List[str]], pattern: Optional[Filter]) ->
|
|
|
26
26
|
return not any(re.search(item, tag) for item in patterns for tag in tags)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
def should_skip_by_operation_id(operation_id:
|
|
29
|
+
def should_skip_by_operation_id(operation_id: str | None, pattern: Filter | None) -> bool:
|
|
30
30
|
if pattern is None:
|
|
31
31
|
return False
|
|
32
32
|
if not operation_id:
|
|
@@ -43,7 +43,7 @@ def _match_any_pattern(target: str, pattern: Filter) -> bool:
|
|
|
43
43
|
return any(re.search(item, target) for item in patterns)
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
def _ensure_tuple(item: Filter) ->
|
|
46
|
+
def _ensure_tuple(item: Filter) -> list | set | tuple:
|
|
47
47
|
if not isinstance(item, (list, set, tuple)):
|
|
48
48
|
return (item,)
|
|
49
49
|
return item
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
6
|
from hypothesis import strategies as st
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
STRING_FORMATS:
|
|
9
|
+
STRING_FORMATS: dict[str, st.SearchStrategy] = {}
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def register_string_format(name: str, strategy: st.SearchStrategy) -> None:
|
|
@@ -5,7 +5,7 @@ Based on https://swagger.io/docs/specification/links/
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
7
|
from difflib import get_close_matches
|
|
8
|
-
from typing import Any,
|
|
8
|
+
from typing import Any, Generator, NoReturn, Sequence, Union, TYPE_CHECKING
|
|
9
9
|
|
|
10
10
|
from ...models import APIOperation, Case
|
|
11
11
|
from ...parameters import ParameterSet
|
|
@@ -27,7 +27,7 @@ if TYPE_CHECKING:
|
|
|
27
27
|
@dataclass(repr=False)
|
|
28
28
|
class Link(StatefulTest):
|
|
29
29
|
operation: APIOperation
|
|
30
|
-
parameters:
|
|
30
|
+
parameters: dict[str, Any]
|
|
31
31
|
request_body: Any = NOT_SET
|
|
32
32
|
|
|
33
33
|
def __post_init__(self) -> None:
|
|
@@ -38,9 +38,7 @@ class Link(StatefulTest):
|
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
@classmethod
|
|
41
|
-
def from_definition(
|
|
42
|
-
cls, name: str, definition: Dict[str, Dict[str, Any]], source_operation: APIOperation
|
|
43
|
-
) -> "Link":
|
|
41
|
+
def from_definition(cls, name: str, definition: dict[str, dict[str, Any]], source_operation: APIOperation) -> Link:
|
|
44
42
|
# Links can be behind a reference
|
|
45
43
|
_, definition = source_operation.schema.resolver.resolve_in_scope( # type: ignore
|
|
46
44
|
definition, source_operation.definition.scope
|
|
@@ -73,7 +71,7 @@ class Link(StatefulTest):
|
|
|
73
71
|
body=expressions.evaluate(self.request_body, context),
|
|
74
72
|
)
|
|
75
73
|
|
|
76
|
-
def make_operation(self, collected:
|
|
74
|
+
def make_operation(self, collected: list[ParsedData]) -> APIOperation:
|
|
77
75
|
"""Create a modified version of the original API operation with additional data merged in."""
|
|
78
76
|
# We split the gathered data among all locations & store the original parameter
|
|
79
77
|
containers = {
|
|
@@ -91,7 +89,7 @@ class Link(StatefulTest):
|
|
|
91
89
|
if "body" in containers["body"] and item.body is not NOT_SET:
|
|
92
90
|
containers["body"]["body"]["options"].append(item.body)
|
|
93
91
|
# These are the final `path_parameters`, `query`, and other API operation components
|
|
94
|
-
components:
|
|
92
|
+
components: dict[str, ParameterSet] = {
|
|
95
93
|
container_name: getattr(self.operation, container_name).__class__()
|
|
96
94
|
for location, container_name in LOCATION_TO_CONTAINER.items()
|
|
97
95
|
}
|
|
@@ -123,9 +121,9 @@ class Link(StatefulTest):
|
|
|
123
121
|
components[LOCATION_TO_CONTAINER[location]].add(parameter)
|
|
124
122
|
return self.operation.clone(**components)
|
|
125
123
|
|
|
126
|
-
def _get_container_by_parameter_name(self, full_name: str, templates:
|
|
124
|
+
def _get_container_by_parameter_name(self, full_name: str, templates: dict[str, dict[str, dict[str, Any]]]) -> list:
|
|
127
125
|
"""Detect in what request part the parameters is defined."""
|
|
128
|
-
location:
|
|
126
|
+
location: str | None
|
|
129
127
|
try:
|
|
130
128
|
# The parameter name is prefixed with its location. Example: `path.id`
|
|
131
129
|
location, name = full_name.split(".")
|
|
@@ -174,10 +172,10 @@ class OpenAPILink(Direction):
|
|
|
174
172
|
|
|
175
173
|
name: str
|
|
176
174
|
status_code: str
|
|
177
|
-
definition:
|
|
175
|
+
definition: dict[str, Any]
|
|
178
176
|
operation: APIOperation
|
|
179
|
-
parameters:
|
|
180
|
-
body:
|
|
177
|
+
parameters: list[tuple[str | None, str, str]] = field(init=False)
|
|
178
|
+
body: dict[str, Any] | NotSet = field(init=False)
|
|
181
179
|
|
|
182
180
|
def __post_init__(self) -> None:
|
|
183
181
|
self.parameters = [
|
|
@@ -218,7 +216,7 @@ class OpenAPILink(Direction):
|
|
|
218
216
|
return self.operation.schema.get_operation_by_reference(self.definition["operationRef"]) # type: ignore
|
|
219
217
|
|
|
220
218
|
|
|
221
|
-
def get_container(case: Case, location:
|
|
219
|
+
def get_container(case: Case, location: str | None, name: str) -> dict[str, Any] | None:
|
|
222
220
|
"""Get a container that suppose to store the given parameter."""
|
|
223
221
|
if location:
|
|
224
222
|
container_name = LOCATION_TO_CONTAINER[location]
|
|
@@ -232,7 +230,7 @@ def get_container(case: Case, location: Optional[str], name: str) -> Optional[Di
|
|
|
232
230
|
return getattr(case, container_name)
|
|
233
231
|
|
|
234
232
|
|
|
235
|
-
def normalize_parameter(parameter: str, expression: str) ->
|
|
233
|
+
def normalize_parameter(parameter: str, expression: str) -> tuple[str | None, str, str]:
|
|
236
234
|
"""Normalize runtime expressions.
|
|
237
235
|
|
|
238
236
|
Runtime expressions may have parameter names prefixed with their location - `path.id`.
|
|
@@ -247,7 +245,7 @@ def normalize_parameter(parameter: str, expression: str) -> Tuple[Optional[str],
|
|
|
247
245
|
return None, parameter, expression
|
|
248
246
|
|
|
249
247
|
|
|
250
|
-
def get_all_links(operation: APIOperation) -> Generator[
|
|
248
|
+
def get_all_links(operation: APIOperation) -> Generator[tuple[str, OpenAPILink], None, None]:
|
|
251
249
|
for status_code, definition in operation.definition.resolved["responses"].items():
|
|
252
250
|
for name, link_definition in definition.get(operation.schema.links_field, {}).items(): # type: ignore
|
|
253
251
|
yield status_code, OpenAPILink(name, status_code, link_definition, operation)
|
|
@@ -256,7 +254,7 @@ def get_all_links(operation: APIOperation) -> Generator[Tuple[str, OpenAPILink],
|
|
|
256
254
|
StatusCode = Union[str, int]
|
|
257
255
|
|
|
258
256
|
|
|
259
|
-
def _get_response_by_status_code(responses:
|
|
257
|
+
def _get_response_by_status_code(responses: dict[StatusCode, dict[str, Any]], status_code: str | int) -> dict:
|
|
260
258
|
if isinstance(status_code, int):
|
|
261
259
|
# Invalid schemas may contain status codes as integers
|
|
262
260
|
if status_code in responses:
|
|
@@ -275,17 +273,17 @@ def _get_response_by_status_code(responses: Dict[StatusCode, Dict[str, Any]], st
|
|
|
275
273
|
|
|
276
274
|
|
|
277
275
|
def add_link(
|
|
278
|
-
responses:
|
|
276
|
+
responses: dict[StatusCode, dict[str, Any]],
|
|
279
277
|
links_field: str,
|
|
280
|
-
parameters:
|
|
278
|
+
parameters: dict[str, str] | None,
|
|
281
279
|
request_body: Any,
|
|
282
280
|
status_code: StatusCode,
|
|
283
|
-
target:
|
|
284
|
-
name:
|
|
281
|
+
target: str | APIOperation,
|
|
282
|
+
name: str | None = None,
|
|
285
283
|
) -> None:
|
|
286
284
|
response = _get_response_by_status_code(responses, status_code)
|
|
287
285
|
links_definition = response.setdefault(links_field, {})
|
|
288
|
-
new_link:
|
|
286
|
+
new_link: dict[str, str | dict[str, str]] = {}
|
|
289
287
|
if parameters is not None:
|
|
290
288
|
new_link["parameters"] = parameters
|
|
291
289
|
if request_body is not None:
|