schemathesis 3.25.5__py3-none-any.whl → 4.0.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. schemathesis/__init__.py +27 -65
  2. schemathesis/auths.py +102 -82
  3. schemathesis/checks.py +126 -46
  4. schemathesis/cli/__init__.py +11 -1766
  5. schemathesis/cli/__main__.py +4 -0
  6. schemathesis/cli/commands/__init__.py +37 -0
  7. schemathesis/cli/commands/run/__init__.py +662 -0
  8. schemathesis/cli/commands/run/checks.py +80 -0
  9. schemathesis/cli/commands/run/context.py +117 -0
  10. schemathesis/cli/commands/run/events.py +35 -0
  11. schemathesis/cli/commands/run/executor.py +138 -0
  12. schemathesis/cli/commands/run/filters.py +194 -0
  13. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  14. schemathesis/cli/commands/run/handlers/base.py +18 -0
  15. schemathesis/cli/commands/run/handlers/cassettes.py +494 -0
  16. schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
  17. schemathesis/cli/commands/run/handlers/output.py +746 -0
  18. schemathesis/cli/commands/run/hypothesis.py +105 -0
  19. schemathesis/cli/commands/run/loaders.py +129 -0
  20. schemathesis/cli/{callbacks.py → commands/run/validation.py} +103 -174
  21. schemathesis/cli/constants.py +5 -52
  22. schemathesis/cli/core.py +17 -0
  23. schemathesis/cli/ext/fs.py +14 -0
  24. schemathesis/cli/ext/groups.py +55 -0
  25. schemathesis/cli/{options.py → ext/options.py} +39 -10
  26. schemathesis/cli/hooks.py +36 -0
  27. schemathesis/contrib/__init__.py +1 -3
  28. schemathesis/contrib/openapi/__init__.py +1 -3
  29. schemathesis/contrib/openapi/fill_missing_examples.py +3 -5
  30. schemathesis/core/__init__.py +58 -0
  31. schemathesis/core/compat.py +25 -0
  32. schemathesis/core/control.py +2 -0
  33. schemathesis/core/curl.py +58 -0
  34. schemathesis/core/deserialization.py +65 -0
  35. schemathesis/core/errors.py +370 -0
  36. schemathesis/core/failures.py +285 -0
  37. schemathesis/core/fs.py +19 -0
  38. schemathesis/{_lazy_import.py → core/lazy_import.py} +1 -0
  39. schemathesis/core/loaders.py +104 -0
  40. schemathesis/core/marks.py +66 -0
  41. schemathesis/{transports/content_types.py → core/media_types.py} +17 -13
  42. schemathesis/core/output/__init__.py +69 -0
  43. schemathesis/core/output/sanitization.py +197 -0
  44. schemathesis/core/rate_limit.py +60 -0
  45. schemathesis/core/registries.py +31 -0
  46. schemathesis/{internal → core}/result.py +1 -1
  47. schemathesis/core/transforms.py +113 -0
  48. schemathesis/core/transport.py +108 -0
  49. schemathesis/core/validation.py +38 -0
  50. schemathesis/core/version.py +7 -0
  51. schemathesis/engine/__init__.py +30 -0
  52. schemathesis/engine/config.py +59 -0
  53. schemathesis/engine/context.py +119 -0
  54. schemathesis/engine/control.py +36 -0
  55. schemathesis/engine/core.py +157 -0
  56. schemathesis/engine/errors.py +394 -0
  57. schemathesis/engine/events.py +337 -0
  58. schemathesis/engine/phases/__init__.py +66 -0
  59. schemathesis/{cli → engine/phases}/probes.py +63 -70
  60. schemathesis/engine/phases/stateful/__init__.py +65 -0
  61. schemathesis/engine/phases/stateful/_executor.py +326 -0
  62. schemathesis/engine/phases/stateful/context.py +85 -0
  63. schemathesis/engine/phases/unit/__init__.py +174 -0
  64. schemathesis/engine/phases/unit/_executor.py +321 -0
  65. schemathesis/engine/phases/unit/_pool.py +74 -0
  66. schemathesis/engine/recorder.py +241 -0
  67. schemathesis/errors.py +31 -0
  68. schemathesis/experimental/__init__.py +18 -14
  69. schemathesis/filters.py +103 -14
  70. schemathesis/generation/__init__.py +21 -37
  71. schemathesis/generation/case.py +190 -0
  72. schemathesis/generation/coverage.py +931 -0
  73. schemathesis/generation/hypothesis/__init__.py +30 -0
  74. schemathesis/generation/hypothesis/builder.py +585 -0
  75. schemathesis/generation/hypothesis/examples.py +50 -0
  76. schemathesis/generation/hypothesis/given.py +66 -0
  77. schemathesis/generation/hypothesis/reporting.py +14 -0
  78. schemathesis/generation/hypothesis/strategies.py +16 -0
  79. schemathesis/generation/meta.py +115 -0
  80. schemathesis/generation/modes.py +28 -0
  81. schemathesis/generation/overrides.py +96 -0
  82. schemathesis/generation/stateful/__init__.py +20 -0
  83. schemathesis/{stateful → generation/stateful}/state_machine.py +68 -81
  84. schemathesis/generation/targets.py +69 -0
  85. schemathesis/graphql/__init__.py +15 -0
  86. schemathesis/graphql/checks.py +115 -0
  87. schemathesis/graphql/loaders.py +131 -0
  88. schemathesis/hooks.py +99 -67
  89. schemathesis/openapi/__init__.py +13 -0
  90. schemathesis/openapi/checks.py +412 -0
  91. schemathesis/openapi/generation/__init__.py +0 -0
  92. schemathesis/openapi/generation/filters.py +63 -0
  93. schemathesis/openapi/loaders.py +178 -0
  94. schemathesis/pytest/__init__.py +5 -0
  95. schemathesis/pytest/control_flow.py +7 -0
  96. schemathesis/pytest/lazy.py +273 -0
  97. schemathesis/pytest/loaders.py +12 -0
  98. schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +106 -127
  99. schemathesis/python/__init__.py +0 -0
  100. schemathesis/python/asgi.py +12 -0
  101. schemathesis/python/wsgi.py +12 -0
  102. schemathesis/schemas.py +537 -261
  103. schemathesis/specs/graphql/__init__.py +0 -1
  104. schemathesis/specs/graphql/_cache.py +25 -0
  105. schemathesis/specs/graphql/nodes.py +1 -0
  106. schemathesis/specs/graphql/scalars.py +7 -5
  107. schemathesis/specs/graphql/schemas.py +215 -187
  108. schemathesis/specs/graphql/validation.py +11 -18
  109. schemathesis/specs/openapi/__init__.py +7 -1
  110. schemathesis/specs/openapi/_cache.py +122 -0
  111. schemathesis/specs/openapi/_hypothesis.py +146 -165
  112. schemathesis/specs/openapi/checks.py +565 -67
  113. schemathesis/specs/openapi/converter.py +33 -6
  114. schemathesis/specs/openapi/definitions.py +11 -18
  115. schemathesis/specs/openapi/examples.py +153 -39
  116. schemathesis/specs/openapi/expressions/__init__.py +37 -2
  117. schemathesis/specs/openapi/expressions/context.py +4 -6
  118. schemathesis/specs/openapi/expressions/extractors.py +23 -0
  119. schemathesis/specs/openapi/expressions/lexer.py +20 -18
  120. schemathesis/specs/openapi/expressions/nodes.py +38 -14
  121. schemathesis/specs/openapi/expressions/parser.py +26 -5
  122. schemathesis/specs/openapi/formats.py +45 -0
  123. schemathesis/specs/openapi/links.py +65 -165
  124. schemathesis/specs/openapi/media_types.py +32 -0
  125. schemathesis/specs/openapi/negative/__init__.py +7 -3
  126. schemathesis/specs/openapi/negative/mutations.py +24 -8
  127. schemathesis/specs/openapi/parameters.py +46 -30
  128. schemathesis/specs/openapi/patterns.py +137 -0
  129. schemathesis/specs/openapi/references.py +47 -57
  130. schemathesis/specs/openapi/schemas.py +483 -367
  131. schemathesis/specs/openapi/security.py +25 -7
  132. schemathesis/specs/openapi/serialization.py +11 -6
  133. schemathesis/specs/openapi/stateful/__init__.py +185 -73
  134. schemathesis/specs/openapi/utils.py +6 -1
  135. schemathesis/transport/__init__.py +104 -0
  136. schemathesis/transport/asgi.py +26 -0
  137. schemathesis/transport/prepare.py +99 -0
  138. schemathesis/transport/requests.py +221 -0
  139. schemathesis/{_xml.py → transport/serialization.py} +143 -28
  140. schemathesis/transport/wsgi.py +165 -0
  141. schemathesis-4.0.0a1.dist-info/METADATA +297 -0
  142. schemathesis-4.0.0a1.dist-info/RECORD +152 -0
  143. {schemathesis-3.25.5.dist-info → schemathesis-4.0.0a1.dist-info}/WHEEL +1 -1
  144. {schemathesis-3.25.5.dist-info → schemathesis-4.0.0a1.dist-info}/entry_points.txt +1 -1
  145. schemathesis/_compat.py +0 -74
  146. schemathesis/_dependency_versions.py +0 -17
  147. schemathesis/_hypothesis.py +0 -246
  148. schemathesis/_override.py +0 -49
  149. schemathesis/cli/cassettes.py +0 -375
  150. schemathesis/cli/context.py +0 -55
  151. schemathesis/cli/debug.py +0 -26
  152. schemathesis/cli/handlers.py +0 -16
  153. schemathesis/cli/junitxml.py +0 -43
  154. schemathesis/cli/output/__init__.py +0 -1
  155. schemathesis/cli/output/default.py +0 -765
  156. schemathesis/cli/output/short.py +0 -40
  157. schemathesis/cli/sanitization.py +0 -20
  158. schemathesis/code_samples.py +0 -149
  159. schemathesis/constants.py +0 -55
  160. schemathesis/contrib/openapi/formats/__init__.py +0 -9
  161. schemathesis/contrib/openapi/formats/uuid.py +0 -15
  162. schemathesis/contrib/unique_data.py +0 -41
  163. schemathesis/exceptions.py +0 -560
  164. schemathesis/extra/_aiohttp.py +0 -27
  165. schemathesis/extra/_flask.py +0 -10
  166. schemathesis/extra/_server.py +0 -17
  167. schemathesis/failures.py +0 -209
  168. schemathesis/fixups/__init__.py +0 -36
  169. schemathesis/fixups/fast_api.py +0 -41
  170. schemathesis/fixups/utf8_bom.py +0 -29
  171. schemathesis/graphql.py +0 -4
  172. schemathesis/internal/__init__.py +0 -7
  173. schemathesis/internal/copy.py +0 -13
  174. schemathesis/internal/datetime.py +0 -5
  175. schemathesis/internal/deprecation.py +0 -34
  176. schemathesis/internal/jsonschema.py +0 -35
  177. schemathesis/internal/transformation.py +0 -15
  178. schemathesis/internal/validation.py +0 -34
  179. schemathesis/lazy.py +0 -361
  180. schemathesis/loaders.py +0 -120
  181. schemathesis/models.py +0 -1231
  182. schemathesis/parameters.py +0 -86
  183. schemathesis/runner/__init__.py +0 -555
  184. schemathesis/runner/events.py +0 -309
  185. schemathesis/runner/impl/__init__.py +0 -3
  186. schemathesis/runner/impl/core.py +0 -986
  187. schemathesis/runner/impl/solo.py +0 -90
  188. schemathesis/runner/impl/threadpool.py +0 -400
  189. schemathesis/runner/serialization.py +0 -411
  190. schemathesis/sanitization.py +0 -248
  191. schemathesis/serializers.py +0 -315
  192. schemathesis/service/__init__.py +0 -18
  193. schemathesis/service/auth.py +0 -11
  194. schemathesis/service/ci.py +0 -201
  195. schemathesis/service/client.py +0 -100
  196. schemathesis/service/constants.py +0 -38
  197. schemathesis/service/events.py +0 -57
  198. schemathesis/service/hosts.py +0 -107
  199. schemathesis/service/metadata.py +0 -46
  200. schemathesis/service/models.py +0 -49
  201. schemathesis/service/report.py +0 -255
  202. schemathesis/service/serialization.py +0 -184
  203. schemathesis/service/usage.py +0 -65
  204. schemathesis/specs/graphql/loaders.py +0 -344
  205. schemathesis/specs/openapi/filters.py +0 -49
  206. schemathesis/specs/openapi/loaders.py +0 -667
  207. schemathesis/specs/openapi/stateful/links.py +0 -92
  208. schemathesis/specs/openapi/validation.py +0 -25
  209. schemathesis/stateful/__init__.py +0 -133
  210. schemathesis/targets.py +0 -45
  211. schemathesis/throttling.py +0 -41
  212. schemathesis/transports/__init__.py +0 -5
  213. schemathesis/transports/auth.py +0 -15
  214. schemathesis/transports/headers.py +0 -35
  215. schemathesis/transports/responses.py +0 -52
  216. schemathesis/types.py +0 -35
  217. schemathesis/utils.py +0 -169
  218. schemathesis-3.25.5.dist-info/METADATA +0 -356
  219. schemathesis-3.25.5.dist-info/RECORD +0 -134
  220. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
  221. {schemathesis-3.25.5.dist-info → schemathesis-4.0.0a1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,14 @@
1
+ import click
2
+
3
+ from schemathesis.core.fs import ensure_parent
4
+
5
+
6
+ def open_file(file: click.utils.LazyFile) -> None:
7
+ try:
8
+ ensure_parent(file.name, fail_silently=False)
9
+ except OSError as exc:
10
+ raise click.BadParameter(f"'{file.name}': {exc.strerror}") from exc
11
+ try:
12
+ file.open()
13
+ except click.FileError as exc:
14
+ raise click.BadParameter(exc.format_message()) from exc
@@ -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,11 +1,12 @@
1
1
  from __future__ import annotations
2
+
2
3
  from enum import Enum
3
4
  from typing import Any, NoReturn
4
5
 
5
6
  import click
6
7
 
7
- from ..constants import NOT_SET
8
- from ..types import NotSet
8
+ from schemathesis.core import NOT_SET, NotSet
9
+ from schemathesis.core.registries import Registry
9
10
 
10
11
 
11
12
  class CustomHelpMessageChoice(click.Choice):
@@ -25,13 +26,23 @@ class BaseCsvChoice(click.Choice):
25
26
  invalid_options = set(selected) - set(self.choices)
26
27
  return selected, invalid_options
27
28
 
28
- 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]
29
30
  # Sort to keep the error output consistent with the passed values
