schemathesis 3.25.5__py3-none-any.whl → 3.39.7__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/__init__.py +6 -6
- schemathesis/_compat.py +2 -2
- schemathesis/_dependency_versions.py +4 -2
- schemathesis/_hypothesis.py +369 -56
- schemathesis/_lazy_import.py +1 -0
- schemathesis/_override.py +5 -4
- schemathesis/_patches.py +21 -0
- schemathesis/_rate_limiter.py +7 -0
- schemathesis/_xml.py +75 -22
- schemathesis/auths.py +78 -16
- schemathesis/checks.py +21 -9
- schemathesis/cli/__init__.py +793 -448
- schemathesis/cli/__main__.py +4 -0
- schemathesis/cli/callbacks.py +58 -13
- schemathesis/cli/cassettes.py +233 -47
- schemathesis/cli/constants.py +8 -2
- schemathesis/cli/context.py +24 -4
- schemathesis/cli/debug.py +2 -1
- schemathesis/cli/handlers.py +4 -1
- schemathesis/cli/junitxml.py +103 -22
- schemathesis/cli/options.py +15 -4
- schemathesis/cli/output/default.py +286 -115
- schemathesis/cli/output/short.py +25 -6
- schemathesis/cli/reporting.py +79 -0
- schemathesis/cli/sanitization.py +6 -0
- schemathesis/code_samples.py +5 -3
- schemathesis/constants.py +1 -0
- schemathesis/contrib/openapi/__init__.py +1 -1
- schemathesis/contrib/openapi/fill_missing_examples.py +3 -1
- schemathesis/contrib/openapi/formats/uuid.py +2 -1
- schemathesis/contrib/unique_data.py +3 -3
- schemathesis/exceptions.py +76 -65
- schemathesis/experimental/__init__.py +35 -0
- schemathesis/extra/_aiohttp.py +1 -0
- schemathesis/extra/_flask.py +4 -1
- schemathesis/extra/_server.py +1 -0
- schemathesis/extra/pytest_plugin.py +17 -25
- schemathesis/failures.py +77 -9
- schemathesis/filters.py +185 -8
- schemathesis/fixups/__init__.py +1 -0
- schemathesis/fixups/fast_api.py +2 -2
- schemathesis/fixups/utf8_bom.py +1 -2
- schemathesis/generation/__init__.py +20 -36
- schemathesis/generation/_hypothesis.py +59 -0
- schemathesis/generation/_methods.py +44 -0
- schemathesis/generation/coverage.py +931 -0
- schemathesis/graphql.py +0 -1
- schemathesis/hooks.py +89 -12
- schemathesis/internal/checks.py +84 -0
- schemathesis/internal/copy.py +22 -3
- schemathesis/internal/deprecation.py +6 -2
- schemathesis/internal/diff.py +15 -0
- schemathesis/internal/extensions.py +27 -0
- schemathesis/internal/jsonschema.py +2 -1
- schemathesis/internal/output.py +68 -0
- schemathesis/internal/result.py +1 -1
- schemathesis/internal/transformation.py +11 -0
- schemathesis/lazy.py +138 -25
- schemathesis/loaders.py +7 -5
- schemathesis/models.py +323 -213
- schemathesis/parameters.py +4 -0
- schemathesis/runner/__init__.py +72 -22
- schemathesis/runner/events.py +86 -6
- schemathesis/runner/impl/context.py +104 -0
- schemathesis/runner/impl/core.py +447 -187
- schemathesis/runner/impl/solo.py +19 -29
- schemathesis/runner/impl/threadpool.py +70 -79
- schemathesis/{cli → runner}/probes.py +37 -25
- schemathesis/runner/serialization.py +150 -17
- schemathesis/sanitization.py +5 -1
- schemathesis/schemas.py +170 -102
- schemathesis/serializers.py +17 -4
- schemathesis/service/ci.py +1 -0
- schemathesis/service/client.py +39 -6
- schemathesis/service/events.py +5 -1
- schemathesis/service/extensions.py +224 -0
- schemathesis/service/hosts.py +6 -2
- schemathesis/service/metadata.py +25 -0
- schemathesis/service/models.py +211 -2
- schemathesis/service/report.py +6 -6
- schemathesis/service/serialization.py +60 -71
- schemathesis/service/usage.py +1 -0
- schemathesis/specs/graphql/_cache.py +26 -0
- schemathesis/specs/graphql/loaders.py +25 -5
- schemathesis/specs/graphql/nodes.py +1 -0
- schemathesis/specs/graphql/scalars.py +2 -2
- schemathesis/specs/graphql/schemas.py +130 -100
- schemathesis/specs/graphql/validation.py +1 -2
- schemathesis/specs/openapi/__init__.py +1 -0
- schemathesis/specs/openapi/_cache.py +123 -0
- schemathesis/specs/openapi/_hypothesis.py +79 -61
- schemathesis/specs/openapi/checks.py +504 -25
- schemathesis/specs/openapi/converter.py +31 -4
- schemathesis/specs/openapi/definitions.py +10 -17
- schemathesis/specs/openapi/examples.py +143 -31
- schemathesis/specs/openapi/expressions/__init__.py +37 -2
- schemathesis/specs/openapi/expressions/context.py +1 -1
- schemathesis/specs/openapi/expressions/extractors.py +26 -0
- schemathesis/specs/openapi/expressions/lexer.py +20 -18
- schemathesis/specs/openapi/expressions/nodes.py +29 -6
- schemathesis/specs/openapi/expressions/parser.py +26 -5
- schemathesis/specs/openapi/formats.py +44 -0
- schemathesis/specs/openapi/links.py +125 -42
- schemathesis/specs/openapi/loaders.py +77 -36
- schemathesis/specs/openapi/media_types.py +34 -0
- schemathesis/specs/openapi/negative/__init__.py +6 -3
- schemathesis/specs/openapi/negative/mutations.py +21 -6
- schemathesis/specs/openapi/parameters.py +39 -25
- schemathesis/specs/openapi/patterns.py +137 -0
- schemathesis/specs/openapi/references.py +37 -7
- schemathesis/specs/openapi/schemas.py +368 -242
- schemathesis/specs/openapi/security.py +25 -7
- schemathesis/specs/openapi/serialization.py +1 -0
- schemathesis/specs/openapi/stateful/__init__.py +198 -70
- schemathesis/specs/openapi/stateful/statistic.py +198 -0
- schemathesis/specs/openapi/stateful/types.py +14 -0
- schemathesis/specs/openapi/utils.py +6 -1
- schemathesis/specs/openapi/validation.py +1 -0
- schemathesis/stateful/__init__.py +35 -21
- schemathesis/stateful/config.py +97 -0
- schemathesis/stateful/context.py +135 -0
- schemathesis/stateful/events.py +274 -0
- schemathesis/stateful/runner.py +309 -0
- schemathesis/stateful/sink.py +68 -0
- schemathesis/stateful/state_machine.py +67 -38
- schemathesis/stateful/statistic.py +22 -0
- schemathesis/stateful/validation.py +100 -0
- schemathesis/targets.py +33 -1
- schemathesis/throttling.py +25 -5
- schemathesis/transports/__init__.py +354 -0
- schemathesis/transports/asgi.py +7 -0
- schemathesis/transports/auth.py +25 -2
- schemathesis/transports/content_types.py +3 -1
- schemathesis/transports/headers.py +2 -1
- schemathesis/transports/responses.py +9 -4
- schemathesis/types.py +9 -0
- schemathesis/utils.py +11 -16
- schemathesis-3.39.7.dist-info/METADATA +293 -0
- schemathesis-3.39.7.dist-info/RECORD +160 -0
- {schemathesis-3.25.5.dist-info → schemathesis-3.39.7.dist-info}/WHEEL +1 -1
- schemathesis/specs/openapi/filters.py +0 -49
- schemathesis/specs/openapi/stateful/links.py +0 -92
- schemathesis-3.25.5.dist-info/METADATA +0 -356
- schemathesis-3.25.5.dist-info/RECORD +0 -134
- {schemathesis-3.25.5.dist-info → schemathesis-3.39.7.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.25.5.dist-info → schemathesis-3.39.7.dist-info}/licenses/LICENSE +0 -0
schemathesis/cli/junitxml.py
CHANGED
|
@@ -1,43 +1,124 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import platform
|
|
4
|
+
import textwrap
|
|
3
5
|
from dataclasses import dataclass, field
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
6
|
+
from typing import TYPE_CHECKING, cast
|
|
5
7
|
|
|
6
8
|
from junit_xml import TestCase, TestSuite, to_xml_report_file
|
|
7
9
|
|
|
10
|
+
from ..exceptions import RuntimeErrorType
|
|
11
|
+
from ..internal.output import prepare_response_payload
|
|
8
12
|
from ..models import Status
|
|
9
13
|
from ..runner import events
|
|
10
|
-
from ..runner.serialization import deduplicate_failures
|
|
11
14
|
from .handlers import EventHandler
|
|
12
|
-
|
|
15
|
+
from .reporting import TEST_CASE_ID_TITLE, get_runtime_error_suggestion, group_by_case, split_traceback
|
|
13
16
|
|
|
14
17
|
if TYPE_CHECKING:
|
|
15
18
|
from click.utils import LazyFile
|
|
19
|
+
|
|
20
|
+
from ..runner.serialization import SerializedCheck, SerializedError
|
|
16
21
|
from .context import ExecutionContext
|
|
17
22
|
|
|
18
23
|
|
|
19
24
|
@dataclass
|
|
20
25
|
class JunitXMLHandler(EventHandler):
|
|
21
26
|
file_handle: LazyFile
|
|
22
|
-
test_cases:
|
|
27
|
+
test_cases: dict = field(default_factory=dict)
|
|
23
28
|
|
|
24
29
|
def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
|
|
25
|
-
if isinstance(event, events.AfterExecution):
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if
|
|
37
|
-
test_case.
|
|
38
|
-
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
if isinstance(event, (events.AfterExecution, events.AfterStatefulExecution)):
|
|
31
|
+
event_: events.AfterExecution | events.AfterStatefulExecution = event
|
|
32
|
+
if isinstance(event_, events.AfterExecution):
|
|
33
|
+
name = f"{event_.result.method} {event_.result.path}"
|
|
34
|
+
else:
|
|
35
|
+
name = event_.result.verbose_name
|
|
36
|
+
if name in self.test_cases:
|
|
37
|
+
test_case = self.test_cases[name]
|
|
38
|
+
test_case.elapsed_sec += event_.elapsed_time
|
|
39
|
+
else:
|
|
40
|
+
test_case = TestCase(name, elapsed_sec=event_.elapsed_time, allow_multiple_subelements=True)
|
|
41
|
+
if event_.status == Status.failure:
|
|
42
|
+
_add_failure(test_case, event_.result.checks, context)
|
|
43
|
+
elif event_.status == Status.error:
|
|
44
|
+
test_case.add_error_info(output=build_error_message(context, event_.result.errors[-1]))
|
|
45
|
+
elif event_.status == Status.skip:
|
|
46
|
+
test_case.add_skipped_info(output=event_.result.skip_reason)
|
|
47
|
+
self.test_cases[name] = test_case
|
|
48
|
+
elif isinstance(event, events.Finished):
|
|
49
|
+
test_suites = [
|
|
50
|
+
TestSuite("schemathesis", test_cases=list(self.test_cases.values()), hostname=platform.node())
|
|
51
|
+
]
|
|
43
52
|
to_xml_report_file(file_descriptor=self.file_handle, test_suites=test_suites, prettyprint=True)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _add_failure(test_case: TestCase, checks: list[SerializedCheck], context: ExecutionContext) -> None:
|
|
56
|
+
messages = []
|
|
57
|
+
for idx, (code_sample, group) in enumerate(group_by_case(checks, context.code_sample_style), 1):
|
|
58
|
+
checks = sorted(group, key=lambda c: c.name != "not_a_server_error")
|
|
59
|
+
messages.append(build_failure_message(context, idx, code_sample, checks))
|
|
60
|
+
test_case.add_failure_info(message="\n\n".join(messages))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def build_failure_message(context: ExecutionContext, idx: int, code_sample: str, checks: list[SerializedCheck]) -> str:
|
|
64
|
+
from ..transports.responses import get_reason
|
|
65
|
+
|
|
66
|
+
message = ""
|
|
67
|
+
for check_idx, check in enumerate(checks):
|
|
68
|
+
if check_idx == 0:
|
|
69
|
+
message += f"{idx}. {TEST_CASE_ID_TITLE}: {check.example.id}\n"
|
|
70
|
+
message += f"\n- {check.title}\n"
|
|
71
|
+
formatted_message = check.formatted_message
|
|
72
|
+
if formatted_message:
|
|
73
|
+
message += f"\n{formatted_message}\n"
|
|
74
|
+
if check_idx + 1 == len(checks):
|
|
75
|
+
if check.response is not None:
|
|
76
|
+
status_code = check.response.status_code
|
|
77
|
+
reason = get_reason(status_code)
|
|
78
|
+
message += f"\n[{check.response.status_code}] {reason}:\n"
|
|
79
|
+
if check.response.body is not None:
|
|
80
|
+
if not check.response.body:
|
|
81
|
+
message += "\n <EMPTY>\n"
|
|
82
|
+
else:
|
|
83
|
+
encoding = check.response.encoding or "utf8"
|
|
84
|
+
try:
|
|
85
|
+
# Checked that is not None
|
|
86
|
+
body = cast(bytes, check.response.deserialize_body())
|
|
87
|
+
payload = body.decode(encoding)
|
|
88
|
+
payload = prepare_response_payload(payload, config=context.output_config)
|
|
89
|
+
payload = textwrap.indent(f"\n`{payload}`\n", prefix=" ")
|
|
90
|
+
message += payload
|
|
91
|
+
except UnicodeDecodeError:
|
|
92
|
+
message += "\n <BINARY>\n"
|
|
93
|
+
|
|
94
|
+
message += f"\nReproduce with: \n\n {code_sample}"
|
|
95
|
+
return message
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def build_error_message(context: ExecutionContext, error: SerializedError) -> str:
|
|
99
|
+
message = ""
|
|
100
|
+
if error.title:
|
|
101
|
+
if error.type == RuntimeErrorType.SCHEMA_GENERIC:
|
|
102
|
+
message = "Schema Error\n"
|
|
103
|
+
else:
|
|
104
|
+
message = f"{error.title}\n"
|
|
105
|
+
if error.message:
|
|
106
|
+
message += f"\n{error.message}\n"
|
|
107
|
+
elif error.message:
|
|
108
|
+
message = error.message
|
|
109
|
+
else:
|
|
110
|
+
message = error.exception
|
|
111
|
+
if error.extras:
|
|
112
|
+
extras = error.extras
|
|
113
|
+
elif context.show_trace and error.type.has_useful_traceback:
|
|
114
|
+
extras = split_traceback(error.exception_with_traceback)
|
|
115
|
+
else:
|
|
116
|
+
extras = []
|
|
117
|
+
if extras:
|
|
118
|
+
message += "\n"
|
|
119
|
+
for extra in extras:
|
|
120
|
+
message += f" {extra}\n"
|
|
121
|
+
suggestion = get_runtime_error_suggestion(error.type, bold=str)
|
|
122
|
+
if suggestion is not None:
|
|
123
|
+
message += f"\nTip: {suggestion}"
|
|
124
|
+
return message
|
schemathesis/cli/options.py
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Any, NoReturn
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, NoReturn
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
|
|
7
7
|
from ..constants import NOT_SET
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from enum import Enum
|
|
11
|
+
|
|
12
|
+
from ..types import NotSet
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
class CustomHelpMessageChoice(click.Choice):
|
|
@@ -54,6 +58,13 @@ class CsvChoice(BaseCsvChoice):
|
|
|
54
58
|
self.fail_on_invalid_options(invalid_options, selected)
|
|
55
59
|
|
|
56
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
|
+
|
|
57
68
|
class OptionalInt(click.types.IntRange):
|
|
58
69
|
def convert( # type: ignore
|
|
59
70
|
self, value: str, param: click.core.Parameter | None, ctx: click.core.Context | None
|
|
@@ -64,4 +75,4 @@ class OptionalInt(click.types.IntRange):
|
|
|
64
75
|
int(value)
|
|
65
76
|
return super().convert(value, param, ctx)
|
|
66
77
|
except ValueError:
|
|
67
|
-
self.fail("
|
|
78
|
+
self.fail(f"{value} is not a valid integer or None.", param, ctx)
|