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
@@ -1 +0,0 @@
1
- from .loaders import from_asgi, from_dict, from_file, from_path, from_url, from_wsgi
@@ -4,8 +4,7 @@ from dataclasses import dataclass, field
4
4
  from typing import TYPE_CHECKING
5
5
 
6
6
  if TYPE_CHECKING:
7
- from ...models import APIOperation
8
- from ...schemas import APIOperationMap
7
+ from ...schemas import APIOperation, APIOperationMap
9
8
 
10
9
 
11
10
  @dataclass
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from functools import lru_cache
4
4
  from typing import TYPE_CHECKING
5
5
 
6
- from ...exceptions import UsageError
6
+ from schemathesis.core.errors import IncorrectUsage
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  import graphql
@@ -21,9 +21,11 @@ def scalar(name: str, strategy: st.SearchStrategy[graphql.ValueNode]) -> None:
21
21
  from hypothesis.strategies import SearchStrategy
22
22
 
23
23
  if not isinstance(name, str):
24
- raise UsageError(f"Scalar name {name!r} must be a string")
24
+ raise IncorrectUsage(f"Scalar name {name!r} must be a string")
25
25
  if not isinstance(strategy, SearchStrategy):
26
- raise UsageError(f"{strategy!r} must be a Hypothesis strategy which generates AST nodes matching this scalar")
26
+ raise IncorrectUsage(
27
+ f"{strategy!r} must be a Hypothesis strategy which generates AST nodes matching this scalar"
28
+ )
27
29
  CUSTOM_SCALARS[name] = strategy
28
30
 
29
31
 
@@ -14,44 +14,49 @@ from typing import (
14
14
  Iterator,
15
15
  Mapping,
16
16
  NoReturn,
17
- Sequence,
18
- TypeVar,
17
+ Union,
19
18
  cast,
20
19
  )
21
- from urllib.parse import urlsplit, urlunsplit
20
+ from urllib.parse import urlsplit
22
21
 
23
22
  import graphql
24
23
  from hypothesis import strategies as st
25
24
  from hypothesis_graphql import strategies as gql_st
26
25
  from requests.structures import CaseInsensitiveDict
27
26
 