30
31
  sorted_options = ", ".join(sorted(invalid_options, key=selected.index))
31
32
  available_options = ", ".join(self.choices)
32
33
  self.fail(f"invalid choice(s): {sorted_options}. Choose from {available_options}.")
33
34
 
34
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
+
35
46
  class CsvEnumChoice(BaseCsvChoice):
36
47
  def __init__(self, choices: type[Enum]):
37
48
  self.enum = choices
@@ -46,12 +57,30 @@ class CsvEnumChoice(BaseCsvChoice):
46
57
  self.fail_on_invalid_options(invalid_options, selected)
47
58
 
48
59
 
49
- class CsvChoice(BaseCsvChoice):
50
- def convert(self, value: str, param: click.core.Parameter | None, ctx: click.core.Context | None) -> list[str]:
51
- selected, invalid_options = self.parse_value(value)
52
- if not invalid_options and selected:
53
- return selected
54
- self.fail_on_invalid_options(invalid_options, selected)
60
+ class CsvListChoice(click.ParamType):
61
+ def convert( # type: ignore[return]
62
+ self, value: str, param: click.core.Parameter | None, ctx: click.core.Context | None
63
+ ) -> list[str]:
64
+ return [item for item in value.split(",") if item]
65
+
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
+ return [item for item in value.split(",") if item]
55
84
 
