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,27 +1,89 @@
1
- from copy import deepcopy
2
- from itertools import chain
3
- from typing import Any, Callable, Dict, List
1
+ from __future__ import annotations
4
2
 
5
- from ...utils import traverse_schema
3
+ from typing import Any, Callable, overload
4
+
5
+ from schemathesis.core.jsonschema.bundler import BUNDLE_STORAGE_KEY
6
+ from schemathesis.core.jsonschema.types import JsonSchema
7
+ from schemathesis.core.transforms import deepclone
8
+ from schemathesis.specs.openapi.patterns import update_quantifier
9
+
10
+
11
+ @overload
12
+ def to_json_schema(
13
+ schema: dict[str, Any],
14
+ nullable_keyword: str,
15
+ is_response_schema: bool = False,
16
+ update_quantifiers: bool = True,
17
+ clone: bool = True,
18
+ ) -> dict[str, Any]: ... # pragma: no cover
19
+
20
+
21
+ @overload
22
+ def to_json_schema(
23
+ schema: bool,
24
+ nullable_keyword: str,
25
+ is_response_schema: bool = False,
26
+ update_quantifiers: bool = True,
27
+ clone: bool = True,
28
+ ) -> bool: ... # pragma: no cover
6
29
 
7
30
 
8
31
  def to_json_schema(
9
- schema: Dict[str, Any], *, nullable_name: str, copy: bool = True, is_response_schema: bool = False
10
- ) -> Dict[str, Any]:
11
- """Convert Open API parameters to JSON Schema.
12
-
13
- NOTE. This function is applied to all keywords (including nested) during a schema resolving, thus it is not recursive.
14
- See a recursive version below.
15
- """
16
- if copy:
17
- schema = deepcopy(schema)
18
- if schema.get(nullable_name) is True:
19
- del schema[nullable_name]
32
+ schema: dict[str, Any] | bool,
33
+ nullable_keyword: str,
34
+ is_response_schema: bool = False,
35
+ update_quantifiers: bool = True,
36
+ clone: bool = True,
37
+ ) -> dict[str, Any] | bool:
38
+ if isinstance(schema, bool):
39
+ return schema
40
+ if clone:
41
+ schema = deepclone(schema)
42
+ return _to_json_schema(
43
+ schema,
44
+ nullable_keyword=nullable_keyword,
45
+ is_response_schema=is_response_schema,
46
+ update_quantifiers=update_quantifiers,
47
+ )
48
+
49
+
50
+ def _to_json_schema(
51
+ schema: JsonSchema,
52
+ *,
53
+ nullable_keyword: str,
54
+ is_response_schema: bool = False,
55
+ update_quantifiers: bool = True,
56
+ ) -> JsonSchema:
57
+ if isinstance(schema, bool):
58
+ return schema
59
+
60
+ if schema.get(nullable_keyword) is True:
61
+ del schema[nullable_keyword]
62
+ bundled = schema.pop(BUNDLE_STORAGE_KEY, None)
20
63
  schema = {"anyOf": [schema, {"type": "null"}]}
64
+ if bundled:
65
+ schema[BUNDLE_STORAGE_KEY] = bundled
21
66
  schema_type = schema.get("type")
22
67
  if schema_type == "file":
23
68
  schema["type"] = "string"
24
69
  schema["format"] = "binary"
70
+ if update_quantifiers:
71
+ update_pattern_in_schema(schema)
72
+ # Sometimes `required` is incorrectly has a boolean value
73
+ properties = schema.get("properties")
74
+ if isinstance(properties, dict):
75
+ for name, subschema in properties.items():
76
+ if not isinstance(subschema, dict):
77
+ continue
78
+ is_required = subschema.get("required")
79
+ if is_required is True:
80
+ schema.setdefault("required", []).append(name)
81
+ del subschema["required"]
82
+ elif is_required is False:
83
+ if "required" in schema and name in schema["required"]:
84
+ schema["required"].remove(name)
85
+ del subschema["required"]
86
+
25
87
  if schema_type == "object":
26
88
  if is_response_schema:
27
89
  # Write-only properties should not occur in responses
