schemathesis 3.39.15__py3-none-any.whl → 4.0.0__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 +41 -79
- schemathesis/auths.py +111 -122
- schemathesis/checks.py +169 -60
- schemathesis/cli/__init__.py +15 -2117
- schemathesis/cli/commands/__init__.py +85 -0
- schemathesis/cli/commands/data.py +10 -0
- schemathesis/cli/commands/run/__init__.py +590 -0
- schemathesis/cli/commands/run/context.py +204 -0
- schemathesis/cli/commands/run/events.py +60 -0
- schemathesis/cli/commands/run/executor.py +157 -0
- schemathesis/cli/commands/run/filters.py +53 -0
- schemathesis/cli/commands/run/handlers/__init__.py +46 -0
- schemathesis/cli/commands/run/handlers/base.py +18 -0
- schemathesis/cli/commands/run/handlers/cassettes.py +474 -0
- schemathesis/cli/commands/run/handlers/junitxml.py +55 -0
- schemathesis/cli/commands/run/handlers/output.py +1628 -0
- schemathesis/cli/commands/run/loaders.py +114 -0
- schemathesis/cli/commands/run/validation.py +246 -0
- schemathesis/cli/constants.py +5 -58
- schemathesis/cli/core.py +19 -0
- schemathesis/cli/ext/fs.py +16 -0
- schemathesis/cli/ext/groups.py +84 -0
- schemathesis/cli/{options.py → ext/options.py} +36 -34
- schemathesis/config/__init__.py +189 -0
- schemathesis/config/_auth.py +51 -0
- schemathesis/config/_checks.py +268 -0
- schemathesis/config/_diff_base.py +99 -0
- schemathesis/config/_env.py +21 -0
- schemathesis/config/_error.py +156 -0
- schemathesis/config/_generation.py +149 -0
- schemathesis/config/_health_check.py +24 -0
- schemathesis/config/_operations.py +327 -0
- schemathesis/config/_output.py +171 -0
- schemathesis/config/_parameters.py +19 -0
- schemathesis/config/_phases.py +187 -0
- schemathesis/config/_projects.py +527 -0
- schemathesis/config/_rate_limit.py +17 -0
- schemathesis/config/_report.py +120 -0
- schemathesis/config/_validator.py +9 -0
- schemathesis/config/_warnings.py +25 -0
- schemathesis/config/schema.json +885 -0
- schemathesis/core/__init__.py +67 -0
- schemathesis/core/compat.py +32 -0
- schemathesis/core/control.py +2 -0
- schemathesis/core/curl.py +58 -0
- schemathesis/core/deserialization.py +65 -0
- schemathesis/core/errors.py +459 -0
- schemathesis/core/failures.py +315 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/core/hooks.py +20 -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/core/output/__init__.py +46 -0
- schemathesis/core/output/sanitization.py +54 -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 +223 -0
- schemathesis/core/validation.py +54 -0
- schemathesis/core/version.py +7 -0
- schemathesis/engine/__init__.py +28 -0
- schemathesis/engine/context.py +118 -0
- schemathesis/engine/control.py +36 -0
- schemathesis/engine/core.py +169 -0
- schemathesis/engine/errors.py +464 -0
- schemathesis/engine/events.py +258 -0
- schemathesis/engine/phases/__init__.py +88 -0
- schemathesis/{runner → engine/phases}/probes.py +52 -68
- schemathesis/engine/phases/stateful/__init__.py +68 -0
- schemathesis/engine/phases/stateful/_executor.py +356 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +212 -0
- schemathesis/engine/phases/unit/_executor.py +416 -0
- schemathesis/engine/phases/unit/_pool.py +82 -0
- schemathesis/engine/recorder.py +247 -0
- schemathesis/errors.py +43 -0
- schemathesis/filters.py +17 -98
- schemathesis/generation/__init__.py +5 -33
- schemathesis/generation/case.py +317 -0
- schemathesis/generation/coverage.py +282 -175
- schemathesis/generation/hypothesis/__init__.py +36 -0
- schemathesis/generation/hypothesis/builder.py +800 -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/metrics.py +93 -0
- schemathesis/generation/modes.py +20 -0
- schemathesis/generation/overrides.py +116 -0
- schemathesis/generation/stateful/__init__.py +37 -0
- schemathesis/generation/stateful/state_machine.py +278 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +109 -0
- schemathesis/graphql/loaders.py +284 -0
- schemathesis/hooks.py +80 -101
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +455 -0
- schemathesis/openapi/generation/__init__.py +0 -0
- schemathesis/openapi/generation/filters.py +72 -0
- schemathesis/openapi/loaders.py +313 -0
- schemathesis/pytest/__init__.py +5 -0
- schemathesis/pytest/control_flow.py +7 -0
- schemathesis/pytest/lazy.py +281 -0
- schemathesis/pytest/loaders.py +36 -0
- schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +128 -108
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +537 -273
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/_cache.py +1 -2
- schemathesis/specs/graphql/scalars.py +42 -6
- schemathesis/specs/graphql/schemas.py +141 -137
- 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 +142 -156
- schemathesis/specs/openapi/checks.py +368 -257
- schemathesis/specs/openapi/converter.py +4 -4
- schemathesis/specs/openapi/definitions.py +1 -1
- schemathesis/specs/openapi/examples.py +23 -21
- schemathesis/specs/openapi/expressions/__init__.py +31 -19
- schemathesis/specs/openapi/expressions/extractors.py +1 -4
- schemathesis/specs/openapi/expressions/lexer.py +1 -1
- schemathesis/specs/openapi/expressions/nodes.py +36 -41
- schemathesis/specs/openapi/expressions/parser.py +1 -1
- schemathesis/specs/openapi/formats.py +35 -7
- schemathesis/specs/openapi/media_types.py +53 -12
- schemathesis/specs/openapi/negative/__init__.py +7 -4
- schemathesis/specs/openapi/negative/mutations.py +6 -5
- schemathesis/specs/openapi/parameters.py +7 -10
- schemathesis/specs/openapi/patterns.py +94 -31
- schemathesis/specs/openapi/references.py +12 -53
- schemathesis/specs/openapi/schemas.py +238 -308
- schemathesis/specs/openapi/security.py +1 -1
- schemathesis/specs/openapi/serialization.py +12 -6
- schemathesis/specs/openapi/stateful/__init__.py +268 -133
- schemathesis/specs/openapi/stateful/control.py +87 -0
- schemathesis/specs/openapi/stateful/links.py +209 -0
- schemathesis/transport/__init__.py +142 -0
- schemathesis/transport/asgi.py +26 -0
- schemathesis/transport/prepare.py +124 -0
- schemathesis/transport/requests.py +244 -0
- schemathesis/{_xml.py → transport/serialization.py} +69 -11
- schemathesis/transport/wsgi.py +171 -0
- schemathesis-4.0.0.dist-info/METADATA +204 -0
- schemathesis-4.0.0.dist-info/RECORD +164 -0
- {schemathesis-3.39.15.dist-info → schemathesis-4.0.0.dist-info}/entry_points.txt +1 -1
- {schemathesis-3.39.15.dist-info → schemathesis-4.0.0.dist-info}/licenses/LICENSE +1 -1
- schemathesis/_compat.py +0 -74
- schemathesis/_dependency_versions.py +0 -19
- schemathesis/_hypothesis.py +0 -712
- schemathesis/_override.py +0 -50
- schemathesis/_patches.py +0 -21
- schemathesis/_rate_limiter.py +0 -7
- schemathesis/cli/callbacks.py +0 -466
- schemathesis/cli/cassettes.py +0 -561
- 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 -920
- 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 -54
- schemathesis/contrib/__init__.py +0 -11
- schemathesis/contrib/openapi/__init__.py +0 -11
- schemathesis/contrib/openapi/fill_missing_examples.py +0 -24
- 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/experimental/__init__.py +0 -109
- schemathesis/extra/_aiohttp.py +0 -28
- schemathesis/extra/_flask.py +0 -13
- schemathesis/extra/_server.py +0 -18
- schemathesis/failures.py +0 -284
- 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 -86
- schemathesis/internal/copy.py +0 -32
- schemathesis/internal/datetime.py +0 -5
- schemathesis/internal/deprecation.py +0 -37
- schemathesis/internal/diff.py +0 -15
- schemathesis/internal/extensions.py +0 -27
- schemathesis/internal/jsonschema.py +0 -36
- schemathesis/internal/output.py +0 -68
- 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 -88
- schemathesis/runner/impl/core.py +0 -1280
- 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/links.py +0 -389
- schemathesis/specs/openapi/loaders.py +0 -707
- 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/state_machine.py +0 -328
- schemathesis/stateful/statistic.py +0 -22
- schemathesis/stateful/validation.py +0 -100
- schemathesis/targets.py +0 -77
- schemathesis/transports/__init__.py +0 -369
- 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.15.dist-info/METADATA +0 -293
- schemathesis-3.39.15.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.15.dist-info → schemathesis-4.0.0.dist-info}/WHEEL +0 -0
schemathesis/extra/_aiohttp.py
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import asyncio
|
4
|
-
|
5
|
-
from aiohttp import web
|
6
|
-
|
7
|
-
from . import _server
|
8
|
-
|
9
|
-
|
10
|
-
def _run_server(app: web.Application, port: int) -> None:
|
11
|
-
"""Run the given app on the given port.
|
12
|
-
|
13
|
-
Intended to be called as a target for a separate thread.
|
14
|
-
NOTE. `aiohttp.web.run_app` works only in the main thread and can't be used here (or maybe can we some tuning)
|
15
|
-
"""
|
16
|
-
# Set a loop for a new thread (there is no by default for non-main threads)
|
17
|
-
loop = asyncio.new_event_loop()
|
18
|
-
asyncio.set_event_loop(loop)
|
19
|
-
runner = web.AppRunner(app)
|
20
|
-
loop.run_until_complete(runner.setup())
|
21
|
-
site = web.TCPSite(runner, "127.0.0.1", port)
|
22
|
-
loop.run_until_complete(site.start())
|
23
|
-
loop.run_forever()
|
24
|
-
|
25
|
-
|
26
|
-
def run_server(app: web.Application, port: int | None = None, timeout: float = 0.05) -> int:
|
27
|
-
"""Start a thread with the given aiohttp application."""
|
28
|
-
return _server.run(_run_server, app=app, port=port, timeout=timeout)
|
schemathesis/extra/_flask.py
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from typing import TYPE_CHECKING
|
4
|
-
|
5
|
-
from . import _server
|
6
|
-
|
7
|
-
if TYPE_CHECKING:
|
8
|
-
from flask import Flask
|
9
|
-
|
10
|
-
|
11
|
-
def run_server(app: Flask, port: int | None = None, timeout: float = 0.05) -> int:
|
12
|
-
"""Start a thread with the given aiohttp application."""
|
13
|
-
return _server.run(app.run, port=port, timeout=timeout)
|
schemathesis/extra/_server.py
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import threading
|
4
|
-
from time import sleep
|
5
|
-
from typing import Any, Callable
|
6
|
-
|
7
|
-
from aiohttp.test_utils import unused_port
|
8
|
-
|
9
|
-
|
10
|
-
def run(target: Callable, port: int | None = None, timeout: float = 0.05, **kwargs: Any) -> int:
|
11
|
-
"""Start a thread with the given aiohttp application."""
|
12
|
-
if port is None:
|
13
|
-
port = unused_port()
|
14
|
-
server_thread = threading.Thread(target=target, kwargs={"port": port, **kwargs})
|
15
|
-
server_thread.daemon = True
|
16
|
-
server_thread.start()
|
17
|
-
sleep(timeout)
|
18
|
-
return port
|
schemathesis/failures.py
DELETED
@@ -1,284 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import textwrap
|
4
|
-
from dataclasses import dataclass
|
5
|
-
from typing import TYPE_CHECKING, Any
|
6
|
-
|
7
|
-
from schemathesis.internal.output import OutputConfig
|
8
|
-
|
9
|
-
if TYPE_CHECKING:
|
10
|
-
from json import JSONDecodeError
|
11
|
-
|
12
|
-
from graphql.error import GraphQLFormattedError
|
13
|
-
from jsonschema import ValidationError
|
14
|
-
|
15
|
-
|
16
|
-
class FailureContext:
|
17
|
-
"""Additional data specific to certain failure kind."""
|
18
|
-
|
19
|
-
# Short description of what happened
|
20
|
-
title: str
|
21
|
-
# A longer one
|
22
|
-
message: str
|
23
|
-
type: str
|
24
|
-
|
25
|
-
def unique_by_key(self, check_message: str | None) -> tuple[str, ...]:
|
26
|
-
"""A key to distinguish different failure contexts."""
|
27
|
-
return (check_message or self.message,)
|
28
|
-
|
29
|
-
|
30
|
-
@dataclass(repr=False)
|
31
|
-
class ValidationErrorContext(FailureContext):
|
32
|
-
"""Additional information about JSON Schema validation errors."""
|
33
|
-
|
34
|
-
validation_message: str
|
35
|
-
schema_path: list[str | int]
|
36
|
-
schema: dict[str, Any] | bool
|
37
|
-
instance_path: list[str | int]
|
38
|
-
instance: None | bool | float | str | list | dict[str, Any]
|
39
|
-
message: str
|
40
|
-
title: str = "Response violates schema"
|
41
|
-
type: str = "json_schema"
|
42
|
-
|
43
|
-
def unique_by_key(self, check_message: str | None) -> tuple[str, ...]:
|
44
|
-
# Deduplicate by JSON Schema path. All errors that happened on this sub-schema will be deduplicated
|
45
|
-
return ("/".join(map(str, self.schema_path)),)
|
46
|
-
|
47
|
-
@classmethod
|
48
|
-
def from_exception(
|
49
|
-
cls, exc: ValidationError, *, output_config: OutputConfig | None = None
|
50
|
-
) -> ValidationErrorContext:
|
51
|
-
from .internal.output import truncate_json
|
52
|
-
|
53
|
-
output_config = OutputConfig.from_parent(output_config, max_lines=20)
|
54
|
-
schema = textwrap.indent(truncate_json(exc.schema, config=output_config), prefix=" ")
|
55
|
-
value = textwrap.indent(truncate_json(exc.instance, config=output_config), prefix=" ")
|
56
|
-
schema_path = list(exc.absolute_schema_path)
|
57
|
-
if len(schema_path) > 1:
|
58
|
-
# Exclude the last segment, which is already in the schema
|
59
|
-
schema_title = "Schema at "
|
60
|
-
for segment in schema_path[:-1]:
|
61
|
-
schema_title += f"/{segment}"
|
62
|
-
else:
|
63
|
-
schema_title = "Schema"
|
64
|
-
message = f"{exc.message}\n\n{schema_title}:\n\n{schema}\n\nValue:\n\n{value}"
|
65
|
-
return cls(
|
66
|
-
message=message,
|
67
|
-
validation_message=exc.message,
|
68
|
-
schema_path=schema_path,
|
69
|
-
schema=exc.schema,
|
70
|
-
instance_path=list(exc.absolute_path),
|
71
|
-
instance=exc.instance,
|
72
|
-
)
|
73
|
-
|
74
|
-
|
75
|
-
@dataclass(repr=False)
|
76
|
-
class JSONDecodeErrorContext(FailureContext):
|
77
|
-
"""Failed to decode JSON."""
|
78
|
-
|
79
|
-
validation_message: str
|
80
|
-
document: str
|
81
|
-
position: int
|
82
|
-
lineno: int
|
83
|
-
colno: int
|
84
|
-
message: str
|
85
|
-
title: str = "JSON deserialization error"
|
86
|
-
type: str = "json_decode"
|
87
|
-
|
88
|
-
def unique_by_key(self, check_message: str | None) -> tuple[str, ...]:
|
89
|
-
# Treat different JSON decoding failures as the same issue
|
90
|
-
# Payloads often contain dynamic data and distinguishing it by the error location still would not be sufficient
|
91
|
-
# as it may be different on different dynamic payloads
|
92
|
-
return (self.title,)
|
93
|
-
|
94
|
-
@classmethod
|
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}"
|
97
|
-
return cls(
|
98
|
-
message=message,
|
99
|
-
validation_message=exc.msg,
|
100
|
-
document=exc.doc,
|
101
|
-
position=exc.pos,
|
102
|
-
lineno=exc.lineno,
|
103
|
-
colno=exc.colno,
|
104
|
-
)
|
105
|
-
|
106
|
-
|
107
|
-
@dataclass(repr=False)
|
108
|
-
class ServerError(FailureContext):
|
109
|
-
status_code: int
|
110
|
-
title: str = "Server error"
|
111
|
-
message: str = ""
|
112
|
-
type: str = "server_error"
|
113
|
-
|
114
|
-
|
115
|
-
@dataclass(repr=False)
|
116
|
-
class MissingContentType(FailureContext):
|
117
|
-
"""Content type header is missing."""
|
118
|
-
|
119
|
-
media_types: list[str]
|
120
|
-
message: str
|
121
|
-
title: str = "Missing Content-Type header"
|
122
|
-
type: str = "missing_content_type"
|
123
|
-
|
124
|
-
|
125
|
-
@dataclass(repr=False)
|
126
|
-
class UndefinedContentType(FailureContext):
|
127
|
-
"""Response has Content-Type that is not documented in the schema."""
|
128
|
-
|
129
|
-
content_type: str
|
130
|
-
defined_content_types: list[str]
|
131
|
-
message: str
|
132
|
-
title: str = "Undocumented Content-Type"
|
133
|
-
type: str = "undefined_content_type"
|
134
|
-
|
135
|
-
|
136
|
-
@dataclass(repr=False)
|
137
|
-
class AcceptedNegativeData(FailureContext):
|
138
|
-
"""Response with negative data was accepted."""
|
139
|
-
|
140
|
-
message: str
|
141
|
-
status_code: int
|
142
|
-
allowed_statuses: list[str]
|
143
|
-
title: str = "Accepted negative data"
|
144
|
-
type: str = "accepted_negative_data"
|
145
|
-
|
146
|
-
def unique_by_key(self, check_message: str | None) -> tuple[str, ...]:
|
147
|
-
return (
|
148
|
-
check_message or self.message,
|
149
|
-
str(self.status_code),
|
150
|
-
)
|
151
|
-
|
152
|
-
|
153
|
-
@dataclass(repr=False)
|
154
|
-
class RejectedPositiveData(FailureContext):
|
155
|
-
"""Response with positive data was rejected."""
|
156
|
-
|
157
|
-
message: str
|
158
|
-
status_code: int
|
159
|
-
allowed_statuses: list[str]
|
160
|
-
title: str = "Rejected positive data"
|
161
|
-
type: str = "rejected_positive_data"
|
162
|
-
|
163
|
-
|
164
|
-
@dataclass(repr=False)
|
165
|
-
class UseAfterFree(FailureContext):
|
166
|
-
"""Resource was used after a successful DELETE operation on it."""
|
167
|
-
|
168
|
-
message: str
|
169
|
-
free: str
|
170
|
-
usage: str
|
171
|
-
title: str = "Use after free"
|
172
|
-
type: str = "use_after_free"
|
173
|
-
|
174
|
-
|
175
|
-
@dataclass(repr=False)
|
176
|
-
class EnsureResourceAvailability(FailureContext):
|
177
|
-
"""Resource is not available immediately after creation."""
|
178
|
-
|
179
|
-
message: str
|
180
|
-
created_with: str
|
181
|
-
not_available_with: str
|
182
|
-
title: str = "Resource is not available after creation"
|
183
|
-
type: str = "ensure_resource_availability"
|
184
|
-
|
185
|
-
|
186
|
-
@dataclass(repr=False)
|
187
|
-
class IgnoredAuth(FailureContext):
|
188
|
-
"""The API operation does not check the specified authentication."""
|
189
|
-
|
190
|
-
message: str
|
191
|
-
title: str = "Authentication declared but not enforced for this operation"
|
192
|
-
type: str = "ignored_auth"
|
193
|
-
|
194
|
-
|
195
|
-
@dataclass(repr=False)
|
196
|
-
class UndefinedStatusCode(FailureContext):
|
197
|
-
"""Response has a status code that is not defined in the schema."""
|
198
|
-
|
199
|
-
# Response's status code
|
200
|
-
status_code: int
|
201
|
-
# Status codes as defined in schema
|
202
|
-
defined_status_codes: list[str]
|
203
|
-
# Defined status code with expanded wildcards
|
204
|
-
allowed_status_codes: list[int]
|
205
|
-
message: str
|
206
|
-
title: str = "Undocumented HTTP status code"
|
207
|
-
type: str = "undefined_status_code"
|
208
|
-
|
209
|
-
|
210
|
-
@dataclass(repr=False)
|
211
|
-
class MissingHeaders(FailureContext):
|
212
|
-
"""Some required headers are missing."""
|
213
|
-
|
214
|
-
missing_headers: list[str]
|
215
|
-
message: str
|
216
|
-
title: str = "Missing required headers"
|
217
|
-
type: str = "missing_headers"
|
218
|
-
|
219
|
-
|
220
|
-
@dataclass(repr=False)
|
221
|
-
class MalformedMediaType(FailureContext):
|
222
|
-
"""Media type name is malformed.
|
223
|
-
|
224
|
-
Example: `application-json` instead of `application/json`
|
225
|
-
"""
|
226
|
-
|
227
|
-
actual: str
|
228
|
-
defined: str
|
229
|
-
message: str
|
230
|
-
title: str = "Malformed media type"
|
231
|
-
type: str = "malformed_media_type"
|
232
|
-
|
233
|
-
|
234
|
-
@dataclass(repr=False)
|
235
|
-
class ResponseTimeExceeded(FailureContext):
|
236
|
-
"""Response took longer than expected."""
|
237
|
-
|
238
|
-
elapsed: float
|
239
|
-
deadline: int
|
240
|
-
message: str
|
241
|
-
title: str = "Response time limit exceeded"
|
242
|
-
type: str = "response_time_exceeded"
|
243
|
-
|
244
|
-
def unique_by_key(self, check_message: str | None) -> tuple[str, ...]:
|
245
|
-
return (self.title,)
|
246
|
-
|
247
|
-
|
248
|
-
@dataclass(repr=False)
|
249
|
-
class RequestTimeout(FailureContext):
|
250
|
-
"""Request took longer than timeout."""
|
251
|
-
|
252
|
-
timeout: int
|
253
|
-
message: str
|
254
|
-
title: str = "Response timeout"
|
255
|
-
type: str = "request_timeout"
|
256
|
-
|
257
|
-
|
258
|
-
@dataclass(repr=False)
|
259
|
-
class UnexpectedGraphQLResponse(FailureContext):
|
260
|
-
"""GraphQL response is not a JSON object."""
|
261
|
-
|
262
|
-
message: str
|
263
|
-
title: str = "Unexpected GraphQL Response"
|
264
|
-
type: str = "graphql_unexpected_response"
|
265
|
-
|
266
|
-
|
267
|
-
@dataclass(repr=False)
|
268
|
-
class GraphQLClientError(FailureContext):
|
269
|
-
"""GraphQL query has not been executed."""
|
270
|
-
|
271
|
-
message: str
|
272
|
-
errors: list[GraphQLFormattedError]
|
273
|
-
title: str = "GraphQL client error"
|
274
|
-
type: str = "graphql_client_error"
|
275
|
-
|
276
|
-
|
277
|
-
@dataclass(repr=False)
|
278
|
-
class GraphQLServerError(FailureContext):
|
279
|
-
"""GraphQL response indicates at least one server error."""
|
280
|
-
|
281
|
-
message: str
|
282
|
-
errors: list[GraphQLFormattedError]
|
283
|
-
title: str = "GraphQL server error"
|
284
|
-
type: str = "graphql_server_error"
|
schemathesis/fixups/__init__.py
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from typing import Iterable
|
4
|
-
|
5
|
-
from . import fast_api, utf8_bom
|
6
|
-
|
7
|
-
ALL_FIXUPS = {"fast_api": fast_api, "utf8_bom": utf8_bom}
|
8
|
-
ALL_FIXUP_NAMES = list(ALL_FIXUPS.keys())
|
9
|
-
|
10
|
-
|
11
|
-
def install(fixups: Iterable[str] | None = None) -> None:
|
12
|
-
"""Install fixups.
|
13
|
-
|
14
|
-
Without the first argument installs all available fixups.
|
15
|
-
|
16
|
-
:param fixups: Names of fixups to install.
|
17
|
-
"""
|
18
|
-
fixups = fixups or ALL_FIXUP_NAMES
|
19
|
-
for name in fixups:
|
20
|
-
ALL_FIXUPS[name].install() # type: ignore
|
21
|
-
|
22
|
-
|
23
|
-
def uninstall(fixups: Iterable[str] | None = None) -> None:
|
24
|
-
"""Uninstall fixups.
|
25
|
-
|
26
|
-
Without the first argument uninstalls all available fixups.
|
27
|
-
|
28
|
-
:param fixups: Names of fixups to uninstall.
|
29
|
-
"""
|
30
|
-
fixups = fixups or ALL_FIXUP_NAMES
|
31
|
-
for name in fixups:
|
32
|
-
ALL_FIXUPS[name].uninstall() # type: ignore
|
33
|
-
|
34
|
-
|
35
|
-
def is_installed(name: str) -> bool:
|
36
|
-
"""Check whether fixup is installed."""
|
37
|
-
return ALL_FIXUPS[name].is_installed()
|
schemathesis/fixups/fast_api.py
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from typing import Any
|
4
|
-
|
5
|
-
from ..hooks import HookContext, register, unregister
|
6
|
-
from ..hooks import is_installed as global_is_installed
|
7
|
-
from ..internal.jsonschema import traverse_schema
|
8
|
-
|
9
|
-
|
10
|
-
def install() -> None:
|
11
|
-
register(before_load_schema)
|
12
|
-
|
13
|
-
|
14
|
-
def uninstall() -> None:
|
15
|
-
unregister(before_load_schema)
|
16
|
-
|
17
|
-
|
18
|
-
def is_installed() -> bool:
|
19
|
-
return global_is_installed("before_load_schema", before_load_schema)
|
20
|
-
|
21
|
-
|
22
|
-
def before_load_schema(context: HookContext, schema: dict[str, Any]) -> None:
|
23
|
-
adjust_schema(schema)
|
24
|
-
|
25
|
-
|
26
|
-
def adjust_schema(schema: dict[str, Any]) -> None:
|
27
|
-
traverse_schema(schema, _handle_boundaries)
|
28
|
-
|
29
|
-
|
30
|
-
def _handle_boundaries(schema: dict[str, Any]) -> dict[str, Any]:
|
31
|
-
"""Convert Draft 7 keywords to Draft 4 compatible versions.
|
32
|
-
|
33
|
-
FastAPI uses ``pydantic``, which generates Draft 7 compatible schemas.
|
34
|
-
"""
|
35
|
-
for boundary_name, boundary_exclusive_name in (("maximum", "exclusiveMaximum"), ("minimum", "exclusiveMinimum")):
|
36
|
-
value = schema.get(boundary_exclusive_name)
|
37
|
-
# `bool` check is needed, since in Python `True` is an instance of `int`
|
38
|
-
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
39
|
-
schema[boundary_exclusive_name] = True
|
40
|
-
schema[boundary_name] = value
|
41
|
-
return schema
|
schemathesis/fixups/utf8_bom.py
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
from typing import TYPE_CHECKING
|
2
|
-
|
3
|
-
from ..constants import BOM_MARK
|
4
|
-
from ..hooks import HookContext, register, unregister
|
5
|
-
from ..hooks import is_installed as global_is_installed
|
6
|
-
|
7
|
-
if TYPE_CHECKING:
|
8
|
-
from ..models import Case
|
9
|
-
from ..transports.responses import GenericResponse
|
10
|
-
|
11
|
-
|
12
|
-
def install() -> None:
|
13
|
-
register(after_call)
|
14
|
-
|
15
|
-
|
16
|
-
def uninstall() -> None:
|
17
|
-
unregister(after_call)
|
18
|
-
|
19
|
-
|
20
|
-
def is_installed() -> bool:
|
21
|
-
return global_is_installed("after_call", after_call)
|
22
|
-
|
23
|
-
|
24
|
-
def after_call(context: HookContext, case: "Case", response: "GenericResponse") -> None:
|
25
|
-
from requests import Response
|
26
|
-
|
27
|
-
if isinstance(response, Response) and response.encoding == "utf-8" and response.text[0:1] == BOM_MARK:
|
28
|
-
response.encoding = "utf-8-sig"
|
@@ -1,44 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from enum import Enum
|
4
|
-
from typing import Iterable, Union
|
5
|
-
|
6
|
-
|
7
|
-
class DataGenerationMethod(str, Enum):
|
8
|
-
"""Defines what data Schemathesis generates for tests."""
|
9
|
-
|
10
|
-
# Generate data, that fits the API schema
|
11
|
-
positive = "positive"
|
12
|
-
# Doesn't fit the API schema
|
13
|
-
negative = "negative"
|
14
|
-
|
15
|
-
@classmethod
|
16
|
-
def default(cls) -> DataGenerationMethod:
|
17
|
-
return cls.positive
|
18
|
-
|
19
|
-
@classmethod
|
20
|
-
def all(cls) -> list[DataGenerationMethod]:
|
21
|
-
return list(DataGenerationMethod)
|
22
|
-
|
23
|
-
def as_short_name(self) -> str:
|
24
|
-
return {
|
25
|
-
DataGenerationMethod.positive: "P",
|
26
|
-
DataGenerationMethod.negative: "N",
|
27
|
-
}[self]
|
28
|
-
|
29
|
-
@property
|
30
|
-
def is_positive(self) -> bool:
|
31
|
-
return self == DataGenerationMethod.positive
|
32
|
-
|
33
|
-
@property
|
34
|
-
def is_negative(self) -> bool:
|
35
|
-
return self == DataGenerationMethod.negative
|
36
|
-
|
37
|
-
@classmethod
|
38
|
-
def ensure_list(cls, value: DataGenerationMethodInput) -> list[DataGenerationMethod]:
|
39
|
-
if isinstance(value, DataGenerationMethod):
|
40
|
-
return [value]
|
41
|
-
return list(value)
|
42
|
-
|
43
|
-
|
44
|
-
DataGenerationMethodInput = Union[DataGenerationMethod, Iterable[DataGenerationMethod]]
|
schemathesis/graphql.py
DELETED
schemathesis/internal/checks.py
DELETED
@@ -1,86 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import inspect
|
4
|
-
import warnings
|
5
|
-
from dataclasses import dataclass, field
|
6
|
-
from typing import TYPE_CHECKING, Callable, Optional
|
7
|
-
|
8
|
-
if TYPE_CHECKING:
|
9
|
-
from requests.auth import HTTPDigestAuth
|
10
|
-
from requests.structures import CaseInsensitiveDict
|
11
|
-
|
12
|
-
from .._override import CaseOverride
|
13
|
-
from ..models import Case
|
14
|
-
from ..transports.responses import GenericResponse
|
15
|
-
from ..types import RawAuth
|
16
|
-
|
17
|
-
|
18
|
-
CheckFunction = Callable[["CheckContext", "GenericResponse", "Case"], Optional[bool]]
|
19
|
-
|
20
|
-
|
21
|
-
@dataclass
|
22
|
-
class NegativeDataRejectionConfig:
|
23
|
-
# 5xx will pass through
|
24
|
-
allowed_statuses: list[str] = field(
|
25
|
-
default_factory=lambda: ["400", "401", "403", "404", "406", "422", "428", "5xx"]
|
26
|
-
)
|
27
|
-
|
28
|
-
|
29
|
-
@dataclass
|
30
|
-
class PositiveDataAcceptanceConfig:
|
31
|
-
allowed_statuses: list[str] = field(default_factory=lambda: ["2xx", "401", "403", "404"])
|
32
|
-
|
33
|
-
|
34
|
-
@dataclass
|
35
|
-
class MissingRequiredHeaderConfig:
|
36
|
-
allowed_statuses: list[str] = field(default_factory=lambda: ["406"])
|
37
|
-
|
38
|
-
|
39
|
-
@dataclass
|
40
|
-
class CheckConfig:
|
41
|
-
missing_required_header: MissingRequiredHeaderConfig = field(default_factory=MissingRequiredHeaderConfig)
|
42
|
-
negative_data_rejection: NegativeDataRejectionConfig = field(default_factory=NegativeDataRejectionConfig)
|
43
|
-
positive_data_acceptance: PositiveDataAcceptanceConfig = field(default_factory=PositiveDataAcceptanceConfig)
|
44
|
-
|
45
|
-
|
46
|
-
@dataclass
|
47
|
-
class CheckContext:
|
48
|
-
"""Context for Schemathesis checks.
|
49
|
-
|
50
|
-
Provides access to broader test execution data beyond individual test cases.
|
51
|
-
"""
|
52
|
-
|
53
|
-
override: CaseOverride | None
|
54
|
-
auth: HTTPDigestAuth | RawAuth | None
|
55
|
-
headers: CaseInsensitiveDict | None
|
56
|
-
config: CheckConfig = field(default_factory=CheckConfig)
|
57
|
-
transport_kwargs: dict | None = None
|
58
|
-
|
59
|
-
|
60
|
-
def wrap_check(check: Callable) -> CheckFunction:
|
61
|
-
"""Make older checks compatible with the new signature."""
|
62
|
-
signature = inspect.signature(check)
|
63
|
-
parameters = len(signature.parameters)
|
64
|
-
|
65
|
-
if parameters == 3:
|
66
|
-
# New style check, return as is
|
67
|
-
return check
|
68
|
-
|
69
|
-
if parameters == 2:
|
70
|
-
# Old style check, wrap it
|
71
|
-
warnings.warn(
|
72
|
-
f"The check function '{check.__name__}' uses an outdated signature. "
|
73
|
-
"Please update it to accept 'ctx' as the first argument: "
|
74
|
-
"(ctx: CheckContext, response: GenericResponse, case: Case) -> Optional[bool]",
|
75
|
-
DeprecationWarning,
|
76
|
-
stacklevel=2,
|
77
|
-
)
|
78
|
-
|
79
|
-
def wrapper(_: CheckContext, response: GenericResponse, case: Case) -> Optional[bool]:
|
80
|
-
return check(response, case)
|
81
|
-
|
82
|
-
wrapper.__name__ = check.__name__
|
83
|
-
|
84
|
-
return wrapper
|
85
|
-
|
86
|
-
raise ValueError(f"Invalid check function signature. Expected 2 or 3 parameters, got {parameters}")
|
schemathesis/internal/copy.py
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
from typing import Any
|
2
|
-
|
3
|
-
from .extensions import extensible
|
4
|
-
|
5
|
-
|
6
|
-
@extensible("SCHEMATHESIS_EXTENSION_FAST_DEEP_COPY")
|
7
|
-
def fast_deepcopy(value: Any) -> Any:
|
8
|
-
"""A specialized version of `deepcopy` that copies only `dict` and `list` and does unrolling.
|
9
|
-
|
10
|
-
It is on average 3x faster than `deepcopy` and given the amount of calls, it is an important optimization.
|
11
|
-
"""
|
12
|
-
if isinstance(value, dict):
|
13
|
-
return {
|
14
|
-
k1: (
|
15
|
-
{k2: fast_deepcopy(v2) for k2, v2 in v1.items()}
|
16
|
-
if isinstance(v1, dict)
|
17
|
-
else [fast_deepcopy(v2) for v2 in v1]
|
18
|
-
if isinstance(v1, list)
|
19
|
-
else v1
|
20
|
-
)
|
21
|
-
for k1, v1 in value.items()
|
22
|
-
}
|
23
|
-
if isinstance(value, list):
|
24
|
-
return [
|
25
|
-
{k2: fast_deepcopy(v2) for k2, v2 in v1.items()}
|
26
|
-
if isinstance(v1, dict)
|
27
|
-
else [fast_deepcopy(v2) for v2 in v1]
|
28
|
-
if isinstance(v1, list)
|
29
|
-
else v1
|
30
|
-
for v1 in value
|
31
|
-
]
|
32
|
-
return value
|
@@ -1,37 +0,0 @@
|
|
1
|
-
import warnings
|
2
|
-
from typing import Any, Callable
|
3
|
-
|
4
|
-
|
5
|
-
def _warn_deprecation(*, kind: str, thing: str, removed_in: str, replacement: str) -> None:
|
6
|
-
warnings.warn(
|
7
|
-
f"{kind} `{thing}` is deprecated and will be removed in Schemathesis {removed_in}. Use {replacement} instead.",
|
8
|
-
DeprecationWarning,
|
9
|
-
stacklevel=1,
|
10
|
-
)
|
11
|
-
|
12
|
-
|
13
|
-
def deprecated_property(*, removed_in: str, replacement: str) -> Callable:
|
14
|
-
def wrapper(prop: Callable) -> Callable:
|
15
|
-
@property # type: ignore
|
16
|
-
def inner(self: Any) -> Any:
|
17
|
-
_warn_deprecation(kind="Property", thing=prop.__name__, removed_in=removed_in, replacement=replacement)
|
18
|
-
return prop(self)
|
19
|
-
|
20
|
-
return inner
|
21
|
-
|
22
|
-
return wrapper
|
23
|
-
|
24
|
-
|
25
|
-
def warn_filtration_arguments(name: str) -> None:
|
26
|
-
_warn_deprecation(kind="Argument", thing=name, removed_in="4.0", replacement="`include` and `exclude` methods")
|
27
|
-
|
28
|
-
|
29
|
-
def deprecated_function(*, removed_in: str, replacement: str) -> Callable:
|
30
|
-
def wrapper(func: Callable) -> Callable:
|
31
|
-
def inner(*args: Any, **kwargs: Any) -> Any:
|
32
|
-
_warn_deprecation(kind="Function", thing=func.__name__, removed_in=removed_in, replacement=replacement)
|
33
|
-
return func(*args, **kwargs)
|
34
|
-
|
35
|
-
return inner
|
36
|
-
|
37
|
-
return wrapper
|
schemathesis/internal/diff.py
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from typing import Any, Mapping
|
4
|
-
|
5
|
-
|
6
|
-
def diff(left: Mapping[str, Any], right: Mapping[str, Any]) -> dict[str, Any]:
|
7
|
-
"""Calculate the difference between two dictionaries."""
|
8
|
-
diff = {}
|
9
|
-
for key, value in right.items():
|
10
|
-
if key not in left or left[key] != value:
|
11
|
-
diff[key] = value
|
12
|
-
for key in left:
|
13
|
-
if key not in right:
|
14
|
-
diff[key] = None # Mark deleted items as None
|
15
|
-
return diff
|