56
85
 
57
86
  class OptionalInt(click.types.IntRange):
@@ -64,4 +93,4 @@ class OptionalInt(click.types.IntRange):
64
93
  int(value)
65
94
  return super().convert(value, param, ctx)
66
95
  except ValueError:
67
- self.fail("%s is not a valid integer or None." % value, param, ctx)
96
+ self.fail(f"{value} is not a valid integer or None.", param, ctx)
@@ -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 formats, fill_missing_examples
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,9 +1,7 @@
1
1
  from __future__ import annotations
2
- from typing import TYPE_CHECKING
3
- from ...hooks import HookContext, register, unregister
4
2
 
5
- if TYPE_CHECKING:
6
- from ...models import Case
3
+ from schemathesis.generation.case import Case
4
+ from schemathesis.hooks import HookContext, register, unregister
7
5
 
8
6
 
9
7
  def install() -> None:
@@ -16,7 +14,7 @@ def uninstall() -> None:
16
14
 
17
15
  def before_add_examples(context: HookContext, examples: list[Case]) -> None:
18
16
  if not examples and context.operation is not None:
19
- from ..._hypothesis import add_single_example
17
+ from schemathesis.generation.hypothesis.examples import add_single_example
20
18
 
21
19
  strategy = context.operation.as_strategy()
22
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())