schemathesis 3.36.4__py3-none-any.whl → 3.37.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
 
@@ -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."""
@@ -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,10 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
3
  import functools
4
+ import json
5
+ import re
5
6
  from contextlib import contextmanager, suppress
6
7
  from dataclasses import dataclass
7
- from functools import lru_cache
8
+ from functools import lru_cache, partial
8
9
  from itertools import combinations
9
10
  from typing import Any, Generator, Iterator, TypeVar, cast
10
11
 
@@ -14,8 +15,8 @@ from hypothesis.errors import InvalidArgument, Unsatisfiable
14
15
  from hypothesis_jsonschema import from_schema
15
16
  from hypothesis_jsonschema._canonicalise import canonicalish
16
17
 
17
- from schemathesis.constants import NOT_SET
18
-
18
+ from ..constants import NOT_SET
19
+ from ..specs.openapi.patterns import update_quantifier
19
20
  from ._hypothesis import get_single_example
20
21
  from ._methods import DataGenerationMethod
21
22
 
@@ -233,14 +234,23 @@ def cover_schema_iter(
233
234
  seen.add(k)
234
235
  elif key == "minLength" and 0 < value < BUFFER_SIZE:
235
236
  with suppress(InvalidArgument):
236
- 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)
237
242
  k = _to_hashable_key(value)
238
243
  if k not in seen:
239
244
  yield NegativeValue(value, description="String smaller than minLength")
240
245
  seen.add(k)
241
246
  elif key == "maxLength" and value < BUFFER_SIZE:
242
- with suppress(InvalidArgument):
243
- 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)
244
254
  k = _to_hashable_key(value)
245
255
  if k not in seen:
246
256
  yield NegativeValue(value, description="String larger than maxLength")
@@ -586,9 +596,13 @@ def _negative_properties(
586
596
  )
587
597
 
588
598
 
599
+ def _not_matching_pattern(value: str, pattern: str) -> bool:
600
+ return re.search(pattern, value) is None
601
+
602
+
589
603
  def _negative_pattern(ctx: CoverageContext, pattern: str) -> Generator[GeneratedValue, None, None]:
590
604
  yield NegativeValue(
591
- ctx.generate_from(st.text().filter(pattern.__ne__)),
605
+ ctx.generate_from(st.text().filter(partial(_not_matching_pattern, pattern=pattern))),
592
606
  description=f"Value not matching the '{pattern}' pattern",
593
607
  )
594
608
 
schemathesis/hooks.py CHANGED
@@ -13,6 +13,7 @@ from .internal.deprecation import deprecated_property
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from hypothesis import strategies as st
16
+ from hypothesis.vendor.pretty import RepresentationPrinter
16
17
 
17
18
  from .models import APIOperation, Case
18
19
  from .schemas import BaseSchema
@@ -32,6 +33,9 @@ class RegisteredHook:
32
33
  signature: inspect.Signature
33
34
  scopes: list[HookScope]
34
35
 
36
+ def _repr_pretty_(self, printer: RepresentationPrinter, cycle: bool) -> None:
37
+ return None
38
+
35
39
 
36
40
  @dataclass
37
41
  class HookContext:
@@ -285,9 +289,9 @@ class HookDispatcher:
285
289
  self._hooks = defaultdict(list)
286
290
 
287
291
 
288
- def _should_skip_hook(hook: Callable, context: HookContext) -> bool:
292
+ def _should_skip_hook(hook: Callable, ctx: HookContext) -> bool:
289
293
  filter_set = getattr(hook, "filter_set", None)
290
- return filter_set is not None and not filter_set.match(context)
294
+ return filter_set is not None and ctx.operation is not None and not filter_set.match(ctx)
291
295
 
292
296
 
293
297
  def apply_to_all_dispatchers(
@@ -2,7 +2,7 @@ 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:
@@ -17,6 +17,23 @@ if TYPE_CHECKING:
17
17
  CheckFunction = Callable[["CheckContext", "GenericResponse", "Case"], Optional[bool]]
18
18
 
19
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
+
20
37
  @dataclass
21
38
  class CheckContext:
22
39
  """Context for Schemathesis checks.
@@ -26,6 +43,7 @@ class CheckContext:
26
43
 
27
44
  auth: HTTPDigestAuth | RawAuth | None = None
28
45
  headers: CaseInsensitiveDict | None = None
46
+ config: CheckConfig = field(default_factory=CheckConfig)
29
47
 
30
48
 
