schemathesis 3.25.6__py3-none-any.whl → 4.0.0a1__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 (221) hide show
  1. schemathesis/__init__.py +27 -65
  2. schemathesis/auths.py +102 -82
  3. schemathesis/checks.py +126 -46
  4. schemathesis/cli/__init__.py +11 -1760
  5. schemathesis/cli/__main__.py +4 -0
  6. schemathesis/cli/commands/__init__.py +37 -0
  7. schemathesis/cli/commands/run/__init__.py +662 -0
  8. schemathesis/cli/commands/run/checks.py +80 -0
  9. schemathesis/cli/commands/run/context.py +117 -0
  10. schemathesis/cli/commands/run/events.py +35 -0
  11. schemathesis/cli/commands/run/executor.py +138 -0
  12. schemathesis/cli/commands/run/filters.py +194 -0
  13. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  14. schemathesis/cli/commands/run/handlers/base.py +18 -0
  15. schemathesis/cli/commands/run/handlers/cassettes.py +494 -0
  16. schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
  17. schemathesis/cli/commands/run/handlers/output.py +746 -0
  18. schemathesis/cli/commands/run/hypothesis.py +105 -0
  19. schemathesis/cli/commands/run/loaders.py +129 -0
  20. schemathesis/cli/{callbacks.py → commands/run/validation.py} +103 -174
  21. schemathesis/cli/constants.py +5 -52
  22. schemathesis/cli/core.py +17 -0
  23. schemathesis/cli/ext/fs.py +14 -0
  24. schemathesis/cli/ext/groups.py +55 -0
  25. schemathesis/cli/{options.py → ext/options.py} +39 -10
  26. schemathesis/cli/hooks.py +36 -0
  27. schemathesis/contrib/__init__.py +1 -3
  28. schemathesis/contrib/openapi/__init__.py +1 -3
  29. schemathesis/contrib/openapi/fill_missing_examples.py +3 -5
  30. schemathesis/core/__init__.py +58 -0
  31. schemathesis/core/compat.py +25 -0
  32. schemathesis/core/control.py +2 -0
  33. schemathesis/core/curl.py +58 -0
  34. schemathesis/core/deserialization.py +65 -0
  35. schemathesis/core/errors.py +370 -0
  36. schemathesis/core/failures.py +285 -0
  37. schemathesis/core/fs.py +19 -0
  38. schemathesis/{_lazy_import.py → core/lazy_import.py} +1 -0
  39. schemathesis/core/loaders.py +104 -0
  40. schemathesis/core/marks.py +66 -0
  41. schemathesis/{transports/content_types.py → core/media_types.py} +17 -13
  42. schemathesis/core/output/__init__.py +69 -0
  43. schemathesis/core/output/sanitization.py +197 -0
  44. schemathesis/core/rate_limit.py +60 -0
  45. schemathesis/core/registries.py +31 -0
  46. schemathesis/{internal → core}/result.py +1 -1
  47. schemathesis/core/transforms.py +113 -0
  48. schemathesis/core/transport.py +108 -0
  49. schemathesis/core/validation.py +38 -0
  50. schemathesis/core/version.py +7 -0
  51. schemathesis/engine/__init__.py +30 -0
  52. schemathesis/engine/config.py +59 -0
  53. schemathesis/engine/context.py +119 -0
  54. schemathesis/engine/control.py +36 -0
  55. schemathesis/engine/core.py +157 -0
  56. schemathesis/engine/errors.py +394 -0
  57. schemathesis/engine/events.py +337 -0
  58. schemathesis/engine/phases/__init__.py +66 -0
  59. schemathesis/{runner → engine/phases}/probes.py +50 -67
  60. schemathesis/engine/phases/stateful/__init__.py +65 -0
  61. schemathesis/engine/phases/stateful/_executor.py +326 -0
  62. schemathesis/engine/phases/stateful/context.py +85 -0
  63. schemathesis/engine/phases/unit/__init__.py +174 -0
  64. schemathesis/engine/phases/unit/_executor.py +321 -0
  65. schemathesis/engine/phases/unit/_pool.py +74 -0
  66. schemathesis/engine/recorder.py +241 -0
  67. schemathesis/errors.py +31 -0
  68. schemathesis/experimental/__init__.py +18 -14
  69. schemathesis/filters.py +103 -14
  70. schemathesis/generation/__init__.py +21 -37
  71. schemathesis/generation/case.py +190 -0
  72. schemathesis/generation/coverage.py +931 -0
  73. schemathesis/generation/hypothesis/__init__.py +30 -0
  74. schemathesis/generation/hypothesis/builder.py +585 -0
  75. schemathesis/generation/hypothesis/examples.py +50 -0
  76. schemathesis/generation/hypothesis/given.py +66 -0
  77. schemathesis/generation/hypothesis/reporting.py +14 -0
  78. schemathesis/generation/hypothesis/strategies.py +16 -0
  79. schemathesis/generation/meta.py +115 -0
  80. schemathesis/generation/modes.py +28 -0
  81. schemathesis/generation/overrides.py +96 -0
  82. schemathesis/generation/stateful/__init__.py +20 -0
  83. schemathesis/{stateful → generation/stateful}/state_machine.py +68 -81
  84. schemathesis/generation/targets.py +69 -0
  85. schemathesis/graphql/__init__.py +15 -0
  86. schemathesis/graphql/checks.py +115 -0
  87. schemathesis/graphql/loaders.py +131 -0
  88. schemathesis/hooks.py +99 -67
  89. schemathesis/openapi/__init__.py +13 -0
  90. schemathesis/openapi/checks.py +412 -0
  91. schemathesis/openapi/generation/__init__.py +0 -0
  92. schemathesis/openapi/generation/filters.py +63 -0
  93. schemathesis/openapi/loaders.py +178 -0
  94. schemathesis/pytest/__init__.py +5 -0
  95. schemathesis/pytest/control_flow.py +7 -0
  96. schemathesis/pytest/lazy.py +273 -0
  97. schemathesis/pytest/loaders.py +12 -0
  98. schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +106 -127
  99. schemathesis/python/__init__.py +0 -0
  100. schemathesis/python/asgi.py +12 -0
  101. schemathesis/python/wsgi.py +12 -0
  102. schemathesis/schemas.py +537 -261
  103. schemathesis/specs/graphql/__init__.py +0 -1
  104. schemathesis/specs/graphql/_cache.py +25 -0
  105. schemathesis/specs/graphql/nodes.py +1 -0
  106. schemathesis/specs/graphql/scalars.py +7 -5
  107. schemathesis/specs/graphql/schemas.py +215 -187
  108. schemathesis/specs/graphql/validation.py +11 -18
  109. schemathesis/specs/openapi/__init__.py +7 -1
  110. schemathesis/specs/openapi/_cache.py +122 -0
  111. schemathesis/specs/openapi/_hypothesis.py +146 -165
  112. schemathesis/specs/openapi/checks.py +565 -67
  113. schemathesis/specs/openapi/converter.py +33 -6
  114. schemathesis/specs/openapi/definitions.py +11 -18
  115. schemathesis/specs/openapi/examples.py +139 -23
  116. schemathesis/specs/openapi/expressions/__init__.py +37 -2
  117. schemathesis/specs/openapi/expressions/context.py +4 -6
  118. schemathesis/specs/openapi/expressions/extractors.py +23 -0
  119. schemathesis/specs/openapi/expressions/lexer.py +20 -18
  120. schemathesis/specs/openapi/expressions/nodes.py +38 -14
  121. schemathesis/specs/openapi/expressions/parser.py +26 -5
  122. schemathesis/specs/openapi/formats.py +45 -0
  123. schemathesis/specs/openapi/links.py +65 -165
  124. schemathesis/specs/openapi/media_types.py +32 -0
  125. schemathesis/specs/openapi/negative/__init__.py +7 -3
  126. schemathesis/specs/openapi/negative/mutations.py +24 -8
  127. schemathesis/specs/openapi/parameters.py +46 -30
  128. schemathesis/specs/openapi/patterns.py +137 -0
  129. schemathesis/specs/openapi/references.py +47 -57
  130. schemathesis/specs/openapi/schemas.py +478 -369
  131. schemathesis/specs/openapi/security.py +25 -7
  132. schemathesis/specs/openapi/serialization.py +11 -6
  133. schemathesis/specs/openapi/stateful/__init__.py +185 -73
  134. schemathesis/specs/openapi/utils.py +6 -1
  135. schemathesis/transport/__init__.py +104 -0
  136. schemathesis/transport/asgi.py +26 -0
  137. schemathesis/transport/prepare.py +99 -0
  138. schemathesis/transport/requests.py +221 -0
  139. schemathesis/{_xml.py → transport/serialization.py} +143 -28
  140. schemathesis/transport/wsgi.py +165 -0
  141. schemathesis-4.0.0a1.dist-info/METADATA +297 -0
  142. schemathesis-4.0.0a1.dist-info/RECORD +152 -0
  143. {schemathesis-3.25.6.dist-info → schemathesis-4.0.0a1.dist-info}/WHEEL +1 -1
  144. {schemathesis-3.25.6.dist-info → schemathesis-4.0.0a1.dist-info}/entry_points.txt +1 -1
  145. schemathesis/_compat.py +0 -74
  146. schemathesis/_dependency_versions.py +0 -17
  147. schemathesis/_hypothesis.py +0 -246
  148. schemathesis/_override.py +0 -49
  149. schemathesis/cli/cassettes.py +0 -375
  150. schemathesis/cli/context.py +0 -58
  151. schemathesis/cli/debug.py +0 -26
  152. schemathesis/cli/handlers.py +0 -16
  153. schemathesis/cli/junitxml.py +0 -43
  154. schemathesis/cli/output/__init__.py +0 -1
  155. schemathesis/cli/output/default.py +0 -790
  156. schemathesis/cli/output/short.py +0 -44
  157. schemathesis/cli/sanitization.py +0 -20
  158. schemathesis/code_samples.py +0 -149
  159. schemathesis/constants.py +0 -55
  160. schemathesis/contrib/openapi/formats/__init__.py +0 -9
  161. schemathesis/contrib/openapi/formats/uuid.py +0 -15
  162. schemathesis/contrib/unique_data.py +0 -41
  163. schemathesis/exceptions.py +0 -560
  164. schemathesis/extra/_aiohttp.py +0 -27
  165. schemathesis/extra/_flask.py +0 -10
  166. schemathesis/extra/_server.py +0 -17
  167. schemathesis/failures.py +0 -209
  168. schemathesis/fixups/__init__.py +0 -36
  169. schemathesis/fixups/fast_api.py +0 -41
  170. schemathesis/fixups/utf8_bom.py +0 -29
  171. schemathesis/graphql.py +0 -4
  172. schemathesis/internal/__init__.py +0 -7
  173. schemathesis/internal/copy.py +0 -13
  174. schemathesis/internal/datetime.py +0 -5
  175. schemathesis/internal/deprecation.py +0 -34
  176. schemathesis/internal/jsonschema.py +0 -35
  177. schemathesis/internal/transformation.py +0 -15
  178. schemathesis/internal/validation.py +0 -34
  179. schemathesis/lazy.py +0 -361
  180. schemathesis/loaders.py +0 -120
  181. schemathesis/models.py +0 -1234
  182. schemathesis/parameters.py +0 -86
  183. schemathesis/runner/__init__.py +0 -570
  184. schemathesis/runner/events.py +0 -329
  185. schemathesis/runner/impl/__init__.py +0 -3
  186. schemathesis/runner/impl/core.py +0 -1035
  187. schemathesis/runner/impl/solo.py +0 -90
  188. schemathesis/runner/impl/threadpool.py +0 -400
  189. schemathesis/runner/serialization.py +0 -411
  190. schemathesis/sanitization.py +0 -248
  191. schemathesis/serializers.py +0 -323
  192. schemathesis/service/__init__.py +0 -18
  193. schemathesis/service/auth.py +0 -11
  194. schemathesis/service/ci.py +0 -201
  195. schemathesis/service/client.py +0 -100
  196. schemathesis/service/constants.py +0 -38
  197. schemathesis/service/events.py +0 -57
  198. schemathesis/service/hosts.py +0 -107
  199. schemathesis/service/metadata.py +0 -46
  200. schemathesis/service/models.py +0 -49
  201. schemathesis/service/report.py +0 -255
  202. schemathesis/service/serialization.py +0 -199
  203. schemathesis/service/usage.py +0 -65
  204. schemathesis/specs/graphql/loaders.py +0 -344
  205. schemathesis/specs/openapi/filters.py +0 -49
  206. schemathesis/specs/openapi/loaders.py +0 -667
  207. schemathesis/specs/openapi/stateful/links.py +0 -92
  208. schemathesis/specs/openapi/validation.py +0 -25
  209. schemathesis/stateful/__init__.py +0 -133
  210. schemathesis/targets.py +0 -45
  211. schemathesis/throttling.py +0 -41
  212. schemathesis/transports/__init__.py +0 -5
  213. schemathesis/transports/auth.py +0 -15
  214. schemathesis/transports/headers.py +0 -35
  215. schemathesis/transports/responses.py +0 -52
  216. schemathesis/types.py +0 -35
  217. schemathesis/utils.py +0 -169
  218. schemathesis-3.25.6.dist-info/METADATA +0 -356
  219. schemathesis-3.25.6.dist-info/RECORD +0 -134
  220. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
  221. {schemathesis-3.25.6.dist-info → schemathesis-4.0.0a1.dist-info}/licenses/LICENSE +0 -0
