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,707 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import io
4
- import json
5
- import pathlib
6
- import re
7
- from typing import IO, TYPE_CHECKING, Any, Callable, cast
8
- from urllib.parse import urljoin
9
-
10
- from ... import experimental, fixups
11
- from ...code_samples import CodeSampleStyle
12
- from ...constants import DEFAULT_RESPONSE_TIMEOUT, NOT_SET, WAIT_FOR_SCHEMA_INTERVAL
13
- from ...exceptions import SchemaError, SchemaErrorType
14
- from ...filters import filter_set_from_components
15
- from ...generation import (
16
- DEFAULT_DATA_GENERATION_METHODS,
17
- DataGenerationMethod,
18
- DataGenerationMethodInput,
19
- GenerationConfig,
20
- )
21
- from ...hooks import HookContext, dispatch
22
- from ...internal.deprecation import warn_filtration_arguments
23
- from ...internal.output import OutputConfig
24
- from ...internal.validation import require_relative_url
25
- from ...loaders import load_schema_from_url, load_yaml
26
- from ...throttling import build_limiter
27
- from ...transports.content_types import is_json_media_type, is_yaml_media_type
28
- from ...transports.headers import setup_default_headers
29
- from ...types import Filter, NotSet, PathLike, Specification
30
- from . import definitions, validation
31
-
32
- if TYPE_CHECKING:
33
- import jsonschema
34
- from pyrate_limiter import Limiter
35
-
36
- from ...lazy import LazySchema
37
- from ...transports.responses import GenericResponse
38
- from .schemas import BaseOpenAPISchema
39
-
40
-
41
- def _is_json_response(response: GenericResponse) -> bool:
42
- """Guess if the response contains JSON."""
43
- content_type = response.headers.get("Content-Type")
44
- if content_type is not None:
45
- return is_json_media_type(content_type)
46
- return False
47
-
48
-
49
- def _has_suffix(path: PathLike, suffix: str) -> bool:
50
- if isinstance(path, str):
51
- return path.endswith(suffix)
52
- return path.suffix == suffix
53
-
54
-
55
- def _is_json_path(path: PathLike) -> bool:
56
- return _has_suffix(path, ".json")
57
-
58
-
59
- def _is_yaml_response(response: GenericResponse) -> bool:
60
- """Guess if the response contains YAML."""
61
- content_type = response.headers.get("Content-Type")
62
- if content_type is not None:
63
- return is_yaml_media_type(content_type)
64
- return False
65
-
66
-
67
- def _is_yaml_path(path: PathLike) -> bool:
68
- return _has_suffix(path, ".yaml") or _has_suffix(path, ".yml")
69
-
70
-
71
- def from_path(
72
- path: PathLike,
73
- *,
74
- app: Any = None,
75
- base_url: str | None = None,
76
- method: Filter | None = None,
77
- endpoint: Filter | None = None,
78
- tag: Filter | None = None,
79
- operation_id: Filter | None = None,
80
- skip_deprecated_operations: bool | None = None,
81
- validate_schema: bool = False,
82
- force_schema_version: str | None = None,
83
- data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
84
- generation_config: GenerationConfig | None = None,
85
- output_config: OutputConfig | None = None,
86
- code_sample_style: str = CodeSampleStyle.default().name,
87
- rate_limit: str | None = None,
88
- encoding: str = "utf8",
89
- sanitize_output: bool = True,
90
- ) -> BaseOpenAPISchema:
91
- """Load Open API schema via a file from an OS path.
92
-
93
- :param path: A path to the schema file.
94
- :param encoding: The name of the encoding used to decode the file.
95
- """
96
- with open(path, encoding=encoding) as fd:
97
- return from_file(
98
- fd,
99
- app=app,
100
- base_url=base_url,
101
- method=method,
102
- endpoint=endpoint,
103
- tag=tag,
104
- operation_id=operation_id,
105
- skip_deprecated_operations=skip_deprecated_operations,
106
- validate_schema=validate_schema,
107
- force_schema_version=force_schema_version,
108
- data_generation_methods=data_generation_methods,
109
- generation_config=generation_config,
110
- output_config=output_config,
111
- code_sample_style=code_sample_style,
112
- location=pathlib.Path(path).absolute().as_uri(),
113
- rate_limit=rate_limit,
114
- sanitize_output=sanitize_output,
115
- __expects_json=_is_json_path(path),
116
- __expects_yaml=_is_yaml_path(path),
117
- )
118
-
119
-
120
- def from_uri(
121
- uri: str,
122
- *,
123
- app: Any = None,
124
- base_url: str | None = None,
125
- port: int | None = None,
126
- method: Filter | None = None,
127
- endpoint: Filter | None = None,
128
- tag: Filter | None = None,
129
- operation_id: Filter | None = None,
130
- skip_deprecated_operations: bool | None = None,
131
- validate_schema: bool = False,
132
- force_schema_version: str | None = None,
133
- data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
134
- generation_config: GenerationConfig | None = None,
135
- output_config: OutputConfig | None = None,
136
- code_sample_style: str = CodeSampleStyle.default().name,
137
- wait_for_schema: float | None = None,
138
- rate_limit: str | None = None,
139
- sanitize_output: bool = True,
140
- **kwargs: Any,
141
- ) -> BaseOpenAPISchema:
142
- """Load Open API schema from the network.
143
-
144
- :param str uri: Schema URL.
145
- """
146
- import backoff
147
- import requests
148
-
149
- setup_default_headers(kwargs)
150
- if port:
151
- from yarl import URL
152
-
153
- uri = str(URL(uri).with_port(port))
154
- if not base_url:
155
- base_url = uri
156
-
157
- if wait_for_schema is not None:
158
-
159
- @backoff.on_exception( # type: ignore
160
- backoff.constant,
161
- requests.exceptions.ConnectionError,
162
- max_time=wait_for_schema,
163
- interval=WAIT_FOR_SCHEMA_INTERVAL,
164
- )
165
- def _load_schema(_uri: str, **_kwargs: Any) -> requests.Response:
166
- return requests.get(_uri, **_kwargs)
167
-
168
- else:
169
- _load_schema = requests.get
170
-
171
- kwargs.setdefault("timeout", DEFAULT_RESPONSE_TIMEOUT / 1000)
172
- response = load_schema_from_url(lambda: _load_schema(uri, **kwargs))
173
- return from_file(
174
- response.text,
175
- app=app,
176
- base_url=base_url,
177
- method=method,
178
- endpoint=endpoint,
179
- tag=tag,
180
- operation_id=operation_id,
181
- skip_deprecated_operations=skip_deprecated_operations,
182
- validate_schema=validate_schema,
183
- force_schema_version=force_schema_version,
184
- data_generation_methods=data_generation_methods,
185
- generation_config=generation_config,
186
- output_config=output_config,
187
- code_sample_style=code_sample_style,
188
- location=uri,
189
- rate_limit=rate_limit,
190
- sanitize_output=sanitize_output,
191
- __expects_json=_is_json_response(response),
192
- __expects_yaml=_is_yaml_response(response),
193
- )
194
-
195
-
196
- SCHEMA_INVALID_ERROR = "The provided API schema does not appear to be a valid OpenAPI schema"
197
- SCHEMA_LOADING_ERROR = "Received unsupported content while expecting a JSON or YAML payload for Open API"
198
- SCHEMA_SYNTAX_ERROR = "API schema does not appear syntactically valid"
199
-
200
-
201
- def _load_yaml(data: str, include_details_on_error: bool = False) -> dict[str, Any]:
202
- import yaml
203
-
204
- try:
205
- return load_yaml(data)
206
- except yaml.YAMLError as exc:
207
- if include_details_on_error:
208
- type_ = SchemaErrorType.SYNTAX_ERROR
209
- message = SCHEMA_SYNTAX_ERROR
210
- extras = [entry for entry in str(exc).splitlines() if entry]
211
- else:
212
- type_ = SchemaErrorType.UNEXPECTED_CONTENT_TYPE
213
- message = SCHEMA_LOADING_ERROR
214
- extras = []
215
- raise SchemaError(type_, message, extras=extras) from exc
216
-
217
-
218
- def from_file(
219
- file: IO[str] | str,
220
- *,
221
- app: Any = None,
222
- base_url: str | None = None,
223
- method: Filter | None = None,
224
- endpoint: Filter | None = None,
225
- tag: Filter | None = None,
226
- operation_id: Filter | None = None,
227
- skip_deprecated_operations: bool | None = None,
228
- validate_schema: bool = False,
229
- force_schema_version: 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
- location: str | None = None,
235
- rate_limit: str | None = None,
236
- sanitize_output: bool = True,
237
- __expects_json: bool = False,
238
- __expects_yaml: bool = False,
239
- **kwargs: Any, # needed in the runner to have compatible API across all loaders
240
- ) -> BaseOpenAPISchema:
241
- """Load Open API schema from a file descriptor, string or bytes.
242
-
243
- :param file: Could be a file descriptor, string or bytes.
244
- """
245
- if hasattr(file, "read"):
246
- data = file.read() # type: ignore
247
- else:
248
- data = file
249
- if __expects_json:
250
- try:
251
- raw = json.loads(data)
252
- except json.JSONDecodeError as exc:
253
- # Fallback to a slower YAML loader. This way we'll still load schemas from responses with
254
- # invalid `Content-Type` headers or YAML files that have the `.json` extension.
255
- # This is a rare case, and it will be slower but trying JSON first improves a more common use case
256
- try:
257
- raw = _load_yaml(data)
258
- except SchemaError:
259
- raise SchemaError(
260
- SchemaErrorType.SYNTAX_ERROR,
261
- SCHEMA_SYNTAX_ERROR,
262
- extras=[entry for entry in str(exc).splitlines() if entry],
263
- ) from exc
264
- else:
265
- raw = _load_yaml(data, include_details_on_error=__expects_yaml)
266
- return from_dict(
267
- raw,
268
- app=app,
269
- base_url=base_url,
270
- method=method,
271
- endpoint=endpoint,
272
- tag=tag,
273
- operation_id=operation_id,
274
- skip_deprecated_operations=skip_deprecated_operations,
275
- validate_schema=validate_schema,
276
- force_schema_version=force_schema_version,
277
- data_generation_methods=data_generation_methods,
278
- generation_config=generation_config,
279
- output_config=output_config,
280
- code_sample_style=code_sample_style,
281
- location=location,
282
- rate_limit=rate_limit,
283
- sanitize_output=sanitize_output,
284
- )
285
-
286
-
287
- def _is_fast_api(app: Any) -> bool:
288
- for cls in app.__class__.__mro__:
289
- if f"{cls.__module__}.{cls.__qualname__}" == "fastapi.applications.FastAPI":
290
- return True
291
- return False
292
-
293
-
294
- def from_dict(
295
- raw_schema: dict[str, Any],
296
- *,
297
- app: Any = None,
298
- base_url: str | None = None,
299
- method: Filter | None = None,
300
- endpoint: Filter | None = None,
301
- tag: Filter | None = None,
302
- operation_id: Filter | None = None,
303
- skip_deprecated_operations: bool | None = None,
304
- validate_schema: bool = False,
305
- force_schema_version: str | None = None,
306
- data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
307
- generation_config: GenerationConfig | None = None,
308
- output_config: OutputConfig | None = None,
309
- code_sample_style: str = CodeSampleStyle.default().name,
310
- location: str | None = None,
311
- rate_limit: str | None = None,
312
- sanitize_output: bool = True,
313
- ) -> BaseOpenAPISchema:
314
- """Load Open API schema from a Python dictionary.
315
-
316
- :param dict raw_schema: A schema to load.
317
- """
318
- from ... import transports
319
- from .schemas import OpenApi30, SwaggerV20
320
-
321
- if not isinstance(raw_schema, dict):
322
- raise SchemaError(SchemaErrorType.OPEN_API_INVALID_SCHEMA, SCHEMA_INVALID_ERROR)
323
- _code_sample_style = CodeSampleStyle.from_str(code_sample_style)
324
- hook_context = HookContext()
325
- is_openapi_31 = raw_schema.get("openapi", "").startswith("3.1")
326
- is_fast_api_fixup_installed = fixups.is_installed("fast_api")
327
- if is_fast_api_fixup_installed and is_openapi_31:
328
- fixups.fast_api.uninstall()
329
- elif _is_fast_api(app):
330
- fixups.fast_api.adjust_schema(raw_schema)
331
- dispatch("before_load_schema", hook_context, raw_schema)
332
- rate_limiter: Limiter | None = None
333
- if rate_limit is not None:
334
- rate_limiter = build_limiter(rate_limit)
335
-
336
- for name in ("method", "endpoint", "tag", "operation_id", "skip_deprecated_operations"):
337
- value = locals()[name]
338
- if value is not None:
339
- warn_filtration_arguments(name)
340
- filter_set = filter_set_from_components(
341
- include=True,
342
- method=method,
343
- endpoint=endpoint,
344
- tag=tag,
345
- operation_id=operation_id,
346
- skip_deprecated_operations=skip_deprecated_operations,
347
- )
348
-
349
- def init_openapi_2() -> SwaggerV20:
350
- _maybe_validate_schema(raw_schema, definitions.SWAGGER_20_VALIDATOR, validate_schema)
351
- instance = SwaggerV20(
352
- raw_schema,
353
- specification=Specification.OPENAPI,
354
- app=app,
355
- base_url=base_url,
356
- filter_set=filter_set,
357
- validate_schema=validate_schema,
358
- data_generation_methods=DataGenerationMethod.ensure_list(data_generation_methods),
359
- generation_config=generation_config or GenerationConfig(),
360
- output_config=output_config or OutputConfig(),
361
- code_sample_style=_code_sample_style,
362
- location=location,
363
- rate_limiter=rate_limiter,
364
- sanitize_output=sanitize_output,
365
- transport=transports.get(app),
366
- )
367
- dispatch("after_load_schema", hook_context, instance)
368
- return instance
369
-
370
- def init_openapi_3(forced: bool) -> OpenApi30:
371
- version = raw_schema["openapi"]
372
- if (
373
- not (is_openapi_31 and experimental.OPEN_API_3_1.is_enabled)
374
- and not forced
375
- and not OPENAPI_30_VERSION_RE.match(version)
376
- ):
377
- if is_openapi_31:
378
- raise SchemaError(
379
- SchemaErrorType.OPEN_API_EXPERIMENTAL_VERSION,
380
- f"The provided schema uses Open API {version}, which is currently not fully supported.",
381
- )
382
- raise SchemaError(
383
- SchemaErrorType.OPEN_API_UNSUPPORTED_VERSION,
384
- f"The provided schema uses Open API {version}, which is currently not supported.",
385
- )
386
- if is_openapi_31:
387
- validator = definitions.OPENAPI_31_VALIDATOR
388
- else:
389
- validator = definitions.OPENAPI_30_VALIDATOR
390
- _maybe_validate_schema(raw_schema, validator, validate_schema)
391
- instance = OpenApi30(
392
- raw_schema,
393
- specification=Specification.OPENAPI,
394
- app=app,
395
- base_url=base_url,
396
- filter_set=filter_set,
397
- validate_schema=validate_schema,
398
- data_generation_methods=DataGenerationMethod.ensure_list(data_generation_methods),
399
- generation_config=generation_config or GenerationConfig(),
400
- output_config=output_config or OutputConfig(),
401
- code_sample_style=_code_sample_style,
402
- location=location,
403
- rate_limiter=rate_limiter,
404
- sanitize_output=sanitize_output,
405
- transport=transports.get(app),
406
- )
407
- dispatch("after_load_schema", hook_context, instance)
408
- return instance
409
-
410
- if force_schema_version == "20":
411
- return init_openapi_2()
412
- if force_schema_version == "30":
413
- return init_openapi_3(forced=True)
414
- if "swagger" in raw_schema:
415
- return init_openapi_2()
416
- if "openapi" in raw_schema:
417
- return init_openapi_3(forced=False)
418
- raise SchemaError(
419
- SchemaErrorType.OPEN_API_UNSPECIFIED_VERSION,
420
- "Unable to determine the Open API version as it's not specified in the document.",
421
- )
422
-
423
-
424
- OPENAPI_30_VERSION_RE = re.compile(r"^3\.0\.\d(-.+)?$")
425
-
426
- # It is a common case when API schemas are stored in the YAML format and HTTP status codes are numbers
427
- # The Open API spec requires HTTP status codes as strings
428
- DOC_ENTRY = "https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#patterned-fields-1"
429
- NUMERIC_STATUS_CODES_MESSAGE = f"""Numeric HTTP status codes detected in your YAML schema.
430
- According to the Open API specification, status codes must be strings, not numbers.
431
- For more details, check the Open API documentation: {DOC_ENTRY}
432
-
433
- Please, stringify the following status codes:"""
434
- NON_STRING_OBJECT_KEY_MESSAGE = (
435
- "The Open API specification requires all keys in the schema to be strings. You have some keys that are not strings."
436
- )
437
-
438
-
439
- def _format_status_codes(status_codes: list[tuple[int, list[str | int]]]) -> str:
440
- buffer = io.StringIO()
441
- for status_code, path in status_codes:
442
- buffer.write(f" - {status_code} at schema['paths']")
443
- for chunk in path:
444
- buffer.write(f"[{chunk!r}]")
445
- buffer.write("['responses']\n")
446
- return buffer.getvalue().rstrip()
447
-
448
-
449
- def _maybe_validate_schema(
450
- instance: dict[str, Any], validator: jsonschema.validators.Draft4Validator, validate_schema: bool
451
- ) -> None:
452
- from jsonschema import ValidationError
453
-
454
- if validate_schema:
455
- try:
456
- validator.validate(instance)
457
- except TypeError as exc:
458
- if validation.is_pattern_error(exc):
459
- status_codes = validation.find_numeric_http_status_codes(instance)
460
- if status_codes:
461
- message = _format_status_codes(status_codes)
462
- raise SchemaError(
463
- SchemaErrorType.YAML_NUMERIC_STATUS_CODES, f"{NUMERIC_STATUS_CODES_MESSAGE}\n{message}"
464
- ) from exc
465
- # Some other pattern error
466
- raise SchemaError(SchemaErrorType.YAML_NON_STRING_KEYS, NON_STRING_OBJECT_KEY_MESSAGE) from exc
467
- raise SchemaError(SchemaErrorType.UNCLASSIFIED, "Unknown error") from exc
468
- except ValidationError as exc:
469
- raise SchemaError(
470
- SchemaErrorType.OPEN_API_INVALID_SCHEMA,
471
- SCHEMA_INVALID_ERROR,
472
- extras=[entry for entry in str(exc).splitlines() if entry],
473
- ) from exc
474
-
475
-
476
- def from_pytest_fixture(
477
- fixture_name: str,
478
- *,
479
- app: Any = NOT_SET,
480
- base_url: str | None | NotSet = NOT_SET,
481
- method: Filter | None = NOT_SET,
482
- endpoint: Filter | None = NOT_SET,
483
- tag: Filter | None = NOT_SET,
484
- operation_id: Filter | None = NOT_SET,
485
- skip_deprecated_operations: bool | None = None,
486
- validate_schema: bool = False,
487
- data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET,
488
- generation_config: GenerationConfig | NotSet = NOT_SET,
489
- output_config: OutputConfig | NotSet = NOT_SET,
490
- code_sample_style: str = CodeSampleStyle.default().name,
491
- rate_limit: str | None = None,
492
- sanitize_output: bool = True,
493
- ) -> LazySchema:
494
- """Load schema from a ``pytest`` fixture.
495
-
496
- It is useful if you don't want to make network requests during module loading. With this loader you can defer it
497
- to a fixture.
498
-
499
- Note, the fixture should return a ``BaseSchema`` instance loaded with another loader.
500
-
501
- :param str fixture_name: The name of a fixture to load.
502
- """
503
- from ...lazy import LazySchema
504
-
505
- _code_sample_style = CodeSampleStyle.from_str(code_sample_style)
506
- _data_generation_methods: DataGenerationMethodInput | NotSet
507
- if data_generation_methods is not NOT_SET:
508
- data_generation_methods = cast(DataGenerationMethodInput, data_generation_methods)
509
- _data_generation_methods = DataGenerationMethod.ensure_list(data_generation_methods)
510
- else:
511
- _data_generation_methods = data_generation_methods
512
- rate_limiter: Limiter | None = None
513
- if rate_limit is not None:
514
- rate_limiter = build_limiter(rate_limit)
515
- for name in ("method", "endpoint", "tag", "operation_id", "skip_deprecated_operations"):
516
- value = locals()[name]
517
- if value is not None:
518
- warn_filtration_arguments(name)
519
- filter_set = filter_set_from_components(
520
- include=True,
521
- method=method,
522
- endpoint=endpoint,
523
- tag=tag,
524
- operation_id=operation_id,
525
- skip_deprecated_operations=skip_deprecated_operations,
526
- )
527
- return LazySchema(
528
- fixture_name,
529
- app=app,
530
- base_url=base_url,
531
- filter_set=filter_set,
532
- validate_schema=validate_schema,
533
- data_generation_methods=_data_generation_methods,
534
- generation_config=generation_config,
535
- output_config=output_config,
536
- code_sample_style=_code_sample_style,
537
- rate_limiter=rate_limiter,
538
- sanitize_output=sanitize_output,
539
- )
540
-
541
-
542
- def from_wsgi(
543
- schema_path: str,
544
- app: Any,
545
- *,
546
- base_url: str | None = None,
547
- method: Filter | None = None,
548
- endpoint: Filter | None = None,
549
- tag: Filter | None = None,
550
- operation_id: Filter | None = None,
551
- skip_deprecated_operations: bool | None = None,
552
- validate_schema: bool = False,
553
- force_schema_version: str | None = None,
554
- data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
555
- generation_config: GenerationConfig | None = None,
556
- output_config: OutputConfig | None = None,
557
- code_sample_style: str = CodeSampleStyle.default().name,
558
- rate_limit: str | None = None,
559
- sanitize_output: bool = True,
560
- **kwargs: Any,
561
- ) -> BaseOpenAPISchema:
562
- """Load Open API schema from a WSGI app.
563
-
564
- :param str schema_path: An in-app relative URL to the schema.
565
- :param app: A WSGI app instance.
566
- """
567
- from werkzeug.test import Client
568
-
569
- from ...transports.responses import WSGIResponse
570
-
571
- require_relative_url(schema_path)
572
- setup_default_headers(kwargs)
573
- client = Client(app, WSGIResponse)
574
- response = load_schema_from_url(lambda: client.get(schema_path, **kwargs))
575
- return from_file(
576
- response.data,
577
- app=app,
578
- base_url=base_url,
579
- method=method,
580
- endpoint=endpoint,
581
- tag=tag,
582
- operation_id=operation_id,
583
- skip_deprecated_operations=skip_deprecated_operations,
584
- validate_schema=validate_schema,
585
- force_schema_version=force_schema_version,
586
- data_generation_methods=data_generation_methods,
587
- generation_config=generation_config,
588
- output_config=output_config,
589
- code_sample_style=code_sample_style,
590
- location=schema_path,
591
- rate_limit=rate_limit,
592
- sanitize_output=sanitize_output,
593
- __expects_json=_is_json_response(response),
594
- )
595
-
596
-
597
- def get_loader_for_app(app: Any) -> Callable:
598
- from ...transports.asgi import is_asgi_app
599
-
600
- if is_asgi_app(app):
601
- return from_asgi
602
- if app.__class__.__module__.startswith("aiohttp."):
603
- return from_aiohttp
604
- return from_wsgi
605
-
606
-
607
- def from_aiohttp(
608
- schema_path: str,
609
- app: Any,
610
- *,
611
- base_url: str | None = None,
612
- method: Filter | None = None,
613
- endpoint: Filter | None = None,
614
- tag: Filter | None = None,
615
- operation_id: Filter | None = None,
616
- skip_deprecated_operations: bool | None = None,
617
- validate_schema: bool = False,
618
- force_schema_version: str | None = None,
619
- data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
620
- generation_config: GenerationConfig | None = None,
621
- output_config: OutputConfig | None = None,
622
- code_sample_style: str = CodeSampleStyle.default().name,
623
- rate_limit: str | None = None,
624
- sanitize_output: bool = True,
625
- **kwargs: Any,
626
- ) -> BaseOpenAPISchema:
627
- """Load Open API schema from an AioHTTP app.
628
-
629
- :param str schema_path: An in-app relative URL to the schema.
630
- :param app: An AioHTTP app instance.
631
- """
632
- from ...extra._aiohttp import run_server
633
-
634
- port = run_server(app)
635
- app_url = f"http://127.0.0.1:{port}/"
636
- url = urljoin(app_url, schema_path)
637
- return from_uri(
638
- url,
639
- base_url=base_url,
640
- method=method,
641
- endpoint=endpoint,
642
- tag=tag,
643
- operation_id=operation_id,
644
- skip_deprecated_operations=skip_deprecated_operations,
645
- validate_schema=validate_schema,
646
- force_schema_version=force_schema_version,
647
- data_generation_methods=data_generation_methods,
648
- generation_config=generation_config,
649
- output_config=output_config,
650
- code_sample_style=code_sample_style,
651
- rate_limit=rate_limit,
652
- sanitize_output=sanitize_output,
653
- **kwargs,
654
- )
655
-
656
-
657
- def from_asgi(
658
- schema_path: str,
659
- app: Any,
660
- *,
661
- base_url: str | None = None,
662
- method: Filter | None = None,
663
- endpoint: Filter | None = None,
664
- tag: Filter | None = None,
665
- operation_id: Filter | None = None,
666
- skip_deprecated_operations: bool | None = None,
667
- validate_schema: bool = False,
668
- force_schema_version: str | None = None,
669
- data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
670
- generation_config: GenerationConfig | None = None,
671
- output_config: OutputConfig | None = None,
672
- code_sample_style: str = CodeSampleStyle.default().name,
673
- rate_limit: str | None = None,
674
- sanitize_output: bool = True,
675
- **kwargs: Any,
676
- ) -> BaseOpenAPISchema:
677
- """Load Open API schema from an ASGI app.
678
-
679
- :param str schema_path: An in-app relative URL to the schema.
680
- :param app: An ASGI app instance.
681
- """
682
- from starlette_testclient import TestClient as ASGIClient
683
-
684
- require_relative_url(schema_path)
685
- setup_default_headers(kwargs)
686
- client = ASGIClient(app)
687
- response = load_schema_from_url(lambda: client.get(schema_path, **kwargs))
688
- return from_file(
689
- response.text,
690
- app=app,
691
- base_url=base_url,
692
- method=method,
693
- endpoint=endpoint,
694
- tag=tag,
695
- operation_id=operation_id,
696
- skip_deprecated_operations=skip_deprecated_operations,
697
- validate_schema=validate_schema,
698
- force_schema_version=force_schema_version,
699
- data_generation_methods=data_generation_methods,
700
- generation_config=generation_config,
701
- output_config=output_config,
702
- code_sample_style=code_sample_style,
703
- location=schema_path,
704
- rate_limit=rate_limit,
705
- sanitize_output=sanitize_output,
706
- __expects_json=_is_json_response(response),
707
- )