31
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
schemathesis/models.py CHANGED
@@ -61,6 +61,7 @@ if TYPE_CHECKING:
61
61
  import requests.auth
62
62
  import werkzeug
63
63
  from hypothesis import strategies as st
64
+ from hypothesis.vendor.pretty import RepresentationPrinter
64
65
  from requests.structures import CaseInsensitiveDict
65
66
 
66
67
  from .auths import AuthStorage
@@ -202,6 +203,9 @@ class Case:
202
203
  def __hash__(self) -> int:
203
204
  return hash(self.as_curl_command({SCHEMATHESIS_TEST_CASE_HEADER: "0"}))
204
205
 
206
+ def _repr_pretty_(self, printer: RepresentationPrinter, cycle: bool) -> None:
207
+ return None
208
+
205
209
  @deprecated_property(removed_in="4.0", replacement="`operation`")
206
210
  def endpoint(self) -> APIOperation:
207
211
  return self.operation
@@ -1098,6 +1102,9 @@ class TestResult:
1098
1102
  # DEPRECATED: Seed is the same per test run
1099
1103
  seed: int | None = None
1100
1104
 
1105
+ def _repr_pretty_(self, printer: RepresentationPrinter, cycle: bool) -> None:
1106
+ return None
1107
+
1101
1108
  def mark_errored(self) -> None:
1102
1109
  self.is_errored = True
1103
1110
 
@@ -1191,6 +1198,9 @@ class TestResultSet:
1191
1198
  generic_errors: list[OperationSchemaError] = field(default_factory=list)
1192
1199
  warnings: list[str] = field(default_factory=list)
1193
1200
 
1201
+ def _repr_pretty_(self, printer: RepresentationPrinter, cycle: bool) -> None:
1202
+ return None
1203
+
1194
1204
  def __iter__(self) -> Iterator[TestResult]:
1195
1205
  return iter(self.results)
1196
1206
 
@@ -9,6 +9,8 @@ from dataclasses import dataclass, field
9
9
  from typing import TYPE_CHECKING, Any, Generator, Generic, TypeVar
10
10
 
11
11
  if TYPE_CHECKING:
12
+ from hypothesis.vendor.pretty import RepresentationPrinter
13
+
12
14
  from .models import APIOperation
13
15
 
14
16
 
@@ -55,6 +57,9 @@ class ParameterSet(Generic[P]):
55
57
 
56
58
  items: list[P] = field(default_factory=list)
57
59
 
60
+ def _repr_pretty_(self, printer: RepresentationPrinter, cycle: bool) -> None:
61
+ return None
62
+
58
63
  def add(self, parameter: P) -> None:
59
64
  """Add a new parameter."""
60
65
  self.items.append(parameter)
@@ -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,11 +4,14 @@ 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:
10
11
  import threading
11
12
 
13
+ from hypothesis.vendor.pretty import RepresentationPrinter
14
+
12
15
  from ...exceptions import OperationSchemaError
13
16
  from ...models import Case
14
17
  from ...types import NotSet, RawAuth
@@ -24,11 +27,18 @@ class RunnerContext:
24
27
  stop_event: threading.Event
25
28
  unique_data: bool
26
29
  outcome_cache: dict[int, BaseException | None]
30
+ checks_config: CheckConfig
27
31
 
28
- __slots__ = ("data", "auth", "seed", "stop_event", "unique_data", "outcome_cache")
32
+ __slots__ = ("data", "auth", "seed", "stop_event", "unique_data", "outcome_cache", "checks_config")
29
33
 
30
34
  def __init__(
31
- self, *, seed: int | None, auth: RawAuth | None, stop_event: threading.Event, unique_data: bool
35
+ self,
36
+ *,
37
+ seed: int | None,
38
+ auth: RawAuth | None,
39
+ stop_event: threading.Event,
40
+ unique_data: bool,
41
+ checks_config: CheckConfig,
32
42
  ) -> None:
33
43
  self.data = TestResultSet(seed=seed)
34
44
  self.auth = auth
@@ -36,6 +46,10 @@ class RunnerContext:
36
46
  self.stop_event = stop_event
37
47
  self.outcome_cache = {}
38
48
  self.unique_data = unique_data
49
+ self.checks_config = checks_config
50
+
51
+ def _repr_pretty_(self, printer: RepresentationPrinter, cycle: bool) -> None:
52
+ return None
39
53
 
40
54
  @property
41
55
  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
 
