schemathesis 3.39.15__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 +238 -308
  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.15.dist-info → schemathesis-4.0.0.dist-info}/entry_points.txt +1 -1
  151. {schemathesis-3.39.15.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 -712
  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.15.dist-info/METADATA +0 -293
  251. schemathesis-3.39.15.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.15.dist-info → schemathesis-4.0.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,85 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ import click
7
+ from tomli import TOMLDecodeError
8
+
9
+ from schemathesis.cli.commands.data import Data
10
+ from schemathesis.cli.commands.run import run as run_command
11
+ from schemathesis.cli.commands.run.handlers.output import display_header
12
+ from schemathesis.cli.constants import EXTENSIONS_DOCUMENTATION_URL
13
+ from schemathesis.cli.core import get_terminal_width
14
+ from schemathesis.cli.ext.groups import CommandWithGroupedOptions, GroupedOption
15
+ from schemathesis.config import ConfigError, SchemathesisConfig
16
+ from schemathesis.core.errors import HookError, format_exception
17
+ from schemathesis.core.version import SCHEMATHESIS_VERSION
18
+
19
+ CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
20
+
21
+
22
+ @click.group(context_settings=CONTEXT_SETTINGS) # type: ignore[misc]
23
+ @click.option( # type: ignore[misc]
24
+ "--config-file",
25
+ "config_file",
26
+ help="The path to `schemathesis.toml` file to use for configuration",
27
+ metavar="PATH",
28
+ type=str,
29
+ )
30
+ @click.pass_context # type: ignore[misc]
31
+ @click.version_option() # type: ignore[misc]
32
+ def schemathesis(ctx: click.Context, config_file: str | None) -> None:
33
+ """Property-based API testing for OpenAPI and GraphQL."""
34
+ try:
35
+ if config_file is not None:
36
+ config = SchemathesisConfig.from_path(config_file)
37
+ else:
38
+ config = SchemathesisConfig.discover()
39
+ except (TOMLDecodeError, ConfigError) as exc:
40
+ display_header(SCHEMATHESIS_VERSION)
41
+ click.secho(
42
+ f"❌ Failed to load configuration file{f' from {config_file}' if config_file else ''}",
43
+ fg="red",
44
+ bold=True,
45
+ )
46
+ if isinstance(exc, TOMLDecodeError):
47
+ detail = "The configuration file content is not valid TOML"
48
+ else:
49
+ detail = "The loaded configuration is incorrect"
50
+ click.echo(f"\n{detail}\n\n{exc}")
51
+ ctx.exit(1)
52
+ except HookError as exc:
53
+ click.secho("Unable to load Schemathesis extension hooks", fg="red", bold=True)
54
+ formatted_module_name = click.style(f"'{exc.module_path}'", bold=True)
55
+ cause = exc.__cause__
56
+ assert isinstance(cause, Exception)
57
+ if isinstance(cause, ModuleNotFoundError) and cause.name == exc.module_path:
58
+ click.echo(
59
+ f"\nAn attempt to import the module {formatted_module_name} failed because it could not be found."
60
+ )
61
+ click.echo("\nEnsure the module name is correctly spelled and reachable from the current directory.")
62
+ else:
63
+ click.echo(f"\nAn error occurred while importing the module {formatted_module_name}. Traceback:")
64
+ message = format_exception(cause, with_traceback=True, skip_frames=1)
65
+ click.secho(f"\n{message}", fg="red")
66
+ click.echo(f"\nFor more information on how to work with hooks, visit {EXTENSIONS_DOCUMENTATION_URL}")
67
+ ctx.exit(1)
68
+ ctx.obj = Data(config=config)
69
+
70
+
71
+ @dataclass
72
+ class Group:
73
+ name: str
74
+
75
+ __slots__ = ("name",)
76
+
77
+ def add_option(self, *args: Any, **kwargs: Any) -> None:
78
+ run.params.append(GroupedOption(args, group=self.name, **kwargs))
79
+
80
+
81
+ run = schemathesis.command(
82
+ short_help="Execute automated tests based on API specifications",
83
+ cls=CommandWithGroupedOptions,
84
+ context_settings={"terminal_width": get_terminal_width(), **CONTEXT_SETTINGS},
85
+ )(run_command)
@@ -0,0 +1,10 @@
1
+ from dataclasses import dataclass
2
+
3
+ from schemathesis.config import SchemathesisConfig
4
+
5
+
6
+ @dataclass
7
+ class Data:
8
+ config: SchemathesisConfig
9
+
10
+ __slots__ = ("config",)
@@ -0,0 +1,590 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Callable
5
+
6
+ import click
7
+ from click.utils import LazyFile
8
+
9
+ from schemathesis.checks import CHECKS, load_all_checks
10
+ from schemathesis.cli.commands.run import executor, validation
11
+ from schemathesis.cli.commands.run.filters import with_filters
12
+ from schemathesis.cli.constants import DEFAULT_WORKERS, MAX_WORKERS, MIN_WORKERS
13
+ from schemathesis.cli.core import ensure_color
14
+ from schemathesis.cli.ext.groups import group, grouped_option
15
+ from schemathesis.cli.ext.options import (
16
+ CsvChoice,
17
+ CsvEnumChoice,
18
+ CustomHelpMessageChoice,
19
+ RegistryChoice,
20
+ )
21
+ from schemathesis.config import (
22
+ DEFAULT_REPORT_DIRECTORY,
23
+ HealthCheck,
24
+ ReportFormat,
25
+ SchemathesisConfig,
26
+ SchemathesisWarning,
27
+ )
28
+ from schemathesis.core import HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER
29
+ from schemathesis.core.transport import DEFAULT_RESPONSE_TIMEOUT
30
+ from schemathesis.generation import GenerationMode
31
+ from schemathesis.generation.metrics import METRICS, MetricFunction
32
+
33
+ load_all_checks()
34
+
35
+ COLOR_OPTIONS_INVALID_USAGE_MESSAGE = "Can't use `--no-color` and `--force-color` simultaneously"
36
+
37
+ DEFAULT_PHASES = ["examples", "coverage", "fuzzing", "stateful"]
38
+
39
+
40
+ @click.argument( # type: ignore[misc]
41
+ "location",
42
+ type=str,
43
+ callback=validation.validate_schema_location,
44
+ )
45
+ @group("Options")
46
+ @grouped_option(
47
+ "--url",
48
+ "-u",
49
+ "base_url",
50
+ help="API base URL (required for file-based schemas)",
51
+ metavar="URL",
52
+ type=str,
53
+ callback=validation.validate_base_url,
54
+ envvar="SCHEMATHESIS_BASE_URL",
55
+ )
56
+ @grouped_option(
57
+ "--workers",
58
+ "-w",
59
+ "workers",
60
+ help="Number of concurrent workers for testing. Auto-adjusts if 'auto' is specified",
61
+ type=CustomHelpMessageChoice(
62
+ ["auto", *list(map(str, range(MIN_WORKERS, MAX_WORKERS + 1)))],
63
+ choices_repr=f"[auto, {MIN_WORKERS}-{MAX_WORKERS}]",
64
+ ),
65
+ default=str(DEFAULT_WORKERS),
66
+ show_default=True,
67
+ callback=validation.convert_workers,
68
+ metavar="",
69
+ )
70
+ @grouped_option(
71
+ "--phases",
72
+ help="A comma-separated list of test phases to run",
73
+ type=CsvChoice(DEFAULT_PHASES),
74
+ default=",".join(DEFAULT_PHASES),
75
+ metavar="",
76
+ )
77
+ @grouped_option(
78
+ "--suppress-health-check",
79
+ help="A comma-separated list of Schemathesis health checks to disable",
80
+ type=CsvEnumChoice(HealthCheck),
81
+ metavar="",
82
+ )
83
+ @grouped_option(
84
+ "--wait-for-schema",
85
+ help="Maximum duration, in seconds, to wait for the API schema to become available. Disabled by default",
86
+ type=click.FloatRange(1.0),
87
+ default=None,
88
+ envvar="SCHEMATHESIS_WAIT_FOR_SCHEMA",
89
+ )
90
+ @grouped_option(
91
+ "--warnings",
92
+ help="Control warning display: 'off' to disable all, or comma-separated list of warning types to enable",
93
+ type=str,
94
+ default=None,
95
+ callback=validation.validate_warnings,
96
+ metavar="WARNINGS",
97
+ )
98
+ @group("API validation options")
99
+ @grouped_option(
100
+ "--checks",
101
+ "-c",
102
+ "included_check_names",
103
+ multiple=True,
104
+ help="Comma-separated list of checks to run against API responses",
105
+ type=RegistryChoice(CHECKS, with_all=True),
106
+ default=None,
107
+ callback=validation.reduce_list,
108
+ show_default=True,
109
+ metavar="",
110
+ )
111
+ @grouped_option(
112
+ "--exclude-checks",
113
+ "excluded_check_names",
114
+ multiple=True,
115
+ help="Comma-separated list of checks to skip during testing",
116
+ type=RegistryChoice(CHECKS, with_all=True),
117
+ default=None,
118
+ callback=validation.reduce_list,
119
+ show_default=True,
120
+ metavar="",
121
+ )
122
+ @grouped_option(
123
+ "--max-failures",
124
+ "max_failures",
125
+ type=click.IntRange(min=1),
126
+ help="Terminate the test suite after reaching a specified number of failures or errors",
127
+ show_default=True,
128
+ )
129
+ @grouped_option(
130
+ "--continue-on-failure",
131
+ "continue_on_failure",
132
+ help="Continue executing all test cases within a scenario, even after encountering failures",
133
+ is_flag=True,
134
+ default=False,
135
+ metavar="",
136
+ )
137
+ @grouped_option(
138
+ "--max-response-time",
139
+ help="Maximum allowed API response time in seconds",
140
+ type=click.FloatRange(min=0.0, min_open=True),
141
+ metavar="SECONDS",
142
+ )
143
+ @group(
144
+ "Filtering options",
145
+ description=(
146
+ "Filter operations by path, method, name, tag, or operation-id using:\n\n"
147
+ "--include-TYPE VALUE Match operations with exact VALUE\n"
148
+ "--include-TYPE-regex PATTERN Match operations using regular expression\n"
149
+ "--exclude-TYPE VALUE Exclude operations with exact VALUE\n"
150
+ "--exclude-TYPE-regex PATTERN Exclude operations using regular expression"
151
+ ),
152
+ )
153
+ @with_filters
154
+ @grouped_option(
155
+ "--include-by",
156
+ "include_by",
157
+ type=str,
158
+ metavar="EXPR",
159
+ callback=validation.validate_filter_expression,
160
+ help="Include using custom expression",
161
+ )
162
+ @grouped_option(
163
+ "--exclude-by",
164
+ "exclude_by",
165
+ type=str,
166
+ callback=validation.validate_filter_expression,
167
+ metavar="EXPR",
168
+ help="Exclude using custom expression",
169
+ )
170
+ @grouped_option(
171
+ "--exclude-deprecated",
172
+ help="Skip deprecated operations",
173
+ is_flag=True,
174
+ is_eager=True,
175
+ default=False,
176
+ show_default=True,
177
+ )
178
+ @group("Network requests options")
179
+ @grouped_option(
180
+ "--header",
181
+ "-H",
182
+ "headers",
183
+ help=r"Add a custom HTTP header to all API requests",
184
+ metavar="NAME:VALUE",
185
+ multiple=True,
186
+ type=str,
187
+ callback=validation.validate_headers,
188
+ )
189
+ @grouped_option(
190
+ "--auth",
191
+ "-a",
192
+ help="Authenticate all API requests with basic authentication",
193
+ metavar="USER:PASS",
194
+ type=str,
195
+ callback=validation.validate_auth,
196
+ )
197
+ @grouped_option(
198
+ "--proxy",
199
+ "request_proxy",
200
+ help="Set the proxy for all network requests",
201
+ metavar="URL",
202
+ type=str,
203
+ )
204
+ @grouped_option(
205
+ "--tls-verify",
206
+ "request_tls_verify",
207
+ help="Path to CA bundle for TLS verification, or 'false' to disable",
208
+ type=str,
209
+ default="true",
210
+ show_default=True,
211
+ callback=validation.convert_boolean_string,
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
+ @grouped_option(
221
+ "--request-timeout",
222
+ help="Timeout limit, in seconds, for each network request during tests",
223
+ type=click.FloatRange(min=0.0, min_open=True),
224
+ default=DEFAULT_RESPONSE_TIMEOUT,
225
+ )
226
+ @grouped_option(
227
+ "--request-cert",
228
+ help="File path of unencrypted client certificate for authentication. "
229
+ "The certificate can be bundled with a private key (e.g. PEM) or the private "
230
+ "key can be provided with the --request-cert-key argument",
231
+ type=click.Path(exists=True),
232
+ default=None,
233
+ show_default=False,
234
+ )
235
+ @grouped_option(
236
+ "--request-cert-key",
237
+ help="Specify the file path of the private key for the client certificate",
238
+ type=click.Path(exists=True),
239
+ default=None,
240
+ show_default=False,
241
+ callback=validation.validate_request_cert_key,
242
+ )
243
+ @group("Output options")
244
+ @grouped_option(
245
+ "--report",
246
+ "report_formats",
247
+ help="Generate test reports in formats specified as a comma-separated list",
248
+ type=CsvEnumChoice(ReportFormat),
249
+ is_eager=True,
250
+ metavar="FORMAT",
251
+ )
252
+ @grouped_option(
253
+ "--report-dir",
254
+ "report_directory",
255
+ help="Directory to store all report files",
256
+ type=click.Path(file_okay=False, dir_okay=True),
257
+ default=DEFAULT_REPORT_DIRECTORY,
258
+ show_default=True,
259
+ )
260
+ @grouped_option(
261
+ "--report-junit-path",
262
+ help="Custom path for JUnit XML report",
263
+ type=click.File("w", encoding="utf-8"),
264
+ is_eager=True,
265
+ )
266
+ @grouped_option(
267
+ "--report-vcr-path",
268
+ help="Custom path for VCR cassette",
269
+ type=click.File("w", encoding="utf-8"),
270
+ is_eager=True,
271
+ )
272
+ @grouped_option(
273
+ "--report-har-path",
274
+ help="Custom path for HAR file",
275
+ type=click.File("w", encoding="utf-8"),
276
+ is_eager=True,
277
+ )
278
+ @grouped_option(
279
+ "--report-preserve-bytes",
280
+ help="Retain exact byte sequence of payloads in cassettes, encoded as base64",
281
+ type=bool,
282
+ is_flag=True,
283
+ default=False,
284
+ callback=validation.validate_preserve_bytes,
285
+ )
286
+ @grouped_option(
287
+ "--output-sanitize",
288
+ type=str,
289
+ default="true",
290
+ show_default=True,
291
+ help="Enable or disable automatic output sanitization to obscure sensitive data",
292
+ metavar="BOOLEAN",
293
+ callback=validation.convert_boolean_string,
294
+ )
295
+ @grouped_option(
296
+ "--output-truncate",
297
+ help="Truncate schemas and responses in error messages",
298
+ type=str,
299
+ default="true",
300
+ show_default=True,
301
+ metavar="BOOLEAN",
302
+ callback=validation.convert_boolean_string,
303
+ )
304
+ @group("Data generation options")
305
+ @grouped_option(
306
+ "--mode",
307
+ "-m",
308
+ "generation_modes",
309
+ help="Test data generation mode",
310
+ type=click.Choice([item.value for item in GenerationMode] + ["all"]),
311
+ default="all",
312
+ callback=validation.convert_generation_mode,
313
+ show_default=True,
314
+ metavar="",
315
+ )
316
+ @grouped_option(
317
+ "--max-examples",
318
+ "-n",
319
+ "generation_max_examples",
320
+ help="Maximum number of test cases per API operation",
321
+ type=click.IntRange(1),
322
+ )
323
+ @grouped_option(
324
+ "--seed",
325
+ "generation_seed",
326
+ help="Random seed for reproducible test runs",
327
+ type=int,
328
+ )
329
+ @grouped_option(
330
+ "--no-shrink",
331
+ "generation_no_shrink",
332
+ help="Disable test case shrinking. Makes test failures harder to debug but improves performance",
333
+ is_flag=True,
334
+ )
335
+ @grouped_option(
336
+ "--generation-deterministic",
337
+ help="Enables deterministic mode, which eliminates random variation between tests",
338
+ is_flag=True,
339
+ is_eager=True,
340
+ default=False,
341
+ show_default=True,
342
+ )
343
+ @grouped_option(
344
+ "--generation-allow-x00",
345
+ help="Whether to allow the generation of 'NULL' bytes within strings",
346
+ type=str,
347
+ default="true",
348
+ show_default=True,
349
+ metavar="BOOLEAN",
350
+ callback=validation.convert_boolean_string,
351
+ )
352
+ @grouped_option(
353
+ "--generation-codec",
354
+ help="The codec used for generating strings",
355
+ type=str,
356
+ default="utf-8",
357
+ callback=validation.validate_generation_codec,
358
+ )
359
+ @grouped_option(
360
+ "--generation-maximize",
361
+ "generation_maximize",
362
+ multiple=True,
363
+ help="Guide input generation to values more likely to expose bugs via targeted property-based testing",
364
+ type=RegistryChoice(METRICS),
365
+ default=None,
366
+ callback=validation.convert_maximize,
367
+ show_default=True,
368
+ metavar="METRIC",
369
+ )
370
+ @grouped_option(
371
+ "--generation-with-security-parameters",
372
+ help="Whether to generate security parameters",
373
+ type=str,
374
+ default="true",
375
+ show_default=True,
376
+ callback=validation.convert_boolean_string,
377
+ metavar="BOOLEAN",
378
+ )
379
+ @grouped_option(
380
+ "--generation-graphql-allow-null",
381
+ help="Whether to use `null` values for optional arguments in GraphQL queries",
382
+ type=str,
383
+ default="true",
384
+ show_default=True,
385
+ callback=validation.convert_boolean_string,
386
+ metavar="BOOLEAN",
387
+ )
388
+ @grouped_option(
389
+ "--generation-database",
390
+ help="Storage for examples discovered by Schemathesis. "
391
+ f"Use 'none' to disable, '{HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER}' for temporary storage, "
392
+ f"or specify a file path for persistent storage",
393
+ type=str,
394
+ callback=validation.validate_hypothesis_database,
395
+ )
396
+ @grouped_option(
397
+ "--generation-unique-inputs",
398
+ "generation_unique_inputs",
399
+ help="Force the generation of unique test cases",
400
+ is_flag=True,
401
+ default=False,
402
+ show_default=True,
403
+ metavar="BOOLEAN",
404
+ )
405
+ @group("Global options")
406
+ @grouped_option("--no-color", help="Disable ANSI color escape codes", type=bool, is_flag=True)
407
+ @grouped_option("--force-color", help="Explicitly tells to enable ANSI color escape codes", type=bool, is_flag=True)
408
+ @click.pass_context # type: ignore[misc]
409
+ def run(
410
+ ctx: click.Context,
411
+ *,
412
+ location: str,
413
+ auth: tuple[str, str] | None,
414
+ headers: dict[str, str],
415
+ included_check_names: list[str] | None,
416
+ excluded_check_names: list[str] | None,
417
+ max_response_time: float | None = None,
418
+ phases: list[str] = DEFAULT_PHASES,
419
+ max_failures: int | None = None,
420
+ continue_on_failure: bool | None = None,
421
+ include_path: tuple[str, ...],
422
+ include_path_regex: str | None,
423
+ include_method: tuple[str, ...],
424
+ include_method_regex: str | None,
425
+ include_name: tuple[str, ...],
426
+ include_name_regex: str | None,
427
+ include_tag: tuple[str, ...],
428
+ include_tag_regex: str | None,
429
+ include_operation_id: tuple[str, ...],
430
+ include_operation_id_regex: str | None,
431
+ exclude_path: tuple[str, ...],
432
+ exclude_path_regex: str | None,
433
+ exclude_method: tuple[str, ...],
434
+ exclude_method_regex: str | None,
435
+ exclude_name: tuple[str, ...],
436
+ exclude_name_regex: str | None,
437
+ exclude_tag: tuple[str, ...],
438
+ exclude_tag_regex: str | None,
439
+ exclude_operation_id: tuple[str, ...],
440
+ exclude_operation_id_regex: str | None,
441
+ include_by: Callable | None = None,
442
+ exclude_by: Callable | None = None,
443
+ exclude_deprecated: bool = False,
444
+ workers: int = DEFAULT_WORKERS,
445
+ base_url: str | None,
446
+ wait_for_schema: float | None = None,
447
+ suppress_health_check: list[HealthCheck] | None,
448
+ warnings: bool | list[SchemathesisWarning] | None,
449
+ rate_limit: str | None = None,
450
+ request_timeout: int | None = None,
451
+ request_tls_verify: bool = True,
452
+ request_cert: str | None = None,
453
+ request_cert_key: str | None = None,
454
+ request_proxy: str | None = None,
455
+ report_formats: list[ReportFormat] | None,
456
+ report_directory: Path | str = DEFAULT_REPORT_DIRECTORY,
457
+ report_junit_path: LazyFile | None = None,
458
+ report_vcr_path: LazyFile | None = None,
459
+ report_har_path: LazyFile | None = None,
460
+ report_preserve_bytes: bool = False,
461
+ output_sanitize: bool = True,
462
+ output_truncate: bool = True,
463
+ generation_modes: list[GenerationMode],
464
+ generation_seed: int | None = None,
465
+ generation_max_examples: int | None = None,
466
+ generation_maximize: list[MetricFunction] | None,
467
+ generation_deterministic: bool = False,
468
+ generation_database: str | None = None,
469
+ generation_unique_inputs: bool = False,
470
+ generation_allow_x00: bool = True,
471
+ generation_graphql_allow_null: bool = True,
472
+ generation_with_security_parameters: bool = True,
473
+ generation_codec: str = "utf-8",
474
+ generation_no_shrink: bool = False,
475
+ force_color: bool = False,
476
+ no_color: bool = False,
477
+ **__kwargs: Any,
478
+ ) -> None:
479
+ """Generate and run property-based tests against your API.
480
+
481
+ \b
482
+ LOCATION can be:
483
+ - Local file: ./openapi.json, ./schema.yaml, ./schema.graphql
484
+ - OpenAPI URL: https://api.example.com/openapi.json
485
+ - GraphQL URL: https://api.example.com/graphql/
486
+ """ # noqa: D301
487
+ if no_color and force_color:
488
+ raise click.UsageError(COLOR_OPTIONS_INVALID_USAGE_MESSAGE)
489
+
490
+ config: SchemathesisConfig = ctx.obj.config
491
+
492
+ # First, set the right color
493
+ color: bool | None
494
+ if force_color:
495
+ color = True
496
+ elif no_color:
497
+ color = False
498
+ else:
499
+ color = config.color
500
+ ensure_color(ctx, color)
501
+
502
+ validation.validate_auth_overlap(auth, headers)
503
+
504
+ # Then override the global config from CLI options
505
+ config.update(
506
+ color=color,
507
+ suppress_health_check=suppress_health_check,
508
+ seed=generation_seed,
509
+ wait_for_schema=wait_for_schema,
510
+ max_failures=max_failures,
511
+ )
512
+ config.output.sanitization.update(enabled=output_sanitize)
513
+ config.output.truncation.update(enabled=output_truncate)
514
+ config.reports.update(
515
+ formats=report_formats,
516
+ junit_path=report_junit_path.name if report_junit_path else None,
517
+ vcr_path=report_vcr_path.name if report_vcr_path else None,
518
+ har_path=report_har_path.name if report_har_path else None,
519
+ directory=Path(report_directory),
520
+ preserve_bytes=report_preserve_bytes,
521
+ )
522
+ # Other CLI options work as an override for all defined projects
523
+ config.projects.override.update(
524
+ base_url=base_url,
525
+ headers=headers if headers else None,
526
+ basic_auth=auth,
527
+ workers=workers,
528
+ continue_on_failure=continue_on_failure,
529
+ rate_limit=rate_limit,
530
+ request_timeout=request_timeout,
531
+ tls_verify=request_tls_verify,
532
+ request_cert=request_cert,
533
+ request_cert_key=request_cert_key,
534
+ proxy=request_proxy,
535
+ warnings=warnings,
536
+ )
537
+ # These are filters for what API operations should be tested
538
+ filter_set = {
539
+ "include_path": include_path,
540
+ "include_method": include_method,
541
+ "include_name": include_name,
542
+ "include_tag": include_tag,
543
+ "include_operation_id": include_operation_id,
544
+ "include_path_regex": include_path_regex,
545
+ "include_method_regex": include_method_regex,
546
+ "include_name_regex": include_name_regex,
547
+ "include_tag_regex": include_tag_regex,
548
+ "include_operation_id_regex": include_operation_id_regex,
549
+ "exclude_path": exclude_path,
550
+ "exclude_method": exclude_method,
551
+ "exclude_name": exclude_name,
552
+ "exclude_tag": exclude_tag,
553
+ "exclude_operation_id": exclude_operation_id,
554
+ "exclude_path_regex": exclude_path_regex,
555
+ "exclude_method_regex": exclude_method_regex,
556
+ "exclude_name_regex": exclude_name_regex,
557
+ "exclude_tag_regex": exclude_tag_regex,
558
+ "exclude_operation_id_regex": exclude_operation_id_regex,
559
+ "include_by": include_by,
560
+ "exclude_by": exclude_by,
561
+ "exclude_deprecated": exclude_deprecated,
562
+ }
563
+ config.projects.override.phases.update(phases=phases)
564
+ config.projects.override.checks.update(
565
+ included_check_names=included_check_names,
566
+ excluded_check_names=excluded_check_names,
567
+ max_response_time=max_response_time,
568
+ )
569
+ config.projects.override.generation.update(
570
+ modes=generation_modes,
571
+ max_examples=generation_max_examples,
572
+ no_shrink=generation_no_shrink,
573
+ maximize=generation_maximize,
574
+ deterministic=generation_deterministic,
575
+ database=generation_database,
576
+ unique_inputs=generation_unique_inputs,
577
+ allow_x00=generation_allow_x00,
578
+ graphql_allow_null=generation_graphql_allow_null,
579
+ with_security_parameters=generation_with_security_parameters,
580
+ codec=generation_codec,
581
+ )
582
+
583
+ executor.execute(
584
+ location=location,
585
+ filter_set=filter_set,
586
+ # We don't the project yet, so pass the default config
587
+ config=config.projects.get_default(),
588
+ args=ctx.args,
589
+ params=ctx.params,
590
+ )