schemathesis 3.13.0__py3-none-any.whl → 4.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. schemathesis/__init__.py +53 -25
  2. schemathesis/auths.py +507 -0
  3. schemathesis/checks.py +190 -25
  4. schemathesis/cli/__init__.py +27 -1016
  5. schemathesis/cli/__main__.py +4 -0
  6. schemathesis/cli/commands/__init__.py +133 -0
  7. schemathesis/cli/commands/data.py +10 -0
  8. schemathesis/cli/commands/run/__init__.py +602 -0
  9. schemathesis/cli/commands/run/context.py +228 -0
  10. schemathesis/cli/commands/run/events.py +60 -0
  11. schemathesis/cli/commands/run/executor.py +157 -0
  12. schemathesis/cli/commands/run/filters.py +53 -0
  13. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  14. schemathesis/cli/commands/run/handlers/base.py +45 -0
  15. schemathesis/cli/commands/run/handlers/cassettes.py +464 -0
  16. schemathesis/cli/commands/run/handlers/junitxml.py +60 -0
  17. schemathesis/cli/commands/run/handlers/output.py +1750 -0
  18. schemathesis/cli/commands/run/loaders.py +118 -0
  19. schemathesis/cli/commands/run/validation.py +256 -0
  20. schemathesis/cli/constants.py +5 -0
  21. schemathesis/cli/core.py +19 -0
  22. schemathesis/cli/ext/fs.py +16 -0
  23. schemathesis/cli/ext/groups.py +203 -0
  24. schemathesis/cli/ext/options.py +81 -0
  25. schemathesis/config/__init__.py +202 -0
  26. schemathesis/config/_auth.py +51 -0
  27. schemathesis/config/_checks.py +268 -0
  28. schemathesis/config/_diff_base.py +101 -0
  29. schemathesis/config/_env.py +21 -0
  30. schemathesis/config/_error.py +163 -0
  31. schemathesis/config/_generation.py +157 -0
  32. schemathesis/config/_health_check.py +24 -0
  33. schemathesis/config/_operations.py +335 -0
  34. schemathesis/config/_output.py +171 -0
  35. schemathesis/config/_parameters.py +19 -0
  36. schemathesis/config/_phases.py +253 -0
  37. schemathesis/config/_projects.py +543 -0
  38. schemathesis/config/_rate_limit.py +17 -0
  39. schemathesis/config/_report.py +120 -0
  40. schemathesis/config/_validator.py +9 -0
  41. schemathesis/config/_warnings.py +89 -0
  42. schemathesis/config/schema.json +975 -0
  43. schemathesis/core/__init__.py +72 -0
  44. schemathesis/core/adapter.py +34 -0
  45. schemathesis/core/compat.py +32 -0
  46. schemathesis/core/control.py +2 -0
  47. schemathesis/core/curl.py +100 -0
  48. schemathesis/core/deserialization.py +210 -0
  49. schemathesis/core/errors.py +588 -0
  50. schemathesis/core/failures.py +316 -0
  51. schemathesis/core/fs.py +19 -0
  52. schemathesis/core/hooks.py +20 -0
  53. schemathesis/core/jsonschema/__init__.py +13 -0
  54. schemathesis/core/jsonschema/bundler.py +183 -0
  55. schemathesis/core/jsonschema/keywords.py +40 -0
  56. schemathesis/core/jsonschema/references.py +222 -0
  57. schemathesis/core/jsonschema/types.py +41 -0
  58. schemathesis/core/lazy_import.py +15 -0
  59. schemathesis/core/loaders.py +107 -0
  60. schemathesis/core/marks.py +66 -0
  61. schemathesis/core/media_types.py +79 -0
  62. schemathesis/core/output/__init__.py +46 -0
  63. schemathesis/core/output/sanitization.py +54 -0
  64. schemathesis/core/parameters.py +45 -0
  65. schemathesis/core/rate_limit.py +60 -0
  66. schemathesis/core/registries.py +34 -0
  67. schemathesis/core/result.py +27 -0
  68. schemathesis/core/schema_analysis.py +17 -0
  69. schemathesis/core/shell.py +203 -0
  70. schemathesis/core/transforms.py +144 -0
  71. schemathesis/core/transport.py +223 -0
  72. schemathesis/core/validation.py +73 -0
  73. schemathesis/core/version.py +7 -0
  74. schemathesis/engine/__init__.py +28 -0
  75. schemathesis/engine/context.py +152 -0
  76. schemathesis/engine/control.py +44 -0
  77. schemathesis/engine/core.py +201 -0
  78. schemathesis/engine/errors.py +446 -0
  79. schemathesis/engine/events.py +284 -0
  80. schemathesis/engine/observations.py +42 -0
  81. schemathesis/engine/phases/__init__.py +108 -0
  82. schemathesis/engine/phases/analysis.py +28 -0
  83. schemathesis/engine/phases/probes.py +172 -0
  84. schemathesis/engine/phases/stateful/__init__.py +68 -0
  85. schemathesis/engine/phases/stateful/_executor.py +364 -0
  86. schemathesis/engine/phases/stateful/context.py +85 -0
  87. schemathesis/engine/phases/unit/__init__.py +220 -0
  88. schemathesis/engine/phases/unit/_executor.py +459 -0
  89. schemathesis/engine/phases/unit/_pool.py +82 -0
  90. schemathesis/engine/recorder.py +254 -0
  91. schemathesis/errors.py +47 -0
  92. schemathesis/filters.py +395 -0
  93. schemathesis/generation/__init__.py +25 -0
  94. schemathesis/generation/case.py +478 -0
  95. schemathesis/generation/coverage.py +1528 -0
  96. schemathesis/generation/hypothesis/__init__.py +121 -0
  97. schemathesis/generation/hypothesis/builder.py +992 -0
  98. schemathesis/generation/hypothesis/examples.py +56 -0
  99. schemathesis/generation/hypothesis/given.py +66 -0
  100. schemathesis/generation/hypothesis/reporting.py +285 -0
  101. schemathesis/generation/meta.py +227 -0
  102. schemathesis/generation/metrics.py +93 -0
  103. schemathesis/generation/modes.py +20 -0
  104. schemathesis/generation/overrides.py +127 -0
  105. schemathesis/generation/stateful/__init__.py +37 -0
  106. schemathesis/generation/stateful/state_machine.py +294 -0
  107. schemathesis/graphql/__init__.py +15 -0
  108. schemathesis/graphql/checks.py +109 -0
  109. schemathesis/graphql/loaders.py +285 -0
  110. schemathesis/hooks.py +270 -91
  111. schemathesis/openapi/__init__.py +13 -0
  112. schemathesis/openapi/checks.py +467 -0
  113. schemathesis/openapi/generation/__init__.py +0 -0
  114. schemathesis/openapi/generation/filters.py +72 -0
  115. schemathesis/openapi/loaders.py +315 -0
  116. schemathesis/pytest/__init__.py +5 -0
  117. schemathesis/pytest/control_flow.py +7 -0
  118. schemathesis/pytest/lazy.py +341 -0
  119. schemathesis/pytest/loaders.py +36 -0
  120. schemathesis/pytest/plugin.py +357 -0
  121. schemathesis/python/__init__.py +0 -0
  122. schemathesis/python/asgi.py +12 -0
  123. schemathesis/python/wsgi.py +12 -0
  124. schemathesis/schemas.py +683 -247
  125. schemathesis/specs/graphql/__init__.py +0 -1
  126. schemathesis/specs/graphql/nodes.py +27 -0
  127. schemathesis/specs/graphql/scalars.py +86 -0
  128. schemathesis/specs/graphql/schemas.py +395 -123
  129. schemathesis/specs/graphql/validation.py +33 -0
  130. schemathesis/specs/openapi/__init__.py +9 -1
  131. schemathesis/specs/openapi/_hypothesis.py +578 -317
  132. schemathesis/specs/openapi/adapter/__init__.py +10 -0
  133. schemathesis/specs/openapi/adapter/parameters.py +729 -0
  134. schemathesis/specs/openapi/adapter/protocol.py +59 -0
  135. schemathesis/specs/openapi/adapter/references.py +19 -0
  136. schemathesis/specs/openapi/adapter/responses.py +368 -0
  137. schemathesis/specs/openapi/adapter/security.py +144 -0
  138. schemathesis/specs/openapi/adapter/v2.py +30 -0
  139. schemathesis/specs/openapi/adapter/v3_0.py +30 -0
  140. schemathesis/specs/openapi/adapter/v3_1.py +30 -0
  141. schemathesis/specs/openapi/analysis.py +96 -0
  142. schemathesis/specs/openapi/checks.py +753 -74
  143. schemathesis/specs/openapi/converter.py +176 -37
  144. schemathesis/specs/openapi/definitions.py +599 -4
  145. schemathesis/specs/openapi/examples.py +581 -165
  146. schemathesis/specs/openapi/expressions/__init__.py +52 -5
  147. schemathesis/specs/openapi/expressions/extractors.py +25 -0
  148. schemathesis/specs/openapi/expressions/lexer.py +34 -31
  149. schemathesis/specs/openapi/expressions/nodes.py +97 -46
  150. schemathesis/specs/openapi/expressions/parser.py +35 -13
  151. schemathesis/specs/openapi/formats.py +122 -0
  152. schemathesis/specs/openapi/media_types.py +75 -0
  153. schemathesis/specs/openapi/negative/__init__.py +117 -68
  154. schemathesis/specs/openapi/negative/mutations.py +294 -104
  155. schemathesis/specs/openapi/negative/utils.py +3 -6
  156. schemathesis/specs/openapi/patterns.py +458 -0
  157. schemathesis/specs/openapi/references.py +60 -81
  158. schemathesis/specs/openapi/schemas.py +648 -650
  159. schemathesis/specs/openapi/serialization.py +53 -30
  160. schemathesis/specs/openapi/stateful/__init__.py +404 -69
  161. schemathesis/specs/openapi/stateful/control.py +87 -0
  162. schemathesis/specs/openapi/stateful/dependencies/__init__.py +232 -0
  163. schemathesis/specs/openapi/stateful/dependencies/inputs.py +428 -0
  164. schemathesis/specs/openapi/stateful/dependencies/models.py +341 -0
  165. schemathesis/specs/openapi/stateful/dependencies/naming.py +491 -0
  166. schemathesis/specs/openapi/stateful/dependencies/outputs.py +34 -0
  167. schemathesis/specs/openapi/stateful/dependencies/resources.py +339 -0
  168. schemathesis/specs/openapi/stateful/dependencies/schemas.py +447 -0
  169. schemathesis/specs/openapi/stateful/inference.py +254 -0
  170. schemathesis/specs/openapi/stateful/links.py +219 -78
  171. schemathesis/specs/openapi/types/__init__.py +3 -0
  172. schemathesis/specs/openapi/types/common.py +23 -0
  173. schemathesis/specs/openapi/types/v2.py +129 -0
  174. schemathesis/specs/openapi/types/v3.py +134 -0
  175. schemathesis/specs/openapi/utils.py +7 -6
  176. schemathesis/specs/openapi/warnings.py +75 -0
  177. schemathesis/transport/__init__.py +224 -0
  178. schemathesis/transport/asgi.py +26 -0
  179. schemathesis/transport/prepare.py +126 -0
  180. schemathesis/transport/requests.py +278 -0
  181. schemathesis/transport/serialization.py +329 -0
  182. schemathesis/transport/wsgi.py +175 -0
  183. schemathesis-4.4.2.dist-info/METADATA +213 -0
  184. schemathesis-4.4.2.dist-info/RECORD +192 -0
  185. {schemathesis-3.13.0.dist-info → schemathesis-4.4.2.dist-info}/WHEEL +1 -1
  186. schemathesis-4.4.2.dist-info/entry_points.txt +6 -0
  187. {schemathesis-3.13.0.dist-info → schemathesis-4.4.2.dist-info/licenses}/LICENSE +1 -1
  188. schemathesis/_compat.py +0 -41
  189. schemathesis/_hypothesis.py +0 -115
  190. schemathesis/cli/callbacks.py +0 -188
  191. schemathesis/cli/cassettes.py +0 -253
  192. schemathesis/cli/context.py +0 -36
  193. schemathesis/cli/debug.py +0 -21
  194. schemathesis/cli/handlers.py +0 -11
  195. schemathesis/cli/junitxml.py +0 -41
  196. schemathesis/cli/options.py +0 -51
  197. schemathesis/cli/output/__init__.py +0 -1
  198. schemathesis/cli/output/default.py +0 -508
  199. schemathesis/cli/output/short.py +0 -40
  200. schemathesis/constants.py +0 -79
  201. schemathesis/exceptions.py +0 -207
  202. schemathesis/extra/_aiohttp.py +0 -27
  203. schemathesis/extra/_flask.py +0 -10
  204. schemathesis/extra/_server.py +0 -16
  205. schemathesis/extra/pytest_plugin.py +0 -216
  206. schemathesis/failures.py +0 -131
  207. schemathesis/fixups/__init__.py +0 -29
  208. schemathesis/fixups/fast_api.py +0 -30
  209. schemathesis/lazy.py +0 -227
  210. schemathesis/models.py +0 -1041
  211. schemathesis/parameters.py +0 -88
  212. schemathesis/runner/__init__.py +0 -460
  213. schemathesis/runner/events.py +0 -240
  214. schemathesis/runner/impl/__init__.py +0 -3
  215. schemathesis/runner/impl/core.py +0 -755
  216. schemathesis/runner/impl/solo.py +0 -85
  217. schemathesis/runner/impl/threadpool.py +0 -367
  218. schemathesis/runner/serialization.py +0 -189
  219. schemathesis/serializers.py +0 -233
  220. schemathesis/service/__init__.py +0 -3
  221. schemathesis/service/client.py +0 -46
  222. schemathesis/service/constants.py +0 -12
  223. schemathesis/service/events.py +0 -39
  224. schemathesis/service/handler.py +0 -39
  225. schemathesis/service/models.py +0 -7
  226. schemathesis/service/serialization.py +0 -153
  227. schemathesis/service/worker.py +0 -40
  228. schemathesis/specs/graphql/loaders.py +0 -215
  229. schemathesis/specs/openapi/constants.py +0 -7
  230. schemathesis/specs/openapi/expressions/context.py +0 -12
  231. schemathesis/specs/openapi/expressions/pointers.py +0 -29
  232. schemathesis/specs/openapi/filters.py +0 -44
  233. schemathesis/specs/openapi/links.py +0 -302
  234. schemathesis/specs/openapi/loaders.py +0 -453
  235. schemathesis/specs/openapi/parameters.py +0 -413
  236. schemathesis/specs/openapi/security.py +0 -129
  237. schemathesis/specs/openapi/validation.py +0 -24
  238. schemathesis/stateful.py +0 -349
  239. schemathesis/targets.py +0 -32
  240. schemathesis/types.py +0 -38
  241. schemathesis/utils.py +0 -436
  242. schemathesis-3.13.0.dist-info/METADATA +0 -202
  243. schemathesis-3.13.0.dist-info/RECORD +0 -91
  244. schemathesis-3.13.0.dist-info/entry_points.txt +0 -6
  245. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
