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.
- schemathesis/_hypothesis.py +1 -1
- schemathesis/cli/__init__.py +41 -5
- schemathesis/cli/callbacks.py +42 -0
- schemathesis/cli/options.py +7 -0
- schemathesis/exceptions.py +4 -0
- schemathesis/experimental/__init__.py +7 -0
- schemathesis/failures.py +13 -0
- schemathesis/generation/_methods.py +4 -0
- schemathesis/generation/coverage.py +22 -8
- schemathesis/hooks.py +6 -2
- schemathesis/internal/checks.py +19 -1
- schemathesis/lazy.py +4 -3
- schemathesis/models.py +10 -0
- schemathesis/parameters.py +5 -0
- schemathesis/runner/__init__.py +9 -0
- schemathesis/runner/impl/context.py +16 -2
- schemathesis/runner/impl/core.py +18 -5
- schemathesis/specs/openapi/checks.py +40 -3
- schemathesis/specs/openapi/links.py +4 -0
- schemathesis/specs/openapi/parameters.py +5 -0
- schemathesis/specs/openapi/utils.py +5 -1
- schemathesis/transports/__init__.py +4 -0
- {schemathesis-3.36.4.dist-info → schemathesis-3.37.1.dist-info}/METADATA +2 -2
- {schemathesis-3.36.4.dist-info → schemathesis-3.37.1.dist-info}/RECORD +27 -27
- {schemathesis-3.36.4.dist-info → schemathesis-3.37.1.dist-info}/WHEEL +0 -0
- {schemathesis-3.36.4.dist-info → schemathesis-3.37.1.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.36.4.dist-info → schemathesis-3.37.1.dist-info}/licenses/LICENSE +0 -0
schemathesis/_hypothesis.py
CHANGED
schemathesis/cli/__init__.py
CHANGED
|
@@ -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
|
-
"--
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
type=
|
|
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,
|
schemathesis/cli/callbacks.py
CHANGED
|
@@ -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
|
|
schemathesis/cli/options.py
CHANGED
|
@@ -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
|
schemathesis/exceptions.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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(
|
|
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(
|
schemathesis/internal/checks.py
CHANGED
|
@@ -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
|
|
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
|
|
schemathesis/parameters.py
CHANGED
|
@@ -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)
|
schemathesis/runner/__init__.py
CHANGED
|
@@ -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,
|
|
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:
|
schemathesis/runner/impl/core.py
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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.
|
|
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.
|
|
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=
|
|
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=
|
|
14
|
-
schemathesis/failures.py,sha256=
|
|
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=
|
|
18
|
-
schemathesis/lazy.py,sha256=
|
|
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=
|
|
21
|
-
schemathesis/parameters.py,sha256=
|
|
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=
|
|
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
|
|
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=
|
|
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=
|
|
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=
|
|
63
|
-
schemathesis/generation/coverage.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
81
|
-
schemathesis/runner/impl/core.py,sha256=
|
|
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
|
|
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=
|
|
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=
|
|
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
|
|
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=
|
|
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.
|
|
155
|
-
schemathesis-3.
|
|
156
|
-
schemathesis-3.
|
|
157
|
-
schemathesis-3.
|
|
158
|
-
schemathesis-3.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|