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.
Files changed (245) hide show
  1. schemathesis/__init__.py +53 -25
  2. schemathesis/auths.py +507 -0
  3. schemathesis/checks.py +190 -25
  4. schemathesis/cli/__init__.py +27 -1016
  5. schemathesis/cli/__main__.py +4 -0
  6. schemathesis/cli/commands/__init__.py +133 -0
  7. schemathesis/cli/commands/data.py +10 -0
  8. schemathesis/cli/commands/run/__init__.py +602 -0
  9. schemathesis/cli/commands/run/context.py +228 -0
  10. schemathesis/cli/commands/run/events.py +60 -0
  11. schemathesis/cli/commands/run/executor.py +157 -0
  12. schemathesis/cli/commands/run/filters.py +53 -0
  13. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  14. schemathesis/cli/commands/run/handlers/base.py +45 -0
  15. schemathesis/cli/commands/run/handlers/cassettes.py +464 -0
  16. schemathesis/cli/commands/run/handlers/junitxml.py +60 -0
  17. schemathesis/cli/commands/run/handlers/output.py +1750 -0
  18. schemathesis/cli/commands/run/loaders.py +118 -0
  19. schemathesis/cli/commands/run/validation.py +256 -0
  20. schemathesis/cli/constants.py +5 -0
  21. schemathesis/cli/core.py +19 -0
  22. schemathesis/cli/ext/fs.py +16 -0
  23. schemathesis/cli/ext/groups.py +203 -0
  24. schemathesis/cli/ext/options.py +81 -0
  25. schemathesis/config/__init__.py +202 -0
  26. schemathesis/config/_auth.py +51 -0
  27. schemathesis/config/_checks.py +268 -0
  28. schemathesis/config/_diff_base.py +101 -0
  29. schemathesis/config/_env.py +21 -0
  30. schemathesis/config/_error.py +163 -0
  31. schemathesis/config/_generation.py +157 -0
  32. schemathesis/config/_health_check.py +24 -0
  33. schemathesis/config/_operations.py +335 -0
  34. schemathesis/config/_output.py +171 -0
  35. schemathesis/config/_parameters.py +19 -0
  36. schemathesis/config/_phases.py +253 -0
  37. schemathesis/config/_projects.py +543 -0
  38. schemathesis/config/_rate_limit.py +17 -0
  39. schemathesis/config/_report.py +120 -0
  40. schemathesis/config/_validator.py +9 -0
  41. schemathesis/config/_warnings.py +89 -0
  42. schemathesis/config/schema.json +975 -0
  43. schemathesis/core/__init__.py +72 -0
  44. schemathesis/core/adapter.py +34 -0
  45. schemathesis/core/compat.py +32 -0
  46. schemathesis/core/control.py +2 -0
  47. schemathesis/core/curl.py +100 -0
  48. schemathesis/core/deserialization.py +210 -0
  49. schemathesis/core/errors.py +588 -0
  50. schemathesis/core/failures.py +316 -0
  51. schemathesis/core/fs.py +19 -0
  52. schemathesis/core/hooks.py +20 -0
  53. schemathesis/core/jsonschema/__init__.py +13 -0
  54. schemathesis/core/jsonschema/bundler.py +183 -0
  55. schemathesis/core/jsonschema/keywords.py +40 -0
  56. schemathesis/core/jsonschema/references.py +222 -0
  57. schemathesis/core/jsonschema/types.py +41 -0
  58. schemathesis/core/lazy_import.py +15 -0
  59. schemathesis/core/loaders.py +107 -0
  60. schemathesis/core/marks.py +66 -0
  61. schemathesis/core/media_types.py +79 -0
  62. schemathesis/core/output/__init__.py +46 -0
  63. schemathesis/core/output/sanitization.py +54 -0
  64. schemathesis/core/parameters.py +45 -0
  65. schemathesis/core/rate_limit.py +60 -0
  66. schemathesis/core/registries.py +34 -0
  67. schemathesis/core/result.py +27 -0
  68. schemathesis/core/schema_analysis.py +17 -0
  69. schemathesis/core/shell.py +203 -0
  70. schemathesis/core/transforms.py +144 -0
  71. schemathesis/core/transport.py +223 -0
  72. schemathesis/core/validation.py +73 -0
  73. schemathesis/core/version.py +7 -0
  74. schemathesis/engine/__init__.py +28 -0
  75. schemathesis/engine/context.py +152 -0
  76. schemathesis/engine/control.py +44 -0
  77. schemathesis/engine/core.py +201 -0
  78. schemathesis/engine/errors.py +446 -0
  79. schemathesis/engine/events.py +284 -0
  80. schemathesis/engine/observations.py +42 -0
  81. schemathesis/engine/phases/__init__.py +108 -0
  82. schemathesis/engine/phases/analysis.py +28 -0
  83. schemathesis/engine/phases/probes.py +172 -0
  84. schemathesis/engine/phases/stateful/__init__.py +68 -0
  85. schemathesis/engine/phases/stateful/_executor.py +364 -0
  86. schemathesis/engine/phases/stateful/context.py +85 -0
  87. schemathesis/engine/phases/unit/__init__.py +220 -0
  88. schemathesis/engine/phases/unit/_executor.py +459 -0
  89. schemathesis/engine/phases/unit/_pool.py +82 -0
  90. schemathesis/engine/recorder.py +254 -0
  91. schemathesis/errors.py +47 -0
  92. schemathesis/filters.py +395 -0
  93. schemathesis/generation/__init__.py +25 -0
  94. schemathesis/generation/case.py +478 -0
  95. schemathesis/generation/coverage.py +1528 -0
  96. schemathesis/generation/hypothesis/__init__.py +121 -0
  97. schemathesis/generation/hypothesis/builder.py +992 -0
  98. schemathesis/generation/hypothesis/examples.py +56 -0
  99. schemathesis/generation/hypothesis/given.py +66 -0
  100. schemathesis/generation/hypothesis/reporting.py +285 -0
  101. schemathesis/generation/meta.py +227 -0
  102. schemathesis/generation/metrics.py +93 -0
  103. schemathesis/generation/modes.py +20 -0
  104. schemathesis/generation/overrides.py +127 -0
  105. schemathesis/generation/stateful/__init__.py +37 -0
  106. schemathesis/generation/stateful/state_machine.py +294 -0
  107. schemathesis/graphql/__init__.py +15 -0
  108. schemathesis/graphql/checks.py +109 -0
  109. schemathesis/graphql/loaders.py +285 -0
  110. schemathesis/hooks.py +270 -91
  111. schemathesis/openapi/__init__.py +13 -0
  112. schemathesis/openapi/checks.py +467 -0
  113. schemathesis/openapi/generation/__init__.py +0 -0
  114. schemathesis/openapi/generation/filters.py +72 -0
  115. schemathesis/openapi/loaders.py +315 -0
  116. schemathesis/pytest/__init__.py +5 -0
  117. schemathesis/pytest/control_flow.py +7 -0
  118. schemathesis/pytest/lazy.py +341 -0
  119. schemathesis/pytest/loaders.py +36 -0
  120. schemathesis/pytest/plugin.py +357 -0
  121. schemathesis/python/__init__.py +0 -0
  122. schemathesis/python/asgi.py +12 -0
  123. schemathesis/python/wsgi.py +12 -0
  124. schemathesis/schemas.py +683 -247
  125. schemathesis/specs/graphql/__init__.py +0 -1
  126. schemathesis/specs/graphql/nodes.py +27 -0
  127. schemathesis/specs/graphql/scalars.py +86 -0
  128. schemathesis/specs/graphql/schemas.py +395 -123
  129. schemathesis/specs/graphql/validation.py +33 -0
  130. schemathesis/specs/openapi/__init__.py +9 -1
  131. schemathesis/specs/openapi/_hypothesis.py +578 -317
  132. schemathesis/specs/openapi/adapter/__init__.py +10 -0
  133. schemathesis/specs/openapi/adapter/parameters.py +729 -0
  134. schemathesis/specs/openapi/adapter/protocol.py +59 -0
  135. schemathesis/specs/openapi/adapter/references.py +19 -0
  136. schemathesis/specs/openapi/adapter/responses.py +368 -0
  137. schemathesis/specs/openapi/adapter/security.py +144 -0
  138. schemathesis/specs/openapi/adapter/v2.py +30 -0
  139. schemathesis/specs/openapi/adapter/v3_0.py +30 -0
  140. schemathesis/specs/openapi/adapter/v3_1.py +30 -0
  141. schemathesis/specs/openapi/analysis.py +96 -0
  142. schemathesis/specs/openapi/checks.py +753 -74
  143. schemathesis/specs/openapi/converter.py +176 -37
  144. schemathesis/specs/openapi/definitions.py +599 -4
  145. schemathesis/specs/openapi/examples.py +581 -165
  146. schemathesis/specs/openapi/expressions/__init__.py +52 -5
  147. schemathesis/specs/openapi/expressions/extractors.py +25 -0
  148. schemathesis/specs/openapi/expressions/lexer.py +34 -31
  149. schemathesis/specs/openapi/expressions/nodes.py +97 -46
  150. schemathesis/specs/openapi/expressions/parser.py +35 -13
  151. schemathesis/specs/openapi/formats.py +122 -0
  152. schemathesis/specs/openapi/media_types.py +75 -0
  153. schemathesis/specs/openapi/negative/__init__.py +117 -68
  154. schemathesis/specs/openapi/negative/mutations.py +294 -104
  155. schemathesis/specs/openapi/negative/utils.py +3 -6
  156. schemathesis/specs/openapi/patterns.py +458 -0
  157. schemathesis/specs/openapi/references.py +60 -81
  158. schemathesis/specs/openapi/schemas.py +648 -650
  159. schemathesis/specs/openapi/serialization.py +53 -30
  160. schemathesis/specs/openapi/stateful/__init__.py +404 -69
  161. schemathesis/specs/openapi/stateful/control.py +87 -0
  162. schemathesis/specs/openapi/stateful/dependencies/__init__.py +232 -0
  163. schemathesis/specs/openapi/stateful/dependencies/inputs.py +428 -0
  164. schemathesis/specs/openapi/stateful/dependencies/models.py +341 -0
  165. schemathesis/specs/openapi/stateful/dependencies/naming.py +491 -0
  166. schemathesis/specs/openapi/stateful/dependencies/outputs.py +34 -0
  167. schemathesis/specs/openapi/stateful/dependencies/resources.py +339 -0
  168. schemathesis/specs/openapi/stateful/dependencies/schemas.py +447 -0
  169. schemathesis/specs/openapi/stateful/inference.py +254 -0
  170. schemathesis/specs/openapi/stateful/links.py +219 -78
  171. schemathesis/specs/openapi/types/__init__.py +3 -0
  172. schemathesis/specs/openapi/types/common.py +23 -0
  173. schemathesis/specs/openapi/types/v2.py +129 -0
  174. schemathesis/specs/openapi/types/v3.py +134 -0
  175. schemathesis/specs/openapi/utils.py +7 -6
  176. schemathesis/specs/openapi/warnings.py +75 -0
  177. schemathesis/transport/__init__.py +224 -0
  178. schemathesis/transport/asgi.py +26 -0
  179. schemathesis/transport/prepare.py +126 -0
  180. schemathesis/transport/requests.py +278 -0
  181. schemathesis/transport/serialization.py +329 -0
  182. schemathesis/transport/wsgi.py +175 -0
  183. schemathesis-4.4.2.dist-info/METADATA +213 -0
  184. schemathesis-4.4.2.dist-info/RECORD +192 -0
  185. {schemathesis-3.13.0.dist-info → schemathesis-4.4.2.dist-info}/WHEEL +1 -1
  186. schemathesis-4.4.2.dist-info/entry_points.txt +6 -0
  187. {schemathesis-3.13.0.dist-info → schemathesis-4.4.2.dist-info/licenses}/LICENSE +1 -1
  188. schemathesis/_compat.py +0 -41
  189. schemathesis/_hypothesis.py +0 -115
  190. schemathesis/cli/callbacks.py +0 -188
  191. schemathesis/cli/cassettes.py +0 -253
  192. schemathesis/cli/context.py +0 -36
  193. schemathesis/cli/debug.py +0 -21
  194. schemathesis/cli/handlers.py +0 -11
  195. schemathesis/cli/junitxml.py +0 -41
  196. schemathesis/cli/options.py +0 -51
  197. schemathesis/cli/output/__init__.py +0 -1
  198. schemathesis/cli/output/default.py +0 -508
  199. schemathesis/cli/output/short.py +0 -40
  200. schemathesis/constants.py +0 -79
  201. schemathesis/exceptions.py +0 -207
  202. schemathesis/extra/_aiohttp.py +0 -27
  203. schemathesis/extra/_flask.py +0 -10
  204. schemathesis/extra/_server.py +0 -16
  205. schemathesis/extra/pytest_plugin.py +0 -216
  206. schemathesis/failures.py +0 -131
  207. schemathesis/fixups/__init__.py +0 -29
  208. schemathesis/fixups/fast_api.py +0 -30
  209. schemathesis/lazy.py +0 -227
  210. schemathesis/models.py +0 -1041
  211. schemathesis/parameters.py +0 -88
  212. schemathesis/runner/__init__.py +0 -460
  213. schemathesis/runner/events.py +0 -240
  214. schemathesis/runner/impl/__init__.py +0 -3
  215. schemathesis/runner/impl/core.py +0 -755
  216. schemathesis/runner/impl/solo.py +0 -85
  217. schemathesis/runner/impl/threadpool.py +0 -367
  218. schemathesis/runner/serialization.py +0 -189
  219. schemathesis/serializers.py +0 -233
  220. schemathesis/service/__init__.py +0 -3
  221. schemathesis/service/client.py +0 -46
  222. schemathesis/service/constants.py +0 -12
  223. schemathesis/service/events.py +0 -39
  224. schemathesis/service/handler.py +0 -39
  225. schemathesis/service/models.py +0 -7
  226. schemathesis/service/serialization.py +0 -153
  227. schemathesis/service/worker.py +0 -40
  228. schemathesis/specs/graphql/loaders.py +0 -215
  229. schemathesis/specs/openapi/constants.py +0 -7
  230. schemathesis/specs/openapi/expressions/context.py +0 -12
  231. schemathesis/specs/openapi/expressions/pointers.py +0 -29
  232. schemathesis/specs/openapi/filters.py +0 -44
  233. schemathesis/specs/openapi/links.py +0 -302
  234. schemathesis/specs/openapi/loaders.py +0 -453
  235. schemathesis/specs/openapi/parameters.py +0 -413
  236. schemathesis/specs/openapi/security.py +0 -129
  237. schemathesis/specs/openapi/validation.py +0 -24
  238. schemathesis/stateful.py +0 -349
  239. schemathesis/targets.py +0 -32
  240. schemathesis/types.py +0 -38
  241. schemathesis/utils.py +0 -436
  242. schemathesis-3.13.0.dist-info/METADATA +0 -202
  243. schemathesis-3.13.0.dist-info/RECORD +0 -91
  244. schemathesis-3.13.0.dist-info/entry_points.txt +0 -6
  245. /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