schemathesis 3.15.4__py3-none-any.whl → 4.4.2__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 (251) hide show
  1. schemathesis/__init__.py +53 -25
  2. schemathesis/auths.py +507 -0
  3. schemathesis/checks.py +190 -25
  4. schemathesis/cli/__init__.py +27 -1219
  5. schemathesis/cli/__main__.py +4 -0
  6. schemathesis/cli/commands/__init__.py +133 -0
  7. schemathesis/cli/commands/data.py +10 -0
  8. schemathesis/cli/commands/run/__init__.py +602 -0
  9. schemathesis/cli/commands/run/context.py +228 -0
  10. schemathesis/cli/commands/run/events.py +60 -0
  11. schemathesis/cli/commands/run/executor.py +157 -0
  12. schemathesis/cli/commands/run/filters.py +53 -0
  13. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  14. schemathesis/cli/commands/run/handlers/base.py +45 -0
  15. schemathesis/cli/commands/run/handlers/cassettes.py +464 -0
  16. schemathesis/cli/commands/run/handlers/junitxml.py +60 -0
  17. schemathesis/cli/commands/run/handlers/output.py +1750 -0
  18. schemathesis/cli/commands/run/loaders.py +118 -0
  19. schemathesis/cli/commands/run/validation.py +256 -0
  20. schemathesis/cli/constants.py +5 -0
  21. schemathesis/cli/core.py +19 -0
  22. schemathesis/cli/ext/fs.py +16 -0
  23. schemathesis/cli/ext/groups.py +203 -0
  24. schemathesis/cli/ext/options.py +81 -0
  25. schemathesis/config/__init__.py +202 -0
  26. schemathesis/config/_auth.py +51 -0
  27. schemathesis/config/_checks.py +268 -0
  28. schemathesis/config/_diff_base.py +101 -0
  29. schemathesis/config/_env.py +21 -0
  30. schemathesis/config/_error.py +163 -0
  31. schemathesis/config/_generation.py +157 -0
  32. schemathesis/config/_health_check.py +24 -0
  33. schemathesis/config/_operations.py +335 -0
  34. schemathesis/config/_output.py +171 -0
  35. schemathesis/config/_parameters.py +19 -0
  36. schemathesis/config/_phases.py +253 -0
  37. schemathesis/config/_projects.py +543 -0
  38. schemathesis/config/_rate_limit.py +17 -0
  39. schemathesis/config/_report.py +120 -0
  40. schemathesis/config/_validator.py +9 -0
  41. schemathesis/config/_warnings.py +89 -0
  42. schemathesis/config/schema.json +975 -0
  43. schemathesis/core/__init__.py +72 -0
  44. schemathesis/core/adapter.py +34 -0
  45. schemathesis/core/compat.py +32 -0
  46. schemathesis/core/control.py +2 -0
  47. schemathesis/core/curl.py +100 -0
  48. schemathesis/core/deserialization.py +210 -0
  49. schemathesis/core/errors.py +588 -0
  50. schemathesis/core/failures.py +316 -0
  51. schemathesis/core/fs.py +19 -0
  52. schemathesis/core/hooks.py +20 -0
  53. schemathesis/core/jsonschema/__init__.py +13 -0
  54. schemathesis/core/jsonschema/bundler.py +183 -0
  55. schemathesis/core/jsonschema/keywords.py +40 -0
  56. schemathesis/core/jsonschema/references.py +222 -0
  57. schemathesis/core/jsonschema/types.py +41 -0
  58. schemathesis/core/lazy_import.py +15 -0
  59. schemathesis/core/loaders.py +107 -0
  60. schemathesis/core/marks.py +66 -0
  61. schemathesis/core/media_types.py +79 -0
  62. schemathesis/core/output/__init__.py +46 -0
  63. schemathesis/core/output/sanitization.py +54 -0
  64. schemathesis/core/parameters.py +45 -0
  65. schemathesis/core/rate_limit.py +60 -0
  66. schemathesis/core/registries.py +34 -0
  67. schemathesis/core/result.py +27 -0
  68. schemathesis/core/schema_analysis.py +17 -0
  69. schemathesis/core/shell.py +203 -0
  70. schemathesis/core/transforms.py +144 -0
  71. schemathesis/core/transport.py +223 -0
  72. schemathesis/core/validation.py +73 -0
  73. schemathesis/core/version.py +7 -0
  74. schemathesis/engine/__init__.py +28 -0
  75. schemathesis/engine/context.py +152 -0
  76. schemathesis/engine/control.py +44 -0
  77. schemathesis/engine/core.py +201 -0
  78. schemathesis/engine/errors.py +446 -0
  79. schemathesis/engine/events.py +284 -0
  80. schemathesis/engine/observations.py +42 -0
  81. schemathesis/engine/phases/__init__.py +108 -0
  82. schemathesis/engine/phases/analysis.py +28 -0
  83. schemathesis/engine/phases/probes.py +172 -0
  84. schemathesis/engine/phases/stateful/__init__.py +68 -0
  85. schemathesis/engine/phases/stateful/_executor.py +364 -0
  86. schemathesis/engine/phases/stateful/context.py +85 -0
  87. schemathesis/engine/phases/unit/__init__.py +220 -0
  88. schemathesis/engine/phases/unit/_executor.py +459 -0
  89. schemathesis/engine/phases/unit/_pool.py +82 -0
  90. schemathesis/engine/recorder.py +254 -0
  91. schemathesis/errors.py +47 -0
  92. schemathesis/filters.py +395 -0
  93. schemathesis/generation/__init__.py +25 -0
  94. schemathesis/generation/case.py +478 -0
  95. schemathesis/generation/coverage.py +1528 -0
  96. schemathesis/generation/hypothesis/__init__.py +121 -0
  97. schemathesis/generation/hypothesis/builder.py +992 -0
  98. schemathesis/generation/hypothesis/examples.py +56 -0
  99. schemathesis/generation/hypothesis/given.py +66 -0
  100. schemathesis/generation/hypothesis/reporting.py +285 -0
  101. schemathesis/generation/meta.py +227 -0
  102. schemathesis/generation/metrics.py +93 -0
  103. schemathesis/generation/modes.py +20 -0
  104. schemathesis/generation/overrides.py +127 -0
  105. schemathesis/generation/stateful/__init__.py +37 -0
  106. schemathesis/generation/stateful/state_machine.py +294 -0
  107. schemathesis/graphql/__init__.py +15 -0
  108. schemathesis/graphql/checks.py +109 -0
  109. schemathesis/graphql/loaders.py +285 -0
  110. schemathesis/hooks.py +270 -91
  111. schemathesis/openapi/__init__.py +13 -0
  112. schemathesis/openapi/checks.py +467 -0
  113. schemathesis/openapi/generation/__init__.py +0 -0
  114. schemathesis/openapi/generation/filters.py +72 -0
  115. schemathesis/openapi/loaders.py +315 -0
  116. schemathesis/pytest/__init__.py +5 -0
  117. schemathesis/pytest/control_flow.py +7 -0
  118. schemathesis/pytest/lazy.py +341 -0
  119. schemathesis/pytest/loaders.py +36 -0
  120. schemathesis/pytest/plugin.py +357 -0
  121. schemathesis/python/__init__.py +0 -0
  122. schemathesis/python/asgi.py +12 -0
  123. schemathesis/python/wsgi.py +12 -0
  124. schemathesis/schemas.py +682 -257
  125. schemathesis/specs/graphql/__init__.py +0 -1
  126. schemathesis/specs/graphql/nodes.py +26 -2
  127. schemathesis/specs/graphql/scalars.py +77 -12
  128. schemathesis/specs/graphql/schemas.py +367 -148
  129. schemathesis/specs/graphql/validation.py +33 -0
  130. schemathesis/specs/openapi/__init__.py +9 -1
  131. schemathesis/specs/openapi/_hypothesis.py +555 -318
  132. schemathesis/specs/openapi/adapter/__init__.py +10 -0
  133. schemathesis/specs/openapi/adapter/parameters.py +729 -0
  134. schemathesis/specs/openapi/adapter/protocol.py +59 -0
  135. schemathesis/specs/openapi/adapter/references.py +19 -0
  136. schemathesis/specs/openapi/adapter/responses.py +368 -0
  137. schemathesis/specs/openapi/adapter/security.py +144 -0
  138. schemathesis/specs/openapi/adapter/v2.py +30 -0
  139. schemathesis/specs/openapi/adapter/v3_0.py +30 -0
  140. schemathesis/specs/openapi/adapter/v3_1.py +30 -0
  141. schemathesis/specs/openapi/analysis.py +96 -0
  142. schemathesis/specs/openapi/checks.py +748 -82
  143. schemathesis/specs/openapi/converter.py +176 -37
  144. schemathesis/specs/openapi/definitions.py +599 -4
  145. schemathesis/specs/openapi/examples.py +581 -165
  146. schemathesis/specs/openapi/expressions/__init__.py +52 -5
  147. schemathesis/specs/openapi/expressions/extractors.py +25 -0
  148. schemathesis/specs/openapi/expressions/lexer.py +34 -31
  149. schemathesis/specs/openapi/expressions/nodes.py +97 -46
  150. schemathesis/specs/openapi/expressions/parser.py +35 -13
  151. schemathesis/specs/openapi/formats.py +122 -0
  152. schemathesis/specs/openapi/media_types.py +75 -0
  153. schemathesis/specs/openapi/negative/__init__.py +93 -73
  154. schemathesis/specs/openapi/negative/mutations.py +294 -103
  155. schemathesis/specs/openapi/negative/utils.py +0 -9
  156. schemathesis/specs/openapi/patterns.py +458 -0
  157. schemathesis/specs/openapi/references.py +60 -81
  158. schemathesis/specs/openapi/schemas.py +647 -666
  159. schemathesis/specs/openapi/serialization.py +53 -30
  160. schemathesis/specs/openapi/stateful/__init__.py +403 -68
  161. schemathesis/specs/openapi/stateful/control.py +87 -0
  162. schemathesis/specs/openapi/stateful/dependencies/__init__.py +232 -0
  163. schemathesis/specs/openapi/stateful/dependencies/inputs.py +428 -0
  164. schemathesis/specs/openapi/stateful/dependencies/models.py +341 -0
  165. schemathesis/specs/openapi/stateful/dependencies/naming.py +491 -0
  166. schemathesis/specs/openapi/stateful/dependencies/outputs.py +34 -0
  167. schemathesis/specs/openapi/stateful/dependencies/resources.py +339 -0
  168. schemathesis/specs/openapi/stateful/dependencies/schemas.py +447 -0
  169. schemathesis/specs/openapi/stateful/inference.py +254 -0
  170. schemathesis/specs/openapi/stateful/links.py +219 -78
  171. schemathesis/specs/openapi/types/__init__.py +3 -0
  172. schemathesis/specs/openapi/types/common.py +23 -0
  173. schemathesis/specs/openapi/types/v2.py +129 -0
  174. schemathesis/specs/openapi/types/v3.py +134 -0
  175. schemathesis/specs/openapi/utils.py +7 -6
  176. schemathesis/specs/openapi/warnings.py +75 -0
  177. schemathesis/transport/__init__.py +224 -0
  178. schemathesis/transport/asgi.py +26 -0
  179. schemathesis/transport/prepare.py +126 -0
  180. schemathesis/transport/requests.py +278 -0
  181. schemathesis/transport/serialization.py +329 -0
  182. schemathesis/transport/wsgi.py +175 -0
  183. schemathesis-4.4.2.dist-info/METADATA +213 -0
  184. schemathesis-4.4.2.dist-info/RECORD +192 -0
  185. {schemathesis-3.15.4.dist-info → schemathesis-4.4.2.dist-info}/WHEEL +1 -1
  186. schemathesis-4.4.2.dist-info/entry_points.txt +6 -0
  187. {schemathesis-3.15.4.dist-info → schemathesis-4.4.2.dist-info/licenses}/LICENSE +1 -1
  188. schemathesis/_compat.py +0 -57
  189. schemathesis/_hypothesis.py +0 -123
  190. schemathesis/auth.py +0 -214
  191. schemathesis/cli/callbacks.py +0 -240
  192. schemathesis/cli/cassettes.py +0 -351
  193. schemathesis/cli/context.py +0 -38
  194. schemathesis/cli/debug.py +0 -21
  195. schemathesis/cli/handlers.py +0 -11
  196. schemathesis/cli/junitxml.py +0 -41
  197. schemathesis/cli/options.py +0 -70
  198. schemathesis/cli/output/__init__.py +0 -1
  199. schemathesis/cli/output/default.py +0 -521
  200. schemathesis/cli/output/short.py +0 -40
  201. schemathesis/constants.py +0 -88
  202. schemathesis/exceptions.py +0 -257
  203. schemathesis/extra/_aiohttp.py +0 -27
  204. schemathesis/extra/_flask.py +0 -10
  205. schemathesis/extra/_server.py +0 -16
  206. schemathesis/extra/pytest_plugin.py +0 -251
  207. schemathesis/failures.py +0 -145
  208. schemathesis/fixups/__init__.py +0 -29
  209. schemathesis/fixups/fast_api.py +0 -30
  210. schemathesis/graphql.py +0 -5
  211. schemathesis/internal.py +0 -6
  212. schemathesis/lazy.py +0 -301
  213. schemathesis/models.py +0 -1113
  214. schemathesis/parameters.py +0 -91
  215. schemathesis/runner/__init__.py +0 -470
  216. schemathesis/runner/events.py +0 -242
  217. schemathesis/runner/impl/__init__.py +0 -3
  218. schemathesis/runner/impl/core.py +0 -791
  219. schemathesis/runner/impl/solo.py +0 -85
  220. schemathesis/runner/impl/threadpool.py +0 -367
  221. schemathesis/runner/serialization.py +0 -206
  222. schemathesis/serializers.py +0 -253
  223. schemathesis/service/__init__.py +0 -18
  224. schemathesis/service/auth.py +0 -10
  225. schemathesis/service/client.py +0 -62
  226. schemathesis/service/constants.py +0 -25
  227. schemathesis/service/events.py +0 -39
  228. schemathesis/service/handler.py +0 -46
  229. schemathesis/service/hosts.py +0 -74
  230. schemathesis/service/metadata.py +0 -42
  231. schemathesis/service/models.py +0 -21
  232. schemathesis/service/serialization.py +0 -184
  233. schemathesis/service/worker.py +0 -39
  234. schemathesis/specs/graphql/loaders.py +0 -215
  235. schemathesis/specs/openapi/constants.py +0 -7
  236. schemathesis/specs/openapi/expressions/context.py +0 -12
  237. schemathesis/specs/openapi/expressions/pointers.py +0 -29
  238. schemathesis/specs/openapi/filters.py +0 -44
  239. schemathesis/specs/openapi/links.py +0 -303
  240. schemathesis/specs/openapi/loaders.py +0 -453
  241. schemathesis/specs/openapi/parameters.py +0 -430
  242. schemathesis/specs/openapi/security.py +0 -129
  243. schemathesis/specs/openapi/validation.py +0 -24
  244. schemathesis/stateful.py +0 -358
  245. schemathesis/targets.py +0 -32
  246. schemathesis/types.py +0 -38
  247. schemathesis/utils.py +0 -475
  248. schemathesis-3.15.4.dist-info/METADATA +0 -202
  249. schemathesis-3.15.4.dist-info/RECORD +0 -99
  250. schemathesis-3.15.4.dist-info/entry_points.txt +0 -7
  251. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
