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,8 +3,8 @@ from __future__ import annotations
3
3
  from itertools import chain
4
4
  from typing import Any, Callable
5
5
 
6
- from ...internal.copy import fast_deepcopy
7
- from ...internal.jsonschema import traverse_schema
6
+ from schemathesis.core.transforms import deepclone, transform
7
+
8
8
  from .patterns import update_quantifier
9
9
 
10
10
 
@@ -22,7 +22,7 @@ def to_json_schema(
22
22
  See a recursive version below.
23
23
  """
24
24
  if copy:
25
- schema = fast_deepcopy(schema)
25
+ schema = deepclone(schema)
26
26
  if schema.get(nullable_name) is True:
27
27
  del schema[nullable_name]
28
28
  schema = {"anyOf": [schema, {"type": "null"}]}
@@ -94,7 +94,7 @@ def is_read_only(schema: dict[str, Any] | bool) -> bool:
94
94
  def to_json_schema_recursive(
95
95
  schema: dict[str, Any], nullable_name: str, is_response_schema: bool = False, update_quantifiers: bool = True
96
96
  ) -> dict[str, Any]:
97
- return traverse_schema(
97
+ return transform(
98
98
  schema,
99
99
  to_json_schema,
100
100
  nullable_name=nullable_name,
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
 
4
4
  from typing import TYPE_CHECKING, Any
5
5
 
6
- from ..._lazy_import import lazy_import
6
+ from schemathesis.core.lazy_import import lazy_import
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from jsonschema import Validator
@@ -9,11 +9,15 @@ from typing import TYPE_CHECKING, Any, Generator, Iterator, Union, cast
9
9
  import requests
10
10
  from hypothesis_jsonschema import from_schema
11
11
 
12
- from ...constants import DEFAULT_RESPONSE_TIMEOUT
13
- from ...generation import get_single_example
14
- from ...internal.copy import fast_deepcopy
15
- from ...models import APIOperation, Case, TestPhase
16
- from ._hypothesis import get_case_strategy, get_default_format_strategies
12
+ from schemathesis.core.transforms import deepclone
13
+ from schemathesis.core.transport import DEFAULT_RESPONSE_TIMEOUT
14
+ from schemathesis.generation import GenerationConfig
15
+ from schemathesis.generation.case import Case
16
+ from schemathesis.generation.hypothesis import examples
17
+ from schemathesis.generation.meta import TestPhase
18
+ from schemathesis.schemas import APIOperation
19
+
20
+ from ._hypothesis import get_default_format_strategies, openapi_cases
17
21
  from .constants import LOCATION_TO_CONTAINER
18
22
  from .formats import STRING_FORMATS
19
23
  from .parameters import OpenAPIBody, OpenAPIParameter
@@ -21,8 +25,6 @@ from .parameters import OpenAPIBody, OpenAPIParameter
21
25
  if TYPE_CHECKING:
22
26
  from hypothesis.strategies import SearchStrategy
23
27
 
24
- from ...generation import GenerationConfig
25
-
26
28
 
27
29
  @dataclass
28
30
  class ParameterExample:
@@ -45,7 +47,7 @@ Example = Union[ParameterExample, BodyExample]
45
47
 
46
48
 
47
49
  def get_strategies_from_examples(
48
- operation: APIOperation[OpenAPIParameter, Case], as_strategy_kwargs: dict[str, Any] | None = None
50
+ operation: APIOperation[OpenAPIParameter], **kwargs: Any
49
51
  ) -> list[SearchStrategy[Case]]:
50
52
  """Build a set of strategies that generate test cases based on explicit examples in the schema."""
51
53
  maps = {}
@@ -68,15 +70,15 @@ def get_strategies_from_examples(
68
70
  examples = list(extract_top_level(operation))
69
71
  # Add examples from parameter's schemas
70
72
  examples.extend(extract_from_schemas(operation))
71
- as_strategy_kwargs = as_strategy_kwargs or {}
72
- as_strategy_kwargs["phase"] = TestPhase.EXPLICIT
73
73
  return [
74
- get_case_strategy(operation=operation, **{**parameters, **(as_strategy_kwargs or {})}).map(serialize_components)
74
+ openapi_cases(operation=operation, **{**parameters, **kwargs, "phase": TestPhase.EXPLICIT}).map(
75
+ serialize_components
76
+ )
75
77
  for parameters in produce_combinations(examples)
76
78
  ]
77
79
 
78
80
 
79
- def extract_top_level(operation: APIOperation[OpenAPIParameter, Case]) -> Generator[Example, None, None]:
81
+ def extract_top_level(operation: APIOperation[OpenAPIParameter]) -> Generator[Example, None, None]:
80
82
  """Extract top-level parameter examples from `examples` & `example` fields."""
81
83
  responses = find_in_responses(operation)
82
84
  for parameter in operation.iter_parameters():
@@ -144,7 +146,7 @@ def _expand_subschemas(schema: dict[str, Any] | bool) -> Generator[dict[str, Any
144
146
  for subschema in schema[key]:
145
147
  yield subschema
146
148
  if "allOf" in schema:
147
- subschema = fast_deepcopy(schema["allOf"][0])
149
+ subschema = deepclone(schema["allOf"][0])
148
150
  for sub in schema["allOf"][1:]:
149
151
  if isinstance(sub, dict):
150
152
  for key, value in sub.items():
@@ -162,7 +164,7 @@ def _expand_subschemas(schema: dict[str, Any] | bool) -> Generator[dict[str, Any
162
164
 
163
165
 
164
166
  def _find_parameter_examples_definition(
165
- operation: APIOperation[OpenAPIParameter, Case], parameter_name: str, field_name: str
167
+ operation: APIOperation[OpenAPIParameter], parameter_name: str, field_name: str
166
168
  ) -> dict[str, Any]:
167
169
  """Find the original, unresolved `examples` definition of a parameter."""
168
170
  from .schemas import BaseOpenAPISchema
@@ -180,13 +182,13 @@ def _find_parameter_examples_definition(
180
182
 
181
183
 
182
184
  def _find_request_body_examples_definition(
183
- operation: APIOperation[OpenAPIParameter, Case], alternative: OpenAPIBody
185
+ operation: APIOperation[OpenAPIParameter], alternative: OpenAPIBody
184
186
  ) -> dict[str, Any]:
185
187
  """Find the original, unresolved `examples` definition of a request body variant."""
186
188
  from .schemas import BaseOpenAPISchema
187
189
 
188
190
  schema = cast(BaseOpenAPISchema, operation.schema)
189
- if schema.spec_version == "2.0":
191
+ if schema.specification.version == "2.0":
190
192
  raw_schema = schema.raw_schema
191
193
  path_data = raw_schema["paths"][operation.path]
192
194
  parameters = chain(path_data[operation.method].get("parameters", []), path_data.get("parameters", []))
@@ -222,12 +224,12 @@ def extract_inner_examples(
222
224
  @lru_cache
223
225
  def load_external_example(url: str) -> bytes:
224
226
  """Load examples the `externalValue` keyword."""
225
- response = requests.get(url, timeout=DEFAULT_RESPONSE_TIMEOUT / 1000)
227
+ response = requests.get(url, timeout=DEFAULT_RESPONSE_TIMEOUT)
226
228
  response.raise_for_status()
227
229
  return response.content
228
230
 
229
231
 
230
- def extract_from_schemas(operation: APIOperation[OpenAPIParameter, Case]) -> Generator[Example, None, None]:
232
+ def extract_from_schemas(operation: APIOperation[OpenAPIParameter]) -> Generator[Example, None, None]:
231
233
  """Extract examples from parameters' schema definitions."""
232
234
  for parameter in operation.iter_parameters():
233
235
  schema = parameter.as_json_schema(operation)
@@ -244,7 +246,7 @@ def extract_from_schemas(operation: APIOperation[OpenAPIParameter, Case]) -> Gen
244
246
 
245
247
 
246
248
  def extract_from_schema(
247
- operation: APIOperation[OpenAPIParameter, Case],
249
+ operation: APIOperation[OpenAPIParameter],
248
250
  schema: dict[str, Any],
249
251
  example_field_name: str,
250
252
  examples_field_name: str,
@@ -306,7 +308,7 @@ def _generate_single_example(
306
308
  allow_x00=generation_config.allow_x00,
307
309
  codec=generation_config.codec,
308
310
  )
309
- return get_single_example(strategy)
311
+ return examples.generate_one(strategy)
310
312
 
311
313
 
312
314
  def produce_combinations(examples: list[Example]) -> Generator[dict[str, Any], None, None]:
@@ -8,42 +8,38 @@ from __future__ import annotations
8
8
  import json
9
9
  from typing import Any
10
10
 
11
+ from schemathesis.generation.stateful.state_machine import StepOutput
12
+
11
13
  from . import lexer, nodes, parser
12
- from .context import ExpressionContext
13
14
 
14
- __all__ = [
15
- "lexer",
16
- "nodes",
17
- "parser",
18
- "ExpressionContext",
19
- ]
15
+ __all__ = ["lexer", "nodes", "parser"]
20
16
 
21
17
 
22
- def evaluate(expr: Any, context: ExpressionContext, evaluate_nested: bool = False) -> Any:
18
+ def evaluate(expr: Any, output: StepOutput, evaluate_nested: bool = False) -> Any:
23
19
  """Evaluate runtime expression in context."""
24
20
  if isinstance(expr, (dict, list)) and evaluate_nested:
25
- return _evaluate_nested(expr, context)
21
+ return _evaluate_nested(expr, output)
26
22
  if not isinstance(expr, str):
27
23
  # Can be a non-string constant
28
24
  return expr
29
- parts = [node.evaluate(context) for node in parser.parse(expr)]
25
+ parts = [node.evaluate(output) for node in parser.parse(expr)]
30
26
  if len(parts) == 1:
31
27
  return parts[0] # keep the return type the same as the internal value type
32
28
  # otherwise, concatenate into a string
33
29
  return "".join(str(part) for part in parts if part is not None)
34
30
 
35
31
 
36
- def _evaluate_nested(expr: dict[str, Any] | list, context: ExpressionContext) -> Any:
32
+ def _evaluate_nested(expr: dict[str, Any] | list, output: StepOutput) -> Any:
37
33
  if isinstance(expr, dict):
38
34
  return {
39
- _evaluate_object_key(key, context): evaluate(value, context, evaluate_nested=True)
35
+ _evaluate_object_key(key, output): evaluate(value, output, evaluate_nested=True)
40
36
  for key, value in expr.items()
41
37
  }
42
- return [evaluate(item, context, evaluate_nested=True) for item in expr]
38
+ return [evaluate(item, output, evaluate_nested=True) for item in expr]
43
39
 
44
40
 
45
- def _evaluate_object_key(key: str, context: ExpressionContext) -> Any:
46
- evaluated = evaluate(key, context)
41
+ def _evaluate_object_key(key: str, output: StepOutput) -> Any:
42
+ evaluated = evaluate(key, output)
47
43
  if isinstance(evaluated, str):
48
44
  return evaluated
49
45
  if isinstance(evaluated, bool):
@@ -1,10 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import re
3
4
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING
5
-
6
- if TYPE_CHECKING:
7
- import re
8
5
 
9
6
 
10
7
  @dataclass
@@ -4,14 +4,15 @@ from __future__ import annotations
4
4
 
5
5
  from dataclasses import dataclass
6
6
  from enum import Enum, unique
7
- from typing import TYPE_CHECKING, Any
7
+ from typing import TYPE_CHECKING, Any, cast
8
8
 
9
9
  from requests.structures import CaseInsensitiveDict
10
10
 
11
- from .. import references
11
+ from schemathesis.core.transforms import UNRESOLVABLE, resolve_pointer
12
+ from schemathesis.generation.stateful.state_machine import StepOutput
13
+ from schemathesis.transport.requests import REQUESTS_TRANSPORT
12
14
 
13
15
  if TYPE_CHECKING:
14
- from .context import ExpressionContext
15
16
  from .extractors import Extractor
16
17
 
17
18
 
@@ -19,7 +20,7 @@ if TYPE_CHECKING:
19
20
  class Node:
20
21
  """Generic expression node."""
21
22
 
22
- def evaluate(self, context: ExpressionContext) -> str:
23
+ def evaluate(self, output: StepOutput) -> str:
23
24
  raise NotImplementedError
24
25
 
25
26
 
@@ -38,7 +39,7 @@ class String(Node):
38
39
 
39
40
  value: str
40
41
 
41
- def evaluate(self, context: ExpressionContext) -> str:
42
+ def evaluate(self, output: StepOutput) -> str:
42
43
  """String tokens are passed as they are.
43
44
 
44
45
  ``foo{$request.path.id}``
@@ -52,24 +53,29 @@ class String(Node):
52
53
  class URL(Node):
53
54
  """A node for `$url` expression."""
54
55
 
55
- def evaluate(self, context: ExpressionContext) -> str:
56
- return context.case.get_full_url()
56
+ def evaluate(self, output: StepOutput) -> str:
57
+ import requests
58
+
59
+ base_url = output.case.operation.base_url or "http://127.0.0.1"
60
+ kwargs = REQUESTS_TRANSPORT.serialize_case(output.case, base_url=base_url)
61
+ prepared = requests.Request(**kwargs).prepare()
62
+ return cast(str, prepared.url)
57
63
 
58
64
 
59
65
  @dataclass
60
66
  class Method(Node):
61
67
  """A node for `$method` expression."""
62
68
 
63
- def evaluate(self, context: ExpressionContext) -> str:
64
- return context.case.operation.method.upper()
69
+ def evaluate(self, output: StepOutput) -> str:
70
+ return output.case.operation.method.upper()
65
71
 
66
72
 
67
73
  @dataclass
68
74
  class StatusCode(Node):
69
75
  """A node for `$statusCode` expression."""
70
76
 
71
- def evaluate(self, context: ExpressionContext) -> str:
72
- return str(context.response.status_code)
77
+ def evaluate(self, output: StepOutput) -> str:
78
+ return str(output.response.status_code)
73
79
 
74
80
 
75
81
  @dataclass
@@ -80,11 +86,11 @@ class NonBodyRequest(Node):
80
86
  parameter: str
81
87
  extractor: Extractor | None = None
82
88
 
83
- def evaluate(self, context: ExpressionContext) -> str:
89
+ def evaluate(self, output: StepOutput) -> str:
84
90
  container: dict | CaseInsensitiveDict = {
85
- "query": context.case.query,
86
- "path": context.case.path_parameters,
87
- "header": context.case.headers,
91
+ "query": output.case.query,
92
+ "path": output.case.path_parameters,
93
+ "header": output.case.headers,
88
94
  }[self.location] or {}
89
95
  if self.location == "header":
90
96
  container = CaseInsensitiveDict(container)
@@ -102,12 +108,12 @@ class BodyRequest(Node):
102
108
 
103
109
  pointer: str | None = None
104
110
 
105
- def evaluate(self, context: ExpressionContext) -> Any:
106
- document = context.case.body
111
+ def evaluate(self, output: StepOutput) -> Any:
112
+ document = output.case.body
107
113
  if self.pointer is None:
108
114
  return document
109
- resolved = references.resolve_pointer(document, self.pointer[1:])
110
- if resolved is references.UNRESOLVABLE:
115
+ resolved = resolve_pointer(document, self.pointer[1:])
116
+ if resolved is UNRESOLVABLE:
111
117
  return None
112
118
  return resolved
113
119
 
@@ -119,13 +125,13 @@ class HeaderResponse(Node):
119
125
  parameter: str
120
126
  extractor: Extractor | None = None
121
127
 
122
- def evaluate(self, context: ExpressionContext) -> str:
123
- value = context.response.headers.get(self.parameter)
128
+ def evaluate(self, output: StepOutput) -> str:
129
+ value = output.response.headers.get(self.parameter.lower())
124
130
  if value is None:
125
131
  return ""
126
132
  if self.extractor is not None:
127
- return self.extractor.extract(value) or ""
128
- return value
133
+ return self.extractor.extract(value[0]) or ""
134
+ return value[0]
129
135
 
130
136
 
131
137
  @dataclass
@@ -134,17 +140,12 @@ class BodyResponse(Node):
134
140
 
135
141
  pointer: str | None = None
136
142
 
137
- def evaluate(self, context: ExpressionContext) -> Any:
138
- from ....transports.responses import WSGIResponse
139
-
140
- if isinstance(context.response, WSGIResponse):
141
- document = context.response.json
142
- else:
143
- document = context.response.json()
143
+ def evaluate(self, output: StepOutput) -> Any:
144
+ document = output.response.json()
144
145
  if self.pointer is None:
145
146
  # We need the parsed document - data will be serialized before sending to the application
146
147
  return document
147
- resolved = references.resolve_pointer(document, self.pointer[1:])
148
- if resolved is references.UNRESOLVABLE:
148
+ resolved = resolve_pointer(document, self.pointer[1:])
149
+ if resolved is UNRESOLVABLE:
149
150
  return None
150
151
  return resolved
@@ -5,6 +5,8 @@ from base64 import b64encode
5
5
  from functools import lru_cache
6
6
  from typing import TYPE_CHECKING
7
7
 
8
+ from schemathesis.transport.serialization import Binary
9
+
8
10
  if TYPE_CHECKING:
9
11
  from hypothesis import strategies as st
10
12
 
@@ -54,8 +56,6 @@ def get_default_format_strategies() -> dict[str, st.SearchStrategy]:
54
56
  from hypothesis import strategies as st
55
57
  from requests.auth import _basic_auth_str
56
58
 
57
- from ...serializers import Binary
58
-
59
59
  def make_basic_auth_str(item: tuple[str, str]) -> str:
60
60
  return _basic_auth_str(*item)
61
61
 
@@ -67,6 +67,7 @@ def get_default_format_strategies() -> dict[str, st.SearchStrategy]:
67
67
  return {
68
68
  "binary": st.binary().map(Binary),
69
69
  "byte": st.binary().map(lambda x: b64encode(x).decode()),
70
+ "uuid": st.uuids().map(str),
70
71
  # RFC 7230, Section 3.2.6
71
72
  "_header_name": st.text(
72
73
  min_size=1, alphabet=st.sampled_from("!#$%&'*+-.^_`|~" + string.digits + string.ascii_letters)