@@ -21,6 +21,7 @@ from .parameters import OpenAPI20Body, OpenAPI30Body, OpenAPIParameter
21
21
  from .references import RECURSION_DEPTH_LIMIT, Unresolvable
22
22
 
23
23
  if TYPE_CHECKING:
24
+ from hypothesis.vendor.pretty import RepresentationPrinter
24
25
  from jsonschema import RefResolver
25
26
 
26
27
  from ...parameters import ParameterSet
@@ -203,6 +204,9 @@ class OpenAPILink(Direction):
203
204
  method = self.operation.method
204
205
  return f"state.schema['{path}']['{method}'].links['{self.status_code}']['{self.name}']"
205
206
 
207
+ def _repr_pretty_(self, printer: RepresentationPrinter, cycle: bool) -> None:
208
+ return printer.text(repr(self))
209
+
206
210
  def __post_init__(self) -> None:
207
211
  extension = self.definition.get(SCHEMATHESIS_LINK_EXTENSION)
208
212
  self.parameters = [
@@ -9,6 +9,8 @@ from ...parameters import Parameter
9
9
  from .converter import to_json_schema_recursive
10
10
 
11
11
  if TYPE_CHECKING:
12
+ from hypothesis.vendor.pretty import RepresentationPrinter
13
+
12
14
  from ...models import APIOperation
13
15
 
14
16
 
@@ -21,6 +23,9 @@ class OpenAPIParameter(Parameter):
21
23
  nullable_field: ClassVar[str]
22
24
  supported_jsonschema_keywords: ClassVar[tuple[str, ...]]
23
25
 
26
+ def _repr_pretty_(self, printer: RepresentationPrinter, cycle: bool) -> None:
27
+ return None
28
+
24
29
  @property
25
30
  def description(self) -> str | None:
26
31
  """A brief parameter description."""
@@ -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")
@@ -21,6 +21,7 @@ if TYPE_CHECKING:
21
21
  import requests
22
22
  import werkzeug
23
23
  from _typeshed.wsgi import WSGIApplication
24
+ from hypothesis.vendor.pretty import RepresentationPrinter
24
25
  from starlette_testclient._testclient import ASGI2App, ASGI3App
25
26
 
26
27
  from ..models import Case
@@ -34,6 +35,9 @@ class RequestConfig:
34
35
  proxy: str | None = None
35
36
  cert: RequestCert | None = None
36
37
 
38
+ def _repr_pretty_(self, printer: RepresentationPrinter, cycle: bool) -> None:
39
+ return None
40
+
37
41
  @property
38
42
  def prepared_timeout(self) -> float | None:
39
43
  return prepare_timeout(self.timeout)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: schemathesis
3
- Version: 3.36.4
3
+ Version: 3.37.1
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,15 +10,15 @@ 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=f0AUPxyBenpe1YGIWDY_uwSRoT2mE4Tp4Qase7f0L08,14953
18
+ schemathesis/lazy.py,sha256=Ddhkk7Tpc_VcRGYkCtKDmP2gpjxVmEZ3b01ZTNjbm8I,19004
19
19
  schemathesis/loaders.py,sha256=MoEhcdOEBJxNRn5X-ZNhWB9jZDHQQNpkNfEdQjf_NDw,4590
20
- schemathesis/models.py,sha256=mgufFwK6pVWn6hjfJIJwbk0qZM-XUeUaVVaOOEPaR50,46646
21
- schemathesis/parameters.py,sha256=PndmqQRlEYsCt1kWjSShPsFf6vj7X_7FRdz_-A95eNg,2258
20
+ schemathesis/models.py,sha256=WZZNBX5ujypy9wXislkoURIn6F_agqPedmm7-kVWIis,47018
21
+ schemathesis/parameters.py,sha256=_LN3NL5XwoRfvjcU8o2ArrNFK9sbBZo25UFdxuywkRw,2425
22
22
  schemathesis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  schemathesis/sanitization.py,sha256=Lycn1VVfula9B6XpzkxTHja7CZ7RHqbUh9kBic0Yi4M,9056
24
24
  schemathesis/schemas.py,sha256=3xTZOZ1lLdAdwLAkiW0eakRb96mQ0MpbcwmrT-XO4KA,20457
@@ -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
32
+ schemathesis/cli/callbacks.py,sha256=-VA_I_mVma9WxFNtUR8d2KNICKJD5ScayfSdKKPEP5Y,16321
33
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
@@ -59,10 +59,10 @@ schemathesis/fixups/fast_api.py,sha256=mn-KzBqnR8jl4W5fY-_ZySabMDMUnpzCIESMHnlvE
59
59
  schemathesis/fixups/utf8_bom.py,sha256=lWT9RNmJG8i-l5AXIpaCT3qCPUwRgzXPW3eoOjmZETA,745
60
60
  schemathesis/generation/__init__.py,sha256=29Zys_tD6kfngaC4zHeC6TOBZQcmo7CWm7KDSYsHStQ,1581
61
61
  schemathesis/generation/_hypothesis.py,sha256=Aaol5w3TEOVZn8znrnnJCYfrll8eALW0dCRtz3k0Eis,1661
62
- schemathesis/generation/_methods.py,sha256=jCK09f4sedDfePrS-6BIiE-CcEE8fJ4ZHxq1BHoTltQ,1101
63
- schemathesis/generation/coverage.py,sha256=rq7em0ifTQMZxprBE4NIJWFiV6PquKFcnMMSuR2-ohI,28268
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=qxJ5Ndeiveh_BT0QZq0Vbv72-ZTpbevOY48XKGQC9Ec,1730
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=4nHFPsq0ShfuZ2jPrJUuYu-vQpD1ImGE0fmXAKC_cZw,2883
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,22 +105,22 @@ 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
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
- schemathesis/specs/openapi/links.py,sha256=a8JmWM9aZhrR5CfyIh6t2SkfonMLfYKOScXY2XlZYN0,17749
114
+ schemathesis/specs/openapi/links.py,sha256=C4Uir2P_EcpqME8ee_a1vdUM8Tm3ZcKNn2YsGjZiMUQ,17935
115
115
  schemathesis/specs/openapi/loaders.py,sha256=5B1cgYEBj3h2psPQxzrQ5Xq5owLVGw-u9HsCQIx7yFE,25705
116
116
  schemathesis/specs/openapi/media_types.py,sha256=dNTxpRQbY3SubdVjh4Cjb38R6Bc9MF9BsRQwPD87x0g,1017
117
- schemathesis/specs/openapi/parameters.py,sha256=CqJdS4d14l25_yEbqkLCnfIdDTlodRhJpxD8EXdaFwM,14059
117
+ schemathesis/specs/openapi/parameters.py,sha256=Ga_g8mSl_2lFSgmuLYNEaA92JfqsAdxbhu5kMtalK_Y,14226
118
118
  schemathesis/specs/openapi/patterns.py,sha256=IK2BkXI1xByEz5if6jvydFE07nq5rDa4k_-2xX7ifG8,4715
119
119
  schemathesis/specs/openapi/references.py,sha256=euxM02kQGMHh4Ss1jWjOY_gyw_HazafKITIsvOEiAvI,9831
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
@@ -145,14 +145,14 @@ schemathesis/stateful/sink.py,sha256=bHYlgh-fMwg1Srxk_XGs0-WV34YccotwH9PGrxCK57A
145
145
  schemathesis/stateful/state_machine.py,sha256=PFztY82W5enuXjO6k4Mz8fbHmDJ7Z8OLYZRWtuBeyjg,12956
146
146
  schemathesis/stateful/statistic.py,sha256=2-uU5xpT9CbMulKgJWLZN6MUpC0Fskf5yXTt4ef4NFA,542
147
147
  schemathesis/stateful/validation.py,sha256=23qSZjC1_xRmtCX4OqsyG6pGxdlo6IZYid695ZpDQyU,3747
148
- schemathesis/transports/__init__.py,sha256=ybH90TrGwODO5s94UMEX2P2HX-6Jb66X5UUOgKTbZz8,12882
148
+ schemathesis/transports/__init__.py,sha256=kFM_0RcBjjw5Jg5ddBhWi98pvC0WrVsR_irJNAb4FQs,13048
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.4.dist-info/METADATA,sha256=_FwLfobUt1E2LW6L-xhL-bMbe3VGSOgoykQT8DhOan0,12904
155
- schemathesis-3.36.4.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
156
- schemathesis-3.36.4.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
157
- schemathesis-3.36.4.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
158
- schemathesis-3.36.4.dist-info/RECORD,,
154
+ schemathesis-3.37.1.dist-info/METADATA,sha256=I0-7WaiuMLY42hqpDEOfx1ipBOi5hAfyMM-U9CP46aY,12905
155
+ schemathesis-3.37.1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
156
+ schemathesis-3.37.1.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
157
+ schemathesis-3.37.1.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
158
+ schemathesis-3.37.1.dist-info/RECORD,,