28
- from ... import auths
29
- from ...checks import not_a_server_error
30
- from ...constants import NOT_SET, SCHEMATHESIS_TEST_CASE_HEADER
31
- from ...exceptions import OperationNotFound, OperationSchemaError
32
- from ...generation import DataGenerationMethod, GenerationConfig
33
- from ...hooks import (
34
- GLOBAL_HOOK_DISPATCHER,
35
- HookContext,
36
- HookDispatcher,
37
- apply_to_all_dispatchers,
38
- should_skip_operation,
27
+ from schemathesis import auths
28
+ from schemathesis.core import NOT_SET, NotSet, Specification
29
+ from schemathesis.core.errors import InvalidSchema, OperationNotFound
30
+ from schemathesis.core.result import Ok, Result
31
+ from schemathesis.generation import GenerationConfig, GenerationMode
32
+ from schemathesis.generation.case import Case
33
+ from schemathesis.generation.meta import (
34
+ CaseMetadata,
35
+ ComponentInfo,
36
+ ComponentKind,
37
+ ExplicitPhaseData,
38
+ GeneratePhaseData,
39
+ GenerationInfo,
40
+ PhaseInfo,
41
+ TestPhase,
39
42
  )
40
- from ...internal.result import Ok, Result
41
- from ...models import APIOperation, Case, OperationDefinition
42
- from ...schemas import APIOperationMap, BaseSchema
43
- from ...types import Body, Cookies, Headers, NotSet, PathParameters, Query
44
- from ..openapi.constants import LOCATION_TO_CONTAINER
43
+ from schemathesis.hooks import HookContext, HookDispatcher, apply_to_all_dispatchers
44
+ from schemathesis.schemas import (
45
+ APIOperation,
46
+ APIOperationMap,
47
+ ApiStatistic,
48
+ BaseSchema,
49
+ OperationDefinition,
50
+ )
51
+ from schemathesis.specs.openapi.constants import LOCATION_TO_CONTAINER
52
+
45
53
  from ._cache import OperationCache
46
54
  from .scalars import CUSTOM_SCALARS, get_extra_scalar_strategies
47
55
 
48
56
  if TYPE_CHECKING:
49
57
  from hypothesis.strategies import SearchStrategy
50
58
 
51
- from ...auths import AuthStorage
52
- from ...internal.checks import CheckFunction
53
- from ...stateful import Stateful, StatefulTest
54
- from ...transports.responses import GenericResponse
59
+ from schemathesis.auths import AuthStorage
55
60
 
56
61
 
57
62
  @unique
@@ -61,47 +66,13 @@ class RootType(enum.Enum):
61
66
 
62
67
 
63
68
  @dataclass(repr=False)
64
- class GraphQLCase(Case):
65
- def __hash__(self) -> int:
66
- return hash(self.as_curl_command({SCHEMATHESIS_TEST_CASE_HEADER: "0"}))
67
-
68
- def _get_url(self, base_url: str | None) -> str:
69
- base_url = self._get_base_url(base_url)
70
- # Replace the path, in case if the user provided any path parameters via hooks
71
- parts = list(urlsplit(base_url))
72
- parts[2] = self.formatted_path
73
- return urlunsplit(parts)
74
-
75
- def _get_body(self) -> Body | NotSet:
76
- return self.body if isinstance(self.body, (NotSet, bytes)) else {"query": self.body}
77
-
78
- def validate_response(
79
- self,
80
- response: GenericResponse,
81
- checks: tuple[CheckFunction, ...] = (),
82
- additional_checks: tuple[CheckFunction, ...] = (),
83
- excluded_checks: tuple[CheckFunction, ...] = (),
84
- code_sample_style: str | None = None,
85
- headers: dict[str, Any] | None = None,
86
- transport_kwargs: dict[str, Any] | None = None,
87
- ) -> None:
88
- checks = checks or (not_a_server_error,)
89
- checks += additional_checks
90
- checks = tuple(check for check in checks if check not in excluded_checks)
91
- return super().validate_response(
92
- response, checks, code_sample_style=code_sample_style, headers=headers, transport_kwargs=transport_kwargs
93
- )
94
-
95
-
96
- C = TypeVar("C", bound=Case)
97
-
98
-
99
- @dataclass
100
69
  class GraphQLOperationDefinition(OperationDefinition):
101
70
  field_name: str
102
71
  type_: graphql.GraphQLType
103
72
  root_type: RootType
104
73
 
74
+ def _repr_pretty_(self, *args: Any, **kwargs: Any) -> None: ...
75
+
105
76
  @property
106
77
  def is_query(self) -> bool:
107
78
  return self.root_type == RootType.QUERY
@@ -157,8 +128,8 @@ class GraphQLSchema(BaseSchema):
157
128
  return self.base_path
158
129
 
159
130
  @property
160
- def verbose_name(self) -> str:
161
- return "GraphQL"
131
+ def specification(self) -> Specification:
132
+ return Specification.graphql(version="")
162
133
 
163
134
  @property
164
135
  def client_schema(self) -> graphql.GraphQLSchema:
@@ -175,27 +146,34 @@ class GraphQLSchema(BaseSchema):
175
146
  def _get_base_path(self) -> str:
176
147
  return cast(str, urlsplit(self.location).path)
177
148
 
178
- @property
179
- def operations_count(self) -> int:
149
+ def _measure_statistic(self) -> ApiStatistic:
150
+ statistic = ApiStatistic()
180
151
  raw_schema = self.raw_schema["__schema"]
181
- total = 0
152
+ dummy_operation = APIOperation(
153
+ base_url=self.get_base_url(),
154
+ path=self.base_path,
155
+ label="",
156
+ method="POST",
157
+ schema=self,
158
+ definition=None, # type: ignore
159
+ )
160
+
182
161
  for type_name in ("queryType", "mutationType"):
183
162
  type_def = raw_schema.get(type_name)
184
163
  if type_def is not None:
185
164
  query_type_name = type_def["name"]
186
165
  for type_def in raw_schema.get("types", []):
187
166
  if type_def["name"] == query_type_name:
188
- total += len(type_def["fields"])
189
- return total
190
-
191
- @property
192
- def links_count(self) -> int:
193
- # Links are not supported for GraphQL
194
- return 0
167
+ for field in type_def["fields"]:
168
+ statistic.operations.total += 1
169
+ dummy_operation.label = f"{query_type_name}.{field['name']}"
170
+ if not self._should_skip(dummy_operation):
171
+ statistic.operations.selected += 1
172
+ return statistic
195
173
 
196
174
  def get_all_operations(
197
- self, hooks: HookDispatcher | None = None, generation_config: GenerationConfig | None = None
198
- ) -> Generator[Result[APIOperation, OperationSchemaError], None, None]:
175
+ self, generation_config: GenerationConfig | None = None
176
+ ) -> Generator[Result[APIOperation, InvalidSchema], None, None]:
199
177
  schema = self.client_schema
