schemathesis 4.3.13__py3-none-any.whl → 4.3.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of schemathesis might be problematic. Click here for more details.

schemathesis/checks.py CHANGED
@@ -187,3 +187,14 @@ def run_checks(
187
187
  on_failure(name, collected, sub_failure)
188
188
 
189
189
  return collected
190
+
191
+
192
+ def __getattr__(name: str) -> Any:
193
+ try:
194
+ return CHECKS.get_one(name)
195
+ except KeyError:
196
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None
197
+
198
+
199
+ def __dir__() -> list[str]:
200
+ return sorted(list(globals().keys()) + CHECKS.get_all_names())
@@ -26,6 +26,9 @@ class Registry(Generic[T]):
26
26
  def get_all(self) -> list[T]:
27
27
  return list(self._items.values())
28
28
 
29
+ def get_one(self, name: str) -> T:
30
+ return self._items[name]
31
+
29
32
  def get_by_names(self, names: Sequence[str]) -> list[T]:
30
33
  """Get items by their names."""
31
34
  return [self._items[name] for name in names]
@@ -12,6 +12,8 @@ from dataclasses import dataclass
12
12
  from functools import cached_property
13
13
  from typing import TYPE_CHECKING, Callable, Iterator, Sequence, cast
14
14
 
15
+ from hypothesis import HealthCheck
16
+
15
17
  from schemathesis import errors
16
18
  from schemathesis.core.errors import (
17
19
  InfiniteRecursiveReference,
@@ -23,6 +25,7 @@ from schemathesis.core.errors import (
23
25
  get_request_error_message,
24
26
  split_traceback,
25
27
  )
28
+ from schemathesis.generation.hypothesis.reporting import HEALTH_CHECK_ACTIONS, HEALTH_CHECK_TITLES
26
29
 
27
30
  if TYPE_CHECKING:
28
31
  import hypothesis.errors
@@ -119,18 +122,6 @@ class EngineErrorInfo:
119
122
  scalar_name = scalar_name_from_error(self._error)
120
123
  return f"Scalar type '{scalar_name}' is not recognized"
121
124
 
122
- if self._kind == RuntimeErrorKind.HYPOTHESIS_HEALTH_CHECK_DATA_TOO_LARGE:
123
- return HEALTH_CHECK_MESSAGE_DATA_TOO_LARGE
124
- if self._kind == RuntimeErrorKind.HYPOTHESIS_HEALTH_CHECK_FILTER_TOO_MUCH:
125
- return HEALTH_CHECK_MESSAGE_FILTER_TOO_MUCH
126
- if self._kind == RuntimeErrorKind.HYPOTHESIS_HEALTH_CHECK_TOO_SLOW:
127
- return HEALTH_CHECK_MESSAGE_TOO_SLOW
128
- if self._kind == RuntimeErrorKind.HYPOTHESIS_HEALTH_CHECK_LARGE_BASE_EXAMPLE:
129
- return HEALTH_CHECK_MESSAGE_LARGE_BASE_EXAMPLE
130
-
131
- if self._kind == RuntimeErrorKind.HYPOTHESIS_UNSATISFIABLE:
132
- return f"{self._error}. Possible reasons:"
133
-
134
125
  if self._kind in (
135
126
  RuntimeErrorKind.SCHEMA_INVALID_REGULAR_EXPRESSION,
136
127
  RuntimeErrorKind.SCHEMA_GENERIC,
@@ -147,13 +138,6 @@ class EngineErrorInfo:
147
138
  if isinstance(self._error, requests.RequestException):
148
139
  return get_request_error_extras(self._error)
149
140
 
150
- if self._kind == RuntimeErrorKind.HYPOTHESIS_UNSATISFIABLE:
151
- return [
152
- "- Contradictory schema constraints, such as a minimum value exceeding the maximum.",
153
- "- Invalid schema definitions for headers or cookies, for example allowing for non-ASCII characters.",
154
- "- Excessive schema complexity, which hinders parameter generation.",
155
- ]
156
-
157
141
  return []
158
142
 
159
143
  @cached_property
@@ -226,16 +210,9 @@ def scalar_name_from_error(exception: hypothesis.errors.InvalidArgument) -> str:
226
210
 
227
211
 
228
212
  def extract_health_check_error(error: hypothesis.errors.FailedHealthCheck) -> hypothesis.HealthCheck | None:
229
- from hypothesis import HealthCheck
230
-
231
- match = re.search(r"add HealthCheck\.(\w+) to the suppress_health_check ", str(error))
232
- if match:
233
- return {
234
- "data_too_large": HealthCheck.data_too_large,
235
- "filter_too_much": HealthCheck.filter_too_much,
236
- "too_slow": HealthCheck.too_slow,
237
- "large_base_example": HealthCheck.large_base_example,
238
- }.get(match.group(1))
213
+ for key, title in HEALTH_CHECK_TITLES.items():
214
+ if title in str(error):
215
+ return key
239
216
  return None
240
217
 
241
218
 
@@ -243,11 +220,17 @@ def get_runtime_error_suggestion(error_type: RuntimeErrorKind, bold: Callable[[s
243
220
  """Get a user-friendly suggestion for handling the error."""
244
221
 
245
222
  def _format_health_check_suggestion(label: str) -> str:
246
- return f"Bypass this health check using {bold(f'`--suppress-health-check={label}`')}."
223
+ base = {
224
+ "data_too_large": HEALTH_CHECK_ACTIONS[HealthCheck.data_too_large],
225
+ "filter_too_much": HEALTH_CHECK_ACTIONS[HealthCheck.filter_too_much],
226
+ "too_slow": HEALTH_CHECK_ACTIONS[HealthCheck.too_slow],
227
+ "large_base_example": HEALTH_CHECK_ACTIONS[HealthCheck.large_base_example],
228
+ }[label]
229
+ return f"{base} or bypass this health check using {bold(f'`--suppress-health-check={label}`')}."
247
230
 
248
231
  return {
249
232
  RuntimeErrorKind.CONNECTION_SSL: f"Bypass SSL verification with {bold('`--tls-verify=false`')}.",
250
- RuntimeErrorKind.HYPOTHESIS_UNSATISFIABLE: "Examine the schema for inconsistencies and consider simplifying it.",
233
+ RuntimeErrorKind.HYPOTHESIS_UNSATISFIABLE: "Review all parameters and request body schemas for conflicting constraints.",
251
234
  RuntimeErrorKind.SCHEMA_NO_LINKS_FOUND: "Review your endpoint filters to include linked operations",
252
235
  RuntimeErrorKind.SCHEMA_INVALID_REGULAR_EXPRESSION: "Ensure your regex is compatible with Python's syntax.\n"
253
236
  "For guidance, visit: https://docs.python.org/3/library/re.html",
@@ -262,28 +245,6 @@ def get_runtime_error_suggestion(error_type: RuntimeErrorKind, bold: Callable[[s
262
245
  }.get(error_type)
263
246
 
264
247
 
265
- HEALTH_CHECK_MESSAGE_DATA_TOO_LARGE = """There's a notable occurrence of examples surpassing the maximum size limit.
266
- Typically, generating excessively large examples can compromise the quality of test outcomes.
267
-
268
- Consider revising the schema to more accurately represent typical use cases
269
- or applying constraints to reduce the data size."""
270
- HEALTH_CHECK_MESSAGE_FILTER_TOO_MUCH = """A significant number of generated examples are being filtered out, indicating
271
- that the schema's constraints may be too complex.
272
-
273
- This level of filtration can slow down testing and affect the distribution
274
- of generated data. Review and simplify the schema constraints where
275
- possible to mitigate this issue."""
276
- HEALTH_CHECK_MESSAGE_TOO_SLOW = "Data generation is extremely slow. Consider reducing the complexity of the schema."
277
- HEALTH_CHECK_MESSAGE_LARGE_BASE_EXAMPLE = """A health check has identified that the smallest example derived from the schema
278
- is excessively large, potentially leading to inefficient test execution.
279
-
280
- This is commonly due to schemas that specify large-scale data structures by
281
- default, such as an array with an extensive number of elements.
282
-
283
- Consider revising the schema to more accurately represent typical use cases
284
- or applying constraints to reduce the data size."""
285
-
286
-
287
248
  @enum.unique
288
249
  class RuntimeErrorKind(str, enum.Enum):
289
250
  """Classification of runtime errors."""
@@ -54,7 +54,11 @@ from schemathesis.generation.hypothesis.builder import (
54
54
  UnresolvableReferenceMark,
55
55
  UnsatisfiableExampleMark,
56
56
  )
57
- from schemathesis.generation.hypothesis.reporting import ignore_hypothesis_output
57
+ from schemathesis.generation.hypothesis.reporting import (
58
+ build_health_check_error,
59
+ build_unsatisfiable_error,
60
+ ignore_hypothesis_output,
61
+ )
58
62
 
59
63
  if TYPE_CHECKING:
60
64
  from schemathesis.schemas import APIOperation
@@ -162,10 +166,13 @@ def run_test(
162
166
  status = Status.FAILURE
163
167
  except BaseExceptionGroup:
164
168
  status = Status.ERROR
169
+ except hypothesis.errors.FailedHealthCheck as exc:
170
+ status = Status.ERROR
171
+ yield non_fatal_error(build_health_check_error(operation, exc, with_tip=False))
165
172
  except hypothesis.errors.Unsatisfiable:
166
173
  # We need more clear error message here
167
174
  status = Status.ERROR
168
- yield non_fatal_error(hypothesis.errors.Unsatisfiable("Failed to generate test cases for this API operation"))
175
+ yield non_fatal_error(build_unsatisfiable_error(operation, with_tip=False))
169
176
  except KeyboardInterrupt:
170
177
  yield scenario_finished(Status.INTERRUPTED)
171
178
  yield events.Interrupted(phase=phase)
@@ -211,7 +218,11 @@ def run_test(
211
218
  # `hypothesis-jsonschema` emits a warning on invalid regular expression syntax
212
219
  yield non_fatal_error(InvalidRegexPattern.from_hypothesis_jsonschema_message(message))
213
220
  else:
214
- yield non_fatal_error(exc)
221
+ health_check = build_health_check_error(operation, exc, with_tip=False)
222
+ if isinstance(health_check, hypothesis.errors.FailedHealthCheck):
223
+ yield non_fatal_error(health_check)
224
+ else:
225
+ yield non_fatal_error(exc)
215
226
  except hypothesis.errors.DeadlineExceeded as exc:
216
227
  status = Status.ERROR
217
228
  yield non_fatal_error(DeadlineExceeded.from_exc(exc))
@@ -121,6 +121,8 @@ def create_test(
121
121
  kwargs=config.given_kwargs,
122
122
  )
123
123
 
124
+ ApiOperationMark.set(hypothesis_test, operation)
125
+
124
126
  if config.seed is not None:
125
127
  hypothesis_test = hypothesis.seed(config.seed)(hypothesis_test)
126
128
 
@@ -957,3 +959,4 @@ InvalidHeadersExampleMark = Mark[dict[str, str]](attr_name="invalid_example_head
957
959
  MissingPathParameters = Mark[InvalidSchema](attr_name="missing_path_parameters")
958
960
  InfiniteRecursiveReferenceMark = Mark[InfiniteRecursiveReference](attr_name="infinite_recursive_reference")
959
961
  UnresolvableReferenceMark = Mark[UnresolvableReference](attr_name="unresolvable_reference")
962
+ ApiOperationMark = Mark[APIOperation](attr_name="api_operation")
@@ -28,17 +28,23 @@ def default_settings() -> settings:
28
28
  T = TypeVar("T")
29
29
 
30
30
 
31
- def generate_one(strategy: st.SearchStrategy[T]) -> T: # type: ignore[type-var]
31
+ def generate_one(strategy: st.SearchStrategy[T], suppress_health_check: list | None = None) -> T: # type: ignore[type-var]
32
32
  examples: list[T] = []
33
- add_single_example(strategy, examples)
33
+ add_single_example(strategy, examples, suppress_health_check)
34
34
  return examples[0]
35
35
 
36
36
 
37
- def add_single_example(strategy: st.SearchStrategy[T], examples: list[T]) -> None:
38
- from hypothesis import given, seed
37
+ def add_single_example(
38
+ strategy: st.SearchStrategy[T], examples: list[T], suppress_health_check: list | None = None
39
+ ) -> None:
40
+ from hypothesis import given, seed, settings
41
+
42
+ applied_settings = default_settings()
43
+ if suppress_health_check is not None:
44
+ applied_settings = settings(applied_settings, suppress_health_check=suppress_health_check)
39
45
 
40
46
  @given(strategy) # type: ignore
41
- @default_settings() # type: ignore
47
+ @applied_settings # type: ignore
42
48
  def example_generating_inner_function(ex: T) -> None:
43
49
  examples.append(ex)
44
50
 
@@ -1,8 +1,23 @@
1
+ from __future__ import annotations
2
+
1
3
  from contextlib import contextmanager
2
- from typing import Generator
4
+ from dataclasses import dataclass
5
+ from enum import Enum
6
+ from typing import TYPE_CHECKING, Any, Generator
3
7
 
8
+ from hypothesis import HealthCheck
9
+ from hypothesis.errors import FailedHealthCheck, InvalidArgument, Unsatisfiable
4
10
  from hypothesis.reporting import with_reporter
5
11
 
12
+ from schemathesis.config import OutputConfig
13
+ from schemathesis.core.jsonschema.types import JsonSchema
14
+ from schemathesis.core.output import truncate_json
15
+ from schemathesis.core.parameters import ParameterLocation
16
+ from schemathesis.generation.hypothesis.examples import generate_one
17
+
18
+ if TYPE_CHECKING:
19
+ from schemathesis.schemas import APIOperation
20
+
6
21
 
7
22
  def ignore(_: str) -> None:
8
23
  pass
@@ -12,3 +27,259 @@ def ignore(_: str) -> None:
12
27
  def ignore_hypothesis_output() -> Generator:
13
28
  with with_reporter(ignore): # type: ignore
14
29
  yield
30
+
31
+
32
+ UNSATISFIABILITY_CAUSE = """ - Type mismatch (e.g., enum with strings but type: integer)
33
+ - Contradictory constraints (e.g., minimum > maximum)
34
+ - Regex that's too complex to generate values for"""
35
+
36
+ GENERIC_UNSATISFIABLE_MESSAGE = f"""Cannot generate test data for this operation
37
+
38
+ Unable to identify the specific parameter. Common causes:
39
+ {UNSATISFIABILITY_CAUSE}"""
40
+
41
+
42
+ @dataclass
43
+ class UnsatisfiableParameter:
44
+ location: ParameterLocation
45
+ name: str
46
+ schema: JsonSchema
47
+
48
+ __slots__ = ("location", "name", "schema")
49
+
50
+ def get_error_message(self, config: OutputConfig) -> str:
51
+ formatted_schema = truncate_json(self.schema, config=config)
52
+
53
+ if self.location == ParameterLocation.BODY:
54
+ # For body, name is the media type
55
+ location = f"request body ({self.name})"
56
+ else:
57
+ location = f"{self.location.value} parameter '{self.name}'"
58
+
59
+ return f"""Cannot generate test data for {location}
60
+ Schema:
61
+
62
+ {formatted_schema}
63
+
64
+ This usually means:
65
+ {UNSATISFIABILITY_CAUSE}"""
66
+
67
+
68
+ def find_unsatisfiable_parameter(operation: APIOperation) -> UnsatisfiableParameter | None:
69
+ from hypothesis_jsonschema import from_schema
70
+
71
+ for location, container in (
72
+ (ParameterLocation.QUERY, operation.query),
73
+ (ParameterLocation.PATH, operation.path_parameters),
74
+ (ParameterLocation.HEADER, operation.headers),
75
+ (ParameterLocation.COOKIE, operation.cookies),
76
+ (ParameterLocation.BODY, operation.body),
77
+ ):
78
+ for parameter in container:
79
+ try:
80
+ generate_one(from_schema(parameter.optimized_schema))
81
+ except Unsatisfiable:
82
+ if location == ParameterLocation.BODY:
83
+ name = parameter.media_type
84
+ else:
85
+ name = parameter.name
86
+ schema = unbundle_schema_refs(parameter.optimized_schema, parameter.name_to_uri)
87
+ return UnsatisfiableParameter(location=location, name=name, schema=schema)
88
+ return None
89
+
90
+
91
+ def unbundle_schema_refs(schema: JsonSchema | list[JsonSchema], name_to_uri: dict[str, str]) -> JsonSchema:
92
+ if isinstance(schema, dict):
93
+ result: dict[str, Any] = {}
94
+ for key, value in schema.items():
95
+ if key == "$ref" and isinstance(value, str) and value.startswith("#/x-bundled/"):
96
+ # Extract bundled name (e.g., "schema1" from "#/x-bundled/schema1")
97
+ bundled_name = value.split("/")[-1]
98
+ if bundled_name in name_to_uri:
99
+ original_uri = name_to_uri[bundled_name]
100
+ # Extract fragment after # (e.g., "#/components/schemas/ObjectType")
101
+ if "#" in original_uri:
102
+ result[key] = "#" + original_uri.split("#", 1)[1]
103
+ else:
104
+ # Fallback if no fragment
105
+ result[key] = value
106
+ else:
107
+ result[key] = value
108
+ elif key == "x-bundled" and isinstance(value, dict):
109
+ # Replace x-bundled with proper components/schemas structure
110
+ components: dict[str, dict[str, Any]] = {"schemas": {}}
111
+ for bundled_name, bundled_schema in value.items():
112
+ if bundled_name in name_to_uri:
113
+ original_uri = name_to_uri[bundled_name]
114
+ # Extract schema name (e.g., "ObjectType" from "...#/components/schemas/ObjectType")
115
+ if "#/components/schemas/" in original_uri:
116
+ schema_name = original_uri.split("#/components/schemas/")[1]
117
+ components["schemas"][schema_name] = unbundle_schema_refs(bundled_schema, name_to_uri)
118
+ else:
119
+ # Fallback: keep bundled name if URI doesn't match expected pattern
120
+ components["schemas"][bundled_name] = unbundle_schema_refs(bundled_schema, name_to_uri)
121
+ else:
122
+ components["schemas"][bundled_name] = unbundle_schema_refs(bundled_schema, name_to_uri)
123
+ result["components"] = components
124
+ elif isinstance(value, (dict, list)):
125
+ # Recursively process all other values
126
+ result[key] = unbundle_schema_refs(value, name_to_uri)
127
+ else:
128
+ result[key] = value
129
+ return result
130
+ elif isinstance(schema, list):
131
+ return [unbundle_schema_refs(item, name_to_uri) for item in schema] # type: ignore
132
+ return schema
133
+
134
+
135
+ def build_unsatisfiable_error(operation: APIOperation, *, with_tip: bool) -> Unsatisfiable:
136
+ __tracebackhide__ = True
137
+ unsatisfiable = find_unsatisfiable_parameter(operation)
138
+
139
+ if unsatisfiable is not None:
140
+ message = unsatisfiable.get_error_message(operation.schema.config.output)
141
+ else:
142
+ message = GENERIC_UNSATISFIABLE_MESSAGE
143
+
144
+ if with_tip:
145
+ message += "\n\nTip: Review all parameters and request body schemas for conflicting constraints"
146
+
147
+ return Unsatisfiable(message)
148
+
149
+
150
+ HEALTH_CHECK_CAUSES = {
151
+ HealthCheck.data_too_large: """ - Arrays with large minItems (e.g., minItems: 1000)
152
+ - Strings with large minLength (e.g., minLength: 10000)
153
+ - Deeply nested objects with many required properties""",
154
+ HealthCheck.filter_too_much: """ - Complex regex patterns that match few strings
155
+ - Multiple overlapping constraints (pattern + format + enum)""",
156
+ HealthCheck.too_slow: """ - Regex with excessive backtracking (e.g., (a+)+b)
157
+ - Many interdependent constraints
158
+ - Large combinatorial complexity""",
159
+ HealthCheck.large_base_example: """ - Arrays with large minimum size (e.g., minItems: 100)
160
+ - Many required properties with their own large minimums
161
+ - Nested structures that multiply size requirements""",
162
+ }
163
+
164
+ HEALTH_CHECK_ACTIONS = {
165
+ HealthCheck.data_too_large: "Reduce minItems, minLength, or size constraints to realistic values",
166
+ HealthCheck.filter_too_much: "Simplify constraints or widen acceptable value ranges",
167
+ HealthCheck.too_slow: "Simplify regex patterns or reduce constraint complexity",
168
+ HealthCheck.large_base_example: "Reduce minimum size requirements or number of required properties",
169
+ }
170
+
171
+ HEALTH_CHECK_TITLES = {
172
+ HealthCheck.data_too_large: "Generated examples exceed size limits",
173
+ HealthCheck.filter_too_much: "Too many generated examples are filtered out",
174
+ HealthCheck.too_slow: "Data generation is too slow",
175
+ HealthCheck.large_base_example: "Minimum possible example is too large",
176
+ }
177
+
178
+
179
+ @dataclass
180
+ class SlowParameter:
181
+ """Information about a parameter with slow or problematic data generation."""
182
+
183
+ location: ParameterLocation
184
+ name: str
185
+ schema: JsonSchema
186
+ original: HealthCheck
187
+
188
+ __slots__ = ("location", "name", "schema", "original")
189
+
190
+ def get_error_message(self, config: OutputConfig) -> str:
191
+ formatted_schema = truncate_json(self.schema, config=config)
192
+ if self.location == ParameterLocation.BODY:
193
+ # For body, name is the media type
194
+ location = f"request body ({self.name})"
195
+ else:
196
+ location = f"{self.location.value} parameter '{self.name}'"
197
+ title = HEALTH_CHECK_TITLES[self.original]
198
+ causes = HEALTH_CHECK_CAUSES[self.original]
199
+
200
+ return f"""{title} for {location}
201
+ Schema:
202
+
203
+ {formatted_schema}
204
+
205
+ This usually means:
206
+ {causes}"""
207
+
208
+
209
+ def _extract_health_check_reason(exc: FailedHealthCheck | InvalidArgument) -> HealthCheck | None:
210
+ message = str(exc).lower()
211
+ if "data_too_large" in message or "too large" in message:
212
+ return HealthCheck.data_too_large
213
+ elif "filter_too_much" in message or "filtered out" in message:
214
+ return HealthCheck.filter_too_much
215
+ elif "too_slow" in message or "too slow" in message:
216
+ return HealthCheck.too_slow
217
+ elif ("large_base_example" in message or "can never generate an example, because min_size" in message) or (
218
+ isinstance(exc, InvalidArgument)
219
+ and message.endswith("larger than hypothesis is designed to handle")
220
+ or "can never generate an example, because min_size is larger than hypothesis supports" in message
221
+ ):
222
+ return HealthCheck.large_base_example
223
+
224
+ return None
225
+
226
+
227
+ def find_slow_parameter(operation: APIOperation, reason: HealthCheck) -> SlowParameter | None:
228
+ from hypothesis.errors import FailedHealthCheck
229
+ from hypothesis_jsonschema import from_schema
230
+
231
+ for location, container in (
232
+ (ParameterLocation.QUERY, operation.query),
233
+ (ParameterLocation.PATH, operation.path_parameters),
234
+ (ParameterLocation.HEADER, operation.headers),
235
+ (ParameterLocation.COOKIE, operation.cookies),
236
+ (ParameterLocation.BODY, operation.body),
237
+ ):
238
+ for parameter in container:
239
+ try:
240
+ generate_one(from_schema(parameter.optimized_schema), suppress_health_check=[])
241
+ except (FailedHealthCheck, Unsatisfiable, InvalidArgument):
242
+ if location == ParameterLocation.BODY:
243
+ name = parameter.media_type
244
+ else:
245
+ name = parameter.name
246
+
247
+ schema = unbundle_schema_refs(parameter.optimized_schema, parameter.name_to_uri)
248
+ return SlowParameter(location=location, name=name, schema=schema, original=reason)
249
+ return None
250
+
251
+
252
+ def _get_generic_health_check_message(reason: HealthCheck) -> str:
253
+ title = HEALTH_CHECK_TITLES[reason]
254
+ causes = HEALTH_CHECK_CAUSES[reason]
255
+ return f"{title} for this operation\n\nUnable to identify the specific parameter. Common causes:\n{causes}"
256
+
257
+
258
+ class HealthCheckTipStyle(Enum):
259
+ DEFAULT = "default"
260
+ PYTEST = "pytest"
261
+
262
+
263
+ def build_health_check_error(
264
+ operation: APIOperation,
265
+ original: FailedHealthCheck | InvalidArgument,
266
+ with_tip: bool,
267
+ tip_style: HealthCheckTipStyle = HealthCheckTipStyle.DEFAULT,
268
+ ) -> FailedHealthCheck | InvalidArgument:
269
+ __tracebackhide__ = True
270
+ reason = _extract_health_check_reason(original)
271
+ if reason is None:
272
+ return original
273
+ slow_param = find_slow_parameter(operation, reason)
274
+
275
+ if slow_param is not None:
276
+ message = slow_param.get_error_message(operation.schema.config.output)
277
+ else:
278
+ message = _get_generic_health_check_message(reason)
279
+
280
+ if with_tip:
281
+ message += f"\n\nTip: {HEALTH_CHECK_ACTIONS[reason]}"
282
+ if tip_style == HealthCheckTipStyle.PYTEST:
283
+ message += f". You can disable this health check with @settings(suppress_health_check=[{reason!r}])"
284
+
285
+ return FailedHealthCheck(message)
@@ -449,7 +449,7 @@ class UnsupportedMethodResponse(Failure):
449
449
  allow_header_present: bool | None = None,
450
450
  failure_reason: str, # "wrong_status" or "missing_allow_header"
451
451
  message: str,
452
- title: str = "Unsupported method incorrect response",
452
+ title: str = "Unsupported methods",
453
453
  case_id: str | None = None,
454
454
  ) -> None:
455
455
  self.operation = operation
@@ -9,7 +9,7 @@ import pytest
9
9
  from _pytest import nodes
10
10
  from _pytest.config import hookimpl
11
11
  from _pytest.python import Class, Function, FunctionDefinition, Metafunc, Module, PyCollector
12
- from hypothesis.errors import InvalidArgument, Unsatisfiable
12
+ from hypothesis.errors import FailedHealthCheck, InvalidArgument, Unsatisfiable
13
13
  from jsonschema.exceptions import SchemaError
14
14
 
15
15
  from schemathesis.core.control import SkipTest
@@ -34,7 +34,12 @@ from schemathesis.generation.hypothesis.given import (
34
34
  merge_given_args,
35
35
  validate_given_args,
36
36
  )
37
- from schemathesis.generation.hypothesis.reporting import ignore_hypothesis_output
37
+ from schemathesis.generation.hypothesis.reporting import (
38
+ HealthCheckTipStyle,
39
+ build_health_check_error,
40
+ build_unsatisfiable_error,
41
+ ignore_hypothesis_output,
42
+ )
38
43
  from schemathesis.pytest.control_flow import fail_on_no_matches
39
44
  from schemathesis.schemas import APIOperation
40
45
 
@@ -293,6 +298,7 @@ def pytest_pyfunc_call(pyfuncitem): # type:ignore
293
298
  For example - kwargs validation is failed for some strategy.
294
299
  """
295
300
  from schemathesis.generation.hypothesis.builder import (
301
+ ApiOperationMark,
296
302
  InvalidHeadersExampleMark,
297
303
  InvalidRegexMark,
298
304
  MissingPathParameters,
@@ -327,6 +333,16 @@ def pytest_pyfunc_call(pyfuncitem): # type:ignore
327
333
  if invalid_headers is not None:
328
334
  raise InvalidHeadersExample.from_headers(invalid_headers) from None
329
335
  pytest.skip(exc.args[0])
336
+ except FailedHealthCheck as exc:
337
+ operation = ApiOperationMark.get(pyfuncitem.obj)
338
+ assert operation is not None
339
+ raise build_health_check_error(
340
+ operation, exc, with_tip=True, tip_style=HealthCheckTipStyle.PYTEST
341
+ ) from None
342
+ except Unsatisfiable:
343
+ operation = ApiOperationMark.get(pyfuncitem.obj)
344
+ assert operation is not None
345
+ raise build_unsatisfiable_error(operation, with_tip=True) from None
330
346
  except SchemaError as exc:
331
347
  raise InvalidRegexPattern.from_schema_error(exc, from_examples=False) from exc
332
348
 
@@ -19,9 +19,7 @@ from typing import (
19
19
  )
20
20
  from urllib.parse import urlsplit
21
21
 
22
- import graphql
23
22
  from hypothesis import strategies as st
24
- from hypothesis_graphql import strategies as gql_st
25
23
  from requests.structures import CaseInsensitiveDict
26
24
 
27
25
  from schemathesis import auths
@@ -52,6 +50,7 @@ from schemathesis.schemas import (
52
50
  from .scalars import CUSTOM_SCALARS, get_extra_scalar_strategies
53
51
 
54
52
  if TYPE_CHECKING:
53
+ import graphql
55
54
  from hypothesis.strategies import SearchStrategy
56
55
 
57
56
  from schemathesis.auths import AuthStorage
@@ -143,6 +142,8 @@ class GraphQLSchema(BaseSchema):
143
142
 
144
143
  @property
145
144
  def client_schema(self) -> graphql.GraphQLSchema:
145
+ import graphql
146
+
146
147
  if not hasattr(self, "_client_schema"):
147
148
  self._client_schema = graphql.build_client_schema(self.raw_schema)
148
149
  return self._client_schema
@@ -336,6 +337,9 @@ def graphql_cases(
336
337
  media_type: str | None = None,
337
338
  phase: TestPhase = TestPhase.FUZZING,
338
339
  ) -> Any:
340
+ import graphql
341
+ from hypothesis_graphql import strategies as gql_st
342
+
339
343
  start = time.monotonic()
340
344
  definition = cast(GraphQLOperationDefinition, operation.definition)
341
345
  strategy_factory = {
@@ -320,7 +320,7 @@ def unsupported_method(ctx: CheckContext, response: Response, case: Case) -> boo
320
320
  method=cast(str, response.request.method),
321
321
  status_code=response.status_code,
322
322
  failure_reason="wrong_status",
323
- message=f"Wrong status for unsupported method {response.request.method} (got {response.status_code}, expected 405)",
323
+ message=f"Unsupported method {response.request.method} returned {response.status_code}, expected 405 Method Not Allowed\n\nReturn 405 for methods not listed in the OpenAPI spec",
324
324
  )
325
325
 
326
326
  allow_header = response.headers.get("allow")
@@ -331,7 +331,7 @@ def unsupported_method(ctx: CheckContext, response: Response, case: Case) -> boo
331
331
  status_code=response.status_code,
332
332
  allow_header_present=False,
333
333
  failure_reason="missing_allow_header",
334
- message=f"Missing Allow header for unsupported method {response.request.method}",
334
+ message=f"{response.request.method} returned 405 without required `Allow` header\n\nAdd `Allow` header listing supported methods (required by RFC 9110)",
335
335
  )
336
336
  return None
337
337
 
@@ -93,6 +93,8 @@ def _to_json_schema(
93
93
  # Read-only properties should not occur in requests
94
94
  rewrite_properties(schema, is_read_only)
95
95
 
96
+ ensure_required_properties(schema)
97
+
96
98
  for keyword, value in schema.items():
97
99
  if keyword in IN_VALUE and isinstance(value, dict):
98
100
  schema[keyword] = _to_json_schema(
@@ -121,6 +123,22 @@ def _to_json_schema(
121
123
  return schema
122
124
 
123
125
 
126
+ def ensure_required_properties(schema: dict[str, Any]) -> None:
127
+ if schema.get("additionalProperties") is not False:
128
+ return
129
+
130
+ required = schema.get("required")
131
+ if not required or not isinstance(required, list):
132
+ return
133
+
134
+ properties = schema.setdefault("properties", {})
135
+
136
+ # Add missing required properties as empty schemas
137
+ for name in required:
138
+ if name not in properties:
139
+ properties[name] = {}
140
+
141
+
124
142
  IN_VALUE = frozenset(
125
143
  (
126
144
  "additionalProperties",
@@ -114,7 +114,9 @@ class RequestsTransport(BaseTransport["requests.Session"]):
114
114
  data.setdefault("cert", cert)
115
115
 
116
116
  kwargs.pop("base_url", None)
117
- data.update({key: value for key, value in kwargs.items() if key not in data})
117
+ for key, value in kwargs.items():
118
+ if key not in ("headers", "cookies", "params") or key not in data:
119
+ data[key] = value
118
120
  data.setdefault("timeout", DEFAULT_RESPONSE_TIMEOUT)
119
121
 
120
122
  current_session_headers: MutableMapping[str, Any] = {}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schemathesis
3
- Version: 4.3.13
3
+ Version: 4.3.15
4
4
  Summary: Property-based testing framework for Open API and GraphQL based apps
5
5
  Project-URL: Documentation, https://schemathesis.readthedocs.io/en/stable/
6
6
  Project-URL: Changelog, https://github.com/schemathesis/schemathesis/blob/master/CHANGELOG.md
@@ -1,6 +1,6 @@
1
1
  schemathesis/__init__.py,sha256=QqVUCBQr-RDEstgCZLsxzIa9HJslVSeijrm9gES4b_0,1423
2
2
  schemathesis/auths.py,sha256=JdEwPRS9WKmPcxzGXYYz9pjlIUMQYCfif7ZJU0Kde-I,16400
3
- schemathesis/checks.py,sha256=yyR219TQjLAieop5O2waEVLILF6_i10B8mkutd6QaCs,6590
3
+ schemathesis/checks.py,sha256=QaG8ErFCOSYe1GqJKjoiOesv2oZgFTedRUhBZUpTgR8,6879
4
4
  schemathesis/errors.py,sha256=K3irHIZkrBH2-9LIjlgXlm8RNC41Nffd39ncfwagUvw,1053
5
5
  schemathesis/filters.py,sha256=IevPA5A04GfRLLjmkFLZ0CLhjNO3RmpZq_yw6MqjLIA,13515
6
6
  schemathesis/hooks.py,sha256=q2wqYNgpMCO8ImSBkbrWDSwN0BSELelqJMgAAgGvv2M,14836
@@ -62,7 +62,7 @@ schemathesis/core/marks.py,sha256=SH7jsVuNRJjx2gZN9Ze5MY01u7FJiHeO0iruzKi5rm4,21
62
62
  schemathesis/core/media_types.py,sha256=VN1QhgfwBHpTw0CQquzAfhJstxXIXBzSSy0NZmAUcAo,2185
63
63
  schemathesis/core/parameters.py,sha256=20pd4DLW1uXs61YuESKG1Fx9QJJQN3JY7CKnOJMgDb4,790
64
64
  schemathesis/core/rate_limit.py,sha256=7tg9Znk11erTfw8-ANutjEmu7hbfUHZx_iEdkoaP174,1757
65
- schemathesis/core/registries.py,sha256=T4jZB4y3zBHdeSgQc0pRbgSeMblvO-6z4I3zmzIfTi0,811
65
+ schemathesis/core/registries.py,sha256=ksWLxPcohKInH9DwB56KNK0AwGO0Gk5LR2z_Q7cRvps,884
66
66
  schemathesis/core/result.py,sha256=d449YvyONjqjDs-A5DAPgtAI96iT753K8sU6_1HLo2Q,461
67
67
  schemathesis/core/transforms.py,sha256=n_inn8Vb-CFiDUd9Z8E7JIp2kn2eAnxO8DD3OSwOTiM,4310
68
68
  schemathesis/core/transport.py,sha256=LQcamAkFqJ0HuXQzepevAq2MCJW-uq5Nm-HE9yc7HMI,7503
@@ -79,7 +79,7 @@ schemathesis/engine/__init__.py,sha256=QaFE-FinaTAaarteADo2RRMJ-Sz6hZB9TzD5KjMin
79
79
  schemathesis/engine/context.py,sha256=YaBfwTUyTCZaMq7-jtAKFQj-Eh1aQdbZ0UNcC5d_epU,5792
80
80
  schemathesis/engine/control.py,sha256=FXzP8dxL47j1Giqpy2-Bsr_MdMw9YiATSK_UfpFwDtk,1348
81
81
  schemathesis/engine/core.py,sha256=qlPHnZVq2RrUe93fOciXd1hC3E1gVyF2BIWMPMeLIj8,6655
82
- schemathesis/engine/errors.py,sha256=FlpEk44WRLzRkdK9m37z93EQuY3kbeMIQRGwU5e3Qm4,19005
82
+ schemathesis/engine/errors.py,sha256=WVDITmwEgs1O59yrBtQX4_bWCNhdNKZ8DkJXYMRjUpY,16835
83
83
  schemathesis/engine/events.py,sha256=jpCtMkWWfNe2jUeZh_Ly_wfZEF44EOodL-I_W4C9rgg,6594
84
84
  schemathesis/engine/observations.py,sha256=T-5R8GeVIqvxpCMxc6vZ04UUxUTx3w7689r3Dc6bIcE,1416
85
85
  schemathesis/engine/recorder.py,sha256=KWyWkGkZxIwSDU92jNWCJXU4G4E5WqfhLM6G1Yi7Jyo,8636
@@ -89,7 +89,7 @@ schemathesis/engine/phases/stateful/__init__.py,sha256=Lz1rgNqCfUSIz173XqCGsiMuU
89
89
  schemathesis/engine/phases/stateful/_executor.py,sha256=yRpUJqKLTKMVRy7hEXPwmI23CtgGIprz341lCJwvTrU,15613
90
90
  schemathesis/engine/phases/stateful/context.py,sha256=A7X1SLDOWFpCvFN9IiIeNVZM0emjqatmJL_k9UsO7vM,2946
91
91
  schemathesis/engine/phases/unit/__init__.py,sha256=9dDcxyj887pktnE9YDIPNaR-vc7iqKQWIrFr77SbUTQ,8786
92
- schemathesis/engine/phases/unit/_executor.py,sha256=4wr7POpPfeI7_Mx6i2pk2efyK1FxKGjXdMwi_MURTDU,17427
92
+ schemathesis/engine/phases/unit/_executor.py,sha256=9qlc0SEq-9M1mqszB-IaIuaqQYoGkSUD3ygTciUhBl0,17871
93
93
  schemathesis/engine/phases/unit/_pool.py,sha256=iU0hdHDmohPnEv7_S1emcabuzbTf-Cznqwn0pGQ5wNQ,2480
94
94
  schemathesis/generation/__init__.py,sha256=tvNO2FLiY8z3fZ_kL_QJhSgzXfnT4UqwSXMHCwfLI0g,645
95
95
  schemathesis/generation/case.py,sha256=SLMw6zkzmeiZdaIij8_0tjTF70BrMlRSWREaqWii0uM,12508
@@ -99,17 +99,17 @@ schemathesis/generation/metrics.py,sha256=cZU5HdeAMcLFEDnTbNE56NuNq4P0N4ew-g1NEz
99
99
  schemathesis/generation/modes.py,sha256=Q1fhjWr3zxabU5qdtLvKfpMFZJAwlW9pnxgenjeXTyU,481
100
100
  schemathesis/generation/overrides.py,sha256=xI2djHsa42fzP32xpxgxO52INixKagf5DjDAWJYswM8,3890
101
101
  schemathesis/generation/hypothesis/__init__.py,sha256=68BHULoXQC1WjFfw03ga5lvDGZ-c-J7H_fNEuUzFWRw,4976
102
- schemathesis/generation/hypothesis/builder.py,sha256=pnPfJIXBYKyju98xiGUvavh5W2xvuO89RO_NOsvdxYQ,38443
103
- schemathesis/generation/hypothesis/examples.py,sha256=6eGaKUEC3elmKsaqfKj1sLvM8EHc-PWT4NRBq4NI0Rs,1409
102
+ schemathesis/generation/hypothesis/builder.py,sha256=N9UiJfwUplmrCLsROHQtirm1UPt_TDqUiLv4as2qAjU,38562
103
+ schemathesis/generation/hypothesis/examples.py,sha256=9fc3fQW1e0ZxJuHyyCGhPeyL_gCtb8h29XK-Yz-s1Vk,1716
104
104
  schemathesis/generation/hypothesis/given.py,sha256=sTZR1of6XaHAPWtHx2_WLlZ50M8D5Rjux0GmWkWjDq4,2337
105
- schemathesis/generation/hypothesis/reporting.py,sha256=uDVow6Ya8YFkqQuOqRsjbzsbyP4KKfr3jA7ZaY4FuKY,279
105
+ schemathesis/generation/hypothesis/reporting.py,sha256=d5vuv5KF4bgIoMV2qy_LwikfowK8BQkj8fjORMJT6PU,11481
106
106
  schemathesis/generation/stateful/__init__.py,sha256=s7jiJEnguIj44IsRyMi8afs-8yjIUuBbzW58bH5CHjs,1042
107
107
  schemathesis/generation/stateful/state_machine.py,sha256=CiVtpBEeotpNOUkYO3vJLKRe89gdT1kjguZ88vbfqs0,9500
108
108
  schemathesis/graphql/__init__.py,sha256=_eO6MAPHGgiADVGRntnwtPxmuvk666sAh-FAU4cG9-0,326
109
109
  schemathesis/graphql/checks.py,sha256=IADbxiZjgkBWrC5yzHDtohRABX6zKXk5w_zpWNwdzYo,3186
110
110
  schemathesis/graphql/loaders.py,sha256=2tgG4HIvFmjHLr_KexVXnT8hSBM-dKG_fuXTZgE97So,9445
111
111
  schemathesis/openapi/__init__.py,sha256=-KcsSAM19uOM0N5J4s-yTnQ1BFsptYhW1E51cEf6kVM,311
112
- schemathesis/openapi/checks.py,sha256=nrkkagRqg-HOsDCAMbJqCnHyBZEA2PpRV_AB8lI_I9c,13080
112
+ schemathesis/openapi/checks.py,sha256=icUnRTkfFNP0nOYCyaj7hFHO8gJic0XPKxP0xHkm0Qw,13062
113
113
  schemathesis/openapi/loaders.py,sha256=aaCIf6P8R33l6DBNGD_99m_wruYOPR7ecyL5hT6UChg,10710
114
114
  schemathesis/openapi/generation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
115
115
  schemathesis/openapi/generation/filters.py,sha256=pY9cUZdL_kQK80Z2aylTOqqa12zmaYUlYC5BfYgeQMk,2395
@@ -117,7 +117,7 @@ schemathesis/pytest/__init__.py,sha256=7W0q-Thcw03IAQfXE_Mo8JPZpUdHJzfu85fjK1Zdf
117
117
  schemathesis/pytest/control_flow.py,sha256=F8rAPsPeNv_sJiJgbZYtTpwKWjauZmqFUaKroY2GmQI,217
118
118
  schemathesis/pytest/lazy.py,sha256=wP0sqcVFcD-OjDIFUpYdJdFQ-BY18CVyL0iB6eHiWRw,11088
119
119
  schemathesis/pytest/loaders.py,sha256=Sbv8e5F77_x4amLP50iwubfm6kpOhx7LhLFGsVXW5Ys,925
120
- schemathesis/pytest/plugin.py,sha256=8VaaYoO4RtgtZLOAHiheFNjd0xceyKe0U1n6AV5s9kY,14253
120
+ schemathesis/pytest/plugin.py,sha256=fAT76woft3Uak5kGPpt3I8n_LjnzBDjl1adHrho0dA4,14900
121
121
  schemathesis/python/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
122
122
  schemathesis/python/asgi.py,sha256=5PyvuTBaivvyPUEi3pwJni91K1kX5Zc0u9c6c1D8a1Q,287
123
123
  schemathesis/python/wsgi.py,sha256=uShAgo_NChbfYaV1117e6UHp0MTg7jaR0Sy_to3Jmf8,219
@@ -125,12 +125,12 @@ schemathesis/specs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
125
125
  schemathesis/specs/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
126
126
  schemathesis/specs/graphql/nodes.py,sha256=bE3G1kNmqJ8OV4igBvIK-UORrkQA6Nofduf87O3TD9I,541
127
127
  schemathesis/specs/graphql/scalars.py,sha256=6lew8mnwhrtg23leiEbG43mLGPLlRln8mClCY94XpDA,2680
128
- schemathesis/specs/graphql/schemas.py,sha256=GKJcnTAT1wUzzUr3r6wiTfiAdFLcgFQjYRRz7x4VQl0,14457
128
+ schemathesis/specs/graphql/schemas.py,sha256=oNbluhfxpbxRWVIMoLn3_g-bUaVLCea29jOZiHN1MT0,14509
129
129
  schemathesis/specs/graphql/validation.py,sha256=-W1Noc1MQmTb4RX-gNXMeU2qkgso4mzVfHxtdLkCPKM,1422
130
130
  schemathesis/specs/openapi/__init__.py,sha256=C5HOsfuDJGq_3mv8CRBvRvb0Diy1p0BFdqyEXMS-loE,238
131
131
  schemathesis/specs/openapi/_hypothesis.py,sha256=O8vN-koBjzBVZfpD3pmgIt6ecU4ddAPHOxTAORd23Lo,22642
132
- schemathesis/specs/openapi/checks.py,sha256=12ks0V2F8-YKPkItgAc0ZrxsHufWWlsgj-jpj-cF40A,31578
133
- schemathesis/specs/openapi/converter.py,sha256=4a6-8STT5snF7B-t6IsOIGdK5rV16oNqsdvWL7VFf2M,6472
132
+ schemathesis/specs/openapi/checks.py,sha256=tb2s8azZYcO__Jf13ONUstO1inXoZBlo3dD2uuABB24,31712
133
+ schemathesis/specs/openapi/converter.py,sha256=tnh08EDvmac5ONaCUmBwrblpWu43TaQ7qhJZeh5PJIc,6963
134
134
  schemathesis/specs/openapi/definitions.py,sha256=8htclglV3fW6JPBqs59lgM4LnA25Mm9IptXBPb_qUT0,93949
135
135
  schemathesis/specs/openapi/examples.py,sha256=uHV1HRqFhwpGNsBWHt7WmehyIyr8d-n-VeKKs4FRt2c,24475
136
136
  schemathesis/specs/openapi/formats.py,sha256=4tYRdckauHxkJCmOhmdwDq_eOpHPaKloi89lzMPbPzw,3975
@@ -175,11 +175,11 @@ schemathesis/specs/openapi/types/v3.py,sha256=Vondr9Amk6JKCIM6i6RGcmTUjFfPgOOqzB
175
175
  schemathesis/transport/__init__.py,sha256=6yg_RfV_9L0cpA6qpbH-SL9_3ggtHQji9CZrpIkbA6s,5321
176
176
  schemathesis/transport/asgi.py,sha256=qTClt6oT_xUEWnRHokACN_uqCNNUZrRPT6YG0PjbElY,926
177
177
  schemathesis/transport/prepare.py,sha256=erYXRaxpQokIDzaIuvt_csHcw72iHfCyNq8VNEzXd0o,4743
178
- schemathesis/transport/requests.py,sha256=wriRI9fprTplE_qEZLEz1TerX6GwkE3pwr6ZnU2o6vQ,10648
178
+ schemathesis/transport/requests.py,sha256=jJAKqmsnlErU066WlUTIZHa3rx5lFtHWT43nm8CewUc,10717
179
179
  schemathesis/transport/serialization.py,sha256=GwO6OAVTmL1JyKw7HiZ256tjV4CbrRbhQN0ep1uaZwI,11157
180
180
  schemathesis/transport/wsgi.py,sha256=kQtasFre6pjdJWRKwLA_Qb-RyQHCFNpaey9ubzlFWKI,5907
181
- schemathesis-4.3.13.dist-info/METADATA,sha256=n-G9iaIj5lgmV9oro_G7FL4f2tAeI9xOXaLe95r6WnA,8566
182
- schemathesis-4.3.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
183
- schemathesis-4.3.13.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
184
- schemathesis-4.3.13.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
185
- schemathesis-4.3.13.dist-info/RECORD,,
181
+ schemathesis-4.3.15.dist-info/METADATA,sha256=4huPYaRVN0uReWxrIBrV0LSn1mLHu91RxF-O-J9KIIo,8566
182
+ schemathesis-4.3.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
183
+ schemathesis-4.3.15.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
184
+ schemathesis-4.3.15.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
185
+ schemathesis-4.3.15.dist-info/RECORD,,