schemathesis 3.13.0__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 (245) 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 -1016
  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 +683 -247
  125. schemathesis/specs/graphql/__init__.py +0 -1
  126. schemathesis/specs/graphql/nodes.py +27 -0
  127. schemathesis/specs/graphql/scalars.py +86 -0
  128. schemathesis/specs/graphql/schemas.py +395 -123
  129. schemathesis/specs/graphql/validation.py +33 -0
  130. schemathesis/specs/openapi/__init__.py +9 -1
  131. schemathesis/specs/openapi/_hypothesis.py +578 -317
  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 +753 -74
  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 +117 -68
  154. schemathesis/specs/openapi/negative/mutations.py +294 -104
  155. schemathesis/specs/openapi/negative/utils.py +3 -6
  156. schemathesis/specs/openapi/patterns.py +458 -0
  157. schemathesis/specs/openapi/references.py +60 -81
  158. schemathesis/specs/openapi/schemas.py +648 -650
  159. schemathesis/specs/openapi/serialization.py +53 -30
  160. schemathesis/specs/openapi/stateful/__init__.py +404 -69
  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.13.0.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.13.0.dist-info → schemathesis-4.4.2.dist-info/licenses}/LICENSE +1 -1
  188. schemathesis/_compat.py +0 -41
  189. schemathesis/_hypothesis.py +0 -115
  190. schemathesis/cli/callbacks.py +0 -188
  191. schemathesis/cli/cassettes.py +0 -253
  192. schemathesis/cli/context.py +0 -36
  193. schemathesis/cli/debug.py +0 -21
  194. schemathesis/cli/handlers.py +0 -11
  195. schemathesis/cli/junitxml.py +0 -41
  196. schemathesis/cli/options.py +0 -51
  197. schemathesis/cli/output/__init__.py +0 -1
  198. schemathesis/cli/output/default.py +0 -508
  199. schemathesis/cli/output/short.py +0 -40
  200. schemathesis/constants.py +0 -79
  201. schemathesis/exceptions.py +0 -207
  202. schemathesis/extra/_aiohttp.py +0 -27
  203. schemathesis/extra/_flask.py +0 -10
  204. schemathesis/extra/_server.py +0 -16
  205. schemathesis/extra/pytest_plugin.py +0 -216
  206. schemathesis/failures.py +0 -131
  207. schemathesis/fixups/__init__.py +0 -29
  208. schemathesis/fixups/fast_api.py +0 -30
  209. schemathesis/lazy.py +0 -227
  210. schemathesis/models.py +0 -1041
  211. schemathesis/parameters.py +0 -88
  212. schemathesis/runner/__init__.py +0 -460
  213. schemathesis/runner/events.py +0 -240
  214. schemathesis/runner/impl/__init__.py +0 -3
  215. schemathesis/runner/impl/core.py +0 -755
  216. schemathesis/runner/impl/solo.py +0 -85
  217. schemathesis/runner/impl/threadpool.py +0 -367
  218. schemathesis/runner/serialization.py +0 -189
  219. schemathesis/serializers.py +0 -233
  220. schemathesis/service/__init__.py +0 -3
  221. schemathesis/service/client.py +0 -46
  222. schemathesis/service/constants.py +0 -12
  223. schemathesis/service/events.py +0 -39
  224. schemathesis/service/handler.py +0 -39
  225. schemathesis/service/models.py +0 -7
  226. schemathesis/service/serialization.py +0 -153
  227. schemathesis/service/worker.py +0 -40
  228. schemathesis/specs/graphql/loaders.py +0 -215
  229. schemathesis/specs/openapi/constants.py +0 -7
  230. schemathesis/specs/openapi/expressions/context.py +0 -12
  231. schemathesis/specs/openapi/expressions/pointers.py +0 -29
  232. schemathesis/specs/openapi/filters.py +0 -44
  233. schemathesis/specs/openapi/links.py +0 -302
  234. schemathesis/specs/openapi/loaders.py +0 -453
  235. schemathesis/specs/openapi/parameters.py +0 -413
  236. schemathesis/specs/openapi/security.py +0 -129
  237. schemathesis/specs/openapi/validation.py +0 -24
  238. schemathesis/stateful.py +0 -349
  239. schemathesis/targets.py +0 -32
  240. schemathesis/types.py +0 -38
  241. schemathesis/utils.py +0 -436
  242. schemathesis-3.13.0.dist-info/METADATA +0 -202
  243. schemathesis-3.13.0.dist-info/RECORD +0 -91
  244. schemathesis-3.13.0.dist-info/entry_points.txt +0 -6
  245. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Collection
