schemathesis 4.0.0a2__py3-none-any.whl → 4.0.0a3__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.
@@ -66,8 +66,8 @@ from .stateful import create_state_machine
66
66
  if TYPE_CHECKING:
67
67
  from hypothesis.strategies import SearchStrategy
68
68
 
69
- from ...auths import AuthStorage
70
- from ...stateful.state_machine import APIStateMachine
69
+ from schemathesis.auths import AuthStorage
70
+ from schemathesis.generation.stateful import APIStateMachine
71
71
 
72
72
  HTTP_METHODS = frozenset({"get", "put", "post", "delete", "options", "head", "patch", "trace"})
73
73
  SCHEMA_ERROR_MESSAGE = "Ensure that the definition complies with the OpenAPI specification"
@@ -174,30 +174,64 @@ class BaseOpenAPISchema(BaseSchema):
174
174
  return statistic
175
175
 
176
176
  resolve = self.resolver.resolve
177
+ resolve_path_item = self._resolve_path_item
177
178
  should_skip = self._should_skip
178
179
  links_field = self.links_field
179
180
 
181
+ # For operationId lookup
182
+ selected_operations_by_id: set[str] = set()
183
+ # Tuples of (method, path)
184
+ selected_operations_by_path: set[tuple[str, str]] = set()
185
+ collected_links: list[dict] = []
186
+
180
187
  for path, path_item in paths.items():
181
188
  try:
182
- if "$ref" in path_item:
183
- _, path_item = resolve(path_item["$ref"])
184
- for method, definition in path_item.items():
185
- if method not in HTTP_METHODS:
186
- continue
187
- statistic.operations.total += 1
188
- is_selected = not should_skip(path, method, definition)
189
- if is_selected:
190
- statistic.operations.selected += 1
191
- for response in definition.get("responses", {}).values():
192
- if "$ref" in response:
193
- _, response = resolve(response["$ref"])
194
- defined_links = response.get(links_field)
195
- if defined_links is not None:
196
- statistic.links.total += len(defined_links)
197
- if is_selected:
198
- statistic.links.selected = len(defined_links)
189
+ scope, path_item = resolve_path_item(path_item)
190
+ self.resolver.push_scope(scope)
191
+ try:
192
+ for method, definition in path_item.items():
193
+ if method not in HTTP_METHODS:
194
+ continue
195
+ statistic.operations.total += 1
196
+ is_selected = not should_skip(path, method, definition)
197
+ if is_selected:
198
+ statistic.operations.selected += 1
199
+ # Store both identifiers
200
+ if "operationId" in definition:
201
+ selected_operations_by_id.add(definition["operationId"])
202
+ selected_operations_by_path.add((method, path))
203
+ for response in definition.get("responses", {}).values():
204
+ if "$ref" in response:
205
+ _, response = resolve(response["$ref"])
206
+ defined_links = response.get(links_field)
207
+ if defined_links is not None:
208
+ statistic.links.total += len(defined_links)
209
+ if is_selected:
210
+ collected_links.extend(defined_links.values())
211
+ finally:
212
+ self.resolver.pop_scope()
199
213
  except SCHEMA_PARSING_ERRORS:
200
214
  continue
215
+
216
+ def is_link_selected(link: dict) -> bool:
217
+ if "$ref" in link:
218
+ _, link = resolve(link["$ref"])
219
+
220
+ if "operationId" in link:
221
+ return link["operationId"] in selected_operations_by_id
222
+ else:
223
+ try:
224
+ scope, _ = resolve(link["operationRef"])
225
+ path, method = scope.rsplit("/", maxsplit=2)[-2:]
226
+ path = path.replace("~1", "/").replace("~0", "~")
227
+ return (method, path) in selected_operations_by_path
228
+ except Exception:
229
+ return False
230
+
231
+ for link in collected_links:
232
+ if is_link_selected(link):
233
+ statistic.links.selected += 1
234
+
201
235
  return statistic
202
236
 
203
237
  def _operation_iter(self) -> Generator[dict[str, Any], None, None]:
@@ -569,13 +603,7 @@ class BaseOpenAPISchema(BaseSchema):
569
603
  return scopes, definitions.get("headers")
570
604
 
571
605
  def as_state_machine(self) -> type[APIStateMachine]:
572
- try:
573
- return create_state_machine(self)
574
- except OperationNotFound as exc:
575
- raise LoaderError(
576
- kind=LoaderErrorKind.OPEN_API_INVALID_SCHEMA,
577
- message=f"Invalid Open API link definition: Operation `{exc.item}` not found",
578
- ) from exc
606
+ return create_state_machine(self)
579
607
 
