schemathesis 4.0.0a9__py3-none-any.whl → 4.0.0a11__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 (93) hide show
  1. schemathesis/__init__.py +3 -7
  2. schemathesis/checks.py +17 -7
  3. schemathesis/cli/commands/__init__.py +51 -3
  4. schemathesis/cli/commands/data.py +10 -0
  5. schemathesis/cli/commands/run/__init__.py +147 -260
  6. schemathesis/cli/commands/run/context.py +2 -3
  7. schemathesis/cli/commands/run/events.py +4 -0
  8. schemathesis/cli/commands/run/executor.py +60 -73
  9. schemathesis/cli/commands/run/filters.py +15 -165
  10. schemathesis/cli/commands/run/handlers/cassettes.py +105 -104
  11. schemathesis/cli/commands/run/handlers/junitxml.py +6 -5
  12. schemathesis/cli/commands/run/handlers/output.py +26 -47
  13. schemathesis/cli/commands/run/loaders.py +35 -50
  14. schemathesis/cli/commands/run/validation.py +36 -161
  15. schemathesis/cli/core.py +5 -3
  16. schemathesis/cli/ext/fs.py +7 -5
  17. schemathesis/cli/ext/options.py +0 -21
  18. schemathesis/config/__init__.py +188 -0
  19. schemathesis/config/_auth.py +51 -0
  20. schemathesis/config/_checks.py +268 -0
  21. schemathesis/config/_diff_base.py +99 -0
  22. schemathesis/config/_env.py +21 -0
  23. schemathesis/config/_error.py +156 -0
  24. schemathesis/config/_generation.py +150 -0
  25. schemathesis/config/_health_check.py +24 -0
  26. schemathesis/config/_operations.py +313 -0
  27. schemathesis/config/_output.py +171 -0
  28. schemathesis/config/_parameters.py +19 -0
  29. schemathesis/config/_phases.py +151 -0
  30. schemathesis/config/_projects.py +495 -0
  31. schemathesis/config/_rate_limit.py +17 -0
  32. schemathesis/config/_report.py +116 -0
  33. schemathesis/config/_validator.py +9 -0
  34. schemathesis/config/schema.json +837 -0
  35. schemathesis/core/__init__.py +3 -0
  36. schemathesis/core/compat.py +16 -9
  37. schemathesis/core/errors.py +19 -2
  38. schemathesis/core/failures.py +6 -7
  39. schemathesis/core/hooks.py +20 -0
  40. schemathesis/core/output/__init__.py +14 -37
  41. schemathesis/core/output/sanitization.py +3 -146
  42. schemathesis/core/validation.py +16 -0
  43. schemathesis/engine/__init__.py +2 -4
  44. schemathesis/engine/context.py +41 -43
  45. schemathesis/engine/core.py +7 -5
  46. schemathesis/engine/phases/__init__.py +10 -0
  47. schemathesis/engine/phases/probes.py +8 -8
  48. schemathesis/engine/phases/stateful/_executor.py +68 -43
  49. schemathesis/engine/phases/unit/__init__.py +23 -15
  50. schemathesis/engine/phases/unit/_executor.py +77 -17
  51. schemathesis/engine/phases/unit/_pool.py +1 -1
  52. schemathesis/errors.py +2 -0
  53. schemathesis/filters.py +2 -3
  54. schemathesis/generation/__init__.py +6 -31
  55. schemathesis/generation/case.py +5 -3
  56. schemathesis/generation/coverage.py +174 -134
  57. schemathesis/generation/hypothesis/__init__.py +7 -1
  58. schemathesis/generation/hypothesis/builder.py +40 -14
  59. schemathesis/generation/meta.py +3 -3
  60. schemathesis/generation/overrides.py +37 -1
  61. schemathesis/generation/stateful/state_machine.py +8 -1
  62. schemathesis/graphql/loaders.py +21 -12
  63. schemathesis/openapi/checks.py +12 -8
  64. schemathesis/openapi/generation/filters.py +10 -8
  65. schemathesis/openapi/loaders.py +22 -13
  66. schemathesis/pytest/lazy.py +2 -5
  67. schemathesis/pytest/plugin.py +11 -2
  68. schemathesis/schemas.py +13 -61
  69. schemathesis/specs/graphql/schemas.py +11 -15
  70. schemathesis/specs/openapi/_hypothesis.py +12 -8
  71. schemathesis/specs/openapi/checks.py +16 -18
  72. schemathesis/specs/openapi/examples.py +4 -3
  73. schemathesis/specs/openapi/formats.py +2 -2
  74. schemathesis/specs/openapi/negative/__init__.py +2 -2
  75. schemathesis/specs/openapi/patterns.py +46 -16
  76. schemathesis/specs/openapi/references.py +2 -3
  77. schemathesis/specs/openapi/schemas.py +11 -20
  78. schemathesis/specs/openapi/stateful/__init__.py +10 -5
  79. schemathesis/transport/prepare.py +7 -6
  80. schemathesis/transport/requests.py +3 -1
  81. schemathesis/transport/wsgi.py +3 -4
  82. {schemathesis-4.0.0a9.dist-info → schemathesis-4.0.0a11.dist-info}/METADATA +7 -8
  83. schemathesis-4.0.0a11.dist-info/RECORD +166 -0
  84. schemathesis/cli/commands/run/checks.py +0 -79
  85. schemathesis/cli/commands/run/hypothesis.py +0 -78
  86. schemathesis/cli/commands/run/reports.py +0 -72
  87. schemathesis/cli/hooks.py +0 -36
  88. schemathesis/engine/config.py +0 -59
  89. schemathesis/experimental/__init__.py +0 -72
  90. schemathesis-4.0.0a9.dist-info/RECORD +0 -153
  91. {schemathesis-4.0.0a9.dist-info → schemathesis-4.0.0a11.dist-info}/WHEEL +0 -0
  92. {schemathesis-4.0.0a9.dist-info → schemathesis-4.0.0a11.dist-info}/entry_points.txt +0 -0
  93. {schemathesis-4.0.0a9.dist-info → schemathesis-4.0.0a11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,171 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from schemathesis.config._diff_base import DiffBase
7
+
8
+ # Exact keys to sanitize
9
+ DEFAULT_KEYS_TO_SANITIZE = [
10
+ "phpsessid",
11
+ "xsrf-token",
12
+ "_csrf",
13
+ "_csrf_token",
14
+ "_session",
15
+ "_xsrf",
16
+ "aiohttp_session",
17
+ "api_key",
18
+ "api-key",
19
+ "apikey",
20
+ "auth",
21
+ "authorization",
22
+ "connect.sid",
23
+ "cookie",
24
+ "credentials",
25
+ "csrf",
26
+ "csrf_token",
27
+ "csrf-token",
28
+ "csrftoken",
29
+ "ip_address",
30
+ "mysql_pwd",
31
+ "passwd",
32
+ "password",
33
+ "private_key",
34
+ "private-key",
35
+ "privatekey",
36
+ "remote_addr",
37
+ "remote-addr",
38
+ "secret",
39
+ "session",
40
+ "sessionid",
41
+ "set_cookie",
42
+ "set-cookie",
43
+ "token",
44
+ "x_api_key",
45
+ "x-api-key",
46
+ "x_csrftoken",
47
+ "x-csrftoken",
48
+ "x_forwarded_for",
49
+ "x-forwarded-for",
50
+ "x_real_ip",
51
+ "x-real-ip",
52
+ ]
53
+
54
+ # Markers indicating potentially sensitive keys
55
+ DEFAULT_SENSITIVE_MARKERS = [
56
+ "token",
57
+ "key",
58
+ "secret",
59
+ "password",
60
+ "auth",
61
+ "session",
62
+ "passwd",
63
+ "credential",
64
+ ]
65
+
66
+ DEFAULT_REPLACEMENT = "[Filtered]"
67
+
68
+
69
+ @dataclass(repr=False)
70
+ class SanitizationConfig(DiffBase):
71
+ """Configuration for sanitizing sensitive data."""
72
+
73
+ enabled: bool
74
+ keys_to_sanitize: list[str]
75
+ sensitive_markers: list[str]
76
+ replacement: str
77
+
78
+ __slots__ = ("enabled", "keys_to_sanitize", "sensitive_markers", "replacement")
79
+
80
+ def __init__(
81
+ self,
82
+ *,
83
+ enabled: bool = True,
84
+ keys_to_sanitize: list[str] | None = None,
85
+ sensitive_markers: list[str] | None = None,
86
+ replacement: str | None = None,
87
+ ) -> None:
88
+ self.enabled = enabled
89
+ self.keys_to_sanitize = keys_to_sanitize or DEFAULT_KEYS_TO_SANITIZE
90
+ self.sensitive_markers = sensitive_markers or DEFAULT_SENSITIVE_MARKERS
91
+ self.replacement = replacement or DEFAULT_REPLACEMENT
92
+
93
+ @classmethod
94
+ def from_dict(cls, data: dict[str, Any]) -> SanitizationConfig:
95
+ return cls(
96
+ enabled=data.get("enabled", True),
97
+ keys_to_sanitize=[k.lower() for k in data.get("keys-to-sanitize", [])] or DEFAULT_KEYS_TO_SANITIZE,
98
+ sensitive_markers=[m.lower() for m in data.get("sensitive-markers", [])] or DEFAULT_SENSITIVE_MARKERS,
99
+ replacement=data.get("replacement", DEFAULT_REPLACEMENT),
100
+ )
101
+
102
+ def update(self, *, enabled: bool | None = None) -> None:
103
+ if enabled is not None:
104
+ self.enabled = enabled
105
+
106
+
107
+ MAX_PAYLOAD_SIZE = 512
108
+ MAX_LINES = 10
109
+ MAX_WIDTH = 80
110
+
111
+
112
+ @dataclass(repr=False)
113
+ class TruncationConfig(DiffBase):
114
+ """Configuration for truncating large output."""
115
+
116
+ enabled: bool
117
+ max_payload_size: int
118
+ max_lines: int
119
+ max_width: int
120
+
121
+ __slots__ = ("enabled", "max_payload_size", "max_lines", "max_width")
122
+
123
+ def __init__(
124
+ self,
125
+ *,
126
+ enabled: bool = True,
127
+ max_payload_size: int = MAX_PAYLOAD_SIZE,
128
+ max_lines: int = MAX_LINES,
129
+ max_width: int = MAX_WIDTH,
130
+ ) -> None:
131
+ self.enabled = enabled
132
+ self.max_payload_size = max_payload_size
133
+ self.max_lines = max_lines
134
+ self.max_width = max_width
135
+
136
+ @classmethod
137
+ def from_dict(cls, data: dict[str, Any]) -> TruncationConfig:
138
+ return cls(
139
+ enabled=data.get("enabled", True),
140
+ max_payload_size=data.get("max-payload-size", MAX_PAYLOAD_SIZE),
141
+ max_lines=data.get("max-lines", MAX_LINES),
142
+ max_width=data.get("max-width", MAX_WIDTH),
143
+ )
144
+
145
+ def update(self, *, enabled: bool | None = None) -> None:
146
+ if enabled is not None:
147
+ self.enabled = enabled
148
+
149
+
150
+ @dataclass(repr=False)
151
+ class OutputConfig(DiffBase):
152
+ sanitization: SanitizationConfig
153
+ truncation: TruncationConfig
154
+
155
+ __slots__ = ("sanitization", "truncation")
156
+
157
+ def __init__(
158
+ self,
159
+ *,
160
+ sanitization: SanitizationConfig | None = None,
161
+ truncation: TruncationConfig | None = None,
162
+ ) -> None:
163
+ self.sanitization = sanitization or SanitizationConfig()
164
+ self.truncation = truncation or TruncationConfig()
165
+
166
+ @classmethod
167
+ def from_dict(cls, data: dict[str, Any]) -> OutputConfig:
168
+ return cls(
169
+ sanitization=SanitizationConfig.from_dict(data.get("sanitization", {})),
170
+ truncation=TruncationConfig.from_dict(data.get("truncation", {})),
171
+ )
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Literal
4
+
5
+ from schemathesis.config._env import resolve
6
+
7
+ # Define valid parameter locations from OpenAPI
8
+ ParameterLocation = Literal["path", "query", "header", "cookie", "body"]
9
+ VALID_LOCATIONS: list[ParameterLocation] = ["path", "query", "header", "cookie", "body"]
10
+
11
+
12
+ def load_parameters(data: dict[str, Any]) -> dict[str, Any]:
13
+ parameters = {}
14
+ for key, value in data.get("parameters", {}).items():
15
+ if isinstance(value, str):
16
+ parameters[key] = resolve(value)
17
+ else:
18
+ parameters[key] = value
19
+ return parameters
@@ -0,0 +1,151 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from schemathesis.config._checks import ChecksConfig
7
+ from schemathesis.config._diff_base import DiffBase
8
+ from schemathesis.config._generation import GenerationConfig
9
+ from schemathesis.core import DEFAULT_STATEFUL_STEP_COUNT
10
+
11
+ DEFAULT_UNEXPECTED_METHODS = {"get", "put", "post", "delete", "options", "patch", "trace"}
12
+
13
+
14
+ @dataclass(repr=False)
15
+ class PhaseConfig(DiffBase):
16
+ enabled: bool
17
+ generation: GenerationConfig
18
+ checks: ChecksConfig
19
+
20
+ __slots__ = ("enabled", "generation", "checks")
21
+
22
+ def __init__(
23
+ self,
24
+ *,
25
+ enabled: bool = True,
26
+ generation: GenerationConfig | None = None,
27
+ checks: ChecksConfig | None = None,
28
+ ) -> None:
29
+ self.enabled = enabled
30
+ self.generation = generation or GenerationConfig()
31
+ self.checks = checks or ChecksConfig()
32
+
33
+ @classmethod
34
+ def from_dict(cls, data: dict[str, Any]) -> PhaseConfig:
35
+ return cls(
36
+ enabled=data.get("enabled", True),
37
+ generation=GenerationConfig.from_dict(data.get("generation", {})),
38
+ checks=ChecksConfig.from_dict(data.get("checks", {})),
39
+ )
40
+
41
+
42
+ @dataclass(repr=False)
43
+ class CoveragePhaseConfig(DiffBase):
44
+ enabled: bool
45
+ generation: GenerationConfig
46
+ checks: ChecksConfig
47
+ unexpected_methods: set[str]
48
+
49
+ __slots__ = ("enabled", "generation", "checks", "unexpected_methods")
50
+
51
+ def __init__(
52
+ self,
53
+ *,
54
+ enabled: bool = True,
55
+ generation: GenerationConfig | None = None,
56
+ checks: ChecksConfig | None = None,
57
+ unexpected_methods: set[str] | None = None,
58
+ ) -> None:
59
+ self.enabled = enabled
60
+ self.unexpected_methods = unexpected_methods or DEFAULT_UNEXPECTED_METHODS
61
+ self.generation = generation or GenerationConfig()
62
+ self.checks = checks or ChecksConfig()
63
+
64
+ @classmethod
65
+ def from_dict(cls, data: dict[str, Any]) -> CoveragePhaseConfig:
66
+ return cls(
67
+ enabled=data.get("enabled", True),
68
+ unexpected_methods={method.lower() for method in data.get("unexpected-methods", [])}
69
+ if "unexpected-methods" in data
70
+ else None,
71
+ generation=GenerationConfig.from_dict(data.get("generation", {})),
72
+ checks=ChecksConfig.from_dict(data.get("checks", {})),
73
+ )
74
+
75
+
76
+ @dataclass(repr=False)
77
+ class StatefulPhaseConfig(DiffBase):
78
+ enabled: bool
79
+ generation: GenerationConfig
80
+ checks: ChecksConfig
81
+ max_steps: int
82
+
83
+ __slots__ = ("enabled", "generation", "checks", "max_steps")
84
+
85
+ def __init__(
86
+ self,
87
+ *,
88
+ enabled: bool = True,
89
+ generation: GenerationConfig | None = None,
90
+ checks: ChecksConfig | None = None,
91
+ max_steps: int | None = None,
92
+ ) -> None:
93
+ self.enabled = enabled
94
+ self.max_steps = max_steps or DEFAULT_STATEFUL_STEP_COUNT
95
+ self.generation = generation or GenerationConfig()
96
+ self.checks = checks or ChecksConfig()
97
+
98
+ @classmethod
99
+ def from_dict(cls, data: dict[str, Any]) -> StatefulPhaseConfig:
100
+ return cls(
101
+ enabled=data.get("enabled", True),
102
+ max_steps=data.get("max-steps"),
103
+ generation=GenerationConfig.from_dict(data.get("generation", {})),
104
+ checks=ChecksConfig.from_dict(data.get("checks", {})),
105
+ )
106
+
107
+
108
+ @dataclass(repr=False)
109
+ class PhasesConfig(DiffBase):
110
+ examples: PhaseConfig
111
+ coverage: CoveragePhaseConfig
112
+ fuzzing: PhaseConfig
113
+ stateful: StatefulPhaseConfig
114
+
115
+ __slots__ = ("examples", "coverage", "fuzzing", "stateful")
116
+
117
+ def __init__(
118
+ self,
119
+ *,
120
+ examples: PhaseConfig | None = None,
121
+ coverage: CoveragePhaseConfig | None = None,
122
+ fuzzing: PhaseConfig | None = None,
123
+ stateful: StatefulPhaseConfig | None = None,
124
+ ) -> None:
125
+ self.examples = examples or PhaseConfig()
126
+ self.coverage = coverage or CoveragePhaseConfig()
127
+ self.fuzzing = fuzzing or PhaseConfig()
128
+ self.stateful = stateful or StatefulPhaseConfig()
129
+
130
+ def get_by_name(self, *, name: str) -> PhaseConfig | CoveragePhaseConfig | StatefulPhaseConfig:
131
+ return {
132
+ "examples": self.examples,
133
+ "coverage": self.coverage,
134
+ "fuzzing": self.fuzzing,
135
+ "stateful": self.stateful,
136
+ }[name] # type: ignore[return-value]
137
+
138
+ @classmethod
139
+ def from_dict(cls, data: dict[str, Any]) -> PhasesConfig:
140
+ return cls(
141
+ examples=PhaseConfig.from_dict(data.get("examples", {})),
142
+ coverage=CoveragePhaseConfig.from_dict(data.get("coverage", {})),
143
+ fuzzing=PhaseConfig.from_dict(data.get("fuzzing", {})),
144
+ stateful=StatefulPhaseConfig.from_dict(data.get("stateful", {})),
145
+ )
146
+
147
+ def update(self, *, phases: list[str]) -> None:
148
+ self.examples.enabled = "examples" in phases
149
+ self.coverage.enabled = "coverage" in phases
150
+ self.fuzzing.enabled = "fuzzing" in phases
151
+ self.stateful.enabled = "stateful" in phases