4
+
5
+ from schemathesis.transport import SerializationContext
6
+ from schemathesis.transport.asgi import ASGI_TRANSPORT
7
+ from schemathesis.transport.requests import REQUESTS_TRANSPORT
8
+ from schemathesis.transport.wsgi import WSGI_TRANSPORT
9
+
10
+ if TYPE_CHECKING:
11
+ from hypothesis import strategies as st
12
+
13
+
14
+ MEDIA_TYPES: dict[str, st.SearchStrategy[bytes]] = {}
15
+
16
+
17
+ def register_media_type(name: str, strategy: st.SearchStrategy[bytes], *, aliases: Collection[str] = ()) -> None:
18
+ r"""Register a custom Hypothesis strategy for generating media type content.
19
+
20
+ Args:
21
+ name: Media type name that matches your OpenAPI requestBody content type
22
+ strategy: Hypothesis strategy that generates bytes for this media type
23
+ aliases: Additional media type names that use the same strategy
24
+
25
+ Example:
26
+ ```python
27
+ import schemathesis
28
+ from hypothesis import strategies as st
29
+
30
+ # Register PDF file strategy
31
+ pdf_strategy = st.sampled_from([
32
+ b"%PDF-1.4\n1 0 obj\n<<\n/Type /Catalog\n>>\nendobj\n%%EOF",
33
+ b"%PDF-1.5\n%\xe2\xe3\xcf\xd3\n1 0 obj\n<<\n/Type /Catalog\n>>\nendobj\n%%EOF"
34
+ ])
35
+ schemathesis.openapi.media_type("application/pdf", pdf_strategy)
36
+
37
+ # Dynamic content generation
38
+ @st.composite
39
+ def xml_content(draw):
40
+ tag = draw(st.text(min_size=3, max_size=10))
41
+ content = draw(st.text(min_size=1, max_size=50))
42
+ return f"<?xml version='1.0'?><{tag}>{content}</{tag}>".encode()
43
+
44
+ schemathesis.openapi.media_type("application/xml", xml_content())
45
+ ```
46
+
47
+ Schema usage:
48
+ ```yaml
49
+ requestBody:
50
+ content:
51
+ application/pdf: # Uses your PDF strategy
52
+ schema:
53
+ type: string
54
+ format: binary
55
+ application/xml: # Uses your XML strategy
56
+ schema:
57
+ type: string
58
+ format: binary
59
+ ```
60
+
61
+ """
62
+
63
+ @REQUESTS_TRANSPORT.serializer(name, *aliases)
64
+ @ASGI_TRANSPORT.serializer(name, *aliases)
65
+ @WSGI_TRANSPORT.serializer(name, *aliases)
66
+ def serialize(ctx: SerializationContext, value: Any) -> dict[str, Any]:
67
+ return {"data": value}
68
+
69
+ MEDIA_TYPES[name] = strategy
70
+ for alias in aliases:
71
+ MEDIA_TYPES[alias] = strategy
72
+
73
+
74
+ def unregister_all() -> None:
75
+ MEDIA_TYPES.clear()
@@ -1,85 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
1
4
  from functools import lru_cache
2
- from typing import Any, Dict, Optional, Tuple
5
+ from typing import TYPE_CHECKING, Any
6
+ from urllib.parse import urlencode
3
7
 
4
- import attr
5
8
  import jsonschema
6
9
  from hypothesis import strategies as st
7
10
  from hypothesis_jsonschema import from_schema
8
11
 
