schemathesis 3.36.3__py3-none-any.whl → 3.37.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,8 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import json
7
- from copy import copy
8
7
  import warnings
8
+ from copy import copy
9
9
  from typing import TYPE_CHECKING, Any, Callable, Generator, Mapping
10
10
 
11
11
  import hypothesis
@@ -36,6 +36,7 @@ from ..filters import FilterSet, expression_to_filter_function, is_deprecated
36
36
  from ..fixups import ALL_FIXUPS
37
37
  from ..generation import DEFAULT_DATA_GENERATION_METHODS, DataGenerationMethod
38
38
  from ..hooks import GLOBAL_HOOK_DISPATCHER, HookContext, HookDispatcher, HookScope
39
+ from ..internal.checks import CheckConfig, PositiveDataAcceptanceConfig
39
40
  from ..internal.datetime import current_datetime
40
41
  from ..internal.output import OutputConfig
41
42
  from ..internal.validation import file_exists
@@ -52,7 +53,7 @@ from .context import ExecutionContext, FileReportContext, ServiceReportContext
52
53
  from .debug import DebugOutputHandler
53
54
  from .handlers import EventHandler
54
55
  from .junitxml import JunitXMLHandler
55
- from .options import CsvChoice, CsvEnumChoice, CustomHelpMessageChoice, OptionalInt
56
+ from .options import CsvChoice, CsvEnumChoice, CsvListChoice, CustomHelpMessageChoice, OptionalInt
56
57
  from .sanitization import SanitizationHandler
57
58
 
58
59
  if TYPE_CHECKING:
@@ -296,6 +297,14 @@ REPORT_TO_SERVICE = ReportToService()
296
297
  default=False,
297
298
  help="Simulate test execution without making any actual requests, useful for validating data generation",
298
299
  )
300
+ @grouped_option(
301
+ "--fixups",
302
+ help="Apply compatibility adjustments",
303
+ multiple=True,
304
+ type=click.Choice([*ALL_FIXUPS, "all"]),
305
+ metavar="",
306
+ )
307
+ @group("Experimental options")
299
308
  @grouped_option(
300
309
  "--experimental",
301
310
  "experiments",
@@ -307,6 +316,7 @@ REPORT_TO_SERVICE = ReportToService()
307
316
  experimental.STATEFUL_TEST_RUNNER.name,
308
317
  experimental.STATEFUL_ONLY.name,
309
318
  experimental.COVERAGE_PHASE.name,
319
+ experimental.POSITIVE_DATA_ACCEPTANCE.name,
310
320
  ]
311
321
  ),
312
322
  callback=callbacks.convert_experimental,
@@ -314,11 +324,22 @@ REPORT_TO_SERVICE = ReportToService()
314
324
  metavar="",
315
325
  )
316
326
  @grouped_option(
317
- "--fixups",
318
- help="Apply compatibility adjustments",
319
- multiple=True,
320
- type=click.Choice([*ALL_FIXUPS, "all"]),
327
+ "--experimental-positive-data-acceptance-allowed-statuses",
328
+ "positive_data_acceptance_allowed_statuses",
329
+ help="Comma-separated list of status codes considered as successful responses",
330
+ type=CsvListChoice(),
331
+ callback=callbacks.convert_status_codes,
332
+ metavar="",
333
+ envvar="SCHEMATHESIS_EXPERIMENTAL_POSITIVE_DATA_ACCEPTANCE_ALLOWED_STATUSES",
334
+ )
335
+ @grouped_option(
336
+ "--experimental-negative-data-rejection-allowed-statuses",
337
+ "negative_data_rejection_allowed_statuses",
338
+ help="Comma-separated list of status codes expected for rejected negative data",
339
+ type=CsvListChoice(),
340
+ callback=callbacks.convert_status_codes,
321
341
  metavar="",
342
+ envvar="SCHEMATHESIS_EXPERIMENTAL_NEGATIVE_DATA_REJECTION_ALLOWED_STATUSES",
322
343
  )
323
344
  @group("API validation options")
