schemathesis 3.25.6__py3-none-any.whl → 4.0.0a1__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 +102 -82
- schemathesis/checks.py +126 -46
- schemathesis/cli/__init__.py +11 -1760
- schemathesis/cli/__main__.py +4 -0
- 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 +35 -0
- schemathesis/cli/commands/run/executor.py +138 -0
- schemathesis/cli/commands/run/filters.py +194 -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 +494 -0
- schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
- schemathesis/cli/commands/run/handlers/output.py +746 -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} +103 -174
- schemathesis/cli/constants.py +5 -52
- 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} +39 -10
- 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 -5
- 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 +285 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/{_lazy_import.py → core/lazy_import.py} +1 -0
- schemathesis/core/loaders.py +104 -0
- schemathesis/core/marks.py +66 -0
- schemathesis/{transports/content_types.py → core/media_types.py} +17 -13
- schemathesis/core/output/__init__.py +69 -0
- schemathesis/core/output/sanitization.py +197 -0
- schemathesis/core/rate_limit.py +60 -0
- schemathesis/core/registries.py +31 -0
- schemathesis/{internal → core}/result.py +1 -1
- 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 +337 -0
- schemathesis/engine/phases/__init__.py +66 -0
- schemathesis/{runner → engine/phases}/probes.py +50 -67
- schemathesis/engine/phases/stateful/__init__.py +65 -0
- schemathesis/engine/phases/stateful/_executor.py +326 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +174 -0
- schemathesis/engine/phases/unit/_executor.py +321 -0
- schemathesis/engine/phases/unit/_pool.py +74 -0
- schemathesis/engine/recorder.py +241 -0
- schemathesis/errors.py +31 -0
- schemathesis/experimental/__init__.py +18 -14
- schemathesis/filters.py +103 -14
- schemathesis/generation/__init__.py +21 -37
- schemathesis/generation/case.py +190 -0
- schemathesis/generation/coverage.py +931 -0
- schemathesis/generation/hypothesis/__init__.py +30 -0
- schemathesis/generation/hypothesis/builder.py +585 -0
- schemathesis/generation/hypothesis/examples.py +50 -0
- 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 +68 -81
- schemathesis/generation/targets.py +69 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +115 -0
- schemathesis/graphql/loaders.py +131 -0
- schemathesis/hooks.py +99 -67
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +412 -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} +106 -127
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +537 -261
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/_cache.py +25 -0
- schemathesis/specs/graphql/nodes.py +1 -0
- schemathesis/specs/graphql/scalars.py +7 -5
- schemathesis/specs/graphql/schemas.py +215 -187
- schemathesis/specs/graphql/validation.py +11 -18
- schemathesis/specs/openapi/__init__.py +7 -1
- schemathesis/specs/openapi/_cache.py +122 -0
- schemathesis/specs/openapi/_hypothesis.py +146 -165
- schemathesis/specs/openapi/checks.py +565 -67
- schemathesis/specs/openapi/converter.py +33 -6
- schemathesis/specs/openapi/definitions.py +11 -18
- schemathesis/specs/openapi/examples.py +139 -23
- schemathesis/specs/openapi/expressions/__init__.py +37 -2
- schemathesis/specs/openapi/expressions/context.py +4 -6
- schemathesis/specs/openapi/expressions/extractors.py +23 -0
- schemathesis/specs/openapi/expressions/lexer.py +20 -18
- schemathesis/specs/openapi/expressions/nodes.py +38 -14
- schemathesis/specs/openapi/expressions/parser.py +26 -5
- schemathesis/specs/openapi/formats.py +45 -0
- schemathesis/specs/openapi/links.py +65 -165
- schemathesis/specs/openapi/media_types.py +32 -0
- schemathesis/specs/openapi/negative/__init__.py +7 -3
- schemathesis/specs/openapi/negative/mutations.py +24 -8
- schemathesis/specs/openapi/parameters.py +46 -30
- schemathesis/specs/openapi/patterns.py +137 -0
- schemathesis/specs/openapi/references.py +47 -57
- schemathesis/specs/openapi/schemas.py +478 -369
- schemathesis/specs/openapi/security.py +25 -7
- schemathesis/specs/openapi/serialization.py +11 -6
- schemathesis/specs/openapi/stateful/__init__.py +185 -73
- schemathesis/specs/openapi/utils.py +6 -1
- 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} +143 -28
- schemathesis/transport/wsgi.py +165 -0
- schemathesis-4.0.0a1.dist-info/METADATA +297 -0
- schemathesis-4.0.0a1.dist-info/RECORD +152 -0
- {schemathesis-3.25.6.dist-info → schemathesis-4.0.0a1.dist-info}/WHEEL +1 -1
- {schemathesis-3.25.6.dist-info → schemathesis-4.0.0a1.dist-info}/entry_points.txt +1 -1
- schemathesis/_compat.py +0 -74
- schemathesis/_dependency_versions.py +0 -17
- schemathesis/_hypothesis.py +0 -246
- schemathesis/_override.py +0 -49
- schemathesis/cli/cassettes.py +0 -375
- schemathesis/cli/context.py +0 -58
- schemathesis/cli/debug.py +0 -26
- schemathesis/cli/handlers.py +0 -16
- schemathesis/cli/junitxml.py +0 -43
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -790
- schemathesis/cli/output/short.py +0 -44
- schemathesis/cli/sanitization.py +0 -20
- schemathesis/code_samples.py +0 -149
- schemathesis/constants.py +0 -55
- schemathesis/contrib/openapi/formats/__init__.py +0 -9
- schemathesis/contrib/openapi/formats/uuid.py +0 -15
- schemathesis/contrib/unique_data.py +0 -41
- schemathesis/exceptions.py +0 -560
- schemathesis/extra/_aiohttp.py +0 -27
- schemathesis/extra/_flask.py +0 -10
- schemathesis/extra/_server.py +0 -17
- schemathesis/failures.py +0 -209
- schemathesis/fixups/__init__.py +0 -36
- schemathesis/fixups/fast_api.py +0 -41
- schemathesis/fixups/utf8_bom.py +0 -29
- schemathesis/graphql.py +0 -4
- schemathesis/internal/__init__.py +0 -7
- schemathesis/internal/copy.py +0 -13
- schemathesis/internal/datetime.py +0 -5
- schemathesis/internal/deprecation.py +0 -34
- schemathesis/internal/jsonschema.py +0 -35
- schemathesis/internal/transformation.py +0 -15
- schemathesis/internal/validation.py +0 -34
- schemathesis/lazy.py +0 -361
- schemathesis/loaders.py +0 -120
- schemathesis/models.py +0 -1234
- schemathesis/parameters.py +0 -86
- schemathesis/runner/__init__.py +0 -570
- schemathesis/runner/events.py +0 -329
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/core.py +0 -1035
- schemathesis/runner/impl/solo.py +0 -90
- schemathesis/runner/impl/threadpool.py +0 -400
- schemathesis/runner/serialization.py +0 -411
- schemathesis/sanitization.py +0 -248
- schemathesis/serializers.py +0 -323
- schemathesis/service/__init__.py +0 -18
- schemathesis/service/auth.py +0 -11
- schemathesis/service/ci.py +0 -201
- schemathesis/service/client.py +0 -100
- schemathesis/service/constants.py +0 -38
- schemathesis/service/events.py +0 -57
- schemathesis/service/hosts.py +0 -107
- schemathesis/service/metadata.py +0 -46
- schemathesis/service/models.py +0 -49
- schemathesis/service/report.py +0 -255
- schemathesis/service/serialization.py +0 -199
- schemathesis/service/usage.py +0 -65
- schemathesis/specs/graphql/loaders.py +0 -344
- schemathesis/specs/openapi/filters.py +0 -49
- schemathesis/specs/openapi/loaders.py +0 -667
- schemathesis/specs/openapi/stateful/links.py +0 -92
- schemathesis/specs/openapi/validation.py +0 -25
- schemathesis/stateful/__init__.py +0 -133
- schemathesis/targets.py +0 -45
- schemathesis/throttling.py +0 -41
- schemathesis/transports/__init__.py +0 -5
- schemathesis/transports/auth.py +0 -15
- schemathesis/transports/headers.py +0 -35
- schemathesis/transports/responses.py +0 -52
- schemathesis/types.py +0 -35
- schemathesis/utils.py +0 -169
- schemathesis-3.25.6.dist-info/METADATA +0 -356
- schemathesis-3.25.6.dist-info/RECORD +0 -134
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
- {schemathesis-3.25.6.dist-info → schemathesis-4.0.0a1.dist-info}/licenses/LICENSE +0 -0
@@ -1,11 +1,12 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
2
|
|
3
|
+
from typing import Any, List, cast
|
3
4
|
|
4
|
-
from
|
5
|
-
from
|
5
|
+
from schemathesis.generation.case import Case
|
6
|
+
from schemathesis.graphql.checks import GraphQLClientError, GraphQLServerError, UnexpectedGraphQLResponse
|
6
7
|
|
7
8
|
|
8
|
-
def validate_graphql_response(payload: Any) -> None:
|
9
|
+
def validate_graphql_response(case: Case, payload: Any) -> None:
|
9
10
|
"""Validate GraphQL response.
|
10
11
|
|
11
12
|
Semantically valid GraphQL responses are JSON objects and may contain `data` or `errors` keys.
|
@@ -13,28 +14,20 @@ def validate_graphql_response(payload: Any) -> None:
|
|
13
14
|
from graphql.error import GraphQLFormattedError
|
14
15
|
|
15
16
|
if not isinstance(payload, dict):
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
raise UnexpectedGraphQLResponse(
|
18
|
+
operation=case.operation.label,
|
19
|
+
message="GraphQL response is not a JSON object",
|
20
|
+
type_name=str(type(payload)),
|
20
21
|
)
|
21
22
|
|
22
23
|
errors = cast(List[GraphQLFormattedError], payload.get("errors"))
|
23
24
|
if errors is not None and len(errors) > 0:
|
24
|
-
exc_class = get_grouped_graphql_error(errors)
|
25
25
|
data = payload.get("data")
|
26
26
|
# There is no `path` pointing to some part of the input query, assuming client error
|
27
27
|
if data is None and "path" not in errors[0]:
|
28
|
-
|
29
|
-
raise exc_class(
|
30
|
-
failures.GraphQLClientError.title,
|
31
|
-
context=failures.GraphQLClientError(message=message, errors=errors),
|
32
|
-
)
|
28
|
+
raise GraphQLClientError(operation=case.operation.label, message=errors[0]["message"], errors=errors)
|
33
29
|
if len(errors) > 1:
|
34
30
|
message = "\n\n".join([f"{idx}. {error['message']}" for idx, error in enumerate(errors, 1)])
|
35
31
|
else:
|
36
32
|
message = errors[0]["message"]
|
37
|
-
raise
|
38
|
-
failures.GraphQLServerError.title,
|
39
|
-
context=failures.GraphQLServerError(message=message, errors=errors),
|
40
|
-
)
|
33
|
+
raise GraphQLServerError(operation=case.operation.label, message=message, errors=errors)
|
@@ -1,3 +1,9 @@
|
|
1
1
|
from .formats import register_string_format as format
|
2
2
|
from .formats import unregister_string_format
|
3
|
-
from .
|
3
|
+
from .media_types import register_media_type as media_type
|
4
|
+
|
5
|
+
__all__ = [
|
6
|
+
"format",
|
7
|
+
"unregister_string_format",
|
8
|
+
"media_type",
|
9
|
+
]
|
@@ -0,0 +1,122 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass, field
|
4
|
+
from typing import TYPE_CHECKING, Any, Tuple
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from ...schemas import APIOperation, APIOperationMap
|
8
|
+
|
9
|
+
|
10
|
+
@dataclass
|
11
|
+
class OperationCacheEntry:
|
12
|
+
path: str
|
13
|
+
method: str
|
14
|
+
# The resolution scope of the operation
|
15
|
+
scope: str
|
16
|
+
# Parent path item
|
17
|
+
path_item: dict[str, Any]
|
18
|
+
# Unresolved operation definition
|
19
|
+
operation: dict[str, Any]
|
20
|
+
__slots__ = ("path", "method", "scope", "path_item", "operation")
|
21
|
+
|
22
|
+
|
23
|
+
# During traversal, we need to keep track of the scope, path, and method
|
24
|
+
TraversalKey = Tuple[str, str, str]
|
25
|
+
OperationId = str
|
26
|
+
Reference = str
|
27
|
+
|
28
|
+
|
29
|
+
@dataclass
|
30
|
+
class OperationCache:
|
31
|
+
"""Cache for Open API operations.
|
32
|
+
|
33
|
+
This cache contains multiple levels to avoid unnecessary parsing of the schema.
|
34
|
+
"""
|
35
|
+
|
36
|
+
# Cache to avoid schema traversal on every access
|
37
|
+
_id_to_definition: dict[OperationId, OperationCacheEntry] = field(default_factory=dict)
|
38
|
+
# Map map between 1st & 2nd level cache keys
|
39
|
+
# Even though 1st level keys could be directly mapped to Python objects in memory, we need to keep them separate
|
40
|
+
# to ensure a single owner of the operation instance.
|
41
|
+
_id_to_operation: dict[OperationId, int] = field(default_factory=dict)
|
42
|
+
_traversal_key_to_operation: dict[TraversalKey, int] = field(default_factory=dict)
|
43
|
+
_reference_to_operation: dict[Reference, int] = field(default_factory=dict)
|
44
|
+
# The actual operations
|
45
|
+
_operations: list[APIOperation] = field(default_factory=list)
|
46
|
+
# Cache for operation maps
|
47
|
+
_maps: dict[str, APIOperationMap] = field(default_factory=dict)
|
48
|
+
|
49
|
+
@property
|
50
|
+
def known_operation_ids(self) -> list[str]:
|
51
|
+
return list(self._id_to_definition)
|
52
|
+
|
53
|
+
@property
|
54
|
+
def has_ids_to_definitions(self) -> bool:
|
55
|
+
return bool(self._id_to_definition)
|
56
|
+
|
57
|
+
def _append_operation(self, operation: APIOperation) -> int:
|
58
|
+
idx = len(self._operations)
|
59
|
+
self._operations.append(operation)
|
60
|
+
return idx
|
61
|
+
|
62
|
+
def insert_definition_by_id(
|
63
|
+
self,
|
64
|
+
operation_id: str,
|
65
|
+
path: str,
|
66
|
+
method: str,
|
67
|
+
scope: str,
|
68
|
+
path_item: dict[str, Any],
|
69
|
+
operation: dict[str, Any],
|
70
|
+
) -> None:
|
71
|
+
"""Insert a new operation definition into cache."""
|
72
|
+
self._id_to_definition[operation_id] = OperationCacheEntry(
|
73
|
+
path=path, method=method, scope=scope, path_item=path_item, operation=operation
|
74
|
+
)
|
75
|
+
|
76
|
+
def get_definition_by_id(self, operation_id: str) -> OperationCacheEntry:
|
77
|
+
"""Get an operation definition by its ID."""
|
78
|
+
# TODO: Avoid KeyError in the future
|
79
|
+
return self._id_to_definition[operation_id]
|
80
|
+
|
81
|
+
def insert_operation(
|
82
|
+
self,
|
83
|
+
operation: APIOperation,
|
84
|
+
*,
|
85
|
+
traversal_key: TraversalKey,
|
86
|
+
operation_id: str | None = None,
|
87
|
+
reference: str | None = None,
|
88
|
+
) -> None:
|
89
|
+
"""Insert a new operation into cache by one or multiple keys."""
|
90
|
+
idx = self._append_operation(operation)
|
91
|
+
self._traversal_key_to_operation[traversal_key] = idx
|
92
|
+
if operation_id is not None:
|
93
|
+
self._id_to_operation[operation_id] = idx
|
94
|
+
if reference is not None:
|
95
|
+
self._reference_to_operation[reference] = idx
|
96
|
+
|
97
|
+
def get_operation_by_id(self, operation_id: str) -> APIOperation | None:
|
98
|
+
"""Get an operation by its ID."""
|
99
|
+
idx = self._id_to_operation.get(operation_id)
|
100
|
+
if idx is not None:
|
101
|
+
return self._operations[idx]
|
102
|
+
return None
|
103
|
+
|
104
|
+
def get_operation_by_reference(self, reference: str) -> APIOperation | None:
|
105
|
+
"""Get an operation by its reference."""
|
106
|
+
idx = self._reference_to_operation.get(reference)
|
107
|
+
if idx is not None:
|
108
|
+
return self._operations[idx]
|
109
|
+
return None
|
110
|
+
|
111
|
+
def get_operation_by_traversal_key(self, key: TraversalKey) -> APIOperation | None:
|
112
|
+
"""Get an operation by its traverse key."""
|
113
|
+
idx = self._traversal_key_to_operation.get(key)
|
114
|
+
if idx is not None:
|
115
|
+
return self._operations[idx]
|
116
|
+
return None
|
117
|
+
|
118
|
+
def get_map(self, key: str) -> APIOperationMap | None:
|
119
|
+
return self._maps.get(key)
|
120
|
+
|
121
|
+
def insert_map(self, key: str, value: APIOperationMap) -> None:
|
122
|
+
self._maps[key] = value
|