@@ -0,0 +1,253 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+ from typing import Any
6
+
7
+ from schemathesis.config._checks import ChecksConfig
8
+ from schemathesis.config._diff_base import DiffBase
9
+ from schemathesis.config._generation import GenerationConfig
10
+ from schemathesis.core import DEFAULT_STATEFUL_STEP_COUNT
11
+
12
+ DEFAULT_UNEXPECTED_METHODS = {"get", "put", "post", "delete", "options", "patch", "trace"}
13
+
14
+
15
+ @dataclass(repr=False)
16
+ class PhaseConfig(DiffBase):
17
+ enabled: bool
18
+ generation: GenerationConfig
19
+ checks: ChecksConfig
20
+
21
+ __slots__ = ("enabled", "generation", "checks", "_is_default")
22
+
23
+ def __init__(
24
+ self,
25
+ *,
26
+ enabled: bool = True,
27
+ generation: GenerationConfig | None = None,
28
+ checks: ChecksConfig | None = None,
29
+ ) -> None:
30
+ self.enabled = enabled
31
+ self.generation = generation or GenerationConfig()
32
+ self.checks = checks or ChecksConfig()
33
+ self._is_default = enabled and generation is None and checks is None
34
+
35
+ @classmethod
36
+ def from_dict(cls, data: dict[str, Any]) -> PhaseConfig:
37
+ return cls(
38
+ enabled=data.get("enabled", True),
39
+ generation=GenerationConfig.from_dict(data.get("generation", {})),
40
+ checks=ChecksConfig.from_dict(data.get("checks", {})),
41
+ )
42
+
43
+
44
+ @dataclass(repr=False)
45
+ class ExamplesPhaseConfig(DiffBase):
46
+ enabled: bool
47
+ fill_missing: bool
48
+ generation: GenerationConfig
49
+ checks: ChecksConfig
50
+
51
+ __slots__ = ("enabled", "fill_missing", "generation", "checks", "_is_default")
52
+
53
+ def __init__(
54
+ self,
55
+ *,
56
+ enabled: bool = True,
57
+ fill_missing: bool = False,
58
+ generation: GenerationConfig | None = None,
59
+ checks: ChecksConfig | None = None,
60
+ ) -> None:
61
+ self.enabled = enabled
62
+ self.fill_missing = fill_missing
63
+ self.generation = generation or GenerationConfig()
64
+ self.checks = checks or ChecksConfig()
65
+ self._is_default = enabled and not fill_missing and generation is None and checks is None
66
+
67
+ @classmethod
68
+ def from_dict(cls, data: dict[str, Any]) -> ExamplesPhaseConfig:
69
+ return cls(
70
+ enabled=data.get("enabled", True),
71
+ fill_missing=data.get("fill-missing", False),
72
+ generation=GenerationConfig.from_dict(data.get("generation", {})),
73
+ checks=ChecksConfig.from_dict(data.get("checks", {})),
74
+ )
75
+
76
+
77
+ @dataclass(repr=False)
78
+ class CoveragePhaseConfig(DiffBase):
79
+ enabled: bool
80
+ generate_duplicate_query_parameters: bool
81
+ generation: GenerationConfig
82
+ checks: ChecksConfig
83
+ unexpected_methods: set[str]
84
+
85
+ __slots__ = (
86
+ "enabled",
87
+ "generate_duplicate_query_parameters",
88
+ "generation",
89
+ "checks",
90
+ "unexpected_methods",
91
+ "_is_default",
92
+ )
93
+
94
+ def __init__(
95
+ self,
96
+ *,
97
+ enabled: bool = True,
98
+ generate_duplicate_query_parameters: bool = False,
99
+ generation: GenerationConfig | None = None,
100
+ checks: ChecksConfig | None = None,
101
+ unexpected_methods: set[str] | None = None,
102
+ ) -> None:
103
+ self.enabled = enabled
104
+ self.generate_duplicate_query_parameters = generate_duplicate_query_parameters
105
+ self.unexpected_methods = unexpected_methods if unexpected_methods is not None else DEFAULT_UNEXPECTED_METHODS
106
+ self.generation = generation or GenerationConfig()
107
+ self.checks = checks or ChecksConfig()
108
+ self._is_default = (
109
+ enabled
110
+ and not generate_duplicate_query_parameters
111
+ and generation is None
112
+ and checks is None
113
+ and unexpected_methods is None
114
+ )
115
+
116
+ @classmethod
117
+ def from_dict(cls, data: dict[str, Any]) -> CoveragePhaseConfig:
118
+ return cls(
119
+ enabled=data.get("enabled", True),
120
+ generate_duplicate_query_parameters=data.get("generate-duplicate-query-parameters", False),
121
+ unexpected_methods={method.lower() for method in data.get("unexpected-methods", [])}
122
+ if "unexpected-methods" in data
123
+ else None,
124
+ generation=GenerationConfig.from_dict(data.get("generation", {})),
125
+ checks=ChecksConfig.from_dict(data.get("checks", {})),
126
+ )
127
+
128
+
129
+ class InferenceAlgorithm(str, Enum):
130
+ LOCATION_HEADERS = "location-headers"
131
+ DEPENDENCY_ANALYSIS = "dependency-analysis"
132
+
133
+
134
+ @dataclass(repr=False)
135
+ class InferenceConfig(DiffBase):
136
+ algorithms: list[InferenceAlgorithm]
137
+
138
+ __slots__ = ("algorithms",)
139
+
140
+ def __init__(
141
+ self,
142
+ *,
143
+ algorithms: list[str] | None = None,
144
+ ) -> None:
145
+ self.algorithms = (
146
+ [InferenceAlgorithm(a) for a in algorithms] if algorithms is not None else list(InferenceAlgorithm)
147
+ )
148
+
149
+ @classmethod
150
+ def from_dict(cls, data: dict[str, Any]) -> InferenceConfig:
151
+ return cls(
152
+ algorithms=data.get("algorithms", list(InferenceAlgorithm)),
153
+ )
154
+
155
+ @property
156
+ def is_enabled(self) -> bool:
157
+ """Inference is enabled if any algorithms are configured."""
158
+ return bool(self.algorithms)
159
+
160
+ def is_algorithm_enabled(self, algorithm: InferenceAlgorithm) -> bool:
161
+ return algorithm in self.algorithms
162
+
163
+
164
+ @dataclass(repr=False)
165
+ class StatefulPhaseConfig(DiffBase):
166
+ enabled: bool
167
+ generation: GenerationConfig
168
+ checks: ChecksConfig
169
+ max_steps: int
170
+ inference: InferenceConfig
171
+
172
+ __slots__ = ("enabled", "generation", "checks", "max_steps", "inference", "_is_default")
173
+
174
+ def __init__(
175
+ self,
176
+ *,
177
+ enabled: bool = True,
178
+ generation: GenerationConfig | None = None,
179
+ checks: ChecksConfig | None = None,
180
+ max_steps: int | None = None,
181
+ inference: InferenceConfig | None = None,
182
+ ) -> None:
183
+ self.enabled = enabled
184
+ self.max_steps = max_steps or DEFAULT_STATEFUL_STEP_COUNT
185
+ self.generation = generation or GenerationConfig()
186
+ self.checks = checks or ChecksConfig()
187
+ self.inference = inference or InferenceConfig()
188
+ self._is_default = enabled and generation is None and checks is None and max_steps is None and inference is None
189
+
190
+ @classmethod
191
+ def from_dict(cls, data: dict[str, Any]) -> StatefulPhaseConfig:
192
+ return cls(
193
+ enabled=data.get("enabled", True),
194
+ max_steps=data.get("max-steps"),
195
+ generation=GenerationConfig.from_dict(data.get("generation", {})),
196
+ checks=ChecksConfig.from_dict(data.get("checks", {})),
197
+ inference=InferenceConfig.from_dict(data.get("inference", {})),
198
+ )
199
+
200
+
201
+ @dataclass(repr=False)
202
+ class PhasesConfig(DiffBase):
203
+ examples: ExamplesPhaseConfig
204
+ coverage: CoveragePhaseConfig
205
+ fuzzing: PhaseConfig
206
+ stateful: StatefulPhaseConfig
207
+
208
+ __slots__ = ("examples", "coverage", "fuzzing", "stateful")
209
+
210
+ def __init__(
211
+ self,
212
+ *,
213
+ examples: ExamplesPhaseConfig | None = None,
214
+ coverage: CoveragePhaseConfig | None = None,
215
+ fuzzing: PhaseConfig | None = None,
216
+ stateful: StatefulPhaseConfig | None = None,
217
+ ) -> None:
218
+ self.examples = examples or ExamplesPhaseConfig()
219
+ self.coverage = coverage or CoveragePhaseConfig()
220
+ self.fuzzing = fuzzing or PhaseConfig()
221
+ self.stateful = stateful or StatefulPhaseConfig()
222
+
223
+ def get_by_name(self, *, name: str) -> PhaseConfig | CoveragePhaseConfig | StatefulPhaseConfig:
224
+ return {
225
+ "examples": self.examples,
226
+ "coverage": self.coverage,
227
+ "fuzzing": self.fuzzing,
228
+ "stateful": self.stateful,
229
+ }[name] # type: ignore[return-value]
230
+
231
+ @classmethod
232
+ def from_dict(cls, data: dict[str, Any]) -> PhasesConfig:
233
+ # Use the outer "enabled" value as default for all phases.
234
+ default_enabled = data.get("enabled", None)
235
+
236
+ def merge(sub: dict[str, Any]) -> dict[str, Any]:
237
+ # Merge the default enabled flag with the sub-dict; the sub-dict takes precedence.
238
+ if default_enabled is not None:
239
+ return {"enabled": default_enabled, **sub}
240
+ return sub
241
+
242
+ return cls(
243
+ examples=ExamplesPhaseConfig.from_dict(merge(data.get("examples", {}))),
244
+ coverage=CoveragePhaseConfig.from_dict(merge(data.get("coverage", {}))),
245
+ fuzzing=PhaseConfig.from_dict(merge(data.get("fuzzing", {}))),
246
+ stateful=StatefulPhaseConfig.from_dict(merge(data.get("stateful", {}))),
247
+ )
248
+
249
+ def update(self, *, phases: list[str]) -> None:
250
+ self.examples.enabled = "examples" in phases
251
+ self.coverage.enabled = "coverage" in phases
252
+ self.fuzzing.enabled = "fuzzing" in phases
253
+ self.stateful.enabled = "stateful" in phases