@@ -1,11 +1,12 @@
1
- from typing import Any, cast, List
1
+ from __future__ import annotations
2
2
 
3
+ from typing import Any, List, cast
3
4
 
4
- from ... import failures
5
- 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
6
7
 
7
8
 
8
- def validate_graphql_response(payload: Any) -> None:
9
+ def validate_graphql_response(case: Case, payload: Any) -> None:
9
10
  """Validate GraphQL response.
10
11
 
11
12
  Semantically valid GraphQL responses are JSON objects and may contain `data` or `errors` keys.
@@ -13,28 +14,20 @@ def validate_graphql_response(payload: Any) -> None:
13
14
  from graphql.error import GraphQLFormattedError
14
15
 
15
16
  if not isinstance(payload, dict):
16
- exc_class = get_unexpected_graphql_response_error(type(payload))
17
- raise exc_class(
18
- failures.UnexpectedGraphQLResponse.title,
19
- 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)),
20
21
  )
21
22
 
22
23
  errors = cast(List[GraphQLFormattedError], payload.get("errors"))
23
24
  if errors is not None and len(errors) > 0:
24
- exc_class = get_grouped_graphql_error(errors)
25
25
  data = payload.get("data")
26
26
  # There is no `path` pointing to some part of the input query, assuming client error
27
27
  if data is None and "path" not in errors[0]:
28
- message = errors[0]["message"]
29
- raise exc_class(
30
- failures.GraphQLClientError.title,
31
- context=failures.GraphQLClientError(message=message, errors=errors),
32
- )
28
+ raise GraphQLClientError(operation=case.operation.label, message=errors[0]["message"], errors=errors)
33
29
  if len(errors) > 1:
34
30
  message = "\n\n".join([f"{idx}. {error['message']}" for idx, error in enumerate(errors, 1)])
35
31
  else:
36
32
  message = errors[0]["message"]
37
- raise exc_class(
38
- failures.GraphQLServerError.title,
39
- context=failures.GraphQLServerError(message=message, errors=errors),
40
- )
33
+ raise GraphQLServerError(operation=case.operation.label, message=message, errors=errors)
@@ -1,3 +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
3
+ from .media_types import register_media_type as media_type
4
+
5
+ __all__ = [
6
+ "format",
7
+ "unregister_string_format",
8
+ "media_type",
9
+ ]
@@ -0,0 +1,122 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING, Any, Tuple
5
+
6
+ if TYPE_CHECKING:
7
+ from ...schemas import APIOperation, APIOperationMap
8
+
9
+
10
+ @dataclass
11
+ class OperationCacheEntry:
12
+ path: str
13
+ method: str
14
+ # The resolution scope of the operation
15
+ scope: str
16
+ # Parent path item
17
+ path_item: dict[str, Any]
18
+ # Unresolved operation definition
19
+ operation: dict[str, Any]
20
+ __slots__ = ("path", "method", "scope", "path_item", "operation")
21
+
22
+
23
+ # During traversal, we need to keep track of the scope, path, and method
24
+ TraversalKey = Tuple[str, str, str]
25
+ OperationId = str
26
+ Reference = str
27
+
28
+
29
+ @dataclass
30
+ class OperationCache:
31
+ """Cache for Open API operations.
32
+
33
+ This cache contains multiple levels to avoid unnecessary parsing of the schema.
34
+ """
35
+
36
+ # Cache to avoid schema traversal on every access
37
+ _id_to_definition: dict[OperationId, OperationCacheEntry] = field(default_factory=dict)
38
+ # Map map between 1st & 2nd level cache keys
39
+ # Even though 1st level keys could be directly mapped to Python objects in memory, we need to keep them separate
40
+ # to ensure a single owner of the operation instance.
41
+ _id_to_operation: dict[OperationId, int] = field(default_factory=dict)
42
+ _traversal_key_to_operation: dict[TraversalKey, int] = field(default_factory=dict)
43
+ _reference_to_operation: dict[Reference, int] = field(default_factory=dict)
44
+ # The actual operations
45
+ _operations: list[APIOperation] = field(default_factory=list)
46
+ # Cache for operation maps
47
+ _maps: dict[str, APIOperationMap] = field(default_factory=dict)
48
+
49
+ @property
50
+ def known_operation_ids(self) -> list[str]:
51
+ return list(self._id_to_definition)
52
+
53
+ @property
54
+ def has_ids_to_definitions(self) -> bool:
55
+ return bool(self._id_to_definition)
56
+
57
+ def _append_operation(self, operation: APIOperation) -> int:
58
+ idx = len(self._operations)
59
+ self._operations.append(operation)
60
+ return idx
61
+
62
+ def insert_definition_by_id(
63
+ self,
64
+ operation_id: str,
65
+ path: str,
66
+ method: str,
67
+ scope: str,
68
+ path_item: dict[str, Any],
69
+ operation: dict[str, Any],
70
+ ) -> None:
71
+ """Insert a new operation definition into cache."""
72
+ self._id_to_definition[operation_id] = OperationCacheEntry(
73
+ path=path, method=method, scope=scope, path_item=path_item, operation=operation
74
+ )
75
+
76
+ def get_definition_by_id(self, operation_id: str) -> OperationCacheEntry:
77
+ """Get an operation definition by its ID."""
78
+ # TODO: Avoid KeyError in the future
79
+ return self._id_to_definition[operation_id]
80
+
81
+ def insert_operation(
82
+ self,
83
+ operation: APIOperation,
84
+ *,
85
+ traversal_key: TraversalKey,
86
+ operation_id: str | None = None,
87
+ reference: str | None = None,
88
+ ) -> None:
89
+ """Insert a new operation into cache by one or multiple keys."""
90
+ idx = self._append_operation(operation)
91
+ self._traversal_key_to_operation[traversal_key] = idx
92
+ if operation_id is not None:
93
+ self._id_to_operation[operation_id] = idx
94
+ if reference is not None:
95
+ self._reference_to_operation[reference] = idx
96
+
97
+ def get_operation_by_id(self, operation_id: str) -> APIOperation | None:
98
+ """Get an operation by its ID."""
99
+ idx = self._id_to_operation.get(operation_id)
100
+ if idx is not None:
101
+ return self._operations[idx]
102
+ return None
103
+
104
+ def get_operation_by_reference(self, reference: str) -> APIOperation | None:
105
+ """Get an operation by its reference."""
106
+ idx = self._reference_to_operation.get(reference)
107
+ if idx is not None:
108
+ return self._operations[idx]
109
+ return None
110
+
111
+ def get_operation_by_traversal_key(self, key: TraversalKey) -> APIOperation | None:
112
+ """Get an operation by its traverse key."""
113
+ idx = self._traversal_key_to_operation.get(key)
114
+ if idx is not None:
115
+ return self._operations[idx]
116
+ return None
117
+
118
+ def get_map(self, key: str) -> APIOperationMap | None:
119
+ return self._maps.get(key)
120
+
121
+ def insert_map(self, key: str, value: APIOperationMap) -> None:
122
+ self._maps[key] = value