@@ -1,207 +0,0 @@
1
- from hashlib import sha1
2
- from json import JSONDecodeError
3
- from typing import TYPE_CHECKING, Any, Callable, Dict, NoReturn, Optional, Type, Union
4
-
5
- import attr
6
- import hypothesis.errors
7
- import requests
8
- from jsonschema import ValidationError
9
-
10
- from .constants import SERIALIZERS_SUGGESTION_MESSAGE
11
- from .failures import FailureContext
12
-
13
- if TYPE_CHECKING:
14
- from .utils import GenericResponse
15
-
16
-
17
- class CheckFailed(AssertionError):
18
- """Custom error type to distinguish from arbitrary AssertionError that may happen in the dependent libraries."""
19
-
20
- __module__ = "builtins"
21
- context: Optional[FailureContext]
22
-
23
- def __init__(self, *args: Any, context: Optional[FailureContext] = None):
24
- super().__init__(*args)
25
- self.context = context
26
-
27
-
28
- CACHE: Dict[Union[str, int], Type[CheckFailed]] = {}
29
-
30
-
31
- def get_exception(name: str) -> Type[CheckFailed]:
32
- """Create a new exception class with provided name or fetch one from the cache."""
33
- if name in CACHE:
34
- exception_class = CACHE[name]
35
- else:
36
- exception_class = type(name, (CheckFailed,), {})
37
- exception_class.__qualname__ = CheckFailed.__name__
38
- exception_class.__name__ = CheckFailed.__name__
39
- CACHE[name] = exception_class
40
- return exception_class
41
-
42
-
43
- def _get_hashed_exception(prefix: str, message: str) -> Type[CheckFailed]:
44
- """Give different exceptions for different error messages."""
45
- messages_digest = sha1(message.encode("utf-8")).hexdigest()
46
- name = f"{prefix}{messages_digest}"
47
- return get_exception(name)
48
-
49
-
50
- def get_grouped_exception(prefix: str, *exceptions: AssertionError) -> Type[CheckFailed]:
51
- # The prefix is needed to distinguish multiple operations with the same error messages
52
- # that are coming from different operations
53
- messages = [exception.args[0] for exception in exceptions]
54
- message = "".join(messages)
55
- return _get_hashed_exception("GroupedException", f"{prefix}{message}")
56
-
57
-
58
- def get_server_error(status_code: int) -> Type[CheckFailed]:
59
- """Return new exception for the Internal Server Error cases."""
60
- name = f"ServerError{status_code}"
61
- return get_exception(name)
62
-
63
-
64
- def get_status_code_error(status_code: int) -> Type[CheckFailed]:
65
- """Return new exception for an unexpected status code."""
66
- name = f"StatusCodeError{status_code}"
67
- return get_exception(name)
68
-
69
-
70
- def get_response_type_error(expected: str, received: str) -> Type[CheckFailed]:
71
- """Return new exception for an unexpected response type."""
72
- name = f"SchemaValidationError{expected}_{received}"
73
- return get_exception(name)
74
-
75
-
76
- def get_malformed_media_type_error(media_type: str) -> Type[CheckFailed]:
77
- name = f"MalformedMediaType{media_type}"
78
- return get_exception(name)
79
-
80
-
81
- def get_missing_content_type_error() -> Type[CheckFailed]:
82
- """Return new exception for a missing Content-Type header."""
83
- return get_exception("MissingContentTypeError")
84
-
85
-
86
- def get_schema_validation_error(exception: ValidationError) -> Type[CheckFailed]:
87
- """Return new exception for schema validation error."""
88
- return _get_hashed_exception("SchemaValidationError", str(exception))
89
-
90
-
91
- def get_response_parsing_error(exception: JSONDecodeError) -> Type[CheckFailed]:
92
- """Return new exception for response parsing error."""
93
- return _get_hashed_exception("ResponseParsingError", str(exception))
94
-
95
-
96
- def get_headers_error(message: str) -> Type[CheckFailed]:
97
- """Return new exception for missing headers."""
98
- return _get_hashed_exception("MissingHeadersError", message)
99
-
100
-
101
- def get_timeout_error(deadline: Union[float, int]) -> Type[CheckFailed]:
102
- """Request took too long."""
103
- return _get_hashed_exception("TimeoutError", str(deadline))
104
-
105
-
106
- @attr.s(slots=True)
107
- class InvalidSchema(Exception):
108
- """Schema associated with an API operation contains an error."""
109
-
110
- __module__ = "builtins"
111
- message: Optional[str] = attr.ib(default=None)
112
- path: Optional[str] = attr.ib(default=None)
113
- method: Optional[str] = attr.ib(default=None)
114
- full_path: Optional[str] = attr.ib(default=None)
115
-
116
- def as_failing_test_function(self) -> Callable:
117
- """Create a test function that will fail.
118
-
119
- This approach allows us to use default pytest reporting style for operation-level schema errors.
120
- """
121
-
122
- def actual_test(*args: Any, **kwargs: Any) -> NoReturn:
123
- __tracebackhide__ = True # pylint: disable=unused-variable
124
- raise self
125
-
126
- return actual_test
127
-
128
-
129
- class DeadlineExceeded(Exception):
130
- """Test took too long to run."""
131
-
132
- __module__ = "builtins"
133
-
134
- @classmethod
135
- def from_exc(cls, exc: hypothesis.errors.DeadlineExceeded) -> "DeadlineExceeded":
136
- runtime = exc.runtime.total_seconds() * 1000
137
- deadline = exc.deadline.total_seconds() * 1000
138
- return cls(
139
- f"API response time is too slow! It took {runtime:.2f}ms, which exceeds the deadline of {deadline:.2f}ms.\n"
140
- )
141
-
142
-
143
- class SchemaLoadingError(ValueError):
144
- """Failed to load an API schema."""
145
-
146
-
147
- class NonCheckError(Exception):
148
- """An error happened in side the runner, but is not related to failed checks.
149
-
150
- Used primarily to not let Hypothesis to consider the test as flaky or detect multiple failures as we handle it
151
- on our side.
152
- """
153
-
154
- __module__ = "builtins"
155
-
156
-
157
- class InternalError(Exception):
158
- """Internal error in Schemathesis."""
159
-
160
- __module__ = "builtins"
161
-
162
-
163
- SERIALIZATION_NOT_POSSIBLE_MESSAGE = (
164
- f"Schemathesis can't serialize data to any of the defined media types: {{}} \n{SERIALIZERS_SUGGESTION_MESSAGE}"
165
- )
166
-
167
-
168
- class SerializationNotPossible(Exception):
169
- """Not possible to serialize to any of the media types defined for some API operation.
170
-
171
- Usually, there is still `application/json` along with less common ones, but this error happens when there is no
172
- media type that Schemathesis knows how to serialize data to.
173
- """
174
-
175
- __module__ = "builtins"
176
-
177
- @classmethod
178
- def from_media_types(cls, *media_types: str) -> "SerializationNotPossible":
179
- return cls(SERIALIZATION_NOT_POSSIBLE_MESSAGE.format(", ".join(media_types)))
180
-
181
-
182
- class InvalidRegularExpression(Exception):
183
- __module__ = "builtins"
184
-
185
-
186
- @attr.s # pragma: no mutate
187
- class HTTPError(Exception):
188
- response: "GenericResponse" = attr.ib() # pragma: no mutate
189
- url: str = attr.ib() # pragma: no mutate
190
-
191
- @classmethod
192
- def raise_for_status(cls, response: requests.Response) -> None:
193
- try:
194
- response.raise_for_status()
195
- except requests.HTTPError as exc:
196
- raise cls(response=response, url=response.url) from exc
197
-
198
- @classmethod
199
- def check_response(cls, response: requests.Response, schema_path: str) -> None:
200
- # Raising exception to provide unified behavior
201
- # E.g. it will be handled in CLI - a proper error message will be shown
202
- if 400 <= response.status_code < 600:
203
- raise cls(response=response, url=schema_path)
204
-
205
-
206
- class UsageError(Exception):
207
- """Incorrect usage of Schemathesis functions."""
@@ -1,27 +0,0 @@
1
- import asyncio
2
- from typing import Optional
3
-
4
- from aiohttp import web
5
-
6
- from . import _server
7
-
8
-
9
- def _run_server(app: web.Application, port: int) -> None:
10
- """Run the given app on the given port.
11
-
12
- Intended to be called as a target for a separate thread.
13
- NOTE. `aiohttp.web.run_app` works only in the main thread and can't be used here (or maybe can we some tuning)
14
- """
15
- # Set a loop for a new thread (there is no by default for non-main threads)
16
- loop = asyncio.new_event_loop()
17
- asyncio.set_event_loop(loop)
18
- runner = web.AppRunner(app)
19
- loop.run_until_complete(runner.setup())
20
- site = web.TCPSite(runner, "127.0.0.1", port)
21
- loop.run_until_complete(site.start())
22
- loop.run_forever()
23
-
24
-
25
- def run_server(app: web.Application, port: Optional[int] = None, timeout: float = 0.05) -> int:
26
- """Start a thread with the given aiohttp application."""
27
- return _server.run(_run_server, app=app, port=port, timeout=timeout)
@@ -1,10 +0,0 @@
1
- from typing import Optional
2
-
3
- from flask import Flask
4
-
5
- from . import _server
6
-
7
-
8
- def run_server(app: Flask, port: Optional[int] = None, timeout: float = 0.05) -> int:
9
- """Start a thread with the given aiohttp application."""
10
- return _server.run(app.run, port=port, timeout=timeout)
@@ -1,16 +0,0 @@
1
- import threading
2
- from time import sleep
3
- from typing import Any, Callable, Optional
4
-
5
- from aiohttp.test_utils import unused_port
6
-
7
-
8
- def run(target: Callable, port: Optional[int] = None, timeout: float = 0.05, **kwargs: Any) -> int:
9
- """Start a thread with the given aiohttp application."""
10
- if port is None:
11
- port = unused_port()
12
- server_thread = threading.Thread(target=target, kwargs={"port": port, **kwargs})
13
- server_thread.daemon = True
14
- server_thread.start()
15
- sleep(timeout)
16
- return port
@@ -1,216 +0,0 @@
1
- from functools import partial
2
- from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, TypeVar, cast
3
-
4
- import pytest
5
- from _pytest import fixtures, nodes
6
- from _pytest.config import hookimpl
7
- from _pytest.fixtures import FuncFixtureInfo
8
- from _pytest.nodes import Node
9
- from _pytest.python import Class, Function, FunctionDefinition, Metafunc, Module, PyCollector
10
- from hypothesis.errors import InvalidArgument
11
- from hypothesis_jsonschema._canonicalise import HypothesisRefResolutionError
12
-
13
- from .. import DataGenerationMethod
14
- from .._hypothesis import create_test
15
- from ..constants import IS_PYTEST_ABOVE_54, RECURSIVE_REFERENCE_ERROR_MESSAGE
16
- from ..exceptions import InvalidSchema
17
- from ..models import APIOperation
18
- from ..utils import (
19
- PARAMETRIZE_MARKER,
20
- Ok,
21
- Result,
22
- fail_on_no_matches,
23
- get_given_args,
24
- get_given_kwargs,
25
- is_given_applied,
26
- is_schemathesis_test,
27
- merge_given_args,
28
- validate_given_args,
29
- )
30
-
31
- T = TypeVar("T", bound=Node)
32
-
33
-
34
- def create(cls: Type[T], *args: Any, **kwargs: Any) -> T:
35
- if IS_PYTEST_ABOVE_54:
36
- return cls.from_parent(*args, **kwargs) # type: ignore
37
- return cls(*args, **kwargs)
38
-
39
-
40
- class SchemathesisFunction(Function): # pylint: disable=too-many-ancestors
41
- def __init__(
42
- self,
43
- *args: Any,
44
- test_func: Callable,
45
- test_name: Optional[str] = None,
46
- data_generation_method: DataGenerationMethod,
47
- **kwargs: Any,
48
- ) -> None:
49
- super().__init__(*args, **kwargs)
50
- self.test_function = test_func
51
- self.test_name = test_name
52
- self.data_generation_method = data_generation_method
53
-
54
- def _getobj(self) -> partial:
55
- """Tests defined as methods require `self` as the first argument.
56
-
57
- This method is called only for this case.
58
- """
59
- return partial(self.obj, self.parent.obj) # type: ignore
60
-
61
-
62
- class SchemathesisCase(PyCollector):
63
- def __init__(self, test_function: Callable, *args: Any, **kwargs: Any) -> None:
64
- self.given_kwargs: Optional[Dict[str, Any]]
65
- given_args = get_given_args(test_function)
66
- given_kwargs = get_given_kwargs(test_function)
67
-
68
- def _init_with_valid_test(_test_function: Callable, _args: Tuple, _kwargs: Dict[str, Any]) -> None:
69
- self.test_function = _test_function
70
- self.is_invalid_test = False
71
- self.given_kwargs = merge_given_args(test_function, _args, _kwargs)
72
-
73
- if is_given_applied(test_function):
74
- failing_test = validate_given_args(test_function, given_args, given_kwargs)
75
- if failing_test is not None:
76
- self.test_function = failing_test
77
- self.is_invalid_test = True
78
- self.given_kwargs = None
79
- else:
80
- _init_with_valid_test(test_function, given_args, given_kwargs)
81
- else:
82
- _init_with_valid_test(test_function, given_args, given_kwargs)
83
- self.schemathesis_case = getattr(test_function, PARAMETRIZE_MARKER)
84
- super().__init__(*args, **kwargs)
85
-
86
- def _get_test_name(self, operation: APIOperation, data_generation_method: DataGenerationMethod) -> str:
87
- return f"{self.name}[{operation.verbose_name}][{data_generation_method.as_short_name()}]"
88
-
89
- def _gen_items(
90
- self, result: Result[APIOperation, InvalidSchema], data_generation_method: DataGenerationMethod
91
- ) -> Generator[SchemathesisFunction, None, None]:
92
- """Generate all tests for the given API operation.
93
-
94
- Could produce more than one test item if
95
- parametrization is applied via ``pytest.mark.parametrize`` or ``pytest_generate_tests``.
96
-
97
- This implementation is based on the original one in pytest, but with slight adjustments
98
- to produce tests out of hypothesis ones.
99
- """
100
- if isinstance(result, Ok):
101
- operation = result.ok()
102
- if self.is_invalid_test:
103
- funcobj = self.test_function
104
- else:
105
- funcobj = create_test(
106
- operation=operation,
107
- test=self.test_function,
108
- _given_kwargs=self.given_kwargs,
109
- data_generation_method=data_generation_method,
110
- )
111
- name = self._get_test_name(operation, data_generation_method)
112
- else:
113
- error = result.err()
114
- funcobj = error.as_failing_test_function()
115
- name = self.name
116
- # `full_path` is always available in this case
117
- if error.method:
118
- name += f"[{error.method.upper()} {error.full_path}]"
119
- else:
120
- name += f"[{error.full_path}]"
121
- name += f"[{data_generation_method.as_short_name()}]"
122
-
123
- cls = self._get_class_parent()
124
- definition: FunctionDefinition = create(FunctionDefinition, name=self.name, parent=self.parent, callobj=funcobj)
125
- fixturemanager = self.session._fixturemanager
126
- fixtureinfo = fixturemanager.getfixtureinfo(definition, funcobj, cls)
127
-
128
- metafunc = self._parametrize(cls, definition, fixtureinfo)
129
-
130
- if not metafunc._calls:
131
- yield create(
132
- SchemathesisFunction,
133
- name=name,
134
- parent=self.parent,
135
- callobj=funcobj,
136
- fixtureinfo=fixtureinfo,
137
- test_func=self.test_function,
138
- originalname=self.name,
139
- data_generation_method=data_generation_method,
140
- )
141
- else:
142
- fixtures.add_funcarg_pseudo_fixture_def(self.parent, metafunc, fixturemanager) # type: ignore[arg-type]
143
- fixtureinfo.prune_dependency_tree()
144
- for callspec in metafunc._calls:
145
- subname = f"{name}[{callspec.id}]"
146
- yield create(
147
- SchemathesisFunction,
148
- name=subname,
149
- parent=self.parent,
150
- callspec=callspec,
151
- callobj=funcobj,
152
- fixtureinfo=fixtureinfo,
153
- keywords={callspec.id: True},
154
- originalname=name,
155
- test_func=self.test_function,
156
- data_generation_method=data_generation_method,
157
- )
158
-
159
- def _get_class_parent(self) -> Optional[Type]:
160
- clscol = self.getparent(Class)
161
- return clscol.obj if clscol else None
162
-
163
- def _parametrize(
164
- self, cls: Optional[Type], definition: FunctionDefinition, fixtureinfo: FuncFixtureInfo
165
- ) -> Metafunc:
166
- parent = self.getparent(Module)
167
- module = parent.obj if parent is not None else parent
168
- metafunc = Metafunc(definition, fixtureinfo, self.config, cls=cls, module=module)
169
- methods = []
170
- if hasattr(module, "pytest_generate_tests"):
171
- methods.append(module.pytest_generate_tests)
172
- if hasattr(cls, "pytest_generate_tests"):
173
- cls = cast(Type, cls)
174
- methods.append(cls().pytest_generate_tests)
175
- self.ihook.pytest_generate_tests.call_extra(methods, {"metafunc": metafunc})
176
- return metafunc
177
-
178
- def collect(self) -> List[Function]: # type: ignore
179
- """Generate different test items for all API operations available in the given schema."""
180
- try:
181
- items = [
182
- item
183
- for data_generation_method in self.schemathesis_case.data_generation_methods
184
- for operation in self.schemathesis_case.get_all_operations()
185
- for item in self._gen_items(operation, data_generation_method)
186
- ]
187
- if not items:
188
- fail_on_no_matches(self.nodeid)
189
- return items
190
- except Exception:
191
- pytest.fail("Error during collection")
192
-
193
-
194
- @hookimpl(hookwrapper=True) # type:ignore # pragma: no mutate
195
- def pytest_pycollect_makeitem(collector: nodes.Collector, name: str, obj: Any) -> Generator[None, Any, None]:
196
- """Switch to a different collector if the test is parametrized marked by schemathesis."""
197
- outcome = yield
198
- if is_schemathesis_test(obj):
199
- outcome.force_result(create(SchemathesisCase, parent=collector, test_function=obj, name=name))
200
- else:
201
- outcome.get_result()
202
-
203
-
204
- @hookimpl(hookwrapper=True) # pragma: no mutate
205
- def pytest_pyfunc_call(pyfuncitem): # type:ignore
206
- """It is possible to have a Hypothesis exception in runtime.
207
-
208
- For example - kwargs validation is failed for some strategy.
209
- """
210
- outcome = yield
211
- try:
212
- outcome.get_result()
213
- except InvalidArgument as exc:
214
- raise InvalidSchema(exc.args[0]) from None
215
- except HypothesisRefResolutionError:
216
- pytest.skip(RECURSIVE_REFERENCE_ERROR_MESSAGE)
schemathesis/failures.py DELETED
@@ -1,131 +0,0 @@
1
- from typing import Any, Dict, List, Union
2
-
3
- import attr
4
-
5
-
6
- @attr.s(slots=True, repr=False) # pragma: no mutate
7
- class FailureContext:
8
- """Additional data specific to certain failure kind."""
9
-
10
- # Short description of what happened
11
- title: str
12
- # A longer one
13
- message: str
14
- type: str
15
-
16
-
17
- @attr.s(slots=True, repr=False)
18
- class ValidationErrorContext(FailureContext):
19
- """Additional information about JSON Schema validation errors."""
20
-
21
- validation_message: str = attr.ib()
22
- schema_path: List[Union[str, int]] = attr.ib()
23
- schema: Union[Dict[str, Any], bool] = attr.ib()
24
- instance_path: List[Union[str, int]] = attr.ib()
25
- instance: Union[None, bool, float, str, list, Dict[str, Any]] = attr.ib()
26
- title: str = attr.ib(default="Non-conforming response payload")
27
- message: str = attr.ib(default="Response does not conform to the defined schema")
28
- type: str = attr.ib(default="json_schema")
29
-
30
-
31
- @attr.s(slots=True, repr=False)
32
- class JSONDecodeErrorContext(FailureContext):
33
- """Failed to decode JSON."""
34
-
35
- validation_message: str = attr.ib()
36
- document: str = attr.ib()
37
- position: int = attr.ib()
38
- lineno: int = attr.ib()
39
- colno: int = attr.ib()
40
- title: str = attr.ib(default="JSON deserialization error")
41
- message: str = attr.ib(default="Response is not a valid JSON")
42
- type: str = attr.ib(default="json_decode")
43
-
44
-
45
- @attr.s(slots=True, repr=False)
46
- class ServerError(FailureContext):
47
- status_code: int = attr.ib()
48
- title: str = attr.ib(default="Internal server error")
49
- message: str = attr.ib(default="Server got itself in trouble")
50
- type: str = attr.ib(default="server_error")
51
-
52
-
53
- @attr.s(slots=True, repr=False)
54
- class MissingContentType(FailureContext):
55
- """Content type header is missing."""
56
-
57
- media_types: List[str] = attr.ib()
58
- title: str = attr.ib(default="Missing Content-Type header")
59
- message: str = attr.ib(default="Response is missing the `Content-Type` header")
60
- type: str = attr.ib(default="missing_content_type")
61
-
62
-
63
- @attr.s(slots=True, repr=False)
64
- class UndefinedContentType(FailureContext):
65
- """Response has Content-Type that is not defined in the schema."""
66
-
67
- content_type: str = attr.ib()
68
- defined_content_types: List[str] = attr.ib()
69
- title: str = attr.ib(default="Undefined Content-Type")
70
- message: str = attr.ib(default="Response has `Content-Type` that is not declared in the schema")
71
- type: str = attr.ib(default="undefined_content_type")
72
-
73
-
74
- @attr.s(slots=True, repr=False)
75
- class UndefinedStatusCode(FailureContext):
76
- """Response has a status code that is not defined in the schema."""
77
-
78
- # Response's status code
79
- status_code: int = attr.ib()
80
- # Status codes as defined in schema
81
- defined_status_codes: List[str] = attr.ib()
82
- # Defined status code with expanded wildcards
83
- allowed_status_codes: List[int] = attr.ib()
84
- title: str = attr.ib(default="Undefined status code")
85
- message: str = attr.ib(default="Response has a status code that is not declared in the schema")
86
- type: str = attr.ib(default="undefined_status_code")
87
-
88
-
89
- @attr.s(slots=True, repr=False)
90
- class MissingHeaders(FailureContext):
91
- """Some required headers are missing."""
92
-
93
- missing_headers: List[str] = attr.ib()
94
- title: str = attr.ib(default="Missing required headers")
95
- message: str = attr.ib(default="Response is missing headers required by the schema")
96
- type: str = attr.ib(default="missing_headers")
97
-
98
-
99
- @attr.s(slots=True, repr=False)
100
- class MalformedMediaType(FailureContext):
101
- """Media type name is malformed.
102
-
103
- Example: `application-json` instead of `application/json`
104
- """
105
-
106
- actual: str = attr.ib()
107
- defined: str = attr.ib()
108
- title: str = attr.ib(default="Malformed media type name")
109
- message: str = attr.ib(default="Media type name is not valid")
110
- type: str = attr.ib(default="malformed_media_type")
111
-
112
-
113
- @attr.s(slots=True, repr=False)
114
- class ResponseTimeExceeded(FailureContext):
115
- """Response took longer than expected."""
116
-
117
- elapsed: float = attr.ib()
118
- deadline: int = attr.ib()
119
- title: str = attr.ib(default="Response time exceeded")
120
- message: str = attr.ib(default="Response time exceeds the deadline")
121
- type: str = attr.ib(default="response_time_exceeded")
122
-
123
-
124
- @attr.s(slots=True, repr=False)
125
- class RequestTimeout(FailureContext):
126
- """Request took longer than timeout."""
127
-
128
- timeout: int = attr.ib()
129
- title: str = attr.ib(default="Request timeout")
130
- message: str = attr.ib(default="The request timed out")
131
- type: str = attr.ib(default="request_timeout")
@@ -1,29 +0,0 @@
1
- from typing import Iterable, Optional
2
-
3
- from . import fast_api
4
-
5
- ALL_FIXUPS = {"fast_api": fast_api}
6
-
7
-
8
- def install(fixups: Optional[Iterable[str]] = None) -> None:
9
- """Install fixups.
10
-
11
- Without the first argument installs all available fixups.
12
-
13
- :param fixups: Names of fixups to install.
14
- """
15
- fixups = fixups or list(ALL_FIXUPS.keys())
16
- for name in fixups:
17
- ALL_FIXUPS[name].install() # type: ignore
18
-
19
-
20
- def uninstall(fixups: Optional[Iterable[str]] = None) -> None:
21
- """Uninstall fixups.
22
-
23
- Without the first argument uninstalls all available fixups.
24
-
25
- :param fixups: Names of fixups to uninstall.
26
- """
27
- fixups = fixups or list(ALL_FIXUPS.keys())
28
- for name in fixups:
29
- ALL_FIXUPS[name].uninstall() # type: ignore
@@ -1,30 +0,0 @@
1
- from typing import Any, Dict
2
-
3
- from ..hooks import HookContext, register, unregister
4
- from ..utils import traverse_schema
5
-
6
-
7
- def install() -> None:
8
- register(before_load_schema)
9
-
10
-
11
- def uninstall() -> None:
12
- unregister(before_load_schema)
13
-
14
-
15
- def before_load_schema(context: HookContext, schema: Dict[str, Any]) -> None:
16
- traverse_schema(schema, _handle_boundaries)
17
-
18
-
19
- def _handle_boundaries(schema: Dict[str, Any]) -> Dict[str, Any]:
20
- """Convert Draft 7 keywords to Draft 4 compatible versions.
21
-
22
- FastAPI uses ``pydantic``, which generates Draft 7 compatible schemas.
23
- """
24
- for boundary_name, boundary_exclusive_name in (("maximum", "exclusiveMaximum"), ("minimum", "exclusiveMinimum")):
25
- value = schema.get(boundary_exclusive_name)
26
- # `bool` check is needed, since in Python `True` is an instance of `int`
27
- if isinstance(value, (int, float)) and not isinstance(value, bool):
28
- schema[boundary_exclusive_name] = True
29
- schema[boundary_name] = value
30
- return schema