schemathesis 3.15.4__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 (251) 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 -1219
  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 +682 -257
  125. schemathesis/specs/graphql/__init__.py +0 -1
  126. schemathesis/specs/graphql/nodes.py +26 -2
  127. schemathesis/specs/graphql/scalars.py +77 -12
  128. schemathesis/specs/graphql/schemas.py +367 -148
  129. schemathesis/specs/graphql/validation.py +33 -0
  130. schemathesis/specs/openapi/__init__.py +9 -1
  131. schemathesis/specs/openapi/_hypothesis.py +555 -318
  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 +748 -82
  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 +93 -73
  154. schemathesis/specs/openapi/negative/mutations.py +294 -103
  155. schemathesis/specs/openapi/negative/utils.py +0 -9
  156. schemathesis/specs/openapi/patterns.py +458 -0
  157. schemathesis/specs/openapi/references.py +60 -81
  158. schemathesis/specs/openapi/schemas.py +647 -666
  159. schemathesis/specs/openapi/serialization.py +53 -30
  160. schemathesis/specs/openapi/stateful/__init__.py +403 -68
  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.15.4.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.15.4.dist-info → schemathesis-4.4.2.dist-info/licenses}/LICENSE +1 -1
  188. schemathesis/_compat.py +0 -57
  189. schemathesis/_hypothesis.py +0 -123
  190. schemathesis/auth.py +0 -214
  191. schemathesis/cli/callbacks.py +0 -240
  192. schemathesis/cli/cassettes.py +0 -351
  193. schemathesis/cli/context.py +0 -38
  194. schemathesis/cli/debug.py +0 -21
  195. schemathesis/cli/handlers.py +0 -11
  196. schemathesis/cli/junitxml.py +0 -41
  197. schemathesis/cli/options.py +0 -70
  198. schemathesis/cli/output/__init__.py +0 -1
  199. schemathesis/cli/output/default.py +0 -521
  200. schemathesis/cli/output/short.py +0 -40
  201. schemathesis/constants.py +0 -88
  202. schemathesis/exceptions.py +0 -257
  203. schemathesis/extra/_aiohttp.py +0 -27
  204. schemathesis/extra/_flask.py +0 -10
  205. schemathesis/extra/_server.py +0 -16
  206. schemathesis/extra/pytest_plugin.py +0 -251
  207. schemathesis/failures.py +0 -145
  208. schemathesis/fixups/__init__.py +0 -29
  209. schemathesis/fixups/fast_api.py +0 -30
  210. schemathesis/graphql.py +0 -5
  211. schemathesis/internal.py +0 -6
  212. schemathesis/lazy.py +0 -301
  213. schemathesis/models.py +0 -1113
  214. schemathesis/parameters.py +0 -91
  215. schemathesis/runner/__init__.py +0 -470
  216. schemathesis/runner/events.py +0 -242
  217. schemathesis/runner/impl/__init__.py +0 -3
  218. schemathesis/runner/impl/core.py +0 -791
  219. schemathesis/runner/impl/solo.py +0 -85
  220. schemathesis/runner/impl/threadpool.py +0 -367
  221. schemathesis/runner/serialization.py +0 -206
  222. schemathesis/serializers.py +0 -253
  223. schemathesis/service/__init__.py +0 -18
  224. schemathesis/service/auth.py +0 -10
  225. schemathesis/service/client.py +0 -62
  226. schemathesis/service/constants.py +0 -25
  227. schemathesis/service/events.py +0 -39
  228. schemathesis/service/handler.py +0 -46
  229. schemathesis/service/hosts.py +0 -74
  230. schemathesis/service/metadata.py +0 -42
  231. schemathesis/service/models.py +0 -21
  232. schemathesis/service/serialization.py +0 -184
  233. schemathesis/service/worker.py +0 -39
  234. schemathesis/specs/graphql/loaders.py +0 -215
  235. schemathesis/specs/openapi/constants.py +0 -7
  236. schemathesis/specs/openapi/expressions/context.py +0 -12
  237. schemathesis/specs/openapi/expressions/pointers.py +0 -29
  238. schemathesis/specs/openapi/filters.py +0 -44
  239. schemathesis/specs/openapi/links.py +0 -303
  240. schemathesis/specs/openapi/loaders.py +0 -453
  241. schemathesis/specs/openapi/parameters.py +0 -430
  242. schemathesis/specs/openapi/security.py +0 -129
  243. schemathesis/specs/openapi/validation.py +0 -24
  244. schemathesis/stateful.py +0 -358
  245. schemathesis/targets.py +0 -32
  246. schemathesis/types.py +0 -38
  247. schemathesis/utils.py +0 -475
  248. schemathesis-3.15.4.dist-info/METADATA +0 -202
  249. schemathesis-3.15.4.dist-info/RECORD +0 -99
  250. schemathesis-3.15.4.dist-info/entry_points.txt +0 -7
  251. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