@@ -29,43 +91,120 @@ def to_json_schema(
29
91
  else:
30
92
  # Read-only properties should not occur in requests
31
93
  rewrite_properties(schema, is_read_only)
94
+
95
+ ensure_required_properties(schema)
96
+
97
+ for keyword, value in schema.items():
98
+ if keyword in IN_VALUE and isinstance(value, dict):
99
+ schema[keyword] = _to_json_schema(
100
+ value,
101
+ nullable_keyword=nullable_keyword,
102
+ is_response_schema=is_response_schema,
103
+ update_quantifiers=update_quantifiers,
104
+ )
105
+ elif keyword in IN_ITEM and isinstance(value, list):
106
+ for idx, subschema in enumerate(value):
107
+ value[idx] = _to_json_schema(
108
+ subschema,
109
+ nullable_keyword=nullable_keyword,
110
+ is_response_schema=is_response_schema,
111
+ update_quantifiers=update_quantifiers,
112
+ )
113
+ elif keyword in IN_CHILD and isinstance(value, dict):
114
+ for name, subschema in value.items():
115
+ value[name] = _to_json_schema(
116
+ subschema,
117
+ nullable_keyword=nullable_keyword,
118
+ is_response_schema=is_response_schema,
119
+ update_quantifiers=update_quantifiers,
120
+ )
121
+
32
122
  return schema
33
123
 
34
124
 
35
- def rewrite_properties(schema: Dict[str, Any], predicate: Callable[[Dict[str, Any]], bool]) -> None:
125
+ def ensure_required_properties(schema: dict[str, Any]) -> None:
126
+ if schema.get("additionalProperties") is not False:
127
+ return
128
+
129
+ required = schema.get("required")
130
+ if not required or not isinstance(required, list):
131
+ return
132
+
133
+ properties = schema.setdefault("properties", {})
134
+
135
+ # Add missing required properties as empty schemas
136
+ for name in required:
137
+ if name not in properties:
138
+ properties[name] = {}
139
+
140
+
141
+ IN_VALUE = frozenset(
142
+ (
143
+ "additionalProperties",
144
+ "contains",
145
+ "contentSchema",
146
+ "else",
147
+ "if",
148
+ "items",
149
+ "not",
150
+ "propertyNames",
151
+ "then",
152
+ "unevaluatedItems",
153
+ "unevaluatedProperties",
154
+ )
155
+ )
156
+ IN_ITEM = frozenset(
157
+ (
158
+ "allOf",
159
+ "anyOf",
160
+ "oneOf",
161
+ )
162
+ )
163
+ IN_CHILD = frozenset(
164
+ (
165
+ "prefixItems",
166
+ "$defs",
167
+ "definitions",
168
+ "dependentSchemas",
169
+ "patternProperties",
170
+ "properties",
171
+ BUNDLE_STORAGE_KEY,
172
+ )
173
+ )
174
+
175
+
176
+ def update_pattern_in_schema(schema: dict[str, Any]) -> None:
177
+ pattern = schema.get("pattern")
178
+ min_length = schema.get("minLength")
179
+ max_length = schema.get("maxLength")
180
+ if pattern and (min_length or max_length):
181
+ new_pattern = update_quantifier(pattern, min_length, max_length)
182
+ if new_pattern != pattern:
183
+ schema.pop("minLength", None)
184
+ schema.pop("maxLength", None)
185
+ schema["pattern"] = new_pattern
186
+
187
+
188
+ def rewrite_properties(schema: dict[str, Any], predicate: Callable[[dict[str, Any]], bool]) -> None:
36
189
  required = schema.get("required", [])
37
- forbidden = []
38
190
  for name, subschema in list(schema.get("properties", {}).items()):
39
191
  if predicate(subschema):
40
192
  if name in required:
41
193
  required.remove(name)
42
- del schema["properties"][name]
43
- forbidden.append(name)
44
- if forbidden:
45
- forbid_properties(schema, forbidden)
194
+ schema["properties"][name] = {"not": {}}
46
195
  if not schema.get("required"):
47
196
  schema.pop("required", None)
48
197
  if not schema.get("properties"):
49
198
  schema.pop("properties", None)
50
199
 
51
200
 
52
- def forbid_properties(schema: Dict[str, Any], forbidden: List[str]) -> None:
53
- """Explicitly forbid properties via the `not` keyword."""
54
- not_schema = schema.setdefault("not", {})
55
- already_forbidden = not_schema.setdefault("required", [])
56
- already_forbidden.extend(forbidden)
57
- not_schema["required"] = list(set(chain(already_forbidden, forbidden)))
58
-
59
-
60
- def is_write_only(schema: Dict[str, Any]) -> bool:
201
+ def is_write_only(schema: dict[str, Any] | bool) -> bool:
202
+ if isinstance(schema, bool):
203
+ return False
61
204
  return schema.get("writeOnly", False) or schema.get("x-writeOnly", False)
62
205
 
63
206
 
64
- def is_read_only(schema: Dict[str, Any]) -> bool:
207
+ def is_read_only(schema: dict[str, Any] | bool) -> bool:
208
+ if isinstance(schema, bool):
209
+ return False
65
210
  return schema.get("readOnly", False)
66
-
67
-
68
- def to_json_schema_recursive(
69
- schema: Dict[str, Any], nullable_name: str, is_response_schema: bool = False
70
- ) -> Dict[str, Any]:
71
- return traverse_schema(schema, to_json_schema, nullable_name=nullable_name, is_response_schema=is_response_schema)