schemathesis 3.39.7__py3-none-any.whl → 4.0.0a2__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 +27 -65
- schemathesis/auths.py +26 -68
- schemathesis/checks.py +130 -60
- schemathesis/cli/__init__.py +5 -2105
- schemathesis/cli/commands/__init__.py +37 -0
- schemathesis/cli/commands/run/__init__.py +662 -0
- schemathesis/cli/commands/run/checks.py +80 -0
- schemathesis/cli/commands/run/context.py +117 -0
- schemathesis/cli/commands/run/events.py +30 -0
- schemathesis/cli/commands/run/executor.py +141 -0
- schemathesis/cli/commands/run/filters.py +202 -0
- schemathesis/cli/commands/run/handlers/__init__.py +46 -0
- schemathesis/cli/commands/run/handlers/base.py +18 -0
- schemathesis/cli/{cassettes.py → commands/run/handlers/cassettes.py} +178 -247
- schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
- schemathesis/cli/commands/run/handlers/output.py +1368 -0
- schemathesis/cli/commands/run/hypothesis.py +105 -0
- schemathesis/cli/commands/run/loaders.py +129 -0
- schemathesis/cli/{callbacks.py → commands/run/validation.py} +59 -175
- schemathesis/cli/constants.py +5 -58
- schemathesis/cli/core.py +17 -0
- schemathesis/cli/ext/fs.py +14 -0
- schemathesis/cli/ext/groups.py +55 -0
- schemathesis/cli/{options.py → ext/options.py} +37 -16
- schemathesis/cli/hooks.py +36 -0
- schemathesis/contrib/__init__.py +1 -3
- schemathesis/contrib/openapi/__init__.py +1 -3
- schemathesis/contrib/openapi/fill_missing_examples.py +3 -7
- schemathesis/core/__init__.py +58 -0
- schemathesis/core/compat.py +25 -0
- schemathesis/core/control.py +2 -0
- schemathesis/core/curl.py +58 -0
- schemathesis/core/deserialization.py +65 -0
- schemathesis/core/errors.py +370 -0
- schemathesis/core/failures.py +315 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/core/loaders.py +104 -0
- schemathesis/core/marks.py +66 -0
- schemathesis/{transports/content_types.py → core/media_types.py} +14 -12
- schemathesis/{internal/output.py → core/output/__init__.py} +1 -0
- schemathesis/core/output/sanitization.py +197 -0
- schemathesis/{throttling.py → core/rate_limit.py} +16 -17
- schemathesis/core/registries.py +31 -0
- schemathesis/core/transforms.py +113 -0
- schemathesis/core/transport.py +108 -0
- schemathesis/core/validation.py +38 -0
- schemathesis/core/version.py +7 -0
- schemathesis/engine/__init__.py +30 -0
- schemathesis/engine/config.py +59 -0
- schemathesis/engine/context.py +119 -0
- schemathesis/engine/control.py +36 -0
- schemathesis/engine/core.py +157 -0
- schemathesis/engine/errors.py +394 -0
- schemathesis/engine/events.py +243 -0
- schemathesis/engine/phases/__init__.py +66 -0
- schemathesis/{runner → engine/phases}/probes.py +49 -68
- schemathesis/engine/phases/stateful/__init__.py +66 -0
- schemathesis/engine/phases/stateful/_executor.py +301 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +175 -0
- schemathesis/engine/phases/unit/_executor.py +322 -0
- schemathesis/engine/phases/unit/_pool.py +74 -0
- schemathesis/engine/recorder.py +246 -0
- schemathesis/errors.py +31 -0
- schemathesis/experimental/__init__.py +9 -40
- schemathesis/filters.py +7 -95
- schemathesis/generation/__init__.py +3 -3
- schemathesis/generation/case.py +190 -0
- schemathesis/generation/coverage.py +22 -22
- schemathesis/{_patches.py → generation/hypothesis/__init__.py} +15 -6
- schemathesis/generation/hypothesis/builder.py +585 -0
- schemathesis/generation/{_hypothesis.py → hypothesis/examples.py} +2 -11
- schemathesis/generation/hypothesis/given.py +66 -0
- schemathesis/generation/hypothesis/reporting.py +14 -0
- schemathesis/generation/hypothesis/strategies.py +16 -0
- schemathesis/generation/meta.py +115 -0
- schemathesis/generation/modes.py +28 -0
- schemathesis/generation/overrides.py +96 -0
- schemathesis/generation/stateful/__init__.py +20 -0
- schemathesis/{stateful → generation/stateful}/state_machine.py +84 -109
- schemathesis/generation/targets.py +69 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +109 -0
- schemathesis/graphql/loaders.py +131 -0
- schemathesis/hooks.py +17 -62
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +387 -0
- schemathesis/openapi/generation/__init__.py +0 -0
- schemathesis/openapi/generation/filters.py +63 -0
- schemathesis/openapi/loaders.py +178 -0
- schemathesis/pytest/__init__.py +5 -0
- schemathesis/pytest/control_flow.py +7 -0
- schemathesis/pytest/lazy.py +273 -0
- schemathesis/pytest/loaders.py +12 -0
- schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +94 -107
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +456 -228
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/_cache.py +1 -2
- schemathesis/specs/graphql/scalars.py +5 -3
- schemathesis/specs/graphql/schemas.py +122 -123
- schemathesis/specs/graphql/validation.py +11 -17
- schemathesis/specs/openapi/__init__.py +6 -1
- schemathesis/specs/openapi/_cache.py +1 -2
- schemathesis/specs/openapi/_hypothesis.py +97 -134
- schemathesis/specs/openapi/checks.py +238 -219
- schemathesis/specs/openapi/converter.py +4 -4
- schemathesis/specs/openapi/definitions.py +1 -1
- schemathesis/specs/openapi/examples.py +22 -20
- schemathesis/specs/openapi/expressions/__init__.py +11 -15
- schemathesis/specs/openapi/expressions/extractors.py +1 -4
- schemathesis/specs/openapi/expressions/nodes.py +33 -32
- schemathesis/specs/openapi/formats.py +3 -2
- schemathesis/specs/openapi/links.py +123 -299
- schemathesis/specs/openapi/media_types.py +10 -12
- schemathesis/specs/openapi/negative/__init__.py +2 -1
- schemathesis/specs/openapi/negative/mutations.py +3 -2
- schemathesis/specs/openapi/parameters.py +8 -6
- schemathesis/specs/openapi/patterns.py +1 -1
- schemathesis/specs/openapi/references.py +11 -51
- schemathesis/specs/openapi/schemas.py +177 -191
- schemathesis/specs/openapi/security.py +1 -1
- schemathesis/specs/openapi/serialization.py +10 -6
- schemathesis/specs/openapi/stateful/__init__.py +97 -91
- schemathesis/transport/__init__.py +104 -0
- schemathesis/transport/asgi.py +26 -0
- schemathesis/transport/prepare.py +99 -0
- schemathesis/transport/requests.py +221 -0
- schemathesis/{_xml.py → transport/serialization.py} +69 -7
- schemathesis/transport/wsgi.py +165 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/METADATA +18 -14
- schemathesis-4.0.0a2.dist-info/RECORD +151 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/entry_points.txt +1 -1
- schemathesis/_compat.py +0 -74
- schemathesis/_dependency_versions.py +0 -19
- schemathesis/_hypothesis.py +0 -559
- schemathesis/_override.py +0 -50
- schemathesis/_rate_limiter.py +0 -7
- schemathesis/cli/context.py +0 -75
- schemathesis/cli/debug.py +0 -27
- schemathesis/cli/handlers.py +0 -19
- schemathesis/cli/junitxml.py +0 -124
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -936
- schemathesis/cli/output/short.py +0 -59
- schemathesis/cli/reporting.py +0 -79
- schemathesis/cli/sanitization.py +0 -26
- schemathesis/code_samples.py +0 -151
- schemathesis/constants.py +0 -56
- schemathesis/contrib/openapi/formats/__init__.py +0 -9
- schemathesis/contrib/openapi/formats/uuid.py +0 -16
- schemathesis/contrib/unique_data.py +0 -41
- schemathesis/exceptions.py +0 -571
- schemathesis/extra/_aiohttp.py +0 -28
- schemathesis/extra/_flask.py +0 -13
- schemathesis/extra/_server.py +0 -18
- schemathesis/failures.py +0 -277
- schemathesis/fixups/__init__.py +0 -37
- schemathesis/fixups/fast_api.py +0 -41
- schemathesis/fixups/utf8_bom.py +0 -28
- schemathesis/generation/_methods.py +0 -44
- schemathesis/graphql.py +0 -3
- schemathesis/internal/__init__.py +0 -7
- schemathesis/internal/checks.py +0 -84
- schemathesis/internal/copy.py +0 -32
- schemathesis/internal/datetime.py +0 -5
- schemathesis/internal/deprecation.py +0 -38
- schemathesis/internal/diff.py +0 -15
- schemathesis/internal/extensions.py +0 -27
- schemathesis/internal/jsonschema.py +0 -36
- schemathesis/internal/transformation.py +0 -26
- schemathesis/internal/validation.py +0 -34
- schemathesis/lazy.py +0 -474
- schemathesis/loaders.py +0 -122
- schemathesis/models.py +0 -1341
- schemathesis/parameters.py +0 -90
- schemathesis/runner/__init__.py +0 -605
- schemathesis/runner/events.py +0 -389
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/context.py +0 -104
- schemathesis/runner/impl/core.py +0 -1246
- schemathesis/runner/impl/solo.py +0 -80
- schemathesis/runner/impl/threadpool.py +0 -391
- schemathesis/runner/serialization.py +0 -544
- schemathesis/sanitization.py +0 -252
- schemathesis/serializers.py +0 -328
- schemathesis/service/__init__.py +0 -18
- schemathesis/service/auth.py +0 -11
- schemathesis/service/ci.py +0 -202
- schemathesis/service/client.py +0 -133
- schemathesis/service/constants.py +0 -38
- schemathesis/service/events.py +0 -61
- schemathesis/service/extensions.py +0 -224
- schemathesis/service/hosts.py +0 -111
- schemathesis/service/metadata.py +0 -71
- schemathesis/service/models.py +0 -258
- schemathesis/service/report.py +0 -255
- schemathesis/service/serialization.py +0 -173
- schemathesis/service/usage.py +0 -66
- schemathesis/specs/graphql/loaders.py +0 -364
- schemathesis/specs/openapi/expressions/context.py +0 -16
- schemathesis/specs/openapi/loaders.py +0 -708
- schemathesis/specs/openapi/stateful/statistic.py +0 -198
- schemathesis/specs/openapi/stateful/types.py +0 -14
- schemathesis/specs/openapi/validation.py +0 -26
- schemathesis/stateful/__init__.py +0 -147
- schemathesis/stateful/config.py +0 -97
- schemathesis/stateful/context.py +0 -135
- schemathesis/stateful/events.py +0 -274
- schemathesis/stateful/runner.py +0 -309
- schemathesis/stateful/sink.py +0 -68
- schemathesis/stateful/statistic.py +0 -22
- schemathesis/stateful/validation.py +0 -100
- schemathesis/targets.py +0 -77
- schemathesis/transports/__init__.py +0 -359
- schemathesis/transports/asgi.py +0 -7
- schemathesis/transports/auth.py +0 -38
- schemathesis/transports/headers.py +0 -36
- schemathesis/transports/responses.py +0 -57
- schemathesis/types.py +0 -44
- schemathesis/utils.py +0 -164
- schemathesis-3.39.7.dist-info/RECORD +0 -160
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
- /schemathesis/{_lazy_import.py → core/lazy_import.py} +0 -0
- /schemathesis/{internal → core}/result.py +0 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/WHEEL +0 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/licenses/LICENSE +0 -0
schemathesis/cli/output/short.py
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
import click
|
2
|
-
|
3
|
-
from ...runner import events
|
4
|
-
from ...stateful import events as stateful_events
|
5
|
-
from ..context import ExecutionContext
|
6
|
-
from ..handlers import EventHandler
|
7
|
-
from . import default
|
8
|
-
|
9
|
-
|
10
|
-
def handle_before_execution(context: ExecutionContext, event: events.BeforeExecution) -> None:
|
11
|
-
if event.recursion_level > 0:
|
12
|
-
context.operations_count += 1 # type: ignore
|
13
|
-
|
14
|
-
|
15
|
-
def handle_after_execution(context: ExecutionContext, event: events.AfterExecution) -> None:
|
16
|
-
context.operations_processed += 1
|
17
|
-
context.results.append(event.result)
|
18
|
-
context.hypothesis_output.extend(event.hypothesis_output)
|
19
|
-
default.display_execution_result(context, event.status.value)
|
20
|
-
|
21
|
-
|
22
|
-
def handle_stateful_event(context: ExecutionContext, event: events.StatefulEvent) -> None:
|
23
|
-
if isinstance(event.data, stateful_events.RunStarted):
|
24
|
-
click.echo()
|
25
|
-
default.handle_stateful_event(context, event)
|
26
|
-
|
27
|
-
|
28
|
-
class ShortOutputStyleHandler(EventHandler):
|
29
|
-
def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
|
30
|
-
"""Short output style shows single symbols in the progress bar.
|
31
|
-
|
32
|
-
Otherwise, identical to the default output style.
|
33
|
-
"""
|
34
|
-
if isinstance(event, events.Initialized):
|
35
|
-
default.handle_initialized(context, event)
|
36
|
-
elif isinstance(event, events.BeforeProbing):
|
37
|
-
default.handle_before_probing(context, event)
|
38
|
-
elif isinstance(event, events.AfterProbing):
|
39
|
-
default.handle_after_probing(context, event)
|
40
|
-
elif isinstance(event, events.BeforeAnalysis):
|
41
|
-
default.handle_before_analysis(context, event)
|
42
|
-
elif isinstance(event, events.AfterAnalysis):
|
43
|
-
default.handle_after_analysis(context, event)
|
44
|
-
elif isinstance(event, events.BeforeExecution):
|
45
|
-
handle_before_execution(context, event)
|
46
|
-
elif isinstance(event, events.AfterExecution):
|
47
|
-
handle_after_execution(context, event)
|
48
|
-
elif isinstance(event, events.Finished):
|
49
|
-
if context.operations_count == context.operations_processed:
|
50
|
-
click.echo()
|
51
|
-
default.handle_finished(context, event)
|
52
|
-
elif isinstance(event, events.Interrupted):
|
53
|
-
default.handle_interrupted(context, event)
|
54
|
-
elif isinstance(event, events.InternalError):
|
55
|
-
default.handle_internal_error(context, event)
|
56
|
-
elif isinstance(event, events.StatefulEvent):
|
57
|
-
handle_stateful_event(context, event)
|
58
|
-
elif isinstance(event, events.AfterStatefulExecution):
|
59
|
-
default.handle_after_stateful_execution(context, event)
|
schemathesis/cli/reporting.py
DELETED
@@ -1,79 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from itertools import groupby
|
4
|
-
from typing import TYPE_CHECKING, Callable, Generator, Iterator
|
5
|
-
|
6
|
-
import click
|
7
|
-
|
8
|
-
from ..exceptions import RuntimeErrorType
|
9
|
-
from ..runner.serialization import SerializedCheck, deduplicate_failures
|
10
|
-
|
11
|
-
if TYPE_CHECKING:
|
12
|
-
from ..code_samples import CodeSampleStyle
|
13
|
-
|
14
|
-
TEST_CASE_ID_TITLE = "Test Case ID"
|
15
|
-
|
16
|
-
|
17
|
-
def group_by_case(
|
18
|
-
checks: list[SerializedCheck], code_sample_style: CodeSampleStyle
|
19
|
-
) -> Generator[tuple[str, Iterator[SerializedCheck]], None, None]:
|
20
|
-
checks = deduplicate_failures(checks)
|
21
|
-
checks = sorted(checks, key=lambda c: _by_unique_key(c, code_sample_style))
|
22
|
-
for (sample, _, _), gen in groupby(checks, lambda c: _by_unique_key(c, code_sample_style)):
|
23
|
-
yield (sample, gen)
|
24
|
-
|
25
|
-
|
26
|
-
def _by_unique_key(check: SerializedCheck, code_sample_style: CodeSampleStyle) -> tuple[str, int, str]:
|
27
|
-
return (
|
28
|
-
code_sample_style.generate(
|
29
|
-
method=check.example.method,
|
30
|
-
url=check.example.url,
|
31
|
-
body=check.example.deserialize_body(),
|
32
|
-
headers=check.example.headers,
|
33
|
-
verify=check.example.verify,
|
34
|
-
extra_headers=check.example.extra_headers,
|
35
|
-
),
|
36
|
-
0 if not check.response else check.response.status_code,
|
37
|
-
"SCHEMATHESIS-INTERNAL-NO-RESPONSE"
|
38
|
-
if not check.response
|
39
|
-
else check.response.body or "SCHEMATHESIS-INTERNAL-EMPTY-BODY",
|
40
|
-
)
|
41
|
-
|
42
|
-
|
43
|
-
def split_traceback(traceback: str) -> list[str]:
|
44
|
-
return [entry for entry in traceback.splitlines() if entry]
|
45
|
-
|
46
|
-
|
47
|
-
def bold(option: str) -> str:
|
48
|
-
return click.style(option, bold=True)
|
49
|
-
|
50
|
-
|
51
|
-
def get_runtime_error_suggestion(error_type: RuntimeErrorType, bold: Callable[[str], str] = bold) -> str | None:
|
52
|
-
DISABLE_SSL_SUGGESTION = f"Bypass SSL verification with {bold('`--request-tls-verify=false`')}."
|
53
|
-
DISABLE_SCHEMA_VALIDATION_SUGGESTION = (
|
54
|
-
f"Bypass validation using {bold('`--validate-schema=false`')}. Caution: May cause unexpected errors."
|
55
|
-
)
|
56
|
-
|
57
|
-
def _format_health_check_suggestion(label: str) -> str:
|
58
|
-
return f"Bypass this health check using {bold(f'`--hypothesis-suppress-health-check={label}`')}."
|
59
|
-
|
60
|
-
RUNTIME_ERROR_SUGGESTIONS = {
|
61
|
-
RuntimeErrorType.CONNECTION_SSL: DISABLE_SSL_SUGGESTION,
|
62
|
-
RuntimeErrorType.HYPOTHESIS_DEADLINE_EXCEEDED: (
|
63
|
-
f"Adjust the deadline using {bold('`--hypothesis-deadline=MILLIS`')} or "
|
64
|
-
f"disable with {bold('`--hypothesis-deadline=None`')}."
|
65
|
-
),
|
66
|
-
RuntimeErrorType.HYPOTHESIS_UNSATISFIABLE: "Examine the schema for inconsistencies and consider simplifying it.",
|
67
|
-
RuntimeErrorType.SCHEMA_BODY_IN_GET_REQUEST: DISABLE_SCHEMA_VALIDATION_SUGGESTION,
|
68
|
-
RuntimeErrorType.SCHEMA_INVALID_REGULAR_EXPRESSION: "Ensure your regex is compatible with Python's syntax.\n"
|
69
|
-
"For guidance, visit: https://docs.python.org/3/library/re.html",
|
70
|
-
RuntimeErrorType.HYPOTHESIS_UNSUPPORTED_GRAPHQL_SCALAR: "Define a custom strategy for it.\n"
|
71
|
-
"For guidance, visit: https://schemathesis.readthedocs.io/en/stable/graphql.html#custom-scalars",
|
72
|
-
RuntimeErrorType.HYPOTHESIS_HEALTH_CHECK_DATA_TOO_LARGE: _format_health_check_suggestion("data_too_large"),
|
73
|
-
RuntimeErrorType.HYPOTHESIS_HEALTH_CHECK_FILTER_TOO_MUCH: _format_health_check_suggestion("filter_too_much"),
|
74
|
-
RuntimeErrorType.HYPOTHESIS_HEALTH_CHECK_TOO_SLOW: _format_health_check_suggestion("too_slow"),
|
75
|
-
RuntimeErrorType.HYPOTHESIS_HEALTH_CHECK_LARGE_BASE_EXAMPLE: _format_health_check_suggestion(
|
76
|
-
"large_base_example"
|
77
|
-
),
|
78
|
-
}
|
79
|
-
return RUNTIME_ERROR_SUGGESTIONS.get(error_type)
|
schemathesis/cli/sanitization.py
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from dataclasses import dataclass
|
4
|
-
from typing import TYPE_CHECKING
|
5
|
-
|
6
|
-
from ..runner import events
|
7
|
-
from ..sanitization import sanitize_serialized_check, sanitize_serialized_interaction
|
8
|
-
from .handlers import EventHandler
|
9
|
-
|
10
|
-
if TYPE_CHECKING:
|
11
|
-
from .context import ExecutionContext
|
12
|
-
|
13
|
-
|
14
|
-
@dataclass
|
15
|
-
class SanitizationHandler(EventHandler):
|
16
|
-
def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
|
17
|
-
if isinstance(event, events.AfterExecution):
|
18
|
-
for check in event.result.checks:
|
19
|
-
sanitize_serialized_check(check)
|
20
|
-
for interaction in event.result.interactions:
|
21
|
-
sanitize_serialized_interaction(interaction)
|
22
|
-
elif isinstance(event, events.AfterStatefulExecution):
|
23
|
-
for check in event.result.checks:
|
24
|
-
sanitize_serialized_check(check)
|
25
|
-
for interaction in event.result.interactions:
|
26
|
-
sanitize_serialized_interaction(interaction)
|
schemathesis/code_samples.py
DELETED
@@ -1,151 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from enum import Enum
|
4
|
-
from functools import lru_cache
|
5
|
-
from shlex import quote
|
6
|
-
from typing import TYPE_CHECKING
|
7
|
-
|
8
|
-
from .constants import SCHEMATHESIS_TEST_CASE_HEADER
|
9
|
-
|
10
|
-
if TYPE_CHECKING:
|
11
|
-
from requests.structures import CaseInsensitiveDict
|
12
|
-
|
13
|
-
from .types import Headers
|
14
|
-
|
15
|
-
|
16
|
-
@lru_cache
|
17
|
-
def get_excluded_headers() -> CaseInsensitiveDict:
|
18
|
-
from requests.structures import CaseInsensitiveDict
|
19
|
-
from requests.utils import default_headers
|
20
|
-
|
21
|
-
# These headers are added automatically by Schemathesis or `requests`.
|
22
|
-
# Do not show them in code samples to make them more readable
|
23
|
-
|
24
|
-
return CaseInsensitiveDict(
|
25
|
-
{
|
26
|
-
"Content-Length": None,
|
27
|
-
"Transfer-Encoding": None,
|
28
|
-
SCHEMATHESIS_TEST_CASE_HEADER: None,
|
29
|
-
**default_headers(),
|
30
|
-
}
|
31
|
-
)
|
32
|
-
|
33
|
-
|
34
|
-
class CodeSampleStyle(str, Enum):
|
35
|
-
"""Controls the style of code samples for failure reproduction."""
|
36
|
-
|
37
|
-
python = "python"
|
38
|
-
curl = "curl"
|
39
|
-
|
40
|
-
@property
|
41
|
-
def verbose_name(self) -> str:
|
42
|
-
return {
|
43
|
-
self.curl: "cURL command",
|
44
|
-
self.python: "Python code",
|
45
|
-
}[self]
|
46
|
-
|
47
|
-
@classmethod
|
48
|
-
def default(cls) -> CodeSampleStyle:
|
49
|
-
return cls.curl
|
50
|
-
|
51
|
-
@classmethod
|
52
|
-
def from_str(cls, value: str) -> CodeSampleStyle:
|
53
|
-
try:
|
54
|
-
return cls[value]
|
55
|
-
except KeyError:
|
56
|
-
available_styles = ", ".join(cls)
|
57
|
-
raise ValueError(
|
58
|
-
f"Invalid value for code sample style: {value}. Available styles: {available_styles}"
|
59
|
-
) from None
|
60
|
-
|
61
|
-
def generate(
|
62
|
-
self,
|
63
|
-
*,
|
64
|
-
method: str,
|
65
|
-
url: str,
|
66
|
-
body: str | bytes | None,
|
67
|
-
headers: Headers | None,
|
68
|
-
verify: bool,
|
69
|
-
extra_headers: Headers | None = None,
|
70
|
-
) -> str:
|
71
|
-
"""Generate a code snippet for making HTTP requests."""
|
72
|
-
handlers = {
|
73
|
-
self.curl: _generate_curl,
|
74
|
-
self.python: _generate_requests,
|
75
|
-
}
|
76
|
-
return handlers[self](
|
77
|
-
method=method, url=url, body=body, headers=_filter_headers(headers, extra_headers), verify=verify
|
78
|
-
)
|
79
|
-
|
80
|
-
|
81
|
-
def _filter_headers(headers: Headers | None, extra: Headers | None = None) -> Headers:
|
82
|
-
headers = headers.copy() if headers else {}
|
83
|
-
if extra is not None:
|
84
|
-
for key, value in extra.items():
|
85
|
-
if key not in get_excluded_headers():
|
86
|
-
headers[key] = value
|
87
|
-
return headers
|
88
|
-
|
89
|
-
|
90
|
-
def _generate_curl(
|
91
|
-
*,
|
92
|
-
method: str,
|
93
|
-
url: str,
|
94
|
-
body: str | bytes | None,
|
95
|
-
headers: Headers,
|
96
|
-
verify: bool,
|
97
|
-
) -> str:
|
98
|
-
"""Create a cURL command to reproduce an HTTP request."""
|
99
|
-
command = f"curl -X {method}"
|
100
|
-
for key, value in headers.items():
|
101
|
-
header = f"{key}: {value}"
|
102
|
-
command += f" -H {quote(header)}"
|
103
|
-
if body:
|
104
|
-
if isinstance(body, bytes):
|
105
|
-
body = body.decode("utf-8", errors="replace")
|
106
|
-
command += f" -d {quote(body)}"
|
107
|
-
if not verify:
|
108
|
-
command += " --insecure"
|
109
|
-
return f"{command} {quote(url)}"
|
110
|
-
|
111
|
-
|
112
|
-
def _generate_requests(
|
113
|
-
*,
|
114
|
-
method: str,
|
115
|
-
url: str,
|
116
|
-
body: str | bytes | None,
|
117
|
-
headers: Headers,
|
118
|
-
verify: bool,
|
119
|
-
) -> str:
|
120
|
-
"""Create a Python code to reproduce an HTTP request."""
|
121
|
-
url = _escape_single_quotes(url)
|
122
|
-
command = f"requests.{method.lower()}('{url}'"
|
123
|
-
if body:
|
124
|
-
command += f", data={body!r}"
|
125
|
-
if headers:
|
126
|
-
command += f", headers={headers!r}"
|
127
|
-
if not verify:
|
128
|
-
command += ", verify=False"
|
129
|
-
command += ")"
|
130
|
-
return command
|
131
|
-
|
132
|
-
|
133
|
-
def _escape_single_quotes(url: str) -> str:
|
134
|
-
"""Escape single quotes in a string, so it is usable as in generated Python code.
|
135
|
-
|
136
|
-
The usual ``str.replace`` is not suitable as it may convert already escaped quotes to not-escaped.
|
137
|
-
"""
|
138
|
-
result = []
|
139
|
-
escape = False
|
140
|
-
for char in url:
|
141
|
-
if escape:
|
142
|
-
result.append(char)
|
143
|
-
escape = False
|
144
|
-
elif char == "\\":
|
145
|
-
result.append(char)
|
146
|
-
escape = True
|
147
|
-
elif char == "'":
|
148
|
-
result.append("\\'")
|
149
|
-
else:
|
150
|
-
result.append(char)
|
151
|
-
return "".join(result)
|
schemathesis/constants.py
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
from importlib import metadata
|
2
|
-
|
3
|
-
from .types import NotSet
|
4
|
-
|
5
|
-
try:
|
6
|
-
SCHEMATHESIS_VERSION = metadata.version(__package__)
|
7
|
-
except metadata.PackageNotFoundError:
|
8
|
-
# Local run without installation
|
9
|
-
SCHEMATHESIS_VERSION = "dev"
|
10
|
-
|
11
|
-
NOT_SET = NotSet()
|
12
|
-
USER_AGENT = f"schemathesis/{SCHEMATHESIS_VERSION}"
|
13
|
-
SCHEMATHESIS_TEST_CASE_HEADER = "X-Schemathesis-TestCaseId"
|
14
|
-
HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER = ":memory:"
|
15
|
-
DISCORD_LINK = "https://discord.gg/R9ASRAmHnA"
|
16
|
-
GITHUB_APP_LINK = "https://github.com/apps/schemathesis"
|
17
|
-
# Maximum test running time
|
18
|
-
DEFAULT_DEADLINE = 15000
|
19
|
-
DEFAULT_RESPONSE_TIMEOUT = 10000
|
20
|
-
DEFAULT_STATEFUL_RECURSION_LIMIT = 5
|
21
|
-
HTTP_METHODS = frozenset({"get", "put", "post", "delete", "options", "head", "patch", "trace"})
|
22
|
-
RECURSIVE_REFERENCE_ERROR_MESSAGE = (
|
23
|
-
"Currently, Schemathesis can't generate data for this operation due to "
|
24
|
-
"recursive references in the operation definition. See more information in "
|
25
|
-
"this issue - https://github.com/schemathesis/schemathesis/issues/947"
|
26
|
-
)
|
27
|
-
GIVEN_AND_EXPLICIT_EXAMPLES_ERROR_MESSAGE = (
|
28
|
-
"Unsupported test setup. Tests using `@schema.given` cannot be combined with explicit schema examples in the same "
|
29
|
-
"function. Separate these tests into distinct functions to avoid conflicts."
|
30
|
-
)
|
31
|
-
SERIALIZERS_SUGGESTION_MESSAGE = (
|
32
|
-
"You can register your own serializer with `schemathesis.serializer` "
|
33
|
-
"and Schemathesis will be able to make API calls with this media type. \n"
|
34
|
-
"See https://schemathesis.readthedocs.io/en/stable/how.html#payload-serialization for more information."
|
35
|
-
)
|
36
|
-
NO_LINKS_ERROR_MESSAGE = (
|
37
|
-
"Stateful testing requires at least one OpenAPI link in the schema, but no links detected. "
|
38
|
-
"Please add OpenAPI links to enable stateful testing or use stateless tests instead. \n"
|
39
|
-
"See https://schemathesis.readthedocs.io/en/stable/stateful.html#how-to-specify-connections for more information."
|
40
|
-
)
|
41
|
-
EXTENSIONS_DOCUMENTATION_URL = "https://schemathesis.readthedocs.io/en/stable/extending.html"
|
42
|
-
ISSUE_TRACKER_URL = (
|
43
|
-
"https://github.com/schemathesis/schemathesis/issues/new?"
|
44
|
-
"labels=Status%3A%20Needs%20Triage%2C+Type%3A+Bug&template=bug_report.md&title=%5BBUG%5D"
|
45
|
-
)
|
46
|
-
FLAKY_FAILURE_MESSAGE = "[FLAKY] Schemathesis was not able to reliably reproduce this failure"
|
47
|
-
BOM_MARK = "\ufeff"
|
48
|
-
WAIT_FOR_SCHEMA_INTERVAL = 0.05
|
49
|
-
HOOKS_MODULE_ENV_VAR = "SCHEMATHESIS_HOOKS"
|
50
|
-
API_NAME_ENV_VAR = "SCHEMATHESIS_API_NAME"
|
51
|
-
BASE_URL_ENV_VAR = "SCHEMATHESIS_BASE_URL"
|
52
|
-
WAIT_FOR_SCHEMA_ENV_VAR = "SCHEMATHESIS_WAIT_FOR_SCHEMA"
|
53
|
-
REPORT_SUGGESTION_ENV_VAR = "SCHEMATHESIS_REPORT_SUGGESTION"
|
54
|
-
|
55
|
-
TRUE_VALUES = ("y", "yes", "t", "true", "on", "1")
|
56
|
-
FALSE_VALUES = ("n", "no", "f", "false", "off", "0")
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# Open API 2.0 / 3.0 do not include `uuid` in the list of built-in formats, hence it lives in `contrib`.
|
2
|
-
FORMAT_NAME = "uuid"
|
3
|
-
|
4
|
-
|
5
|
-
def install() -> None:
|
6
|
-
from hypothesis import strategies as st
|
7
|
-
|
8
|
-
from ....specs import openapi
|
9
|
-
|
10
|
-
openapi.format(FORMAT_NAME, st.uuids().map(str))
|
11
|
-
|
12
|
-
|
13
|
-
def uninstall() -> None:
|
14
|
-
from ....specs import openapi
|
15
|
-
|
16
|
-
openapi.unregister_string_format("uuid")
|
@@ -1,41 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import warnings
|
4
|
-
from typing import TYPE_CHECKING
|
5
|
-
|
6
|
-
from ..hooks import HookContext, register, unregister
|
7
|
-
|
8
|
-
if TYPE_CHECKING:
|
9
|
-
from hypothesis import strategies as st
|
10
|
-
|
11
|
-
from ..models import Case
|
12
|
-
|
13
|
-
|
14
|
-
def install() -> None:
|
15
|
-
warnings.warn(
|
16
|
-
"The `schemathesis.contrib.unique_data` hook is **DEPRECATED**. The concept of this feature does not fit the core principles of Hypothesis where "
|
17
|
-
"strategies are configurable on a per-example basis but this feature implies uniqueness across examples. "
|
18
|
-
"This leads to cryptic error messages about external state and flaky test runs, "
|
19
|
-
"therefore it will be removed in Schemathesis 4.0",
|
20
|
-
DeprecationWarning,
|
21
|
-
stacklevel=1,
|
22
|
-
)
|
23
|
-
register(before_generate_case)
|
24
|
-
|
25
|
-
|
26
|
-
def uninstall() -> None:
|
27
|
-
unregister(before_generate_case)
|
28
|
-
|
29
|
-
|
30
|
-
def before_generate_case(context: HookContext, strategy: st.SearchStrategy[Case]) -> st.SearchStrategy[Case]:
|
31
|
-
seen = set()
|
32
|
-
|
33
|
-
def is_not_seen(case: Case) -> bool:
|
34
|
-
# Calculate hash just once as it is costly
|
35
|
-
hashed = hash(case)
|
36
|
-
if hashed not in seen:
|
37
|
-
seen.add(hashed)
|
38
|
-
return True
|
39
|
-
return False
|
40
|
-
|
41
|
-
return strategy.filter(is_not_seen)
|