9
- from .mutations import MutationContext
10
- from .types import Draw, Schema
12
+ from schemathesis.config import GenerationConfig
13
+ from schemathesis.core.jsonschema import ALL_KEYWORDS
14
+ from schemathesis.core.jsonschema.types import JsonSchema
15
+ from schemathesis.core.parameters import ParameterLocation
16
+
17
+ from .mutations import MutationContext, MutationMetadata
18
+
19
+ if TYPE_CHECKING:
20
+ from .types import Draw, Schema
21
+
22
+
23
+ @dataclass
24
+ class GeneratedValue:
25
+ """Wrapper for generated values with optional mutation metadata.
26
+
27
+ This allows us to pass both the value and metadata through the generation pipeline
28
+ without using tuples, making the code cleaner and type-safe.
29
+ """
30
+
31
+ value: Any
32
+ meta: MutationMetadata | None
11
33
 
34
+ __slots__ = ("value", "meta")
12
35
 
13
- @attr.s(slots=True, hash=False)
36
+
37
+ @dataclass
14
38
  class CacheKey:
15
39
  """A cache key for API Operation / location.
16
40
 
17
41
  Carries the schema around but don't use it for hashing to simplify LRU cache usage.
18
42
  """
19
43
 
20
- operation_name: str = attr.ib()
21
- location: str = attr.ib()
22
- schema: Schema = attr.ib()
44
+ operation_name: str
45
+ location: str
46
+ schema: JsonSchema
47
+ validator_cls: type[jsonschema.Validator]
48
+
49
+ __slots__ = ("operation_name", "location", "schema", "validator_cls")
23
50
 
24
51
  def __hash__(self) -> int:
25
52
  return hash((self.operation_name, self.location))
26
53
 
27
54
 
28
- @lru_cache()
29
- def get_validator(cache_key: CacheKey) -> jsonschema.Draft4Validator:
55
+ @lru_cache
56
+ def get_validator(cache_key: CacheKey) -> jsonschema.Validator:
30
57
  """Get JSON Schema validator for the given schema."""
31
58
  # Each operation / location combo has only a single schema, therefore could be cached
32
- return jsonschema.Draft4Validator(cache_key.schema)
33
-
34
-
35
- ALL_KEYWORDS = {
36
- "additionalItems",
37
- "additionalProperties",
38
- "allOf",
39
- "anyOf",
40
- "const",
41
- "contains",
42
- "contentEncoding",
43
- "contentMediaType",
44
- "dependencies",
45
- "enum",
46
- "else",
47
- "exclusiveMaximum",
48
- "exclusiveMinimum",
49
- "format",
50
- "if",
51
- "items",
52
- "maxItems",
53
- "maxLength",
54
- "maxProperties",
55
- "maximum",
56
- "minItems",
57
- "minLength",
58
- "minProperties",
59
- "minimum",
60
- "multipleOf",
61
- "not",
62
- "oneOf",
63
- "pattern",
64
- "patternProperties",
65
- "properties",
66
- "propertyNames",
67
- "$ref",
68
- "required",
69
- "then",
70
- "type",
71
- "uniqueItems",
72
- }
73
-
74
-
75
- @lru_cache()
76
- def split_schema(cache_key: CacheKey) -> Tuple[Schema, Schema]:
59
+ return cache_key.validator_cls(cache_key.schema)
60
+
61
+
62
+ @lru_cache
63
+ def split_schema(cache_key: CacheKey) -> tuple[Schema, Schema]:
77
64
  """Split the schema in two parts.
78
65
 
79
66
  The first one contains only validation JSON Schema keywords, the second one everything else.
80
67
  """
81
68
  keywords, non_keywords = {}, {}
82
- for keyword, value in cache_key.schema.items():
69
+ schema = {} if isinstance(cache_key.schema, bool) else cache_key.schema
70
+ for keyword, value in schema.items():
83
71
  if keyword in ALL_KEYWORDS:
84
72
  keywords[keyword] = value
85
73
  else:
@@ -88,29 +76,90 @@ def split_schema(cache_key: CacheKey) -> Tuple[Schema, Schema]:
88
76
 
89
77
 
