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
|
@@ -1,413 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from typing import Any, ClassVar, Dict, Iterable, List, Optional, Tuple
|
|
3
|
-
|
|
4
|
-
import attr
|
|
5
|
-
|
|
6
|
-
from ...parameters import Parameter
|
|
7
|
-
from .converter import to_json_schema_recursive
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@attr.s(slots=True, eq=False)
|
|
11
|
-
class OpenAPIParameter(Parameter):
|
|
12
|
-
"""A single Open API operation parameter."""
|
|
13
|
-
|
|
14
|
-
example_field: ClassVar[str]
|
|
15
|
-
examples_field: ClassVar[str]
|
|
16
|
-
nullable_field: ClassVar[str]
|
|
17
|
-
supported_jsonschema_keywords: ClassVar[Tuple[str, ...]]
|
|
18
|
-
|
|
19
|
-
@property
|
|
20
|
-
def description(self) -> Optional[str]:
|
|
21
|
-
"""A brief parameter description."""
|
|
22
|
-
return self.definition.get("description")
|
|
23
|
-
|
|
24
|
-
@property
|
|
25
|
-
def example(self) -> Any:
|
|
26
|
-
"""The primary example defined for this parameter."""
|
|
27
|
-
if self._example:
|
|
28
|
-
return self._example
|
|
29
|
-
if self._schema_example:
|
|
30
|
-
# It is processed only if there are no `example` / `examples` in the root, overridden otherwise
|
|
31
|
-
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-10
|
|
32
|
-
# We mimic this behavior for Open API 2.0
|
|
33
|
-
return self._schema_example
|
|
34
|
-
|
|
35
|
-
@property
|
|
36
|
-
def location(self) -> str:
|
|
37
|
-
"""Where this parameter is located.
|
|
38
|
-
|
|
39
|
-
E.g. "query".
|
|
40
|
-
"""
|
|
41
|
-
return {"formData": "body"}.get(self.raw_location, self.raw_location)
|
|
42
|
-
|
|
43
|
-
@property
|
|
44
|
-
def raw_location(self) -> str:
|
|
45
|
-
"""Open API specific location name."""
|
|
46
|
-
return self.definition["in"]
|
|
47
|
-
|
|
48
|
-
@property
|
|
49
|
-
def name(self) -> str:
|
|
50
|
-
"""Parameter name."""
|
|
51
|
-
return self.definition["name"]
|
|
52
|
-
|
|
53
|
-
@property
|
|
54
|
-
def is_required(self) -> bool:
|
|
55
|
-
return self.definition.get("required", False)
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
def is_header(self) -> bool:
|
|
59
|
-
raise NotImplementedError
|
|
60
|
-
|
|
61
|
-
@property
|
|
62
|
-
def _example(self) -> Any:
|
|
63
|
-
"""A not-named example, defined in the parameter root.
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
"in": "query",
|
|
67
|
-
"name": "key",
|
|
68
|
-
"type": "string"
|
|
69
|
-
"example": "foo", # This one
|
|
70
|
-
}
|
|
71
|
-
"""
|
|
72
|
-
return self.definition.get(self.example_field)
|
|
73
|
-
|
|
74
|
-
@property
|
|
75
|
-
def _schema_example(self) -> Any:
|
|
76
|
-
"""Example defined on the schema-level.
|
|
77
|
-
|
|
78
|
-
{
|
|
79
|
-
"in": "query", (only "body" is possible for Open API 2.0)
|
|
80
|
-
"name": "key",
|
|
81
|
-
"schema": {
|
|
82
|
-
"type": "string",
|
|
83
|
-
"example": "foo", # This one
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
"""
|
|
87
|
-
return self.definition.get("schema", {}).get("example")
|
|
88
|
-
|
|
89
|
-
def as_json_schema(self) -> Dict[str, Any]:
|
|
90
|
-
"""Convert parameter's definition to JSON Schema."""
|
|
91
|
-
schema = self.from_open_api_to_json_schema(self.definition)
|
|
92
|
-
return self.transform_keywords(schema)
|
|
93
|
-
|
|
94
|
-
def transform_keywords(self, schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
95
|
-
"""Transform Open API specific keywords into JSON Schema compatible form."""
|
|
96
|
-
definition = to_json_schema_recursive(schema, self.nullable_field)
|
|
97
|
-
# Headers are strings, but it is not always explicitly defined in the schema. By preparing them properly, we
|
|
98
|
-
# can achieve significant performance improvements for such cases.
|
|
99
|
-
# For reference (my machine) - running a single test with 100 examples with the resulting strategy:
|
|
100
|
-
# - without: 4.37 s
|
|
101
|
-
# - with: 294 ms
|
|
102
|
-
#
|
|
103
|
-
# It also reduces the number of cases when the "filter_too_much" health check fails during testing.
|
|
104
|
-
if self.is_header:
|
|
105
|
-
definition.setdefault("type", "string")
|
|
106
|
-
return definition
|
|
107
|
-
|
|
108
|
-
def from_open_api_to_json_schema(self, open_api_schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
109
|
-
"""Convert Open API's `Schema` to JSON Schema."""
|
|
110
|
-
return {
|
|
111
|
-
key: value
|
|
112
|
-
for key, value in open_api_schema.items()
|
|
113
|
-
# Allow only supported keywords or vendor extensions
|
|
114
|
-
if key in self.supported_jsonschema_keywords or key.startswith("x-") or key == self.nullable_field
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
def serialize(self) -> str:
|
|
118
|
-
# For simplicity, JSON Schema semantics is not taken into account (e.g. 1 == 1.0)
|
|
119
|
-
# I.e. two semantically equal schemas may have different representation
|
|
120
|
-
return json.dumps(self.as_json_schema(), sort_keys=True)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
@attr.s(slots=True, eq=False)
|
|
124
|
-
class OpenAPI20Parameter(OpenAPIParameter):
|
|
125
|
-
"""Open API 2.0 parameter.
|
|
126
|
-
|
|
127
|
-
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#parameterObject
|
|
128
|
-
"""
|
|
129
|
-
|
|
130
|
-
example_field = "x-example"
|
|
131
|
-
examples_field = "x-examples"
|
|
132
|
-
nullable_field = "x-nullable"
|
|
133
|
-
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#parameterObject
|
|
134
|
-
# Excluding informative keywords - `title`, `description`, `default`.
|
|
135
|
-
# `required` is not included because it has a different meaning here. It determines whether or not this parameter
|
|
136
|
-
# is required, which is not relevant because these parameters are later constructed
|
|
137
|
-
# into an "object" schema, and the value of this keyword is used there.
|
|
138
|
-
# The following keywords are relevant only for non-body parameters.
|
|
139
|
-
supported_jsonschema_keywords: ClassVar[Tuple[str, ...]] = (
|
|
140
|
-
"$ref",
|
|
141
|
-
"type", # only as a string
|
|
142
|
-
"format",
|
|
143
|
-
"items",
|
|
144
|
-
"maximum",
|
|
145
|
-
"exclusiveMaximum",
|
|
146
|
-
"minimum",
|
|
147
|
-
"exclusiveMinimum",
|
|
148
|
-
"maxLength",
|
|
149
|
-
"minLength",
|
|
150
|
-
"pattern",
|
|
151
|
-
"maxItems",
|
|
152
|
-
"minItems",
|
|
153
|
-
"uniqueItems",
|
|
154
|
-
"enum",
|
|
155
|
-
"multipleOf",
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
@property
|
|
159
|
-
def is_header(self) -> bool:
|
|
160
|
-
return self.location == "header"
|
|
161
|
-
|
|
162
|
-
@property
|
|
163
|
-
def _schema_example(self) -> Any:
|
|
164
|
-
# There is no "schema" in non-body parameters
|
|
165
|
-
return None
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
@attr.s(slots=True, eq=False)
|
|
169
|
-
class OpenAPI30Parameter(OpenAPIParameter):
|
|
170
|
-
"""Open API 3.0 parameter.
|
|
171
|
-
|
|
172
|
-
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-object
|
|
173
|
-
"""
|
|
174
|
-
|
|
175
|
-
example_field = "example"
|
|
176
|
-
examples_field = "examples"
|
|
177
|
-
nullable_field = "nullable"
|
|
178
|
-
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#schema-object
|
|
179
|
-
# Excluding informative keywords - `title`, `description`, `default`.
|
|
180
|
-
# In contrast with Open API 2.0 non-body parameters, in Open API 3.0, all parameters have the `schema` keyword.
|
|
181
|
-
supported_jsonschema_keywords = (
|
|
182
|
-
"$ref",
|
|
183
|
-
"multipleOf",
|
|
184
|
-
"maximum",
|
|
185
|
-
"exclusiveMaximum",
|
|
186
|
-
"minimum",
|
|
187
|
-
"exclusiveMinimum",
|
|
188
|
-
"maxLength",
|
|
189
|
-
"minLength",
|
|
190
|
-
"pattern",
|
|
191
|
-
"maxItems",
|
|
192
|
-
"minItems",
|
|
193
|
-
"uniqueItems",
|
|
194
|
-
"maxProperties",
|
|
195
|
-
"minProperties",
|
|
196
|
-
"required",
|
|
197
|
-
"enum",
|
|
198
|
-
"type",
|
|
199
|
-
"allOf",
|
|
200
|
-
"oneOf",
|
|
201
|
-
"anyOf",
|
|
202
|
-
"not",
|
|
203
|
-
"items",
|
|
204
|
-
"properties",
|
|
205
|
-
"additionalProperties",
|
|
206
|
-
"format",
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
@property
|
|
210
|
-
def is_header(self) -> bool:
|
|
211
|
-
return self.location in ("header", "cookie")
|
|
212
|
-
|
|
213
|
-
def from_open_api_to_json_schema(self, open_api_schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
214
|
-
open_api_schema = get_parameter_schema(open_api_schema)
|
|
215
|
-
return super().from_open_api_to_json_schema(open_api_schema)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
@attr.s(slots=True, eq=False)
|
|
219
|
-
class OpenAPIBody(OpenAPIParameter):
|
|
220
|
-
media_type: str = attr.ib()
|
|
221
|
-
|
|
222
|
-
@property
|
|
223
|
-
def location(self) -> str:
|
|
224
|
-
return "body"
|
|
225
|
-
|
|
226
|
-
@property
|
|
227
|
-
def name(self) -> str:
|
|
228
|
-
# The name doesn't matter but is here for the interface completeness.
|
|
229
|
-
return "body"
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
@attr.s(slots=True, eq=False)
|
|
233
|
-
class OpenAPI20Body(OpenAPIBody, OpenAPI20Parameter):
|
|
234
|
-
"""Open API 2.0 body variant."""
|
|
235
|
-
|
|
236
|
-
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schemaObject
|
|
237
|
-
# The `body` parameter contains the `schema` keyword that represents the `Schema Object`.
|
|
238
|
-
# It has slightly different keywords than other parameters. Informational keywords are excluded as well.
|
|
239
|
-
supported_jsonschema_keywords = (
|
|
240
|
-
"$ref",
|
|
241
|
-
"format",
|
|
242
|
-
"multipleOf",
|
|
243
|
-
"maximum",
|
|
244
|
-
"exclusiveMaximum",
|
|
245
|
-
"minimum",
|
|
246
|
-
"exclusiveMinimum",
|
|
247
|
-
"maxLength",
|
|
248
|
-
"minLength",
|
|
249
|
-
"pattern",
|
|
250
|
-
"maxItems",
|
|
251
|
-
"minItems",
|
|
252
|
-
"uniqueItems",
|
|
253
|
-
"maxProperties",
|
|
254
|
-
"minProperties",
|
|
255
|
-
"enum",
|
|
256
|
-
"type",
|
|
257
|
-
"items",
|
|
258
|
-
"allOf",
|
|
259
|
-
"properties",
|
|
260
|
-
"additionalProperties",
|
|
261
|
-
)
|
|
262
|
-
# NOTE. For Open API 2.0 bodies, we still give `x-example` precedence over the schema-level `example` field to keep
|
|
263
|
-
# the precedence rules consistent.
|
|
264
|
-
|
|
265
|
-
def as_json_schema(self) -> Dict[str, Any]:
|
|
266
|
-
"""Convert body definition to JSON Schema."""
|
|
267
|
-
# `schema` is required in Open API 2.0 when the `in` keyword is `body`
|
|
268
|
-
schema = self.definition["schema"]
|
|
269
|
-
return self.transform_keywords(schema)
|
|
270
|
-
|
|
271
|
-
@property
|
|
272
|
-
def _schema_example(self) -> Any:
|
|
273
|
-
# In Open API 2.0, there is the `example` keyword,
|
|
274
|
-
# so we use the default behavior of the `OpenAPIParameter` class
|
|
275
|
-
return super(OpenAPI20Parameter, self)._schema_example
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
FORM_MEDIA_TYPES = ("multipart/form-data", "application/x-www-form-urlencoded")
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
@attr.s(slots=True, eq=False)
|
|
282
|
-
class OpenAPI30Body(OpenAPIBody, OpenAPI30Parameter):
|
|
283
|
-
"""Open API 3.0 body variant.
|
|
284
|
-
|
|
285
|
-
We consider each media type defined in the schema as a separate variant that can be chosen for data generation.
|
|
286
|
-
The value of the `definition` field is essentially the Open API 3.0 `MediaType`.
|
|
287
|
-
"""
|
|
288
|
-
|
|
289
|
-
# The `required` keyword is located above the schema for concrete media-type;
|
|
290
|
-
# Therefore, it is passed here explicitly
|
|
291
|
-
required: bool = attr.ib(default=False)
|
|
292
|
-
description: Optional[str] = attr.ib(default=None)
|
|
293
|
-
|
|
294
|
-
def as_json_schema(self) -> Dict[str, Any]:
|
|
295
|
-
"""Convert body definition to JSON Schema."""
|
|
296
|
-
schema = get_media_type_schema(self.definition)
|
|
297
|
-
return self.transform_keywords(schema)
|
|
298
|
-
|
|
299
|
-
def transform_keywords(self, schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
300
|
-
definition = super().transform_keywords(schema)
|
|
301
|
-
if self.is_form:
|
|
302
|
-
# It significantly reduces the "filtering" part of data generation.
|
|
303
|
-
definition.setdefault("type", "object")
|
|
304
|
-
return definition
|
|
305
|
-
|
|
306
|
-
@property
|
|
307
|
-
def is_form(self) -> bool:
|
|
308
|
-
"""Whether this payload represent a form."""
|
|
309
|
-
return self.media_type in FORM_MEDIA_TYPES
|
|
310
|
-
|
|
311
|
-
@property
|
|
312
|
-
def is_required(self) -> bool:
|
|
313
|
-
return self.required
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
@attr.s(slots=True, eq=False)
|
|
317
|
-
class OpenAPI20CompositeBody(OpenAPIBody, OpenAPI20Parameter):
|
|
318
|
-
"""A special container to abstract over multiple `formData` parameters."""
|
|
319
|
-
|
|
320
|
-
definition: List[OpenAPI20Parameter] = attr.ib()
|
|
321
|
-
|
|
322
|
-
@classmethod
|
|
323
|
-
def from_parameters(cls, *parameters: Dict[str, Any], media_type: str) -> "OpenAPI20CompositeBody":
|
|
324
|
-
return cls(
|
|
325
|
-
definition=[OpenAPI20Parameter(parameter) for parameter in parameters],
|
|
326
|
-
media_type=media_type,
|
|
327
|
-
)
|
|
328
|
-
|
|
329
|
-
@property
|
|
330
|
-
def description(self) -> Optional[str]:
|
|
331
|
-
return None
|
|
332
|
-
|
|
333
|
-
@property
|
|
334
|
-
def is_required(self) -> bool:
|
|
335
|
-
# We generate an object for formData - it is always required.
|
|
336
|
-
return bool(self.definition)
|
|
337
|
-
|
|
338
|
-
@property
|
|
339
|
-
def _example(self) -> Any:
|
|
340
|
-
return {parameter.name: parameter._example for parameter in self.definition if parameter._example}
|
|
341
|
-
|
|
342
|
-
@property
|
|
343
|
-
def _schema_example(self) -> Any:
|
|
344
|
-
return {parameter.name: parameter._schema_example for parameter in self.definition if parameter._schema_example}
|
|
345
|
-
|
|
346
|
-
def as_json_schema(self) -> Dict[str, Any]:
|
|
347
|
-
"""The composite body is transformed into an "object" JSON Schema."""
|
|
348
|
-
return parameters_to_json_schema(self.definition)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
def parameters_to_json_schema(parameters: Iterable[OpenAPIParameter]) -> Dict[str, Any]:
|
|
352
|
-
"""Create an "object" JSON schema from a list of Open API parameters.
|
|
353
|
-
|
|
354
|
-
:param List[OpenAPIParameter] parameters: A list of Open API parameters related to the same location. All of
|
|
355
|
-
them are expected to have the same "in" value.
|
|
356
|
-
|
|
357
|
-
For each input parameter, there will be a property in the output schema.
|
|
358
|
-
|
|
359
|
-
This:
|
|
360
|
-
|
|
361
|
-
[
|
|
362
|
-
{
|
|
363
|
-
"in": "query",
|
|
364
|
-
"name": "id",
|
|
365
|
-
"type": "string",
|
|
366
|
-
"required": True
|
|
367
|
-
}
|
|
368
|
-
]
|
|
369
|
-
|
|
370
|
-
Will become:
|
|
371
|
-
|
|
372
|
-
{
|
|
373
|
-
"properties": {
|
|
374
|
-
"id": {"type": "string"}
|
|
375
|
-
},
|
|
376
|
-
"additionalProperties": False,
|
|
377
|
-
"type": "object",
|
|
378
|
-
"required": ["id"]
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
We need this transformation for locations that imply multiple components with a unique name within
|
|
382
|
-
the same location.
|
|
383
|
-
|
|
384
|
-
For example, "query" - first, we generate an object that contains all defined parameters and then serialize it
|
|
385
|
-
to the proper format.
|
|
386
|
-
"""
|
|
387
|
-
properties = {}
|
|
388
|
-
required = []
|
|
389
|
-
for parameter in parameters:
|
|
390
|
-
name = parameter.name
|
|
391
|
-
properties[name] = parameter.as_json_schema()
|
|
392
|
-
if parameter.is_required:
|
|
393
|
-
required.append(name)
|
|
394
|
-
return {"properties": properties, "additionalProperties": False, "type": "object", "required": required}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
def get_parameter_schema(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
398
|
-
"""Extract `schema` from Open API 3.0 `Parameter`."""
|
|
399
|
-
# In Open API 3.0, there could be "schema" or "content" field. They are mutually exclusive.
|
|
400
|
-
if "schema" in data:
|
|
401
|
-
return data["schema"]
|
|
402
|
-
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-10
|
|
403
|
-
# > The map MUST only contain one entry.
|
|
404
|
-
options = iter(data["content"].values())
|
|
405
|
-
media_type_object = next(options)
|
|
406
|
-
return get_media_type_schema(media_type_object)
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
def get_media_type_schema(definition: Dict[str, Any]) -> Dict[str, Any]:
|
|
410
|
-
"""Extract `schema` from Open API 3.0 `MediaType`."""
|
|
411
|
-
# The `schema` keyword is optional, and we treat it as the payload could be any value of the specified media type
|
|
412
|
-
# Note, the main reason to have this function is to have an explicit name for the action we're doing.
|
|
413
|
-
return definition.get("schema", {})
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
"""Processing of ``securityDefinitions`` or ``securitySchemes`` keywords."""
|
|
2
|
-
from typing import Any, ClassVar, Dict, Generator, List, Tuple, Type
|
|
3
|
-
|
|
4
|
-
import attr
|
|
5
|
-
from jsonschema import RefResolver
|
|
6
|
-
|
|
7
|
-
from ...models import APIOperation
|
|
8
|
-
from .parameters import OpenAPI20Parameter, OpenAPI30Parameter, OpenAPIParameter
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@attr.s(slots=True) # pragma: no mutate
|
|
12
|
-
class BaseSecurityProcessor:
|
|
13
|
-
api_key_locations: Tuple[str, ...] = ("header", "query")
|
|
14
|
-
http_security_name = "basic"
|
|
15
|
-
parameter_cls: ClassVar[Type[OpenAPIParameter]] = OpenAPI20Parameter
|
|
16
|
-
|
|
17
|
-
def process_definitions(self, schema: Dict[str, Any], operation: APIOperation, resolver: RefResolver) -> None:
|
|
18
|
-
"""Add relevant security parameters to data generation."""
|
|
19
|
-
for definition in self._get_active_definitions(schema, operation, resolver):
|
|
20
|
-
if definition["type"] == "apiKey":
|
|
21
|
-
self.process_api_key_security_definition(definition, operation)
|
|
22
|
-
self.process_http_security_definition(definition, operation)
|
|
23
|
-
|
|
24
|
-
def _get_active_definitions(
|
|
25
|
-
self, schema: Dict[str, Any], operation: APIOperation, resolver: RefResolver
|
|
26
|
-
) -> Generator[Dict[str, Any], None, None]:
|
|
27
|
-
"""Get only security definitions active for the given API operation."""
|
|
28
|
-
definitions = self.get_security_definitions(schema, resolver)
|
|
29
|
-
requirements = get_security_requirements(schema, operation)
|
|
30
|
-
for name, definition in definitions.items():
|
|
31
|
-
if name in requirements:
|
|
32
|
-
yield definition
|
|
33
|
-
|
|
34
|
-
def get_security_definitions(self, schema: Dict[str, Any], resolver: RefResolver) -> Dict[str, Any]:
|
|
35
|
-
return schema.get("securityDefinitions", {})
|
|
36
|
-
|
|
37
|
-
def get_security_definitions_as_parameters(
|
|
38
|
-
self, schema: Dict[str, Any], operation: APIOperation, resolver: RefResolver, location: str
|
|
39
|
-
) -> List[Dict[str, Any]]:
|
|
40
|
-
"""Security definitions converted to OAS parameters.
|
|
41
|
-
|
|
42
|
-
We need it to get proper serialization that will be applied on generated values. For this case it is only
|
|
43
|
-
coercing to a string.
|
|
44
|
-
"""
|
|
45
|
-
return [
|
|
46
|
-
self._to_parameter(definition)
|
|
47
|
-
for definition in self._get_active_definitions(schema, operation, resolver)
|
|
48
|
-
if self._is_match(definition, location)
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
def process_api_key_security_definition(self, definition: Dict[str, Any], operation: APIOperation) -> None:
|
|
52
|
-
parameter = self.parameter_cls(self._make_api_key_parameter(definition))
|
|
53
|
-
operation.add_parameter(parameter)
|
|
54
|
-
|
|
55
|
-
def process_http_security_definition(self, definition: Dict[str, Any], operation: APIOperation) -> None:
|
|
56
|
-
if definition["type"] == self.http_security_name:
|
|
57
|
-
parameter = self.parameter_cls(self._make_http_auth_parameter(definition))
|
|
58
|
-
operation.add_parameter(parameter)
|
|
59
|
-
|
|
60
|
-
def _is_match(self, definition: Dict[str, Any], location: str) -> bool:
|
|
61
|
-
return (definition["type"] == "apiKey" and location in self.api_key_locations) or (
|
|
62
|
-
definition["type"] == self.http_security_name and location == "header"
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
def _to_parameter(self, definition: Dict[str, Any]) -> Dict[str, Any]:
|
|
66
|
-
func = {
|
|
67
|
-
"apiKey": self._make_api_key_parameter,
|
|
68
|
-
self.http_security_name: self._make_http_auth_parameter,
|
|
69
|
-
}[definition["type"]]
|
|
70
|
-
return func(definition)
|
|
71
|
-
|
|
72
|
-
def _make_http_auth_parameter(self, definition: Dict[str, Any]) -> Dict[str, Any]:
|
|
73
|
-
schema = make_auth_header_schema(definition)
|
|
74
|
-
return make_auth_header(**schema)
|
|
75
|
-
|
|
76
|
-
def _make_api_key_parameter(self, definition: Dict[str, Any]) -> Dict[str, Any]:
|
|
77
|
-
return make_api_key_schema(definition, type="string")
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def make_auth_header_schema(definition: Dict[str, Any]) -> Dict[str, str]:
|
|
81
|
-
schema = definition.get("scheme", "basic").lower()
|
|
82
|
-
return {"type": "string", "format": f"_{schema}_auth"}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def make_auth_header(**kwargs: Any) -> Dict[str, Any]:
|
|
86
|
-
return {"name": "Authorization", "in": "header", "required": True, **kwargs}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def make_api_key_schema(definition: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]:
|
|
90
|
-
return {"name": definition["name"], "required": True, "in": definition["in"], **kwargs}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
SwaggerSecurityProcessor = BaseSecurityProcessor
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
@attr.s(slots=True) # pragma: no mutate
|
|
97
|
-
class OpenAPISecurityProcessor(BaseSecurityProcessor):
|
|
98
|
-
api_key_locations = ("header", "cookie", "query")
|
|
99
|
-
http_security_name = "http"
|
|
100
|
-
parameter_cls: ClassVar[Type[OpenAPIParameter]] = OpenAPI30Parameter
|
|
101
|
-
|
|
102
|
-
def get_security_definitions(self, schema: Dict[str, Any], resolver: RefResolver) -> Dict[str, Any]:
|
|
103
|
-
"""In Open API 3 security definitions are located in ``components`` and may have references inside."""
|
|
104
|
-
components = schema.get("components", {})
|
|
105
|
-
security_schemes = components.get("securitySchemes", {})
|
|
106
|
-
if "$ref" in security_schemes:
|
|
107
|
-
return resolver.resolve(security_schemes["$ref"])[1]
|
|
108
|
-
return security_schemes
|
|
109
|
-
|
|
110
|
-
def _make_http_auth_parameter(self, definition: Dict[str, Any]) -> Dict[str, Any]:
|
|
111
|
-
schema = make_auth_header_schema(definition)
|
|
112
|
-
return make_auth_header(schema=schema)
|
|
113
|
-
|
|
114
|
-
def _make_api_key_parameter(self, definition: Dict[str, Any]) -> Dict[str, Any]:
|
|
115
|
-
return make_api_key_schema(definition, schema={"type": "string"})
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def get_security_requirements(schema: Dict[str, Any], operation: APIOperation) -> List[str]:
|
|
119
|
-
"""Get applied security requirements for the given API operation."""
|
|
120
|
-
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object
|
|
121
|
-
# > This definition overrides any declared top-level security.
|
|
122
|
-
# > To remove a top-level security declaration, an empty array can be used.
|
|
123
|
-
global_requirements = schema.get("security", [])
|
|
124
|
-
local_requirements = operation.definition.raw.get("security", None)
|
|
125
|
-
if local_requirements is not None:
|
|
126
|
-
requirements = local_requirements
|
|
127
|
-
else:
|
|
128
|
-
requirements = global_requirements
|
|
129
|
-
return [key for requirement in requirements for key in requirement]
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Tuple, Union
|
|
2
|
-
|
|
3
|
-
from ...constants import HTTP_METHODS
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def is_pattern_error(exception: TypeError) -> bool:
|
|
7
|
-
"""Detect whether the input exception was caused by invalid type passed to `re.search`."""
|
|
8
|
-
# This is intentionally simplistic and do not involve any traceback analysis
|
|
9
|
-
return str(exception) == "expected string or bytes-like object"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def find_numeric_http_status_codes(schema: Dict[str, Any]) -> List[Tuple[int, List[Union[str, int]]]]:
|
|
13
|
-
if not isinstance(schema, dict):
|
|
14
|
-
return []
|
|
15
|
-
found = []
|
|
16
|
-
for path, methods in schema.get("paths", {}).items():
|
|
17
|
-
if isinstance(methods, dict):
|
|
18
|
-
for method, definition in methods.items():
|
|
19
|
-
if method not in HTTP_METHODS or not isinstance(definition, dict):
|
|
20
|
-
continue
|
|
21
|
-
for key in definition.get("responses", {}):
|
|
22
|
-
if isinstance(key, int):
|
|
23
|
-
found.append((key, [path, method]))
|
|
24
|
-
return found
|