200
178
  for root_type, operation_type in (
201
179
  (RootType.QUERY, schema.query_type),
@@ -207,13 +185,6 @@ class GraphQLSchema(BaseSchema):
207
185
  operation = self._build_operation(root_type, operation_type, field_name, field_)
208
186
  if self._should_skip(operation):
209
187
  continue
210
- context = HookContext(operation=operation)
211
- if (
212
- should_skip_operation(GLOBAL_HOOK_DISPATCHER, context)
213
- or should_skip_operation(self.hooks, context)
214
- or (hooks and should_skip_operation(hooks, context))
215
- ):
216
- continue
217
188
  yield Ok(operation)
218
189
 
219
190
  def _should_skip(
@@ -234,7 +205,7 @@ class GraphQLSchema(BaseSchema):
234
205
  return APIOperation(
235
206
  base_url=self.get_base_url(),
236
207
  path=self.base_path,
237
- verbose_name=f"{operation_type.name}.{field_name}",
208
+ label=f"{operation_type.name}.{field_name}",
238
209
  method="POST",
239
210
  app=self.app,
240
211
  schema=self,
@@ -247,7 +218,6 @@ class GraphQLSchema(BaseSchema):
247
218
  field_name=field_name,
248
219
  root_type=root_type,
249
220
  ),
250
- case_cls=GraphQLCase,
251
221
  )
252
222
 
253
223
  def get_case_strategy(
@@ -255,51 +225,47 @@ class GraphQLSchema(BaseSchema):
255
225
  operation: APIOperation,
256
226
  hooks: HookDispatcher | None = None,
257
227
  auth_storage: AuthStorage | None = None,
258
- data_generation_method: DataGenerationMethod = DataGenerationMethod.default(),
228
+ generation_mode: GenerationMode = GenerationMode.default(),
259
229
  generation_config: GenerationConfig | None = None,
260
230
  **kwargs: Any,
261
231
  ) -> SearchStrategy:
262
- return get_case_strategy(
232
+ return graphql_cases(
263
233
  operation=operation,
264
- client_schema=self.client_schema,
265
234
  hooks=hooks,
266
235
  auth_storage=auth_storage,
267
- data_generation_method=data_generation_method,
236
+ generation_mode=generation_mode,
268
237
  generation_config=generation_config or self.generation_config,
269
238
  **kwargs,
270
239
  )
271
240
 
272
- def get_strategies_from_examples(
273
- self, operation: APIOperation, as_strategy_kwargs: dict[str, Any] | None = None
274
- ) -> list[SearchStrategy[Case]]:
275
- return []
276
-
277
- def get_stateful_tests(
278
- self, response: GenericResponse, operation: APIOperation, stateful: Stateful | None
279
- ) -> Sequence[StatefulTest]:
241
+ def get_strategies_from_examples(self, operation: APIOperation, **kwargs: Any) -> list[SearchStrategy[Case]]:
280
242
  return []
281
243
 
282
244
  def make_case(
283
245
  self,
284
246
  *,
285
- case_cls: type[C],
286
247
  operation: APIOperation,
287
- path_parameters: PathParameters | None = None,
288
- headers: Headers | None = None,
289
- cookies: Cookies | None = None,
290
- query: Query | None = None,
291
- body: Body | NotSet = NOT_SET,
248
+ method: str | None = None,
249
+ path: str | None = None,
250
+ path_parameters: dict[str, Any] | None = None,
251
+ headers: dict[str, Any] | None = None,
252
+ cookies: dict[str, Any] | None = None,
253
+ query: dict[str, Any] | None = None,
254
+ body: list | dict[str, Any] | str | int | float | bool | bytes | NotSet = NOT_SET,
292
255
  media_type: str | None = None,
293
- ) -> C:
294
- return case_cls(
256
+ meta: CaseMetadata | None = None,
257
+ ) -> Case:
258
+ return Case(
295
259
  operation=operation,
260
+ method=method or operation.method.upper(),
261
+ path=path or operation.path,
296
262
  path_parameters=path_parameters,
297
263
  headers=CaseInsensitiveDict(headers) if headers is not None else headers,
298
264
  cookies=cookies,
299
265
  query=query,
300
266
  body=body,
301
267
  media_type=media_type or "application/json",
302
- generation_time=0.0,
268
+ meta=meta,
303
269
  )
304
270
 
305
271
  def get_tags(self, operation: APIOperation) -> list[str] | None:
@@ -353,15 +319,21 @@ class FieldMap(Mapping):
353
319
 
354
320
 
355
321
  @st.composite # type: ignore
356
- def get_case_strategy(
322
+ def graphql_cases(
357
323
  draw: Callable,
324
+ *,
358
325
  operation: APIOperation,
359
- client_schema: graphql.GraphQLSchema,
360
326
  hooks: HookDispatcher | None = None,
361
- auth_storage: AuthStorage | None = None,
362
- data_generation_method: DataGenerationMethod = DataGenerationMethod.default(),
363
- generation_config: GenerationConfig | None = None,
364
- **kwargs: Any,
327
+ auth_storage: auths.AuthStorage | None = None,
328
+ generation_mode: GenerationMode = GenerationMode.default(),
329
+ generation_config: GenerationConfig,
330
+ path_parameters: NotSet | dict[str, Any] = NOT_SET,
331
+ headers: NotSet | dict[str, Any] = NOT_SET,
332
+ cookies: NotSet | dict[str, Any] = NOT_SET,
333
+ query: NotSet | dict[str, Any] = NOT_SET,
334
+ body: Any = NOT_SET,
335
+ media_type: str | None = None,
336
+ phase: TestPhase = TestPhase.GENERATE,
365
337
  ) -> Any:
366
338
  start = time.monotonic()
367
339
  definition = cast(GraphQLOperationDefinition, operation.definition)
@@ -370,10 +342,9 @@ def get_case_strategy(
370
342
  RootType.MUTATION: gql_st.mutations,
371
343
  }[definition.root_type]
372
344
  hook_context = HookContext(operation)
373
- generation_config = generation_config or GenerationConfig()
374
345
  custom_scalars = {**get_extra_scalar_strategies(), **CUSTOM_SCALARS}
375
346
  strategy = strategy_factory(
376
- client_schema,
347
+ operation.schema.client_schema, # type: ignore[attr-defined]
377
348
  fields=[definition.field_name],
378
349
  custom_scalars=custom_scalars,
379
350
  print_ast=_noop, # type: ignore
@@ -384,21 +355,41 @@ def get_case_strategy(
384
355
  strategy = apply_to_all_dispatchers(operation, hook_context, hooks, strategy, "body").map(graphql.print_ast)
385
356
  body = draw(strategy)
386
357
 
387
- path_parameters_ = _generate_parameter("path", draw, operation, hook_context, hooks)
388
- headers_ = _generate_parameter("header", draw, operation, hook_context, hooks)
389
- cookies_ = _generate_parameter("cookie", draw, operation, hook_context, hooks)
390
- query_ = _generate_parameter("query", draw, operation, hook_context, hooks)
391
-
392
- instance = GraphQLCase(
358
+ path_parameters_ = _generate_parameter("path", path_parameters, draw, operation, hook_context, hooks)
359
+ headers_ = _generate_parameter("header", headers, draw, operation, hook_context, hooks)
360
+ cookies_ = _generate_parameter("cookie", cookies, draw, operation, hook_context, hooks)
361
+ query_ = _generate_parameter("query", query, draw, operation, hook_context, hooks)
362
+
363
+ _phase_data = {
364
+ TestPhase.EXPLICIT: ExplicitPhaseData(),
365
+ TestPhase.GENERATE: GeneratePhaseData(),
366
+ }[phase]
367
+ phase_data = cast(Union[ExplicitPhaseData, GeneratePhaseData], _phase_data)
368
+ instance = operation.Case(
393
369
  path_parameters=path_parameters_,
394
370
  headers=headers_,
395
371
  cookies=cookies_,
396
372
  query=query_,
397
373
  body=body,
398
- operation=operation,
399
- data_generation_method=data_generation_method,
400
- generation_time=time.monotonic() - start,
401
- media_type="application/json",
374
+ meta=CaseMetadata(
375
+ generation=GenerationInfo(
376
+ time=time.monotonic() - start,
377
+ mode=generation_mode,
378
+ ),
379
+ phase=PhaseInfo(name=phase, data=phase_data),
380
+ components={
381
+ kind: ComponentInfo(mode=generation_mode)
382
+ for kind, value in [
383
+ (ComponentKind.QUERY, query_),
384
+ (ComponentKind.PATH_PARAMETERS, path_parameters_),
385
+ (ComponentKind.HEADERS, headers_),
386
+ (ComponentKind.COOKIES, cookies_),
387
+ (ComponentKind.BODY, body),
388
+ ]
389
+ if value is not NOT_SET
390
+ },
391
+ ),
392
+ media_type=media_type or "application/json",
402
393
  ) # type: ignore
403
394
  context = auths.AuthContext(
404
395
  operation=operation,
@@ -409,11 +400,19 @@ def get_case_strategy(
409
400
 
410
401
 
411
402
  def _generate_parameter(
412
- location: str, draw: Callable, operation: APIOperation, context: HookContext, hooks: HookDispatcher | None
403
+ location: str,
404
+ explicit: NotSet | dict[str, Any],
405
+ draw: Callable,
406
+ operation: APIOperation,
407
+ context: HookContext,
408
+ hooks: HookDispatcher | None,
413
409
  ) -> Any:
414
410
  # Schemathesis does not generate anything but `body` for GraphQL, hence use `None`
415
411
  container = LOCATION_TO_CONTAINER[location]
416
- strategy = apply_to_all_dispatchers(operation, context, hooks, st.none(), container)
412
+ if isinstance(explicit, NotSet):
413
+ strategy = apply_to_all_dispatchers(operation, context, hooks, st.none(), container)
414
+ else:
415
+ strategy = apply_to_all_dispatchers(operation, context, hooks, st.just(explicit), container)
417
416
  return draw(strategy)
418
417
 
419
418
 
@@ -1,10 +1,12 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import Any, List, cast
2
4
 
3
- from ... import failures
4
- from ...exceptions import get_grouped_graphql_error, get_unexpected_graphql_response_error
5
+ from schemathesis.generation.case import Case
6
+ from schemathesis.graphql.checks import GraphQLClientError, GraphQLServerError, UnexpectedGraphQLResponse
5
7
 
6
8
 
7
- def validate_graphql_response(payload: Any) -> None:
9
+ def validate_graphql_response(case: Case, payload: Any) -> None:
8
10
  """Validate GraphQL response.
9
11
 
10
12
  Semantically valid GraphQL responses are JSON objects and may contain `data` or `errors` keys.
@@ -12,28 +14,20 @@ def validate_graphql_response(payload: Any) -> None:
12
14
  from graphql.error import GraphQLFormattedError
13
15
 
14
16
  if not isinstance(payload, dict):
15
- exc_class = get_unexpected_graphql_response_error(type(payload))
16
- raise exc_class(
17
- failures.UnexpectedGraphQLResponse.title,
18
- context=failures.UnexpectedGraphQLResponse(message="GraphQL response is not a JSON object"),
17
+ raise UnexpectedGraphQLResponse(
18
+ operation=case.operation.label,
19
+ message="GraphQL response is not a JSON object",
20
+ type_name=str(type(payload)),
19
21
  )
20
22
 
21
23
  errors = cast(List[GraphQLFormattedError], payload.get("errors"))
22
24
  if errors is not None and len(errors) > 0:
23
- exc_class = get_grouped_graphql_error(errors)
24
25
  data = payload.get("data")
25
26
  # There is no `path` pointing to some part of the input query, assuming client error
26
27
  if data is None and "path" not in errors[0]:
27
- message = errors[0]["message"]
28
- raise exc_class(
29
- failures.GraphQLClientError.title,
30
- context=failures.GraphQLClientError(message=message, errors=errors),
31
- )
28
+ raise GraphQLClientError(operation=case.operation.label, message=errors[0]["message"], errors=errors)
32
29
  if len(errors) > 1:
33
30
  message = "\n\n".join([f"{idx}. {error['message']}" for idx, error in enumerate(errors, 1)])
34
31
  else:
35
32
  message = errors[0]["message"]
36
- raise exc_class(
37
- failures.GraphQLServerError.title,
38
- context=failures.GraphQLServerError(message=message, errors=errors),
39
- )
33
+ raise GraphQLServerError(operation=case.operation.label, message=message, errors=errors)
@@ -1,4 +1,9 @@
1
1
  from .formats import register_string_format as format
2
2
  from .formats import unregister_string_format
3
- from .loaders import from_aiohttp, from_asgi, from_dict, from_file, from_path, from_pytest_fixture, from_uri, from_wsgi
4
3
  from .media_types import register_media_type as media_type
4
+
5
+ __all__ = [
6
+ "format",
7
+ "unregister_string_format",
8
+ "media_type",
9
+ ]
@@ -4,8 +4,7 @@ from dataclasses import dataclass, field
4
4
  from typing import TYPE_CHECKING, Any, Tuple
5
5
 
6
6
  if TYPE_CHECKING:
7
- from ...models import APIOperation
8
- from ...schemas import APIOperationMap
7
+ from ...schemas import APIOperation, APIOperationMap
9
8
 
10
9
 
11
10
  @dataclass