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