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
@@ -3,29 +3,35 @@ from __future__ import annotations
3
3
  import time
4
4
  from contextlib import suppress
5
5
  from dataclasses import dataclass
6
- from typing import Any, Callable, Dict, Iterable, Optional
6
+ from typing import Any, Callable, Dict, Iterable, Optional, Union, cast
7
7
  from urllib.parse import quote_plus
8
8
  from weakref import WeakKeyDictionary
9
9
 
10
- from hypothesis import reject
10
+ from hypothesis import event, note, reject
11
11
  from hypothesis import strategies as st
12
12
  from hypothesis_jsonschema import from_schema
13
- from requests.structures import CaseInsensitiveDict
14
- from requests.utils import to_key_val_list
15
-
16
- from ... import auths, serializers
17
- from ..._hypothesis import prepare_urlencoded
18
- from ...constants import NOT_SET
19
- from ...exceptions import BodyInGetRequestError, SerializationNotPossible
20
- from ...generation import DataGenerationMethod, GenerationConfig
13
+
14
+ from schemathesis.core import NOT_SET, NotSet, media_types
15
+ from schemathesis.core.control import SkipTest
16
+ from schemathesis.core.errors import SERIALIZERS_SUGGESTION_MESSAGE, SerializationNotPossible
17
+ from schemathesis.core.transforms import deepclone
18
+ from schemathesis.core.transport import prepare_urlencoded
19
+ from schemathesis.generation.meta import (
20
+ CaseMetadata,
21
+ ComponentInfo,
22
+ ComponentKind,
23
+ ExplicitPhaseData,
24
+ GeneratePhaseData,
25
+ GenerationInfo,
26
+ PhaseInfo,
27
+ TestPhase,
28
+ )
29
+ from schemathesis.openapi.generation.filters import is_valid_header, is_valid_path, is_valid_query, is_valid_urlencoded
30
+ from schemathesis.schemas import APIOperation
31
+
32
+ from ... import auths
33
+ from ...generation import GenerationConfig, GenerationMode
21
34
  from ...hooks import HookContext, HookDispatcher, apply_to_all_dispatchers
22
- from ...internal.copy import fast_deepcopy
23
- from ...internal.validation import is_illegal_surrogate
24
- from ...models import APIOperation, Case, GenerationMetadata, TestPhase, cant_serialize
25
- from ...transports.content_types import parse_content_type
26
- from ...transports.headers import has_invalid_characters, is_latin_1_encodable
27
- from ...types import NotSet
28
- from ...utils import skip
29
35
  from .constants import LOCATION_TO_CONTAINER
30
36
  from .formats import HEADER_FORMAT, STRING_FORMATS, get_default_format_strategies, header_values
31
37
  from .media_types import MEDIA_TYPES
@@ -38,53 +44,21 @@ SLASH = "/"
38
44
  StrategyFactory = Callable[[Dict[str, Any], str, str, Optional[str], GenerationConfig], st.SearchStrategy]
39
45
 
40
46
 
41
- def is_valid_header(headers: dict[str, Any]) -> bool:
42
- """Verify if the generated headers are valid."""
43
- for name, value in headers.items():
44
- if not is_latin_1_encodable(value):
45
- return False
46
- if has_invalid_characters(name, value):
47
- return False
48
- return True
49
-
50
-
51
- def is_valid_query(query: dict[str, Any]) -> bool:
52
- """Surrogates are not allowed in a query string.
53
-
54
- `requests` and `werkzeug` will fail to send it to the application.
55
- """
56
- for name, value in query.items():
57
- if is_illegal_surrogate(name) or is_illegal_surrogate(value):
58
- return False
59
- return True
60
-
61
-
62
- def is_valid_urlencoded(data: Any) -> bool:
63
- if data is NOT_SET:
64
- return True
65
- try:
66
- for _, __ in to_key_val_list(data): # type: ignore[no-untyped-call]
67
- pass
68
- return True
69
- except (TypeError, ValueError):
70
- return False
71
-
72
-
73
47
  @st.composite # type: ignore
