schemathesis 3.25.6__py3-none-any.whl → 4.0.0a1__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 (221) hide show
  1. schemathesis/__init__.py +27 -65
  2. schemathesis/auths.py +102 -82
  3. schemathesis/checks.py +126 -46
  4. schemathesis/cli/__init__.py +11 -1760
  5. schemathesis/cli/__main__.py +4 -0
  6. schemathesis/cli/commands/__init__.py +37 -0
  7. schemathesis/cli/commands/run/__init__.py +662 -0
  8. schemathesis/cli/commands/run/checks.py +80 -0
  9. schemathesis/cli/commands/run/context.py +117 -0
  10. schemathesis/cli/commands/run/events.py +35 -0
  11. schemathesis/cli/commands/run/executor.py +138 -0
  12. schemathesis/cli/commands/run/filters.py +194 -0
  13. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  14. schemathesis/cli/commands/run/handlers/base.py +18 -0
  15. schemathesis/cli/commands/run/handlers/cassettes.py +494 -0
  16. schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
  17. schemathesis/cli/commands/run/handlers/output.py +746 -0
  18. schemathesis/cli/commands/run/hypothesis.py +105 -0
  19. schemathesis/cli/commands/run/loaders.py +129 -0
  20. schemathesis/cli/{callbacks.py → commands/run/validation.py} +103 -174
  21. schemathesis/cli/constants.py +5 -52
  22. schemathesis/cli/core.py +17 -0
  23. schemathesis/cli/ext/fs.py +14 -0
  24. schemathesis/cli/ext/groups.py +55 -0
  25. schemathesis/cli/{options.py → ext/options.py} +39 -10
  26. schemathesis/cli/hooks.py +36 -0
  27. schemathesis/contrib/__init__.py +1 -3
  28. schemathesis/contrib/openapi/__init__.py +1 -3
  29. schemathesis/contrib/openapi/fill_missing_examples.py +3 -5
  30. schemathesis/core/__init__.py +58 -0
  31. schemathesis/core/compat.py +25 -0
  32. schemathesis/core/control.py +2 -0
  33. schemathesis/core/curl.py +58 -0
  34. schemathesis/core/deserialization.py +65 -0
  35. schemathesis/core/errors.py +370 -0
  36. schemathesis/core/failures.py +285 -0
  37. schemathesis/core/fs.py +19 -0
  38. schemathesis/{_lazy_import.py → core/lazy_import.py} +1 -0
  39. schemathesis/core/loaders.py +104 -0
  40. schemathesis/core/marks.py +66 -0
  41. schemathesis/{transports/content_types.py → core/media_types.py} +17 -13
  42. schemathesis/core/output/__init__.py +69 -0
  43. schemathesis/core/output/sanitization.py +197 -0
  44. schemathesis/core/rate_limit.py +60 -0
  45. schemathesis/core/registries.py +31 -0
  46. schemathesis/{internal → core}/result.py +1 -1
  47. schemathesis/core/transforms.py +113 -0
  48. schemathesis/core/transport.py +108 -0
  49. schemathesis/core/validation.py +38 -0
  50. schemathesis/core/version.py +7 -0
  51. schemathesis/engine/__init__.py +30 -0
  52. schemathesis/engine/config.py +59 -0
  53. schemathesis/engine/context.py +119 -0
  54. schemathesis/engine/control.py +36 -0
  55. schemathesis/engine/core.py +157 -0
  56. schemathesis/engine/errors.py +394 -0
  57. schemathesis/engine/events.py +337 -0
  58. schemathesis/engine/phases/__init__.py +66 -0
  59. schemathesis/{runner → engine/phases}/probes.py +50 -67
  60. schemathesis/engine/phases/stateful/__init__.py +65 -0
  61. schemathesis/engine/phases/stateful/_executor.py +326 -0
  62. schemathesis/engine/phases/stateful/context.py +85 -0
  63. schemathesis/engine/phases/unit/__init__.py +174 -0
  64. schemathesis/engine/phases/unit/_executor.py +321 -0
  65. schemathesis/engine/phases/unit/_pool.py +74 -0
  66. schemathesis/engine/recorder.py +241 -0
  67. schemathesis/errors.py +31 -0
  68. schemathesis/experimental/__init__.py +18 -14
  69. schemathesis/filters.py +103 -14
  70. schemathesis/generation/__init__.py +21 -37
  71. schemathesis/generation/case.py +190 -0
  72. schemathesis/generation/coverage.py +931 -0
  73. schemathesis/generation/hypothesis/__init__.py +30 -0
  74. schemathesis/generation/hypothesis/builder.py +585 -0
  75. schemathesis/generation/hypothesis/examples.py +50 -0
  76. schemathesis/generation/hypothesis/given.py +66 -0
  77. schemathesis/generation/hypothesis/reporting.py +14 -0
  78. schemathesis/generation/hypothesis/strategies.py +16 -0
  79. schemathesis/generation/meta.py +115 -0
  80. schemathesis/generation/modes.py +28 -0
  81. schemathesis/generation/overrides.py +96 -0
  82. schemathesis/generation/stateful/__init__.py +20 -0
  83. schemathesis/{stateful → generation/stateful}/state_machine.py +68 -81
  84. schemathesis/generation/targets.py +69 -0
  85. schemathesis/graphql/__init__.py +15 -0
  86. schemathesis/graphql/checks.py +115 -0
  87. schemathesis/graphql/loaders.py +131 -0
  88. schemathesis/hooks.py +99 -67
  89. schemathesis/openapi/__init__.py +13 -0
  90. schemathesis/openapi/checks.py +412 -0
  91. schemathesis/openapi/generation/__init__.py +0 -0
  92. schemathesis/openapi/generation/filters.py +63 -0
  93. schemathesis/openapi/loaders.py +178 -0
  94. schemathesis/pytest/__init__.py +5 -0
  95. schemathesis/pytest/control_flow.py +7 -0
  96. schemathesis/pytest/lazy.py +273 -0
  97. schemathesis/pytest/loaders.py +12 -0
  98. schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +106 -127
  99. schemathesis/python/__init__.py +0 -0
  100. schemathesis/python/asgi.py +12 -0
  101. schemathesis/python/wsgi.py +12 -0
  102. schemathesis/schemas.py +537 -261
  103. schemathesis/specs/graphql/__init__.py +0 -1
  104. schemathesis/specs/graphql/_cache.py +25 -0
  105. schemathesis/specs/graphql/nodes.py +1 -0
  106. schemathesis/specs/graphql/scalars.py +7 -5
  107. schemathesis/specs/graphql/schemas.py +215 -187
  108. schemathesis/specs/graphql/validation.py +11 -18
  109. schemathesis/specs/openapi/__init__.py +7 -1
  110. schemathesis/specs/openapi/_cache.py +122 -0
  111. schemathesis/specs/openapi/_hypothesis.py +146 -165
  112. schemathesis/specs/openapi/checks.py +565 -67
  113. schemathesis/specs/openapi/converter.py +33 -6
  114. schemathesis/specs/openapi/definitions.py +11 -18
  115. schemathesis/specs/openapi/examples.py +139 -23
  116. schemathesis/specs/openapi/expressions/__init__.py +37 -2
  117. schemathesis/specs/openapi/expressions/context.py +4 -6
  118. schemathesis/specs/openapi/expressions/extractors.py +23 -0
  119. schemathesis/specs/openapi/expressions/lexer.py +20 -18
  120. schemathesis/specs/openapi/expressions/nodes.py +38 -14
  121. schemathesis/specs/openapi/expressions/parser.py +26 -5
  122. schemathesis/specs/openapi/formats.py +45 -0
  123. schemathesis/specs/openapi/links.py +65 -165
  124. schemathesis/specs/openapi/media_types.py +32 -0
  125. schemathesis/specs/openapi/negative/__init__.py +7 -3
  126. schemathesis/specs/openapi/negative/mutations.py +24 -8
  127. schemathesis/specs/openapi/parameters.py +46 -30
  128. schemathesis/specs/openapi/patterns.py +137 -0
  129. schemathesis/specs/openapi/references.py +47 -57
  130. schemathesis/specs/openapi/schemas.py +478 -369
  131. schemathesis/specs/openapi/security.py +25 -7
  132. schemathesis/specs/openapi/serialization.py +11 -6
  133. schemathesis/specs/openapi/stateful/__init__.py +185 -73
  134. schemathesis/specs/openapi/utils.py +6 -1
  135. schemathesis/transport/__init__.py +104 -0
  136. schemathesis/transport/asgi.py +26 -0
  137. schemathesis/transport/prepare.py +99 -0
  138. schemathesis/transport/requests.py +221 -0
  139. schemathesis/{_xml.py → transport/serialization.py} +143 -28
  140. schemathesis/transport/wsgi.py +165 -0
  141. schemathesis-4.0.0a1.dist-info/METADATA +297 -0
  142. schemathesis-4.0.0a1.dist-info/RECORD +152 -0
  143. {schemathesis-3.25.6.dist-info → schemathesis-4.0.0a1.dist-info}/WHEEL +1 -1
  144. {schemathesis-3.25.6.dist-info → schemathesis-4.0.0a1.dist-info}/entry_points.txt +1 -1
  145. schemathesis/_compat.py +0 -74
  146. schemathesis/_dependency_versions.py +0 -17
  147. schemathesis/_hypothesis.py +0 -246
  148. schemathesis/_override.py +0 -49
  149. schemathesis/cli/cassettes.py +0 -375
  150. schemathesis/cli/context.py +0 -58
  151. schemathesis/cli/debug.py +0 -26
  152. schemathesis/cli/handlers.py +0 -16
  153. schemathesis/cli/junitxml.py +0 -43
  154. schemathesis/cli/output/__init__.py +0 -1
  155. schemathesis/cli/output/default.py +0 -790
  156. schemathesis/cli/output/short.py +0 -44
  157. schemathesis/cli/sanitization.py +0 -20
  158. schemathesis/code_samples.py +0 -149
  159. schemathesis/constants.py +0 -55
  160. schemathesis/contrib/openapi/formats/__init__.py +0 -9
  161. schemathesis/contrib/openapi/formats/uuid.py +0 -15
  162. schemathesis/contrib/unique_data.py +0 -41
  163. schemathesis/exceptions.py +0 -560
  164. schemathesis/extra/_aiohttp.py +0 -27
  165. schemathesis/extra/_flask.py +0 -10
  166. schemathesis/extra/_server.py +0 -17
  167. schemathesis/failures.py +0 -209
  168. schemathesis/fixups/__init__.py +0 -36
  169. schemathesis/fixups/fast_api.py +0 -41
  170. schemathesis/fixups/utf8_bom.py +0 -29
  171. schemathesis/graphql.py +0 -4
  172. schemathesis/internal/__init__.py +0 -7
  173. schemathesis/internal/copy.py +0 -13
  174. schemathesis/internal/datetime.py +0 -5
  175. schemathesis/internal/deprecation.py +0 -34
  176. schemathesis/internal/jsonschema.py +0 -35
  177. schemathesis/internal/transformation.py +0 -15
  178. schemathesis/internal/validation.py +0 -34
  179. schemathesis/lazy.py +0 -361
  180. schemathesis/loaders.py +0 -120
  181. schemathesis/models.py +0 -1234
  182. schemathesis/parameters.py +0 -86
  183. schemathesis/runner/__init__.py +0 -570
  184. schemathesis/runner/events.py +0 -329
  185. schemathesis/runner/impl/__init__.py +0 -3
  186. schemathesis/runner/impl/core.py +0 -1035
  187. schemathesis/runner/impl/solo.py +0 -90
  188. schemathesis/runner/impl/threadpool.py +0 -400
  189. schemathesis/runner/serialization.py +0 -411
  190. schemathesis/sanitization.py +0 -248
  191. schemathesis/serializers.py +0 -323
  192. schemathesis/service/__init__.py +0 -18
  193. schemathesis/service/auth.py +0 -11
  194. schemathesis/service/ci.py +0 -201
  195. schemathesis/service/client.py +0 -100
  196. schemathesis/service/constants.py +0 -38
  197. schemathesis/service/events.py +0 -57
  198. schemathesis/service/hosts.py +0 -107
  199. schemathesis/service/metadata.py +0 -46
  200. schemathesis/service/models.py +0 -49
  201. schemathesis/service/report.py +0 -255
  202. schemathesis/service/serialization.py +0 -199
  203. schemathesis/service/usage.py +0 -65
  204. schemathesis/specs/graphql/loaders.py +0 -344
  205. schemathesis/specs/openapi/filters.py +0 -49
  206. schemathesis/specs/openapi/loaders.py +0 -667
  207. schemathesis/specs/openapi/stateful/links.py +0 -92
  208. schemathesis/specs/openapi/validation.py +0 -25
  209. schemathesis/stateful/__init__.py +0 -133
  210. schemathesis/targets.py +0 -45
  211. schemathesis/throttling.py +0 -41
  212. schemathesis/transports/__init__.py +0 -5
  213. schemathesis/transports/auth.py +0 -15
  214. schemathesis/transports/headers.py +0 -35
  215. schemathesis/transports/responses.py +0 -52
  216. schemathesis/types.py +0 -35
  217. schemathesis/utils.py +0 -169
  218. schemathesis-3.25.6.dist-info/METADATA +0 -356
  219. schemathesis-3.25.6.dist-info/RECORD +0 -134
  220. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
  221. {schemathesis-3.25.6.dist-info → schemathesis-4.0.0a1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,4 @@
1
+ if __name__ == "__main__":
2
+ import schemathesis.cli
3
+
4
+ schemathesis.cli.schemathesis()
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ import click
7
+
8
+ from schemathesis.cli import hooks
9
+ from schemathesis.cli.commands.run import run as run_command
10
+ from schemathesis.cli.core import get_terminal_width
11
+ from schemathesis.cli.ext.groups import CommandWithGroupedOptions, GroupedOption
12
+
13
+ CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
14
+
15
+
16
+ @click.group(context_settings=CONTEXT_SETTINGS) # type: ignore[misc]
17
+ @click.version_option() # type: ignore[misc]
18
+ def schemathesis() -> None:
19
+ """Property-based API testing for OpenAPI and GraphQL."""
20
+ hooks.load()
21
+
22
+
23
+ @dataclass
24
+ class Group:
25
+ name: str
26
+
27
+ __slots__ = ("name",)
28
+
29
+ def add_option(self, *args: Any, **kwargs: Any) -> None:
30
+ run.params.append(GroupedOption(args, group=self.name, **kwargs))
31
+
32
+
33
+ run = schemathesis.command(
34
+ short_help="Execute automated tests based on API specifications",
35
+ cls=CommandWithGroupedOptions,
36
+ context_settings={"terminal_width": get_terminal_width(), **CONTEXT_SETTINGS},
37
+ )(run_command)
@@ -0,0 +1,662 @@
1
+ from __future__ import annotations
2
+
3
+ from random import Random
4
+ from typing import Any, Sequence
5
+
6
+ import click
7
+
8
+ from schemathesis import contrib, experimental
9
+ from schemathesis.checks import CHECKS
10
+ from schemathesis.cli.commands.run import executor, validation
11
+ from schemathesis.cli.commands.run.checks import CheckArguments
12
+ from schemathesis.cli.commands.run.filters import FilterArguments, with_filters
13
+ from schemathesis.cli.commands.run.handlers.cassettes import CassetteConfig, CassetteFormat
14
+ from schemathesis.cli.commands.run.hypothesis import (
15
+ HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER,
16
+ HealthCheck,
17
+ Phase,
18
+ prepare_health_checks,
19
+ prepare_phases,
20
+ prepare_settings,
21
+ )
22
+ from schemathesis.cli.constants import DEFAULT_WORKERS, MAX_WORKERS, MIN_WORKERS
23
+ from schemathesis.cli.core import ensure_color
24
+ from schemathesis.cli.ext.groups import group, grouped_option
25
+ from schemathesis.cli.ext.options import (
26
+ CsvChoice,
27
+ CsvEnumChoice,
28
+ CsvListChoice,
29
+ CustomHelpMessageChoice,
30
+ RegistryChoice,
31
+ )
32
+ from schemathesis.core.output import OutputConfig
33
+ from schemathesis.core.transport import DEFAULT_RESPONSE_TIMEOUT
34
+ from schemathesis.engine.config import EngineConfig, ExecutionConfig, NetworkConfig
35
+ from schemathesis.engine.phases import PhaseName
36
+ from schemathesis.generation import DEFAULT_GENERATOR_MODES, GenerationConfig, GenerationMode
37
+ from schemathesis.generation.overrides import Override
38
+ from schemathesis.generation.targets import TARGETS
39
+
40
+ # NOTE: Need to explicitly import all registered checks
41
+ from schemathesis.specs.openapi.checks import * # noqa: F401, F403
42
+
43
+ COLOR_OPTIONS_INVALID_USAGE_MESSAGE = "Can't use `--no-color` and `--force-color` simultaneously"
44
+
45
+ DEFAULT_PHASES = ("unit", "stateful")
46
+
47
+
48
+ @click.argument("schema", type=str) # type: ignore[misc]
49
+ @group("Options")
50
+ @grouped_option(
51
+ "--phases",
52
+ help="A comma-separated list of test phases to run",
53
+ type=CsvChoice(["unit", "stateful"]),
54
+ default=",".join(DEFAULT_PHASES),
55
+ metavar="",
56
+ )
57
+ @grouped_option(
58
+ "--base-url",
59
+ "-b",
60
+ help="Base URL of the API, required when schema is provided as a file",
61
+ type=str,
62
+ callback=validation.validate_base_url,
63
+ envvar="SCHEMATHESIS_BASE_URL",
64
+ )
65
+ @grouped_option(
66
+ "--suppress-health-check",
67
+ help="A comma-separated list of Schemathesis health checks to disable",
68
+ type=CsvEnumChoice(HealthCheck),
69
+ metavar="",
70
+ )
71
+ @grouped_option(
72
+ "--workers",
73
+ "-w",
74
+ "workers_num",
75
+ help="Number of concurrent workers for testing. Auto-adjusts if 'auto' is specified",
76
+ type=CustomHelpMessageChoice(
77
+ ["auto", *list(map(str, range(MIN_WORKERS, MAX_WORKERS + 1)))],
78
+ choices_repr=f"[auto, {MIN_WORKERS}-{MAX_WORKERS}]",
79
+ ),
80
+ default=str(DEFAULT_WORKERS),
81
+ show_default=True,
82
+ callback=validation.convert_workers,
83
+ metavar="",
84
+ )
85
+ @group("API validation options")
86
+ @grouped_option(
87
+ "--checks",
88
+ "-c",
89
+ "included_check_names",
90
+ multiple=True,
91
+ help="Comma-separated list of checks to run against API responses",
92
+ type=RegistryChoice(CHECKS, with_all=True),
93
+ default=("not_a_server_error",),
94
+ callback=validation.convert_checks,
95
+ show_default=True,
96
+ metavar="",
97
+ )
98
+ @grouped_option(
99
+ "--exclude-checks",
100
+ "excluded_check_names",
101
+ multiple=True,
102
+ help="Comma-separated list of checks to skip during testing",
103
+ type=RegistryChoice(CHECKS, with_all=True),
104
+ default=(),
105
+ callback=validation.convert_checks,
106
+ show_default=True,
107
+ metavar="",
108
+ )
109
+ @grouped_option(
110
+ "--max-response-time",
111
+ help="Time limit in seconds for API response times. The test will fail if a response time exceeds this limit",
112
+ type=click.FloatRange(min=0.0, min_open=True),
113
+ )
114
+ @grouped_option(
115
+ "-x",
116
+ "--exitfirst",
117
+ "exit_first",
118
+ is_flag=True,
119
+ default=False,
120
+ help="Terminate the test suite immediately upon the first failure or error encountered",
121
+ show_default=True,
122
+ )
123
+ @grouped_option(
124
+ "--max-failures",
125
+ "max_failures",
126
+ type=click.IntRange(min=1),
127
+ help="Terminate the test suite after reaching a specified number of failures or errors",
128
+ show_default=True,
129
+ )
130
+ @group("Filtering options")
131
+ @with_filters
132
+ @grouped_option(
133
+ "--include-by",
134
+ "include_by",
135
+ type=str,
136
+ help="Include API operations by expression",
137
+ )
138
+ @grouped_option(
139
+ "--exclude-by",
140
+ "exclude_by",
141
+ type=str,
142
+ help="Exclude API operations by expression",
143
+ )
144
+ @grouped_option(
145
+ "--exclude-deprecated",
146
+ help="Exclude deprecated API operations from testing",
147
+ is_flag=True,
148
+ is_eager=True,
149
+ default=False,
150
+ show_default=True,
151
+ )
152
+ @group("Loader options")
153
+ @grouped_option(
154
+ "--wait-for-schema",
155
+ help="Maximum duration, in seconds, to wait for the API schema to become available. Disabled by default",
156
+ type=click.FloatRange(1.0),
157
+ default=None,
158
+ envvar="SCHEMATHESIS_WAIT_FOR_SCHEMA",
159
+ )
160
+ @group("Network requests options")
161
+ @grouped_option(
162
+ "--header",
163
+ "-H",
164
+ "headers",
165
+ help=r"Add a custom HTTP header to all API requests. Format: 'Header-Name: Value'",
166
+ multiple=True,
167
+ type=str,
168
+ callback=validation.validate_headers,
169
+ )
170
+ @grouped_option(
171
+ "--auth",
172
+ "-a",
173
+ help="Provide the server authentication details in the 'USER:PASSWORD' format",
174
+ type=str,
175
+ callback=validation.validate_auth,
176
+ )
177
+ @grouped_option(
178
+ "--request-timeout",
179
+ help="Timeout limit, in seconds, for each network request during tests",
180
+ type=click.FloatRange(min=0.0, min_open=True),
181
+ default=DEFAULT_RESPONSE_TIMEOUT,
182
+ )
183
+ @grouped_option(
184
+ "--request-proxy",
185
+ help="Set the proxy for all network requests",
186
+ type=str,
187
+ )
188
+ @grouped_option(
189
+ "--request-tls-verify",
190
+ help="Configures TLS certificate verification for server requests. Can specify path to CA_BUNDLE for custom certs",
191
+ type=str,
192
+ default="true",
193
+ show_default=True,
194
+ callback=validation.convert_boolean_string,
195
+ )
196
+ @grouped_option(
197
+ "--request-cert",
198
+ help="File path of unencrypted client certificate for authentication. "
199
+ "The certificate can be bundled with a private key (e.g. PEM) or the private "
200
+ "key can be provided with the --request-cert-key argument",
201
+ type=click.Path(exists=True),
202
+ default=None,
203
+ show_default=False,
204
+ )
205
+ @grouped_option(
206
+ "--request-cert-key",
207
+ help="Specify the file path of the private key for the client certificate",
208
+ type=click.Path(exists=True),
209
+ default=None,
210
+ show_default=False,
211
+ callback=validation.validate_request_cert_key,
212
+ )
213
+ @grouped_option(
214
+ "--rate-limit",
215
+ help="Specify a rate limit for test requests in '<limit>/<duration>' format. "
216
+ "Example - `100/m` for 100 requests per minute",
217
+ type=str,
218
+ callback=validation.validate_rate_limit,
219
+ )
220
+ @group("Output options")
221
+ @grouped_option(
222
+ "--junit-xml",
223
+ help="Output a JUnit-XML style report at the specified file path",
224
+ type=click.File("w", encoding="utf-8"),
225
+ )
226
+ @grouped_option(
227
+ "--cassette-path",
228
+ help="Save the test outcomes in a VCR-compatible format",
229
+ type=click.File("w", encoding="utf-8"),
230
+ is_eager=True,
231
+ )
232
+ @grouped_option(
233
+ "--cassette-format",
234
+ help="Format of the saved cassettes",
235
+ type=click.Choice([item.name.lower() for item in CassetteFormat]),
236
+ default=CassetteFormat.VCR.name.lower(),
237
+ callback=validation.convert_cassette_format,
238
+ metavar="",
239
+ )
240
+ @grouped_option(
241
+ "--cassette-preserve-exact-body-bytes",
242
+ help="Retain exact byte sequence of payloads in cassettes, encoded as base64",
243
+ is_flag=True,
244
+ callback=validation.validate_preserve_exact_body_bytes,
245
+ )
246
+ @grouped_option(
247
+ "--output-sanitize",
248
+ type=bool,
249
+ default=True,
250
+ show_default=True,
251
+ help="Enable or disable automatic output sanitization to obscure sensitive data",
252
+ )
253
+ @grouped_option(
254
+ "--output-truncate",
255
+ help="Truncate schemas and responses in error messages",
256
+ type=str,
257
+ default="true",
258
+ show_default=True,
259
+ callback=validation.convert_boolean_string,
260
+ )
261
+ @group("Experimental options")
262
+ @grouped_option(
263
+ "--experimental",
264
+ "experiments",
265
+ help="Enable experimental features",
266
+ type=click.Choice(sorted([experiment.label for experiment in experimental.GLOBAL_EXPERIMENTS.available])),
267
+ callback=validation.convert_experimental,
268
+ multiple=True,
269
+ metavar="FEATURES",
270
+ )
271
+ @grouped_option(
272
+ "--experimental-no-failfast",
273
+ "no_failfast",
274
+ help="Continue testing an API operation after a failure is found",
275
+ is_flag=True,
276
+ default=False,
277
+ metavar="",
278
+ envvar="SCHEMATHESIS_EXPERIMENTAL_NO_FAILFAST",
279
+ )
280
+ @grouped_option(
281
+ "--experimental-missing-required-header-allowed-statuses",
282
+ "missing_required_header_allowed_statuses",
283
+ help="Comma-separated list of status codes expected for test cases with a missing required header",
284
+ type=CsvListChoice(),
285
+ callback=validation.convert_status_codes,
286
+ metavar="",
287
+ envvar="SCHEMATHESIS_EXPERIMENTAL_MISSING_REQUIRED_HEADER_ALLOWED_STATUSES",
288
+ )
289
+ @grouped_option(
290
+ "--experimental-positive-data-acceptance-allowed-statuses",
291
+ "positive_data_acceptance_allowed_statuses",
292
+ help="Comma-separated list of status codes considered as successful responses",
293
+ type=CsvListChoice(),
294
+ callback=validation.convert_status_codes,
295
+ metavar="",
296
+ envvar="SCHEMATHESIS_EXPERIMENTAL_POSITIVE_DATA_ACCEPTANCE_ALLOWED_STATUSES",
297
+ )
298
+ @grouped_option(
299
+ "--experimental-negative-data-rejection-allowed-statuses",
300
+ "negative_data_rejection_allowed_statuses",
301
+ help="Comma-separated list of status codes expected for rejected negative data",
302
+ type=CsvListChoice(),
303
+ callback=validation.convert_status_codes,
304
+ metavar="",
305
+ envvar="SCHEMATHESIS_EXPERIMENTAL_NEGATIVE_DATA_REJECTION_ALLOWED_STATUSES",
306
+ )
307
+ @group("Data generation options")
308
+ @grouped_option(
309
+ "--generation-mode",
310
+ "generation_modes",
311
+ help="Specify the approach Schemathesis uses to generate test data. "
312
+ "Use 'positive' for valid data, 'negative' for invalid data, or 'all' for both",
313
+ type=click.Choice([item.value for item in GenerationMode] + ["all"]),
314
+ default=GenerationMode.default().value,
315
+ callback=validation.convert_generation_mode,
316
+ show_default=True,
317
+ metavar="",
318
+ )
319
+ @grouped_option(
320
+ "--generation-seed",
321
+ help="Seed value for Schemathesis, ensuring reproducibility across test runs",
322
+ type=int,
323
+ )
324
+ @grouped_option(
325
+ "--generation-max-examples",
326
+ help="The cap on the number of examples generated by Schemathesis for each API operation",
327
+ type=click.IntRange(1),
328
+ )
329
+ @grouped_option(
330
+ "--generation-deterministic",
331
+ help="Enables deterministic mode, which eliminates random variation between tests",
332
+ is_flag=True,
333
+ is_eager=True,
334
+ default=None,
335
+ show_default=True,
336
+ )
337
+ @grouped_option(
338
+ "--generation-allow-x00",
339
+ help="Whether to allow the generation of `\x00` bytes within strings",
340
+ type=str,
341
+ default="true",
342
+ show_default=True,
343
+ callback=validation.convert_boolean_string,
344
+ )
345
+ @grouped_option(
346
+ "--generation-codec",
347
+ help="The codec used for generating strings",
348
+ type=str,
349
+ default="utf-8",
350
+ callback=validation.validate_generation_codec,
351
+ )
352
+ @grouped_option(
353
+ "--generation-optimize",
354
+ "generation_optimize",
355
+ multiple=True,
356
+ help="Guide input generation to values more likely to expose bugs via targeted property-based testing",
357
+ type=RegistryChoice(TARGETS),
358
+ default=None,
359
+ callback=validation.convert_checks,
360
+ show_default=True,
361
+ metavar="TARGET",
362
+ )
363
+ @grouped_option(
364
+ "--generation-with-security-parameters",
365
+ help="Whether to generate security parameters",
366
+ type=str,
367
+ default="true",
368
+ show_default=True,
369
+ callback=validation.convert_boolean_string,
370
+ metavar="BOOLEAN",
371
+ )
372
+ @grouped_option(
373
+ "--generation-graphql-allow-null",
374
+ help="Whether to use `null` values for optional arguments in GraphQL queries",
375
+ type=str,
376
+ default="true",
377
+ show_default=True,
378
+ callback=validation.convert_boolean_string,
379
+ metavar="BOOLEAN",
380
+ )
381
+ @grouped_option(
382
+ "--generation-database",
383
+ help="Storage for examples discovered by Schemathesis. "
384
+ f"Use 'none' to disable, '{HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER}' for temporary storage, "
385
+ f"or specify a file path for persistent storage",
386
+ type=str,
387
+ callback=validation.validate_hypothesis_database,
388
+ )
389
+ @grouped_option(
390
+ "--generation-unique-inputs",
391
+ "generation_unique_inputs",
392
+ help="Force the generation of unique test cases",
393
+ is_flag=True,
394
+ default=False,
395
+ show_default=True,
396
+ metavar="BOOLEAN",
397
+ )
398
+ @grouped_option(
399
+ "--contrib-openapi-fill-missing-examples",
400
+ "contrib_openapi_fill_missing_examples",
401
+ help="Enable generation of random examples for API operations that do not have explicit examples",
402
+ is_flag=True,
403
+ default=False,
404
+ show_default=True,
405
+ metavar="BOOLEAN",
406
+ )
407
+ @group("Open API options")
408
+ @grouped_option(
409
+ "--set-query",
410
+ "set_query",
411
+ help=r"OpenAPI: Override a specific query parameter by specifying 'parameter=value'",
412
+ multiple=True,
413
+ type=str,
414
+ callback=validation.validate_set_query,
415
+ )
416
+ @grouped_option(
417
+ "--set-header",
418
+ "set_header",
419
+ help=r"OpenAPI: Override a specific header parameter by specifying 'parameter=value'",
420
+ multiple=True,
421
+ type=str,
422
+ callback=validation.validate_set_header,
423
+ )
424
+ @grouped_option(
425
+ "--set-cookie",
426
+ "set_cookie",
427
+ help=r"OpenAPI: Override a specific cookie parameter by specifying 'parameter=value'",
428
+ multiple=True,
429
+ type=str,
430
+ callback=validation.validate_set_cookie,
431
+ )
432
+ @grouped_option(
433
+ "--set-path",
434
+ "set_path",
435
+ help=r"OpenAPI: Override a specific path parameter by specifying 'parameter=value'",
436
+ multiple=True,
437
+ type=str,
438
+ callback=validation.validate_set_path,
439
+ )
440
+ @group("Hypothesis engine options")
441
+ @grouped_option(
442
+ "--hypothesis-phases",
443
+ help="Testing phases to execute",
444
+ type=CsvEnumChoice(Phase),
445
+ metavar="",
446
+ )
447
+ @grouped_option(
448
+ "--hypothesis-no-phases",
449
+ help="Testing phases to exclude from execution",
450
+ type=CsvEnumChoice(Phase),
451
+ metavar="",
452
+ )
453
+ @group("Global options")
454
+ @grouped_option("--no-color", help="Disable ANSI color escape codes", type=bool, is_flag=True)
455
+ @grouped_option("--force-color", help="Explicitly tells to enable ANSI color escape codes", type=bool, is_flag=True)
456
+ @click.pass_context # type: ignore[misc]
457
+ def run(
458
+ ctx: click.Context,
459
+ schema: str,
460
+ auth: tuple[str, str] | None,
461
+ headers: dict[str, str],
462
+ set_query: dict[str, str],
463
+ set_header: dict[str, str],
464
+ set_cookie: dict[str, str],
465
+ set_path: dict[str, str],
466
+ experiments: list,
467
+ no_failfast: bool,
468
+ missing_required_header_allowed_statuses: list[str],
469
+ positive_data_acceptance_allowed_statuses: list[str],
470
+ negative_data_rejection_allowed_statuses: list[str],
471
+ included_check_names: Sequence[str],
472
+ excluded_check_names: Sequence[str],
473
+ phases: Sequence[str] = DEFAULT_PHASES,
474
+ max_response_time: float | None = None,
475
+ exit_first: bool = False,
476
+ max_failures: int | None = None,
477
+ include_path: Sequence[str] = (),
478
+ include_path_regex: str | None = None,
479
+ include_method: Sequence[str] = (),
480
+ include_method_regex: str | None = None,
481
+ include_name: Sequence[str] = (),
482
+ include_name_regex: str | None = None,
483
+ include_tag: Sequence[str] = (),
484
+ include_tag_regex: str | None = None,
485
+ include_operation_id: Sequence[str] = (),
486
+ include_operation_id_regex: str | None = None,
487
+ exclude_path: Sequence[str] = (),
488
+ exclude_path_regex: str | None = None,
489
+ exclude_method: Sequence[str] = (),
490
+ exclude_method_regex: str | None = None,
491
+ exclude_name: Sequence[str] = (),
492
+ exclude_name_regex: str | None = None,
493
+ exclude_tag: Sequence[str] = (),
494
+ exclude_tag_regex: str | None = None,
495
+ exclude_operation_id: Sequence[str] = (),
496
+ exclude_operation_id_regex: str | None = None,
497
+ include_by: str | None = None,
498
+ exclude_by: str | None = None,
499
+ exclude_deprecated: bool = False,
500
+ workers_num: int = DEFAULT_WORKERS,
501
+ base_url: str | None = None,
502
+ suppress_health_check: list[HealthCheck] | None = None,
503
+ request_timeout: int | None = None,
504
+ request_tls_verify: bool = True,
505
+ request_cert: str | None = None,
506
+ request_cert_key: str | None = None,
507
+ request_proxy: str | None = None,
508
+ junit_xml: click.utils.LazyFile | None = None,
509
+ cassette_path: click.utils.LazyFile | None = None,
510
+ cassette_format: CassetteFormat = CassetteFormat.VCR,
511
+ cassette_preserve_exact_body_bytes: bool = False,
512
+ wait_for_schema: float | None = None,
513
+ rate_limit: str | None = None,
514
+ output_sanitize: bool = True,
515
+ output_truncate: bool = True,
516
+ contrib_openapi_fill_missing_examples: bool = False,
517
+ hypothesis_phases: list[Phase] | None = None,
518
+ hypothesis_no_phases: list[Phase] | None = None,
519
+ generation_modes: tuple[GenerationMode, ...] = DEFAULT_GENERATOR_MODES,
520
+ generation_seed: int | None = None,
521
+ generation_max_examples: int | None = None,
522
+ generation_optimize: Sequence[str] | None = None,
523
+ generation_deterministic: bool | None = None,
524
+ generation_database: str | None = None,
525
+ generation_unique_inputs: bool = False,
526
+ generation_allow_x00: bool = True,
527
+ generation_graphql_allow_null: bool = True,
528
+ generation_with_security_parameters: bool = True,
529
+ generation_codec: str = "utf-8",
530
+ force_color: bool = False,
531
+ no_color: bool = False,
532
+ **__kwargs: Any,
533
+ ) -> None:
534
+ """Run tests against an API using a specified SCHEMA.
535
+
536
+ [Required] SCHEMA: Path to an OpenAPI (`.json`, `.yml`) or GraphQL SDL file, or a URL pointing to such specifications
537
+ """
538
+ if no_color and force_color:
539
+ raise click.UsageError(COLOR_OPTIONS_INVALID_USAGE_MESSAGE)
540
+ ensure_color(ctx, no_color, force_color)
541
+
542
+ validation.validate_schema(schema, base_url)
543
+
544
+ _hypothesis_phases = prepare_phases(hypothesis_phases, hypothesis_no_phases)
545
+ _hypothesis_suppress_health_check = prepare_health_checks(suppress_health_check)
546
+
547
+ for experiment in experiments:
548
+ experiment.enable()
549
+ if contrib_openapi_fill_missing_examples:
550
+ contrib.openapi.fill_missing_examples.install()
551
+
552
+ override = Override(query=set_query, headers=set_header, cookies=set_cookie, path_parameters=set_path)
553
+
554
+ validation.validate_auth_overlap(auth, headers, override)
555
+
556
+ filter_set = FilterArguments(
557
+ include_path=include_path,
558
+ include_method=include_method,
559
+ include_name=include_name,
560
+ include_tag=include_tag,
561
+ include_operation_id=include_operation_id,
562
+ include_path_regex=include_path_regex,
563
+ include_method_regex=include_method_regex,
564
+ include_name_regex=include_name_regex,
565
+ include_tag_regex=include_tag_regex,
566
+ include_operation_id_regex=include_operation_id_regex,
567
+ exclude_path=exclude_path,
568
+ exclude_method=exclude_method,
569
+ exclude_name=exclude_name,
570
+ exclude_tag=exclude_tag,
571
+ exclude_operation_id=exclude_operation_id,
572
+ exclude_path_regex=exclude_path_regex,
573
+ exclude_method_regex=exclude_method_regex,
574
+ exclude_name_regex=exclude_name_regex,
575
+ exclude_tag_regex=exclude_tag_regex,
576
+ exclude_operation_id_regex=exclude_operation_id_regex,
577
+ include_by=include_by,
578
+ exclude_by=exclude_by,
579
+ exclude_deprecated=exclude_deprecated,
580
+ ).into()
581
+
582
+ selected_checks, checks_config = CheckArguments(
583
+ included_check_names=included_check_names,
584
+ excluded_check_names=excluded_check_names,
585
+ positive_data_acceptance_allowed_statuses=positive_data_acceptance_allowed_statuses,
586
+ missing_required_header_allowed_statuses=missing_required_header_allowed_statuses,
587
+ negative_data_rejection_allowed_statuses=negative_data_rejection_allowed_statuses,
588
+ max_response_time=max_response_time,
589
+ ).into()
590
+
591
+ if exit_first:
592
+ max_failures = 1
593
+
594
+ cassette_config = None
595
+ if cassette_path is not None:
596
+ cassette_config = CassetteConfig(
597
+ path=cassette_path,
598
+ format=cassette_format,
599
+ sanitize_output=output_sanitize,
600
+ preserve_exact_body_bytes=cassette_preserve_exact_body_bytes,
601
+ )
602
+
603
+ # Use the same seed for all tests unless `derandomize=True` is used
604
+ seed: int | None
605
+ if generation_seed is None and not generation_deterministic:
606
+ seed = Random().getrandbits(128)
607
+ else:
608
+ seed = generation_seed
609
+
610
+ phases_ = [PhaseName.PROBING] + [PhaseName.from_str(phase) for phase in phases]
611
+
612
+ config = executor.RunConfig(
613
+ location=schema,
614
+ base_url=base_url,
615
+ engine=EngineConfig(
616
+ execution=ExecutionConfig(
617
+ phases=phases_,
618
+ checks=selected_checks,
619
+ targets=TARGETS.get_by_names(generation_optimize or []),
620
+ hypothesis_settings=prepare_settings(
621
+ database=generation_database,
622
+ derandomize=generation_deterministic,
623
+ max_examples=generation_max_examples,
624
+ phases=_hypothesis_phases,
625
+ suppress_health_check=_hypothesis_suppress_health_check,
626
+ ),
627
+ generation=GenerationConfig(
628
+ modes=list(generation_modes),
629
+ allow_x00=generation_allow_x00,
630
+ graphql_allow_null=generation_graphql_allow_null,
631
+ codec=generation_codec,
632
+ with_security_parameters=generation_with_security_parameters,
633
+ ),
634
+ max_failures=max_failures,
635
+ no_failfast=no_failfast,
636
+ unique_inputs=generation_unique_inputs,
637
+ seed=seed,
638
+ workers_num=workers_num,
639
+ ),
640
+ network=NetworkConfig(
641
+ auth=auth,
642
+ headers=headers,
643
+ timeout=request_timeout,
644
+ tls_verify=request_tls_verify,
645
+ proxy=request_proxy,
646
+ cert=(request_cert, request_cert_key)
647
+ if request_cert is not None and request_cert_key is not None
648
+ else request_cert,
649
+ ),
650
+ override=override,
651
+ checks_config=checks_config,
652
+ ),
653
+ filter_set=filter_set,
654
+ wait_for_schema=wait_for_schema,
655
+ rate_limit=rate_limit,
656
+ output=OutputConfig(sanitize=output_sanitize, truncate=output_truncate),
657
+ cassette=cassette_config,
658
+ junit_xml=junit_xml,
659
+ args=ctx.args,
660
+ params=ctx.params,
661
+ )
662
+ executor.execute(config)