schemathesis 3.39.7__py3-none-any.whl → 4.0.0a2__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 (229) hide show
  1. schemathesis/__init__.py +27 -65
  2. schemathesis/auths.py +26 -68
  3. schemathesis/checks.py +130 -60
  4. schemathesis/cli/__init__.py +5 -2105
  5. schemathesis/cli/commands/__init__.py +37 -0
  6. schemathesis/cli/commands/run/__init__.py +662 -0
  7. schemathesis/cli/commands/run/checks.py +80 -0
  8. schemathesis/cli/commands/run/context.py +117 -0
  9. schemathesis/cli/commands/run/events.py +30 -0
  10. schemathesis/cli/commands/run/executor.py +141 -0
  11. schemathesis/cli/commands/run/filters.py +202 -0
  12. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  13. schemathesis/cli/commands/run/handlers/base.py +18 -0
  14. schemathesis/cli/{cassettes.py → commands/run/handlers/cassettes.py} +178 -247
  15. schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
  16. schemathesis/cli/commands/run/handlers/output.py +1368 -0
  17. schemathesis/cli/commands/run/hypothesis.py +105 -0
  18. schemathesis/cli/commands/run/loaders.py +129 -0
  19. schemathesis/cli/{callbacks.py → commands/run/validation.py} +59 -175
  20. schemathesis/cli/constants.py +5 -58
  21. schemathesis/cli/core.py +17 -0
  22. schemathesis/cli/ext/fs.py +14 -0
  23. schemathesis/cli/ext/groups.py +55 -0
  24. schemathesis/cli/{options.py → ext/options.py} +37 -16
  25. schemathesis/cli/hooks.py +36 -0
  26. schemathesis/contrib/__init__.py +1 -3
  27. schemathesis/contrib/openapi/__init__.py +1 -3
  28. schemathesis/contrib/openapi/fill_missing_examples.py +3 -7
  29. schemathesis/core/__init__.py +58 -0
  30. schemathesis/core/compat.py +25 -0
  31. schemathesis/core/control.py +2 -0
  32. schemathesis/core/curl.py +58 -0
  33. schemathesis/core/deserialization.py +65 -0
  34. schemathesis/core/errors.py +370 -0
  35. schemathesis/core/failures.py +315 -0
  36. schemathesis/core/fs.py +19 -0
  37. schemathesis/core/loaders.py +104 -0
  38. schemathesis/core/marks.py +66 -0
  39. schemathesis/{transports/content_types.py → core/media_types.py} +14 -12
  40. schemathesis/{internal/output.py → core/output/__init__.py} +1 -0
  41. schemathesis/core/output/sanitization.py +197 -0
  42. schemathesis/{throttling.py → core/rate_limit.py} +16 -17
  43. schemathesis/core/registries.py +31 -0
  44. schemathesis/core/transforms.py +113 -0
  45. schemathesis/core/transport.py +108 -0
  46. schemathesis/core/validation.py +38 -0
  47. schemathesis/core/version.py +7 -0
  48. schemathesis/engine/__init__.py +30 -0
  49. schemathesis/engine/config.py +59 -0
  50. schemathesis/engine/context.py +119 -0
  51. schemathesis/engine/control.py +36 -0
  52. schemathesis/engine/core.py +157 -0
  53. schemathesis/engine/errors.py +394 -0
  54. schemathesis/engine/events.py +243 -0
  55. schemathesis/engine/phases/__init__.py +66 -0
  56. schemathesis/{runner → engine/phases}/probes.py +49 -68
  57. schemathesis/engine/phases/stateful/__init__.py +66 -0
  58. schemathesis/engine/phases/stateful/_executor.py +301 -0
  59. schemathesis/engine/phases/stateful/context.py +85 -0
  60. schemathesis/engine/phases/unit/__init__.py +175 -0
  61. schemathesis/engine/phases/unit/_executor.py +322 -0
  62. schemathesis/engine/phases/unit/_pool.py +74 -0
  63. schemathesis/engine/recorder.py +246 -0
  64. schemathesis/errors.py +31 -0
  65. schemathesis/experimental/__init__.py +9 -40
  66. schemathesis/filters.py +7 -95
  67. schemathesis/generation/__init__.py +3 -3
  68. schemathesis/generation/case.py +190 -0
  69. schemathesis/generation/coverage.py +22 -22
  70. schemathesis/{_patches.py → generation/hypothesis/__init__.py} +15 -6
  71. schemathesis/generation/hypothesis/builder.py +585 -0
  72. schemathesis/generation/{_hypothesis.py → hypothesis/examples.py} +2 -11
  73. schemathesis/generation/hypothesis/given.py +66 -0
  74. schemathesis/generation/hypothesis/reporting.py +14 -0
  75. schemathesis/generation/hypothesis/strategies.py +16 -0
  76. schemathesis/generation/meta.py +115 -0
  77. schemathesis/generation/modes.py +28 -0
  78. schemathesis/generation/overrides.py +96 -0
  79. schemathesis/generation/stateful/__init__.py +20 -0
  80. schemathesis/{stateful → generation/stateful}/state_machine.py +84 -109
  81. schemathesis/generation/targets.py +69 -0
  82. schemathesis/graphql/__init__.py +15 -0
  83. schemathesis/graphql/checks.py +109 -0
  84. schemathesis/graphql/loaders.py +131 -0
  85. schemathesis/hooks.py +17 -62
  86. schemathesis/openapi/__init__.py +13 -0
  87. schemathesis/openapi/checks.py +387 -0
  88. schemathesis/openapi/generation/__init__.py +0 -0
  89. schemathesis/openapi/generation/filters.py +63 -0
  90. schemathesis/openapi/loaders.py +178 -0
  91. schemathesis/pytest/__init__.py +5 -0
  92. schemathesis/pytest/control_flow.py +7 -0
  93. schemathesis/pytest/lazy.py +273 -0
  94. schemathesis/pytest/loaders.py +12 -0
  95. schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +94 -107
  96. schemathesis/python/__init__.py +0 -0
  97. schemathesis/python/asgi.py +12 -0
  98. schemathesis/python/wsgi.py +12 -0
  99. schemathesis/schemas.py +456 -228
  100. schemathesis/specs/graphql/__init__.py +0 -1
  101. schemathesis/specs/graphql/_cache.py +1 -2
  102. schemathesis/specs/graphql/scalars.py +5 -3
  103. schemathesis/specs/graphql/schemas.py +122 -123
  104. schemathesis/specs/graphql/validation.py +11 -17
  105. schemathesis/specs/openapi/__init__.py +6 -1
  106. schemathesis/specs/openapi/_cache.py +1 -2
  107. schemathesis/specs/openapi/_hypothesis.py +97 -134
  108. schemathesis/specs/openapi/checks.py +238 -219
  109. schemathesis/specs/openapi/converter.py +4 -4
  110. schemathesis/specs/openapi/definitions.py +1 -1
  111. schemathesis/specs/openapi/examples.py +22 -20
  112. schemathesis/specs/openapi/expressions/__init__.py +11 -15
  113. schemathesis/specs/openapi/expressions/extractors.py +1 -4
  114. schemathesis/specs/openapi/expressions/nodes.py +33 -32
  115. schemathesis/specs/openapi/formats.py +3 -2
  116. schemathesis/specs/openapi/links.py +123 -299
  117. schemathesis/specs/openapi/media_types.py +10 -12
  118. schemathesis/specs/openapi/negative/__init__.py +2 -1
  119. schemathesis/specs/openapi/negative/mutations.py +3 -2
  120. schemathesis/specs/openapi/parameters.py +8 -6
  121. schemathesis/specs/openapi/patterns.py +1 -1
  122. schemathesis/specs/openapi/references.py +11 -51
  123. schemathesis/specs/openapi/schemas.py +177 -191
  124. schemathesis/specs/openapi/security.py +1 -1
  125. schemathesis/specs/openapi/serialization.py +10 -6
  126. schemathesis/specs/openapi/stateful/__init__.py +97 -91
  127. schemathesis/transport/__init__.py +104 -0
  128. schemathesis/transport/asgi.py +26 -0
  129. schemathesis/transport/prepare.py +99 -0
  130. schemathesis/transport/requests.py +221 -0
  131. schemathesis/{_xml.py → transport/serialization.py} +69 -7
  132. schemathesis/transport/wsgi.py +165 -0
  133. {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/METADATA +18 -14
  134. schemathesis-4.0.0a2.dist-info/RECORD +151 -0
  135. {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/entry_points.txt +1 -1
  136. schemathesis/_compat.py +0 -74
  137. schemathesis/_dependency_versions.py +0 -19
  138. schemathesis/_hypothesis.py +0 -559
  139. schemathesis/_override.py +0 -50
  140. schemathesis/_rate_limiter.py +0 -7
  141. schemathesis/cli/context.py +0 -75
  142. schemathesis/cli/debug.py +0 -27
  143. schemathesis/cli/handlers.py +0 -19
  144. schemathesis/cli/junitxml.py +0 -124
  145. schemathesis/cli/output/__init__.py +0 -1
  146. schemathesis/cli/output/default.py +0 -936
  147. schemathesis/cli/output/short.py +0 -59
  148. schemathesis/cli/reporting.py +0 -79
  149. schemathesis/cli/sanitization.py +0 -26
  150. schemathesis/code_samples.py +0 -151
  151. schemathesis/constants.py +0 -56
  152. schemathesis/contrib/openapi/formats/__init__.py +0 -9
  153. schemathesis/contrib/openapi/formats/uuid.py +0 -16
  154. schemathesis/contrib/unique_data.py +0 -41
  155. schemathesis/exceptions.py +0 -571
  156. schemathesis/extra/_aiohttp.py +0 -28
  157. schemathesis/extra/_flask.py +0 -13
  158. schemathesis/extra/_server.py +0 -18
  159. schemathesis/failures.py +0 -277
  160. schemathesis/fixups/__init__.py +0 -37
  161. schemathesis/fixups/fast_api.py +0 -41
  162. schemathesis/fixups/utf8_bom.py +0 -28
  163. schemathesis/generation/_methods.py +0 -44
  164. schemathesis/graphql.py +0 -3
  165. schemathesis/internal/__init__.py +0 -7
  166. schemathesis/internal/checks.py +0 -84
  167. schemathesis/internal/copy.py +0 -32
  168. schemathesis/internal/datetime.py +0 -5
  169. schemathesis/internal/deprecation.py +0 -38
  170. schemathesis/internal/diff.py +0 -15
  171. schemathesis/internal/extensions.py +0 -27
  172. schemathesis/internal/jsonschema.py +0 -36
  173. schemathesis/internal/transformation.py +0 -26
  174. schemathesis/internal/validation.py +0 -34
  175. schemathesis/lazy.py +0 -474
  176. schemathesis/loaders.py +0 -122
  177. schemathesis/models.py +0 -1341
  178. schemathesis/parameters.py +0 -90
  179. schemathesis/runner/__init__.py +0 -605
  180. schemathesis/runner/events.py +0 -389
  181. schemathesis/runner/impl/__init__.py +0 -3
  182. schemathesis/runner/impl/context.py +0 -104
  183. schemathesis/runner/impl/core.py +0 -1246
  184. schemathesis/runner/impl/solo.py +0 -80
  185. schemathesis/runner/impl/threadpool.py +0 -391
  186. schemathesis/runner/serialization.py +0 -544
  187. schemathesis/sanitization.py +0 -252
  188. schemathesis/serializers.py +0 -328
  189. schemathesis/service/__init__.py +0 -18
  190. schemathesis/service/auth.py +0 -11
  191. schemathesis/service/ci.py +0 -202
  192. schemathesis/service/client.py +0 -133
  193. schemathesis/service/constants.py +0 -38
  194. schemathesis/service/events.py +0 -61
  195. schemathesis/service/extensions.py +0 -224
  196. schemathesis/service/hosts.py +0 -111
  197. schemathesis/service/metadata.py +0 -71
  198. schemathesis/service/models.py +0 -258
  199. schemathesis/service/report.py +0 -255
  200. schemathesis/service/serialization.py +0 -173
  201. schemathesis/service/usage.py +0 -66
  202. schemathesis/specs/graphql/loaders.py +0 -364
  203. schemathesis/specs/openapi/expressions/context.py +0 -16
  204. schemathesis/specs/openapi/loaders.py +0 -708
  205. schemathesis/specs/openapi/stateful/statistic.py +0 -198
  206. schemathesis/specs/openapi/stateful/types.py +0 -14
  207. schemathesis/specs/openapi/validation.py +0 -26
  208. schemathesis/stateful/__init__.py +0 -147
  209. schemathesis/stateful/config.py +0 -97
  210. schemathesis/stateful/context.py +0 -135
  211. schemathesis/stateful/events.py +0 -274
  212. schemathesis/stateful/runner.py +0 -309
  213. schemathesis/stateful/sink.py +0 -68
  214. schemathesis/stateful/statistic.py +0 -22
  215. schemathesis/stateful/validation.py +0 -100
  216. schemathesis/targets.py +0 -77
  217. schemathesis/transports/__init__.py +0 -359
  218. schemathesis/transports/asgi.py +0 -7
  219. schemathesis/transports/auth.py +0 -38
  220. schemathesis/transports/headers.py +0 -36
  221. schemathesis/transports/responses.py +0 -57
  222. schemathesis/types.py +0 -44
  223. schemathesis/utils.py +0 -164
  224. schemathesis-3.39.7.dist-info/RECORD +0 -160
  225. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
  226. /schemathesis/{_lazy_import.py → core/lazy_import.py} +0 -0
  227. /schemathesis/{internal → core}/result.py +0 -0
  228. {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/WHEEL +0 -0
  229. {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import defaultdict
4
+ from typing import Any, Callable
5
+
6
+ import click
7
+
8
+ GROUPS: list[str] = []
9
+
10
+
11
+ class CommandWithGroupedOptions(click.Command):
12
+ def format_options(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
13
+ groups = defaultdict(list)
14
+ for param in self.get_params(ctx):
15
+ rv = param.get_help_record(ctx)
16
+ if rv is not None:
17
+ (option_repr, message) = rv
18
+ if isinstance(param.type, click.Choice):
19
+ message += (
20
+ getattr(param.type, "choices_repr", None)
21
+ or f" [possible values: {', '.join(param.type.choices)}]"
22
+ )
23
+
24
+ if isinstance(param, GroupedOption):
25
+ group = param.group
26
+ else:
27
+ group = "Global options"
28
+ groups[group].append((option_repr, message))
29
+ for group in GROUPS:
30
+ with formatter.section(group or "Options"):
31
+ formatter.write_dl(groups[group], col_max=40)
32
+
33
+
34
+ class GroupedOption(click.Option):
35
+ def __init__(self, *args: Any, group: str | None = None, **kwargs: Any):
36
+ super().__init__(*args, **kwargs)
37
+ self.group = group
38
+
39
+
40
+ def group(name: str) -> Callable:
41
+ GROUPS.append(name)
42
+
43
+ def _inner(cmd: Callable) -> Callable:
44
+ for param in reversed(cmd.__click_params__): # type: ignore[attr-defined]
45
+ if not isinstance(param, GroupedOption) or param.group is not None:
46
+ break
47
+ param.group = name
48
+ return cmd
49
+
50
+ return _inner
51
+
52
+
53
+ def grouped_option(*args: Any, **kwargs: Any) -> Callable:
54
+ kwargs.setdefault("cls", GroupedOption)
55
+ return click.option(*args, **kwargs)
@@ -1,15 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any, NoReturn
3
+ from enum import Enum
4
+ from typing import Any, NoReturn
4
5
 
5
6
  import click
6
7
 
7
- from ..constants import NOT_SET
8
-
9
- if TYPE_CHECKING:
10
- from enum import Enum
11
-
12
- from ..types import NotSet
8
+ from schemathesis.core import NOT_SET, NotSet
9
+ from schemathesis.core.registries import Registry
13
10
 
14
11
 
15
12
  class CustomHelpMessageChoice(click.Choice):
@@ -29,13 +26,23 @@ class BaseCsvChoice(click.Choice):
29
26
  invalid_options = set(selected) - set(self.choices)
30
27
  return selected, invalid_options
31
28
 
32
- def fail_on_invalid_options(self, invalid_options: set[str], selected: list[str]) -> NoReturn:
29
+ def fail_on_invalid_options(self, invalid_options: set[str], selected: list[str]) -> NoReturn: # type: ignore[misc]
33
30
  # Sort to keep the error output consistent with the passed values
34
31
  sorted_options = ", ".join(sorted(invalid_options, key=selected.index))
35
32
  available_options = ", ".join(self.choices)
36
33
  self.fail(f"invalid choice(s): {sorted_options}. Choose from {available_options}.")
37
34
 
38
35
 
36
+ class CsvChoice(BaseCsvChoice):
37
+ def convert( # type: ignore[return]
38
+ self, value: str, param: click.core.Parameter | None, ctx: click.core.Context | None
39
+ ) -> list[str]:
40
+ selected, invalid_options = self.parse_value(value)
41
+ if not invalid_options and selected:
42
+ return selected
43
+ self.fail_on_invalid_options(invalid_options, selected)
44
+
45
+
39
46
  class CsvEnumChoice(BaseCsvChoice):
40
47
  def __init__(self, choices: type[Enum]):
41
48
  self.enum = choices
@@ -50,14 +57,6 @@ class CsvEnumChoice(BaseCsvChoice):
50
57
  self.fail_on_invalid_options(invalid_options, selected)
51
58
 
52
59
 
53
- class CsvChoice(BaseCsvChoice):
54
- def convert(self, value: str, param: click.core.Parameter | None, ctx: click.core.Context | None) -> list[str]:
55
- selected, invalid_options = self.parse_value(value)
56
- if not invalid_options and selected:
57
- return selected
58
- self.fail_on_invalid_options(invalid_options, selected)
59
-
60
-
61
60
  class CsvListChoice(click.ParamType):
62
61
  def convert( # type: ignore[return]
63
62
  self, value: str, param: click.core.Parameter | None, ctx: click.core.Context | None
@@ -65,6 +64,28 @@ class CsvListChoice(click.ParamType):
65
64
  return [item for item in value.split(",") if item]
66
65
 
67
66
 
67
+ class RegistryChoice(BaseCsvChoice):
68
+ def __init__(self, registry: Registry, with_all: bool = False) -> None:
69
+ self.registry = registry
70
+ self.case_sensitive = True
71
+ self.with_all = with_all
72
+
73
+ @property
74
+ def choices(self) -> list[str]:
75
+ choices = self.registry.get_all_names()
76
+ if self.with_all:
77
+ choices.append("all")
78
+ return choices
79
+
80
+ def convert( # type: ignore[return]
81
+ self, value: str, param: click.core.Parameter | None, ctx: click.core.Context | None
82
+ ) -> list[str]:
83
+ selected, invalid_options = self.parse_value(value)
84
+ if not invalid_options and selected:
85
+ return selected
86
+ self.fail_on_invalid_options(invalid_options, selected)
87
+
88
+
68
89
  class OptionalInt(click.types.IntRange):
69
90
  def convert( # type: ignore
70
91
  self, value: str, param: click.core.Parameter | None, ctx: click.core.Context | None
@@ -0,0 +1,36 @@
1
+ import os
2
+ import sys
3
+
4
+ import click
5
+
6
+ from schemathesis.cli.constants import EXTENSIONS_DOCUMENTATION_URL
7
+ from schemathesis.core.errors import format_exception
8
+
9
+ HOOKS_MODULE_ENV_VAR = "SCHEMATHESIS_HOOKS"
10
+
11
+
12
+ def load() -> None:
13
+ hooks = os.getenv(HOOKS_MODULE_ENV_VAR)
14
+ if hooks:
15
+ _load(hooks)
16
+
17
+
18
+ def _load(module_name: str) -> None:
19
+ """Load the given hook by importing it."""
20
+ try:
21
+ sys.path.append(os.getcwd()) # fix ModuleNotFoundError module in cwd
22
+ __import__(module_name)
23
+ except Exception as exc:
24
+ click.secho("Unable to load Schemathesis extension hooks", fg="red", bold=True)
25
+ formatted_module_name = click.style(f"'{module_name}'", bold=True)
26
+ if isinstance(exc, ModuleNotFoundError) and exc.name == module_name:
27
+ click.echo(
28
+ f"\nAn attempt to import the module {formatted_module_name} failed because it could not be found."
29
+ )
30
+ click.echo("\nEnsure the module name is correctly spelled and reachable from the current directory.")
31
+ else:
32
+ click.echo(f"\nAn error occurred while importing the module {formatted_module_name}. Traceback:")
33
+ message = format_exception(exc, with_traceback=True, skip_frames=1)
34
+ click.secho(f"\n{message}", fg="red")
35
+ click.echo(f"\nFor more information on how to work with hooks, visit {EXTENSIONS_DOCUMENTATION_URL}")
36
+ raise click.exceptions.Exit(1) from None
@@ -1,11 +1,9 @@
1
- from . import openapi, unique_data
1
+ from . import openapi
2
2
 
3
3
 
4
4
  def install() -> None:
5
5
  openapi.install()
6
- unique_data.install()
7
6
 
8
7
 
9
8
  def uninstall() -> None:
10
9
  openapi.uninstall()
11
- unique_data.uninstall()
@@ -1,11 +1,9 @@
1
- from . import fill_missing_examples, formats
1
+ from . import fill_missing_examples
2
2
 
3
3
 
4
4
  def install() -> None:
5
- formats.install()
6
5
  fill_missing_examples.install()
7
6
 
8
7
 
9
8
  def uninstall() -> None:
10
- formats.uninstall()
11
9
  fill_missing_examples.uninstall()
@@ -1,11 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
4
-
5
- from ...hooks import HookContext, register, unregister
6
-
7
- if TYPE_CHECKING:
8
- from ...models import Case
3
+ from schemathesis.generation.case import Case
4
+ from schemathesis.hooks import HookContext, register, unregister
9
5
 
10
6
 
11
7
  def install() -> None:
@@ -18,7 +14,7 @@ def uninstall() -> None:
18
14
 
19
15
  def before_add_examples(context: HookContext, examples: list[Case]) -> None:
20
16
  if not examples and context.operation is not None:
21
- from ...generation import add_single_example
17
+ from schemathesis.generation.hypothesis.examples import add_single_example
22
18
 
23
19
  strategy = context.operation.as_strategy()
24
20
  add_single_example(strategy, examples)
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ from dataclasses import dataclass
5
+
6
+ SCHEMATHESIS_TEST_CASE_HEADER = "X-Schemathesis-TestCaseId"
7
+
8
+
9
+ class NotSet: ...
10
+
11
+
12
+ NOT_SET = NotSet()
13
+
14
+
15
+ class SpecificationFeature(str, enum.Enum):
16
+ """Features that Schemathesis can provide for different specifications."""
17
+
18
+ STATEFUL_TESTING = "stateful_testing"
19
+
20
+
21
+ @dataclass
22
+ class Specification:
23
+ kind: SpecificationKind
24
+ version: str
25
+
26
+ @classmethod
27
+ def openapi(cls, version: str) -> Specification:
28
+ return cls(kind=SpecificationKind.OPENAPI, version=version)
29
+
30
+ @classmethod
31
+ def graphql(cls, version: str) -> Specification:
32
+ return cls(kind=SpecificationKind.GRAPHQL, version=version)
33
+
34
+ @property
35
+ def name(self) -> str:
36
+ name = {SpecificationKind.GRAPHQL: "GraphQL", SpecificationKind.OPENAPI: "Open API"}[self.kind]
37
+ return f"{name} {self.version}".strip()
38
+
39
+ def supports_feature(self, feature: SpecificationFeature) -> bool:
40
+ """Check if Schemathesis supports a given feature for this specification."""
41
+ if self.kind == SpecificationKind.OPENAPI:
42
+ return feature in {SpecificationFeature.STATEFUL_TESTING}
43
+ return False
44
+
45
+
46
+ class SpecificationKind(str, enum.Enum):
47
+ """Specification of the given schema."""
48
+
49
+ OPENAPI = "openapi"
50
+ GRAPHQL = "graphql"
51
+
52
+
53
+ def string_to_boolean(value: str) -> str | bool:
54
+ if value.lower() in ("y", "yes", "t", "true", "on", "1"):
55
+ return True
56
+ if value.lower() in ("n", "no", "f", "false", "off", "0"):
57
+ return False
58
+ return value
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from jsonschema import RefResolutionError
7
+
8
+ try:
9
+ BaseExceptionGroup = BaseExceptionGroup # type: ignore
10
+ except NameError:
11
+ from exceptiongroup import BaseExceptionGroup # type: ignore
12
+
13
+
14
+ def __getattr__(name: str) -> type[RefResolutionError] | type[BaseExceptionGroup]:
15
+ if name == "RefResolutionError":
16
+ # Import it just once to keep just a single warning
17
+ from jsonschema import RefResolutionError
18
+
19
+ return RefResolutionError
20
+ if name == "BaseExceptionGroup":
21
+ return BaseExceptionGroup
22
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
23
+
24
+
25
+ __all__ = ["BaseExceptionGroup", "RefResolutionError"]
@@ -0,0 +1,2 @@
1
+ class SkipTest(BaseException):
2
+ """Raised when the current test should be skipped and the executor then continues normally."""
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import lru_cache
4
+ from shlex import quote
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from schemathesis.core import SCHEMATHESIS_TEST_CASE_HEADER
8
+
9
+ if TYPE_CHECKING:
10
+ from requests.models import CaseInsensitiveDict
11
+
12
+
13
+ def generate(
14
+ *,
15
+ method: str,
16
+ url: str,
17
+ body: str | bytes | None,
18
+ verify: bool,
19
+ headers: dict[str, Any],
20
+ known_generated_headers: dict[str, Any] | None,
21
+ ) -> str:
22
+ """Generate a code snippet for making HTTP requests."""
23
+ _filter_headers(headers, known_generated_headers or {})
24
+ command = f"curl -X {method}"
25
+ for key, value in headers.items():
26
+ header = f"{key}: {value}"
27
+ command += f" -H {quote(header)}"
28
+ if body:
29
+ if isinstance(body, bytes):
30
+ body = body.decode("utf-8", errors="replace")
31
+ command += f" -d {quote(body)}"
32
+ if not verify:
33
+ command += " --insecure"
34
+ return f"{command} {quote(url)}"
35
+
36
+
37
+ def _filter_headers(headers: dict[str, Any], known_generated_headers: dict[str, Any]) -> None:
38
+ for key in list(headers):
39
+ if key not in known_generated_headers and key in get_excluded_headers():
40
+ del headers[key]
41
+
42
+
43
+ @lru_cache
44
+ def get_excluded_headers() -> CaseInsensitiveDict:
45
+ from requests.structures import CaseInsensitiveDict
46
+ from requests.utils import default_headers
47
+
48
+ # These headers are added automatically by Schemathesis or `requests`.
49
+ # Do not show them in code samples to make them more readable
50
+
51
+ return CaseInsensitiveDict(
52
+ {
53
+ "Content-Length": None,
54
+ "Transfer-Encoding": None,
55
+ SCHEMATHESIS_TEST_CASE_HEADER: None,
56
+ **default_headers(),
57
+ }
58
+ )
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from functools import lru_cache
5
+ from typing import TYPE_CHECKING, Any, BinaryIO, TextIO
6
+
7
+ if TYPE_CHECKING:
8
+ import yaml
9
+
10
+
11
+ @lru_cache
12
+ def get_yaml_loader() -> type[yaml.SafeLoader]:
13
+ """Create a YAML loader, that doesn't parse specific tokens into Python objects."""
14
+ import yaml
15
+
16
+ try:
17
+ from yaml import CSafeLoader as SafeLoader
18
+ except ImportError:
19
+ from yaml import SafeLoader # type: ignore
20
+
21
+ cls: type[yaml.SafeLoader] = type("YAMLLoader", (SafeLoader,), {})
22
+ cls.yaml_implicit_resolvers = {
23
+ key: [(tag, regexp) for tag, regexp in mapping if tag != "tag:yaml.org,2002:timestamp"]
24
+ for key, mapping in cls.yaml_implicit_resolvers.copy().items()
25
+ }
26
+
27
+ # Fix pyyaml scientific notation parse bug
28
+ # See PR: https://github.com/yaml/pyyaml/pull/174 for upstream fix
29
+ cls.add_implicit_resolver( # type: ignore
30
+ "tag:yaml.org,2002:float",
31
+ re.compile(
32
+ r"""^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+]?[0-9]+)?
33
+ |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
34
+ |\.[0-9_]+(?:[eE][-+]?[0-9]+)?
35
+ |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*
36
+ |[-+]?\.(?:inf|Inf|INF)
37
+ |\.(?:nan|NaN|NAN))$""",
38
+ re.VERBOSE,
39
+ ),
40
+ list("-+0123456789."),
41
+ )
42
+
43
+ def construct_mapping(self: SafeLoader, node: yaml.Node, deep: bool = False) -> dict[str, Any]:
44
+ if isinstance(node, yaml.MappingNode):
45
+ self.flatten_mapping(node) # type: ignore
46
+ mapping = {}
47
+ for key_node, value_node in node.value:
48
+ # If the key has a tag different from `str` - use its string value.
49
+ # With this change all integer keys or YAML 1.1 boolean-ish values like "on" / "off" will not be cast to
50
+ # a different type
51
+ if key_node.tag != "tag:yaml.org,2002:str":
52
+ key = key_node.value
53
+ else:
54
+ key = self.construct_object(key_node, deep) # type: ignore
55
+ mapping[key] = self.construct_object(value_node, deep) # type: ignore
56
+ return mapping
57
+
58
+ cls.construct_mapping = construct_mapping # type: ignore
59
+ return cls
60
+
61
+
62
+ def deserialize_yaml(stream: str | bytes | TextIO | BinaryIO) -> Any:
63
+ import yaml
64
+
65
+ return yaml.load(stream, get_yaml_loader())