324
345
  @grouped_option(
@@ -834,6 +855,8 @@ def run(
834
855
  set_cookie: dict[str, str],
835
856
  set_path: dict[str, str],
836
857
  experiments: list,
858
+ positive_data_acceptance_allowed_statuses: list[str],
859
+ negative_data_rejection_allowed_statuses: list[str],
837
860
  checks: Iterable[str] = DEFAULT_CHECKS_NAMES,
838
861
  exclude_checks: Iterable[str] = (),
839
862
  data_generation_methods: tuple[DataGenerationMethod, ...] = DEFAULT_DATA_GENERATION_METHODS,
@@ -1142,6 +1165,16 @@ def run(
1142
1165
  else:
1143
1166
  selected_checks = tuple(check for check in checks_module.ALL_CHECKS if check.__name__ in checks)
1144
1167
 
1168
+ checks_config = CheckConfig()
1169
+ if experimental.POSITIVE_DATA_ACCEPTANCE.is_enabled:
1170
+ from ..specs.openapi.checks import positive_data_acceptance
1171
+
1172
+ selected_checks += (positive_data_acceptance,)
1173
+ if positive_data_acceptance_allowed_statuses:
1174
+ checks_config.positive_data_acceptance.allowed_statuses = positive_data_acceptance_allowed_statuses
1175
+ if negative_data_rejection_allowed_statuses:
1176
+ checks_config.negative_data_rejection.allowed_statuses = negative_data_rejection_allowed_statuses
1177
+
1145
1178
  selected_checks = tuple(check for check in selected_checks if check.__name__ not in exclude_checks)
1146
1179
 
1147
1180
  if fixups:
@@ -1197,6 +1230,7 @@ def run(
1197
1230
  stateful_recursion_limit=stateful_recursion_limit,
1198
1231
  hypothesis_settings=hypothesis_settings,
1199
1232
  generation_config=generation_config,
1233
+ checks_config=checks_config,
1200
1234
  output_config=output_config,
1201
1235
  service_client=client,
1202
1236
  filter_set=filter_set,
@@ -1300,6 +1334,7 @@ def into_event_stream(
1300
1334
  filter_set: FilterSet,
1301
1335
  # Runtime behavior
1302
1336
  checks: Iterable[CheckFunction],
1337
+ checks_config: CheckConfig,
1303
1338
  max_response_time: int | None,
1304
1339
  targets: Iterable[Target],
1305
1340
  workers_num: int,
@@ -1358,6 +1393,7 @@ def into_event_stream(
1358
1393
  dry_run=dry_run,
1359
1394
  store_interactions=store_interactions,
1360
1395
  checks=checks,
1396
+ checks_config=checks_config,
1361
1397
  max_response_time=max_response_time,
1362
1398
  targets=targets,
1363
1399
  workers_num=workers_num,
@@ -344,6 +344,48 @@ def convert_checks(ctx: click.core.Context, param: click.core.Parameter, value:
344
344
  return reduce(operator.iadd, value, [])
345
345
 
346
346
 
347
+ def convert_status_codes(
348
+ ctx: click.core.Context, param: click.core.Parameter, value: list[str] | None
349
+ ) -> list[str] | None:
350
+ if not value:
351
+ return value
352
+
353
+ invalid = []
354
+
355
+ for code in value:
356
+ if len(code) != 3:
357
+ invalid.append(code)
358
+ continue
359
+
360
+ if code[0] not in {"1", "2", "3", "4", "5"}:
361
+ invalid.append(code)
362
+ continue
363
+
364
+ upper_code = code.upper()
365
+
366
+ if "X" in upper_code:
367
+ if (
368
+ upper_code[1:] == "XX"
369
+ or (upper_code[1] == "X" and upper_code[2].isdigit())
370
+ or (upper_code[1].isdigit() and upper_code[2] == "X")
371
+ ):
372
+ continue
373
+ else:
374
+ invalid.append(code)
375
+ continue
376
+
377
+ if not code.isnumeric():
378
+ invalid.append(code)
379
+
380
+ if invalid:
381
+ raise click.UsageError(
382
+ f"Invalid status code(s): {', '.join(invalid)}. "
383
+ "Use valid 3-digit codes between 100 and 599, "
384
+ "or wildcards (e.g., 2XX, 2X0, 20X), where X is a wildcard digit."
385
+ )
386
+ return value
387
+
388
+
347
389
  def convert_code_sample_style(ctx: click.core.Context, param: click.core.Parameter, value: str) -> CodeSampleStyle:
348
390
  return CodeSampleStyle.from_str(value)
349
391
 
@@ -79,24 +79,18 @@ class CassetteWriter(EventHandler):
79
79
  def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
80
80
  if isinstance(event, events.Initialized):
81
81
  # In the beginning we write metadata and start `http_interactions` list
82
- self.queue.put(Initialize())
82
+ self.queue.put(Initialize(seed=event.seed))
83
83
  elif isinstance(event, events.AfterExecution):
84
- # Seed is always present at this point, the original Optional[int] type is there because `TestResult`
85
- # instance is created before `seed` is generated on the hypothesis side
86
- seed = cast(int, event.result.seed)
87
84
  self.queue.put(
88
85
  Process(
89
- seed=seed,
90
86
  correlation_id=event.correlation_id,
91
87
  thread_id=event.thread_id,
92
88
  interactions=event.result.interactions,
93
89
  )
94
90
  )
95
91
  elif isinstance(event, events.AfterStatefulExecution):
96
- seed = cast(int, event.result.seed)
97
92
  self.queue.put(
98
93
  Process(
99
- seed=seed,
100
94
  # Correlation ID is not used in stateful testing
101
95
  correlation_id="",
102
96
  thread_id=event.thread_id,
@@ -118,12 +112,13 @@ class CassetteWriter(EventHandler):
118
112
  class Initialize:
119
113
  """Start up, the first message to make preparations before proceeding the input data."""
120
114
 
115
+ seed: int | None
116
+
121
117
 
122
118
  @dataclass
123
119
  class Process:
124
120
  """A new chunk of data should be processed."""
125
121
 
126
- seed: int
127
122
  correlation_id: str
128
123
  thread_id: int
129
124
  interactions: list[SerializedInteraction]
@@ -219,9 +214,11 @@ def vcr_writer(file_handle: click.utils.LazyFile, preserve_exact_body_bytes: boo
219
214
  )
220
215
  write_double_quoted(output, string)
221
216
 
217
+ seed = "null"
222
218
  while True:
223
219
  item = queue.get()
224
220
  if isinstance(item, Initialize):
221
+ seed = f"'{item.seed}'"
225
222
  stream.write(
226
223
  f"""command: '{get_command_representation()}'
227
224
  recorded_with: 'Schemathesis {SCHEMATHESIS_VERSION}'
@@ -235,7 +232,7 @@ http_interactions:"""
235
232
  stream.write(
236
233
  f"""\n- id: '{current_id}'
237
234
  status: '{status}'
238
- seed: '{item.seed}'
235
+ seed: {seed}
239
236
  thread_id: {item.thread_id}
240
237
  correlation_id: '{item.correlation_id}'
241
238
  data_generation_method: '{interaction.data_generation_method.value}'
@@ -58,6 +58,13 @@ class CsvChoice(BaseCsvChoice):
58
58
  self.fail_on_invalid_options(invalid_options, selected)
59
59
 
60
60
 
61
+ class CsvListChoice(click.ParamType):
62
+ def convert( # type: ignore[return]
63
+ self, value: str, param: click.core.Parameter | None, ctx: click.core.Context | None
64
+ ) -> list[str]:
65
+ return [item for item in value.split(",") if item]
66
+
67
+
61
68
  class OptionalInt(click.types.IntRange):
62
69
  def convert( # type: ignore
63
70
  self, value: str, param: click.core.Parameter | None, ctx: click.core.Context | None
@@ -148,6 +148,10 @@ def get_negative_rejection_error(prefix: str, status: int) -> type[CheckFailed]:
148
148
  return _get_hashed_exception(f"AcceptedNegativeDataError{prefix}", str(status))
149
149
 
150
150
 
151
+ def get_positive_acceptance_error(prefix: str, status: int) -> type[CheckFailed]:
152
+ return _get_hashed_exception(f"RejectedPositiveDataError{prefix}", str(status))
153
+
154
+
151
155
  def get_use_after_free_error(free: str) -> type[CheckFailed]:
152
156
  return _get_hashed_exception("UseAfterFreeError", free)
153
157
 
@@ -100,3 +100,10 @@ COVERAGE_PHASE = GLOBAL_EXPERIMENTS.create_experiment(
100
100
  description="Generate covering test cases",
101
101
  discussion_url="https://github.com/schemathesis/schemathesis/discussions/2418",
102
102
  )
103
+ POSITIVE_DATA_ACCEPTANCE = GLOBAL_EXPERIMENTS.create_experiment(
104
+ name="positive_data_acceptance",
105
+ verbose_name="Positive Data Acceptance",
106
+ env_var="POSITIVE_DATA_ACCEPTANCE",
107
+ description="Verifying schema-conformant data is accepted",
108
+ discussion_url="https://github.com/schemathesis/schemathesis/discussions/2499",
109
+ )
schemathesis/failures.py CHANGED
@@ -137,10 +137,23 @@ class AcceptedNegativeData(FailureContext):
137
137
  """Response with negative data was accepted."""
138
138
 
139
139
  message: str
140
+ status_code: int
141
+ allowed_statuses: list[str]
140
142
  title: str = "Accepted negative data"
141
143
  type: str = "accepted_negative_data"
142
144
 
143
145
 
146
+ @dataclass(repr=False)
147
+ class RejectedPositiveData(FailureContext):
148
+ """Response with positive data was rejected."""
149
+
150
+ message: str
151
+ status_code: int
152
+ allowed_statuses: list[str]
153
+ title: str = "Rejected positive data"
154
+ type: str = "rejected_positive_data"
155
+
156
+
144
157
  @dataclass(repr=False)
145
158
  class UseAfterFree(FailureContext):
146
159
  """Resource was used after a successful DELETE operation on it."""
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import os
3
4
  from functools import lru_cache, reduce
4
5
  from operator import or_
5
6
  from typing import TYPE_CHECKING, TypeVar
@@ -8,6 +9,8 @@ if TYPE_CHECKING:
8
9
  from hypothesis import settings
9
10
  from hypothesis import strategies as st
10
11
 
12
+ SCHEMATHESIS_BENCHMARK_SEED = os.environ.get("SCHEMATHESIS_BENCHMARK_SEED")
13
+
11
14
 
12
15
  @lru_cache
13
16
  def default_settings() -> settings:
@@ -33,13 +36,16 @@ def get_single_example(strategy: st.SearchStrategy[T]) -> T: # type: ignore[typ
33
36
 
34
37
 
35
38
  def add_single_example(strategy: st.SearchStrategy[T], examples: list[T]) -> None:
36
- from hypothesis import given
39
+ from hypothesis import given, seed
37
40
 
38
41
  @given(strategy) # type: ignore
39
42
  @default_settings() # type: ignore
40
43
  def example_generating_inner_function(ex: T) -> None:
41
44
  examples.append(ex)
42
45
 
46
+ if SCHEMATHESIS_BENCHMARK_SEED is not None:
47
+ example_generating_inner_function = seed(SCHEMATHESIS_BENCHMARK_SEED)(example_generating_inner_function)
48
+
43
49
  example_generating_inner_function()
44
50
 
45
51
 
@@ -26,6 +26,10 @@ class DataGenerationMethod(str, Enum):
26
26
  DataGenerationMethod.negative: "N",
27
27
  }[self]
28
28
 
29
+ @property
30
+ def is_positive(self) -> bool:
31
+ return self == DataGenerationMethod.positive
32
+
29
33
  @property
30
34
  def is_negative(self) -> bool:
31
35
  return self == DataGenerationMethod.negative
@@ -1,9 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import functools
3
4
  import json
5
+ import re
4
6
  from contextlib import contextmanager, suppress
5
- from dataclasses import dataclass, field
6
- from functools import lru_cache
7
+ from dataclasses import dataclass
8
+ from functools import lru_cache, partial
7
9
  from itertools import combinations
8
10
  from typing import Any, Generator, Iterator, TypeVar, cast
9
11
 
@@ -13,17 +15,25 @@ from hypothesis.errors import InvalidArgument, Unsatisfiable
13
15
  from hypothesis_jsonschema import from_schema
14
16
  from hypothesis_jsonschema._canonicalise import canonicalish
15
17
 
16
- from schemathesis.constants import NOT_SET
17
-
18
+ from ..constants import NOT_SET
19
+ from ..specs.openapi.patterns import update_quantifier
18
20
  from ._hypothesis import get_single_example
19
21
  from ._methods import DataGenerationMethod
20
22
 
23
+
24
+ def _replace_zero_with_nonzero(x: float) -> float:
25
+ return x or 0.0
26
+
27
+
28
+ def json_recursive_strategy(strategy: st.SearchStrategy) -> st.SearchStrategy:
29
+ return st.lists(strategy, max_size=3) | st.dictionaries(st.text(), strategy, max_size=3)
30
+
31
+
21
32
  BUFFER_SIZE = 8 * 1024
22
- FLOAT_STRATEGY: st.SearchStrategy = st.floats(allow_nan=False, allow_infinity=False).map(lambda x: x or 0.0)
33
+ FLOAT_STRATEGY: st.SearchStrategy = st.floats(allow_nan=False, allow_infinity=False).map(_replace_zero_with_nonzero)
23
34
  NUMERIC_STRATEGY: st.SearchStrategy = st.integers() | FLOAT_STRATEGY
24
35
  JSON_STRATEGY: st.SearchStrategy = st.recursive(
25
- st.none() | st.booleans() | NUMERIC_STRATEGY | st.text(),
26
- lambda strategy: st.lists(strategy, max_size=3) | st.dictionaries(st.text(), strategy, max_size=3),
36
+ st.none() | st.booleans() | NUMERIC_STRATEGY | st.text(), json_recursive_strategy
27
37
  )
28
38
  ARRAY_STRATEGY: st.SearchStrategy = st.lists(JSON_STRATEGY)
29
39
  OBJECT_STRATEGY: st.SearchStrategy = st.dictionaries(st.text(), JSON_STRATEGY)
@@ -60,7 +70,14 @@ def cached_draw(strategy: st.SearchStrategy) -> Any:
60
70
 
61
71
  @dataclass
62
72
  class CoverageContext:
63
- data_generation_methods: list[DataGenerationMethod] = field(default_factory=DataGenerationMethod.all)
73
+ data_generation_methods: list[DataGenerationMethod]
74
+
75
+ __slots__ = ("data_generation_methods",)
76
+
77
+ def __init__(self, data_generation_methods: list[DataGenerationMethod] | None = None) -> None:
78
+ self.data_generation_methods = (
79
+ data_generation_methods if data_generation_methods is not None else DataGenerationMethod.all()
80
+ )
64
81
 
65
82
  @classmethod
66
83
  def with_positive(cls) -> CoverageContext:
@@ -70,12 +87,8 @@ class CoverageContext:
70
87
  def with_negative(cls) -> CoverageContext:
71
88
  return CoverageContext(data_generation_methods=[DataGenerationMethod.negative])
72
89
 
73
- def generate_from(self, strategy: st.SearchStrategy, cached: bool = False) -> Any:
74
- if cached:
75
- value = cached_draw(strategy)
76
- else:
77
- value = get_single_example(strategy)
78
- return value
90
+ def generate_from(self, strategy: st.SearchStrategy) -> Any:
91
+ return cached_draw(strategy)
79
92
 
80
93
  def generate_from_schema(self, schema: dict) -> Any:
81
94
  return self.generate_from(from_schema(schema))
@@ -221,14 +234,23 @@ def cover_schema_iter(
221
234
  seen.add(k)
222
235
  elif key == "minLength" and 0 < value < BUFFER_SIZE:
223
236
  with suppress(InvalidArgument):
224
- value = ctx.generate_from_schema({**schema, "minLength": value - 1, "maxLength": value - 1})
237
+ min_length = max_length = value - 1
238
+ new_schema = {**schema, "minLength": min_length, "maxLength": max_length}
239
+ if "pattern" in new_schema:
240
+ new_schema["pattern"] = update_quantifier(schema["pattern"], min_length, max_length)
241
+ value = ctx.generate_from_schema(new_schema)
225
242
  k = _to_hashable_key(value)
226
243
  if k not in seen:
227
244
  yield NegativeValue(value, description="String smaller than minLength")
228
245
  seen.add(k)
229
246
  elif key == "maxLength" and value < BUFFER_SIZE:
230
- with suppress(InvalidArgument):
231
- value = ctx.generate_from_schema({**schema, "minLength": value + 1, "maxLength": value + 1})
247
+ with suppress(InvalidArgument, Unsatisfiable):
248
+ min_length = value + 1
249
+ max_length = value + 1
250
+ new_schema = {**schema, "minLength": min_length, "maxLength": max_length}
251
+ if "pattern" in new_schema:
252
+ new_schema["pattern"] = update_quantifier(schema["pattern"], min_length, max_length)
253
+ value = ctx.generate_from_schema(new_schema)
232
254
  k = _to_hashable_key(value)
233
255
  if k not in seen:
234
256
  yield NegativeValue(value, description="String larger than maxLength")
@@ -554,9 +576,12 @@ def select_combinations(optional: list[str]) -> Iterator[tuple[str, ...]]:
554
576
 
555
577
 
556
578
  def _negative_enum(ctx: CoverageContext, value: list) -> Generator[GeneratedValue, None, None]:
557
- strategy = JSON_STRATEGY.filter(lambda x: x not in value)
579
+ def is_not_in_value(x: Any) -> bool:
580
+ return x not in value
581
+
582
+ strategy = JSON_STRATEGY.filter(is_not_in_value)
558
583
  # The exact negative value is not important here
559
- yield NegativeValue(ctx.generate_from(strategy, cached=True), description="Invalid enum value")
584
+ yield NegativeValue(ctx.generate_from(strategy), description="Invalid enum value")
560
585
 
561
586
 
562
587
  def _negative_properties(
@@ -571,9 +596,13 @@ def _negative_properties(
571
596
  )
572
597
 
573
598
 
599
+ def _not_matching_pattern(value: str, pattern: str) -> bool:
600
+ return re.search(pattern, value) is None
601
+
602
+
574
603
  def _negative_pattern(ctx: CoverageContext, pattern: str) -> Generator[GeneratedValue, None, None]:
575
604
  yield NegativeValue(
576
- ctx.generate_from(st.text().filter(lambda x: x != pattern), cached=True),
605
+ ctx.generate_from(st.text().filter(partial(_not_matching_pattern, pattern=pattern))),
577
606
  description=f"Value not matching the '{pattern}' pattern",
578
607
  )
579
608
 
@@ -606,19 +635,31 @@ def _negative_required(
606
635
  )
607
636
 
608
637
 
638
+ def _is_invalid_hostname(v: Any) -> bool:
639
+ return v == "" or not jsonschema.Draft202012Validator.FORMAT_CHECKER.conforms(v, "hostname")
640
+
641
+
642
+ def _is_invalid_format(v: Any, format: str) -> bool:
643
+ return not jsonschema.Draft202012Validator.FORMAT_CHECKER.conforms(v, format)
644
+
645
+
609
646
  def _negative_format(ctx: CoverageContext, schema: dict, format: str) -> Generator[GeneratedValue, None, None]:
610
647
  # Hypothesis-jsonschema does not canonicalise it properly right now, which leads to unsatisfiable schema
611
648
  without_format = {k: v for k, v in schema.items() if k != "format"}
612
649
  without_format.setdefault("type", "string")
613
650
  strategy = from_schema(without_format)
614
651
  if format in jsonschema.Draft202012Validator.FORMAT_CHECKER.checkers:
615
- strategy = strategy.filter(
616
- lambda v: (format == "hostname" and v == "")
617
- or not jsonschema.Draft202012Validator.FORMAT_CHECKER.conforms(v, format)
618
- )
652
+ if format == "hostname":
653
+ strategy = strategy.filter(_is_invalid_hostname)
654
+ else:
655
+ strategy = strategy.filter(functools.partial(_is_invalid_format, format=format))
619
656
  yield NegativeValue(ctx.generate_from(strategy), description=f"Value not matching the '{format}' format")
620
657
 
621
658
 
659
+ def _is_non_integer_float(x: float) -> bool:
660
+ return x != int(x)
661
+
662
+
622
663
  def _negative_type(ctx: CoverageContext, seen: set, ty: str | list[str]) -> Generator[GeneratedValue, None, None]:
623
664
  strategies = {
624
665
  "integer": st.integers(),
@@ -638,9 +679,9 @@ def _negative_type(ctx: CoverageContext, seen: set, ty: str | list[str]) -> Gene
638
679
  if "number" in types:
639
680
  del strategies["integer"]
640
681
  if "integer" in types:
641
- strategies["number"] = FLOAT_STRATEGY.filter(lambda x: x != int(x))
682
+ strategies["number"] = FLOAT_STRATEGY.filter(_is_non_integer_float)
642
683
  for strat in strategies.values():
643
- value = ctx.generate_from(strat, cached=True)
684
+ value = ctx.generate_from(strat)
644
685
  hashed = _to_hashable_key(value)
645
686
  if hashed in seen:
646
687
  continue
schemathesis/hooks.py CHANGED
@@ -285,9 +285,9 @@ class HookDispatcher:
285
285
  self._hooks = defaultdict(list)
286
286
 
287
287
 
288
- def _should_skip_hook(hook: Callable, context: HookContext) -> bool:
288
+ def _should_skip_hook(hook: Callable, ctx: HookContext) -> bool:
289
289
  filter_set = getattr(hook, "filter_set", None)
290
- return filter_set is not None and not filter_set.match(context)
290
+ return filter_set is not None and ctx.operation is not None and not filter_set.match(ctx)
291
291
 
292
292
 
293
293
  def apply_to_all_dispatchers(
@@ -2,10 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  import inspect
4
4
  import warnings
5
- from dataclasses import dataclass
5
+ from dataclasses import dataclass, field
6
6
  from typing import TYPE_CHECKING, Callable, Optional
7
7
 
8
8
  if TYPE_CHECKING:
9
+ from requests.auth import HTTPDigestAuth
9
10
  from requests.structures import CaseInsensitiveDict
10
11
 
11
12
  from ..models import Case
@@ -16,6 +17,23 @@ if TYPE_CHECKING:
16
17
  CheckFunction = Callable[["CheckContext", "GenericResponse", "Case"], Optional[bool]]
17
18
 
18
19
 
20
+ @dataclass
21
+ class NegativeDataRejectionConfig:
22
+ # 5xx will pass through
23
+ allowed_statuses: list[str] = field(default_factory=lambda: ["4xx", "5xx"])
24
+
25
+
26
+ @dataclass
27
+ class PositiveDataAcceptanceConfig:
28
+ allowed_statuses: list[str] = field(default_factory=lambda: ["2xx", "401", "403", "404"])
29
+
30
+
31
+ @dataclass
32
+ class CheckConfig:
33
+ negative_data_rejection: NegativeDataRejectionConfig = field(default_factory=NegativeDataRejectionConfig)
34
+ positive_data_acceptance: PositiveDataAcceptanceConfig = field(default_factory=PositiveDataAcceptanceConfig)
35
+
36
+
19
37
  @dataclass
20
38
  class CheckContext:
21
39
  """Context for Schemathesis checks.
@@ -23,8 +41,9 @@ class CheckContext:
23
41
  Provides access to broader test execution data beyond individual test cases.
24
42
  """
25
43
 
26
- auth: RawAuth | None = None
44
+ auth: HTTPDigestAuth | RawAuth | None = None
27
45
  headers: CaseInsensitiveDict | None = None
46
+ config: CheckConfig = field(default_factory=CheckConfig)
28
47
 
29
48
 
30
49
  def wrap_check(check: Callable) -> CheckFunction:
schemathesis/lazy.py CHANGED
@@ -1,15 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from contextlib import nullcontext
3
4
  from dataclasses import dataclass, field
4
5
  from inspect import signature
5
- from typing import TYPE_CHECKING, Any, Callable, Generator
6
+ from typing import TYPE_CHECKING, Any, Callable, Generator, Type
6
7
 
7
8
  import pytest
8
9
  from hypothesis.core import HypothesisHandle
9
10
  from hypothesis.errors import Flaky
10
11
  from hypothesis.internal.escalation import format_exception, get_trimmed_traceback
11
12
  from hypothesis.internal.reflection import impersonate
12
- from pytest_subtests import SubTests, nullcontext
13
+ from pytest_subtests import SubTests
13
14
 
14
15
  from ._compat import MultipleFailures, get_interesting_origin
15
16
  from ._override import CaseOverride, check_no_override_mark, get_override_from_mark, set_override_mark
@@ -310,7 +311,7 @@ def _copy_marks(source: Callable, target: Callable) -> None:
310
311
  target.pytestmark.extend(marks) # type: ignore
311
312
 
312
313
 
313
- def _get_capturemanager(request: FixtureRequest) -> Generator:
314
+ def _get_capturemanager(request: FixtureRequest) -> Generator | Type[nullcontext]:
314
315
  capturemanager = request.node.config.pluginmanager.get_plugin("capturemanager")
315
316
  if capturemanager is not None:
316
317
  return capturemanager.global_and_fixture_disabled
@@ -11,6 +11,7 @@ from ..constants import (
11
11
  )
12
12
  from ..exceptions import SchemaError
13
13
  from ..generation import DEFAULT_DATA_GENERATION_METHODS, DataGenerationMethod, GenerationConfig
14
+ from ..internal.checks import CheckConfig
14
15
  from ..internal.datetime import current_datetime
15
16
  from ..internal.deprecation import deprecated_function
16
17
  from ..internal.validation import file_exists
@@ -355,6 +356,7 @@ def from_schema(
355
356
  count_operations: bool = True,
356
357
  count_links: bool = True,
357
358
  probe_config: ProbeConfig | None = None,
359
+ checks_config: CheckConfig | None = None,
358
360
  service_client: ServiceClient | None = None,
359
361
  ) -> BaseRunner:
360
362
  import hypothesis
@@ -371,6 +373,7 @@ def from_schema(
371
373
  )
372
374
 
373
375
  checks = checks or DEFAULT_CHECKS
376
+ checks_config = checks_config or CheckConfig()
374
377
  probe_config = probe_config or ProbeConfig()
375
378
 
376
379
  hypothesis_settings = hypothesis_settings or hypothesis.settings(deadline=DEFAULT_DEADLINE)
@@ -413,6 +416,7 @@ def from_schema(
413
416
  count_operations=count_operations,
414
417
  count_links=count_links,
415
418
  probe_config=probe_config,
419
+ checks_config=checks_config,
416
420
  service_client=service_client,
417
421
  )
418
422
  if is_asgi_app(schema.app):
@@ -439,6 +443,7 @@ def from_schema(
439
443
  count_operations=count_operations,
440
444
  count_links=count_links,
441
445
  probe_config=probe_config,
446
+ checks_config=checks_config,
442
447
  service_client=service_client,
443
448
  )
444
449
  return ThreadPoolWSGIRunner(
@@ -465,6 +470,7 @@ def from_schema(
465
470
  count_operations=count_operations,
466
471
  count_links=count_links,
467
472
  probe_config=probe_config,
473
+ checks_config=checks_config,
468
474
  service_client=service_client,
469
475
  )
470
476
  if not schema.app:
@@ -492,6 +498,7 @@ def from_schema(
492
498
  count_operations=count_operations,
493
499
  count_links=count_links,
494
500
  probe_config=probe_config,
501
+ checks_config=checks_config,
495
502
  service_client=service_client,
496
503
  )
497
504
  if is_asgi_app(schema.app):
@@ -518,6 +525,7 @@ def from_schema(
518
525
  count_operations=count_operations,
519
526
  count_links=count_links,
520
527
  probe_config=probe_config,
528
+ checks_config=checks_config,
521
529
  service_client=service_client,
522
530
  )
523
531
  return SingleThreadWSGIRunner(
@@ -543,6 +551,7 @@ def from_schema(
543
551
  count_operations=count_operations,
544
552
  count_links=count_links,
545
553
  probe_config=probe_config,
554
+ checks_config=checks_config,
546
555
  service_client=service_client,
547
556
  )
548
557
 
@@ -4,6 +4,7 @@ from dataclasses import dataclass
4
4
  from typing import TYPE_CHECKING
5
5
 
6
6
  from ...constants import NOT_SET
7
+ from ...internal.checks import CheckConfig
7
8
  from ...models import TestResult, TestResultSet
8
9
 
9
10
  if TYPE_CHECKING:
@@ -24,11 +25,18 @@ class RunnerContext:
24
25
  stop_event: threading.Event
25
26
  unique_data: bool
26
27
  outcome_cache: dict[int, BaseException | None]
28
+ checks_config: CheckConfig
27
29
 
28
- __slots__ = ("data", "auth", "seed", "stop_event", "unique_data", "outcome_cache")
30
+ __slots__ = ("data", "auth", "seed", "stop_event", "unique_data", "outcome_cache", "checks_config")
29
31
 
30
32
  def __init__(
31
- self, *, seed: int | None, auth: RawAuth | None, stop_event: threading.Event, unique_data: bool
33
+ self,
34
+ *,
35
+ seed: int | None,
36
+ auth: RawAuth | None,
37
+ stop_event: threading.Event,
38
+ unique_data: bool,
39
+ checks_config: CheckConfig,
32
40
  ) -> None:
33
41
  self.data = TestResultSet(seed=seed)
34
42
  self.auth = auth
@@ -36,6 +44,7 @@ class RunnerContext:
36
44
  self.stop_event = stop_event
37
45
  self.outcome_cache = {}
38
46
  self.unique_data = unique_data
47
+ self.checks_config = checks_config
39
48
 
40
49
  @property
41
50
  def is_stopped(self) -> bool:
@@ -57,7 +57,7 @@ from ...exceptions import (
57
57
  )
58
58
  from ...generation import DataGenerationMethod, GenerationConfig
59
59
  from ...hooks import HookContext, get_all_by_name
60
- from ...internal.checks import CheckContext
60
+ from ...internal.checks import CheckConfig, CheckContext
61
61
  from ...internal.datetime import current_datetime
62
62
  from ...internal.result import Err, Ok, Result
63
63
  from ...models import APIOperation, Case, Check, Status, TestResult
@@ -102,6 +102,7 @@ class BaseRunner:
102
102
  hypothesis_settings: hypothesis.settings
103
103
  generation_config: GenerationConfig | None
104
104
  probe_config: probes.ProbeConfig
105
+ checks_config: CheckConfig
105
106
  request_config: RequestConfig = field(default_factory=RequestConfig)
106
107
  override: CaseOverride | None = None
107
108
  auth: RawAuth | None = None
@@ -131,7 +132,13 @@ class BaseRunner:
131
132
  # If auth is explicitly provided, then the global provider is ignored
132
133
  if self.auth is not None:
133
134
  unregister_auth()
134
- ctx = RunnerContext(auth=self.auth, seed=self.seed, stop_event=stop_event, unique_data=self.unique_data)
135
+ ctx = RunnerContext(
136
+ auth=self.auth,
137
+ seed=self.seed,
138
+ stop_event=stop_event,
139
+ unique_data=self.unique_data,
140
+ checks_config=self.checks_config,
141
+ )
135
142
  start_time = time.monotonic()
136
143
  initialized = None
137
144
  __probes = None
@@ -1017,7 +1024,9 @@ def _network_test(
1017
1024
  run_targets(targets, context)
1018
1025
  status = Status.success
1019
1026
 
1020
- check_ctx = CheckContext(auth=ctx.auth, headers=CaseInsensitiveDict(headers) if headers else None)
1027
+ check_ctx = CheckContext(
1028
+ auth=ctx.auth, headers=CaseInsensitiveDict(headers) if headers else None, config=ctx.checks_config
1029
+ )
1021
1030
  try:
1022
1031
  run_checks(
1023
1032
  case=case,
@@ -1109,7 +1118,9 @@ def _wsgi_test(
1109
1118
  result.logs.extend(recorded.records)
1110
1119
  status = Status.success
1111
1120
  check_results: list[Check] = []
1112
- check_ctx = CheckContext(auth=ctx.auth, headers=CaseInsensitiveDict(headers) if headers else None)
1121
+ check_ctx = CheckContext(
1122
+ auth=ctx.auth, headers=CaseInsensitiveDict(headers) if headers else None, config=ctx.checks_config
1123
+ )
1113
1124
  try:
1114
1125
  run_checks(
1115
1126
  case=case,
@@ -1189,7 +1200,9 @@ def _asgi_test(
1189
1200
  run_targets(targets, context)
1190
1201
  status = Status.success
1191
1202
  check_results: list[Check] = []
1192
- check_ctx = CheckContext(auth=ctx.auth, headers=CaseInsensitiveDict(headers) if headers else None)
1203
+ check_ctx = CheckContext(
1204
+ auth=ctx.auth, headers=CaseInsensitiveDict(headers) if headers else None, config=ctx.checks_config
1205
+ )
1193
1206
  try:
1194
1207
  run_checks(
1195
1208
  case=case,
@@ -14,6 +14,7 @@ from ...exceptions import (
14
14
  get_malformed_media_type_error,
15
15
  get_missing_content_type_error,
16
16
  get_negative_rejection_error,
17
+ get_positive_acceptance_error,
17
18
  get_response_type_error,
18
19
  get_schema_validation_error,
19
20
  get_status_code_error,
@@ -21,7 +22,7 @@ from ...exceptions import (
21
22
  )
22
23
  from ...internal.transformation import convert_boolean_string
23
24
  from ...transports.content_types import parse_content_type
24
- from .utils import expand_status_code
25
+ from .utils import expand_status_code, expand_status_codes
25
26
 
26
27
  if TYPE_CHECKING:
27
28
  from requests import PreparedRequest
@@ -218,16 +219,52 @@ def negative_data_rejection(ctx: CheckContext, response: GenericResponse, case:
218
219
 
219
220
  if not isinstance(case.operation.schema, BaseOpenAPISchema):
220
221
  return True
222
+
223
+ config = ctx.config.negative_data_rejection
224
+ allowed_statuses = expand_status_codes(config.allowed_statuses or [])
225
+
221
226
  if (
222
227
  case.data_generation_method
223
228
  and case.data_generation_method.is_negative
224
- and 200 <= response.status_code < 300
229
+ and response.status_code not in allowed_statuses
225
230
  and not has_only_additional_properties_in_non_body_parameters(case)
226
231
  ):
232
+ message = f"Allowed statuses: {', '.join(config.allowed_statuses)}"
227
233
  exc_class = get_negative_rejection_error(case.operation.verbose_name, response.status_code)
228
234
  raise exc_class(
229
235
  failures.AcceptedNegativeData.title,
230
- context=failures.AcceptedNegativeData(message="Negative data was not rejected as expected by the API"),
236
+ context=failures.AcceptedNegativeData(
237
+ message=message,
238
+ status_code=response.status_code,
239
+ allowed_statuses=config.allowed_statuses,
240
+ ),
241
+ )
242
+ return None
243
+
244
+
245
+ def positive_data_acceptance(ctx: CheckContext, response: GenericResponse, case: Case) -> bool | None:
246
+ from .schemas import BaseOpenAPISchema
247
+
248
+ if not isinstance(case.operation.schema, BaseOpenAPISchema):
249
+ return True
250
+
251
+ config = ctx.config.positive_data_acceptance
252
+ allowed_statuses = expand_status_codes(config.allowed_statuses or [])
253
+
254
+ if (
255
+ case.data_generation_method
256
+ and case.data_generation_method.is_positive
257
+ and response.status_code not in allowed_statuses
258
+ ):
259
+ message = f"Allowed statuses: {', '.join(config.allowed_statuses)}"
260
+ exc_class = get_positive_acceptance_error(case.operation.verbose_name, response.status_code)
261
+ raise exc_class(
262
+ failures.RejectedPositiveData.title,
263
+ context=failures.RejectedPositiveData(
264
+ message=message,
265
+ status_code=response.status_code,
266
+ allowed_statuses=config.allowed_statuses,
267
+ ),
231
268
  )
232
269
  return None
233
270
 
@@ -1330,6 +1330,8 @@ OPENAPI_30 = {
1330
1330
  },
1331
1331
  },
1332
1332
  }
1333
+ # Generated from the updated schema.yaml from 0035208, which includes unpublished bugfixes
1334
+ # https://github.com/OAI/OpenAPI-Specification/blob/0035208611701b4f7f2c959eb99a8725cca41e6e/schemas/v3.1/schema.yaml
1333
1335
  OPENAPI_31 = {
1334
1336
  "$id": "https://spec.openapis.org/oas/3.1/schema/2022-10-07",
1335
1337
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -1345,7 +1347,7 @@ OPENAPI_31 = {
1345
1347
  },
1346
1348
  "servers": {"type": "array", "items": {"$ref": "#/$defs/server"}, "default": [{"url": "/"}]},
1347
1349
  "paths": {"$ref": "#/$defs/paths"},
1348
- "webhooks": {"type": "object", "additionalProperties": {"$ref": "#/$defs/path-item-or-reference"}},
1350
+ "webhooks": {"type": "object", "additionalProperties": {"$ref": "#/$defs/path-item"}},
1349
1351
  "components": {"$ref": "#/$defs/components"},
1350
1352
  "security": {"type": "array", "items": {"$ref": "#/$defs/security-requirement"}},
1351
1353
  "tags": {"type": "array", "items": {"$ref": "#/$defs/tag"}},
@@ -1400,7 +1402,7 @@ OPENAPI_31 = {
1400
1402
  "$comment": "https://spec.openapis.org/oas/v3.1.0#server-object",
1401
1403
  "type": "object",
1402
1404
  "properties": {
1403
- "url": {"type": "string", "format": "uri-reference"},
1405
+ "url": {"type": "string"},
1404
1406
  "description": {"type": "string"},
1405
1407
  "variables": {"type": "object", "additionalProperties": {"$ref": "#/$defs/server-variable"}},
1406
1408
  },
@@ -1439,7 +1441,7 @@ OPENAPI_31 = {
1439
1441
  },
1440
1442
  "links": {"type": "object", "additionalProperties": {"$ref": "#/$defs/link-or-reference"}},
1441
1443
  "callbacks": {"type": "object", "additionalProperties": {"$ref": "#/$defs/callbacks-or-reference"}},
1442
- "pathItems": {"type": "object", "additionalProperties": {"$ref": "#/$defs/path-item-or-reference"}},
1444
+ "pathItems": {"type": "object", "additionalProperties": {"$ref": "#/$defs/path-item"}},
1443
1445
  },
1444
1446
  "patternProperties": {
1445
1447
  "^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$": {
@@ -1461,6 +1463,7 @@ OPENAPI_31 = {
1461
1463
  "$comment": "https://spec.openapis.org/oas/v3.1.0#path-item-object",
1462
1464
  "type": "object",
1463
1465
  "properties": {
1466
+ "$ref": {"type": "string", "format": "uri-reference"},
1464
1467
  "summary": {"type": "string"},
1465
1468
  "description": {"type": "string"},
1466
1469
  "servers": {"type": "array", "items": {"$ref": "#/$defs/server"}},
@@ -1477,11 +1480,6 @@ OPENAPI_31 = {
1477
1480
  "$ref": "#/$defs/specification-extensions",
1478
1481
  "unevaluatedProperties": False,
1479
1482
  },
1480
- "path-item-or-reference": {
1481
- "if": {"type": "object", "required": ["$ref"]},
1482
- "then": {"$ref": "#/$defs/reference"},
1483
- "else": {"$ref": "#/$defs/path-item"},
1484
- },
1485
1483
  "operation": {
1486
1484
  "$comment": "https://spec.openapis.org/oas/v3.1.0#operation-object",
1487
1485
  "type": "object",
@@ -1542,7 +1540,6 @@ OPENAPI_31 = {
1542
1540
  "if": {"properties": {"in": {"const": "path"}}, "required": ["in"]},
1543
1541
  "then": {
1544
1542
  "properties": {
1545
- "name": {"pattern": "[^/#?]+$"},
1546
1543
  "style": {"default": "simple", "enum": ["matrix", "label", "simple"]},
1547
1544
  "required": {"const": True},
1548
1545
  },
@@ -1662,7 +1659,7 @@ OPENAPI_31 = {
1662
1659
  "$comment": "https://spec.openapis.org/oas/v3.1.0#callback-object",
1663
1660
  "type": "object",
1664
1661
  "$ref": "#/$defs/specification-extensions",
1665
- "additionalProperties": {"$ref": "#/$defs/path-item-or-reference"},
1662
+ "additionalProperties": {"$ref": "#/$defs/path-item"},
1666
1663
  },
1667
1664
  "callbacks-or-reference": {
1668
1665
  "if": {"type": "object", "required": ["$ref"]},
@@ -1755,7 +1752,6 @@ OPENAPI_31 = {
1755
1752
  "summary": {"type": "string"},
1756
1753
  "description": {"type": "string"},
1757
1754
  },
1758
- "unevaluatedProperties": False,
1759
1755
  },
1760
1756
  "schema": {
1761
1757
  "$comment": "https://spec.openapis.org/oas/v3.1.0#schema-object",
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import string
4
- from itertools import product
4
+ from itertools import chain, product
5
5
  from typing import Any, Generator
6
6
 
7
7
 
@@ -11,6 +11,10 @@ def expand_status_code(status_code: str | int) -> Generator[int, None, None]:
11
11
  yield int("".join(expanded))
12
12
 
13
13
 
14
+ def expand_status_codes(status_codes: list[str]) -> set[int]:
15
+ return set(chain.from_iterable(expand_status_code(code) for code in status_codes))
16
+
17
+
14
18
  def is_header_location(location: str) -> bool:
15
19
  """Whether this location affects HTTP headers."""
16
20
  return location in ("header", "cookie")
@@ -12,6 +12,7 @@ from hypothesis.control import current_build_context
12
12
  from hypothesis.errors import Flaky, Unsatisfiable
13
13
 
14
14
  from ..exceptions import CheckFailed
15
+ from ..internal.checks import CheckContext
15
16
  from ..targets import TargetMetricCollector
16
17
  from . import events
17
18
  from .config import StatefulTestRunnerConfig
@@ -113,6 +114,7 @@ def _execute_state_machine_loop(
113
114
  ) -> None:
114
115
  """Execute the state machine testing loop."""
115
116
  from hypothesis import reporting
117
+ from requests.structures import CaseInsensitiveDict
116
118
 
117
119
  from ..transports import RequestsTransport
118
120
 
@@ -129,6 +131,7 @@ def _execute_state_machine_loop(
129
131
  if config.auth is not None:
130
132
  session.auth = config.auth
131
133
  call_kwargs["session"] = session
134
+ check_ctx = CheckContext(auth=config.auth, headers=CaseInsensitiveDict(config.headers) if config.headers else None)
132
135
 
133
136
  class _InstrumentedStateMachine(state_machine): # type: ignore[valid-type,misc]
134
137
  """State machine with additional hooks for emitting events."""
@@ -223,7 +226,8 @@ def _execute_state_machine_loop(
223
226
  validate_response(
224
227
  response=response,
225
228
  case=case,
226
- ctx=ctx,
229
+ runner_ctx=ctx,
230
+ check_ctx=check_ctx,
227
231
  checks=config.checks,
228
232
  additional_checks=additional_checks,
229
233
  max_response_time=config.max_response_time,
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any
3
+ from typing import TYPE_CHECKING
4
4
 
5
5
  from ..exceptions import CheckFailed, get_grouped_exception
6
6
  from ..internal.checks import CheckContext
@@ -17,27 +17,24 @@ def validate_response(
17
17
  *,
18
18
  response: GenericResponse,
19
19
  case: Case,
20
- ctx: RunnerContext,
20
+ runner_ctx: RunnerContext,
21
+ check_ctx: CheckContext,
21
22
  checks: tuple[CheckFunction, ...],
22
23
  additional_checks: tuple[CheckFunction, ...] = (),
23
24
  max_response_time: int | None = None,
24
- headers: dict[str, Any] | None = None,
25
25
  ) -> None:
26
26
  """Validate the response against the provided checks."""
27
- from requests.structures import CaseInsensitiveDict
28
-
29
27
  from .._compat import MultipleFailures
30
28
  from ..checks import _make_max_response_time_failure_message
31
29
  from ..failures import ResponseTimeExceeded
32
30
  from ..models import Check, Status
33
31
 
34
32
  exceptions: list[CheckFailed | AssertionError] = []
35
- check_results = ctx.checks_for_step
36
- check_ctx = CheckContext(headers=CaseInsensitiveDict(headers) if headers else None)
33
+ check_results = runner_ctx.checks_for_step
37
34
 
38
35
  def _on_failure(exc: CheckFailed | AssertionError, message: str, context: FailureContext | None) -> None:
39
36
  exceptions.append(exc)
40
- if ctx.is_seen_in_suite(exc):
37
+ if runner_ctx.is_seen_in_suite(exc):
41
38
  return
42
39
  failed_check = Check(
43
40
  name=name,
@@ -49,9 +46,9 @@ def validate_response(
49
46
  context=context,
50
47
  request=None,
51
48
  )
52
- ctx.add_failed_check(failed_check)
49
+ runner_ctx.add_failed_check(failed_check)
53
50
  check_results.append(failed_check)
54
- ctx.mark_as_seen_in_suite(exc)
51
+ runner_ctx.mark_as_seen_in_suite(exc)
55
52
 
56
53
  def _on_passed(_name: str, _case: Case) -> None:
57
54
  passed_check = Check(
@@ -72,16 +69,16 @@ def validate_response(
72
69
  if not skip_check:
73
70
  _on_passed(name, copied_case)
74
71
  except CheckFailed as exc:
75
- if ctx.is_seen_in_run(exc):
72
+ if runner_ctx.is_seen_in_run(exc):
76
73
  continue
77
74
  _on_failure(exc, str(exc), exc.context)
78
75
  except AssertionError as exc:
79
- if ctx.is_seen_in_run(exc):
76
+ if runner_ctx.is_seen_in_run(exc):
80
77
  continue
81
78
  _on_failure(exc, str(exc) or f"Custom check failed: `{name}`", None)
82
79
  except MultipleFailures as exc:
83
80
  for subexc in exc.exceptions:
84
- if ctx.is_seen_in_run(subexc):
81
+ if runner_ctx.is_seen_in_run(subexc):
85
82
  continue
86
83
  _on_failure(subexc, str(subexc), subexc.context)
87
84
 
@@ -93,7 +90,7 @@ def validate_response(
93
90
  try:
94
91
  raise AssertionError(message)
95
92
  except AssertionError as _exc:
96
- if not ctx.is_seen_in_run(_exc):
93
+ if not runner_ctx.is_seen_in_run(_exc):
97
94
  _on_failure(_exc, message, context)
98
95
  else:
99
96
  _on_passed("max_response_time", case)
@@ -7,7 +7,7 @@ from contextlib import contextmanager
7
7
  from dataclasses import dataclass
8
8
  from datetime import timedelta
9
9
  from inspect import iscoroutinefunction
10
- from typing import TYPE_CHECKING, Any, Generator, Optional, Protocol, TypeVar, cast
10
+ from typing import TYPE_CHECKING, Any, Generator, Protocol, TypeVar, cast
11
11
  from urllib.parse import urlparse
12
12
 
13
13
  from .. import failures
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: schemathesis
3
- Version: 3.36.3
3
+ Version: 3.37.0
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
@@ -40,7 +40,7 @@ Requires-Dist: hypothesis[zoneinfo]<7,>=6.103.4; python_version == '3.8'
40
40
  Requires-Dist: jsonschema[format]<5.0,>=4.18.0
41
41
  Requires-Dist: junit-xml<2.0,>=1.9
42
42
  Requires-Dist: pyrate-limiter<4.0,>=2.10
43
- Requires-Dist: pytest-subtests<0.8.0,>=0.2.1
43
+ Requires-Dist: pytest-subtests<0.14.0,>=0.2.1
44
44
  Requires-Dist: pytest<9,>=4.6.4
45
45
  Requires-Dist: pyyaml<7.0,>=5.1
46
46
  Requires-Dist: requests<3,>=2.22
@@ -1,7 +1,7 @@
1
1
  schemathesis/__init__.py,sha256=UW2Bq8hDDkcBeAAA7PzpBFXkOOxkmHox-mfQwzHDjL0,1914
2
2
  schemathesis/_compat.py,sha256=y4RZd59i2NCnZ91VQhnKeMn_8t3SgvLOk2Xm8nymUHY,1837
3
3
  schemathesis/_dependency_versions.py,sha256=pjEkkGAfOQJYNb-9UOo84V8nj_lKHr_TGDVdFwY2UU0,816
4
- schemathesis/_hypothesis.py,sha256=TUGODyKJzTSnUQPWt_uj6-MBXCPthquJXGiCobVdFh0,14930
4
+ schemathesis/_hypothesis.py,sha256=Vk38ROENT5yOo38qS2s6JSjRCLDzTmnzQ1sy0ZoZ1C8,14930
5
5
  schemathesis/_lazy_import.py,sha256=aMhWYgbU2JOltyWBb32vnWBb6kykOghucEzI_F70yVE,470
6
6
  schemathesis/_override.py,sha256=TAjYB3eJQmlw9K_xiR9ptt9Wj7if4U7UFlUhGjpBAoM,1625
7
7
  schemathesis/_rate_limiter.py,sha256=q_XWst5hzuAyXQRiZc4s_bx7-JlPYZM_yKDmeavt3oo,242
@@ -10,12 +10,12 @@ schemathesis/auths.py,sha256=De97IS_iOlC36-jRhkZ2DUndjUpXYgsd8R-nA-iHn88,16837
10
10
  schemathesis/checks.py,sha256=YPUI1N5giGBy1072vd77e6HWelGAKrJUmJLEG4oqfF8,2630
11
11
  schemathesis/code_samples.py,sha256=rsdTo6ksyUs3ZMhqx0mmmkPSKUCFa--snIOYsXgZd80,4120
12
12
  schemathesis/constants.py,sha256=l1YQ7PXhEj9dyf9CTESVUpPOaFCH7iz-Fe8o4v6Th_s,2673
13
- schemathesis/exceptions.py,sha256=Ah-6aDjQREl6D1eBQnANYvwK8uxPwhx83C_tsdydJ_4,20281
14
- schemathesis/failures.py,sha256=KlsYYqU0VvKSlPYhsMp7hf1aLYKJiUV6I9_4fPEqYmA,7583
13
+ schemathesis/exceptions.py,sha256=5zjPlyVoQNJGbwufplL6ZVV7FEBPBNPHGdlQRJ7xnhE,20449
14
+ schemathesis/failures.py,sha256=fybNkCF2rqH90e3KW_XwpgZnSM6f7_FERcxHT9Pd3NM,7911
15
15
  schemathesis/filters.py,sha256=f3c_yXIBwIin-9Y0qU2TkcC1NEM_Mw34jGUHQc0BOyw,17026
16
16
  schemathesis/graphql.py,sha256=XiuKcfoOB92iLFC8zpz2msLkM0_V0TLdxPNBqrrGZ8w,216
17
- schemathesis/hooks.py,sha256=qXyVRfJdhsLk1GuJX47VAqkX0VPm6X6fK-cXhEnFLT4,14765
18
- schemathesis/lazy.py,sha256=uE8ef_7U_9ovs0-7UA7ssIiiDipJurJFHuxaUFOUETo,18956
17
+ schemathesis/hooks.py,sha256=ORbrF6xKx7CQJio97Yo9BSpgKFTQ0mTmSooToX_vXAY,14787
18
+ schemathesis/lazy.py,sha256=Ddhkk7Tpc_VcRGYkCtKDmP2gpjxVmEZ3b01ZTNjbm8I,19004
19
19
  schemathesis/loaders.py,sha256=MoEhcdOEBJxNRn5X-ZNhWB9jZDHQQNpkNfEdQjf_NDw,4590
20
20
  schemathesis/models.py,sha256=mgufFwK6pVWn6hjfJIJwbk0qZM-XUeUaVVaOOEPaR50,46646
21
21
  schemathesis/parameters.py,sha256=PndmqQRlEYsCt1kWjSShPsFf6vj7X_7FRdz_-A95eNg,2258
@@ -27,16 +27,16 @@ schemathesis/targets.py,sha256=XIGRghvEzbmEJjse9aZgNEj67L3jAbiazm2rxURWgDE,2351
27
27
  schemathesis/throttling.py,sha256=aisUc4MJDGIOGUAs9L2DlWWpdd4KyAFuNVKhYoaUC9M,1719
28
28
  schemathesis/types.py,sha256=Tem2Q_zyMCd0Clp5iGKv6Fu13wdcbxVE8tCVH9WWt7A,1065
29
29
  schemathesis/utils.py,sha256=8RkTZ9Ft5IUaGkxABhh34oU7WO2ouMsfgtvFPTx9alI,4875
30
- schemathesis/cli/__init__.py,sha256=OC6QO38QDf55DTIVwrWiQKz8BfTD5QcK574m67NCE2w,72862
30
+ schemathesis/cli/__init__.py,sha256=qtoLaYmgkxZ_o4JYe-KTYcVHxR8Tus-vDexRYAbTbXA,74566
31
31
  schemathesis/cli/__main__.py,sha256=MWaenjaUTZIfNPFzKmnkTiawUri7DVldtg3mirLwzU8,92
32
- schemathesis/cli/callbacks.py,sha256=grMKlx_iGiJA4oJsYt_q8l354Y8Nb11IBvOKwbD0jOA,15192
33
- schemathesis/cli/cassettes.py,sha256=uzU51QMqUWhkxdX4H9bavVjeTIWX5_i-E3CnOK2mAU4,19698
32
+ schemathesis/cli/callbacks.py,sha256=-VA_I_mVma9WxFNtUR8d2KNICKJD5ScayfSdKKPEP5Y,16321
33
+ schemathesis/cli/cassettes.py,sha256=jD1JTkkEALUUEyzyuJ-KuxgntfGodILUuOu3C9HKjIw,19412
34
34
  schemathesis/cli/constants.py,sha256=wk-0GsoJIel8wFFerQ6Kf_6eAYUtIWkwMFwyAqv3yj4,1635
35
35
  schemathesis/cli/context.py,sha256=j_lvYQiPa6Q7P4P_IGCM9V2y2gJSpDbpxIIzR5oFB2I,2567
36
36
  schemathesis/cli/debug.py,sha256=_YA-bX1ujHl4bqQDEum7M-I2XHBTEGbvgkhvcvKhmgU,658
37
37
  schemathesis/cli/handlers.py,sha256=EXSAFe5TQlHANz1AVlSttfsoDT2oeaeFbqq1N7e2udw,467
38
38
  schemathesis/cli/junitxml.py,sha256=iWbD5G2mzxrLiC24OuGHCLwkgHQg80OXY00stnGaXL0,5054
39
- schemathesis/cli/options.py,sha256=ZsfBV0Fpm3_Ft5NZMWJ7zWeot4uJ35_Ta1OI8Sw0P_A,2600
39
+ schemathesis/cli/options.py,sha256=yL7nrzKkbGCc4nQya9wpTW48XGz_OT9hOFrzPxRrDe4,2853
40
40
  schemathesis/cli/reporting.py,sha256=KC3sxSc1u4aFQ-0Q8CQ3G4HTEl7QxlubGnJgNKmVJdQ,3627
41
41
  schemathesis/cli/sanitization.py,sha256=Onw_NWZSom6XTVNJ5NHnC0PAhrYAcGzIXJbsBCzLkn4,1005
42
42
  schemathesis/cli/output/__init__.py,sha256=AXaUzQ1nhQ-vXhW4-X-91vE2VQtEcCOrGtQXXNN55iQ,29
@@ -48,7 +48,7 @@ schemathesis/contrib/openapi/__init__.py,sha256=N-BoCzrLGq9aynubhmQBS-LJUBv1wPJc
48
48
  schemathesis/contrib/openapi/fill_missing_examples.py,sha256=SL3LXG4khjGKneU3aBu1MGIhYtwRMjK77NH8L--9JBE,583
49
49
  schemathesis/contrib/openapi/formats/__init__.py,sha256=OpHWPW8MkTLVv83QXPYY1HVLflhmSH49hSVefm1oVV0,111
50
50
  schemathesis/contrib/openapi/formats/uuid.py,sha256=PG7aV0QAQnQ1zKmKiDK3cJue3Xy-TLGzyMeCB_RQbgk,391
51
- schemathesis/experimental/__init__.py,sha256=vza-jHjFAdyrnOBWVkiXyQi2Q7HoTUM9Zs3E7ozJmog,3161
51
+ schemathesis/experimental/__init__.py,sha256=0YVz037-Koc1OZ9Y5oYujc4if0gE9SdtfFlWnfzrWNo,3498
52
52
  schemathesis/extra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
53
  schemathesis/extra/_aiohttp.py,sha256=-bIY0ucv7pfK3gA9PHiO4n7ajtZJJM9pS3EY3cLau9c,957
54
54
  schemathesis/extra/_flask.py,sha256=lpKQxg_kdEFo59Q8sV5T78IJYhgx6p4SCwIohJYSycw,345
@@ -58,11 +58,11 @@ schemathesis/fixups/__init__.py,sha256=RP5QYJVJhp8LXjhH89fCRaIVU26dHCy74jD9seoYM
58
58
  schemathesis/fixups/fast_api.py,sha256=mn-KzBqnR8jl4W5fY-_ZySabMDMUnpzCIESMHnlvE1c,1304
59
59
  schemathesis/fixups/utf8_bom.py,sha256=lWT9RNmJG8i-l5AXIpaCT3qCPUwRgzXPW3eoOjmZETA,745
60
60
  schemathesis/generation/__init__.py,sha256=29Zys_tD6kfngaC4zHeC6TOBZQcmo7CWm7KDSYsHStQ,1581
61
- schemathesis/generation/_hypothesis.py,sha256=QDBzpcM9eXPgLGGdCPdGlxCtfMXD4YBN9_6Oz73lofI,1406
62
- schemathesis/generation/_methods.py,sha256=jCK09f4sedDfePrS-6BIiE-CcEE8fJ4ZHxq1BHoTltQ,1101
63
- schemathesis/generation/coverage.py,sha256=5NyDjceKqmQVmDn4xwOnS61xRpS2Z_YcbtjNuveI0RA,27582
61
+ schemathesis/generation/_hypothesis.py,sha256=Aaol5w3TEOVZn8znrnnJCYfrll8eALW0dCRtz3k0Eis,1661
62
+ schemathesis/generation/_methods.py,sha256=r8oVlJ71_gXcnEhU-byw2E0R2RswQQFm8U7yGErSqbw,1204
63
+ schemathesis/generation/coverage.py,sha256=S8qxFHpIkHr87vZQQSmhttRvmlfRZIL445gj_p2GjoQ,29070
64
64
  schemathesis/internal/__init__.py,sha256=93HcdG3LF0BbQKbCteOsFMa1w6nXl8yTmx87QLNJOik,161
65
- schemathesis/internal/checks.py,sha256=m6lY6x2Pkz6AjU8Hs-UMSDJZEOq8EcgZOCp6BQA7p_g,1668
65
+ schemathesis/internal/checks.py,sha256=0NudOP1vpz76n3OjnN4OkNC2d5bNKAkZLFzTG2k4NBY,2352
66
66
  schemathesis/internal/copy.py,sha256=DcL56z-d69kKR_5u8mlHvjSL1UTyUKNMAwexrwHFY1s,1031
67
67
  schemathesis/internal/datetime.py,sha256=zPLBL0XXLNfP-KYel3H2m8pnsxjsA_4d-zTOhJg2EPQ,136
68
68
  schemathesis/internal/deprecation.py,sha256=Ty5VBFBlufkITpP0WWTPIPbnB7biDi0kQgXVYWZp820,1273
@@ -72,13 +72,13 @@ schemathesis/internal/output.py,sha256=zMaG5knIuBieVH8CrcmPJgbmQukDs2xdekX0BrK7B
72
72
  schemathesis/internal/result.py,sha256=d449YvyONjqjDs-A5DAPgtAI96iT753K8sU6_1HLo2Q,461
73
73
  schemathesis/internal/transformation.py,sha256=M5LA4pFZC4nD_0iGfih1wLF3_q8xJas94Uuaymt-7Cw,690
74
74
  schemathesis/internal/validation.py,sha256=G7i8jIMUpAeOnDsDF_eWYvRZe_yMprRswx0QAtMPyEw,966
75
- schemathesis/runner/__init__.py,sha256=dLvb4FvH1zvYyVj5i7naR0ehfKL7K8QBKKbBNp_ClY8,21536
75
+ schemathesis/runner/__init__.py,sha256=b96aoJQo9Kash0GNKI-uCiLMEKOI8cxKjKCKQlWxkUw,21925
76
76
  schemathesis/runner/events.py,sha256=cRKKSDvHvKLBIyFBz-J0JtAKshbGGKco9eaMyLCgzsY,11734
77
77
  schemathesis/runner/probes.py,sha256=no5AfO3kse25qvHevjeUfB0Q3C860V2AYzschUW3QMQ,5688
78
78
  schemathesis/runner/serialization.py,sha256=jHpfm1PgPAmorNkF8_rkzIYoeA43jpbSKeh5Hm5nqF0,20495
79
79
  schemathesis/runner/impl/__init__.py,sha256=1E2iME8uthYPBh9MjwVBCTFV-P3fi7AdphCCoBBspjs,199
80
- schemathesis/runner/impl/context.py,sha256=KT3Dl1HIUM29Jpp_DwfoSx_NbWFH_7s6gw-p2Sr-N24,2505
81
- schemathesis/runner/impl/core.py,sha256=bAPwfhLJrXLlPN6BDNGyQoO9v35YlmeuxAmyFTNQg5Y,47197
80
+ schemathesis/runner/impl/context.py,sha256=xVxHnXerSoOl-I6IzrdA-bRhaYQDKS4TuShFULsa154,2716
81
+ schemathesis/runner/impl/core.py,sha256=qnALY8PCkU2mmjZlEY6Jboa9pZNLmyCakh0gkuKg43c,47466
82
82
  schemathesis/runner/impl/solo.py,sha256=y5QSxgK8nBCEjZVD5BpFvYUXmB6tEjk6TwxAo__NejA,2911
83
83
  schemathesis/runner/impl/threadpool.py,sha256=yNR5LYE8f3N_4t42OwSgy0_qdGgBPM7d11F9c9oEAAs,15075
84
84
  schemathesis/service/__init__.py,sha256=cDVTCFD1G-vvhxZkJUwiToTAEQ-0ByIoqwXvJBCf_V8,472
@@ -105,10 +105,10 @@ schemathesis/specs/graphql/validation.py,sha256=uINIOt-2E7ZuQV2CxKzwez-7L9tDtqzM
105
105
  schemathesis/specs/openapi/__init__.py,sha256=HDcx3bqpa6qWPpyMrxAbM3uTo0Lqpg-BUNZhDJSJKnw,279
106
106
  schemathesis/specs/openapi/_cache.py,sha256=PAiAu4X_a2PQgD2lG5H3iisXdyg4SaHpU46bRZvfNkM,4320
107
107
  schemathesis/specs/openapi/_hypothesis.py,sha256=Ym1d3GXlabOSbDk_AEkmkZGl9EMIDpumciLZtfYNdUI,24323
108
- schemathesis/specs/openapi/checks.py,sha256=-4qOzkova0e4QSqdgsoUiOv2bg57HZmzbpAiAeotc3Q,22288
108
+ schemathesis/specs/openapi/checks.py,sha256=eRNSs8wGNOqe4Zxt1DsCUQBU9OgaVceu1uioeRTI1XU,23650
109
109
  schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
110
110
  schemathesis/specs/openapi/converter.py,sha256=NkrzBNjtmVwQTeE73NOtwB_puvQTjxxqqrc7gD_yscc,3241
111
- schemathesis/specs/openapi/definitions.py,sha256=nEsCKn_LgqYjZ9nNWp-8KUIrB4S94pT3GsV5A8UIzDw,94043
111
+ schemathesis/specs/openapi/definitions.py,sha256=WTkWwCgTc3OMxfKsqh6YDoGfZMTThSYrHGp8h0vLAK0,93935
112
112
  schemathesis/specs/openapi/examples.py,sha256=FwhPWca7bpdHpUp_LRoK09DVgusojO3aXXhXYrK373I,20354
113
113
  schemathesis/specs/openapi/formats.py,sha256=JmmkQWNAj5XreXb7Edgj4LADAf4m86YulR_Ec8evpJ4,1220
114
114
  schemathesis/specs/openapi/links.py,sha256=a8JmWM9aZhrR5CfyIh6t2SkfonMLfYKOScXY2XlZYN0,17749
@@ -120,7 +120,7 @@ schemathesis/specs/openapi/references.py,sha256=euxM02kQGMHh4Ss1jWjOY_gyw_HazafK
120
120
  schemathesis/specs/openapi/schemas.py,sha256=t3Gz2q-d9b8Oy-hDhz0rNfjYT3Nx-uOeLOmjO9hpRM0,53741
121
121
  schemathesis/specs/openapi/security.py,sha256=Z-6pk2Ga1PTUtBe298KunjVHsNh5A-teegeso7zcPIE,7138
122
122
  schemathesis/specs/openapi/serialization.py,sha256=5qGdFHZ3n80UlbSXrO_bkr4Al_7ci_Z3aSUjZczNDQY,11384
123
- schemathesis/specs/openapi/utils.py,sha256=-TCu0hTrlwp2x5qHNp-TxiHRMeIZC9OBmlhLssjRIiQ,742
123
+ schemathesis/specs/openapi/utils.py,sha256=ER4vJkdFVDIE7aKyxyYatuuHVRNutytezgE52pqZNE8,900
124
124
  schemathesis/specs/openapi/validation.py,sha256=Q9ThZlwU-mSz7ExDnIivnZGi1ivC5hlX2mIMRAM79kc,999
125
125
  schemathesis/specs/openapi/expressions/__init__.py,sha256=hFpJrIWbPi55GcIVjNFRDDUL8xmDu3mdbdldoHBoFJ0,1729
126
126
  schemathesis/specs/openapi/expressions/context.py,sha256=GsraXq4azVg4pn0vilc6P7zZjzUWvoO7VUqmshqvmeQ,350
@@ -140,19 +140,19 @@ schemathesis/stateful/__init__.py,sha256=HBg-h131EI8IipHQgufSaXe-CrFTKmffPVsoEFj
140
140
  schemathesis/stateful/config.py,sha256=huYzqDoD6x20p_VNAR79NgxPwUFO8UXoc3_z4BEuHqU,3586
141
141
  schemathesis/stateful/context.py,sha256=vJ9nxTTjI5wo7A6PBGCvVVO_7y-ELs3XERi9PxLzykA,5085
142
142
  schemathesis/stateful/events.py,sha256=CyYvyQebOaeTn6UevaB7HXOrUhxCWbqXMfQ7pZK7fV8,6727
143
- schemathesis/stateful/runner.py,sha256=doEzd7IOfrW1zPzRC0WOqR-N1x5FmaJVGOZur-za6Ao,12352
143
+ schemathesis/stateful/runner.py,sha256=e3vvRrx0NwN20RdC0cC4mZjcsYezwkhoDwE1cCVOAlw,12615
144
144
  schemathesis/stateful/sink.py,sha256=bHYlgh-fMwg1Srxk_XGs0-WV34YccotwH9PGrxCK57A,2474
145
145
  schemathesis/stateful/state_machine.py,sha256=PFztY82W5enuXjO6k4Mz8fbHmDJ7Z8OLYZRWtuBeyjg,12956
146
146
  schemathesis/stateful/statistic.py,sha256=2-uU5xpT9CbMulKgJWLZN6MUpC0Fskf5yXTt4ef4NFA,542
147
- schemathesis/stateful/validation.py,sha256=mNiT8fT2aaxNwhyj2n2G-wxqtMPYxurtH4PYZNRXCzI,3848
148
- schemathesis/transports/__init__.py,sha256=UKt3GJboaPbwcX0vlBZB3i1rFF6tTBQkwDmzB-3o7ns,12892
147
+ schemathesis/stateful/validation.py,sha256=23qSZjC1_xRmtCX4OqsyG6pGxdlo6IZYid695ZpDQyU,3747
148
+ schemathesis/transports/__init__.py,sha256=ybH90TrGwODO5s94UMEX2P2HX-6Jb66X5UUOgKTbZz8,12882
149
149
  schemathesis/transports/asgi.py,sha256=bwW9vMd1h89Jh7I4jHJVwSNUQzHvc7-JOD5u4hSHZd8,212
150
150
  schemathesis/transports/auth.py,sha256=urSTO9zgFO1qU69xvnKHPFQV0SlJL3d7_Ojl0tLnZwo,1143
151
151
  schemathesis/transports/content_types.py,sha256=MiKOm-Hy5i75hrROPdpiBZPOTDzOwlCdnthJD12AJzI,2187
152
152
  schemathesis/transports/headers.py,sha256=hr_AIDOfUxsJxpHfemIZ_uNG3_vzS_ZeMEKmZjbYiBE,990
153
153
  schemathesis/transports/responses.py,sha256=OFD4ZLqwEFpo7F9vaP_SVgjhxAqatxIj38FS4XVq8Qs,1680
154
- schemathesis-3.36.3.dist-info/METADATA,sha256=7TD5mpHwI_7wHLTg2EfyokGGn12X5d-gT6Wfc08-iEI,12904
155
- schemathesis-3.36.3.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
156
- schemathesis-3.36.3.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
157
- schemathesis-3.36.3.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
158
- schemathesis-3.36.3.dist-info/RECORD,,
154
+ schemathesis-3.37.0.dist-info/METADATA,sha256=Rt0KgoLdxayzfnVhiEbj8lzthkYuWItyJvdjrMw_WxQ,12905
155
+ schemathesis-3.37.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
156
+ schemathesis-3.37.0.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
157
+ schemathesis-3.37.0.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
158
+ schemathesis-3.37.0.dist-info/RECORD,,