90
78
  def negative_schema(
91
- schema: Schema,
79
+ schema: JsonSchema,
92
80
  operation_name: str,
93
- location: str,
94
- media_type: Optional[str],
81
+ location: ParameterLocation,
82
+ media_type: str | None,
83
+ generation_config: GenerationConfig,
95
84
  *,
96
- custom_formats: Dict[str, st.SearchStrategy[str]],
85
+ custom_formats: dict[str, st.SearchStrategy[str]],
86
+ validator_cls: type[jsonschema.Validator],
97
87
  ) -> st.SearchStrategy:
98
88
  """A strategy for instances that DO NOT match the input schema.
99
89
 
100
90
  It is used to cover the input space that is not possible to cover with the "positive" strategy.
91
+
92
+ Returns a strategy that produces GeneratedValue instances with mutation metadata.
101
93
  """
102
94
  # The mutated schema is passed to `from_schema` and guarded against producing instances valid against
103
95
  # the original schema.
104
- cache_key = CacheKey(operation_name, location, schema)
96
+ cache_key = CacheKey(operation_name, location, schema, validator_cls)
105
97
  validator = get_validator(cache_key)
106
98
  keywords, non_keywords = split_schema(cache_key)
107
- return mutated(keywords, non_keywords, location, media_type).flatmap(
108
- lambda s: from_schema(s, custom_formats=custom_formats).filter(lambda v: not validator.is_valid(v))
109
- )
110
-
111
99
 
112
- @st.composite # type: ignore
113
- def mutated(draw: Draw, keywords: Schema, non_keywords: Schema, location: str, media_type: Optional[str]) -> Any:
100
+ if location == ParameterLocation.QUERY:
101
+
102
+ def filter_values(value: dict[str, Any]) -> bool:
103
+ return is_non_empty_query(value) and not validator.is_valid(value)
104
+
105
+ else:
106
+
107
+ def filter_values(value: dict[str, Any]) -> bool:
108
+ return not validator.is_valid(value)
109
+
110
+ def generate_value_with_metadata(value: tuple[dict, MutationMetadata]) -> st.SearchStrategy:
111
+ schema, metadata = value
112
+ return (
113
+ from_schema(
114
+ schema,
115
+ custom_formats=custom_formats,
116
+ allow_x00=generation_config.allow_x00,
117
+ codec=generation_config.codec,
118
+ )
119
+ .filter(filter_values)
120
+ .map(lambda value: GeneratedValue(value, metadata))
121
+ )
122
+
123
+ return mutated(
124
+ keywords=keywords,
125
+ non_keywords=non_keywords,
126
+ location=location,
127
+ media_type=media_type,
128
+ allow_extra_parameters=generation_config.allow_extra_parameters,
129
+ ).flatmap(generate_value_with_metadata)
130
+
131
+
132
+ def is_non_empty_query(query: dict[str, Any]) -> bool:
133
+ # Whether this query parameters will be encoded to a non-empty query string
134
+ result = []
135
+ for key, values in query.items():
136
+ if isinstance(values, str) or not hasattr(values, "__iter__"):
137
+ values = [values]
138
+ for value in values:
139
+ if value is not None:
140
+ result.append(
141
+ (
142
+ key.encode("utf-8") if isinstance(key, str) else key,
143
+ value.encode("utf-8") if isinstance(value, str) else value,
144
+ )
145
+ )
146
+ return urlencode(result, doseq=True) != ""
147
+
148
+
149
+ @st.composite # type: ignore[misc]
150
+ def mutated(
151
+ draw: Draw,
152
+ *,
153
+ keywords: Schema,
154
+ non_keywords: Schema,
155
+ location: ParameterLocation,
156
+ media_type: str | None,
157
+ allow_extra_parameters: bool,
158
+ ) -> Any:
114
159
  return MutationContext(
115
- keywords=keywords, non_keywords=non_keywords, location=location, media_type=media_type
160
+ keywords=keywords,
161
+ non_keywords=non_keywords,
162
+ location=location,
163
+ media_type=media_type,
164
+ allow_extra_parameters=allow_extra_parameters,
116
165
  ).mutate(draw)