schemathesis 3.13.0__py3-none-any.whl → 4.4.2__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 +53 -25
- schemathesis/auths.py +507 -0
- schemathesis/checks.py +190 -25
- schemathesis/cli/__init__.py +27 -1016
- schemathesis/cli/__main__.py +4 -0
- schemathesis/cli/commands/__init__.py +133 -0
- schemathesis/cli/commands/data.py +10 -0
- schemathesis/cli/commands/run/__init__.py +602 -0
- schemathesis/cli/commands/run/context.py +228 -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 +45 -0
- schemathesis/cli/commands/run/handlers/cassettes.py +464 -0
- schemathesis/cli/commands/run/handlers/junitxml.py +60 -0
- schemathesis/cli/commands/run/handlers/output.py +1750 -0
- schemathesis/cli/commands/run/loaders.py +118 -0
- schemathesis/cli/commands/run/validation.py +256 -0
- schemathesis/cli/constants.py +5 -0
- schemathesis/cli/core.py +19 -0
- schemathesis/cli/ext/fs.py +16 -0
- schemathesis/cli/ext/groups.py +203 -0
- schemathesis/cli/ext/options.py +81 -0
- schemathesis/config/__init__.py +202 -0
- schemathesis/config/_auth.py +51 -0
- schemathesis/config/_checks.py +268 -0
- schemathesis/config/_diff_base.py +101 -0
- schemathesis/config/_env.py +21 -0
- schemathesis/config/_error.py +163 -0
- schemathesis/config/_generation.py +157 -0
- schemathesis/config/_health_check.py +24 -0
- schemathesis/config/_operations.py +335 -0
- schemathesis/config/_output.py +171 -0
- schemathesis/config/_parameters.py +19 -0
- schemathesis/config/_phases.py +253 -0
- schemathesis/config/_projects.py +543 -0
- schemathesis/config/_rate_limit.py +17 -0
- schemathesis/config/_report.py +120 -0
- schemathesis/config/_validator.py +9 -0
- schemathesis/config/_warnings.py +89 -0
- schemathesis/config/schema.json +975 -0
- schemathesis/core/__init__.py +72 -0
- schemathesis/core/adapter.py +34 -0
- schemathesis/core/compat.py +32 -0
- schemathesis/core/control.py +2 -0
- schemathesis/core/curl.py +100 -0
- schemathesis/core/deserialization.py +210 -0
- schemathesis/core/errors.py +588 -0
- schemathesis/core/failures.py +316 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/core/hooks.py +20 -0
- schemathesis/core/jsonschema/__init__.py +13 -0
- schemathesis/core/jsonschema/bundler.py +183 -0
- schemathesis/core/jsonschema/keywords.py +40 -0
- schemathesis/core/jsonschema/references.py +222 -0
- schemathesis/core/jsonschema/types.py +41 -0
- schemathesis/core/lazy_import.py +15 -0
- schemathesis/core/loaders.py +107 -0
- schemathesis/core/marks.py +66 -0
- schemathesis/core/media_types.py +79 -0
- schemathesis/core/output/__init__.py +46 -0
- schemathesis/core/output/sanitization.py +54 -0
- schemathesis/core/parameters.py +45 -0
- schemathesis/core/rate_limit.py +60 -0
- schemathesis/core/registries.py +34 -0
- schemathesis/core/result.py +27 -0
- schemathesis/core/schema_analysis.py +17 -0
- schemathesis/core/shell.py +203 -0
- schemathesis/core/transforms.py +144 -0
- schemathesis/core/transport.py +223 -0
- schemathesis/core/validation.py +73 -0
- schemathesis/core/version.py +7 -0
- schemathesis/engine/__init__.py +28 -0
- schemathesis/engine/context.py +152 -0
- schemathesis/engine/control.py +44 -0
- schemathesis/engine/core.py +201 -0
- schemathesis/engine/errors.py +446 -0
- schemathesis/engine/events.py +284 -0
- schemathesis/engine/observations.py +42 -0
- schemathesis/engine/phases/__init__.py +108 -0
- schemathesis/engine/phases/analysis.py +28 -0
- schemathesis/engine/phases/probes.py +172 -0
- schemathesis/engine/phases/stateful/__init__.py +68 -0
- schemathesis/engine/phases/stateful/_executor.py +364 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +220 -0
- schemathesis/engine/phases/unit/_executor.py +459 -0
- schemathesis/engine/phases/unit/_pool.py +82 -0
- schemathesis/engine/recorder.py +254 -0
- schemathesis/errors.py +47 -0
- schemathesis/filters.py +395 -0
- schemathesis/generation/__init__.py +25 -0
- schemathesis/generation/case.py +478 -0
- schemathesis/generation/coverage.py +1528 -0
- schemathesis/generation/hypothesis/__init__.py +121 -0
- schemathesis/generation/hypothesis/builder.py +992 -0
- schemathesis/generation/hypothesis/examples.py +56 -0
- schemathesis/generation/hypothesis/given.py +66 -0
- schemathesis/generation/hypothesis/reporting.py +285 -0
- schemathesis/generation/meta.py +227 -0
- schemathesis/generation/metrics.py +93 -0
- schemathesis/generation/modes.py +20 -0
- schemathesis/generation/overrides.py +127 -0
- schemathesis/generation/stateful/__init__.py +37 -0
- schemathesis/generation/stateful/state_machine.py +294 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +109 -0
- schemathesis/graphql/loaders.py +285 -0
- schemathesis/hooks.py +270 -91
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +467 -0
- schemathesis/openapi/generation/__init__.py +0 -0
- schemathesis/openapi/generation/filters.py +72 -0
- schemathesis/openapi/loaders.py +315 -0
- schemathesis/pytest/__init__.py +5 -0
- schemathesis/pytest/control_flow.py +7 -0
- schemathesis/pytest/lazy.py +341 -0
- schemathesis/pytest/loaders.py +36 -0
- schemathesis/pytest/plugin.py +357 -0
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +683 -247
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/nodes.py +27 -0
- schemathesis/specs/graphql/scalars.py +86 -0
- schemathesis/specs/graphql/schemas.py +395 -123
- schemathesis/specs/graphql/validation.py +33 -0
- schemathesis/specs/openapi/__init__.py +9 -1
- schemathesis/specs/openapi/_hypothesis.py +578 -317
- schemathesis/specs/openapi/adapter/__init__.py +10 -0
- schemathesis/specs/openapi/adapter/parameters.py +729 -0
- schemathesis/specs/openapi/adapter/protocol.py +59 -0
- schemathesis/specs/openapi/adapter/references.py +19 -0
- schemathesis/specs/openapi/adapter/responses.py +368 -0
- schemathesis/specs/openapi/adapter/security.py +144 -0
- schemathesis/specs/openapi/adapter/v2.py +30 -0
- schemathesis/specs/openapi/adapter/v3_0.py +30 -0
- schemathesis/specs/openapi/adapter/v3_1.py +30 -0
- schemathesis/specs/openapi/analysis.py +96 -0
- schemathesis/specs/openapi/checks.py +753 -74
- schemathesis/specs/openapi/converter.py +176 -37
- schemathesis/specs/openapi/definitions.py +599 -4
- schemathesis/specs/openapi/examples.py +581 -165
- schemathesis/specs/openapi/expressions/__init__.py +52 -5
- schemathesis/specs/openapi/expressions/extractors.py +25 -0
- schemathesis/specs/openapi/expressions/lexer.py +34 -31
- schemathesis/specs/openapi/expressions/nodes.py +97 -46
- schemathesis/specs/openapi/expressions/parser.py +35 -13
- schemathesis/specs/openapi/formats.py +122 -0
- schemathesis/specs/openapi/media_types.py +75 -0
- schemathesis/specs/openapi/negative/__init__.py +117 -68
- schemathesis/specs/openapi/negative/mutations.py +294 -104
- schemathesis/specs/openapi/negative/utils.py +3 -6
- schemathesis/specs/openapi/patterns.py +458 -0
- schemathesis/specs/openapi/references.py +60 -81
- schemathesis/specs/openapi/schemas.py +648 -650
- schemathesis/specs/openapi/serialization.py +53 -30
- schemathesis/specs/openapi/stateful/__init__.py +404 -69
- schemathesis/specs/openapi/stateful/control.py +87 -0
- schemathesis/specs/openapi/stateful/dependencies/__init__.py +232 -0
- schemathesis/specs/openapi/stateful/dependencies/inputs.py +428 -0
- schemathesis/specs/openapi/stateful/dependencies/models.py +341 -0
- schemathesis/specs/openapi/stateful/dependencies/naming.py +491 -0
- schemathesis/specs/openapi/stateful/dependencies/outputs.py +34 -0
- schemathesis/specs/openapi/stateful/dependencies/resources.py +339 -0
- schemathesis/specs/openapi/stateful/dependencies/schemas.py +447 -0
- schemathesis/specs/openapi/stateful/inference.py +254 -0
- schemathesis/specs/openapi/stateful/links.py +219 -78
- schemathesis/specs/openapi/types/__init__.py +3 -0
- schemathesis/specs/openapi/types/common.py +23 -0
- schemathesis/specs/openapi/types/v2.py +129 -0
- schemathesis/specs/openapi/types/v3.py +134 -0
- schemathesis/specs/openapi/utils.py +7 -6
- schemathesis/specs/openapi/warnings.py +75 -0
- schemathesis/transport/__init__.py +224 -0
- schemathesis/transport/asgi.py +26 -0
- schemathesis/transport/prepare.py +126 -0
- schemathesis/transport/requests.py +278 -0
- schemathesis/transport/serialization.py +329 -0
- schemathesis/transport/wsgi.py +175 -0
- schemathesis-4.4.2.dist-info/METADATA +213 -0
- schemathesis-4.4.2.dist-info/RECORD +192 -0
- {schemathesis-3.13.0.dist-info → schemathesis-4.4.2.dist-info}/WHEEL +1 -1
- schemathesis-4.4.2.dist-info/entry_points.txt +6 -0
- {schemathesis-3.13.0.dist-info → schemathesis-4.4.2.dist-info/licenses}/LICENSE +1 -1
- schemathesis/_compat.py +0 -41
- schemathesis/_hypothesis.py +0 -115
- schemathesis/cli/callbacks.py +0 -188
- schemathesis/cli/cassettes.py +0 -253
- schemathesis/cli/context.py +0 -36
- schemathesis/cli/debug.py +0 -21
- schemathesis/cli/handlers.py +0 -11
- schemathesis/cli/junitxml.py +0 -41
- schemathesis/cli/options.py +0 -51
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -508
- schemathesis/cli/output/short.py +0 -40
- schemathesis/constants.py +0 -79
- schemathesis/exceptions.py +0 -207
- schemathesis/extra/_aiohttp.py +0 -27
- schemathesis/extra/_flask.py +0 -10
- schemathesis/extra/_server.py +0 -16
- schemathesis/extra/pytest_plugin.py +0 -216
- schemathesis/failures.py +0 -131
- schemathesis/fixups/__init__.py +0 -29
- schemathesis/fixups/fast_api.py +0 -30
- schemathesis/lazy.py +0 -227
- schemathesis/models.py +0 -1041
- schemathesis/parameters.py +0 -88
- schemathesis/runner/__init__.py +0 -460
- schemathesis/runner/events.py +0 -240
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/core.py +0 -755
- schemathesis/runner/impl/solo.py +0 -85
- schemathesis/runner/impl/threadpool.py +0 -367
- schemathesis/runner/serialization.py +0 -189
- schemathesis/serializers.py +0 -233
- schemathesis/service/__init__.py +0 -3
- schemathesis/service/client.py +0 -46
- schemathesis/service/constants.py +0 -12
- schemathesis/service/events.py +0 -39
- schemathesis/service/handler.py +0 -39
- schemathesis/service/models.py +0 -7
- schemathesis/service/serialization.py +0 -153
- schemathesis/service/worker.py +0 -40
- schemathesis/specs/graphql/loaders.py +0 -215
- schemathesis/specs/openapi/constants.py +0 -7
- schemathesis/specs/openapi/expressions/context.py +0 -12
- schemathesis/specs/openapi/expressions/pointers.py +0 -29
- schemathesis/specs/openapi/filters.py +0 -44
- schemathesis/specs/openapi/links.py +0 -302
- schemathesis/specs/openapi/loaders.py +0 -453
- schemathesis/specs/openapi/parameters.py +0 -413
- schemathesis/specs/openapi/security.py +0 -129
- schemathesis/specs/openapi/validation.py +0 -24
- schemathesis/stateful.py +0 -349
- schemathesis/targets.py +0 -32
- schemathesis/types.py +0 -38
- schemathesis/utils.py +0 -436
- schemathesis-3.13.0.dist-info/METADATA +0 -202
- schemathesis-3.13.0.dist-info/RECORD +0 -91
- schemathesis-3.13.0.dist-info/entry_points.txt +0 -6
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Collection
|
|
4
|
+
|
|
5
|
+
from schemathesis.transport import SerializationContext
|
|
6
|
+
from schemathesis.transport.asgi import ASGI_TRANSPORT
|
|
7
|
+
from schemathesis.transport.requests import REQUESTS_TRANSPORT
|
|
8
|
+
from schemathesis.transport.wsgi import WSGI_TRANSPORT
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from hypothesis import strategies as st
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
MEDIA_TYPES: dict[str, st.SearchStrategy[bytes]] = {}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def register_media_type(name: str, strategy: st.SearchStrategy[bytes], *, aliases: Collection[str] = ()) -> None:
|
|
18
|
+
r"""Register a custom Hypothesis strategy for generating media type content.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
name: Media type name that matches your OpenAPI requestBody content type
|
|
22
|
+
strategy: Hypothesis strategy that generates bytes for this media type
|
|
23
|
+
aliases: Additional media type names that use the same strategy
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
```python
|
|
27
|
+
import schemathesis
|
|
28
|
+
from hypothesis import strategies as st
|
|
29
|
+
|
|
30
|
+
# Register PDF file strategy
|
|
31
|
+
pdf_strategy = st.sampled_from([
|
|
32
|
+
b"%PDF-1.4\n1 0 obj\n<<\n/Type /Catalog\n>>\nendobj\n%%EOF",
|
|
33
|
+
b"%PDF-1.5\n%\xe2\xe3\xcf\xd3\n1 0 obj\n<<\n/Type /Catalog\n>>\nendobj\n%%EOF"
|
|
34
|
+
])
|
|
35
|
+
schemathesis.openapi.media_type("application/pdf", pdf_strategy)
|
|
36
|
+
|
|
37
|
+
# Dynamic content generation
|
|
38
|
+
@st.composite
|
|
39
|
+
def xml_content(draw):
|
|
40
|
+
tag = draw(st.text(min_size=3, max_size=10))
|
|
41
|
+
content = draw(st.text(min_size=1, max_size=50))
|
|
42
|
+
return f"<?xml version='1.0'?><{tag}>{content}</{tag}>".encode()
|
|
43
|
+
|
|
44
|
+
schemathesis.openapi.media_type("application/xml", xml_content())
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Schema usage:
|
|
48
|
+
```yaml
|
|
49
|
+
requestBody:
|
|
50
|
+
content:
|
|
51
|
+
application/pdf: # Uses your PDF strategy
|
|
52
|
+
schema:
|
|
53
|
+
type: string
|
|
54
|
+
format: binary
|
|
55
|
+
application/xml: # Uses your XML strategy
|
|
56
|
+
schema:
|
|
57
|
+
type: string
|
|
58
|
+
format: binary
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
@REQUESTS_TRANSPORT.serializer(name, *aliases)
|
|
64
|
+
@ASGI_TRANSPORT.serializer(name, *aliases)
|
|
65
|
+
@WSGI_TRANSPORT.serializer(name, *aliases)
|
|
66
|
+
def serialize(ctx: SerializationContext, value: Any) -> dict[str, Any]:
|
|
67
|
+
return {"data": value}
|
|
68
|
+
|
|
69
|
+
MEDIA_TYPES[name] = strategy
|
|
70
|
+
for alias in aliases:
|
|
71
|
+
MEDIA_TYPES[alias] = strategy
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def unregister_all() -> None:
|
|
75
|
+
MEDIA_TYPES.clear()
|
|
@@ -1,85 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
1
4
|
from functools import lru_cache
|
|
2
|
-
from typing import
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
from urllib.parse import urlencode
|
|
3
7
|
|
|
4
|
-
import attr
|
|
5
8
|
import jsonschema
|
|
6
9
|
from hypothesis import strategies as st
|
|
7
10
|
from hypothesis_jsonschema import from_schema
|
|
8
11
|
|
|
9
|
-
from .
|
|
10
|
-
from .
|
|
12
|
+
from schemathesis.config import GenerationConfig
|
|
13
|
+
from schemathesis.core.jsonschema import ALL_KEYWORDS
|
|
14
|
+
from schemathesis.core.jsonschema.types import JsonSchema
|
|
15
|
+
from schemathesis.core.parameters import ParameterLocation
|
|
16
|
+
|
|
17
|
+
from .mutations import MutationContext, MutationMetadata
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from .types import Draw, Schema
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class GeneratedValue:
|
|
25
|
+
"""Wrapper for generated values with optional mutation metadata.
|
|
26
|
+
|
|
27
|
+
This allows us to pass both the value and metadata through the generation pipeline
|
|
28
|
+
without using tuples, making the code cleaner and type-safe.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
value: Any
|
|
32
|
+
meta: MutationMetadata | None
|
|
11
33
|
|
|
34
|
+
__slots__ = ("value", "meta")
|
|
12
35
|
|
|
13
|
-
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
14
38
|
class CacheKey:
|
|
15
39
|
"""A cache key for API Operation / location.
|
|
16
40
|
|
|
17
41
|
Carries the schema around but don't use it for hashing to simplify LRU cache usage.
|
|
18
42
|
"""
|
|
19
43
|
|
|
20
|
-
operation_name: str
|
|
21
|
-
location: str
|
|
22
|
-
schema:
|
|
44
|
+
operation_name: str
|
|
45
|
+
location: str
|
|
46
|
+
schema: JsonSchema
|
|
47
|
+
validator_cls: type[jsonschema.Validator]
|
|
48
|
+
|
|
49
|
+
__slots__ = ("operation_name", "location", "schema", "validator_cls")
|
|
23
50
|
|
|
24
51
|
def __hash__(self) -> int:
|
|
25
52
|
return hash((self.operation_name, self.location))
|
|
26
53
|
|
|
27
54
|
|
|
28
|
-
@lru_cache
|
|
29
|
-
def get_validator(cache_key: CacheKey) -> jsonschema.
|
|
55
|
+
@lru_cache
|
|
56
|
+
def get_validator(cache_key: CacheKey) -> jsonschema.Validator:
|
|
30
57
|
"""Get JSON Schema validator for the given schema."""
|
|
31
58
|
# Each operation / location combo has only a single schema, therefore could be cached
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"additionalProperties",
|
|
38
|
-
"allOf",
|
|
39
|
-
"anyOf",
|
|
40
|
-
"const",
|
|
41
|
-
"contains",
|
|
42
|
-
"contentEncoding",
|
|
43
|
-
"contentMediaType",
|
|
44
|
-
"dependencies",
|
|
45
|
-
"enum",
|
|
46
|
-
"else",
|
|
47
|
-
"exclusiveMaximum",
|
|
48
|
-
"exclusiveMinimum",
|
|
49
|
-
"format",
|
|
50
|
-
"if",
|
|
51
|
-
"items",
|
|
52
|
-
"maxItems",
|
|
53
|
-
"maxLength",
|
|
54
|
-
"maxProperties",
|
|
55
|
-
"maximum",
|
|
56
|
-
"minItems",
|
|
57
|
-
"minLength",
|
|
58
|
-
"minProperties",
|
|
59
|
-
"minimum",
|
|
60
|
-
"multipleOf",
|
|
61
|
-
"not",
|
|
62
|
-
"oneOf",
|
|
63
|
-
"pattern",
|
|
64
|
-
"patternProperties",
|
|
65
|
-
"properties",
|
|
66
|
-
"propertyNames",
|
|
67
|
-
"$ref",
|
|
68
|
-
"required",
|
|
69
|
-
"then",
|
|
70
|
-
"type",
|
|
71
|
-
"uniqueItems",
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
@lru_cache()
|
|
76
|
-
def split_schema(cache_key: CacheKey) -> Tuple[Schema, Schema]:
|
|
59
|
+
return cache_key.validator_cls(cache_key.schema)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@lru_cache
|
|
63
|
+
def split_schema(cache_key: CacheKey) -> tuple[Schema, Schema]:
|
|
77
64
|
"""Split the schema in two parts.
|
|
78
65
|
|
|
79
66
|
The first one contains only validation JSON Schema keywords, the second one everything else.
|
|
80
67
|
"""
|
|
81
68
|
keywords, non_keywords = {}, {}
|
|
82
|
-
|
|
69
|
+
schema = {} if isinstance(cache_key.schema, bool) else cache_key.schema
|
|
70
|
+
for keyword, value in schema.items():
|
|
83
71
|
if keyword in ALL_KEYWORDS:
|
|
84
72
|
keywords[keyword] = value
|
|
85
73
|
else:
|
|
@@ -88,29 +76,90 @@ def split_schema(cache_key: CacheKey) -> Tuple[Schema, Schema]:
|
|
|
88
76
|
|
|
89
77
|
|
|
90
78
|
def negative_schema(
|
|
91
|
-
schema:
|
|
79
|
+
schema: JsonSchema,
|
|
92
80
|
operation_name: str,
|
|
93
|
-
location:
|
|
94
|
-
media_type:
|
|
81
|
+
location: ParameterLocation,
|
|
82
|
+
media_type: str | None,
|
|
83
|
+
generation_config: GenerationConfig,
|
|
95
84
|
*,
|
|
96
|
-
custom_formats:
|
|
85
|
+
custom_formats: dict[str, st.SearchStrategy[str]],
|
|
86
|
+
validator_cls: type[jsonschema.Validator],
|
|
97
87
|
) -> st.SearchStrategy:
|
|
98
88
|
"""A strategy for instances that DO NOT match the input schema.
|
|
99
89
|
|
|
100
90
|
It is used to cover the input space that is not possible to cover with the "positive" strategy.
|
|
91
|
+
|
|
92
|
+
Returns a strategy that produces GeneratedValue instances with mutation metadata.
|
|
101
93
|
"""
|
|
102
94
|
# The mutated schema is passed to `from_schema` and guarded against producing instances valid against
|
|
103
95
|
# the original schema.
|
|
104
|
-
cache_key = CacheKey(operation_name, location, schema)
|
|
96
|
+
cache_key = CacheKey(operation_name, location, schema, validator_cls)
|
|
105
97
|
validator = get_validator(cache_key)
|
|
106
98
|
keywords, non_keywords = split_schema(cache_key)
|
|
107
|
-
return mutated(keywords, non_keywords, location, media_type).flatmap(
|
|
108
|
-
lambda s: from_schema(s, custom_formats=custom_formats).filter(lambda v: not validator.is_valid(v))
|
|
109
|
-
)
|
|
110
|
-
|
|
111
99
|
|
|
112
|
-
|
|
113
|
-
|
|
100
|
+
if location == ParameterLocation.QUERY:
|
|
101
|
+
|
|
102
|
+
def filter_values(value: dict[str, Any]) -> bool:
|
|
103
|
+
return is_non_empty_query(value) and not validator.is_valid(value)
|
|
104
|
+
|
|
105
|
+
else:
|
|
106
|
+
|
|
107
|
+
def filter_values(value: dict[str, Any]) -> bool:
|
|
108
|
+
return not validator.is_valid(value)
|
|
109
|
+
|
|
110
|
+
def generate_value_with_metadata(value: tuple[dict, MutationMetadata]) -> st.SearchStrategy:
|
|
111
|
+
schema, metadata = value
|
|
112
|
+
return (
|
|
113
|
+
from_schema(
|
|
114
|
+
schema,
|
|
115
|
+
custom_formats=custom_formats,
|
|
116
|
+
allow_x00=generation_config.allow_x00,
|
|
117
|
+
codec=generation_config.codec,
|
|
118
|
+
)
|
|
119
|
+
.filter(filter_values)
|
|
120
|
+
.map(lambda value: GeneratedValue(value, metadata))
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return mutated(
|
|
124
|
+
keywords=keywords,
|
|
125
|
+
non_keywords=non_keywords,
|
|
126
|
+
location=location,
|
|
127
|
+
media_type=media_type,
|
|
128
|
+
allow_extra_parameters=generation_config.allow_extra_parameters,
|
|
129
|
+
).flatmap(generate_value_with_metadata)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def is_non_empty_query(query: dict[str, Any]) -> bool:
|
|
133
|
+
# Whether this query parameters will be encoded to a non-empty query string
|
|
134
|
+
result = []
|
|
135
|
+
for key, values in query.items():
|
|
136
|
+
if isinstance(values, str) or not hasattr(values, "__iter__"):
|
|
137
|
+
values = [values]
|
|
138
|
+
for value in values:
|
|
139
|
+
if value is not None:
|
|
140
|
+
result.append(
|
|
141
|
+
(
|
|
142
|
+
key.encode("utf-8") if isinstance(key, str) else key,
|
|
143
|
+
value.encode("utf-8") if isinstance(value, str) else value,
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
return urlencode(result, doseq=True) != ""
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@st.composite # type: ignore[misc]
|
|
150
|
+
def mutated(
|
|
151
|
+
draw: Draw,
|
|
152
|
+
*,
|
|
153
|
+
keywords: Schema,
|
|
154
|
+
non_keywords: Schema,
|
|
155
|
+
location: ParameterLocation,
|
|
156
|
+
media_type: str | None,
|
|
157
|
+
allow_extra_parameters: bool,
|
|
158
|
+
) -> Any:
|
|
114
159
|
return MutationContext(
|
|
115
|
-
keywords=keywords,
|
|
160
|
+
keywords=keywords,
|
|
161
|
+
non_keywords=non_keywords,
|
|
162
|
+
location=location,
|
|
163
|
+
media_type=media_type,
|
|
164
|
+
allow_extra_parameters=allow_extra_parameters,
|
|
116
165
|
).mutate(draw)
|