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
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())