schemathesis 3.39.15__py3-none-any.whl → 4.0.0__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 (255) hide show
  1. schemathesis/__init__.py +41 -79
  2. schemathesis/auths.py +111 -122
  3. schemathesis/checks.py +169 -60
  4. schemathesis/cli/__init__.py +15 -2117
  5. schemathesis/cli/commands/__init__.py +85 -0
  6. schemathesis/cli/commands/data.py +10 -0
  7. schemathesis/cli/commands/run/__init__.py +590 -0
  8. schemathesis/cli/commands/run/context.py +204 -0
  9. schemathesis/cli/commands/run/events.py +60 -0
  10. schemathesis/cli/commands/run/executor.py +157 -0
  11. schemathesis/cli/commands/run/filters.py +53 -0
  12. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  13. schemathesis/cli/commands/run/handlers/base.py +18 -0
  14. schemathesis/cli/commands/run/handlers/cassettes.py +474 -0
  15. schemathesis/cli/commands/run/handlers/junitxml.py +55 -0
  16. schemathesis/cli/commands/run/handlers/output.py +1628 -0
  17. schemathesis/cli/commands/run/loaders.py +114 -0
  18. schemathesis/cli/commands/run/validation.py +246 -0
  19. schemathesis/cli/constants.py +5 -58
  20. schemathesis/cli/core.py +19 -0
  21. schemathesis/cli/ext/fs.py +16 -0
  22. schemathesis/cli/ext/groups.py +84 -0
  23. schemathesis/cli/{options.py → ext/options.py} +36 -34
  24. schemathesis/config/__init__.py +189 -0
  25. schemathesis/config/_auth.py +51 -0
  26. schemathesis/config/_checks.py +268 -0
  27. schemathesis/config/_diff_base.py +99 -0
  28. schemathesis/config/_env.py +21 -0
  29. schemathesis/config/_error.py +156 -0
  30. schemathesis/config/_generation.py +149 -0
  31. schemathesis/config/_health_check.py +24 -0
  32. schemathesis/config/_operations.py +327 -0
  33. schemathesis/config/_output.py +171 -0
  34. schemathesis/config/_parameters.py +19 -0
  35. schemathesis/config/_phases.py +187 -0
  36. schemathesis/config/_projects.py +527 -0
  37. schemathesis/config/_rate_limit.py +17 -0
  38. schemathesis/config/_report.py +120 -0
  39. schemathesis/config/_validator.py +9 -0
  40. schemathesis/config/_warnings.py +25 -0
  41. schemathesis/config/schema.json +885 -0
  42. schemathesis/core/__init__.py +67 -0
  43. schemathesis/core/compat.py +32 -0
  44. schemathesis/core/control.py +2 -0
  45. schemathesis/core/curl.py +58 -0
  46. schemathesis/core/deserialization.py +65 -0
  47. schemathesis/core/errors.py +459 -0
  48. schemathesis/core/failures.py +315 -0
  49. schemathesis/core/fs.py +19 -0
  50. schemathesis/core/hooks.py +20 -0
  51. schemathesis/core/loaders.py +104 -0
  52. schemathesis/core/marks.py +66 -0
  53. schemathesis/{transports/content_types.py → core/media_types.py} +14 -12
  54. schemathesis/core/output/__init__.py +46 -0
  55. schemathesis/core/output/sanitization.py +54 -0
  56. schemathesis/{throttling.py → core/rate_limit.py} +16 -17
  57. schemathesis/core/registries.py +31 -0
  58. schemathesis/core/transforms.py +113 -0
  59. schemathesis/core/transport.py +223 -0
  60. schemathesis/core/validation.py +54 -0
  61. schemathesis/core/version.py +7 -0
  62. schemathesis/engine/__init__.py +28 -0
  63. schemathesis/engine/context.py +118 -0
  64. schemathesis/engine/control.py +36 -0
  65. schemathesis/engine/core.py +169 -0
  66. schemathesis/engine/errors.py +464 -0
  67. schemathesis/engine/events.py +258 -0
  68. schemathesis/engine/phases/__init__.py +88 -0
  69. schemathesis/{runner → engine/phases}/probes.py +52 -68
  70. schemathesis/engine/phases/stateful/__init__.py +68 -0
  71. schemathesis/engine/phases/stateful/_executor.py +356 -0
  72. schemathesis/engine/phases/stateful/context.py +85 -0
  73. schemathesis/engine/phases/unit/__init__.py +212 -0
  74. schemathesis/engine/phases/unit/_executor.py +416 -0
  75. schemathesis/engine/phases/unit/_pool.py +82 -0
  76. schemathesis/engine/recorder.py +247 -0
  77. schemathesis/errors.py +43 -0
  78. schemathesis/filters.py +17 -98
  79. schemathesis/generation/__init__.py +5 -33
  80. schemathesis/generation/case.py +317 -0
  81. schemathesis/generation/coverage.py +282 -175
  82. schemathesis/generation/hypothesis/__init__.py +36 -0
  83. schemathesis/generation/hypothesis/builder.py +800 -0
  84. schemathesis/generation/{_hypothesis.py → hypothesis/examples.py} +2 -11
  85. schemathesis/generation/hypothesis/given.py +66 -0
  86. schemathesis/generation/hypothesis/reporting.py +14 -0
  87. schemathesis/generation/hypothesis/strategies.py +16 -0
  88. schemathesis/generation/meta.py +115 -0
  89. schemathesis/generation/metrics.py +93 -0
  90. schemathesis/generation/modes.py +20 -0
  91. schemathesis/generation/overrides.py +116 -0
  92. schemathesis/generation/stateful/__init__.py +37 -0
  93. schemathesis/generation/stateful/state_machine.py +278 -0
  94. schemathesis/graphql/__init__.py +15 -0
  95. schemathesis/graphql/checks.py +109 -0
  96. schemathesis/graphql/loaders.py +284 -0
  97. schemathesis/hooks.py +80 -101
  98. schemathesis/openapi/__init__.py +13 -0
  99. schemathesis/openapi/checks.py +455 -0
  100. schemathesis/openapi/generation/__init__.py +0 -0
  101. schemathesis/openapi/generation/filters.py +72 -0
  102. schemathesis/openapi/loaders.py +313 -0
  103. schemathesis/pytest/__init__.py +5 -0
  104. schemathesis/pytest/control_flow.py +7 -0
  105. schemathesis/pytest/lazy.py +281 -0
  106. schemathesis/pytest/loaders.py +36 -0
  107. schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +128 -108
  108. schemathesis/python/__init__.py +0 -0
  109. schemathesis/python/asgi.py +12 -0
  110. schemathesis/python/wsgi.py +12 -0
  111. schemathesis/schemas.py +537 -273
  112. schemathesis/specs/graphql/__init__.py +0 -1
  113. schemathesis/specs/graphql/_cache.py +1 -2
  114. schemathesis/specs/graphql/scalars.py +42 -6
  115. schemathesis/specs/graphql/schemas.py +141 -137
  116. schemathesis/specs/graphql/validation.py +11 -17
  117. schemathesis/specs/openapi/__init__.py +6 -1
  118. schemathesis/specs/openapi/_cache.py +1 -2
  119. schemathesis/specs/openapi/_hypothesis.py +142 -156
  120. schemathesis/specs/openapi/checks.py +368 -257
  121. schemathesis/specs/openapi/converter.py +4 -4
  122. schemathesis/specs/openapi/definitions.py +1 -1
  123. schemathesis/specs/openapi/examples.py +23 -21
  124. schemathesis/specs/openapi/expressions/__init__.py +31 -19
  125. schemathesis/specs/openapi/expressions/extractors.py +1 -4
  126. schemathesis/specs/openapi/expressions/lexer.py +1 -1
  127. schemathesis/specs/openapi/expressions/nodes.py +36 -41
  128. schemathesis/specs/openapi/expressions/parser.py +1 -1
  129. schemathesis/specs/openapi/formats.py +35 -7
  130. schemathesis/specs/openapi/media_types.py +53 -12
  131. schemathesis/specs/openapi/negative/__init__.py +7 -4
  132. schemathesis/specs/openapi/negative/mutations.py +6 -5
  133. schemathesis/specs/openapi/parameters.py +7 -10
  134. schemathesis/specs/openapi/patterns.py +94 -31
  135. schemathesis/specs/openapi/references.py +12 -53
  136. schemathesis/specs/openapi/schemas.py +238 -308
  137. schemathesis/specs/openapi/security.py +1 -1
  138. schemathesis/specs/openapi/serialization.py +12 -6
  139. schemathesis/specs/openapi/stateful/__init__.py +268 -133
  140. schemathesis/specs/openapi/stateful/control.py +87 -0
  141. schemathesis/specs/openapi/stateful/links.py +209 -0
  142. schemathesis/transport/__init__.py +142 -0
  143. schemathesis/transport/asgi.py +26 -0
  144. schemathesis/transport/prepare.py +124 -0
  145. schemathesis/transport/requests.py +244 -0
  146. schemathesis/{_xml.py → transport/serialization.py} +69 -11
  147. schemathesis/transport/wsgi.py +171 -0
  148. schemathesis-4.0.0.dist-info/METADATA +204 -0
  149. schemathesis-4.0.0.dist-info/RECORD +164 -0
  150. {schemathesis-3.39.15.dist-info → schemathesis-4.0.0.dist-info}/entry_points.txt +1 -1
  151. {schemathesis-3.39.15.dist-info → schemathesis-4.0.0.dist-info}/licenses/LICENSE +1 -1
  152. schemathesis/_compat.py +0 -74
  153. schemathesis/_dependency_versions.py +0 -19
  154. schemathesis/_hypothesis.py +0 -712
  155. schemathesis/_override.py +0 -50
  156. schemathesis/_patches.py +0 -21
  157. schemathesis/_rate_limiter.py +0 -7
  158. schemathesis/cli/callbacks.py +0 -466
  159. schemathesis/cli/cassettes.py +0 -561
  160. schemathesis/cli/context.py +0 -75
  161. schemathesis/cli/debug.py +0 -27
  162. schemathesis/cli/handlers.py +0 -19
  163. schemathesis/cli/junitxml.py +0 -124
  164. schemathesis/cli/output/__init__.py +0 -1
  165. schemathesis/cli/output/default.py +0 -920
  166. schemathesis/cli/output/short.py +0 -59
  167. schemathesis/cli/reporting.py +0 -79
  168. schemathesis/cli/sanitization.py +0 -26
  169. schemathesis/code_samples.py +0 -151
  170. schemathesis/constants.py +0 -54
  171. schemathesis/contrib/__init__.py +0 -11
  172. schemathesis/contrib/openapi/__init__.py +0 -11
  173. schemathesis/contrib/openapi/fill_missing_examples.py +0 -24
  174. schemathesis/contrib/openapi/formats/__init__.py +0 -9
  175. schemathesis/contrib/openapi/formats/uuid.py +0 -16
  176. schemathesis/contrib/unique_data.py +0 -41
  177. schemathesis/exceptions.py +0 -571
  178. schemathesis/experimental/__init__.py +0 -109
  179. schemathesis/extra/_aiohttp.py +0 -28
  180. schemathesis/extra/_flask.py +0 -13
  181. schemathesis/extra/_server.py +0 -18
  182. schemathesis/failures.py +0 -284
  183. schemathesis/fixups/__init__.py +0 -37
  184. schemathesis/fixups/fast_api.py +0 -41
  185. schemathesis/fixups/utf8_bom.py +0 -28
  186. schemathesis/generation/_methods.py +0 -44
  187. schemathesis/graphql.py +0 -3
  188. schemathesis/internal/__init__.py +0 -7
  189. schemathesis/internal/checks.py +0 -86
  190. schemathesis/internal/copy.py +0 -32
  191. schemathesis/internal/datetime.py +0 -5
  192. schemathesis/internal/deprecation.py +0 -37
  193. schemathesis/internal/diff.py +0 -15
  194. schemathesis/internal/extensions.py +0 -27
  195. schemathesis/internal/jsonschema.py +0 -36
  196. schemathesis/internal/output.py +0 -68
  197. schemathesis/internal/transformation.py +0 -26
  198. schemathesis/internal/validation.py +0 -34
  199. schemathesis/lazy.py +0 -474
  200. schemathesis/loaders.py +0 -122
  201. schemathesis/models.py +0 -1341
  202. schemathesis/parameters.py +0 -90
  203. schemathesis/runner/__init__.py +0 -605
  204. schemathesis/runner/events.py +0 -389
  205. schemathesis/runner/impl/__init__.py +0 -3
  206. schemathesis/runner/impl/context.py +0 -88
  207. schemathesis/runner/impl/core.py +0 -1280
  208. schemathesis/runner/impl/solo.py +0 -80
  209. schemathesis/runner/impl/threadpool.py +0 -391
  210. schemathesis/runner/serialization.py +0 -544
  211. schemathesis/sanitization.py +0 -252
  212. schemathesis/serializers.py +0 -328
  213. schemathesis/service/__init__.py +0 -18
  214. schemathesis/service/auth.py +0 -11
  215. schemathesis/service/ci.py +0 -202
  216. schemathesis/service/client.py +0 -133
  217. schemathesis/service/constants.py +0 -38
  218. schemathesis/service/events.py +0 -61
  219. schemathesis/service/extensions.py +0 -224
  220. schemathesis/service/hosts.py +0 -111
  221. schemathesis/service/metadata.py +0 -71
  222. schemathesis/service/models.py +0 -258
  223. schemathesis/service/report.py +0 -255
  224. schemathesis/service/serialization.py +0 -173
  225. schemathesis/service/usage.py +0 -66
  226. schemathesis/specs/graphql/loaders.py +0 -364
  227. schemathesis/specs/openapi/expressions/context.py +0 -16
  228. schemathesis/specs/openapi/links.py +0 -389
  229. schemathesis/specs/openapi/loaders.py +0 -707
  230. schemathesis/specs/openapi/stateful/statistic.py +0 -198
  231. schemathesis/specs/openapi/stateful/types.py +0 -14
  232. schemathesis/specs/openapi/validation.py +0 -26
  233. schemathesis/stateful/__init__.py +0 -147
  234. schemathesis/stateful/config.py +0 -97
  235. schemathesis/stateful/context.py +0 -135
  236. schemathesis/stateful/events.py +0 -274
  237. schemathesis/stateful/runner.py +0 -309
  238. schemathesis/stateful/sink.py +0 -68
  239. schemathesis/stateful/state_machine.py +0 -328
  240. schemathesis/stateful/statistic.py +0 -22
  241. schemathesis/stateful/validation.py +0 -100
  242. schemathesis/targets.py +0 -77
  243. schemathesis/transports/__init__.py +0 -369
  244. schemathesis/transports/asgi.py +0 -7
  245. schemathesis/transports/auth.py +0 -38
  246. schemathesis/transports/headers.py +0 -36
  247. schemathesis/transports/responses.py +0 -57
  248. schemathesis/types.py +0 -44
  249. schemathesis/utils.py +0 -164
  250. schemathesis-3.39.15.dist-info/METADATA +0 -293
  251. schemathesis-3.39.15.dist-info/RECORD +0 -160
  252. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
  253. /schemathesis/{_lazy_import.py → core/lazy_import.py} +0 -0
  254. /schemathesis/{internal → core}/result.py +0 -0
  255. {schemathesis-3.39.15.dist-info → schemathesis-4.0.0.dist-info}/WHEEL +0 -0
