schemathesis 3.39.16__py3-none-any.whl → 4.0.0__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 (255) hide show
  1. schemathesis/__init__.py +41 -79
  2. schemathesis/auths.py +111 -122
  3. schemathesis/checks.py +169 -60
  4. schemathesis/cli/__init__.py +15 -2117
  5. schemathesis/cli/commands/__init__.py +85 -0
  6. schemathesis/cli/commands/data.py +10 -0
  7. schemathesis/cli/commands/run/__init__.py +590 -0
  8. schemathesis/cli/commands/run/context.py +204 -0
  9. schemathesis/cli/commands/run/events.py +60 -0
  10. schemathesis/cli/commands/run/executor.py +157 -0
  11. schemathesis/cli/commands/run/filters.py +53 -0
  12. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  13. schemathesis/cli/commands/run/handlers/base.py +18 -0
  14. schemathesis/cli/commands/run/handlers/cassettes.py +474 -0
  15. schemathesis/cli/commands/run/handlers/junitxml.py +55 -0
  16. schemathesis/cli/commands/run/handlers/output.py +1628 -0
  17. schemathesis/cli/commands/run/loaders.py +114 -0
  18. schemathesis/cli/commands/run/validation.py +246 -0
  19. schemathesis/cli/constants.py +5 -58
  20. schemathesis/cli/core.py +19 -0
  21. schemathesis/cli/ext/fs.py +16 -0
  22. schemathesis/cli/ext/groups.py +84 -0
  23. schemathesis/cli/{options.py → ext/options.py} +36 -34
  24. schemathesis/config/__init__.py +189 -0
  25. schemathesis/config/_auth.py +51 -0
  26. schemathesis/config/_checks.py +268 -0
  27. schemathesis/config/_diff_base.py +99 -0
  28. schemathesis/config/_env.py +21 -0
  29. schemathesis/config/_error.py +156 -0
  30. schemathesis/config/_generation.py +149 -0
  31. schemathesis/config/_health_check.py +24 -0
  32. schemathesis/config/_operations.py +327 -0
  33. schemathesis/config/_output.py +171 -0
  34. schemathesis/config/_parameters.py +19 -0
  35. schemathesis/config/_phases.py +187 -0
  36. schemathesis/config/_projects.py +527 -0
  37. schemathesis/config/_rate_limit.py +17 -0
  38. schemathesis/config/_report.py +120 -0
  39. schemathesis/config/_validator.py +9 -0
  40. schemathesis/config/_warnings.py +25 -0
  41. schemathesis/config/schema.json +885 -0
  42. schemathesis/core/__init__.py +67 -0
  43. schemathesis/core/compat.py +32 -0
  44. schemathesis/core/control.py +2 -0
  45. schemathesis/core/curl.py +58 -0
  46. schemathesis/core/deserialization.py +65 -0
  47. schemathesis/core/errors.py +459 -0
  48. schemathesis/core/failures.py +315 -0
  49. schemathesis/core/fs.py +19 -0
  50. schemathesis/core/hooks.py +20 -0
  51. schemathesis/core/loaders.py +104 -0
  52. schemathesis/core/marks.py +66 -0
  53. schemathesis/{transports/content_types.py → core/media_types.py} +14 -12
  54. schemathesis/core/output/__init__.py +46 -0
  55. schemathesis/core/output/sanitization.py +54 -0
  56. schemathesis/{throttling.py → core/rate_limit.py} +16 -17
  57. schemathesis/core/registries.py +31 -0
  58. schemathesis/core/transforms.py +113 -0
  59. schemathesis/core/transport.py +223 -0
  60. schemathesis/core/validation.py +54 -0
  61. schemathesis/core/version.py +7 -0
  62. schemathesis/engine/__init__.py +28 -0
  63. schemathesis/engine/context.py +118 -0
  64. schemathesis/engine/control.py +36 -0
  65. schemathesis/engine/core.py +169 -0
  66. schemathesis/engine/errors.py +464 -0
  67. schemathesis/engine/events.py +258 -0
  68. schemathesis/engine/phases/__init__.py +88 -0
  69. schemathesis/{runner → engine/phases}/probes.py +52 -68
  70. schemathesis/engine/phases/stateful/__init__.py +68 -0
  71. schemathesis/engine/phases/stateful/_executor.py +356 -0
  72. schemathesis/engine/phases/stateful/context.py +85 -0
  73. schemathesis/engine/phases/unit/__init__.py +212 -0
  74. schemathesis/engine/phases/unit/_executor.py +416 -0
  75. schemathesis/engine/phases/unit/_pool.py +82 -0
  76. schemathesis/engine/recorder.py +247 -0
  77. schemathesis/errors.py +43 -0
  78. schemathesis/filters.py +17 -98
  79. schemathesis/generation/__init__.py +5 -33
  80. schemathesis/generation/case.py +317 -0
  81. schemathesis/generation/coverage.py +282 -175
  82. schemathesis/generation/hypothesis/__init__.py +36 -0
  83. schemathesis/generation/hypothesis/builder.py +800 -0
  84. schemathesis/generation/{_hypothesis.py → hypothesis/examples.py} +2 -11
  85. schemathesis/generation/hypothesis/given.py +66 -0
  86. schemathesis/generation/hypothesis/reporting.py +14 -0
  87. schemathesis/generation/hypothesis/strategies.py +16 -0
  88. schemathesis/generation/meta.py +115 -0
  89. schemathesis/generation/metrics.py +93 -0
  90. schemathesis/generation/modes.py +20 -0
  91. schemathesis/generation/overrides.py +116 -0
  92. schemathesis/generation/stateful/__init__.py +37 -0
  93. schemathesis/generation/stateful/state_machine.py +278 -0
  94. schemathesis/graphql/__init__.py +15 -0
  95. schemathesis/graphql/checks.py +109 -0
  96. schemathesis/graphql/loaders.py +284 -0
  97. schemathesis/hooks.py +80 -101
  98. schemathesis/openapi/__init__.py +13 -0
  99. schemathesis/openapi/checks.py +455 -0
  100. schemathesis/openapi/generation/__init__.py +0 -0
  101. schemathesis/openapi/generation/filters.py +72 -0
  102. schemathesis/openapi/loaders.py +313 -0
  103. schemathesis/pytest/__init__.py +5 -0
  104. schemathesis/pytest/control_flow.py +7 -0
  105. schemathesis/pytest/lazy.py +281 -0
  106. schemathesis/pytest/loaders.py +36 -0
  107. schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +128 -108
  108. schemathesis/python/__init__.py +0 -0
  109. schemathesis/python/asgi.py +12 -0
  110. schemathesis/python/wsgi.py +12 -0
  111. schemathesis/schemas.py +537 -273
  112. schemathesis/specs/graphql/__init__.py +0 -1
  113. schemathesis/specs/graphql/_cache.py +1 -2
  114. schemathesis/specs/graphql/scalars.py +42 -6
  115. schemathesis/specs/graphql/schemas.py +141 -137
  116. schemathesis/specs/graphql/validation.py +11 -17
  117. schemathesis/specs/openapi/__init__.py +6 -1
  118. schemathesis/specs/openapi/_cache.py +1 -2
  119. schemathesis/specs/openapi/_hypothesis.py +142 -156
  120. schemathesis/specs/openapi/checks.py +368 -257
  121. schemathesis/specs/openapi/converter.py +4 -4
  122. schemathesis/specs/openapi/definitions.py +1 -1
  123. schemathesis/specs/openapi/examples.py +23 -21
  124. schemathesis/specs/openapi/expressions/__init__.py +31 -19
  125. schemathesis/specs/openapi/expressions/extractors.py +1 -4
  126. schemathesis/specs/openapi/expressions/lexer.py +1 -1
  127. schemathesis/specs/openapi/expressions/nodes.py +36 -41
  128. schemathesis/specs/openapi/expressions/parser.py +1 -1
  129. schemathesis/specs/openapi/formats.py +35 -7
  130. schemathesis/specs/openapi/media_types.py +53 -12
  131. schemathesis/specs/openapi/negative/__init__.py +7 -4
  132. schemathesis/specs/openapi/negative/mutations.py +6 -5
  133. schemathesis/specs/openapi/parameters.py +7 -10
  134. schemathesis/specs/openapi/patterns.py +94 -31
  135. schemathesis/specs/openapi/references.py +12 -53
  136. schemathesis/specs/openapi/schemas.py +233 -307
  137. schemathesis/specs/openapi/security.py +1 -1
  138. schemathesis/specs/openapi/serialization.py +12 -6
  139. schemathesis/specs/openapi/stateful/__init__.py +268 -133
  140. schemathesis/specs/openapi/stateful/control.py +87 -0
  141. schemathesis/specs/openapi/stateful/links.py +209 -0
  142. schemathesis/transport/__init__.py +142 -0
  143. schemathesis/transport/asgi.py +26 -0
  144. schemathesis/transport/prepare.py +124 -0
  145. schemathesis/transport/requests.py +244 -0
  146. schemathesis/{_xml.py → transport/serialization.py} +69 -11
  147. schemathesis/transport/wsgi.py +171 -0
  148. schemathesis-4.0.0.dist-info/METADATA +204 -0
  149. schemathesis-4.0.0.dist-info/RECORD +164 -0
  150. {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/entry_points.txt +1 -1
  151. {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/licenses/LICENSE +1 -1
  152. schemathesis/_compat.py +0 -74
  153. schemathesis/_dependency_versions.py +0 -19
  154. schemathesis/_hypothesis.py +0 -717
  155. schemathesis/_override.py +0 -50
  156. schemathesis/_patches.py +0 -21
  157. schemathesis/_rate_limiter.py +0 -7
  158. schemathesis/cli/callbacks.py +0 -466
  159. schemathesis/cli/cassettes.py +0 -561
  160. schemathesis/cli/context.py +0 -75
  161. schemathesis/cli/debug.py +0 -27
  162. schemathesis/cli/handlers.py +0 -19
  163. schemathesis/cli/junitxml.py +0 -124
  164. schemathesis/cli/output/__init__.py +0 -1
  165. schemathesis/cli/output/default.py +0 -920
  166. schemathesis/cli/output/short.py +0 -59
  167. schemathesis/cli/reporting.py +0 -79
  168. schemathesis/cli/sanitization.py +0 -26
  169. schemathesis/code_samples.py +0 -151
  170. schemathesis/constants.py +0 -54
  171. schemathesis/contrib/__init__.py +0 -11
  172. schemathesis/contrib/openapi/__init__.py +0 -11
  173. schemathesis/contrib/openapi/fill_missing_examples.py +0 -24
  174. schemathesis/contrib/openapi/formats/__init__.py +0 -9
  175. schemathesis/contrib/openapi/formats/uuid.py +0 -16
  176. schemathesis/contrib/unique_data.py +0 -41
  177. schemathesis/exceptions.py +0 -571
  178. schemathesis/experimental/__init__.py +0 -109
  179. schemathesis/extra/_aiohttp.py +0 -28
  180. schemathesis/extra/_flask.py +0 -13
  181. schemathesis/extra/_server.py +0 -18
  182. schemathesis/failures.py +0 -284
  183. schemathesis/fixups/__init__.py +0 -37
  184. schemathesis/fixups/fast_api.py +0 -41
  185. schemathesis/fixups/utf8_bom.py +0 -28
  186. schemathesis/generation/_methods.py +0 -44
  187. schemathesis/graphql.py +0 -3
  188. schemathesis/internal/__init__.py +0 -7
  189. schemathesis/internal/checks.py +0 -86
  190. schemathesis/internal/copy.py +0 -32
  191. schemathesis/internal/datetime.py +0 -5
  192. schemathesis/internal/deprecation.py +0 -37
  193. schemathesis/internal/diff.py +0 -15
  194. schemathesis/internal/extensions.py +0 -27
  195. schemathesis/internal/jsonschema.py +0 -36
  196. schemathesis/internal/output.py +0 -68
  197. schemathesis/internal/transformation.py +0 -26
  198. schemathesis/internal/validation.py +0 -34
  199. schemathesis/lazy.py +0 -474
  200. schemathesis/loaders.py +0 -122
  201. schemathesis/models.py +0 -1341
  202. schemathesis/parameters.py +0 -90
  203. schemathesis/runner/__init__.py +0 -605
  204. schemathesis/runner/events.py +0 -389
  205. schemathesis/runner/impl/__init__.py +0 -3
  206. schemathesis/runner/impl/context.py +0 -88
  207. schemathesis/runner/impl/core.py +0 -1280
  208. schemathesis/runner/impl/solo.py +0 -80
  209. schemathesis/runner/impl/threadpool.py +0 -391
  210. schemathesis/runner/serialization.py +0 -544
  211. schemathesis/sanitization.py +0 -252
  212. schemathesis/serializers.py +0 -328
  213. schemathesis/service/__init__.py +0 -18
  214. schemathesis/service/auth.py +0 -11
  215. schemathesis/service/ci.py +0 -202
  216. schemathesis/service/client.py +0 -133
  217. schemathesis/service/constants.py +0 -38
  218. schemathesis/service/events.py +0 -61
  219. schemathesis/service/extensions.py +0 -224
  220. schemathesis/service/hosts.py +0 -111
  221. schemathesis/service/metadata.py +0 -71
  222. schemathesis/service/models.py +0 -258
  223. schemathesis/service/report.py +0 -255
  224. schemathesis/service/serialization.py +0 -173
  225. schemathesis/service/usage.py +0 -66
  226. schemathesis/specs/graphql/loaders.py +0 -364
  227. schemathesis/specs/openapi/expressions/context.py +0 -16
  228. schemathesis/specs/openapi/links.py +0 -389
  229. schemathesis/specs/openapi/loaders.py +0 -707
  230. schemathesis/specs/openapi/stateful/statistic.py +0 -198
  231. schemathesis/specs/openapi/stateful/types.py +0 -14
  232. schemathesis/specs/openapi/validation.py +0 -26
  233. schemathesis/stateful/__init__.py +0 -147
  234. schemathesis/stateful/config.py +0 -97
  235. schemathesis/stateful/context.py +0 -135
  236. schemathesis/stateful/events.py +0 -274
  237. schemathesis/stateful/runner.py +0 -309
  238. schemathesis/stateful/sink.py +0 -68
  239. schemathesis/stateful/state_machine.py +0 -328
  240. schemathesis/stateful/statistic.py +0 -22
  241. schemathesis/stateful/validation.py +0 -100
  242. schemathesis/targets.py +0 -77
  243. schemathesis/transports/__init__.py +0 -369
  244. schemathesis/transports/asgi.py +0 -7
  245. schemathesis/transports/auth.py +0 -38
  246. schemathesis/transports/headers.py +0 -36
  247. schemathesis/transports/responses.py +0 -57
  248. schemathesis/types.py +0 -44
  249. schemathesis/utils.py +0 -164
  250. schemathesis-3.39.16.dist-info/METADATA +0 -293
  251. schemathesis-3.39.16.dist-info/RECORD +0 -160
  252. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
  253. /schemathesis/{_lazy_import.py → core/lazy_import.py} +0 -0
  254. /schemathesis/{internal → core}/result.py +0 -0
  255. {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/WHEEL +0 -0
@@ -1,28 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
-
5
- from aiohttp import web
6
-
7
- from . import _server
8
-
9
-
10
- def _run_server(app: web.Application, port: int) -> None:
11
- """Run the given app on the given port.
12
-
13
- Intended to be called as a target for a separate thread.
14
- NOTE. `aiohttp.web.run_app` works only in the main thread and can't be used here (or maybe can we some tuning)
15
- """
16
- # Set a loop for a new thread (there is no by default for non-main threads)
17
- loop = asyncio.new_event_loop()
18
- asyncio.set_event_loop(loop)
19
- runner = web.AppRunner(app)
20
- loop.run_until_complete(runner.setup())
21
- site = web.TCPSite(runner, "127.0.0.1", port)
22
- loop.run_until_complete(site.start())
23
- loop.run_forever()
24
-
25
-
26
- def run_server(app: web.Application, port: int | None = None, timeout: float = 0.05) -> int:
27
- """Start a thread with the given aiohttp application."""
28
- return _server.run(_run_server, app=app, port=port, timeout=timeout)
@@ -1,13 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING
4
-
5
- from . import _server
6
-
7
- if TYPE_CHECKING:
8
- from flask import Flask
9
-
10
-
11
- def run_server(app: Flask, port: int | None = None, timeout: float = 0.05) -> int:
12
- """Start a thread with the given aiohttp application."""
13
- return _server.run(app.run, port=port, timeout=timeout)
@@ -1,18 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import threading
4
- from time import sleep
5
- from typing import Any, Callable
6
-
7
- from aiohttp.test_utils import unused_port
8
-
9
-
10
- def run(target: Callable, port: int | None = None, timeout: float = 0.05, **kwargs: Any) -> int:
11
- """Start a thread with the given aiohttp application."""
12
- if port is None:
13
- port = unused_port()
14
- server_thread = threading.Thread(target=target, kwargs={"port": port, **kwargs})
15
- server_thread.daemon = True
16
- server_thread.start()
17
- sleep(timeout)
18
- return port
schemathesis/failures.py DELETED
@@ -1,284 +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
- message = f"Response must be valid JSON with 'Content-Type: application/json' header:\n\n {exc}"
97
- return cls(
98
- message=message,
99
- validation_message=exc.msg,
100
- document=exc.doc,
101
- position=exc.pos,
102
- lineno=exc.lineno,
103
- colno=exc.colno,
104
- )
105
-
106
-
107
- @dataclass(repr=False)
108
- class ServerError(FailureContext):
109
- status_code: int
110
- title: str = "Server error"
111
- message: str = ""
112
- type: str = "server_error"
113
-
114
-
115
- @dataclass(repr=False)
116
- class MissingContentType(FailureContext):
117
- """Content type header is missing."""
118
-
119
- media_types: list[str]
120
- message: str
121
- title: str = "Missing Content-Type header"
122
- type: str = "missing_content_type"
123
-
124
-
125
- @dataclass(repr=False)
126
- class UndefinedContentType(FailureContext):
127
- """Response has Content-Type that is not documented in the schema."""
128
-
129
- content_type: str
130
- defined_content_types: list[str]
131
- message: str
132
- title: str = "Undocumented Content-Type"
133
- type: str = "undefined_content_type"
134
-
135
-
136
- @dataclass(repr=False)
137
- class AcceptedNegativeData(FailureContext):
138
- """Response with negative data was accepted."""
139
-
140
- message: str
141
- status_code: int
142
- allowed_statuses: list[str]
143
- title: str = "Accepted negative data"
144
- type: str = "accepted_negative_data"
145
-
146
- def unique_by_key(self, check_message: str | None) -> tuple[str, ...]:
147
- return (
148
- check_message or self.message,
149
- str(self.status_code),
150
- )
151
-
152
-
153
- @dataclass(repr=False)
154
- class RejectedPositiveData(FailureContext):
155
- """Response with positive data was rejected."""
156
-
157
- message: str
158
- status_code: int
159
- allowed_statuses: list[str]
160
- title: str = "Rejected positive data"
161
- type: str = "rejected_positive_data"
162
-
163
-
164
- @dataclass(repr=False)
165
- class UseAfterFree(FailureContext):
166
- """Resource was used after a successful DELETE operation on it."""
167
-
168
- message: str
169
- free: str
170
- usage: str
171
- title: str = "Use after free"
172
- type: str = "use_after_free"
173
-
174
-
175
- @dataclass(repr=False)
176
- class EnsureResourceAvailability(FailureContext):
177
- """Resource is not available immediately after creation."""
178
-
179
- message: str
180
- created_with: str
181
- not_available_with: str
182
- title: str = "Resource is not available after creation"
183
- type: str = "ensure_resource_availability"
184
-
185
-
186
- @dataclass(repr=False)
187
- class IgnoredAuth(FailureContext):
188
- """The API operation does not check the specified authentication."""
189
-
190
- message: str
191
- title: str = "Authentication declared but not enforced for this operation"
192
- type: str = "ignored_auth"
193
-
194
-
195
- @dataclass(repr=False)
196
- class UndefinedStatusCode(FailureContext):
197
- """Response has a status code that is not defined in the schema."""
198
-
199
- # Response's status code
200
- status_code: int
201
- # Status codes as defined in schema
202
- defined_status_codes: list[str]
203
- # Defined status code with expanded wildcards
204
- allowed_status_codes: list[int]
205
- message: str
206
- title: str = "Undocumented HTTP status code"
207
- type: str = "undefined_status_code"
208
-
209
-
210
- @dataclass(repr=False)
211
- class MissingHeaders(FailureContext):
212
- """Some required headers are missing."""
213
-
214
- missing_headers: list[str]
215
- message: str
216
- title: str = "Missing required headers"
217
- type: str = "missing_headers"
218
-
219
-
220
- @dataclass(repr=False)
221
- class MalformedMediaType(FailureContext):
222
- """Media type name is malformed.
223
-
224
- Example: `application-json` instead of `application/json`
225
- """
226
-
227
- actual: str
228
- defined: str
229
- message: str
230
- title: str = "Malformed media type"
231
- type: str = "malformed_media_type"
232
-
233
-
234
- @dataclass(repr=False)
235
- class ResponseTimeExceeded(FailureContext):
236
- """Response took longer than expected."""
237
-
238
- elapsed: float
239
- deadline: int
240
- message: str
241
- title: str = "Response time limit exceeded"
242
- type: str = "response_time_exceeded"
243
-
244
- def unique_by_key(self, check_message: str | None) -> tuple[str, ...]:
245
- return (self.title,)
246
-
247
-
248
- @dataclass(repr=False)
249
- class RequestTimeout(FailureContext):
250
- """Request took longer than timeout."""
251
-
252
- timeout: int
253
- message: str
254
- title: str = "Response timeout"
255
- type: str = "request_timeout"
256
-
257
-
258
- @dataclass(repr=False)
259
- class UnexpectedGraphQLResponse(FailureContext):
260
- """GraphQL response is not a JSON object."""
261
-
262
- message: str
263
- title: str = "Unexpected GraphQL Response"
264
- type: str = "graphql_unexpected_response"
265
-
266
-
267
- @dataclass(repr=False)
268
- class GraphQLClientError(FailureContext):
269
- """GraphQL query has not been executed."""
270
-
271
- message: str
272
- errors: list[GraphQLFormattedError]
273
- title: str = "GraphQL client error"
274
- type: str = "graphql_client_error"
275
-
276
-
277
- @dataclass(repr=False)
278
- class GraphQLServerError(FailureContext):
279
- """GraphQL response indicates at least one server error."""
280
-
281
- message: str
282
- errors: list[GraphQLFormattedError]
283
- title: str = "GraphQL server error"
284
- 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,86 +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(
25
- default_factory=lambda: ["400", "401", "403", "404", "406", "422", "428", "5xx"]
26
- )
27
-
28
-
29
- @dataclass
30
- class PositiveDataAcceptanceConfig:
31
- allowed_statuses: list[str] = field(default_factory=lambda: ["2xx", "401", "403", "404"])
32
-
33
-
34
- @dataclass
35
- class MissingRequiredHeaderConfig:
36
- allowed_statuses: list[str] = field(default_factory=lambda: ["406"])
37
-
38
-
39
- @dataclass
40
- class CheckConfig:
41
- missing_required_header: MissingRequiredHeaderConfig = field(default_factory=MissingRequiredHeaderConfig)
42
- negative_data_rejection: NegativeDataRejectionConfig = field(default_factory=NegativeDataRejectionConfig)
43
- positive_data_acceptance: PositiveDataAcceptanceConfig = field(default_factory=PositiveDataAcceptanceConfig)
44
-
45
-
46
- @dataclass
47
- class CheckContext:
48
- """Context for Schemathesis checks.
49
-
50
- Provides access to broader test execution data beyond individual test cases.
51
- """
52
-
53
- override: CaseOverride | None
54
- auth: HTTPDigestAuth | RawAuth | None
55
- headers: CaseInsensitiveDict | None
56
- config: CheckConfig = field(default_factory=CheckConfig)
57
- transport_kwargs: dict | None = None
58
-
59
-
60
- def wrap_check(check: Callable) -> CheckFunction:
61
- """Make older checks compatible with the new signature."""
62
- signature = inspect.signature(check)
63
- parameters = len(signature.parameters)
64
-
65
- if parameters == 3:
66
- # New style check, return as is
67
- return check
68
-
69
- if parameters == 2:
70
- # Old style check, wrap it
71
- warnings.warn(
72
- f"The check function '{check.__name__}' uses an outdated signature. "
73
- "Please update it to accept 'ctx' as the first argument: "
74
- "(ctx: CheckContext, response: GenericResponse, case: Case) -> Optional[bool]",
75
- DeprecationWarning,
76
- stacklevel=2,
77
- )
78
-
79
- def wrapper(_: CheckContext, response: GenericResponse, case: Case) -> Optional[bool]:
80
- return check(response, case)
81
-
82
- wrapper.__name__ = check.__name__
83
-
84
- return wrapper
85
-
86
- 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,37 +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}. Use {replacement} instead.",
8
- DeprecationWarning,
9
- stacklevel=1,
10
- )
11
-
12
-
13
- def deprecated_property(*, removed_in: str, replacement: str) -> Callable:
14
- def wrapper(prop: Callable) -> Callable:
15
- @property # type: ignore
16
- def inner(self: Any) -> Any:
17
- _warn_deprecation(kind="Property", thing=prop.__name__, removed_in=removed_in, replacement=replacement)
18
- return prop(self)
19
-
20
- return inner
21
-
22
- return wrapper
23
-
24
-
25
- def warn_filtration_arguments(name: str) -> None:
26
- _warn_deprecation(kind="Argument", thing=name, removed_in="4.0", replacement="`include` and `exclude` methods")
27
-
28
-
29
- def deprecated_function(*, removed_in: str, replacement: str) -> Callable:
30
- def wrapper(func: Callable) -> Callable:
31
- def inner(*args: Any, **kwargs: Any) -> Any:
32
- _warn_deprecation(kind="Function", thing=func.__name__, removed_in=removed_in, replacement=replacement)
33
- return func(*args, **kwargs)
34
-
35
- return inner
36
-
37
- 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