schemathesis 3.39.7__py3-none-any.whl → 4.0.0a2__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 (229) hide show
  1. schemathesis/__init__.py +27 -65
  2. schemathesis/auths.py +26 -68
  3. schemathesis/checks.py +130 -60
  4. schemathesis/cli/__init__.py +5 -2105
  5. schemathesis/cli/commands/__init__.py +37 -0
  6. schemathesis/cli/commands/run/__init__.py +662 -0
  7. schemathesis/cli/commands/run/checks.py +80 -0
  8. schemathesis/cli/commands/run/context.py +117 -0
  9. schemathesis/cli/commands/run/events.py +30 -0
  10. schemathesis/cli/commands/run/executor.py +141 -0
  11. schemathesis/cli/commands/run/filters.py +202 -0
  12. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  13. schemathesis/cli/commands/run/handlers/base.py +18 -0
  14. schemathesis/cli/{cassettes.py → commands/run/handlers/cassettes.py} +178 -247
  15. schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
  16. schemathesis/cli/commands/run/handlers/output.py +1368 -0
  17. schemathesis/cli/commands/run/hypothesis.py +105 -0
  18. schemathesis/cli/commands/run/loaders.py +129 -0
  19. schemathesis/cli/{callbacks.py → commands/run/validation.py} +59 -175
  20. schemathesis/cli/constants.py +5 -58
  21. schemathesis/cli/core.py +17 -0
  22. schemathesis/cli/ext/fs.py +14 -0
  23. schemathesis/cli/ext/groups.py +55 -0
  24. schemathesis/cli/{options.py → ext/options.py} +37 -16
  25. schemathesis/cli/hooks.py +36 -0
  26. schemathesis/contrib/__init__.py +1 -3
  27. schemathesis/contrib/openapi/__init__.py +1 -3
  28. schemathesis/contrib/openapi/fill_missing_examples.py +3 -7
  29. schemathesis/core/__init__.py +58 -0
  30. schemathesis/core/compat.py +25 -0
  31. schemathesis/core/control.py +2 -0
  32. schemathesis/core/curl.py +58 -0
  33. schemathesis/core/deserialization.py +65 -0
  34. schemathesis/core/errors.py +370 -0
  35. schemathesis/core/failures.py +315 -0
  36. schemathesis/core/fs.py +19 -0
  37. schemathesis/core/loaders.py +104 -0
  38. schemathesis/core/marks.py +66 -0
  39. schemathesis/{transports/content_types.py → core/media_types.py} +14 -12
  40. schemathesis/{internal/output.py → core/output/__init__.py} +1 -0
  41. schemathesis/core/output/sanitization.py +197 -0
  42. schemathesis/{throttling.py → core/rate_limit.py} +16 -17
  43. schemathesis/core/registries.py +31 -0
  44. schemathesis/core/transforms.py +113 -0
  45. schemathesis/core/transport.py +108 -0
  46. schemathesis/core/validation.py +38 -0
  47. schemathesis/core/version.py +7 -0
  48. schemathesis/engine/__init__.py +30 -0
  49. schemathesis/engine/config.py +59 -0
  50. schemathesis/engine/context.py +119 -0
  51. schemathesis/engine/control.py +36 -0
  52. schemathesis/engine/core.py +157 -0
  53. schemathesis/engine/errors.py +394 -0
  54. schemathesis/engine/events.py +243 -0
  55. schemathesis/engine/phases/__init__.py +66 -0
  56. schemathesis/{runner → engine/phases}/probes.py +49 -68
  57. schemathesis/engine/phases/stateful/__init__.py +66 -0
  58. schemathesis/engine/phases/stateful/_executor.py +301 -0
  59. schemathesis/engine/phases/stateful/context.py +85 -0
  60. schemathesis/engine/phases/unit/__init__.py +175 -0
  61. schemathesis/engine/phases/unit/_executor.py +322 -0
  62. schemathesis/engine/phases/unit/_pool.py +74 -0
  63. schemathesis/engine/recorder.py +246 -0
  64. schemathesis/errors.py +31 -0
  65. schemathesis/experimental/__init__.py +9 -40
  66. schemathesis/filters.py +7 -95
  67. schemathesis/generation/__init__.py +3 -3
  68. schemathesis/generation/case.py +190 -0
  69. schemathesis/generation/coverage.py +22 -22
  70. schemathesis/{_patches.py → generation/hypothesis/__init__.py} +15 -6
  71. schemathesis/generation/hypothesis/builder.py +585 -0
  72. schemathesis/generation/{_hypothesis.py → hypothesis/examples.py} +2 -11
  73. schemathesis/generation/hypothesis/given.py +66 -0
  74. schemathesis/generation/hypothesis/reporting.py +14 -0
  75. schemathesis/generation/hypothesis/strategies.py +16 -0
  76. schemathesis/generation/meta.py +115 -0
  77. schemathesis/generation/modes.py +28 -0
  78. schemathesis/generation/overrides.py +96 -0
  79. schemathesis/generation/stateful/__init__.py +20 -0
  80. schemathesis/{stateful → generation/stateful}/state_machine.py +84 -109
  81. schemathesis/generation/targets.py +69 -0
  82. schemathesis/graphql/__init__.py +15 -0
  83. schemathesis/graphql/checks.py +109 -0
  84. schemathesis/graphql/loaders.py +131 -0
  85. schemathesis/hooks.py +17 -62
  86. schemathesis/openapi/__init__.py +13 -0
  87. schemathesis/openapi/checks.py +387 -0
  88. schemathesis/openapi/generation/__init__.py +0 -0
  89. schemathesis/openapi/generation/filters.py +63 -0
  90. schemathesis/openapi/loaders.py +178 -0
  91. schemathesis/pytest/__init__.py +5 -0
  92. schemathesis/pytest/control_flow.py +7 -0
  93. schemathesis/pytest/lazy.py +273 -0
  94. schemathesis/pytest/loaders.py +12 -0
  95. schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +94 -107
  96. schemathesis/python/__init__.py +0 -0
  97. schemathesis/python/asgi.py +12 -0
  98. schemathesis/python/wsgi.py +12 -0
  99. schemathesis/schemas.py +456 -228
  100. schemathesis/specs/graphql/__init__.py +0 -1
  101. schemathesis/specs/graphql/_cache.py +1 -2
  102. schemathesis/specs/graphql/scalars.py +5 -3
  103. schemathesis/specs/graphql/schemas.py +122 -123
  104. schemathesis/specs/graphql/validation.py +11 -17
  105. schemathesis/specs/openapi/__init__.py +6 -1
  106. schemathesis/specs/openapi/_cache.py +1 -2
  107. schemathesis/specs/openapi/_hypothesis.py +97 -134
  108. schemathesis/specs/openapi/checks.py +238 -219
  109. schemathesis/specs/openapi/converter.py +4 -4
  110. schemathesis/specs/openapi/definitions.py +1 -1
  111. schemathesis/specs/openapi/examples.py +22 -20
  112. schemathesis/specs/openapi/expressions/__init__.py +11 -15
  113. schemathesis/specs/openapi/expressions/extractors.py +1 -4
  114. schemathesis/specs/openapi/expressions/nodes.py +33 -32
  115. schemathesis/specs/openapi/formats.py +3 -2
  116. schemathesis/specs/openapi/links.py +123 -299
  117. schemathesis/specs/openapi/media_types.py +10 -12
  118. schemathesis/specs/openapi/negative/__init__.py +2 -1
  119. schemathesis/specs/openapi/negative/mutations.py +3 -2
  120. schemathesis/specs/openapi/parameters.py +8 -6
  121. schemathesis/specs/openapi/patterns.py +1 -1
  122. schemathesis/specs/openapi/references.py +11 -51
  123. schemathesis/specs/openapi/schemas.py +177 -191
  124. schemathesis/specs/openapi/security.py +1 -1
  125. schemathesis/specs/openapi/serialization.py +10 -6
  126. schemathesis/specs/openapi/stateful/__init__.py +97 -91
  127. schemathesis/transport/__init__.py +104 -0
  128. schemathesis/transport/asgi.py +26 -0
  129. schemathesis/transport/prepare.py +99 -0
  130. schemathesis/transport/requests.py +221 -0
  131. schemathesis/{_xml.py → transport/serialization.py} +69 -7
  132. schemathesis/transport/wsgi.py +165 -0
  133. {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/METADATA +18 -14
  134. schemathesis-4.0.0a2.dist-info/RECORD +151 -0
  135. {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/entry_points.txt +1 -1
  136. schemathesis/_compat.py +0 -74
  137. schemathesis/_dependency_versions.py +0 -19
  138. schemathesis/_hypothesis.py +0 -559
  139. schemathesis/_override.py +0 -50
  140. schemathesis/_rate_limiter.py +0 -7
  141. schemathesis/cli/context.py +0 -75
  142. schemathesis/cli/debug.py +0 -27
  143. schemathesis/cli/handlers.py +0 -19
  144. schemathesis/cli/junitxml.py +0 -124
  145. schemathesis/cli/output/__init__.py +0 -1
  146. schemathesis/cli/output/default.py +0 -936
  147. schemathesis/cli/output/short.py +0 -59
  148. schemathesis/cli/reporting.py +0 -79
  149. schemathesis/cli/sanitization.py +0 -26
  150. schemathesis/code_samples.py +0 -151
  151. schemathesis/constants.py +0 -56
  152. schemathesis/contrib/openapi/formats/__init__.py +0 -9
  153. schemathesis/contrib/openapi/formats/uuid.py +0 -16
  154. schemathesis/contrib/unique_data.py +0 -41
  155. schemathesis/exceptions.py +0 -571
  156. schemathesis/extra/_aiohttp.py +0 -28
  157. schemathesis/extra/_flask.py +0 -13
  158. schemathesis/extra/_server.py +0 -18
  159. schemathesis/failures.py +0 -277
  160. schemathesis/fixups/__init__.py +0 -37
  161. schemathesis/fixups/fast_api.py +0 -41
  162. schemathesis/fixups/utf8_bom.py +0 -28
  163. schemathesis/generation/_methods.py +0 -44
  164. schemathesis/graphql.py +0 -3
  165. schemathesis/internal/__init__.py +0 -7
  166. schemathesis/internal/checks.py +0 -84
  167. schemathesis/internal/copy.py +0 -32
  168. schemathesis/internal/datetime.py +0 -5
  169. schemathesis/internal/deprecation.py +0 -38
  170. schemathesis/internal/diff.py +0 -15
  171. schemathesis/internal/extensions.py +0 -27
  172. schemathesis/internal/jsonschema.py +0 -36
  173. schemathesis/internal/transformation.py +0 -26
  174. schemathesis/internal/validation.py +0 -34
  175. schemathesis/lazy.py +0 -474
  176. schemathesis/loaders.py +0 -122
  177. schemathesis/models.py +0 -1341
  178. schemathesis/parameters.py +0 -90
  179. schemathesis/runner/__init__.py +0 -605
  180. schemathesis/runner/events.py +0 -389
  181. schemathesis/runner/impl/__init__.py +0 -3
  182. schemathesis/runner/impl/context.py +0 -104
  183. schemathesis/runner/impl/core.py +0 -1246
  184. schemathesis/runner/impl/solo.py +0 -80
  185. schemathesis/runner/impl/threadpool.py +0 -391
  186. schemathesis/runner/serialization.py +0 -544
  187. schemathesis/sanitization.py +0 -252
  188. schemathesis/serializers.py +0 -328
  189. schemathesis/service/__init__.py +0 -18
  190. schemathesis/service/auth.py +0 -11
  191. schemathesis/service/ci.py +0 -202
  192. schemathesis/service/client.py +0 -133
  193. schemathesis/service/constants.py +0 -38
  194. schemathesis/service/events.py +0 -61
  195. schemathesis/service/extensions.py +0 -224
  196. schemathesis/service/hosts.py +0 -111
  197. schemathesis/service/metadata.py +0 -71
  198. schemathesis/service/models.py +0 -258
  199. schemathesis/service/report.py +0 -255
  200. schemathesis/service/serialization.py +0 -173
  201. schemathesis/service/usage.py +0 -66
  202. schemathesis/specs/graphql/loaders.py +0 -364
  203. schemathesis/specs/openapi/expressions/context.py +0 -16
  204. schemathesis/specs/openapi/loaders.py +0 -708
  205. schemathesis/specs/openapi/stateful/statistic.py +0 -198
  206. schemathesis/specs/openapi/stateful/types.py +0 -14
  207. schemathesis/specs/openapi/validation.py +0 -26
  208. schemathesis/stateful/__init__.py +0 -147
  209. schemathesis/stateful/config.py +0 -97
  210. schemathesis/stateful/context.py +0 -135
  211. schemathesis/stateful/events.py +0 -274
  212. schemathesis/stateful/runner.py +0 -309
  213. schemathesis/stateful/sink.py +0 -68
  214. schemathesis/stateful/statistic.py +0 -22
  215. schemathesis/stateful/validation.py +0 -100
  216. schemathesis/targets.py +0 -77
  217. schemathesis/transports/__init__.py +0 -359
  218. schemathesis/transports/asgi.py +0 -7
  219. schemathesis/transports/auth.py +0 -38
  220. schemathesis/transports/headers.py +0 -36
  221. schemathesis/transports/responses.py +0 -57
  222. schemathesis/types.py +0 -44
  223. schemathesis/utils.py +0 -164
  224. schemathesis-3.39.7.dist-info/RECORD +0 -160
  225. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
  226. /schemathesis/{_lazy_import.py → core/lazy_import.py} +0 -0
  227. /schemathesis/{internal → core}/result.py +0 -0
  228. {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/WHEEL +0 -0
  229. {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/licenses/LICENSE +0 -0
schemathesis/failures.py DELETED
@@ -1,277 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import textwrap
4
- from dataclasses import dataclass
5
- from typing import TYPE_CHECKING, Any
6
-
7
- from schemathesis.internal.output import OutputConfig
8
-
9
- if TYPE_CHECKING:
10
- from json import JSONDecodeError
11
-
12
- from graphql.error import GraphQLFormattedError
13
- from jsonschema import ValidationError
14
-
15
-
16
- class FailureContext:
17
- """Additional data specific to certain failure kind."""
18
-
19
- # Short description of what happened
20
- title: str
21
- # A longer one
22
- message: str
23
- type: str
24
-
25
- def unique_by_key(self, check_message: str | None) -> tuple[str, ...]:
26
- """A key to distinguish different failure contexts."""
27
- return (check_message or self.message,)
28
-
29
-
30
- @dataclass(repr=False)
31
- class ValidationErrorContext(FailureContext):
32
- """Additional information about JSON Schema validation errors."""
33
-
34
- validation_message: str
35
- schema_path: list[str | int]
36
- schema: dict[str, Any] | bool
37
- instance_path: list[str | int]
38
- instance: None | bool | float | str | list | dict[str, Any]
39
- message: str
40
- title: str = "Response violates schema"
41
- type: str = "json_schema"
42
-
43
- def unique_by_key(self, check_message: str | None) -> tuple[str, ...]:
44
- # Deduplicate by JSON Schema path. All errors that happened on this sub-schema will be deduplicated
45
- return ("/".join(map(str, self.schema_path)),)
46
-
47
- @classmethod
48
- def from_exception(
49
- cls, exc: ValidationError, *, output_config: OutputConfig | None = None
50
- ) -> ValidationErrorContext:
51
- from .internal.output import truncate_json
52
-
53
- output_config = OutputConfig.from_parent(output_config, max_lines=20)
54
- schema = textwrap.indent(truncate_json(exc.schema, config=output_config), prefix=" ")
55
- value = textwrap.indent(truncate_json(exc.instance, config=output_config), prefix=" ")
56
- schema_path = list(exc.absolute_schema_path)
57
- if len(schema_path) > 1:
58
- # Exclude the last segment, which is already in the schema
59
- schema_title = "Schema at "
60
- for segment in schema_path[:-1]:
61
- schema_title += f"/{segment}"
62
- else:
63
- schema_title = "Schema"
64
- message = f"{exc.message}\n\n{schema_title}:\n\n{schema}\n\nValue:\n\n{value}"
65
- return cls(
66
- message=message,
67
- validation_message=exc.message,
68
- schema_path=schema_path,
69
- schema=exc.schema,
70
- instance_path=list(exc.absolute_path),
71
- instance=exc.instance,
72
- )
73
-
74
-
75
- @dataclass(repr=False)
76
- class JSONDecodeErrorContext(FailureContext):
77
- """Failed to decode JSON."""
78
-
79
- validation_message: str
80
- document: str
81
- position: int
82
- lineno: int
83
- colno: int
84
- message: str
85
- title: str = "JSON deserialization error"
86
- type: str = "json_decode"
87
-
88
- def unique_by_key(self, check_message: str | None) -> tuple[str, ...]:
89
- # Treat different JSON decoding failures as the same issue
90
- # Payloads often contain dynamic data and distinguishing it by the error location still would not be sufficient
91
- # as it may be different on different dynamic payloads
92
- return (self.title,)
93
-
94
- @classmethod
95
- def from_exception(cls, exc: JSONDecodeError) -> JSONDecodeErrorContext:
96
- return cls(
97
- message=str(exc),
98
- validation_message=exc.msg,
99
- document=exc.doc,
100
- position=exc.pos,
101
- lineno=exc.lineno,
102
- colno=exc.colno,
103
- )
104
-
105
-
106
- @dataclass(repr=False)
107
- class ServerError(FailureContext):
108
- status_code: int
109
- title: str = "Server error"
110
- message: str = ""
111
- type: str = "server_error"
112
-
113
-
114
- @dataclass(repr=False)
115
- class MissingContentType(FailureContext):
116
- """Content type header is missing."""
117
-
118
- media_types: list[str]
119
- message: str
120
- title: str = "Missing Content-Type header"
121
- type: str = "missing_content_type"
122
-
123
-
124
- @dataclass(repr=False)
125
- class UndefinedContentType(FailureContext):
126
- """Response has Content-Type that is not documented in the schema."""
127
-
128
- content_type: str
129
- defined_content_types: list[str]
130
- message: str
131
- title: str = "Undocumented Content-Type"
132
- type: str = "undefined_content_type"
133
-
134
-
135
- @dataclass(repr=False)
136
- class AcceptedNegativeData(FailureContext):
137
- """Response with negative data was accepted."""
138
-
139
- message: str
140
- status_code: int
141
- allowed_statuses: list[str]
142
- title: str = "Accepted negative data"
143
- type: str = "accepted_negative_data"
144
-
145
-
146
- @dataclass(repr=False)
147
- class RejectedPositiveData(FailureContext):
148
- """Response with positive data was rejected."""
149
-
150
- message: str
151
- status_code: int
152
- allowed_statuses: list[str]
153
- title: str = "Rejected positive data"
154
- type: str = "rejected_positive_data"
155
-
156
-
157
- @dataclass(repr=False)
158
- class UseAfterFree(FailureContext):
159
- """Resource was used after a successful DELETE operation on it."""
160
-
161
- message: str
162
- free: str
163
- usage: str
164
- title: str = "Use after free"
165
- type: str = "use_after_free"
166
-
167
-
168
- @dataclass(repr=False)
169
- class EnsureResourceAvailability(FailureContext):
170
- """Resource is not available immediately after creation."""
171
-
172
- message: str
173
- created_with: str
174
- not_available_with: str
175
- title: str = "Resource is not available after creation"
176
- type: str = "ensure_resource_availability"
177
-
178
-
179
- @dataclass(repr=False)
180
- class IgnoredAuth(FailureContext):
181
- """The API operation does not check the specified authentication."""
182
-
183
- message: str
184
- title: str = "Authentication declared but not enforced for this operation"
185
- type: str = "ignored_auth"
186
-
187
-
188
- @dataclass(repr=False)
189
- class UndefinedStatusCode(FailureContext):
190
- """Response has a status code that is not defined in the schema."""
191
-
192
- # Response's status code
193
- status_code: int
194
- # Status codes as defined in schema
195
- defined_status_codes: list[str]
196
- # Defined status code with expanded wildcards
197
- allowed_status_codes: list[int]
198
- message: str
199
- title: str = "Undocumented HTTP status code"
200
- type: str = "undefined_status_code"
201
-
202
-
203
- @dataclass(repr=False)
204
- class MissingHeaders(FailureContext):
205
- """Some required headers are missing."""
206
-
207
- missing_headers: list[str]
208
- message: str
209
- title: str = "Missing required headers"
210
- type: str = "missing_headers"
211
-
212
-
213
- @dataclass(repr=False)
214
- class MalformedMediaType(FailureContext):
215
- """Media type name is malformed.
216
-
217
- Example: `application-json` instead of `application/json`
218
- """
219
-
220
- actual: str
221
- defined: str
222
- message: str
223
- title: str = "Malformed media type"
224
- type: str = "malformed_media_type"
225
-
226
-
227
- @dataclass(repr=False)
228
- class ResponseTimeExceeded(FailureContext):
229
- """Response took longer than expected."""
230
-
231
- elapsed: float
232
- deadline: int
233
- message: str
234
- title: str = "Response time limit exceeded"
235
- type: str = "response_time_exceeded"
236
-
237
- def unique_by_key(self, check_message: str | None) -> tuple[str, ...]:
238
- return (self.title,)
239
-
240
-
241
- @dataclass(repr=False)
242
- class RequestTimeout(FailureContext):
243
- """Request took longer than timeout."""
244
-
245
- timeout: int
246
- message: str
247
- title: str = "Response timeout"
248
- type: str = "request_timeout"
249
-
250
-
251
- @dataclass(repr=False)
252
- class UnexpectedGraphQLResponse(FailureContext):
253
- """GraphQL response is not a JSON object."""
254
-
255
- message: str
256
- title: str = "Unexpected GraphQL Response"
257
- type: str = "graphql_unexpected_response"
258
-
259
-
260
- @dataclass(repr=False)
261
- class GraphQLClientError(FailureContext):
262
- """GraphQL query has not been executed."""
263
-
264
- message: str
265
- errors: list[GraphQLFormattedError]
266
- title: str = "GraphQL client error"
267
- type: str = "graphql_client_error"
268
-
269
-
270
- @dataclass(repr=False)
271
- class GraphQLServerError(FailureContext):
272
- """GraphQL response indicates at least one server error."""
273
-
274
- message: str
275
- errors: list[GraphQLFormattedError]
276
- title: str = "GraphQL server error"
277
- type: str = "graphql_server_error"
@@ -1,37 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Iterable
4
-
5
- from . import fast_api, utf8_bom
6
-
7
- ALL_FIXUPS = {"fast_api": fast_api, "utf8_bom": utf8_bom}
8
- ALL_FIXUP_NAMES = list(ALL_FIXUPS.keys())
9
-
10
-
11
- def install(fixups: Iterable[str] | None = None) -> None:
12
- """Install fixups.
13
-
14
- Without the first argument installs all available fixups.
15
-
16
- :param fixups: Names of fixups to install.
17
- """
18
- fixups = fixups or ALL_FIXUP_NAMES
19
- for name in fixups:
20
- ALL_FIXUPS[name].install() # type: ignore
21
-
22
-
23
- def uninstall(fixups: Iterable[str] | None = None) -> None:
24
- """Uninstall fixups.
25
-
26
- Without the first argument uninstalls all available fixups.
27
-
28
- :param fixups: Names of fixups to uninstall.
29
- """
30
- fixups = fixups or ALL_FIXUP_NAMES
31
- for name in fixups:
32
- ALL_FIXUPS[name].uninstall() # type: ignore
33
-
34
-
35
- def is_installed(name: str) -> bool:
36
- """Check whether fixup is installed."""
37
- return ALL_FIXUPS[name].is_installed()
@@ -1,41 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
- from ..hooks import HookContext, register, unregister
6
- from ..hooks import is_installed as global_is_installed
7
- from ..internal.jsonschema import traverse_schema
8
-
9
-
10
- def install() -> None:
11
- register(before_load_schema)
12
-
13
-
14
- def uninstall() -> None:
15
- unregister(before_load_schema)
16
-
17
-
18
- def is_installed() -> bool:
19
- return global_is_installed("before_load_schema", before_load_schema)
20
-
21
-
22
- def before_load_schema(context: HookContext, schema: dict[str, Any]) -> None:
23
- adjust_schema(schema)
24
-
25
-
26
- def adjust_schema(schema: dict[str, Any]) -> None:
27
- traverse_schema(schema, _handle_boundaries)
28
-
29
-
30
- def _handle_boundaries(schema: dict[str, Any]) -> dict[str, Any]:
31
- """Convert Draft 7 keywords to Draft 4 compatible versions.
32
-
33
- FastAPI uses ``pydantic``, which generates Draft 7 compatible schemas.
34
- """
35
- for boundary_name, boundary_exclusive_name in (("maximum", "exclusiveMaximum"), ("minimum", "exclusiveMinimum")):
36
- value = schema.get(boundary_exclusive_name)
37
- # `bool` check is needed, since in Python `True` is an instance of `int`
38
- if isinstance(value, (int, float)) and not isinstance(value, bool):
39
- schema[boundary_exclusive_name] = True
40
- schema[boundary_name] = value
41
- return schema
@@ -1,28 +0,0 @@
1
- from typing import TYPE_CHECKING
2
-
3
- from ..constants import BOM_MARK
4
- from ..hooks import HookContext, register, unregister
5
- from ..hooks import is_installed as global_is_installed
6
-
7
- if TYPE_CHECKING:
8
- from ..models import Case
9
- from ..transports.responses import GenericResponse
10
-
11
-
12
- def install() -> None:
13
- register(after_call)
14
-
15
-
16
- def uninstall() -> None:
17
- unregister(after_call)
18
-
19
-
20
- def is_installed() -> bool:
21
- return global_is_installed("after_call", after_call)
22
-
23
-
24
- def after_call(context: HookContext, case: "Case", response: "GenericResponse") -> None:
25
- from requests import Response
26
-
27
- if isinstance(response, Response) and response.encoding == "utf-8" and response.text[0:1] == BOM_MARK:
28
- response.encoding = "utf-8-sig"
@@ -1,44 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from enum import Enum
4
- from typing import Iterable, Union
5
-
6
-
7
- class DataGenerationMethod(str, Enum):
8
- """Defines what data Schemathesis generates for tests."""
9
-
10
- # Generate data, that fits the API schema
11
- positive = "positive"
12
- # Doesn't fit the API schema
13
- negative = "negative"
14
-
15
- @classmethod
16
- def default(cls) -> DataGenerationMethod:
17
- return cls.positive
18
-
19
- @classmethod
20
- def all(cls) -> list[DataGenerationMethod]:
21
- return list(DataGenerationMethod)
22
-
23
- def as_short_name(self) -> str:
24
- return {
25
- DataGenerationMethod.positive: "P",
26
- DataGenerationMethod.negative: "N",
27
- }[self]
28
-
29
- @property
30
- def is_positive(self) -> bool:
31
- return self == DataGenerationMethod.positive
32
-
33
- @property
34
- def is_negative(self) -> bool:
35
- return self == DataGenerationMethod.negative
36
-
37
- @classmethod
38
- def ensure_list(cls, value: DataGenerationMethodInput) -> list[DataGenerationMethod]:
39
- if isinstance(value, DataGenerationMethod):
40
- return [value]
41
- return list(value)
42
-
43
-
44
- DataGenerationMethodInput = Union[DataGenerationMethod, Iterable[DataGenerationMethod]]
schemathesis/graphql.py DELETED
@@ -1,3 +0,0 @@
1
- from .specs.graphql import nodes # noqa: F401
2
- from .specs.graphql.loaders import from_asgi, from_dict, from_file, from_path, from_url, from_wsgi # noqa: F401
3
- from .specs.graphql.scalars import scalar # noqa: F401
@@ -1,7 +0,0 @@
1
- """A private API to work with Schemathesis internals."""
2
-
3
-
4
- def clear_cache() -> None:
5
- from ..specs.openapi import _hypothesis
6
-
7
- _hypothesis.clear_cache()
@@ -1,84 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import inspect
4
- import warnings
5
- from dataclasses import dataclass, field
6
- from typing import TYPE_CHECKING, Callable, Optional
7
-
8
- if TYPE_CHECKING:
9
- from requests.auth import HTTPDigestAuth
10
- from requests.structures import CaseInsensitiveDict
11
-
12
- from .._override import CaseOverride
13
- from ..models import Case
14
- from ..transports.responses import GenericResponse
15
- from ..types import RawAuth
16
-
17
-
18
- CheckFunction = Callable[["CheckContext", "GenericResponse", "Case"], Optional[bool]]
19
-
20
-
21
- @dataclass
22
- class NegativeDataRejectionConfig:
23
- # 5xx will pass through
24
- allowed_statuses: list[str] = field(default_factory=lambda: ["400", "401", "403", "404", "422", "428", "5xx"])
25
-
26
-
27
- @dataclass
28
- class PositiveDataAcceptanceConfig:
29
- allowed_statuses: list[str] = field(default_factory=lambda: ["2xx", "401", "403", "404"])
30
-
31
-
32
- @dataclass
33
- class MissingRequiredHeaderConfig:
34
- allowed_statuses: list[str] = field(default_factory=lambda: ["406"])
35
-
36
-
37
- @dataclass
38
- class CheckConfig:
39
- missing_required_header: MissingRequiredHeaderConfig = field(default_factory=MissingRequiredHeaderConfig)
40
- negative_data_rejection: NegativeDataRejectionConfig = field(default_factory=NegativeDataRejectionConfig)
41
- positive_data_acceptance: PositiveDataAcceptanceConfig = field(default_factory=PositiveDataAcceptanceConfig)
42
-
43
-
44
- @dataclass
45
- class CheckContext:
46
- """Context for Schemathesis checks.
47
-
48
- Provides access to broader test execution data beyond individual test cases.
49
- """
50
-
51
- override: CaseOverride | None
52
- auth: HTTPDigestAuth | RawAuth | None
53
- headers: CaseInsensitiveDict | None
54
- config: CheckConfig = field(default_factory=CheckConfig)
55
- transport_kwargs: dict | None = None
56
-
57
-
58
- def wrap_check(check: Callable) -> CheckFunction:
59
- """Make older checks compatible with the new signature."""
60
- signature = inspect.signature(check)
61
- parameters = len(signature.parameters)
62
-
63
- if parameters == 3:
64
- # New style check, return as is
65
- return check
66
-
67
- if parameters == 2:
68
- # Old style check, wrap it
69
- warnings.warn(
70
- f"The check function '{check.__name__}' uses an outdated signature. "
71
- "Please update it to accept 'ctx' as the first argument: "
72
- "(ctx: CheckContext, response: GenericResponse, case: Case) -> Optional[bool]",
73
- DeprecationWarning,
74
- stacklevel=2,
75
- )
76
-
77
- def wrapper(_: CheckContext, response: GenericResponse, case: Case) -> Optional[bool]:
78
- return check(response, case)
79
-
80
- wrapper.__name__ = check.__name__
81
-
82
- return wrapper
83
-
84
- raise ValueError(f"Invalid check function signature. Expected 2 or 3 parameters, got {parameters}")
@@ -1,32 +0,0 @@
1
- from typing import Any
2
-
3
- from .extensions import extensible
4
-
5
-
6
- @extensible("SCHEMATHESIS_EXTENSION_FAST_DEEP_COPY")
7
- def fast_deepcopy(value: Any) -> Any:
8
- """A specialized version of `deepcopy` that copies only `dict` and `list` and does unrolling.
9
-
10
- It is on average 3x faster than `deepcopy` and given the amount of calls, it is an important optimization.
11
- """
12
- if isinstance(value, dict):
13
- return {
14
- k1: (
15
- {k2: fast_deepcopy(v2) for k2, v2 in v1.items()}
16
- if isinstance(v1, dict)
17
- else [fast_deepcopy(v2) for v2 in v1]
18
- if isinstance(v1, list)
19
- else v1
20
- )
21
- for k1, v1 in value.items()
22
- }
23
- if isinstance(value, list):
24
- return [
25
- {k2: fast_deepcopy(v2) for k2, v2 in v1.items()}
26
- if isinstance(v1, dict)
27
- else [fast_deepcopy(v2) for v2 in v1]
28
- if isinstance(v1, list)
29
- else v1
30
- for v1 in value
31
- ]
32
- return value
@@ -1,5 +0,0 @@
1
- from datetime import datetime, timezone
2
-
3
-
4
- def current_datetime() -> str:
5
- return datetime.now(timezone.utc).astimezone().isoformat()
@@ -1,38 +0,0 @@
1
- import warnings
2
- from typing import Any, Callable
3
-
4
-
5
- def _warn_deprecation(*, kind: str, thing: str, removed_in: str, replacement: str) -> None:
6
- warnings.warn(
7
- f"{kind} `{thing}` is deprecated and will be removed in Schemathesis {removed_in}. "
8
- f"Use {replacement} instead.",
9
- DeprecationWarning,
10
- stacklevel=1,
11
- )
12
-
13
-
14
- def deprecated_property(*, removed_in: str, replacement: str) -> Callable:
15
- def wrapper(prop: Callable) -> Callable:
16
- @property # type: ignore
17
- def inner(self: Any) -> Any:
18
- _warn_deprecation(kind="Property", thing=prop.__name__, removed_in=removed_in, replacement=replacement)
19
- return prop(self)
20
-
21
- return inner
22
-
23
- return wrapper
24
-
25
-
26
- def warn_filtration_arguments(name: str) -> None:
27
- _warn_deprecation(kind="Argument", thing=name, removed_in="4.0", replacement="`include` and `exclude` methods")
28
-
29
-
30
- def deprecated_function(*, removed_in: str, replacement: str) -> Callable:
31
- def wrapper(func: Callable) -> Callable:
32
- def inner(*args: Any, **kwargs: Any) -> Any:
33
- _warn_deprecation(kind="Function", thing=func.__name__, removed_in=removed_in, replacement=replacement)
34
- return func(*args, **kwargs)
35
-
36
- return inner
37
-
38
- return wrapper
@@ -1,15 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any, Mapping
4
-
5
-
6
- def diff(left: Mapping[str, Any], right: Mapping[str, Any]) -> dict[str, Any]:
7
- """Calculate the difference between two dictionaries."""
8
- diff = {}
9
- for key, value in right.items():
10
- if key not in left or left[key] != value:
11
- diff[key] = value
12
- for key in left:
13
- if key not in right:
14
- diff[key] = None # Mark deleted items as None
15
- return diff
@@ -1,27 +0,0 @@
1
- import os
2
- from typing import Any, Callable
3
-
4
-
5
- class ExtensionLoadingError(ImportError):
6
- """Raised when an extension cannot be loaded."""
7
-
8
-
9
- def import_extension(path: str) -> Any:
10
- try:
11
- module, item = path.rsplit(".", 1)
12
- imported = __import__(module, fromlist=[item])
13
- return getattr(imported, item)
14
- except ValueError as exc:
15
- raise ExtensionLoadingError(f"Invalid path: {path}") from exc
16
- except (ImportError, AttributeError) as exc:
17
- raise ExtensionLoadingError(f"Could not import {path}") from exc
18
-
19
-
20
- def extensible(env_var: str) -> Callable[[Any], Any]:
21
- def decorator(item: Any) -> Any:
22
- path = os.getenv(env_var)
23
- if path is not None:
24
- return import_extension(path)
25
- return item
26
-
27
- return decorator
@@ -1,36 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any, Callable, Dict, List, Union, overload
4
-
5
- JsonValue = Union[Dict[str, Any], List, str, float, int]
6
-
7
-
8
- @overload
9
- def traverse_schema(schema: dict[str, Any], callback: Callable, *args: Any, **kwargs: Any) -> dict[str, Any]:
10
- pass
11
-
12
-
13
- @overload
14
- def traverse_schema(schema: list, callback: Callable, *args: Any, **kwargs: Any) -> list:
15
- pass
16
-
17
-
18
- @overload
19
- def traverse_schema(schema: str, callback: Callable, *args: Any, **kwargs: Any) -> str:
20
- pass
21
-
22
-
23
- @overload
24
- def traverse_schema(schema: float, callback: Callable, *args: Any, **kwargs: Any) -> float:
25
- pass
26
-
27
-
28
- def traverse_schema(schema: JsonValue, callback: Callable[..., dict[str, Any]], *args: Any, **kwargs: Any) -> JsonValue:
29
- """Apply callback recursively to the given schema."""
30
- if isinstance(schema, dict):
31
- schema = callback(schema, *args, **kwargs)
32
- for key, sub_item in schema.items():
33
- schema[key] = traverse_schema(sub_item, callback, *args, **kwargs)
34
- elif isinstance(schema, list):
35
- schema = [traverse_schema(sub_item, callback, *args, **kwargs) for sub_item in schema]
36
- return schema