@@ -1,257 +0,0 @@
1
- from hashlib import sha1
2
- from json import JSONDecodeError
3
- from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, NoReturn, Optional, Tuple, 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
- causes: Optional[Tuple[Union["CheckFailed", AssertionError], ...]]
23
-
24
- def __init__(
25
- self,
26
- *args: Any,
27
- context: Optional[FailureContext] = None,
28
- causes: Optional[Tuple[Union["CheckFailed", AssertionError], ...]] = None,
29
- ):
30
- super().__init__(*args)
31
- self.context = context
32
- self.causes = causes
33
-
34
-
35
- def make_unique_by_key(
36
- check_name: str, check_message: Optional[str], context: Optional[FailureContext]
37
- ) -> Tuple[Optional[str], ...]:
38
- """A key to distinguish different failed checks.
39
-
40
- It is not only based on `FailureContext`, because the end-user may raise plain `AssertionError` in their custom
41
- checks, and those won't have any context attached.
42
- """
43
- if context is not None:
44
- return context.unique_by_key(check_message)
45
- return check_name, check_message
46
-
47
-
48
- def deduplicate_failed_checks(
49
- checks: List[Union[CheckFailed, AssertionError]]
50
- ) -> Generator[Union[CheckFailed, AssertionError], None, None]:
51
- """Keep only unique failed checks."""
52
- seen = set()
53
- for check in checks:
54
- check_message = check.args[0]
55
- if isinstance(check, CheckFailed) and check.context is not None:
56
- key = check.context.unique_by_key(check_message)
57
- else:
58
- key = check_message
59
- if key not in seen:
60
- yield check
61
- seen.add(key)
62
-
63
-
64
- CACHE: Dict[Union[str, int], Type[CheckFailed]] = {}
65
-
66
-
67
- def get_exception(name: str) -> Type[CheckFailed]:
68
- """Create a new exception class with provided name or fetch one from the cache."""
69
- if name in CACHE:
70
- exception_class = CACHE[name]
71
- else:
72
- exception_class = type(name, (CheckFailed,), {})
73
- exception_class.__qualname__ = CheckFailed.__name__
74
- exception_class.__name__ = CheckFailed.__name__
75
- CACHE[name] = exception_class
76
- return exception_class
77
-
78
-
79
- def _get_hashed_exception(prefix: str, message: str) -> Type[CheckFailed]:
80
- """Give different exceptions for different error messages."""
81
- messages_digest = sha1(message.encode("utf-8")).hexdigest()
82
- name = f"{prefix}{messages_digest}"
83
- return get_exception(name)
84
-
85
-
86
- def get_grouped_exception(prefix: str, *exceptions: AssertionError) -> Type[CheckFailed]:
87
- # The prefix is needed to distinguish multiple operations with the same error messages
88
- # that are coming from different operations
89
- messages = [exception.args[0] for exception in exceptions]
90
- message = "".join(messages)
91
- return _get_hashed_exception("GroupedException", f"{prefix}{message}")
92
-
93
-
94
- def get_server_error(status_code: int) -> Type[CheckFailed]:
95
- """Return new exception for the Internal Server Error cases."""
96
- name = f"ServerError{status_code}"
97
- return get_exception(name)
98
-
99
-
100
- def get_status_code_error(status_code: int) -> Type[CheckFailed]:
101
- """Return new exception for an unexpected status code."""
102
- name = f"StatusCodeError{status_code}"
103
- return get_exception(name)
104
-
105
-
106
- def get_response_type_error(expected: str, received: str) -> Type[CheckFailed]:
107
- """Return new exception for an unexpected response type."""
108
- name = f"SchemaValidationError{expected}_{received}"
109
- return get_exception(name)
110
-
111
-
112
- def get_malformed_media_type_error(media_type: str) -> Type[CheckFailed]:
113
- name = f"MalformedMediaType{media_type}"
114
- return get_exception(name)
115
-
116
-
117
- def get_missing_content_type_error() -> Type[CheckFailed]:
118
- """Return new exception for a missing Content-Type header."""
119
- return get_exception("MissingContentTypeError")
120
-
121
-
122
- def get_schema_validation_error(exception: ValidationError) -> Type[CheckFailed]:
123
- """Return new exception for schema validation error."""
124
- return _get_hashed_exception("SchemaValidationError", str(exception))
125
-
126
-
127
- def get_response_parsing_error(exception: JSONDecodeError) -> Type[CheckFailed]:
128
- """Return new exception for response parsing error."""
129
- return _get_hashed_exception("ResponseParsingError", str(exception))
130
-
131
-
132
- def get_headers_error(message: str) -> Type[CheckFailed]:
133
- """Return new exception for missing headers."""
134
- return _get_hashed_exception("MissingHeadersError", message)
135
-
136
-
137
- def get_timeout_error(deadline: Union[float, int]) -> Type[CheckFailed]:
138
- """Request took too long."""
139
- return _get_hashed_exception("TimeoutError", str(deadline))
140
-
141
-
142
- @attr.s(slots=True)
143
- class InvalidSchema(Exception):
144
- """Schema associated with an API operation contains an error."""
145
-
146
- __module__ = "builtins"
147
- message: Optional[str] = attr.ib(default=None)
148
- path: Optional[str] = attr.ib(default=None)
149
- method: Optional[str] = attr.ib(default=None)
150
- full_path: Optional[str] = attr.ib(default=None)
151
-
152
- def as_failing_test_function(self) -> Callable:
153
- """Create a test function that will fail.
154
-
155
- This approach allows us to use default pytest reporting style for operation-level schema errors.
156
- """
157
-
158
- def actual_test(*args: Any, **kwargs: Any) -> NoReturn:
159
- __tracebackhide__ = True # pylint: disable=unused-variable
160
- raise self
161
-
162
- return actual_test
163
-
164
-
165
- class DeadlineExceeded(Exception):
166
- """Test took too long to run."""
167
-
168
- __module__ = "builtins"
169
-
170
- @classmethod
171
- def from_exc(cls, exc: hypothesis.errors.DeadlineExceeded) -> "DeadlineExceeded":
172
- runtime = exc.runtime.total_seconds() * 1000
173
- deadline = exc.deadline.total_seconds() * 1000
174
- return cls(
175
- f"API response time is too slow! It took {runtime:.2f}ms, which exceeds the deadline of {deadline:.2f}ms.\n"
176
- )
177
-
178
-
179
- class SchemaLoadingError(ValueError):
180
- """Failed to load an API schema."""
181
-
182
-
183
- class NonCheckError(Exception):
184
- """An error happened in side the runner, but is not related to failed checks.
185
-
186
- Used primarily to not let Hypothesis consider the test as flaky or detect multiple failures as we handle it
187
- on our side.
188
- """
189
-
190
- __module__ = "builtins"
191
-
192
-
193
- class InternalError(Exception):
194
- """Internal error in Schemathesis."""
195
-
196
- __module__ = "builtins"
197
-
198
-
199
- class SkipTest(BaseException):
200
- """Raises when a test should be skipped and return control to the execution engine (own Schemathesis' or pytest)."""
201
-
202
- __module__ = "builtins"
203
-
204
-
205
- SERIALIZATION_NOT_POSSIBLE_MESSAGE = (
206
- f"Schemathesis can't serialize data to any of the defined media types: {{}} \n{SERIALIZERS_SUGGESTION_MESSAGE}"
207
- )
208
-
209
- SERIALIZATION_FOR_TYPE_IS_NOT_POSSIBLE_MESSAGE = (
210
- f"Schemathesis can't serialize data to {{}} \n{SERIALIZERS_SUGGESTION_MESSAGE}"
211
- )
212
-
213
-
214
- class SerializationNotPossible(Exception):
215
- """Not possible to serialize to any of the media types defined for some API operation.
216
-
217
- Usually, there is still `application/json` along with less common ones, but this error happens when there is no
218
- media type that Schemathesis knows how to serialize data to.
219
- """
220
-
221
- __module__ = "builtins"
222
-
223
- @classmethod
224
- def from_media_types(cls, *media_types: str) -> "SerializationNotPossible":
225
- return cls(SERIALIZATION_NOT_POSSIBLE_MESSAGE.format(", ".join(media_types)))
226
-
227
- @classmethod
228
- def for_media_type(cls, media_type: str) -> "SerializationNotPossible":
229
- return cls(SERIALIZATION_FOR_TYPE_IS_NOT_POSSIBLE_MESSAGE.format(media_type))
230
-
231
-
232
- class InvalidRegularExpression(Exception):
233
- __module__ = "builtins"
234
-
235
-
236
- @attr.s # pragma: no mutate
237
- class HTTPError(Exception):
238
- response: "GenericResponse" = attr.ib() # pragma: no mutate
239
- url: str = attr.ib() # pragma: no mutate
240
-
241
- @classmethod
242
- def raise_for_status(cls, response: requests.Response) -> None:
243
- try:
244
- response.raise_for_status()
245
- except requests.HTTPError as exc:
246
- raise cls(response=response, url=response.url) from exc
247
-
248
- @classmethod
249
- def check_response(cls, response: requests.Response, schema_path: str) -> None:
250
- # Raising exception to provide unified behavior
251
- # E.g. it will be handled in CLI - a proper error message will be shown
252
- if 400 <= response.status_code < 600:
253
- raise cls(response=response, url=schema_path)
254
-
255
-
256
- class UsageError(Exception):
257
- """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,251 +0,0 @@
1
- from contextlib import contextmanager
2
- from functools import partial
3
- from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, TypeVar, cast
4
-
5
- import pytest
6
- from _pytest import fixtures, nodes
7
- from _pytest.config import hookimpl
8
- from _pytest.fixtures import FuncFixtureInfo
9
- from _pytest.nodes import Node
10
- from _pytest.python import Class, Function, FunctionDefinition, Metafunc, Module, PyCollector
11
- from hypothesis import reporting
12
- from hypothesis.errors import InvalidArgument
13
- from hypothesis_jsonschema._canonicalise import HypothesisRefResolutionError
14
-
15
- from .. import DataGenerationMethod
16
- from .._hypothesis import create_test
17
- from ..constants import IS_PYTEST_ABOVE_7, IS_PYTEST_ABOVE_54, RECURSIVE_REFERENCE_ERROR_MESSAGE
18
- from ..exceptions import InvalidSchema, SkipTest
19
- from ..models import APIOperation
20
- from ..utils import (
21
- PARAMETRIZE_MARKER,
22
- Ok,
23
- Result,
24
- fail_on_no_matches,
25
- get_given_args,
26
- get_given_kwargs,
27
- is_given_applied,
28
- is_schemathesis_test,
29
- merge_given_args,
30
- validate_given_args,
31
- )
32
-
33
- T = TypeVar("T", bound=Node)
34
-
35
-
36
- def create(cls: Type[T], *args: Any, **kwargs: Any) -> T:
37
- if IS_PYTEST_ABOVE_54:
38
- return cls.from_parent(*args, **kwargs) # type: ignore
39
- return cls(*args, **kwargs)
40
-
41
-
42
- class SchemathesisFunction(Function): # pylint: disable=too-many-ancestors
43
- def __init__(
44
- self,
45
- *args: Any,
46
- test_func: Callable,
47
- test_name: Optional[str] = None,
48
- data_generation_method: DataGenerationMethod,
49
- **kwargs: Any,
50
- ) -> None:
51
- super().__init__(*args, **kwargs)
52
- self.test_function = test_func
53
- self.test_name = test_name
54
- self.data_generation_method = data_generation_method
55
-
56
- if not IS_PYTEST_ABOVE_7:
57
- # On pytest 7, `self.obj` is already `partial`
58
- def _getobj(self) -> partial:
59
- """Tests defined as methods require `self` as the first argument.
60
-
61
- This method is called only for this case.
62
- """
63
- return partial(self.obj, self.parent.obj) # type: ignore
64
-
65
-
66
- class SchemathesisCase(PyCollector):
67
- def __init__(self, test_function: Callable, *args: Any, **kwargs: Any) -> None:
68
- self.given_kwargs: Optional[Dict[str, Any]]
69
- given_args = get_given_args(test_function)
70
- given_kwargs = get_given_kwargs(test_function)
71
-
72
- def _init_with_valid_test(_test_function: Callable, _args: Tuple, _kwargs: Dict[str, Any]) -> None:
73
- self.test_function = _test_function
74
- self.is_invalid_test = False
75
- self.given_kwargs = merge_given_args(test_function, _args, _kwargs)
76
-
77
- if is_given_applied(test_function):
78
- failing_test = validate_given_args(test_function, given_args, given_kwargs)
79
- if failing_test is not None:
80
- self.test_function = failing_test
81
- self.is_invalid_test = True
82
- self.given_kwargs = None
83
- else:
84
- _init_with_valid_test(test_function, given_args, given_kwargs)
85
- else:
86
- _init_with_valid_test(test_function, given_args, given_kwargs)
87
- self.schemathesis_case = getattr(test_function, PARAMETRIZE_MARKER)
88
- super().__init__(*args, **kwargs)
89
-
90
- def _get_test_name(self, operation: APIOperation, data_generation_method: DataGenerationMethod) -> str:
91
- return f"{self.name}[{operation.verbose_name}][{data_generation_method.as_short_name()}]"
92
-
93
- def _gen_items(
94
- self, result: Result[APIOperation, InvalidSchema], data_generation_method: DataGenerationMethod
95
- ) -> Generator[SchemathesisFunction, None, None]:
96
- """Generate all tests for the given API operation.
97
-
98
- Could produce more than one test item if
99
- parametrization is applied via ``pytest.mark.parametrize`` or ``pytest_generate_tests``.
100
-
101
- This implementation is based on the original one in pytest, but with slight adjustments
102
- to produce tests out of hypothesis ones.
103
- """
104
- if isinstance(result, Ok):
105
- operation = result.ok()
106
- if self.is_invalid_test:
107
- funcobj = self.test_function
108
- else:
109
- funcobj = create_test(
110
- operation=operation,
111
- test=self.test_function,
112
- _given_kwargs=self.given_kwargs,
113
- data_generation_method=data_generation_method,
114
- )
115
- name = self._get_test_name(operation, data_generation_method)
116
- else:
117
- error = result.err()
118
- funcobj = error.as_failing_test_function()
119
- name = self.name
120
- # `full_path` is always available in this case
121
- if error.method:
122
- name += f"[{error.method.upper()} {error.full_path}]"
123
- else:
124
- name += f"[{error.full_path}]"
125
- name += f"[{data_generation_method.as_short_name()}]"
126
-
127
- cls = self._get_class_parent()
128
- definition: FunctionDefinition = create(FunctionDefinition, name=self.name, parent=self.parent, callobj=funcobj)
129
- fixturemanager = self.session._fixturemanager
130
- fixtureinfo = fixturemanager.getfixtureinfo(definition, funcobj, cls)
131
-
132
- metafunc = self._parametrize(cls, definition, fixtureinfo)
133
-
134
- if isinstance(self.parent, Class):
135
- # On pytest 7, Class collects the test methods directly, therefore
136
- funcobj = partial(funcobj, self.parent.obj)
137
-
138
- if not metafunc._calls:
139
- yield create(
140
- SchemathesisFunction,
141
- name=name,
142
- parent=self.parent,
143
- callobj=funcobj,
144
- fixtureinfo=fixtureinfo,
145
- test_func=self.test_function,
146
- originalname=self.name,
147
- data_generation_method=data_generation_method,
148
- )
149
- else:
150
- fixtures.add_funcarg_pseudo_fixture_def(self.parent, metafunc, fixturemanager) # type: ignore[arg-type]
151
- fixtureinfo.prune_dependency_tree()
152
- for callspec in metafunc._calls:
153
- subname = f"{name}[{callspec.id}]"
154
- yield create(
155
- SchemathesisFunction,
156
- name=subname,
157
- parent=self.parent,
158
- callspec=callspec,
159
- callobj=funcobj,
160
- fixtureinfo=fixtureinfo,
161
- keywords={callspec.id: True},
162
- originalname=name,
163
- test_func=self.test_function,
164
- data_generation_method=data_generation_method,
165
- )
166
-
167
- def _get_class_parent(self) -> Optional[Type]:
168
- clscol = self.getparent(Class)
169
- return clscol.obj if clscol else None
170
-
171
- def _parametrize(
172
- self, cls: Optional[Type], definition: FunctionDefinition, fixtureinfo: FuncFixtureInfo
173
- ) -> Metafunc:
174
- parent = self.getparent(Module)
175
- module = parent.obj if parent is not None else parent
176
- kwargs = {"cls": cls, "module": module}
177
- if IS_PYTEST_ABOVE_7:
178
- # Avoiding `Metafunc` is quite problematic for now, as there are quite a lot of internals we rely on
179
- kwargs["_ispytest"] = True
180
- metafunc = Metafunc(definition, fixtureinfo, self.config, **kwargs)
181
- methods = []
182
- if hasattr(module, "pytest_generate_tests"):
183
- methods.append(module.pytest_generate_tests)
184
- if hasattr(cls, "pytest_generate_tests"):
185
- cls = cast(Type, cls)
186
- methods.append(cls().pytest_generate_tests)
187
- self.ihook.pytest_generate_tests.call_extra(methods, {"metafunc": metafunc})
188
- return metafunc
189
-
190
- def collect(self) -> List[Function]: # type: ignore
191
- """Generate different test items for all API operations available in the given schema."""
192
- try:
193
- items = [
194
- item
195
- for data_generation_method in self.schemathesis_case.data_generation_methods
196
- for operation in self.schemathesis_case.get_all_operations()
197
- for item in self._gen_items(operation, data_generation_method)
198
- ]
199
- if not items:
200
- fail_on_no_matches(self.nodeid)
201
- return items
202
- except Exception:
203
- pytest.fail("Error during collection")
204
-
205
-
206
- @hookimpl(hookwrapper=True) # type:ignore # pragma: no mutate
207
- def pytest_pycollect_makeitem(collector: nodes.Collector, name: str, obj: Any) -> Generator[None, Any, None]:
208
- """Switch to a different collector if the test is parametrized marked by schemathesis."""
209
- outcome = yield
210
- if is_schemathesis_test(obj):
211
- outcome.force_result(create(SchemathesisCase, parent=collector, test_function=obj, name=name))
212
- else:
213
- outcome.get_result()
214
-
215
-
216
- IGNORED_HYPOTHESIS_OUTPUT = ("Falsifying example",)
217
-
218
-
219
- def hypothesis_reporter(value: str) -> None:
220
- if value.startswith(IGNORED_HYPOTHESIS_OUTPUT):
221
- return
222
- reporting.default(value)
223
-
224
-
225
- @contextmanager
226
- def skip_unnecessary_hypothesis_output() -> Generator:
227
- """Avoid printing Hypothesis output that is not necessary in Schemathesis' pytest plugin."""
228
- with reporting.with_reporter(hypothesis_reporter): # type: ignore
229
- yield
230
-
231
-
232
- @hookimpl(hookwrapper=True) # pragma: no mutate
233
- def pytest_pyfunc_call(pyfuncitem): # type:ignore
234
- """It is possible to have a Hypothesis exception in runtime.
235
-
236
- For example - kwargs validation is failed for some strategy.
237
- """
238
- if isinstance(pyfuncitem, SchemathesisFunction):
239
- with skip_unnecessary_hypothesis_output():
240
- outcome = yield
241
- try:
242
- outcome.get_result()
243
- except InvalidArgument as exc:
244
- raise InvalidSchema(exc.args[0]) from None
245
- except HypothesisRefResolutionError:
246
- pytest.skip(RECURSIVE_REFERENCE_ERROR_MESSAGE)
247
- except SkipTest as exc:
248
- pytest.skip(exc.args[0])
249
- else:
250
- outcome = yield
251
- outcome.get_result()