schemathesis 3.39.8__py3-none-any.whl → 3.39.10__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 +121 -29
- schemathesis/cli/output/default.py +2 -18
- schemathesis/constants.py +0 -2
- schemathesis/failures.py +2 -1
- schemathesis/generation/coverage.py +6 -1
- schemathesis/internal/deprecation.py +1 -2
- schemathesis/models.py +1 -1
- schemathesis/runner/impl/context.py +0 -16
- schemathesis/runner/impl/core.py +29 -5
- schemathesis/specs/openapi/checks.py +15 -10
- schemathesis/specs/openapi/loaders.py +1 -2
- schemathesis/specs/openapi/parameters.py +1 -1
- schemathesis/stateful/state_machine.py +2 -1
- {schemathesis-3.39.8.dist-info → schemathesis-3.39.10.dist-info}/METADATA +1 -1
- {schemathesis-3.39.8.dist-info → schemathesis-3.39.10.dist-info}/RECORD +18 -18
- {schemathesis-3.39.8.dist-info → schemathesis-3.39.10.dist-info}/WHEEL +0 -0
- {schemathesis-3.39.8.dist-info → schemathesis-3.39.10.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.39.8.dist-info → schemathesis-3.39.10.dist-info}/licenses/LICENSE +0 -0
schemathesis/_hypothesis.py
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import asyncio
|
6
|
+
from dataclasses import dataclass
|
6
7
|
import json
|
7
8
|
import warnings
|
8
9
|
from functools import wraps
|
@@ -223,6 +224,87 @@ def add_coverage(
|
|
223
224
|
return test
|
224
225
|
|
225
226
|
|
227
|
+
class Template:
|
228
|
+
__slots__ = ("_components", "_template")
|
229
|
+
|
230
|
+
def __init__(self) -> None:
|
231
|
+
self._components: dict[str, DataGenerationMethod] = {}
|
232
|
+
self._template: dict[str, Any] = {}
|
233
|
+
|
234
|
+
def __contains__(self, key: str) -> bool:
|
235
|
+
return key in self._template
|
236
|
+
|
237
|
+
def __getitem__(self, key: str) -> dict:
|
238
|
+
return self._template[key]
|
239
|
+
|
240
|
+
def get(self, key: str, default: Any = None) -> dict:
|
241
|
+
return self._template.get(key, default)
|
242
|
+
|
243
|
+
def add_parameter(self, location: str, name: str, value: coverage.GeneratedValue) -> None:
|
244
|
+
from .specs.openapi.constants import LOCATION_TO_CONTAINER
|
245
|
+
|
246
|
+
component_name = LOCATION_TO_CONTAINER[location]
|
247
|
+
method = self._components.get(component_name)
|
248
|
+
if method is None:
|
249
|
+
self._components[component_name] = value.data_generation_method
|
250
|
+
elif value.data_generation_method == DataGenerationMethod.negative:
|
251
|
+
self._components[component_name] = DataGenerationMethod.negative
|
252
|
+
|
253
|
+
container = self._template.setdefault(component_name, {})
|
254
|
+
if _should_stringify(location, value):
|
255
|
+
container[name] = _stringify_value(value.value, location)
|
256
|
+
else:
|
257
|
+
container[name] = value.value
|
258
|
+
|
259
|
+
def set_body(self, body: coverage.GeneratedValue, media_type: str) -> None:
|
260
|
+
self._template["body"] = body.value
|
261
|
+
self._template["media_type"] = media_type
|
262
|
+
self._components["body"] = body.data_generation_method
|
263
|
+
|
264
|
+
def unmodified(self) -> TemplateValue:
|
265
|
+
return TemplateValue(kwargs=self._template.copy(), components=self._components.copy())
|
266
|
+
|
267
|
+
def with_body(self, *, media_type: str, value: coverage.GeneratedValue) -> TemplateValue:
|
268
|
+
kwargs = {**self._template, "media_type": media_type, "body": value.value}
|
269
|
+
components = {**self._components, "body": value.data_generation_method}
|
270
|
+
return TemplateValue(kwargs=kwargs, components=components)
|
271
|
+
|
272
|
+
def with_parameter(self, *, location: str, name: str, value: coverage.GeneratedValue) -> TemplateValue:
|
273
|
+
from .specs.openapi.constants import LOCATION_TO_CONTAINER
|
274
|
+
|
275
|
+
if _should_stringify(location, value):
|
276
|
+
generated = _stringify_value(value.value, location)
|
277
|
+
else:
|
278
|
+
generated = value.value
|
279
|
+
|
280
|
+
container_name = LOCATION_TO_CONTAINER[location]
|
281
|
+
container = self._template[container_name]
|
282
|
+
kwargs = {**self._template, container_name: {**container, name: generated}}
|
283
|
+
components = {**self._components, container_name: value.data_generation_method}
|
284
|
+
return TemplateValue(kwargs=kwargs, components=components)
|
285
|
+
|
286
|
+
|
287
|
+
@dataclass
|
288
|
+
class TemplateValue:
|
289
|
+
kwargs: dict[str, Any]
|
290
|
+
components: dict[str, DataGenerationMethod]
|
291
|
+
__slots__ = ("kwargs", "components")
|
292
|
+
|
293
|
+
|
294
|
+
def _should_stringify(location: str, value: coverage.GeneratedValue) -> bool:
|
295
|
+
return location in ("header", "cookie", "path", "query") and not isinstance(value.value, str)
|
296
|
+
|
297
|
+
|
298
|
+
def _stringify_value(val: Any, location: str) -> str | list[str]:
|
299
|
+
if isinstance(val, list):
|
300
|
+
if location == "query":
|
301
|
+
# Having a list here ensures there will be multiple query parameters wit the same name
|
302
|
+
return [json.dumps(item) for item in val]
|
303
|
+
# use comma-separated values style for arrays
|
304
|
+
return ",".join(json.dumps(sub) for sub in val)
|
305
|
+
return json.dumps(val)
|
306
|
+
|
307
|
+
|
226
308
|
def _iter_coverage_cases(
|
227
309
|
operation: APIOperation, data_generation_methods: list[DataGenerationMethod]
|
228
310
|
) -> Generator[Case, None, None]:
|
@@ -239,7 +321,7 @@ def _iter_coverage_cases(
|
|
239
321
|
return json.dumps(val)
|
240
322
|
|
241
323
|
generators: dict[tuple[str, str], Generator[coverage.GeneratedValue, None, None]] = {}
|
242
|
-
template
|
324
|
+
template = Template()
|
243
325
|
responses = find_in_responses(operation)
|
244
326
|
for parameter in operation.iter_parameters():
|
245
327
|
location = parameter.location
|
@@ -253,11 +335,7 @@ def _iter_coverage_cases(
|
|
253
335
|
value = next(gen, NOT_SET)
|
254
336
|
if isinstance(value, NotSet):
|
255
337
|
continue
|
256
|
-
|
257
|
-
if location in ("header", "cookie", "path", "query") and not isinstance(value.value, str):
|
258
|
-
container[name] = _stringify_value(value.value, location)
|
259
|
-
else:
|
260
|
-
container[name] = value.value
|
338
|
+
template.add_parameter(location, name, value)
|
261
339
|
generators[(location, name)] = gen
|
262
340
|
if operation.body:
|
263
341
|
for body in operation.body:
|
@@ -274,48 +352,48 @@ def _iter_coverage_cases(
|
|
274
352
|
if isinstance(value, NotSet):
|
275
353
|
continue
|
276
354
|
if "body" not in template:
|
277
|
-
template
|
278
|
-
|
279
|
-
case = operation.make_case(**
|
355
|
+
template.set_body(value, body.media_type)
|
356
|
+
data = template.with_body(value=value, media_type=body.media_type)
|
357
|
+
case = operation.make_case(**data.kwargs)
|
280
358
|
case.data_generation_method = value.data_generation_method
|
281
359
|
case.meta = _make_meta(
|
282
360
|
description=value.description,
|
283
361
|
location=value.location,
|
284
362
|
parameter=body.media_type,
|
285
363
|
parameter_location="body",
|
364
|
+
**data.components,
|
286
365
|
)
|
287
366
|
yield case
|
288
367
|
for next_value in gen:
|
289
|
-
|
368
|
+
data = template.with_body(value=next_value, media_type=body.media_type)
|
369
|
+
case = operation.make_case(**data.kwargs)
|
290
370
|
case.data_generation_method = next_value.data_generation_method
|
291
371
|
case.meta = _make_meta(
|
292
372
|
description=next_value.description,
|
293
373
|
location=next_value.location,
|
294
374
|
parameter=body.media_type,
|
295
375
|
parameter_location="body",
|
376
|
+
**data.components,
|
296
377
|
)
|
297
378
|
yield case
|
298
379
|
elif DataGenerationMethod.positive in data_generation_methods:
|
299
|
-
|
380
|
+
data = template.unmodified()
|
381
|
+
case = operation.make_case(**data.kwargs)
|
300
382
|
case.data_generation_method = DataGenerationMethod.positive
|
301
|
-
case.meta = _make_meta(description="Default positive test case")
|
383
|
+
case.meta = _make_meta(description="Default positive test case", **data.components)
|
302
384
|
yield case
|
303
385
|
|
304
386
|
for (location, name), gen in generators.items():
|
305
|
-
container_name = LOCATION_TO_CONTAINER[location]
|
306
|
-
container = template[container_name]
|
307
387
|
for value in gen:
|
308
|
-
|
309
|
-
|
310
|
-
else:
|
311
|
-
generated = value.value
|
312
|
-
case = operation.make_case(**{**template, container_name: {**container, name: generated}})
|
388
|
+
data = template.with_parameter(location=location, name=name, value=value)
|
389
|
+
case = operation.make_case(**data.kwargs)
|
313
390
|
case.data_generation_method = value.data_generation_method
|
314
391
|
case.meta = _make_meta(
|
315
392
|
description=value.description,
|
316
393
|
location=value.location,
|
317
394
|
parameter=name,
|
318
395
|
parameter_location=location,
|
396
|
+
**data.components,
|
319
397
|
)
|
320
398
|
yield case
|
321
399
|
if DataGenerationMethod.negative in data_generation_methods:
|
@@ -323,10 +401,11 @@ def _iter_coverage_cases(
|
|
323
401
|
# NOTE: The HEAD method is excluded
|
324
402
|
methods = {"get", "put", "post", "delete", "options", "patch", "trace"} - set(operation.schema[operation.path])
|
325
403
|
for method in sorted(methods):
|
326
|
-
|
404
|
+
data = template.unmodified()
|
405
|
+
case = operation.make_case(**data.kwargs)
|
327
406
|
case._explicit_method = method
|
328
407
|
case.data_generation_method = DataGenerationMethod.negative
|
329
|
-
case.meta = _make_meta(description=f"Unspecified HTTP method: {method.upper()}")
|
408
|
+
case.meta = _make_meta(description=f"Unspecified HTTP method: {method.upper()}", **data.components)
|
330
409
|
yield case
|
331
410
|
# Generate duplicate query parameters
|
332
411
|
if operation.query:
|
@@ -336,13 +415,17 @@ def _iter_coverage_cases(
|
|
336
415
|
# I.e. contains just `default` value without any other keywords
|
337
416
|
value = container.get(parameter.name, NOT_SET)
|
338
417
|
if value is not NOT_SET:
|
339
|
-
|
418
|
+
data = template.unmodified()
|
419
|
+
case = operation.make_case(
|
420
|
+
**{**data.kwargs, "query": {**container, parameter.name: [value, value]}}
|
421
|
+
)
|
340
422
|
case.data_generation_method = DataGenerationMethod.negative
|
341
423
|
case.meta = _make_meta(
|
342
424
|
description=f"Duplicate `{parameter.name}` query parameter",
|
343
425
|
location=None,
|
344
426
|
parameter=parameter.name,
|
345
427
|
parameter_location="query",
|
428
|
+
**data.components,
|
346
429
|
)
|
347
430
|
yield case
|
348
431
|
# Generate missing required parameters
|
@@ -352,8 +435,9 @@ def _iter_coverage_cases(
|
|
352
435
|
location = parameter.location
|
353
436
|
container_name = LOCATION_TO_CONTAINER[location]
|
354
437
|
container = template[container_name]
|
438
|
+
data = template.unmodified()
|
355
439
|
case = operation.make_case(
|
356
|
-
**{**
|
440
|
+
**{**data.kwargs, container_name: {k: v for k, v in container.items() if k != name}}
|
357
441
|
)
|
358
442
|
case.data_generation_method = DataGenerationMethod.negative
|
359
443
|
case.meta = _make_meta(
|
@@ -361,6 +445,7 @@ def _iter_coverage_cases(
|
|
361
445
|
location=None,
|
362
446
|
parameter=name,
|
363
447
|
parameter_location=location,
|
448
|
+
**{**data.components, container_name: DataGenerationMethod.negative},
|
364
449
|
)
|
365
450
|
yield case
|
366
451
|
# Generate combinations for each location
|
@@ -397,13 +482,15 @@ def _iter_coverage_cases(
|
|
397
482
|
else:
|
398
483
|
container = container_values
|
399
484
|
|
400
|
-
|
485
|
+
data = template.unmodified()
|
486
|
+
case = operation.make_case(**{**data.kwargs, _container_name: container})
|
401
487
|
case.data_generation_method = _data_generation_method
|
402
488
|
case.meta = _make_meta(
|
403
489
|
description=description,
|
404
490
|
location=None,
|
405
491
|
parameter=_parameter,
|
406
492
|
parameter_location=_location,
|
493
|
+
**{**data.components, _container_name: _data_generation_method},
|
407
494
|
)
|
408
495
|
return case
|
409
496
|
|
@@ -496,13 +583,18 @@ def _make_meta(
|
|
496
583
|
location: str | None = None,
|
497
584
|
parameter: str | None = None,
|
498
585
|
parameter_location: str | None = None,
|
586
|
+
query: DataGenerationMethod | None = None,
|
587
|
+
path_parameters: DataGenerationMethod | None = None,
|
588
|
+
headers: DataGenerationMethod | None = None,
|
589
|
+
cookies: DataGenerationMethod | None = None,
|
590
|
+
body: DataGenerationMethod | None = None,
|
499
591
|
) -> GenerationMetadata:
|
500
592
|
return GenerationMetadata(
|
501
|
-
query=
|
502
|
-
path_parameters=
|
503
|
-
headers=
|
504
|
-
cookies=
|
505
|
-
body=
|
593
|
+
query=query,
|
594
|
+
path_parameters=path_parameters,
|
595
|
+
headers=headers,
|
596
|
+
cookies=cookies,
|
597
|
+
body=body,
|
506
598
|
phase=TestPhase.COVERAGE,
|
507
599
|
description=description,
|
508
600
|
location=location,
|
@@ -14,11 +14,8 @@ import click
|
|
14
14
|
from ... import experimental, service
|
15
15
|
from ...constants import (
|
16
16
|
DISCORD_LINK,
|
17
|
-
FALSE_VALUES,
|
18
17
|
FLAKY_FAILURE_MESSAGE,
|
19
|
-
GITHUB_APP_LINK,
|
20
18
|
ISSUE_TRACKER_URL,
|
21
|
-
REPORT_SUGGESTION_ENV_VAR,
|
22
19
|
SCHEMATHESIS_TEST_CASE_HEADER,
|
23
20
|
SCHEMATHESIS_VERSION,
|
24
21
|
)
|
@@ -125,7 +122,7 @@ def get_summary_output(event: events.Finished) -> tuple[str, str]:
|
|
125
122
|
message = "Empty test suite"
|
126
123
|
color = "yellow"
|
127
124
|
else:
|
128
|
-
message = f'
|
125
|
+
message = f"{', '.join(parts)} in {event.running_time:.2f}s"
|
129
126
|
if event.has_failures or event.has_errors:
|
130
127
|
color = "red"
|
131
128
|
elif event.skipped_count > 0:
|
@@ -176,7 +173,7 @@ def display_errors(context: ExecutionContext, event: events.Finished) -> None:
|
|
176
173
|
fg="red",
|
177
174
|
)
|
178
175
|
click.secho(
|
179
|
-
f"\nNeed more help?\n
|
176
|
+
f"\nNeed more help?\n Join our Discord server: {DISCORD_LINK}",
|
180
177
|
fg="red",
|
181
178
|
)
|
182
179
|
|
@@ -499,19 +496,6 @@ def display_statistic(context: ExecutionContext, event: events.Finished) -> None
|
|
499
496
|
elif isinstance(context.report, ServiceReportContext):
|
500
497
|
click.echo()
|
501
498
|
handle_service_integration(context.report)
|
502
|
-
else:
|
503
|
-
env_var = os.getenv(REPORT_SUGGESTION_ENV_VAR)
|
504
|
-
if env_var is not None and env_var.lower() in FALSE_VALUES:
|
505
|
-
return
|
506
|
-
click.echo(
|
507
|
-
f"\n{bold('Tip')}: Use the {bold('`--report`')} CLI option to visualize test results via Schemathesis.io.\n"
|
508
|
-
"We run additional conformance checks on reports from public repos."
|
509
|
-
)
|
510
|
-
if service.ci.detect() == service.ci.CIProvider.GITHUB:
|
511
|
-
click.echo(
|
512
|
-
"Optionally, for reporting results as PR comments, install the Schemathesis GitHub App:\n\n"
|
513
|
-
f" {GITHUB_APP_LINK}"
|
514
|
-
)
|
515
499
|
|
516
500
|
|
517
501
|
def handle_service_integration(context: ServiceReportContext) -> None:
|
schemathesis/constants.py
CHANGED
@@ -13,7 +13,6 @@ USER_AGENT = f"schemathesis/{SCHEMATHESIS_VERSION}"
|
|
13
13
|
SCHEMATHESIS_TEST_CASE_HEADER = "X-Schemathesis-TestCaseId"
|
14
14
|
HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER = ":memory:"
|
15
15
|
DISCORD_LINK = "https://discord.gg/R9ASRAmHnA"
|
16
|
-
GITHUB_APP_LINK = "https://github.com/apps/schemathesis"
|
17
16
|
# Maximum test running time
|
18
17
|
DEFAULT_DEADLINE = 15000
|
19
18
|
DEFAULT_RESPONSE_TIMEOUT = 10000
|
@@ -50,7 +49,6 @@ HOOKS_MODULE_ENV_VAR = "SCHEMATHESIS_HOOKS"
|
|
50
49
|
API_NAME_ENV_VAR = "SCHEMATHESIS_API_NAME"
|
51
50
|
BASE_URL_ENV_VAR = "SCHEMATHESIS_BASE_URL"
|
52
51
|
WAIT_FOR_SCHEMA_ENV_VAR = "SCHEMATHESIS_WAIT_FOR_SCHEMA"
|
53
|
-
REPORT_SUGGESTION_ENV_VAR = "SCHEMATHESIS_REPORT_SUGGESTION"
|
54
52
|
|
55
53
|
TRUE_VALUES = ("y", "yes", "t", "true", "on", "1")
|
56
54
|
FALSE_VALUES = ("n", "no", "f", "false", "off", "0")
|
schemathesis/failures.py
CHANGED
@@ -93,8 +93,9 @@ class JSONDecodeErrorContext(FailureContext):
|
|
93
93
|
|
94
94
|
@classmethod
|
95
95
|
def from_exception(cls, exc: JSONDecodeError) -> JSONDecodeErrorContext:
|
96
|
+
message = f"Response must be valid JSON with 'Content-Type: application/json' header:\n\n {exc}"
|
96
97
|
return cls(
|
97
|
-
message=
|
98
|
+
message=message,
|
98
99
|
validation_message=exc.msg,
|
99
100
|
document=exc.doc,
|
100
101
|
position=exc.pos,
|
@@ -437,7 +437,12 @@ def cover_schema_iter(
|
|
437
437
|
elif key == "required":
|
438
438
|
template = template or ctx.generate_from_schema(_get_template_schema(schema, "object"))
|
439
439
|
yield from _negative_required(ctx, template, value)
|
440
|
-
elif
|
440
|
+
elif (
|
441
|
+
key == "additionalProperties"
|
442
|
+
and not value
|
443
|
+
and "pattern" not in schema
|
444
|
+
and schema.get("type") in ["object", None]
|
445
|
+
):
|
441
446
|
template = template or ctx.generate_from_schema(_get_template_schema(schema, "object"))
|
442
447
|
yield NegativeValue(
|
443
448
|
{**template, UNKNOWN_PROPERTY_KEY: UNKNOWN_PROPERTY_VALUE},
|
@@ -4,8 +4,7 @@ from typing import Any, Callable
|
|
4
4
|
|
5
5
|
def _warn_deprecation(*, kind: str, thing: str, removed_in: str, replacement: str) -> None:
|
6
6
|
warnings.warn(
|
7
|
-
f"{kind} `{thing}` is deprecated and will be removed in Schemathesis {removed_in}. "
|
8
|
-
f"Use {replacement} instead.",
|
7
|
+
f"{kind} `{thing}` is deprecated and will be removed in Schemathesis {removed_in}. Use {replacement} instead.",
|
9
8
|
DeprecationWarning,
|
10
9
|
stacklevel=1,
|
11
10
|
)
|
schemathesis/models.py
CHANGED
@@ -560,7 +560,7 @@ class Case:
|
|
560
560
|
sanitize_response(response)
|
561
561
|
code_message = self._get_code_message(code_sample_style, response.request, verify=verify)
|
562
562
|
raise exception_cls(
|
563
|
-
f"{formatted}\n\n
|
563
|
+
f"{formatted}\n\n{code_message}",
|
564
564
|
causes=tuple(failed_checks),
|
565
565
|
)
|
566
566
|
|
@@ -69,22 +69,6 @@ class RunnerContext:
|
|
69
69
|
def is_stopped(self) -> bool:
|
70
70
|
return self.stop_event.is_set()
|
71
71
|
|
72
|
-
@property
|
73
|
-
def has_all_not_found(self) -> bool:
|
74
|
-
"""Check if all responses are 404."""
|
75
|
-
has_not_found = False
|
76
|
-
for entry in self.data.results:
|
77
|
-
for check in entry.checks:
|
78
|
-
if check.response is not None:
|
79
|
-
if check.response.status_code == 404:
|
80
|
-
has_not_found = True
|
81
|
-
else:
|
82
|
-
# There are non-404 responses, no reason to check any other response
|
83
|
-
return False
|
84
|
-
# Only happens if all responses are 404, or there are no responses at all.
|
85
|
-
# In the first case, it returns True, for the latter - False
|
86
|
-
return has_not_found
|
87
|
-
|
88
72
|
def add_result(self, result: TestResult) -> None:
|
89
73
|
self.data.append(result)
|
90
74
|
|
schemathesis/runner/impl/core.py
CHANGED
@@ -147,6 +147,34 @@ class BaseRunner:
|
|
147
147
|
__probes = None
|
148
148
|
__analysis: Result[AnalysisResult, Exception] | None = None
|
149
149
|
|
150
|
+
def _should_warn_about_only_4xx(result: TestResult) -> bool:
|
151
|
+
if all(check.response is None for check in result.checks):
|
152
|
+
return False
|
153
|
+
# Don't warn if we saw any 2xx or 5xx responses
|
154
|
+
if any(
|
155
|
+
check.response.status_code < 400 or check.response.status_code >= 500
|
156
|
+
for check in result.checks
|
157
|
+
if check.response is not None
|
158
|
+
):
|
159
|
+
return False
|
160
|
+
# Don't duplicate auth warnings
|
161
|
+
if {check.response.status_code for check in result.checks if check.response is not None} <= {401, 403}:
|
162
|
+
return False
|
163
|
+
# At this point we know we only have 4xx responses
|
164
|
+
return True
|
165
|
+
|
166
|
+
def _check_warnings() -> None:
|
167
|
+
for result in ctx.data.results:
|
168
|
+
# Only warn about 4xx responses in successful positive test scenarios
|
169
|
+
if (
|
170
|
+
all(check.value == Status.success for check in result.checks)
|
171
|
+
and result.data_generation_method == [DataGenerationMethod.positive]
|
172
|
+
and _should_warn_about_only_4xx(result)
|
173
|
+
):
|
174
|
+
ctx.add_warning(
|
175
|
+
f"`{result.verbose_name}` returned only 4xx responses during unit tests. Check base URL or adjust data generation settings"
|
176
|
+
)
|
177
|
+
|
150
178
|
def _initialize() -> events.Initialized:
|
151
179
|
nonlocal initialized
|
152
180
|
initialized = events.Initialized.from_schema(
|
@@ -159,8 +187,7 @@ class BaseRunner:
|
|
159
187
|
return initialized
|
160
188
|
|
161
189
|
def _finish() -> events.Finished:
|
162
|
-
|
163
|
-
ctx.add_warning(ALL_NOT_FOUND_WARNING_MESSAGE)
|
190
|
+
_check_warnings()
|
164
191
|
return events.Finished.from_results(results=ctx.data, running_time=time.monotonic() - start_time)
|
165
192
|
|
166
193
|
def _before_probes() -> events.BeforeProbing:
|
@@ -742,9 +769,6 @@ def has_too_many_responses_with_status(result: TestResult, status_code: int) ->
|
|
742
769
|
return unauthorized_count / total >= TOO_MANY_RESPONSES_THRESHOLD
|
743
770
|
|
744
771
|
|
745
|
-
ALL_NOT_FOUND_WARNING_MESSAGE = "All API responses have a 404 status code. Did you specify the proper API location?"
|
746
|
-
|
747
|
-
|
748
772
|
def setup_hypothesis_database_key(test: Callable, operation: APIOperation) -> None:
|
749
773
|
"""Make Hypothesis use separate database entries for every API operation.
|
750
774
|
|
@@ -32,10 +32,15 @@ if TYPE_CHECKING:
|
|
32
32
|
from ...transports.responses import GenericResponse
|
33
33
|
|
34
34
|
|
35
|
+
def is_unexpected_http_status_case(case: Case) -> bool:
|
36
|
+
# Skip checks for requests using HTTP methods not defined in the API spec
|
37
|
+
return bool(case.meta and case.meta.description and case.meta.description.startswith("Unspecified HTTP method"))
|
38
|
+
|
39
|
+
|
35
40
|
def status_code_conformance(ctx: CheckContext, response: GenericResponse, case: Case) -> bool | None:
|
36
41
|
from .schemas import BaseOpenAPISchema
|
37
42
|
|
38
|
-
if not isinstance(case.operation.schema, BaseOpenAPISchema):
|
43
|
+
if not isinstance(case.operation.schema, BaseOpenAPISchema) or is_unexpected_http_status_case(case):
|
39
44
|
return True
|
40
45
|
responses = case.operation.definition.raw.get("responses", {})
|
41
46
|
# "default" can be used as the default response object for all HTTP codes that are not covered individually
|
@@ -66,7 +71,7 @@ def _expand_responses(responses: dict[str | int, Any]) -> Generator[int, None, N
|
|
66
71
|
def content_type_conformance(ctx: CheckContext, response: GenericResponse, case: Case) -> bool | None:
|
67
72
|
from .schemas import BaseOpenAPISchema
|
68
73
|
|
69
|
-
if not isinstance(case.operation.schema, BaseOpenAPISchema):
|
74
|
+
if not isinstance(case.operation.schema, BaseOpenAPISchema) or is_unexpected_http_status_case(case):
|
70
75
|
return True
|
71
76
|
documented_content_types = case.operation.schema.get_content_types(case.operation, response)
|
72
77
|
if not documented_content_types:
|
@@ -124,7 +129,7 @@ def response_headers_conformance(ctx: CheckContext, response: GenericResponse, c
|
|
124
129
|
from .parameters import OpenAPI20Parameter, OpenAPI30Parameter
|
125
130
|
from .schemas import BaseOpenAPISchema, OpenApi30, _maybe_raise_one_or_more
|
126
131
|
|
127
|
-
if not isinstance(case.operation.schema, BaseOpenAPISchema):
|
132
|
+
if not isinstance(case.operation.schema, BaseOpenAPISchema) or is_unexpected_http_status_case(case):
|
128
133
|
return True
|
129
134
|
resolved = case.operation.schema.get_headers(case.operation, response)
|
130
135
|
if not resolved:
|
@@ -209,7 +214,7 @@ def _coerce_header_value(value: str, schema: dict[str, Any]) -> str | int | floa
|
|
209
214
|
def response_schema_conformance(ctx: CheckContext, response: GenericResponse, case: Case) -> bool | None:
|
210
215
|
from .schemas import BaseOpenAPISchema
|
211
216
|
|
212
|
-
if not isinstance(case.operation.schema, BaseOpenAPISchema):
|
217
|
+
if not isinstance(case.operation.schema, BaseOpenAPISchema) or is_unexpected_http_status_case(case):
|
213
218
|
return True
|
214
219
|
return case.operation.validate_response(response)
|
215
220
|
|
@@ -217,7 +222,7 @@ def response_schema_conformance(ctx: CheckContext, response: GenericResponse, ca
|
|
217
222
|
def negative_data_rejection(ctx: CheckContext, response: GenericResponse, case: Case) -> bool | None:
|
218
223
|
from .schemas import BaseOpenAPISchema
|
219
224
|
|
220
|
-
if not isinstance(case.operation.schema, BaseOpenAPISchema):
|
225
|
+
if not isinstance(case.operation.schema, BaseOpenAPISchema) or is_unexpected_http_status_case(case):
|
221
226
|
return True
|
222
227
|
|
223
228
|
config = ctx.config.negative_data_rejection
|
@@ -245,7 +250,7 @@ def negative_data_rejection(ctx: CheckContext, response: GenericResponse, case:
|
|
245
250
|
def positive_data_acceptance(ctx: CheckContext, response: GenericResponse, case: Case) -> bool | None:
|
246
251
|
from .schemas import BaseOpenAPISchema
|
247
252
|
|
248
|
-
if not isinstance(case.operation.schema, BaseOpenAPISchema):
|
253
|
+
if not isinstance(case.operation.schema, BaseOpenAPISchema) or is_unexpected_http_status_case(case):
|
249
254
|
return True
|
250
255
|
|
251
256
|
config = ctx.config.positive_data_acceptance
|
@@ -283,7 +288,7 @@ def missing_required_header(ctx: CheckContext, response: GenericResponse, case:
|
|
283
288
|
config = ctx.config.missing_required_header
|
284
289
|
allowed_statuses = expand_status_codes(config.allowed_statuses or [])
|
285
290
|
if response.status_code not in allowed_statuses:
|
286
|
-
allowed = f"Allowed statuses: {', '.join(map(str,allowed_statuses))}"
|
291
|
+
allowed = f"Allowed statuses: {', '.join(map(str, allowed_statuses))}"
|
287
292
|
raise AssertionError(f"Unexpected response status for a missing header: {response.status_code}\n{allowed}")
|
288
293
|
return None
|
289
294
|
|
@@ -333,7 +338,7 @@ def use_after_free(ctx: CheckContext, response: GenericResponse, original: Case)
|
|
333
338
|
from ...transports.responses import get_reason
|
334
339
|
from .schemas import BaseOpenAPISchema
|
335
340
|
|
336
|
-
if not isinstance(original.operation.schema, BaseOpenAPISchema):
|
341
|
+
if not isinstance(original.operation.schema, BaseOpenAPISchema) or is_unexpected_http_status_case(original):
|
337
342
|
return True
|
338
343
|
if response.status_code == 404 or not original.source or response.status_code >= 500:
|
339
344
|
return None
|
@@ -373,7 +378,7 @@ def ensure_resource_availability(ctx: CheckContext, response: GenericResponse, o
|
|
373
378
|
from ...transports.responses import get_reason
|
374
379
|
from .schemas import BaseOpenAPISchema
|
375
380
|
|
376
|
-
if not isinstance(original.operation.schema, BaseOpenAPISchema):
|
381
|
+
if not isinstance(original.operation.schema, BaseOpenAPISchema) or is_unexpected_http_status_case(original):
|
377
382
|
return True
|
378
383
|
if (
|
379
384
|
# Response indicates a client error, even though all available parameters were taken from links
|
@@ -416,7 +421,7 @@ def ignored_auth(ctx: CheckContext, response: GenericResponse, case: Case) -> bo
|
|
416
421
|
"""Check if an operation declares authentication as a requirement but does not actually enforce it."""
|
417
422
|
from .schemas import BaseOpenAPISchema
|
418
423
|
|
419
|
-
if not isinstance(case.operation.schema, BaseOpenAPISchema):
|
424
|
+
if not isinstance(case.operation.schema, BaseOpenAPISchema) or is_unexpected_http_status_case(case):
|
420
425
|
return True
|
421
426
|
security_parameters = _get_security_parameters(case.operation)
|
422
427
|
# Authentication is required for this API operation and response is successful
|
@@ -432,8 +432,7 @@ For more details, check the Open API documentation: {DOC_ENTRY}
|
|
432
432
|
|
433
433
|
Please, stringify the following status codes:"""
|
434
434
|
NON_STRING_OBJECT_KEY_MESSAGE = (
|
435
|
-
"The Open API specification requires all keys in the schema to be strings. "
|
436
|
-
"You have some keys that are not strings."
|
435
|
+
"The Open API specification requires all keys in the schema to be strings. You have some keys that are not strings."
|
437
436
|
)
|
438
437
|
|
439
438
|
|
@@ -359,7 +359,7 @@ MISSING_SCHEMA_OR_CONTENT_MESSAGE = (
|
|
359
359
|
)
|
360
360
|
|
361
361
|
INVALID_SCHEMA_MESSAGE = (
|
362
|
-
'Can not generate data for {location} parameter "{name}"!
|
362
|
+
'Can not generate data for {location} parameter "{name}"! Its schema should be an object, got {schema}'
|
363
363
|
)
|
364
364
|
|
365
365
|
|
@@ -169,6 +169,7 @@ class APIStateMachine(RuleBasedStateMachine):
|
|
169
169
|
kwargs = self.get_call_kwargs(case)
|
170
170
|
start = time.monotonic()
|
171
171
|
response = self.call(case, **kwargs)
|
172
|
+
self._transport_kwargs = kwargs
|
172
173
|
elapsed = time.monotonic() - start
|
173
174
|
self.after_call(response, case)
|
174
175
|
self.validate_response(response, case, additional_checks=(use_after_free,))
|
@@ -297,7 +298,7 @@ class APIStateMachine(RuleBasedStateMachine):
|
|
297
298
|
all provided checks rather than only the first encountered exception.
|
298
299
|
"""
|
299
300
|
__tracebackhide__ = True
|
300
|
-
case.validate_response(response, additional_checks=additional_checks)
|
301
|
+
case.validate_response(response, additional_checks=additional_checks, transport_kwargs=self._transport_kwargs)
|
301
302
|
|
302
303
|
def store_result(self, response: GenericResponse, case: Case, elapsed: float) -> StepResult:
|
303
304
|
return StepResult(response, case, elapsed)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: schemathesis
|
3
|
-
Version: 3.39.
|
3
|
+
Version: 3.39.10
|
4
4
|
Summary: Property-based testing framework for Open API and GraphQL based apps
|
5
5
|
Project-URL: Documentation, https://schemathesis.readthedocs.io/en/stable/
|
6
6
|
Project-URL: Changelog, https://schemathesis.readthedocs.io/en/stable/changelog.html
|
@@ -1,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=x1AKO4Cr3LWZQPcRlIvor7ACn5t-mxLJfdHFgpdGHmI,28437
|
5
5
|
schemathesis/_lazy_import.py,sha256=aMhWYgbU2JOltyWBb32vnWBb6kykOghucEzI_F70yVE,470
|
6
6
|
schemathesis/_override.py,sha256=TAjYB3eJQmlw9K_xiR9ptt9Wj7if4U7UFlUhGjpBAoM,1625
|
7
7
|
schemathesis/_patches.py,sha256=Hsbpn4UVeXUQD2Kllrbq01CSWsTYENWa0VJTyhX5C2k,895
|
@@ -10,15 +10,15 @@ schemathesis/_xml.py,sha256=qc2LydEwIqcSfgqQOJqiYicivA4YFJGKgCBOem_JqNc,8560
|
|
10
10
|
schemathesis/auths.py,sha256=De97IS_iOlC36-jRhkZ2DUndjUpXYgsd8R-nA-iHn88,16837
|
11
11
|
schemathesis/checks.py,sha256=YPUI1N5giGBy1072vd77e6HWelGAKrJUmJLEG4oqfF8,2630
|
12
12
|
schemathesis/code_samples.py,sha256=rsdTo6ksyUs3ZMhqx0mmmkPSKUCFa--snIOYsXgZd80,4120
|
13
|
-
schemathesis/constants.py,sha256=
|
13
|
+
schemathesis/constants.py,sha256=RHwog2lAz84qG6KCpP1U15A4a9w1xcwbgZ97aY4juQg,2555
|
14
14
|
schemathesis/exceptions.py,sha256=5zjPlyVoQNJGbwufplL6ZVV7FEBPBNPHGdlQRJ7xnhE,20449
|
15
|
-
schemathesis/failures.py,sha256=
|
15
|
+
schemathesis/failures.py,sha256=mrDu7F-OrQY8pRMNVtIxTjovhfyIkcXYjnSkRw-OMuQ,8016
|
16
16
|
schemathesis/filters.py,sha256=f3c_yXIBwIin-9Y0qU2TkcC1NEM_Mw34jGUHQc0BOyw,17026
|
17
17
|
schemathesis/graphql.py,sha256=XiuKcfoOB92iLFC8zpz2msLkM0_V0TLdxPNBqrrGZ8w,216
|
18
18
|
schemathesis/hooks.py,sha256=p5AXgjVGtka0jn9MOeyBaRUtNbqZTs4iaJqytYTacHc,14856
|
19
19
|
schemathesis/lazy.py,sha256=Ddhkk7Tpc_VcRGYkCtKDmP2gpjxVmEZ3b01ZTNjbm8I,19004
|
20
20
|
schemathesis/loaders.py,sha256=MoEhcdOEBJxNRn5X-ZNhWB9jZDHQQNpkNfEdQjf_NDw,4590
|
21
|
-
schemathesis/models.py,sha256=
|
21
|
+
schemathesis/models.py,sha256=8V3ZDTq2l45iFB-kE_NWb64iTy-hkf-hXCI-bIp4oxw,50010
|
22
22
|
schemathesis/parameters.py,sha256=izlu4MFYT1RWrC4RBxrV6weeCal-ODbdLQLMb0PYCZY,2327
|
23
23
|
schemathesis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
24
|
schemathesis/sanitization.py,sha256=Lycn1VVfula9B6XpzkxTHja7CZ7RHqbUh9kBic0Yi4M,9056
|
@@ -41,7 +41,7 @@ schemathesis/cli/options.py,sha256=yL7nrzKkbGCc4nQya9wpTW48XGz_OT9hOFrzPxRrDe4,2
|
|
41
41
|
schemathesis/cli/reporting.py,sha256=KC3sxSc1u4aFQ-0Q8CQ3G4HTEl7QxlubGnJgNKmVJdQ,3627
|
42
42
|
schemathesis/cli/sanitization.py,sha256=Onw_NWZSom6XTVNJ5NHnC0PAhrYAcGzIXJbsBCzLkn4,1005
|
43
43
|
schemathesis/cli/output/__init__.py,sha256=AXaUzQ1nhQ-vXhW4-X-91vE2VQtEcCOrGtQXXNN55iQ,29
|
44
|
-
schemathesis/cli/output/default.py,sha256=
|
44
|
+
schemathesis/cli/output/default.py,sha256=kRJPcZ5RL9Nsy9k4bSZaRtAezzPiHE6hybdNZLfrEhs,39071
|
45
45
|
schemathesis/cli/output/short.py,sha256=CL6-Apxr5tuZ3BL1vecV1MiRY1wDt21g0wiUwZu6mLM,2607
|
46
46
|
schemathesis/contrib/__init__.py,sha256=FH8NL8NXgSKBFOF8Jy_EB6T4CJEaiM-tmDhz16B2o4k,187
|
47
47
|
schemathesis/contrib/unique_data.py,sha256=cTjJfoNpfLMobUzmGnm3k6kVrZcL34_FMPLlpDDsg4c,1249
|
@@ -61,12 +61,12 @@ schemathesis/fixups/utf8_bom.py,sha256=lWT9RNmJG8i-l5AXIpaCT3qCPUwRgzXPW3eoOjmZE
|
|
61
61
|
schemathesis/generation/__init__.py,sha256=29Zys_tD6kfngaC4zHeC6TOBZQcmo7CWm7KDSYsHStQ,1581
|
62
62
|
schemathesis/generation/_hypothesis.py,sha256=74fzLPHugZgMQXerWYFAMqCAjtAXz5E4gek7Gnkhli4,1756
|
63
63
|
schemathesis/generation/_methods.py,sha256=r8oVlJ71_gXcnEhU-byw2E0R2RswQQFm8U7yGErSqbw,1204
|
64
|
-
schemathesis/generation/coverage.py,sha256=
|
64
|
+
schemathesis/generation/coverage.py,sha256=1CilQSe2DIdMdeWA6RL22so2bZULPRwc0CQBRxcLRFs,39370
|
65
65
|
schemathesis/internal/__init__.py,sha256=93HcdG3LF0BbQKbCteOsFMa1w6nXl8yTmx87QLNJOik,161
|
66
66
|
schemathesis/internal/checks.py,sha256=YBhldvs-oQTrtvTlz3cjaO9Ri2oQeyobFcquO4Y0UJ8,2720
|
67
67
|
schemathesis/internal/copy.py,sha256=DcL56z-d69kKR_5u8mlHvjSL1UTyUKNMAwexrwHFY1s,1031
|
68
68
|
schemathesis/internal/datetime.py,sha256=zPLBL0XXLNfP-KYel3H2m8pnsxjsA_4d-zTOhJg2EPQ,136
|
69
|
-
schemathesis/internal/deprecation.py,sha256=
|
69
|
+
schemathesis/internal/deprecation.py,sha256=XnzwSegbbdQyoTF1OGW_s9pdjIfN_Uzzdb2rfah1w2o,1261
|
70
70
|
schemathesis/internal/diff.py,sha256=upGqM6s9WDT653wzxK_tqclFCxqkzB0j4wsO1foq5_k,466
|
71
71
|
schemathesis/internal/extensions.py,sha256=h0aHRK_PTKfiAufkeBziegQS8537TL-Gr1hPW48q8Yc,790
|
72
72
|
schemathesis/internal/jsonschema.py,sha256=-7tF15cXo1ZdhiRFYYfEClXihX2Svc5Loi_Dz1x201k,1157
|
@@ -79,8 +79,8 @@ schemathesis/runner/events.py,sha256=cRKKSDvHvKLBIyFBz-J0JtAKshbGGKco9eaMyLCgzsY
|
|
79
79
|
schemathesis/runner/probes.py,sha256=no5AfO3kse25qvHevjeUfB0Q3C860V2AYzschUW3QMQ,5688
|
80
80
|
schemathesis/runner/serialization.py,sha256=vZi1wd9HX9Swp9VJ_hZFeDgy3Y726URpHra-TbPvQhk,20762
|
81
81
|
schemathesis/runner/impl/__init__.py,sha256=1E2iME8uthYPBh9MjwVBCTFV-P3fi7AdphCCoBBspjs,199
|
82
|
-
schemathesis/runner/impl/context.py,sha256=
|
83
|
-
schemathesis/runner/impl/core.py,sha256=
|
82
|
+
schemathesis/runner/impl/context.py,sha256=KY06FXVOFQ6DBaa_FomSBXL81ULs3D21IW1u3yLqs1E,2434
|
83
|
+
schemathesis/runner/impl/core.py,sha256=vNWUu5F36brmhsv0P9UYavXLklQo5O2A6byQmS75enY,49289
|
84
84
|
schemathesis/runner/impl/solo.py,sha256=y5QSxgK8nBCEjZVD5BpFvYUXmB6tEjk6TwxAo__NejA,2911
|
85
85
|
schemathesis/runner/impl/threadpool.py,sha256=yNR5LYE8f3N_4t42OwSgy0_qdGgBPM7d11F9c9oEAAs,15075
|
86
86
|
schemathesis/service/__init__.py,sha256=cDVTCFD1G-vvhxZkJUwiToTAEQ-0ByIoqwXvJBCf_V8,472
|
@@ -107,16 +107,16 @@ schemathesis/specs/graphql/validation.py,sha256=uINIOt-2E7ZuQV2CxKzwez-7L9tDtqzM
|
|
107
107
|
schemathesis/specs/openapi/__init__.py,sha256=HDcx3bqpa6qWPpyMrxAbM3uTo0Lqpg-BUNZhDJSJKnw,279
|
108
108
|
schemathesis/specs/openapi/_cache.py,sha256=PAiAu4X_a2PQgD2lG5H3iisXdyg4SaHpU46bRZvfNkM,4320
|
109
109
|
schemathesis/specs/openapi/_hypothesis.py,sha256=nU8UDn1PzGCre4IVmwIuO9-CZv1KJe1fYY0d2BojhSo,22981
|
110
|
-
schemathesis/specs/openapi/checks.py,sha256=
|
110
|
+
schemathesis/specs/openapi/checks.py,sha256=NzUoZ0gZMjyC12KfV7J-5ww8IMaaaNiKum4y7bmA_EA,26816
|
111
111
|
schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
|
112
112
|
schemathesis/specs/openapi/converter.py,sha256=Yxw9lS_JKEyi-oJuACT07fm04bqQDlAu-iHwzkeDvE4,3546
|
113
113
|
schemathesis/specs/openapi/definitions.py,sha256=WTkWwCgTc3OMxfKsqh6YDoGfZMTThSYrHGp8h0vLAK0,93935
|
114
114
|
schemathesis/specs/openapi/examples.py,sha256=hdeq7et8AexYGY2iU6SfMZWJ7G0PbOfapUtc4upNs_4,20483
|
115
115
|
schemathesis/specs/openapi/formats.py,sha256=3KtEC-8nQRwMErS-WpMadXsr8R0O-NzYwFisZqMuc-8,2761
|
116
116
|
schemathesis/specs/openapi/links.py,sha256=C4Uir2P_EcpqME8ee_a1vdUM8Tm3ZcKNn2YsGjZiMUQ,17935
|
117
|
-
schemathesis/specs/openapi/loaders.py,sha256=
|
117
|
+
schemathesis/specs/openapi/loaders.py,sha256=jlTYLoG5sVRh8xycIF2M2VDCZ44M80Sct07a_ycg1Po,25698
|
118
118
|
schemathesis/specs/openapi/media_types.py,sha256=dNTxpRQbY3SubdVjh4Cjb38R6Bc9MF9BsRQwPD87x0g,1017
|
119
|
-
schemathesis/specs/openapi/parameters.py,sha256=
|
119
|
+
schemathesis/specs/openapi/parameters.py,sha256=X_3PKqUScIiN_vbSFEauPYyxASyFv-_9lZ_9QEZRLqo,14655
|
120
120
|
schemathesis/specs/openapi/patterns.py,sha256=OxZp31cBEHv8fwoeYJ9JcdWNHFMIGzRISNN3dCBc9Dg,11260
|
121
121
|
schemathesis/specs/openapi/references.py,sha256=euxM02kQGMHh4Ss1jWjOY_gyw_HazafKITIsvOEiAvI,9831
|
122
122
|
schemathesis/specs/openapi/schemas.py,sha256=JA9SiBnwYg75kYnd4_0CWOuQv_XTfYwuDeGmFe4RtVo,53724
|
@@ -144,7 +144,7 @@ schemathesis/stateful/context.py,sha256=lpCOVhJEbPOp8F_Z_YvU5ptVTgaKJsllvI1NK28D
|
|
144
144
|
schemathesis/stateful/events.py,sha256=CyYvyQebOaeTn6UevaB7HXOrUhxCWbqXMfQ7pZK7fV8,6727
|
145
145
|
schemathesis/stateful/runner.py,sha256=3tRRmWcXp5GCeRWGOtQ9-W0rxljoR06qSCKC4r7EQyY,12672
|
146
146
|
schemathesis/stateful/sink.py,sha256=bHYlgh-fMwg1Srxk_XGs0-WV34YccotwH9PGrxCK57A,2474
|
147
|
-
schemathesis/stateful/state_machine.py,sha256=
|
147
|
+
schemathesis/stateful/state_machine.py,sha256=EE1T0L21vBU0UHGiCmfPfIfnhU1WptB16h0t1iNVro0,13037
|
148
148
|
schemathesis/stateful/statistic.py,sha256=2-uU5xpT9CbMulKgJWLZN6MUpC0Fskf5yXTt4ef4NFA,542
|
149
149
|
schemathesis/stateful/validation.py,sha256=23qSZjC1_xRmtCX4OqsyG6pGxdlo6IZYid695ZpDQyU,3747
|
150
150
|
schemathesis/transports/__init__.py,sha256=k35qBp-657qnHE9FfCowqO3rqOgCwSUnrdl2vAV3hnQ,12951
|
@@ -153,8 +153,8 @@ schemathesis/transports/auth.py,sha256=urSTO9zgFO1qU69xvnKHPFQV0SlJL3d7_Ojl0tLnZ
|
|
153
153
|
schemathesis/transports/content_types.py,sha256=MiKOm-Hy5i75hrROPdpiBZPOTDzOwlCdnthJD12AJzI,2187
|
154
154
|
schemathesis/transports/headers.py,sha256=hr_AIDOfUxsJxpHfemIZ_uNG3_vzS_ZeMEKmZjbYiBE,990
|
155
155
|
schemathesis/transports/responses.py,sha256=OFD4ZLqwEFpo7F9vaP_SVgjhxAqatxIj38FS4XVq8Qs,1680
|
156
|
-
schemathesis-3.39.
|
157
|
-
schemathesis-3.39.
|
158
|
-
schemathesis-3.39.
|
159
|
-
schemathesis-3.39.
|
160
|
-
schemathesis-3.39.
|
156
|
+
schemathesis-3.39.10.dist-info/METADATA,sha256=3nOKrS8YGysaLXjvt4uIeysV4bbnLxm1WRoG_DD8W8s,11977
|
157
|
+
schemathesis-3.39.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
158
|
+
schemathesis-3.39.10.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
|
159
|
+
schemathesis-3.39.10.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
|
160
|
+
schemathesis-3.39.10.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|