580
608
  def add_link(
581
609
  self,
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections import defaultdict
3
+ from dataclasses import dataclass
4
4
  from functools import lru_cache
5
5
  from typing import TYPE_CHECKING, Any, Callable, Iterator
6
6
 
@@ -8,25 +8,31 @@ from hypothesis import strategies as st
8
8
  from hypothesis.stateful import Bundle, Rule, precondition, rule
9
9
 
10
10
  from schemathesis.core.result import Ok
11
+ from schemathesis.engine.recorder import ScenarioRecorder
12
+ from schemathesis.generation import GenerationMode
11
13
  from schemathesis.generation.case import Case
12
14
  from schemathesis.generation.hypothesis import strategies
13
15
  from schemathesis.generation.stateful.state_machine import APIStateMachine, StepInput, StepOutput, _normalize_name
14
16
  from schemathesis.schemas import APIOperation
15
-
16
- from ....generation import GenerationMode
17
- from ..links import OpenApiLink, get_all_links
18
- from ..utils import expand_status_code
17
+ from schemathesis.specs.openapi.links import OpenApiLink, get_all_links
18
+ from schemathesis.specs.openapi.stateful.control import TransitionController
19
+ from schemathesis.specs.openapi.utils import expand_status_code
19
20
 
20
21
  if TYPE_CHECKING:
21
22
  from schemathesis.generation.stateful.state_machine import StepOutput
22
-
23
- from ..schemas import BaseOpenAPISchema
23
+ from schemathesis.specs.openapi.schemas import BaseOpenAPISchema
24
24
 
25
25
  FilterFunction = Callable[["StepOutput"], bool]
26
26
 
27
27
 
28
28
  class OpenAPIStateMachine(APIStateMachine):
29
29
  _response_matchers: dict[str, Callable[[StepOutput], str | None]]
30
+ _transitions: ApiTransitions
31
+
32
+ def __init__(self) -> None:
33
+ self.recorder = ScenarioRecorder(label="Stateful tests")
34
+ self.control = TransitionController(self._transitions)
35
+ super().__init__()
30
36
 
31
37
  def _get_target_for_result(self, result: StepOutput) -> str | None:
32
38
  matcher = self._response_matchers.get(result.case.operation.label)
@@ -36,81 +42,118 @@ class OpenAPIStateMachine(APIStateMachine):
36
42
 
37
43
 
38
44
  # The proportion of negative tests generated for "root" transitions
39
- NEGATIVE_TEST_CASES_THRESHOLD = 20
45
+ NEGATIVE_TEST_CASES_THRESHOLD = 10
40
46
 
41
47
 
42
- def create_state_machine(schema: BaseOpenAPISchema) -> type[APIStateMachine]:
43
- """Create a state machine class.
48
+ @dataclass
49
+ class OperationTransitions:
50
+ """Transitions for a single operation."""
44
51
 
45
- It aims to avoid making calls that are not likely to lead to a stateful call later. For example:
46
- 1. POST /users/
47
- 2. GET /users/{id}/
52
+ __slots__ = ("incoming", "outgoing")
48
53
 
49
- This state machine won't make calls to (2) without having a proper response from (1) first.
50
- """
54
+ def __init__(self) -> None:
55
+ self.incoming: list[OpenApiLink] = []
56
+ self.outgoing: list[OpenApiLink] = []
57
+
58
+
59
+ @dataclass
60
+ class ApiTransitions:
61
+ """Stores all transitions grouped by operation."""
62
+
63
+ __slots__ = ("operations",)
64
+
65
+ def __init__(self) -> None:
66
+ # operation label -> its transitions
67
+ self.operations: dict[str, OperationTransitions] = {}
68
+
69
+ def add_outgoing(self, source: str, link: OpenApiLink) -> None:
70
+ """Record an outgoing transition from source operation."""
71
+ self.operations.setdefault(source, OperationTransitions()).outgoing.append(link)
72
+ self.operations.setdefault(link.target.label, OperationTransitions()).incoming.append(link)
73
+
74
+
75
+ def collect_transitions(operations: list[APIOperation]) -> ApiTransitions:
76
+ """Collect all transitions between operations."""
77
+ transitions = ApiTransitions()
78
+
79
+ selected_labels = {operation.label for operation in operations}
80
+ for operation in operations:
81
+ for _, link in get_all_links(operation):
82
+ if link.target.label in selected_labels:
83
+ transitions.add_outgoing(operation.label, link)
84
+
85
+ return transitions
86
+
87
+
88
+ def create_state_machine(schema: BaseOpenAPISchema) -> type[APIStateMachine]:
51
89
  operations = [result.ok() for result in schema.get_all_operations() if isinstance(result, Ok)]
52
90
  bundles = {}
53
- incoming_transitions = defaultdict(list)
91
+ transitions = collect_transitions(operations)
54
92
  _response_matchers: dict[str, Callable[[StepOutput], str | None]] = {}
55
- # Statistic structure follows the links and count for each response status code
93
+
94
+ # Create bundles and matchers
56
95
  for operation in operations:
57
96
  all_status_codes = tuple(operation.definition.raw["responses"])
58
97
  bundle_matchers = []
59
- for _, link in get_all_links(operation):
60
- bundle_name = f"{operation.label} -> {link.status_code}"
61
- bundles[bundle_name] = Bundle(bundle_name)
62
- incoming_transitions[link.target.label].append(link)
63
- bundle_matchers.append((bundle_name, make_response_filter(link.status_code, all_status_codes)))
98
+
99
+ if operation.label in transitions.operations:
100
+ # Use outgoing transitions
101
+ for link in transitions.operations[operation.label].outgoing:
102
+ bundle_name = f"{operation.label} -> {link.status_code}"
103
+ bundles[bundle_name] = Bundle(bundle_name)
104
+ bundle_matchers.append((bundle_name, make_response_filter(link.status_code, all_status_codes)))
105
+
64
106
  if bundle_matchers:
65
107
  _response_matchers[operation.label] = make_response_matcher(bundle_matchers)
108
+
66
109
  rules = {}
67
110
  catch_all = Bundle("catch_all")
68
111
 
69
112
  for target in operations:
70
- incoming = incoming_transitions.get(target.label)
71
- if incoming is not None:
72
- for link in incoming:
73
- bundle_name = f"{link.source.label} -> {link.status_code}"
74
- name = _normalize_name(f"{link.status_code} -> {target.label}")
75
- rules[name] = precondition(ensure_non_empty_bundle(bundle_name))(
76
- transition(
77
- name=name,
78
- target=catch_all,
79
- input=bundles[bundle_name].flatmap(
80
- into_step_input(target=target, link=link, modes=schema.generation_config.modes)
81
- ),
113
+ if target.label in transitions.operations:
114
+ incoming = transitions.operations[target.label].incoming
115
+ if incoming:
116
+ for link in incoming:
117
+ bundle_name = f"{link.source.label} -> {link.status_code}"
118
+ name = _normalize_name(f"{link.status_code} -> {target.label}")
119
+ name = _normalize_name(f"{link.source.label} -> {link.status_code} -> {target.label}")
120
+ assert name not in rules
121
+ rules[name] = precondition(is_transition_allowed(bundle_name, link.source.label, target.label))(
122
+ transition(
123
+ name=name,
124
+ target=catch_all,
125
+ input=bundles[bundle_name].flatmap(
126
+ into_step_input(target=target, link=link, modes=schema.generation_config.modes)
127
+ ),
128
+ )
82
129
  )
130
+ if transitions.operations[target.label].outgoing and target.method == "post":
131
+ # Allow POST methods for operations with outgoing transitions.
132
+ # This approach also includes cases when there is an incoming transition back to POST
133
+ # For example, POST /users/ -> GET /users/{id}/
134
+ # The source operation has no prerequisite, but we need to allow this rule to be executed
135
+ # in order to reach other transitions
136
+ name = _normalize_name(f"{target.label} -> X")
137
+ if len(schema.generation_config.modes) == 1:
138
+ case_strategy = target.as_strategy(generation_mode=schema.generation_config.modes[0])
139
+ else:
140
+ _strategies = {
141
+ method: target.as_strategy(generation_mode=method) for method in schema.generation_config.modes
142
+ }
143
+
144
+ @st.composite # type: ignore[misc]
145
+ def case_strategy_factory(
146
+ draw: st.DrawFn, strategies: dict[GenerationMode, st.SearchStrategy] = _strategies
147
+ ) -> Case:
148
+ if draw(st.integers(min_value=0, max_value=99)) < NEGATIVE_TEST_CASES_THRESHOLD:
149
+ return draw(strategies[GenerationMode.NEGATIVE])
150
+ return draw(strategies[GenerationMode.POSITIVE])
151
+
152
+ case_strategy = case_strategy_factory()
153
+
154
+ rules[name] = precondition(is_root_allowed(target.label))(
155
+ transition(name=name, target=catch_all, input=case_strategy.map(StepInput.initial))
83
156
  )
84
- elif any(
85
- incoming.source.label == target.label
86
- for transitions in incoming_transitions.values()
87
- for incoming in transitions
88
- ):
89
- # No incoming transitions, but has at least one outgoing transition
90
- # For example, POST /users/ -> GET /users/{id}/
91
- # The source operation has no prerequisite, but we need to allow this rule to be executed
92
- # in order to reach other transitions
93
- name = _normalize_name(f"{target.label} -> X")
94
- if len(schema.generation_config.modes) == 1:
95
- case_strategy = target.as_strategy(generation_mode=schema.generation_config.modes[0])
96
- else:
97
- _strategies = {
98
- method: target.as_strategy(generation_mode=method) for method in schema.generation_config.modes
99
- }
100
-
101
- @st.composite # type: ignore[misc]
102
- def case_strategy_factory(
103
- draw: st.DrawFn, strategies: dict[GenerationMode, st.SearchStrategy] = _strategies
104
- ) -> Case:
105
- if draw(st.integers(min_value=0, max_value=99)) < NEGATIVE_TEST_CASES_THRESHOLD:
106
- return draw(strategies[GenerationMode.NEGATIVE])
107
- return draw(strategies[GenerationMode.POSITIVE])
108
-
109
- case_strategy = case_strategy_factory()
110
-
111
- rules[name] = precondition(ensure_links_followed)(
112
- transition(name=name, target=catch_all, input=case_strategy.map(StepInput.initial))
113
- )
114
157
 
115
158
  return type(
116
159
  "APIWorkflow",
@@ -119,6 +162,7 @@ def create_state_machine(schema: BaseOpenAPISchema) -> type[APIStateMachine]:
119
162
  "schema": schema,
120
163
  "bundles": bundles,
121
164
  "_response_matchers": _response_matchers,
165
+ "_transitions": transitions,
122
166
  **rules,
123
167
  },
124
168
  )
@@ -165,23 +209,29 @@ def into_step_input(
165
209
  return builder
166
210
 
167
211
 
168
- def ensure_non_empty_bundle(bundle_name: str) -> Callable[[APIStateMachine], bool]:
169
- def inner(machine: APIStateMachine) -> bool:
170
- return bool(machine.bundles.get(bundle_name))
212
+ def is_transition_allowed(bundle_name: str, source: str, target: str) -> Callable[[OpenAPIStateMachine], bool]:
213
+ def inner(machine: OpenAPIStateMachine) -> bool:
214
+ return bool(machine.bundles.get(bundle_name)) and machine.control.allow_transition(source, target)
171
215
 
172
216
  return inner
173
217
 
174
218
 
175
- def ensure_links_followed(machine: APIStateMachine) -> bool:
176
- # If there are responses that have links to follow, reject any rule without incoming transitions
177
- for bundle in machine.bundles.values():
178
- if bundle:
179
- return False
180
- return True
219
+ def is_root_allowed(label: str) -> Callable[[OpenAPIStateMachine], bool]:
220
+ def inner(machine: OpenAPIStateMachine) -> bool:
221
+ return machine.control.allow_root_transition(label, machine.bundles)
222
+
223
+ return inner
181
224
 
182
225
 
183
226
  def transition(*, name: str, target: Bundle, input: st.SearchStrategy[StepInput]) -> Callable[[Callable], Rule]:
184
- def step_function(self: APIStateMachine, input: StepInput) -> StepOutput | None:
227
+ def step_function(self: OpenAPIStateMachine, input: StepInput) -> StepOutput | None:
228
+ if input.transition is not None:
229
+ self.recorder.record_case(
230
+ parent_id=input.transition.parent_id, transition=input.transition, case=input.case
231
+ )
232
+ else:
233
+ self.recorder.record_case(parent_id=None, transition=None, case=input.case)
234
+ self.control.record_step(input, self.recorder)
185
235
  return APIStateMachine._step(self, input=input)
186
236
 
187
237
  step_function.__name__ = name
@@ -0,0 +1,87 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import Counter
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING
6
+
7
+ from schemathesis.engine.recorder import ScenarioRecorder
8
+ from schemathesis.generation.stateful.state_machine import DEFAULT_STATEFUL_STEP_COUNT
9
+
10
+ if TYPE_CHECKING:
11
+ from requests.structures import CaseInsensitiveDict
12
+
13
+ from schemathesis.generation.stateful.state_machine import StepInput
14
+ from schemathesis.specs.openapi.stateful import ApiTransitions
15
+
16
+
17
+ # It is enough to be able to catch double-click type of issues
18
+ MAX_OPERATIONS_PER_SOURCE_CAP = 2
19
+ # Maximum number of concurrent root sources (e.g., active users in the system)
20
+ MAX_ROOT_SOURCES = 2
21
+
22
+
23
+ def _get_max_operations_per_source(transitions: ApiTransitions) -> int:
24
+ """Calculate global limit based on number of sources to maximize diversity of used API calls."""
25
+ sources = len(transitions.operations)
26
+
27
+ if sources == 0:
28
+ return MAX_OPERATIONS_PER_SOURCE_CAP
29
+
30
+ # Total steps divided by number of sources, but never below the cap
31
+ return max(MAX_OPERATIONS_PER_SOURCE_CAP, DEFAULT_STATEFUL_STEP_COUNT // sources)
32
+
33
+
34
+ @dataclass
35
+ class TransitionController:
36
+ """Controls which transitions can be executed in a state machine."""
37
+
38
+ __slots__ = ("transitions", "max_operations_per_source", "statistic")
39
+
40
+ def __init__(self, transitions: ApiTransitions) -> None:
41
+ # Incoming & outgoing transitions available in the state machine
42
+ self.transitions = transitions
43
+ self.max_operations_per_source = _get_max_operations_per_source(transitions)
44
+ # source -> derived API calls
45
+ self.statistic: dict[str, dict[str, Counter[str]]] = {}
46
+
47
+ def record_step(self, input: StepInput, recorder: ScenarioRecorder) -> None:
48
+ """Record API call input."""
49
+ case = input.case
50
+
51
+ if (
52
+ case.operation.label in self.transitions.operations
53
+ and self.transitions.operations[case.operation.label].outgoing
54
+ ):
55
+ # This API operation has outgoing transitions, hence record it as a source
56
+ entry = self.statistic.setdefault(input.case.operation.label, {})
57
+ entry[input.case.id] = Counter()
58
+
59
+ if input.transition is not None:
60
+ # Find immediate parent and record as derived operation
61
+ parent = recorder.cases[input.transition.parent_id]
62
+ source = parent.value.operation.label
63
+ case_id = parent.value.id
64
+
65
+ if source in self.statistic and case_id in self.statistic[source]:
66
+ self.statistic[source][case_id][case.operation.label] += 1
67
+
68
+ def allow_root_transition(self, source: str, bundles: dict[str, CaseInsensitiveDict]) -> bool:
69
+ """Decide if this root transition should be allowed now."""
70
+ if len(self.statistic.get(source, {})) < MAX_ROOT_SOURCES:
71
+ return True
72
+
73
+ # If all non-root operations are blocked, then allow root ones to make progress
74
+ history = {name.split("->")[0].strip() for name, values in bundles.items() if values}
75
+ return all(
76
+ incoming.source.label not in history
77
+ or not self.allow_transition(incoming.source.label, incoming.target.label)
78
+ for transitions in self.transitions.operations.values()
79
+ for incoming in transitions.incoming
80
+ if transitions.incoming
81
+ )
82
+
83
+ def allow_transition(self, source: str, target: str) -> bool:
84
+ """Decide if this transition should be allowed now."""
85
+ existing = self.statistic.get(source, {})
86
+ total = sum(metric.get(target, 0) for metric in existing.values())
87
+ return total < self.max_operations_per_source
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schemathesis
3
- Version: 4.0.0a2
3
+ Version: 4.0.0a3
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://schemathesis.readthedocs.io/en/stable/changelog.html
@@ -1,12 +1,12 @@
1
1
  schemathesis/__init__.py,sha256=ggp1CxctLo__wwFwlDhvtrexxDXGSbRjFKzXw_Twi7k,1139
2
2
  schemathesis/auths.py,sha256=t-YuPyoLqL7jlRUH-45JxO7Ir3pYxpe31CRmNIJh7rI,15423
3
3
  schemathesis/checks.py,sha256=B5-ROnjvvwpaqgj_iQ7eCjGqvRRVT30eWNPLKmwdrM8,5084
4
- schemathesis/errors.py,sha256=v5LpP5Yv78E96HoMJCsiKT_epDjkzLYMT0TtkarqomY,1336
4
+ schemathesis/errors.py,sha256=R_GkKg9o-XYWsU4f-Devn_clIrhMi5ZXpiAjqi5Tupw,791
5
5
  schemathesis/filters.py,sha256=6kffe_Xbi7-xThsUciWfmy1IU-LBgSYkXROUJJONJ48,13311
6
6
  schemathesis/hooks.py,sha256=jTdN5GJbxHRMshxgcuI_th9ouuL32CN4m2Jt0pmT_bs,13148
7
7
  schemathesis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  schemathesis/schemas.py,sha256=wQqKfZIaPgfAzLVUOxkgW7LqwBdWtLJeryeouytUlTw,27412
9
- schemathesis/cli/__init__.py,sha256=Fxsopap2Mw6mYh27l6xMRJ5PhUsCJ19pDxDj2hCc2wg,582
9
+ schemathesis/cli/__init__.py,sha256=cT8DmZYudre1a0cX2KOBytquJ85CMdmMnqqMZsIATJU,822
10
10
  schemathesis/cli/__main__.py,sha256=MWaenjaUTZIfNPFzKmnkTiawUri7DVldtg3mirLwzU8,92
11
11
  schemathesis/cli/constants.py,sha256=rUixnqorraUFDtOu3Nmm1x_k0qbgmW9xW96kQB_fBCQ,338
12
12
  schemathesis/cli/core.py,sha256=Qm5xvpIIMwJDTeR3N3TjKhMCHV5d5Rp0UstVS2GjWgw,459
@@ -15,8 +15,8 @@ schemathesis/cli/commands/__init__.py,sha256=FFalEss3D7mnCRO0udtYb65onXSjQCCOv8s
15
15
  schemathesis/cli/commands/run/__init__.py,sha256=mpXoFTAuyaBjTQiusLVfzU9BXwcNN84vcmll22bQ-18,22651
16
16
  schemathesis/cli/commands/run/checks.py,sha256=-p6bzuc98kNR1VhkTI2s4xaJx-b5zYSMwdlhONwUhRM,3337
17
17
  schemathesis/cli/commands/run/context.py,sha256=o_lUkR2bpsxO4oMB7eJVCMIR9jmfS0nLZCd_LdOluF4,3906
18
- schemathesis/cli/commands/run/events.py,sha256=rGR0Gm2Vw8YZGz7w2buj8Gck_UO9TDl1oSLLoEitDz8,960
19
- schemathesis/cli/commands/run/executor.py,sha256=vTqr4eq88d3Q0pIh14wgkO6B6N6r-uEP0hdUG7gemqg,4699
18
+ schemathesis/cli/commands/run/events.py,sha256=Dj-xvIr-Hkms8kvh4whNwKSk1Q2Hx4NIENi_4A8nQO8,1224
19
+ schemathesis/cli/commands/run/executor.py,sha256=pozPf5kzizsQtCzssE_5M8AV2lgzPcE3o9xxoOA1n78,4816
20
20
  schemathesis/cli/commands/run/filters.py,sha256=vBYH_Q1Gc2yjo4pysd1nSKfNVQADh5POFoAExvUPmqQ,7390
21
21
  schemathesis/cli/commands/run/hypothesis.py,sha256=3PkcUSe-P6zgkhPJbN8RstjGNzgcY76AVlJ7ayu44Eg,3582
22
22
  schemathesis/cli/commands/run/loaders.py,sha256=VedoeIE1tgFBqVokWxOoUReAjBl-Zhx87RjCEBtCVfs,4840
@@ -25,7 +25,7 @@ schemathesis/cli/commands/run/handlers/__init__.py,sha256=TPZ3KdGi8m0fjlN0GjA31M
25
25
  schemathesis/cli/commands/run/handlers/base.py,sha256=yDsTtCiztLksfk7cRzg8JlaAVOfS-zwK3tsJMOXAFyc,530
26
26
  schemathesis/cli/commands/run/handlers/cassettes.py,sha256=y4gtGnAfhE9dskhXPnDAQSx3kY4R2a1hvAiE3o8AsUA,19001
27
27
  schemathesis/cli/commands/run/handlers/junitxml.py,sha256=MkFq_DyMWves4Ytj72-4secMTlBOt7Gs9W3nfBzknIA,2316
28
- schemathesis/cli/commands/run/handlers/output.py,sha256=l6Q_UZk_Fd3EzLQHU2TgaXvcn9M-ASLfjT4Y9wx9PyU,50197
28
+ schemathesis/cli/commands/run/handlers/output.py,sha256=KlT_omUWRWzygvqz_nwRvXJXjtbmzTYhfWqPCLOQTBw,51917
29
29
  schemathesis/cli/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  schemathesis/cli/ext/fs.py,sha256=OA3mRzra4rq3NyDTcBvlRh0WJrh4ByN-QQ8loI04m88,408
31
31
  schemathesis/cli/ext/groups.py,sha256=9mIZUnQasUxGz6gZA0IoGVGKtSQuVNl8w2pwX_a1nBk,1796
@@ -38,7 +38,7 @@ schemathesis/core/compat.py,sha256=Lflo6z-nQ6S4uKZINc4Fr90pd3LTN6cIG9HJJmmaHeY,7
38
38
  schemathesis/core/control.py,sha256=IzwIc8HIAEMtZWW0Q0iXI7T1niBpjvcLlbuwOSmy5O8,130
39
39
  schemathesis/core/curl.py,sha256=yuaCe_zHLGwUjEeloQi6W3tOA3cGdnHDNI17-5jia0o,1723
40
40
  schemathesis/core/deserialization.py,sha256=ygIj4fNaOd0mJ2IvTsn6bsabBt_2AbSLCz-z9UqfpdQ,2406
41
- schemathesis/core/errors.py,sha256=Euy3YfF49dAH70KgMz4uFO5eRRX485RkT8v4go0TxOc,13423
41
+ schemathesis/core/errors.py,sha256=ocwWs8xtoiTtzGn8eCdCtb_Hrdn-jHi_d_TNl29UIkI,13656
42
42
  schemathesis/core/failures.py,sha256=4ftUsY0q1QqqfWpYkl5lWCZVBUAMy8VP9pXsyDZ8Q3o,8723
43
43
  schemathesis/core/fs.py,sha256=ItQT0_cVwjDdJX9IiI7EnU75NI2H3_DCEyyUjzg_BgI,472
44
44
  schemathesis/core/lazy_import.py,sha256=aMhWYgbU2JOltyWBb32vnWBb6kykOghucEzI_F70yVE,470
@@ -58,14 +58,14 @@ schemathesis/engine/__init__.py,sha256=xncZMXY8S-v4mrfnW4CK6-RQ0S0bigfLDJScpQysb
58
58
  schemathesis/engine/config.py,sha256=vWwtaWuSLvE-w0S9n4_MlJADjN58zlgSp84ZQS2z0ls,1919
59
59
  schemathesis/engine/context.py,sha256=HeLX-0aqSAbXJe_ZlkqVfg3QlhmbCrazbb9-ZPbi0h0,3723
60
60
  schemathesis/engine/control.py,sha256=QKUOs5VMphe7EcAIro_DDo9ZqdOU6ZVwTU1gMNndHWw,1006
61
- schemathesis/engine/core.py,sha256=Q7fkHlGJdMDTt4PBFp53rWJbyQdNOMgn1QIt2nI74KQ,5088
62
- schemathesis/engine/errors.py,sha256=nvl-2DKQSBAbas1MpEveUGLb_DL-LtMkcg9LijmctPM,16179
61
+ schemathesis/engine/core.py,sha256=_Z89tc_aWQ0kHajVXmoTaEbIRYeERvb1s0rD82XpYxg,5085
62
+ schemathesis/engine/errors.py,sha256=EtgqS-6ngtFg6TtloN_g07t3wYPMF6JOSesZ3C2VyXY,16576
63
63
  schemathesis/engine/events.py,sha256=7tpxCxptKx_WvcFgv9_6v0E-5wiQfDs1O5bsOH46xb8,5989
64
- schemathesis/engine/recorder.py,sha256=jmYhsk-RCIXvUyOqmSTL2shMBFepL58s0r1z2Ydg1YE,8428
64
+ schemathesis/engine/recorder.py,sha256=K3HfMARrT5mPWXPnYebjjcq5CcsBRhMrtZwEL9_Lvtg,8432
65
65
  schemathesis/engine/phases/__init__.py,sha256=HZmlOjGvtDkfTwAw2rJFcfsJ2qg2h973l4zDy3AzsQg,2034
66
66
  schemathesis/engine/phases/probes.py,sha256=3M9g3E7CXbDDK_8inuvkRZibCCcoO2Ce5U3lnyTeWXQ,5131
67
- schemathesis/engine/phases/stateful/__init__.py,sha256=_m0gx9ASfAKawAlHtI5hnWdMJs5QFUw1jd8VlxjETJo,2308
68
- schemathesis/engine/phases/stateful/_executor.py,sha256=V_nJJDaDSOEGH2hcoCyZH4xz_57Vqu8k4XpNd53znSg,12640
67
+ schemathesis/engine/phases/stateful/__init__.py,sha256=lWo2RLrutNblHvohTzofQqL22GORwBRA8bf6jvLuGPg,2391
68
+ schemathesis/engine/phases/stateful/_executor.py,sha256=oZSfgvGUmiyjVDGpTeZCRv8KLjesYXJeE3cs0OTkQ1E,12428
69
69
  schemathesis/engine/phases/stateful/context.py,sha256=SKWsok-tlWbUDagiUmP7cLNW6DsgFDc_Afv0vQfWv6c,2964
70
70
  schemathesis/engine/phases/unit/__init__.py,sha256=XgLGwkVemmobLP6cyfAGYLu8RFRCrv6osfB3BD6OAS0,7537
71
71
  schemathesis/engine/phases/unit/_executor.py,sha256=X8hziN6rAd9vRTkbiMKcZWN6ujj4pUEiVdm6OmFsniI,12931
@@ -73,19 +73,19 @@ schemathesis/engine/phases/unit/_pool.py,sha256=01xRGJnmfLqGBH-f3nQEDv7vOufmen5Z
73
73
  schemathesis/experimental/__init__.py,sha256=36H1vLQhrw4SMD_jx76Wt07PHneELRDY1jfBSh7VxU0,2257
74
74
  schemathesis/generation/__init__.py,sha256=2htA0TlQee6AvQmLl1VNxEptRDqvPjksXKJLMVLAJng,1580
75
75
  schemathesis/generation/case.py,sha256=Rt5MCUtPVYVQzNyjUx8magocPJpHV1svyuqQSTwUE-I,7306
76
- schemathesis/generation/coverage.py,sha256=u6VjUUQq3nNFOaJingO8CR-e_qL5BqQrUJRU5MZNzDQ,39149
76
+ schemathesis/generation/coverage.py,sha256=yQwazaxhKacHU3sA8shMPWQ1JhLE_90YgIZjvAymsG8,39133
77
77
  schemathesis/generation/meta.py,sha256=36h6m4E7jzLGa8TCvl7eBl_xUWLiRul3qxzexl5cB58,2515
78
78
  schemathesis/generation/modes.py,sha256=t_EvKr2aOXYMsEfdMu4lLF4KCGcX1LVVyvzTkcpJqhk,663
79
79
  schemathesis/generation/overrides.py,sha256=FhqcFoliEvgW6MZyFPYemfLgzKt3Miy8Cud7OMOCb7g,3045
80
80
  schemathesis/generation/targets.py,sha256=_rN2qgxTE2EfvygiN-Fy3WmDnRH0ERohdx3sKRDaYhU,2120
81
81
  schemathesis/generation/hypothesis/__init__.py,sha256=Rl7QwvMBMJI7pBqTydplX6bXC420n0EGQHVm-vZgaYQ,1204
82
- schemathesis/generation/hypothesis/builder.py,sha256=s1JHSL0ATNk8u-dli1a-LQzjhyiIgUBfUuJCwp-rt8g,24401
82
+ schemathesis/generation/hypothesis/builder.py,sha256=K8pA5hFkx7sXSFIR8zX7ltYCwJHx3CFcq4Rm12ou1wk,24650
83
83
  schemathesis/generation/hypothesis/examples.py,sha256=6eGaKUEC3elmKsaqfKj1sLvM8EHc-PWT4NRBq4NI0Rs,1409
84
84
  schemathesis/generation/hypothesis/given.py,sha256=sTZR1of6XaHAPWtHx2_WLlZ50M8D5Rjux0GmWkWjDq4,2337
85
85
  schemathesis/generation/hypothesis/reporting.py,sha256=uDVow6Ya8YFkqQuOqRsjbzsbyP4KKfr3jA7ZaY4FuKY,279
86
86
  schemathesis/generation/hypothesis/strategies.py,sha256=RurE81E06d99YKG48dizy9346ayfNswYTt38zewmGgw,483
87
87
  schemathesis/generation/stateful/__init__.py,sha256=kXpCGbo1-QqfR2N0Z07tLw0Z5_tvbuG3Tk-WI_I1doI,653
88
- schemathesis/generation/stateful/state_machine.py,sha256=hV-npuzfvp0cxT6SBxAYoseuxmTdbChGnDZ3Wf7nL9o,10962
88
+ schemathesis/generation/stateful/state_machine.py,sha256=S-mTOyLEeiO3PMv9LfSxQWSi8KxD5URptDxRpkO5Fmw,10947
89
89
  schemathesis/graphql/__init__.py,sha256=_eO6MAPHGgiADVGRntnwtPxmuvk666sAh-FAU4cG9-0,326
90
90
  schemathesis/graphql/checks.py,sha256=IADbxiZjgkBWrC5yzHDtohRABX6zKXk5w_zpWNwdzYo,3186
91
91
  schemathesis/graphql/loaders.py,sha256=96R_On1jFvsNuLwqXnO3_TTpsYhdCv0LAmR5jWRXXnY,4756
@@ -112,18 +112,18 @@ schemathesis/specs/graphql/validation.py,sha256=-W1Noc1MQmTb4RX-gNXMeU2qkgso4mzV
112
112
  schemathesis/specs/openapi/__init__.py,sha256=C5HOsfuDJGq_3mv8CRBvRvb0Diy1p0BFdqyEXMS-loE,238
113
113
  schemathesis/specs/openapi/_cache.py,sha256=HpglmETmZU0RCHxp3DO_sg5_B_nzi54Zuw9vGzzYCxY,4295
114
114
  schemathesis/specs/openapi/_hypothesis.py,sha256=n_39iyz1rt2EdSe-Lyr-3sOIEyJIthnCVR4tGUUvH1c,21328
115
- schemathesis/specs/openapi/checks.py,sha256=bgThVKGwmxDLnAxH8B96t0Qn-hNkpE0DrZ1B7GvTXJM,26236
115
+ schemathesis/specs/openapi/checks.py,sha256=R-vehTWC6ZcVYVfKiXVdYHnZBTosMpZQzPPBsJ2dt-Y,26909
116
116
  schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
117
117
  schemathesis/specs/openapi/converter.py,sha256=lil8IewM5j8tvt4lpA9g_KITvIwx1M96i45DNSHNjoc,3505
118
118
  schemathesis/specs/openapi/definitions.py,sha256=8htclglV3fW6JPBqs59lgM4LnA25Mm9IptXBPb_qUT0,93949
119
119
  schemathesis/specs/openapi/examples.py,sha256=Uy6naFBq-m1vo_18j4KuZBUYc9qKrBk19jBCWT7tnRg,20464
120
120
  schemathesis/specs/openapi/formats.py,sha256=ViVF3aFeFI1ctwGQbiRDXhU3so82P0BCaF2aDDbUUm8,2816
121
- schemathesis/specs/openapi/links.py,sha256=c3ko0jrASbqpo7JaD22DKtlZaUvGUTG51jHvEJPzDpQ,8802
121
+ schemathesis/specs/openapi/links.py,sha256=eVlZVnlIcBUSSnYEEb_Bes3tfY5F_BNsOo0IZCBb-io,9452
122
122
  schemathesis/specs/openapi/media_types.py,sha256=ADedOaNWjbAtAekyaKmNj9fY6zBTeqcNqBEjN0EWNhI,1014
123
123
  schemathesis/specs/openapi/parameters.py,sha256=fmOkH-KhMVzWmE6eSw2pw2hernril5MDL7DwwEG4344,14655
124
- schemathesis/specs/openapi/patterns.py,sha256=RpvsAfpcm2dAcFYj-N6w0iQ1VdCT9h8BLdS1mrso6II,5518
124
+ schemathesis/specs/openapi/patterns.py,sha256=6qNZRYQkHtJ98_JMjwhqIGpeR4aR7rlxcCmr1nOHMzk,11258
125
125
  schemathesis/specs/openapi/references.py,sha256=YjD1xMlaYS7xLt6PrrVS20R72ZWHuFZFTa8Llzf54Rg,8808
126
- schemathesis/specs/openapi/schemas.py,sha256=kFihC15IMAMce_o1VtHGhZawjmczFlMkbKUj9sbnmbk,53692
126
+ schemathesis/specs/openapi/schemas.py,sha256=UAukxlH2bVlpZNnx_4EzhDefPpsEtC2ieWz_x336-SQ,54906
127
127
  schemathesis/specs/openapi/security.py,sha256=6UWYMhL-dPtkTineqqBFNKca1i4EuoTduw-EOLeE0aQ,7149
128
128
  schemathesis/specs/openapi/serialization.py,sha256=JFxMqCr8YWwPT4BVrbvVytcAmkzGXyL1_Q1xR1JKBPs,11464
129
129
  schemathesis/specs/openapi/utils.py,sha256=ER4vJkdFVDIE7aKyxyYatuuHVRNutytezgE52pqZNE8,900
@@ -137,15 +137,16 @@ schemathesis/specs/openapi/negative/__init__.py,sha256=60QqVBTXPTsAojcf7GDs7v8Wb
137
137
  schemathesis/specs/openapi/negative/mutations.py,sha256=7jTjD9rt5vxWSVBL5Hx8Avj4WhTA63frDQiFMKysrUU,19248
138
138
  schemathesis/specs/openapi/negative/types.py,sha256=a7buCcVxNBG6ILBM3A7oNTAX0lyDseEtZndBuej8MbI,174
139
139
  schemathesis/specs/openapi/negative/utils.py,sha256=ozcOIuASufLqZSgnKUACjX-EOZrrkuNdXX0SDnLoGYA,168
140
- schemathesis/specs/openapi/stateful/__init__.py,sha256=W1CwDtYYiy4hwpMFEqD6Ya9Sif_shQo7mbeWgo-XJXA,9728
140
+ schemathesis/specs/openapi/stateful/__init__.py,sha256=Tk9ahg6VVXwVw8yfFmrz9xhDWskBUaKEeBobdtd44HU,12020
141
+ schemathesis/specs/openapi/stateful/control.py,sha256=QaXLSbwQWtai5lxvvVtQV3BLJ8n5ePqSKB00XFxp-MA,3695
141
142
  schemathesis/transport/__init__.py,sha256=z-mRNSOlMBKwQyaEIhpmYv0plWTmK5dJqc9UmQOry80,3949
142
143
  schemathesis/transport/asgi.py,sha256=qTClt6oT_xUEWnRHokACN_uqCNNUZrRPT6YG0PjbElY,926
143
144
  schemathesis/transport/prepare.py,sha256=qQ6zXBw5NN2AIM0bzLAc5Ryc3dmMb0R6xN14lnR49pU,3826
144
145
  schemathesis/transport/requests.py,sha256=OObRvcTL72-BZ7AfuDUrZZU9nZtfBqr22oF8nkzaOLE,8389
145
146
  schemathesis/transport/serialization.py,sha256=jIMra1LqRGav0OX3Hx7mvORt38ll4cd2DKit2D58FN0,10531
146
147
  schemathesis/transport/wsgi.py,sha256=RWSuUXPrl91GxAy8a4jyNNozOWVMRBxKx_tljlWA_Lo,5697
147
- schemathesis-4.0.0a2.dist-info/METADATA,sha256=IcvZ7pOCr48wivV_O6DKm6HGqsXvZzkMpYVkM_lv88M,12292
148
- schemathesis-4.0.0a2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
149
- schemathesis-4.0.0a2.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
150
- schemathesis-4.0.0a2.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
151
- schemathesis-4.0.0a2.dist-info/RECORD,,
148
+ schemathesis-4.0.0a3.dist-info/METADATA,sha256=80kE1Tg6m91KKgpuMEUsuPEPYJzCxeXcGthSlW1_BYs,12292
149
+ schemathesis-4.0.0a3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
150
+ schemathesis-4.0.0a3.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
151
+ schemathesis-4.0.0a3.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
152
+ schemathesis-4.0.0a3.dist-info/RECORD,,