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
schemathesis/checks.py CHANGED
@@ -1,35 +1,200 @@
1
- from typing import TYPE_CHECKING, Optional, Tuple
2
-
3
- from . import failures
4
- from .exceptions import get_server_error
5
- from .specs.openapi.checks import (
6
- content_type_conformance,
7
- response_headers_conformance,
8
- response_schema_conformance,
9
- status_code_conformance,
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator, Optional
5
+
6
+ from schemathesis.config import ChecksConfig
7
+ from schemathesis.core.failures import (
8
+ CustomFailure,
9
+ Failure,
10
+ FailureGroup,
11
+ MalformedJson,
12
+ ResponseTimeExceeded,
13
+ ServerError,
10
14
  )
11
- from .utils import GenericResponse
15
+ from schemathesis.core.registries import Registry
16
+ from schemathesis.core.transport import Response
17
+ from schemathesis.generation.overrides import Override
12
18
 
13
19
  if TYPE_CHECKING:
14
- from .models import Case, CheckFunction
20
+ from requests.models import CaseInsensitiveDict
21
+
22
+ from schemathesis.engine.recorder import ScenarioRecorder
23
+ from schemathesis.generation.case import Case
24
+
25
+ CheckFunction = Callable[["CheckContext", "Response", "Case"], Optional[bool]]
26
+
27
+
28
+ class CheckContext:
29
+ """Runtime context passed to validation check functions during API testing.
30
+
31
+ Provides access to configuration for currently checked endpoint.
32
+ """
33
+
34
+ _override: Override | None
35
+ _auth: tuple[str, str] | None
36
+ _headers: CaseInsensitiveDict | None
37
+ config: ChecksConfig
38
+ """Configuration settings for validation checks."""
39
+ _transport_kwargs: dict[str, Any] | None
40
+ _recorder: ScenarioRecorder | None
41
+ _checks: list[CheckFunction]
42
+
43
+ __slots__ = ("_override", "_auth", "_headers", "config", "_transport_kwargs", "_recorder", "_checks")
44
+
45
+ def __init__(
46
+ self,
47
+ override: Override | None,
48
+ auth: tuple[str, str] | None,
49
+ headers: CaseInsensitiveDict | None,
50
+ config: ChecksConfig,
51
+ transport_kwargs: dict[str, Any] | None,
52
+ recorder: ScenarioRecorder | None = None,
53
+ ) -> None:
54
+ self._override = override
55
+ self._auth = auth
56
+ self._headers = headers
57
+ self.config = config
58
+ self._transport_kwargs = transport_kwargs
59
+ self._recorder = recorder
60
+ self._checks = []
61
+ for check in CHECKS.get_all():
62
+ name = check.__name__
63
+ if self.config.get_by_name(name=name).enabled:
64
+ self._checks.append(check)
65
+ if self.config.max_response_time.enabled:
66
+ self._checks.append(max_response_time)
67
+
68
+ def _find_parent(self, *, case_id: str) -> Case | None:
69
+ if self._recorder is not None:
70
+ return self._recorder.find_parent(case_id=case_id)
71
+ return None
72
+
73
+ def _find_related(self, *, case_id: str) -> Iterator[Case]:
74
+ if self._recorder is not None:
75
+ yield from self._recorder.find_related(case_id=case_id)
76
+
77
+ def _find_response(self, *, case_id: str) -> Response | None:
78
+ if self._recorder is not None:
79
+ return self._recorder.find_response(case_id=case_id)
80
+ return None
81
+
82
+ def _record_case(self, *, parent_id: str, case: Case) -> None:
83
+ if self._recorder is not None:
84
+ self._recorder.record_case(parent_id=parent_id, case=case, transition=None, is_transition_applied=False)
85
+
86
+ def _record_response(self, *, case_id: str, response: Response) -> None:
87
+ if self._recorder is not None:
88
+ self._recorder.record_response(case_id=case_id, response=response)
89
+
90
+
91
+ CHECKS = Registry[CheckFunction]()
92
+
93
+
94
+ def load_all_checks() -> None:
95
+ # NOTE: Trigger registering all Open API checks
96
+ from schemathesis.specs.openapi.checks import status_code_conformance # noqa: F401
97
+
15
98
 
99
+ def check(func: CheckFunction) -> CheckFunction:
100
+ """Register a custom validation check to run against API responses.
16
101
 
17
- def not_a_server_error(response: GenericResponse, case: "Case") -> Optional[bool]: # pylint: disable=useless-return
102
+ Args:
103
+ func: Function that takes `(ctx: CheckContext, response: Response, case: Case)` and raises `AssertionError` on validation failure
104
+
105
+ Example:
106
+ ```python
107
+ import schemathesis
108
+
109
+ @schemathesis.check
110
+ def check_cors_headers(ctx, response, case):
111
+ \"\"\"Verify CORS headers are present\"\"\"
112
+ if "Access-Control-Allow-Origin" not in response.headers:
113
+ raise AssertionError("Missing CORS headers")
114
+ ```
115
+
116
+ """
117
+ return CHECKS.register(func)
118
+
119
+
120
+ @check
121
+ def not_a_server_error(ctx: CheckContext, response: Response, case: Case) -> bool | None:
18
122
  """A check to verify that the response is not a server-side error."""
19
- if response.status_code >= 500:
20
- exc_class = get_server_error(response.status_code)
21
- raise exc_class(
22
- f"Received a response with 5xx status code: {response.status_code}",
23
- context=failures.ServerError(status_code=response.status_code),
123
+ from schemathesis.specs.graphql.schemas import GraphQLSchema
124
+ from schemathesis.specs.graphql.validation import validate_graphql_response
125
+ from schemathesis.specs.openapi.utils import expand_status_codes
126
+
127
+ expected_statuses = expand_status_codes(ctx.config.not_a_server_error.expected_statuses or [])
128
+
129
+ status_code = response.status_code
130
+ if status_code not in expected_statuses:
131
+ raise ServerError(operation=case.operation.label, status_code=status_code)
132
+ if isinstance(case.operation.schema, GraphQLSchema):
133
+ try:
134
+ data = response.json()
135
+ validate_graphql_response(case, data)
136
+ except json.JSONDecodeError as exc:
137
+ raise MalformedJson.from_exception(operation=case.operation.label, exc=exc) from None
138
+ return None
139
+
140
+
141
+ DEFAULT_MAX_RESPONSE_TIME = 10.0
142
+
143
+
144
+ def max_response_time(ctx: CheckContext, response: Response, case: Case) -> bool | None:
145
+ limit = ctx.config.max_response_time.limit or DEFAULT_MAX_RESPONSE_TIME
146
+ elapsed = response.elapsed
147
+ if elapsed > limit:
148
+ raise ResponseTimeExceeded(
149
+ operation=case.operation.label,
150
+ message=f"Actual: {elapsed:.2f}ms\nLimit: {limit * 1000:.2f}ms",
151
+ elapsed=elapsed,
152
+ deadline=limit,
24
153
  )
25
154
  return None
26
155
 
27
156
 
28
- DEFAULT_CHECKS: Tuple["CheckFunction", ...] = (not_a_server_error,)
29
- OPTIONAL_CHECKS = (
30
- status_code_conformance,
31
- content_type_conformance,
32
- response_headers_conformance,
33
- response_schema_conformance,
34
- )
35
- ALL_CHECKS: Tuple["CheckFunction", ...] = DEFAULT_CHECKS + OPTIONAL_CHECKS
157
+ def run_checks(
158
+ *,
159
+ case: Case,
160
+ response: Response,
161
+ ctx: CheckContext,
162
+ checks: Iterable[CheckFunction],
163
+ on_failure: Callable[[str, set[Failure], Failure], None],
164
+ on_success: Callable[[str, Case], None] | None = None,
165
+ ) -> set[Failure]:
166
+ """Run a set of checks against a response."""
167
+ collected: set[Failure] = set()
168
+
169
+ for check in checks:
170
+ name = check.__name__
171
+ try:
172
+ skip_check = check(ctx, response, case)
173
+ if not skip_check and on_success:
174
+ on_success(name, case)
175
+ except Failure as failure:
176
+ on_failure(name, collected, failure.with_traceback(None))
177
+ except AssertionError as exc:
178
+ custom_failure = CustomFailure(
179
+ operation=case.operation.label,
180
+ title=f"Custom check failed: `{name}`",
181
+ message=str(exc),
182
+ exception=exc,
183
+ )
184
+ on_failure(name, collected, custom_failure)
185
+ except FailureGroup as group:
186
+ for sub_failure in group.exceptions:
187
+ on_failure(name, collected, sub_failure)
188
+
189
+ return collected
190
+
191
+
192
+ def __getattr__(name: str) -> Any:
193
+ try:
194
+ return CHECKS.get_one(name)
195
+ except KeyError:
196
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None
197
+
198
+
199
+ def __dir__() -> list[str]:
200
+ return sorted(list(globals().keys()) + CHECKS.get_all_names())