74
- def get_case_strategy(
48
+ def openapi_cases(
75
49
  draw: Callable,
50
+ *,
76
51
  operation: APIOperation,
77
52
  hooks: HookDispatcher | None = None,
78
53
  auth_storage: auths.AuthStorage | None = None,
79
- generator: DataGenerationMethod = DataGenerationMethod.default(),
80
- generation_config: GenerationConfig | None = None,
54
+ generation_mode: GenerationMode = GenerationMode.default(),
55
+ generation_config: GenerationConfig,
81
56
  path_parameters: NotSet | dict[str, Any] = NOT_SET,
82
57
  headers: NotSet | dict[str, Any] = NOT_SET,
83
58
  cookies: NotSet | dict[str, Any] = NOT_SET,
84
59
  query: NotSet | dict[str, Any] = NOT_SET,
85
60
  body: Any = NOT_SET,
86
61
  media_type: str | None = None,
87
- skip_on_not_negated: bool = True,
88
62
  phase: TestPhase = TestPhase.GENERATE,
89
63
  ) -> Any:
90
64
  """A strategy that creates `Case` instances.
@@ -100,46 +74,59 @@ def get_case_strategy(
100
74
  as it works with `body`.
101
75
  """
102
76
  start = time.monotonic()
103
- strategy_factory = DATA_GENERATION_METHOD_TO_STRATEGY_FACTORY[generator]
77
+ strategy_factory = GENERATOR_MODE_TO_STRATEGY_FACTORY[generation_mode]
104
78
 
105
79
  context = HookContext(operation)
106
80
 
107
- generation_config = generation_config or operation.schema.generation_config
108
-
109
81
  path_parameters_ = generate_parameter(
110
- "path", path_parameters, operation, draw, context, hooks, generator, generation_config
82
+ "path", path_parameters, operation, draw, context, hooks, generation_mode, generation_config
111
83
  )
112
- headers_ = generate_parameter("header", headers, operation, draw, context, hooks, generator, generation_config)
113
- cookies_ = generate_parameter("cookie", cookies, operation, draw, context, hooks, generator, generation_config)
114
- query_ = generate_parameter("query", query, operation, draw, context, hooks, generator, generation_config)
84
+ headers_ = generate_parameter(
85
+ "header", headers, operation, draw, context, hooks, generation_mode, generation_config
86
+ )
87
+ cookies_ = generate_parameter(
88
+ "cookie", cookies, operation, draw, context, hooks, generation_mode, generation_config
89
+ )
90
+ query_ = generate_parameter("query", query, operation, draw, context, hooks, generation_mode, generation_config)
115
91
 
116
92
  if body is NOT_SET:
117
93
  if operation.body:
118
- body_generator = generator
119
- if generator.is_negative:
94
+ body_generator = generation_mode
95
+ if generation_mode.is_negative:
120
96
  # Consider only schemas that are possible to negate
121
97
  candidates = [item for item in operation.body.items if can_negate(item.as_json_schema(operation))]
122
98
  # Not possible to negate body, fallback to positive data generation
123
99
  if not candidates:
124
100
  candidates = operation.body.items
125
101
  strategy_factory = make_positive_strategy
126
- body_generator = DataGenerationMethod.positive
102
+ body_generator = GenerationMode.POSITIVE
127
103
  else:
128
104
  candidates = operation.body.items
129
105
  parameter = draw(st.sampled_from(candidates))
130
106
  strategy = _get_body_strategy(parameter, strategy_factory, operation, generation_config)
131
107
  strategy = apply_hooks(operation, context, hooks, strategy, "body")
132
108
  # Parameter may have a wildcard media type. In this case, choose any supported one
133
- possible_media_types = sorted(serializers.get_matching_media_types(parameter.media_type))
109
+ possible_media_types = sorted(
110
+ operation.schema.transport.get_matching_media_types(parameter.media_type), key=lambda x: x[0]
111
+ )
134
112
  if not possible_media_types:
135
113
  all_media_types = operation.get_request_payload_content_types()
136
- if all(serializers.get_first_matching_media_type(media_type) is None for media_type in all_media_types):
114
+ if all(
115
+ operation.schema.transport.get_first_matching_media_type(media_type) is None
116
+ for media_type in all_media_types
117
+ ):
137
118
  # None of media types defined for this operation are not supported
138
119
  raise SerializationNotPossible.from_media_types(*all_media_types)
139
120
  # Other media types are possible - avoid choosing this media type in the future
140
- cant_serialize(parameter.media_type)
141
- media_type = draw(st.sampled_from(possible_media_types))
142
- if media_type is not None and parse_content_type(media_type) == ("application", "x-www-form-urlencoded"):
121
+ event_text = f"Can't serialize data to `{parameter.media_type}`."
122
+ note(f"{event_text} {SERIALIZERS_SUGGESTION_MESSAGE}")
123
+ event(event_text)
124
+ reject() # type: ignore
125
+ media_type, _ = draw(st.sampled_from(possible_media_types))
126
+ if media_type is not None and media_types.parse(media_type) == (
127
+ "application",
128
+ "x-www-form-urlencoded",
129
+ ):
143
130
  strategy = strategy.map(prepare_urlencoded).filter(is_valid_urlencoded)
144
131
  body_ = ValueContainer(value=draw(strategy), location="body", generator=body_generator)
145
132
  else:
@@ -152,35 +139,43 @@ def get_case_strategy(
152
139
  raise SerializationNotPossible.from_media_types(*all_media_types)
153
140
  body_ = ValueContainer(value=body, location="body", generator=None)
154
141
 
155
- if operation.schema.validate_schema and operation.method.upper() == "GET" and operation.body:
156
- raise BodyInGetRequestError("GET requests should not contain body parameters.")
157
142
  # If we need to generate negative cases but no generated values were negated, then skip the whole test
158
- if generator.is_negative and not any_negated_values([query_, cookies_, headers_, path_parameters_, body_]):
159
- if skip_on_not_negated:
160
- skip(operation.verbose_name)
143
+ if generation_mode.is_negative and not any_negated_values([query_, cookies_, headers_, path_parameters_, body_]):
144
+ if generation_config.modes == [GenerationMode.NEGATIVE]:
145
+ raise SkipTest("Impossible to generate negative test cases")
161
146
  else:
162
147
  reject()
163
- instance = Case(
164
- operation=operation,
165
- generation_time=time.monotonic() - start,
148
+
149
+ _phase_data = {
150
+ TestPhase.EXPLICIT: ExplicitPhaseData(),
151
+ TestPhase.GENERATE: GeneratePhaseData(),
152
+ }[phase]
153
+ phase_data = cast(Union[ExplicitPhaseData, GeneratePhaseData], _phase_data)
154
+
155
+ instance = operation.Case(
166
156
  media_type=media_type,
167
157
  path_parameters=path_parameters_.value,
168
- headers=CaseInsensitiveDict(headers_.value) if headers_.value is not None else headers_.value,
158
+ headers=headers_.value,
169
159
  cookies=cookies_.value,
170
160
  query=query_.value,
171
161
  body=body_.value,
172
- data_generation_method=generator,
173
- meta=GenerationMetadata(
174
- query=query_.generator,
175
- path_parameters=path_parameters_.generator,
176
- headers=headers_.generator,
177
- cookies=cookies_.generator,
178
- body=body_.generator,
179
- phase=phase,
180
- description=None,
181
- location=None,
182
- parameter=None,
183
- parameter_location=None,
162
+ meta=CaseMetadata(
163
+ generation=GenerationInfo(
164
+ time=time.monotonic() - start,
165
+ mode=generation_mode,
166
+ ),
167
+ phase=PhaseInfo(name=phase, data=phase_data),
168
+ components={
169
+ kind: ComponentInfo(mode=value.generator)
170
+ for kind, value in [
171
+ (ComponentKind.QUERY, query_),
172
+ (ComponentKind.PATH_PARAMETERS, path_parameters_),
173
+ (ComponentKind.HEADERS, headers_),
174
+ (ComponentKind.COOKIES, cookies_),
175
+ (ComponentKind.BODY, body_),
176
+ ]
177
+ if value.generator is not None
178
+ },
184
179
  ),
185
180
  )
186
181
  auth_context = auths.AuthContext(
@@ -208,7 +203,7 @@ def _get_body_strategy(
208
203
  return _BODY_STRATEGIES_CACHE[parameter][strategy_factory]
209
204
  schema = parameter.as_json_schema(operation)
210
205
  schema = operation.schema.prepare_schema(schema)
211
- strategy = strategy_factory(schema, operation.verbose_name, "body", parameter.media_type, generation_config)
206
+ strategy = strategy_factory(schema, operation.label, "body", parameter.media_type, generation_config)
212
207
  if not parameter.is_required:
213
208
  strategy |= st.just(NOT_SET)
214
209
  _BODY_STRATEGIES_CACHE.setdefault(parameter, {})[strategy_factory] = strategy
@@ -238,7 +233,7 @@ def get_parameters_value(
238
233
  strategy = apply_hooks(operation, context, hooks, strategy, location)
239
234
  new = draw(strategy)
240
235
  if new is not None:
241
- copied = fast_deepcopy(value)
236
+ copied = deepclone(value)
242
237
  copied.update(new)
243
238
  return copied
244
239
  return value
@@ -253,7 +248,7 @@ class ValueContainer:
253
248
 
254
249
  value: Any
255
250
  location: str
256
- generator: DataGenerationMethod | None
251
+ generator: GenerationMode | None
257
252
 
258
253
  __slots__ = ("value", "location", "generator")
259
254
 
@@ -265,7 +260,7 @@ class ValueContainer:
265
260
 
266
261
  def any_negated_values(values: list[ValueContainer]) -> bool:
267
262
  """Check if any generated values are negated."""
268
- return any(value.generator == DataGenerationMethod.negative for value in values if value.is_generated)
263
+ return any(value.generator == GenerationMode.NEGATIVE for value in values if value.is_generated)
269
264
 
270
265
 
271
266
  def generate_parameter(
@@ -275,7 +270,7 @@ def generate_parameter(
275
270
  draw: Callable,
276
271
  context: HookContext,
277
272
  hooks: HookDispatcher | None,
278
- generator: DataGenerationMethod,
273
+ generator: GenerationMode,
279
274
  generation_config: GenerationConfig,
280
275
  ) -> ValueContainer:
281
276
  """Generate a value for a parameter.
@@ -289,13 +284,13 @@ def generate_parameter(
289
284
  # If we can't negate any parameter, generate positive ones
290
285
  # If nothing else will be negated, then skip the test completely
291
286
  strategy_factory = make_positive_strategy
292
- generator = DataGenerationMethod.positive
287
+ generator = GenerationMode.POSITIVE
293
288
  else:
294
- strategy_factory = DATA_GENERATION_METHOD_TO_STRATEGY_FACTORY[generator]
289
+ strategy_factory = GENERATOR_MODE_TO_STRATEGY_FACTORY[generator]
295
290
  value = get_parameters_value(
296
291
  explicit, location, draw, operation, context, hooks, strategy_factory, generation_config
297
292
  )
298
- used_generator: DataGenerationMethod | None = generator
293
+ used_generator: GenerationMode | None = generator
299
294
  if value == explicit:
300
295
  # When we pass `explicit`, then its parts are excluded from generation of the final value
301
296
  # If the final value is the same, then other parameters were generated at all
@@ -329,11 +324,7 @@ def get_schema_for_location(
329
324
  ) -> dict[str, Any]:
330
325
  schema = parameters_to_json_schema(operation, parameters)
331
326
  if location == "path":
332
- if not operation.schema.validate_schema:
333
- # If schema validation is disabled, we try to generate data even if the parameter definition
334
- # contains errors.
335
- # In this case, we know that the `required` keyword should always be `True`.
336
- schema["required"] = list(schema["properties"])
327
+ schema["required"] = list(schema["properties"])
337
328
  for prop in schema.get("properties", {}).values():
338
329
  if prop.get("type") == "string":
339
330
  prop.setdefault("minLength", 1)
@@ -365,7 +356,7 @@ def get_parameters_strategy(
365
356
  # Nothing to negate - all properties were excluded
366
357
  strategy = st.none()
367
358
  else:
368
- strategy = strategy_factory(schema, operation.verbose_name, location, None, generation_config)
359
+ strategy = strategy_factory(schema, operation.label, location, None, generation_config)
369
360
  serialize = operation.get_parameter_serializer(location)
370
361
  if serialize is not None:
371
362
  strategy = strategy.map(serialize)
@@ -472,35 +463,12 @@ def make_negative_strategy(
472
463
  )
473
464
 
474
465
 
475
- DATA_GENERATION_METHOD_TO_STRATEGY_FACTORY = {
476
- DataGenerationMethod.positive: make_positive_strategy,
477
- DataGenerationMethod.negative: make_negative_strategy,
466
+ GENERATOR_MODE_TO_STRATEGY_FACTORY = {
467
+ GenerationMode.POSITIVE: make_positive_strategy,
468
+ GenerationMode.NEGATIVE: make_negative_strategy,
478
469
  }
479
470
 
480
471
 
481
- def is_valid_path(parameters: dict[str, Any]) -> bool:
482
- """Empty strings ("") are excluded from path by urllib3.
483
-
484
- A path containing to "/" or "%2F" will lead to ambiguous path resolution in
485
- many frameworks and libraries, such behaviour have been observed in both
486
- WSGI and ASGI applications.
487
-
488
- In this case one variable in the path template will be empty, which will lead to 404 in most of the cases.
489
- Because of it this case doesn't bring much value and might lead to false positives results of Schemathesis runs.
490
- """
491
- disallowed_values = (SLASH, "")
492
-
493
- return not any(
494
- (
495
- value in disallowed_values
496
- or is_illegal_surrogate(value)
497
- or isinstance(value, str)
498
- and (SLASH in value or "}" in value or "{" in value)
499
- )
500
- for value in parameters.values()
501
- )
502
-
503
-
504
472
  def quote_all(parameters: dict[str, Any]) -> dict[str, Any]:
505
473
  """Apply URL quotation for all values in a dictionary."""
506
474
  # Even though, "." is an unreserved character, it has a special meaning in "." and ".." strings.
@@ -530,8 +498,3 @@ def apply_hooks(
530
498
  """Apply all hooks related to the given location."""
531
499
  container = LOCATION_TO_CONTAINER[location]
532
500
  return apply_to_all_dispatchers(operation, context, hooks, strategy, container)
533
-
534
-
535
- def clear_cache() -> None:
536
- _PARAMETER_STRATEGIES_CACHE.clear()
537
- _BODY_STRATEGIES_CACHE.clear()