@@ -1,173 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import asdict
4
- from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, TypeVar, cast
5
-
6
- from ..internal.transformation import merge_recursively
7
- from ..runner import events
8
- from ..runner.serialization import _serialize_check
9
-
10
- if TYPE_CHECKING:
11
- from ..stateful import events as stateful_events
12
-
13
- S = TypeVar("S", bound=events.ExecutionEvent)
14
- SerializeFunc = Callable[[S], Optional[Dict[str, Any]]]
15
-
16
-
17
- def serialize_initialized(event: events.Initialized) -> dict[str, Any] | None:
18
- return {
19
- "operations_count": event.operations_count,
20
- "location": event.location or "",
21
- "base_url": event.base_url,
22
- }
23
-
24
-
25
- def serialize_before_probing(_: events.BeforeProbing) -> None:
26
- return None
27
-
28
-
29
- def serialize_after_probing(event: events.AfterProbing) -> dict[str, Any] | None:
30
- probes = event.probes or []
31
- return {"probes": [probe.serialize() for probe in probes]}
32
-
33
-
34
- def serialize_before_analysis(_: events.BeforeAnalysis) -> None:
35
- return None
36
-
37
-
38
- def serialize_after_analysis(event: events.AfterAnalysis) -> dict[str, Any] | None:
39
- return event._serialize()
40
-
41
-
42
- def serialize_before_execution(event: events.BeforeExecution) -> dict[str, Any] | None:
43
- return {
44
- "correlation_id": event.correlation_id,
45
- "verbose_name": event.verbose_name,
46
- "data_generation_method": event.data_generation_method,
47
- }
48
-
49
-
50
- def serialize_after_execution(event: events.AfterExecution) -> dict[str, Any] | None:
51
- return {
52
- "correlation_id": event.correlation_id,
53
- "verbose_name": event.verbose_name,
54
- "status": event.status,
55
- "elapsed_time": event.elapsed_time,
56
- "data_generation_method": event.data_generation_method,
57
- "result": {
58
- "checks": [_serialize_check(check) for check in event.result.checks],
59
- "errors": [asdict(error) for error in event.result.errors],
60
- "skip_reason": event.result.skip_reason,
61
- },
62
- }
63
-
64
-
65
- def serialize_interrupted(_: events.Interrupted) -> dict[str, Any] | None:
66
- return None
67
-
68
-
69
- def serialize_internal_error(event: events.InternalError) -> dict[str, Any] | None:
70
- return {
71
- "type": event.type.value,
72
- "subtype": event.subtype.value if event.subtype else event.subtype,
73
- "title": event.title,
74
- "message": event.message,
75
- "extras": event.extras,
76
- "exception_type": event.exception_type,
77
- "exception": event.exception,
78
- "exception_with_traceback": event.exception_with_traceback,
79
- }
80
-
81
-
82
- def serialize_finished(event: events.Finished) -> dict[str, Any] | None:
83
- return {
84
- "generic_errors": [
85
- {
86
- "exception": error.exception,
87
- "exception_with_traceback": error.exception_with_traceback,
88
- "title": error.title,
89
- }
90
- for error in event.generic_errors
91
- ],
92
- "running_time": event.running_time,
93
- }
94
-
95
-
96
- def serialize_stateful_event(event: events.StatefulEvent) -> dict[str, Any] | None:
97
- return _serialize_stateful_event(event.data)
98
-
99
-
100
- def _serialize_stateful_event(event: stateful_events.StatefulEvent) -> dict[str, Any] | None:
101
- return {"data": {event.__class__.__name__: event.asdict()}}
102
-
103
-
104
- def serialize_after_stateful_execution(event: events.AfterStatefulExecution) -> dict[str, Any] | None:
105
- return {
106
- "status": event.status,
107
- "data_generation_method": event.data_generation_method,
108
- "result": asdict(event.result),
109
- "elapsed_time": event.elapsed_time,
110
- }
111
-
112
-
113
- SERIALIZER_MAP = {
114
- events.Initialized: serialize_initialized,
115
- events.BeforeProbing: serialize_before_probing,
116
- events.AfterProbing: serialize_after_probing,
117
- events.BeforeAnalysis: serialize_before_analysis,
118
- events.AfterAnalysis: serialize_after_analysis,
119
- events.BeforeExecution: serialize_before_execution,
120
- events.AfterExecution: serialize_after_execution,
121
- events.Interrupted: serialize_interrupted,
122
- events.InternalError: serialize_internal_error,
123
- events.StatefulEvent: serialize_stateful_event,
124
- events.AfterStatefulExecution: serialize_after_stateful_execution,
125
- events.Finished: serialize_finished,
126
- }
127
-
128
-
129
- def serialize_event(
130
- event: events.ExecutionEvent,
131
- *,
132
- on_initialized: SerializeFunc | None = None,
133
- on_before_probing: SerializeFunc | None = None,
134
- on_after_probing: SerializeFunc | None = None,
135
- on_before_analysis: SerializeFunc | None = None,
136
- on_after_analysis: SerializeFunc | None = None,
137
- on_before_execution: SerializeFunc | None = None,
138
- on_after_execution: SerializeFunc | None = None,
139
- on_interrupted: SerializeFunc | None = None,
140
- on_internal_error: SerializeFunc | None = None,
141
- on_stateful_event: SerializeFunc | None = None,
142
- on_after_stateful_execution: SerializeFunc | None = None,
143
- on_finished: SerializeFunc | None = None,
144
- extra: dict[str, Any] | None = None,
145
- ) -> dict[str, dict[str, Any] | None]:
146
- """Turn an event into JSON-serializable structure."""
147
- # Use the explicitly provided serializer for this event and fallback to default one if it is not provided
148
- serializer = {
149
- events.Initialized: on_initialized,
150
- events.BeforeProbing: on_before_probing,
151
- events.AfterProbing: on_after_probing,
152
- events.BeforeAnalysis: on_before_analysis,
153
- events.AfterAnalysis: on_after_analysis,
154
- events.BeforeExecution: on_before_execution,
155
- events.AfterExecution: on_after_execution,
156
- events.Interrupted: on_interrupted,
157
- events.InternalError: on_internal_error,
158
- events.StatefulEvent: on_stateful_event,
159
- events.AfterStatefulExecution: on_after_stateful_execution,
160
- events.Finished: on_finished,
161
- }.get(event.__class__)
162
- if serializer is None:
163
- serializer = cast(SerializeFunc, SERIALIZER_MAP[event.__class__])
164
- data = serializer(event)
165
- if extra is not None:
166
- # If `extra` is present, then merge it with the serialized data. If serialized data is empty, then replace it
167
- # with `extra` value
168
- if data is None:
169
- data = extra
170
- else:
171
- data = merge_recursively(data, extra)
172
- # Externally tagged structure
173
- return {event.__class__.__name__: data}
@@ -1,66 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import sys
4
- from typing import Any
5
-
6
- import click
7
- from click.types import StringParamType
8
-
9
- from .. import cli, hooks
10
-
11
-
12
- def collect(args: list[str] | None = None) -> dict[str, Any] | None:
13
- """Collect anonymized CLI usage data."""
14
- context: click.Context | None = click.get_current_context(silent=True)
15
- if context is not None and not sys.argv[0].endswith("pytest"):
16
- args = args or sys.argv[2:]
17
- parameters, _, types = parse_cli_args(context, args)
18
- parameters_data: dict[str, dict[str, Any]] = {}
19
- used_headers: list[str] = []
20
- schema = parameters["schema"]
21
- app = parameters.get("app")
22
- if not schema:
23
- schema_kind = None
24
- else:
25
- schema_kind = cli.callbacks.parse_schema_kind(schema, app).name
26
- usage = {
27
- "schema_kind": schema_kind,
28
- "parameters": parameters_data,
29
- "used_headers": used_headers,
30
- "hooks": hooks.collect_statistic(),
31
- }
32
- types_iter = iter(types)
33
- for option, value in parameters.items():
34
- option_type = next(types_iter)
35
- if isinstance(option_type, click.Argument):
36
- continue
37
- if option_type.multiple:
38
- # Forward the iterator to the next option type
39
- for _ in range(len(value) - 1):
40
- next(types_iter)
41
- entry = _collect_option(option, option_type, used_headers, value)
42
- if entry:
43
- parameters_data[option] = entry
44
- return usage
45
- return None
46
-
47
-
48
- def _collect_option(option: str, option_type: click.Parameter, used_headers: list[str], value: Any) -> dict[str, Any]:
49
- entry = {}
50
- if isinstance(option_type.type, (StringParamType, click.types.File)):
51
- if option == "headers" and value:
52
- used_headers.extend(header.split(":", 1)[0] for header in value)
53
- else:
54
- # Free-form values are replaced with their number of occurrences, to avoid sending sensitive info
55
- if option_type.multiple:
56
- entry["count"] = len(value)
57
- else:
58
- entry["count"] = 1
59
- else:
60
- entry["value"] = value
61
- return entry
62
-
63
-
64
- def parse_cli_args(context: click.Context, args: list[str]) -> tuple[dict[str, Any], list, list[click.Parameter]]:
65
- parser = cli.run.make_parser(context)
66
- return parser.parse_args(args=args)
@@ -1,364 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import pathlib
5
- from functools import lru_cache
6
- from json import JSONDecodeError
7
- from typing import IO, TYPE_CHECKING, Any, Callable, Dict, NoReturn, cast
8
-
9
- from ...code_samples import CodeSampleStyle
10
- from ...constants import DEFAULT_RESPONSE_TIMEOUT, WAIT_FOR_SCHEMA_INTERVAL
11
- from ...exceptions import SchemaError, SchemaErrorType
12
- from ...generation import (
13
- DEFAULT_DATA_GENERATION_METHODS,
14
- DataGenerationMethod,
15
- DataGenerationMethodInput,
16
- GenerationConfig,
17
- )
18
- from ...hooks import HookContext, dispatch
19
- from ...internal.output import OutputConfig
20
- from ...internal.validation import require_relative_url
21
- from ...loaders import load_schema_from_url
22
- from ...throttling import build_limiter
23
- from ...transports.headers import setup_default_headers
24
- from ...types import PathLike, Specification
25
-
26
- if TYPE_CHECKING:
27
- from graphql import DocumentNode
28
- from pyrate_limiter import Limiter
29
-
30
- from ...transports.responses import GenericResponse
31
- from .schemas import GraphQLSchema
32
-
33
-
34
- @lru_cache
35
- def get_introspection_query() -> str:
36
- import graphql
37
-
38
- return graphql.get_introspection_query()
39
-
40
-
41
- @lru_cache
42
- def get_introspection_query_ast() -> DocumentNode:
43
- import graphql
44
-
45
- query = get_introspection_query()
46
- return graphql.parse(query)
47
-
48
-
49
- def from_path(
50
- path: PathLike,
51
- *,
52
- app: Any = None,
53
- base_url: str | None = None,
54
- data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
55
- generation_config: GenerationConfig | None = None,
56
- output_config: OutputConfig | None = None,
57
- code_sample_style: str = CodeSampleStyle.default().name,
58
- rate_limit: str | None = None,
59
- encoding: str = "utf8",
60
- sanitize_output: bool = True,
61
- ) -> GraphQLSchema:
62
- """Load GraphQL schema via a file from an OS path.
63
-
64
- :param path: A path to the schema file.
65
- :param encoding: The name of the encoding used to decode the file.
66
- """
67
- with open(path, encoding=encoding) as fd:
68
- return from_file(
69
- fd,
70
- app=app,
71
- base_url=base_url,
72
- data_generation_methods=data_generation_methods,
73
- code_sample_style=code_sample_style,
74
- generation_config=generation_config,
75
- output_config=output_config,
76
- location=pathlib.Path(path).absolute().as_uri(),
77
- rate_limit=rate_limit,
78
- sanitize_output=sanitize_output,
79
- )
80
-
81
-
82
- def extract_schema_from_response(response: GenericResponse) -> dict[str, Any]:
83
- from requests import Response
84
-
85
- try:
86
- if isinstance(response, Response):
87
- decoded = response.json()
88
- else:
89
- decoded = response.json
90
- except JSONDecodeError as exc:
91
- raise SchemaError(
92
- SchemaErrorType.UNEXPECTED_CONTENT_TYPE,
93
- "Received unsupported content while expecting a JSON payload for GraphQL",
94
- ) from exc
95
- return decoded
96
-
97
-
98
- def from_url(
99
- url: str,
100
- *,
101
- app: Any = None,
102
- base_url: str | None = None,
103
- port: int | None = None,
104
- data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
105
- generation_config: GenerationConfig | None = None,
106
- code_sample_style: str = CodeSampleStyle.default().name,
107
- wait_for_schema: float | None = None,
108
- rate_limit: str | None = None,
109
- sanitize_output: bool = True,
110
- **kwargs: Any,
111
- ) -> GraphQLSchema:
112
- """Load GraphQL schema from the network.
113
-
114
- :param url: Schema URL.
115
- :param Optional[str] base_url: Base URL to send requests to.
116
- :param Optional[int] port: An optional port if you don't want to pass the ``base_url`` parameter, but only to change
117
- port in ``url``.
118
- :param app: A WSGI app instance.
119
- :return: GraphQLSchema
120
- """
121
- import backoff
122
- import requests
123
-
124
- setup_default_headers(kwargs)
125
- kwargs.setdefault("json", {"query": get_introspection_query()})
126
- if port:
127
- from yarl import URL
128
-
129
- url = str(URL(url).with_port(port))
130
- if not base_url:
131
- base_url = url
132
-
133
- if wait_for_schema is not None:
134
-
135
- @backoff.on_exception( # type: ignore
136
- backoff.constant,
137
- requests.exceptions.ConnectionError,
138
- max_time=wait_for_schema,
139
- interval=WAIT_FOR_SCHEMA_INTERVAL,
140
- )
141
- def _load_schema(_uri: str, **_kwargs: Any) -> requests.Response:
142
- return requests.post(_uri, **_kwargs)
143
-
144
- else:
145
- _load_schema = requests.post
146
-
147
- kwargs.setdefault("timeout", DEFAULT_RESPONSE_TIMEOUT / 1000)
148
- response = load_schema_from_url(lambda: _load_schema(url, **kwargs))
149
- raw_schema = extract_schema_from_response(response)
150
- return from_dict(
151
- raw_schema=raw_schema,
152
- location=url,
153
- base_url=base_url,
154
- app=app,
155
- data_generation_methods=data_generation_methods,
156
- code_sample_style=code_sample_style,
157
- rate_limit=rate_limit,
158
- sanitize_output=sanitize_output,
159
- )
160
-
161
-
162
- def from_file(
163
- file: IO[str] | str,
164
- *,
165
- app: Any = None,
166
- base_url: str | None = None,
167
- data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
168
- generation_config: GenerationConfig | None = None,
169
- output_config: OutputConfig | None = None,
170
- code_sample_style: str = CodeSampleStyle.default().name,
171
- location: str | None = None,
172
- rate_limit: str | None = None,
173
- sanitize_output: bool = True,
174
- ) -> GraphQLSchema:
175
- """Load GraphQL schema from a file descriptor or a string.
176
-
177
- :param file: Could be a file descriptor, string or bytes.
178
- """
179
- import graphql
180
-
181
- if isinstance(file, str):
182
- data = file
183
- else:
184
- data = file.read()
185
- try:
186
- document = graphql.build_schema(data)
187
- result = graphql.execute(document, get_introspection_query_ast())
188
- # TYPES: We don't pass `is_awaitable` above, therefore `result` is of the `ExecutionResult` type
189
- result = cast(graphql.ExecutionResult, result)
190
- # TYPES:
191
- # - `document` is a valid schema, because otherwise `build_schema` will rise an error;
192
- # - `INTROSPECTION_QUERY` is a valid query - it is known upfront;
193
- # Therefore the execution result is always valid at this point and `result.data` is not `None`
194
- raw_schema = cast(Dict[str, Any], result.data)
195
- except Exception as exc:
196
- try:
197
- raw_schema = json.loads(data)
198
- if not isinstance(raw_schema, dict) or "__schema" not in raw_schema:
199
- _on_invalid_schema(exc)
200
- except json.JSONDecodeError:
201
- _on_invalid_schema(exc, extras=[entry for entry in str(exc).splitlines() if entry])
202
- return from_dict(
203
- raw_schema,
204
- app=app,
205
- base_url=base_url,
206
- data_generation_methods=data_generation_methods,
207
- generation_config=generation_config,
208
- output_config=output_config,
209
- code_sample_style=code_sample_style,
210
- location=location,
211
- rate_limit=rate_limit,
212
- sanitize_output=sanitize_output,
213
- )
214
-
215
-
216
- def _on_invalid_schema(exc: Exception, extras: list[str] | None = None) -> NoReturn:
217
- raise SchemaError(
218
- SchemaErrorType.GRAPHQL_INVALID_SCHEMA,
219
- "The provided API schema does not appear to be a valid GraphQL schema",
220
- extras=extras or [],
221
- ) from exc
222
-
223
-
224
- def from_dict(
225
- raw_schema: dict[str, Any],
226
- *,
227
- app: Any = None,
228
- base_url: str | None = None,
229
- location: str | None = None,
230
- data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
231
- generation_config: GenerationConfig | None = None,
232
- output_config: OutputConfig | None = None,
233
- code_sample_style: str = CodeSampleStyle.default().name,
234
- rate_limit: str | None = None,
235
- sanitize_output: bool = True,
236
- ) -> GraphQLSchema:
237
- """Load GraphQL schema from a Python dictionary.
238
-
239
- :param dict raw_schema: A schema to load.
240
- :param Optional[str] location: Optional schema location. Either a full URL or a filesystem path.
241
- :param Optional[str] base_url: Base URL to send requests to.
242
- :param app: A WSGI app instance.
243
- :return: GraphQLSchema
244
- """
245
- from ... import transports
246
- from .schemas import GraphQLSchema
247
-
248
- _code_sample_style = CodeSampleStyle.from_str(code_sample_style)
249
- hook_context = HookContext()
250
- if "data" in raw_schema:
251
- raw_schema = raw_schema["data"]
252
- dispatch("before_load_schema", hook_context, raw_schema)
253
- rate_limiter: Limiter | None = None
254
- if rate_limit is not None:
255
- rate_limiter = build_limiter(rate_limit)
256
- instance = GraphQLSchema(
257
- raw_schema,
258
- specification=Specification.GRAPHQL,
259
- location=location,
260
- base_url=base_url,
261
- app=app,
262
- data_generation_methods=DataGenerationMethod.ensure_list(data_generation_methods),
263
- generation_config=generation_config or GenerationConfig(),
264
- output_config=output_config or OutputConfig(),
265
- code_sample_style=_code_sample_style,
266
- rate_limiter=rate_limiter,
267
- sanitize_output=sanitize_output,
268
- transport=transports.get(app),
269
- ) # type: ignore
270
- dispatch("after_load_schema", hook_context, instance)
271
- return instance
272
-
273
-
274
- def from_wsgi(
275
- schema_path: str,
276
- app: Any,
277
- *,
278
- base_url: str | None = None,
279
- data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
280
- generation_config: GenerationConfig | None = None,
281
- output_config: OutputConfig | None = None,
282
- code_sample_style: str = CodeSampleStyle.default().name,
283
- rate_limit: str | None = None,
284
- sanitize_output: bool = True,
285
- **kwargs: Any,
286
- ) -> GraphQLSchema:
287
- """Load GraphQL schema from a WSGI app.
288
-
289
- :param str schema_path: An in-app relative URL to the schema.
290
- :param app: A WSGI app instance.
291
- :param Optional[str] base_url: Base URL to send requests to.
292
- :return: GraphQLSchema
293
- """
294
- from werkzeug import Client
295
-
296
- from ...transports.responses import WSGIResponse
297
-
298
- require_relative_url(schema_path)
299
- setup_default_headers(kwargs)
300
- kwargs.setdefault("json", {"query": get_introspection_query()})
301
- client = Client(app, WSGIResponse)
302
- response = load_schema_from_url(lambda: client.post(schema_path, **kwargs))
303
- raw_schema = extract_schema_from_response(response)
304
- return from_dict(
305
- raw_schema=raw_schema,
306
- location=schema_path,
307
- base_url=base_url,
308
- app=app,
309
- data_generation_methods=data_generation_methods,
310
- generation_config=generation_config,
311
- output_config=output_config,
312
- code_sample_style=code_sample_style,
313
- rate_limit=rate_limit,
314
- sanitize_output=sanitize_output,
315
- )
316
-
317
-
318
- def from_asgi(
319
- schema_path: str,
320
- app: Any,
321
- *,
322
- base_url: str | None = None,
323
- data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
324
- generation_config: GenerationConfig | None = None,
325
- output_config: OutputConfig | None = None,
326
- code_sample_style: str = CodeSampleStyle.default().name,
327
- rate_limit: str | None = None,
328
- sanitize_output: bool = True,
329
- **kwargs: Any,
330
- ) -> GraphQLSchema:
331
- """Load GraphQL schema from an ASGI app.
332
-
333
- :param str schema_path: An in-app relative URL to the schema.
334
- :param app: An ASGI app instance.
335
- :param Optional[str] base_url: Base URL to send requests to.
336
- """
337
- from starlette_testclient import TestClient as ASGIClient
338
-
339
- require_relative_url(schema_path)
340
- setup_default_headers(kwargs)
341
- kwargs.setdefault("json", {"query": get_introspection_query()})
342
- client = ASGIClient(app)
343
- response = load_schema_from_url(lambda: client.post(schema_path, **kwargs))
344
- raw_schema = extract_schema_from_response(response)
345
- return from_dict(
346
- raw_schema=raw_schema,
347
- location=schema_path,
348
- base_url=base_url,
349
- app=app,
350
- data_generation_methods=data_generation_methods,
351
- generation_config=generation_config,
352
- output_config=output_config,
353
- code_sample_style=code_sample_style,
354
- rate_limit=rate_limit,
355
- sanitize_output=sanitize_output,
356
- )
357
-
358
-
359
- def get_loader_for_app(app: Any) -> Callable:
360
- from ...transports.asgi import is_asgi_app
361
-
362
- if is_asgi_app(app):
363
- return from_asgi
364
- return from_wsgi
@@ -1,16 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from typing import TYPE_CHECKING
5
-
6
- if TYPE_CHECKING:
7
- from ....models import Case
8
- from ....transports.responses import GenericResponse
9
-
10
-
11
- @dataclass
12
- class ExpressionContext:
13
- """Context in what an expression are evaluated."""
14
-
15
- response: GenericResponse
16
- case: Case