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
schemathesis/_override.py DELETED
@@ -1,50 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from typing import TYPE_CHECKING
5
-
6
- from .exceptions import UsageError
7
-
8
- if TYPE_CHECKING:
9
- from .models import APIOperation
10
- from .parameters import ParameterSet
11
- from .types import GenericTest
12
-
13
-
14
- @dataclass
15
- class CaseOverride:
16
- """Overrides for various parts of a test case."""
17
-
18
- query: dict[str, str]
19
- headers: dict[str, str]
20
- cookies: dict[str, str]
21
- path_parameters: dict[str, str]
22
-
23
- def for_operation(self, operation: APIOperation) -> dict[str, dict[str, str]]:
24
- return {
25
- "query": (_for_parameters(self.query, operation.query)),
26
- "headers": (_for_parameters(self.headers, operation.headers)),
27
- "cookies": (_for_parameters(self.cookies, operation.cookies)),
28
- "path_parameters": (_for_parameters(self.path_parameters, operation.path_parameters)),
29
- }
30
-
31
-
32
- def _for_parameters(overridden: dict[str, str], defined: ParameterSet) -> dict[str, str]:
33
- output = {}
34
- for param in defined:
35
- if param.name in overridden:
36
- output[param.name] = overridden[param.name]
37
- return output
38
-
39
-
40
- def get_override_from_mark(test: GenericTest) -> CaseOverride | None:
41
- return getattr(test, "_schemathesis_override", None)
42
-
43
-
44
- def set_override_mark(test: GenericTest, override: CaseOverride) -> None:
45
- test._schemathesis_override = override # type: ignore[attr-defined]
46
-
47
-
48
- def check_no_override_mark(test: GenericTest) -> None:
49
- if hasattr(test, "_schemathesis_override"):
50
- raise UsageError(f"`{test.__name__}` has already been decorated with `override`.")
schemathesis/_patches.py DELETED
@@ -1,21 +0,0 @@
1
- """A set of performance-related patches."""
2
-
3
- from typing import Any
4
-
5
-
6
- def install() -> None:
7
- from hypothesis.internal.reflection import is_first_param_referenced_in_function
8
- from hypothesis.strategies._internal import core
9
- from hypothesis_jsonschema import _from_schema, _resolve
10
-
11
- from .internal.copy import fast_deepcopy
12
-
13
- # This one is used a lot, and under the hood it re-parses the AST of the same function
14
- def _is_first_param_referenced_in_function(f: Any) -> bool:
15
- if f.__name__ == "from_object_schema" and f.__module__ == "hypothesis_jsonschema._from_schema":
16
- return True
17
- return is_first_param_referenced_in_function(f)
18
-
19
- core.is_first_param_referenced_in_function = _is_first_param_referenced_in_function # type: ignore
20
- _resolve.deepcopy = fast_deepcopy # type: ignore
21
- _from_schema.deepcopy = fast_deepcopy # type: ignore
@@ -1,7 +0,0 @@
1
- from ._dependency_versions import IS_PYRATE_LIMITER_ABOVE_3
2
-
3
- if IS_PYRATE_LIMITER_ABOVE_3:
4
- from pyrate_limiter import Limiter, Rate, RateItem
5
- else:
6
- from pyrate_limiter import Limiter
7
- from pyrate_limiter import RequestRate as Rate
@@ -1,466 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import codecs
4
- import enum
5
- import operator
6
- import os
7
- import re
8
- import traceback
9
- from contextlib import contextmanager
10
- from functools import partial, reduce
11
- from typing import TYPE_CHECKING, Callable, Generator
12
- from urllib.parse import urlparse
13
-
14
- import click
15
-
16
- from .. import exceptions, experimental, throttling
17
- from ..code_samples import CodeSampleStyle
18
- from ..constants import TRUE_VALUES
19
- from ..exceptions import extract_nth_traceback
20
- from ..generation import DataGenerationMethod
21
- from ..internal.transformation import convert_boolean_string as _convert_boolean_string
22
- from ..internal.validation import file_exists, is_filename, is_illegal_surrogate
23
- from ..loaders import load_app
24
- from ..service.hosts import get_temporary_hosts_file
25
- from ..stateful import Stateful
26
- from ..transports.headers import has_invalid_characters, is_latin_1_encodable
27
- from .cassettes import CassetteFormat
28
- from .constants import DEFAULT_WORKERS
29
-
30
- if TYPE_CHECKING:
31
- import hypothesis
32
- from click.types import LazyFile # type: ignore[attr-defined]
33
-
34
- from ..types import PathLike
35
-
36
- INVALID_DERANDOMIZE_MESSAGE = (
37
- "`--hypothesis-derandomize` implies no database, so passing `--hypothesis-database` too is invalid."
38
- )
39
- MISSING_CASSETTE_PATH_ARGUMENT_MESSAGE = (
40
- "Missing argument, `--cassette-path` should be specified as well if you use `--cassette-preserve-exact-body-bytes`."
41
- )
42
- INVALID_SCHEMA_MESSAGE = "Invalid SCHEMA, must be a valid URL, file path or an API name from Schemathesis.io."
43
- FILE_DOES_NOT_EXIST_MESSAGE = "The specified file does not exist. Please provide a valid path to an existing file."
44
- INVALID_BASE_URL_MESSAGE = (
45
- "The provided base URL is invalid. This URL serves as a prefix for all API endpoints you want to test. "
46
- "Make sure it is a properly formatted URL."
47
- )
48
- MISSING_BASE_URL_MESSAGE = "The `--base-url` option is required when specifying a schema via a file."
49
- WSGI_DOCUMENTATION_URL = "https://schemathesis.readthedocs.io/en/stable/python.html#asgi-wsgi-support"
50
- APPLICATION_MISSING_MODULE_MESSAGE = f"""Unable to import application from {{module}}.
51
-
52
- The `--app` option should follow this format:
53
-
54
- module_path:variable_name
55
-
56
- - `module_path`: A path to an importable Python module.
57
- - `variable_name`: The name of the application variable within that module.
58
-
59
- Example: `st run --app=your_module:app ...`
60
-
61
- For details on working with WSGI applications, visit {WSGI_DOCUMENTATION_URL}"""
62
- APPLICATION_IMPORT_ERROR_MESSAGE = f"""An error occurred while loading the application from {{module}}.
63
-
64
- Traceback:
65
-
66
- {{traceback}}
67
-
68
- For details on working with WSGI applications, visit {WSGI_DOCUMENTATION_URL}"""
69
- MISSING_REQUEST_CERT_MESSAGE = "The `--request-cert` option must be specified if `--request-cert-key` is used."
70
-
71
-
72
- @enum.unique
73
- class SchemaInputKind(enum.Enum):
74
- """Kinds of SCHEMA input."""
75
-
76
- # Regular URL like https://example.schemathesis.io/openapi.json
77
- URL = 1
78
- # Local path
79
- PATH = 2
80
- # Relative path within a Python app
81
- APP_PATH = 3
82
- # A name for API created in Schemathesis.io
83
- NAME = 4
84
-
85
-
86
- def parse_schema_kind(schema: str, app: str | None) -> SchemaInputKind:
87
- """Detect what kind the input schema is."""
88
- try:
89
- netloc = urlparse(schema).netloc
90
- except ValueError as exc:
91
- raise click.UsageError(INVALID_SCHEMA_MESSAGE) from exc
92
- if "\x00" in schema or not schema:
93
- raise click.UsageError(INVALID_SCHEMA_MESSAGE)
94
- if netloc:
95
- return SchemaInputKind.URL
96
- if file_exists(schema) or is_filename(schema):
97
- return SchemaInputKind.PATH
98
- if app is not None:
99
- return SchemaInputKind.APP_PATH
100
- # Assume NAME if it is not a URL or PATH or APP_PATH
101
- return SchemaInputKind.NAME
102
-
103
-
104
- def validate_schema(
105
- schema: str,
106
- kind: SchemaInputKind,
107
- *,
108
- base_url: str | None,
109
- dry_run: bool,
110
- app: str | None,
111
- api_name: str | None,
112
- ) -> None:
113
- if kind == SchemaInputKind.URL:
114
- validate_url(schema)
115
- if kind == SchemaInputKind.PATH:
116
- if app is None:
117
- if not file_exists(schema):
118
- raise click.UsageError(FILE_DOES_NOT_EXIST_MESSAGE)
119
- # Base URL is required if it is not a dry run
120
- if base_url is None and not dry_run:
121
- raise click.UsageError(MISSING_BASE_URL_MESSAGE)
122
- if kind == SchemaInputKind.NAME:
123
- if api_name is not None:
124
- raise click.UsageError(f"Got unexpected extra argument ({api_name})")
125
-
126
-
127
- def validate_url(value: str) -> None:
128
- from requests import PreparedRequest, RequestException
129
-
130
- try:
131
- PreparedRequest().prepare_url(value, {}) # type: ignore
132
- except RequestException as exc:
133
- raise click.UsageError(INVALID_SCHEMA_MESSAGE) from exc
134
-
135
-
136
- def validate_base_url(ctx: click.core.Context, param: click.core.Parameter, raw_value: str) -> str:
137
- try:
138
- netloc = urlparse(raw_value).netloc
139
- except ValueError as exc:
140
- raise click.UsageError(INVALID_BASE_URL_MESSAGE) from exc
141
- if raw_value and not netloc:
142
- raise click.UsageError(INVALID_BASE_URL_MESSAGE)
143
- return raw_value
144
-
145
-
146
- def validate_generation_codec(ctx: click.core.Context, param: click.core.Parameter, raw_value: str) -> str:
147
- try:
148
- codecs.getencoder(raw_value)
149
- except LookupError as exc:
150
- raise click.UsageError(f"Codec `{raw_value}` is unknown") from exc
151
- return raw_value
152
-
153
-
154
- def validate_rate_limit(ctx: click.core.Context, param: click.core.Parameter, raw_value: str | None) -> str | None:
155
- if raw_value is None:
156
- return raw_value
157
- try:
158
- throttling.parse_units(raw_value)
159
- return raw_value
160
- except exceptions.UsageError as exc:
161
- raise click.UsageError(exc.args[0]) from exc
162
-
163
-
164
- def validate_app(ctx: click.core.Context, param: click.core.Parameter, raw_value: str | None) -> str | None:
165
- if raw_value is None:
166
- return raw_value
167
- try:
168
- load_app(raw_value)
169
- # String is returned instead of an app because it might be passed to a subprocess
170
- # Since most app instances are not-transferable to another process, they are passed as strings and
171
- # imported in a subprocess
172
- return raw_value
173
- except Exception as exc:
174
- formatted_module_name = click.style(f"'{raw_value}'", bold=True)
175
- if isinstance(exc, ModuleNotFoundError):
176
- message = APPLICATION_MISSING_MODULE_MESSAGE.format(module=formatted_module_name)
177
- click.echo(message)
178
- else:
179
- trace = extract_nth_traceback(exc.__traceback__, 2)
180
- lines = traceback.format_exception(type(exc), exc, trace)
181
- traceback_message = "".join(lines).strip()
182
- message = APPLICATION_IMPORT_ERROR_MESSAGE.format(
183
- module=formatted_module_name, traceback=click.style(traceback_message, fg="red")
184
- )
185
- click.echo(message)
186
- raise click.exceptions.Exit(1) from None
187
-
188
-
189
- def validate_hypothesis_database(
190
- ctx: click.core.Context, param: click.core.Parameter, raw_value: str | None
191
- ) -> str | None:
192
- if raw_value is None:
193
- return raw_value
194
- if ctx.params.get("hypothesis_derandomize"):
195
- raise click.UsageError(INVALID_DERANDOMIZE_MESSAGE)
196
- return raw_value
197
-
198
-
199
- def validate_auth(
200
- ctx: click.core.Context, param: click.core.Parameter, raw_value: str | None
201
- ) -> tuple[str, str] | None:
202
- if raw_value is not None:
203
- with reraise_format_error(raw_value):
204
- user, password = tuple(raw_value.split(":"))
205
- if not user:
206
- raise click.BadParameter("Username should not be empty.")
207
- if not is_latin_1_encodable(user):
208
- raise click.BadParameter("Username should be latin-1 encodable.")
209
- if not is_latin_1_encodable(password):
210
- raise click.BadParameter("Password should be latin-1 encodable.")
211
- return user, password
212
- return None
213
-
214
-
215
- def _validate_and_build_multiple_options(
216
- values: tuple[str, ...], name: str, callback: Callable[[str, str], None]
217
- ) -> dict[str, str]:
218
- output = {}
219
- for raw in values:
220
- try:
221
- key, value = raw.split("=", maxsplit=1)
222
- except ValueError as exc:
223
- raise click.BadParameter(f"Expected NAME=VALUE format, received {raw}.") from exc
224
- key = key.strip()
225
- if not key:
226
- raise click.BadParameter(f"{name} parameter name should not be empty.")
227
- if key in output:
228
- raise click.BadParameter(f"{name} parameter {key} is specified multiple times.")
229
- value = value.strip()
230
- callback(key, value)
231
- output[key] = value
232
- return output
233
-
234
-
235
- def _validate_set_query(_: str, value: str) -> None:
236
- if is_illegal_surrogate(value):
237
- raise click.BadParameter("Query parameter value should not contain surrogates.")
238
-
239
-
240
- def validate_set_query(
241
- ctx: click.core.Context, param: click.core.Parameter, raw_value: tuple[str, ...]
242
- ) -> dict[str, str]:
243
- return _validate_and_build_multiple_options(raw_value, "Query", _validate_set_query)
244
-
245
-
246
- def validate_set_header(
247
- ctx: click.core.Context, param: click.core.Parameter, raw_value: tuple[str, ...]
248
- ) -> dict[str, str]:
249
- return _validate_and_build_multiple_options(raw_value, "Header", partial(_validate_header, where="Header"))
250
-
251
-
252
- def validate_set_cookie(
253
- ctx: click.core.Context, param: click.core.Parameter, raw_value: tuple[str, ...]
254
- ) -> dict[str, str]:
255
- return _validate_and_build_multiple_options(raw_value, "Cookie", partial(_validate_header, where="Cookie"))
256
-
257
-
258
- def _validate_set_path(_: str, value: str) -> None:
259
- if is_illegal_surrogate(value):
260
- raise click.BadParameter("Path parameter value should not contain surrogates.")
261
-
262
-
263
- def validate_set_path(
264
- ctx: click.core.Context, param: click.core.Parameter, raw_value: tuple[str, ...]
265
- ) -> dict[str, str]:
266
- return _validate_and_build_multiple_options(raw_value, "Path", _validate_set_path)
267
-
268
-
269
- def _validate_header(key: str, value: str, where: str) -> None:
270
- if not key:
271
- raise click.BadParameter(f"{where} name should not be empty.")
272
- if not is_latin_1_encodable(key):
273
- raise click.BadParameter(f"{where} name should be latin-1 encodable.")
274
- if not is_latin_1_encodable(value):
275
- raise click.BadParameter(f"{where} value should be latin-1 encodable.")
276
- if has_invalid_characters(key, value):
277
- raise click.BadParameter(f"Invalid return character or leading space in {where.lower()}.")
278
-
279
-
280
- def validate_headers(
281
- ctx: click.core.Context, param: click.core.Parameter, raw_value: tuple[str, ...]
282
- ) -> dict[str, str]:
283
- headers = {}
284
- for header in raw_value:
285
- with reraise_format_error(header):
286
- key, value = header.split(":", maxsplit=1)
287
- value = value.lstrip()
288
- key = key.strip()
289
- _validate_header(key, value, where="Header")
290
- headers[key] = value
291
- return headers
292
-
293
-
294
- def validate_regex(ctx: click.core.Context, param: click.core.Parameter, raw_value: tuple[str, ...]) -> tuple[str, ...]:
295
- for value in raw_value:
296
- try:
297
- re.compile(value)
298
- except (re.error, OverflowError, RuntimeError) as exc:
299
- raise click.BadParameter(f"Invalid regex: {exc.args[0]}.") # noqa: B904
300
- return raw_value
301
-
302
-
303
- def validate_request_cert_key(
304
- ctx: click.core.Context, param: click.core.Parameter, raw_value: str | None
305
- ) -> str | None:
306
- if raw_value is not None and "request_cert" not in ctx.params:
307
- raise click.UsageError(MISSING_REQUEST_CERT_MESSAGE)
308
- return raw_value
309
-
310
-
311
- def validate_preserve_exact_body_bytes(ctx: click.core.Context, param: click.core.Parameter, raw_value: bool) -> bool:
312
- if raw_value and ctx.params["cassette_path"] is None:
313
- raise click.UsageError(MISSING_CASSETTE_PATH_ARGUMENT_MESSAGE)
314
- return raw_value
315
-
316
-
317
- def convert_verbosity(
318
- ctx: click.core.Context, param: click.core.Parameter, value: str | None
319
- ) -> hypothesis.Verbosity | None:
320
- import hypothesis
321
-
322
- if value is None:
323
- return value
324
- return hypothesis.Verbosity[value]
325
-
326
-
327
- def convert_stateful(ctx: click.core.Context, param: click.core.Parameter, value: str) -> Stateful | None:
328
- if value == "none":
329
- return None
330
- return Stateful[value]
331
-
332
-
333
- def convert_experimental(
334
- ctx: click.core.Context, param: click.core.Parameter, value: tuple[str, ...]
335
- ) -> list[experimental.Experiment]:
336
- return [
337
- feature
338
- for feature in experimental.GLOBAL_EXPERIMENTS.available
339
- if feature.name in value or feature.is_env_var_set
340
- ]
341
-
342
-
343
- def convert_checks(ctx: click.core.Context, param: click.core.Parameter, value: tuple[list[str]]) -> list[str]:
344
- return reduce(operator.iadd, value, [])
345
-
346
-
347
- def convert_http_methods(
348
- ctx: click.core.Context, param: click.core.Parameter, value: list[str] | None
349
- ) -> set[str] | None:
350
- if value is None:
351
- return value
352
- return {item.lower() for item in value}
353
-
354
-
355
- def convert_status_codes(
356
- ctx: click.core.Context, param: click.core.Parameter, value: list[str] | None
357
- ) -> list[str] | None:
358
- if not value:
359
- return value
360
-
361
- invalid = []
362
-
363
- for code in value:
364
- if len(code) != 3:
365
- invalid.append(code)
366
- continue
367
-
368
- if code[0] not in {"1", "2", "3", "4", "5"}:
369
- invalid.append(code)
370
- continue
371
-
372
- upper_code = code.upper()
373
-
374
- if "X" in upper_code:
375
- if (
376
- upper_code[1:] == "XX"
377
- or (upper_code[1] == "X" and upper_code[2].isdigit())
378
- or (upper_code[1].isdigit() and upper_code[2] == "X")
379
- ):
380
- continue
381
- else:
382
- invalid.append(code)
383
- continue
384
-
385
- if not code.isnumeric():
386
- invalid.append(code)
387
-
388
- if invalid:
389
- raise click.UsageError(
390
- f"Invalid status code(s): {', '.join(invalid)}. "
391
- "Use valid 3-digit codes between 100 and 599, "
392
- "or wildcards (e.g., 2XX, 2X0, 20X), where X is a wildcard digit."
393
- )
394
- return value
395
-
396
-
397
- def convert_code_sample_style(ctx: click.core.Context, param: click.core.Parameter, value: str) -> CodeSampleStyle:
398
- return CodeSampleStyle.from_str(value)
399
-
400
-
401
- def convert_cassette_format(ctx: click.core.Context, param: click.core.Parameter, value: str) -> CassetteFormat:
402
- return CassetteFormat.from_str(value)
403
-
404
-
405
- def convert_data_generation_method(
406
- ctx: click.core.Context, param: click.core.Parameter, value: str
407
- ) -> list[DataGenerationMethod]:
408
- if value == "all":
409
- return DataGenerationMethod.all()
410
- return [DataGenerationMethod[value]]
411
-
412
-
413
- def _is_usable_dir(path: PathLike) -> bool:
414
- if os.path.isfile(path):
415
- path = os.path.dirname(path)
416
- while not os.path.exists(path):
417
- path = os.path.dirname(path)
418
- return os.path.isdir(path) and os.access(path, os.R_OK | os.W_OK | os.X_OK)
419
-
420
-
421
- def convert_hosts_file(ctx: click.core.Context, param: click.core.Parameter, value: PathLike) -> PathLike:
422
- if not _is_usable_dir(value):
423
- path = get_temporary_hosts_file()
424
- click.secho(
425
- "WARNING: The provided hosts.toml file location is unusable - using a temporary file for this session. "
426
- f"path={str(value)!r}",
427
- fg="yellow",
428
- )
429
- return path
430
- return value
431
-
432
-
433
- def convert_boolean_string(ctx: click.core.Context, param: click.core.Parameter, value: str) -> str | bool:
434
- return _convert_boolean_string(value)
435
-
436
-
437
- def convert_report(ctx: click.core.Context, param: click.core.Option, value: LazyFile) -> LazyFile:
438
- if param.resolve_envvar_value(ctx) is not None and value.lower() in TRUE_VALUES:
439
- value = param.flag_value
440
- return value
441
-
442
-
443
- @contextmanager
444
- def reraise_format_error(raw_value: str) -> Generator[None, None, None]:
445
- try:
446
- yield
447
- except ValueError as exc:
448
- raise click.BadParameter(f"Expected KEY:VALUE format, received {raw_value}.") from exc
449
-
450
-
451
- def get_workers_count() -> int:
452
- """Detect the number of available CPUs for the current process, if possible.
453
-
454
- Use ``DEFAULT_WORKERS`` if not possible to detect.
455
- """
456
- if hasattr(os, "sched_getaffinity"):
457
- # In contrast with `os.cpu_count` this call respects limits on CPU resources on some Unix systems
458
- return len(os.sched_getaffinity(0))
459
- # Number of CPUs in the system, or 1 if undetermined
460
- return os.cpu_count() or DEFAULT_WORKERS
461
-
462
-
463
- def convert_workers(ctx: click.core.Context, param: click.core.Parameter, value: str) -> int:
464
- if value == "auto":
465
- return get_workers_count()
466
- return int(value)