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,521 +0,0 @@
1
- import base64
2
- import os
3
- import platform
4
- import shutil
5
- import time
6
- from queue import Queue
7
- from typing import Any, Dict, Generator, List, Optional, Tuple, Union, cast
8
-
9
- import click
10
- import requests
11
-
12
- from ... import service
13
- from ..._compat import metadata
14
- from ...constants import DISCORD_LINK, FLAKY_FAILURE_MESSAGE, CodeSampleStyle, __version__
15
- from ...models import Response, Status
16
- from ...runner import events
17
- from ...runner.serialization import SerializedCase, SerializedError, SerializedTestResult, deduplicate_failures
18
- from ..context import ExecutionContext
19
- from ..handlers import EventHandler
20
-
21
- DISABLE_SCHEMA_VALIDATION_MESSAGE = (
22
- "\nYou can disable input schema validation with --validate-schema=false "
23
- "command-line option\nIn this case, Schemathesis cannot guarantee proper"
24
- " behavior during the test run"
25
- )
26
- ISSUE_TRACKER_URL = (
27
- "https://github.com/schemathesis/schemathesis/issues/new?"
28
- "labels=Status%3A+Review+Needed%2C+Type%3A+Bug&template=bug_report.md&title=%5BBUG%5D"
29
- )
30
- SPINNER_REPETITION_NUMBER = 10
31
-
32
-
33
- def get_terminal_width() -> int:
34
- # Some CI/CD providers (e.g. CircleCI) return a (0, 0) terminal size so provide a default
35
- return shutil.get_terminal_size((80, 24)).columns
36
-
37
-
38
- def display_section_name(title: str, separator: str = "=", extra: str = "", **kwargs: Any) -> None:
39
- """Print section name with separators in terminal with the given title nicely centered."""
40
- extra = extra if not extra else f" [{extra}]"
41
- message = f" {title}{extra} ".center(get_terminal_width(), separator)
42
- kwargs.setdefault("bold", True)
43
- click.secho(message, **kwargs)
44
-
45
-
46
- def display_subsection(result: SerializedTestResult, color: Optional[str] = "red") -> None:
47
- display_section_name(result.verbose_name, "_", result.data_generation_method, fg=color)
48
-
49
-
50
- def get_percentage(position: int, length: int) -> str:
51
- """Format completion percentage in square brackets."""
52
- percentage_message = f"{position * 100 // length}%".rjust(4)
53
- return f"[{percentage_message}]"
54
-
55
-
56
- def display_execution_result(context: ExecutionContext, event: events.AfterExecution) -> None:
57
- """Display an appropriate symbol for the given event's execution result."""
58
- symbol, color = {
59
- Status.success: (".", "green"),
60
- Status.failure: ("F", "red"),
61
- Status.error: ("E", "red"),
62
- Status.skip: ("S", "yellow"),
63
- }[event.status]
64
- context.current_line_length += len(symbol)
65
- click.secho(symbol, nl=False, fg=color)
66
-
67
-
68
- def display_percentage(context: ExecutionContext, event: events.AfterExecution) -> None:
69
- """Add the current progress in % to the right side of the current line."""
70
- operations_count = cast(int, context.operations_count) # is already initialized via `Initialized` event
71
- current_percentage = get_percentage(context.operations_processed, operations_count)
72
- styled = click.style(current_percentage, fg="cyan")
73
- # Total length of the message, so it will fill to the right border of the terminal.
74
- # Padding is already taken into account in `context.current_line_length`
75
- length = max(get_terminal_width() - context.current_line_length + len(styled) - len(current_percentage), 1)
76
- template = f"{{:>{length}}}"
77
- click.echo(template.format(styled))
78
-
79
-
80
- def display_summary(event: events.Finished) -> None:
81
- message, color = get_summary_output(event)
82
- display_section_name(message, fg=color)
83
-
84
-
85
- def get_summary_message_parts(event: events.Finished) -> List[str]:
86
- parts = []
87
- passed = event.passed_count
88
- if passed:
89
- parts.append(f"{passed} passed")
90
- failed = event.failed_count
91
- if failed:
92
- parts.append(f"{failed} failed")
93
- errored = event.errored_count
94
- if errored:
95
- parts.append(f"{errored} errored")
96
- skipped = event.skipped_count
97
- if skipped:
98
- parts.append(f"{skipped} skipped")
99
- return parts
100
-
101
-
102
- def get_summary_output(event: events.Finished) -> Tuple[str, str]:
103
- parts = get_summary_message_parts(event)
104
- if not parts:
105
- message = "Empty test suite"
106
- color = "yellow"
107
- else:
108
- message = f'{", ".join(parts)} in {event.running_time:.2f}s'
109
- if event.has_failures or event.has_errors:
110
- color = "red"
111
- elif event.skipped_count > 0:
112
- color = "yellow"
113
- else:
114
- color = "green"
115
- return message, color
116
-
117
-
118
- def display_hypothesis_output(hypothesis_output: List[str]) -> None:
119
- """Show falsifying examples from Hypothesis output if there are any."""
120
- if hypothesis_output:
121
- display_section_name("HYPOTHESIS OUTPUT")
122
- output = "\n".join(hypothesis_output)
123
- click.secho(output, fg="red")
124
-
125
-
126
- def display_errors(context: ExecutionContext, event: events.Finished) -> None:
127
- """Display all errors in the test run."""
128
- if not event.has_errors:
129
- return
130
-
131
- display_section_name("ERRORS")
132
- should_display_full_traceback_message = False
133
- for result in context.results:
134
- if not result.has_errors:
135
- continue
136
- should_display_full_traceback_message |= display_single_error(context, result)
137
- if event.generic_errors:
138
- display_generic_errors(context, event.generic_errors)
139
- if should_display_full_traceback_message and not context.show_errors_tracebacks:
140
- click.secho(
141
- "Add this option to your command line parameters to see full tracebacks: --show-errors-tracebacks", fg="red"
142
- )
143
- click.secho(
144
- f"\nIf you need assistance in solving this, feel free to join our Discord server and report it:\n\n"
145
- f" {DISCORD_LINK}\n",
146
- fg="red",
147
- )
148
-
149
-
150
- def display_single_error(context: ExecutionContext, result: SerializedTestResult) -> bool:
151
- display_subsection(result)
152
- should_display_full_traceback_message = False
153
- for error in result.errors:
154
- should_display_full_traceback_message |= _display_error(context, error, result.seed)
155
- return should_display_full_traceback_message
156
-
157
-
158
- def display_generic_errors(context: ExecutionContext, errors: List[SerializedError]) -> None:
159
- for error in errors:
160
- display_section_name(error.title or "Generic error", "_", fg="red")
161
- _display_error(context, error)
162
-
163
-
164
- def display_full_traceback_message(exception: str) -> bool:
165
- # Some errors should not trigger the message that suggests to show full tracebacks to the user
166
- return not exception.startswith("DeadlineExceeded")
167
-
168
-
169
- def _display_error(context: ExecutionContext, error: SerializedError, seed: Optional[int] = None) -> bool:
170
- if context.show_errors_tracebacks:
171
- message = error.exception_with_traceback
172
- else:
173
- message = error.exception
174
- if error.exception.startswith("InvalidSchema") and context.validate_schema:
175
- message += DISABLE_SCHEMA_VALIDATION_MESSAGE + "\n"
176
- if error.exception.startswith("DeadlineExceeded"):
177
- message += (
178
- "Consider extending the deadline with the `--hypothesis-deadline` CLI option.\n"
179
- "You can disable it completely with `--hypothesis-deadline=None`.\n"
180
- )
181
- click.secho(message, fg="red")
182
- if error.example is not None:
183
- display_example(context, error.example, seed=seed)
184
- return display_full_traceback_message(error.exception)
185
-
186
-
187
- def display_failures(context: ExecutionContext, event: events.Finished) -> None:
188
- """Display all failures in the test run."""
189
- if not event.has_failures:
190
- return
191
- relevant_results = [result for result in context.results if not result.is_errored]
192
- if not relevant_results:
193
- return
194
- display_section_name("FAILURES")
195
- for result in relevant_results:
196
- if not result.has_failures:
197
- continue
198
- display_failures_for_single_test(context, result)
199
-
200
-
201
- def display_failures_for_single_test(context: ExecutionContext, result: SerializedTestResult) -> None:
202
- """Display a failure for a single method / path."""
203
- display_subsection(result)
204
- if result.is_flaky:
205
- click.secho(FLAKY_FAILURE_MESSAGE, fg="red")
206
- click.echo()
207
- checks = deduplicate_failures(result.checks)
208
- for idx, check in enumerate(checks, 1):
209
- message: Optional[str]
210
- if check.message:
211
- message = f"{idx}. {check.message}"
212
- else:
213
- message = None
214
- display_example(context, check.example, check.response, message, result.seed)
215
- # Display every time except the last check
216
- if idx != len(checks):
217
- click.echo("\n")
218
-
219
-
220
- def reduce_schema_error(message: str) -> str:
221
- """Reduce the error schema output."""
222
- end_of_message_index = message.find(":", message.find("Failed validating"))
223
- if end_of_message_index != -1:
224
- return message[:end_of_message_index]
225
- return message
226
-
227
-
228
- def display_example(
229
- context: ExecutionContext,
230
- case: SerializedCase,
231
- response: Optional[Response] = None,
232
- message: Optional[str] = None,
233
- seed: Optional[int] = None,
234
- ) -> None:
235
- if message is not None:
236
- if not context.verbosity:
237
- message = reduce_schema_error(message)
238
- click.secho(message, fg="red")
239
- click.echo()
240
- for line in case.text_lines:
241
- click.secho(line, fg="red")
242
- click.echo()
243
- if response is not None and response.body is not None:
244
- payload = base64.b64decode(response.body).decode(response.encoding or "utf8", errors="replace")
245
- click.secho(f"----------\n\nResponse status: {response.status_code}\nResponse payload: `{payload}`\n", fg="red")
246
- if context.code_sample_style == CodeSampleStyle.python:
247
- click.secho(f"Run this Python code to reproduce this failure: \n\n {case.requests_code}\n", fg="red")
248
- if context.code_sample_style == CodeSampleStyle.curl:
249
- click.secho(f"Run this cURL command to reproduce this failure: \n\n {case.curl_code}\n", fg="red")
250
- if seed is not None:
251
- click.secho(f"Or add this option to your command line parameters: --hypothesis-seed={seed}", fg="red")
252
-
253
-
254
- def display_application_logs(context: ExecutionContext, event: events.Finished) -> None:
255
- """Print logs captured during the application run."""
256
- if not event.has_logs:
257
- return
258
- display_section_name("APPLICATION LOGS")
259
- for result in context.results:
260
- if not result.has_logs:
261
- continue
262
- display_single_log(result)
263
-
264
-
265
- def display_single_log(result: SerializedTestResult) -> None:
266
- display_subsection(result, None)
267
- click.echo("\n\n".join(result.logs))
268
-
269
-
270
- def display_statistic(context: ExecutionContext, event: events.Finished) -> None:
271
- """Format and print statistic collected by :obj:`models.TestResult`."""
272
- display_section_name("SUMMARY")
273
- click.echo()
274
- total = event.total
275
- if event.is_empty or not total:
276
- click.secho("No checks were performed.", bold=True)
277
-
278
- if total:
279
- display_checks_statistics(total)
280
-
281
- if context.cassette_path:
282
- click.echo()
283
- category = click.style("Network log", bold=True)
284
- click.secho(f"{category}: {context.cassette_path}")
285
-
286
- if context.junit_xml_file:
287
- click.echo()
288
- category = click.style("JUnit XML file", bold=True)
289
- click.secho(f"{category}: {context.junit_xml_file}")
290
-
291
- handle_service_integration(context)
292
-
293
-
294
- def handle_service_integration(context: ExecutionContext) -> None:
295
- """If Schemathesis.io integration is enabled, wait for the handler & print the resulting status."""
296
- if context.service:
297
- click.echo()
298
- title = click.style("Schemathesis.io", bold=True)
299
- event = wait_for_service_handler(context.service.queue, title)
300
- color = {
301
- service.Completed: "green",
302
- service.Error: "red",
303
- service.Timeout: "red",
304
- }[event.__class__]
305
- status = click.style(event.name, fg=color, bold=True)
306
- click.echo(f"{title}: {status}\r", nl=False)
307
- click.echo()
308
- if isinstance(event, service.Completed):
309
- report_title = click.style("Report", bold=True)
310
- click.echo(f"{report_title}: {event.short_url}")
311
- if isinstance(event, service.Error):
312
- click.echo()
313
- display_service_error(event)
314
-
315
-
316
- def display_service_error(event: service.Error) -> None:
317
- """Show information about an error during communication with Schemathesis.io."""
318
- if isinstance(event.exception, requests.HTTPError):
319
- status_code = event.exception.response.status_code
320
- click.secho(f"Schemathesis.io responded with HTTP {status_code}", fg="red")
321
- if 500 <= status_code <= 599:
322
- # Server error, should be resolved soon
323
- click.secho(
324
- "It is likely that we are already notified about the issue and working on a fix\n"
325
- "Please, try again in 30 minutes",
326
- fg="red",
327
- )
328
- elif status_code == 401:
329
- # Likely an invalid token
330
- click.secho(
331
- "Please, check that you use the proper CLI access token\n"
332
- "See https://schemathesis.readthedocs.io/en/stable/service.html for more details",
333
- fg="red",
334
- )
335
- else:
336
- # Other client-side errors are likely caused by a bug on the CLI side
337
- ask_to_report(event)
338
- elif isinstance(event.exception, requests.RequestException):
339
- ask_to_report(event, report_to_issues=False)
340
- else:
341
- ask_to_report(event)
342
-
343
-
344
- def ask_to_report(event: service.Error, report_to_issues: bool = True, extra: str = "") -> None:
345
- # Likely an internal Schemathesis error
346
- message = event.get_message(True)
347
- if isinstance(event.exception, requests.RequestException) and event.exception.response is not None:
348
- response = f"Response: {event.exception.response.text}"
349
- else:
350
- response = ""
351
- if report_to_issues:
352
- ask = f"Please, consider reporting the traceback below it to our issue tracker: {ISSUE_TRACKER_URL}\n"
353
- else:
354
- ask = ""
355
- click.secho(
356
- f"An error happened during uploading reports to Schemathesis.io:\n"
357
- f"{extra}"
358
- f"{ask}"
359
- f"{response}"
360
- f"\n {message.strip()}",
361
- fg="red",
362
- )
363
-
364
-
365
- def wait_for_service_handler(queue: Queue, title: str) -> service.Event:
366
- """Wait for the Schemathesis.io handler to finish its job."""
367
- start = time.monotonic()
368
- spinner = create_spinner(SPINNER_REPETITION_NUMBER)
369
- # The testing process it done and we need to wait for the Schemathesis.io handler to finish
370
- # It might still have some data to send
371
- while queue.empty():
372
- if time.monotonic() - start >= service.WORKER_FINISH_TIMEOUT:
373
- return service.Timeout()
374
- click.echo(f"{title}: {next(spinner)}\r", nl=False)
375
- time.sleep(service.WORKER_CHECK_PERIOD)
376
- return queue.get()
377
-
378
-
379
- def create_spinner(repetitions: int) -> Generator[str, None, None]:
380
- """A simple spinner that yields its individual characters."""
381
- assert repetitions > 0, "The number of repetitions should be greater than zero"
382
- while True:
383
- for ch in "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏":
384
- # Skip branch coverage, as it is not possible because of the assertion above
385
- for _ in range(repetitions): # pragma: no branch
386
- yield ch
387
-
388
-
389
- def display_checks_statistics(total: Dict[str, Dict[Union[str, Status], int]]) -> None:
390
- padding = 20
391
- col1_len = max(map(len, total.keys())) + padding
392
- col2_len = len(str(max(total.values(), key=lambda v: v["total"])["total"])) * 2 + padding
393
- col3_len = padding
394
- click.secho("Performed checks:", bold=True)
395
- template = f" {{:{col1_len}}}{{:{col2_len}}}{{:{col3_len}}}"
396
- for check_name, results in total.items():
397
- display_check_result(check_name, results, template)
398
-
399
-
400
- def display_check_result(check_name: str, results: Dict[Union[str, Status], int], template: str) -> None:
401
- """Show results of single check execution."""
402
- if Status.failure in results:
403
- verdict = "FAILED"
404
- color = "red"
405
- else:
406
- verdict = "PASSED"
407
- color = "green"
408
- success = results.get(Status.success, 0)
409
- total = results.get("total", 0)
410
- click.echo(template.format(check_name, f"{success} / {total} passed", click.style(verdict, fg=color, bold=True)))
411
-
412
-
413
- def display_internal_error(context: ExecutionContext, event: events.InternalError) -> None:
414
- click.secho(event.message, fg="red")
415
- if event.exception:
416
- if context.show_errors_tracebacks:
417
- message = (
418
- f"Error: {event.exception_with_traceback}\n"
419
- f"Please, consider reporting the traceback below it to our issue tracker: {ISSUE_TRACKER_URL}"
420
- )
421
- else:
422
- message = (
423
- f"Error: {event.exception}\n"
424
- f"Add this option to your command line parameters to see full tracebacks: --show-errors-tracebacks\n"
425
- )
426
- if event.exception_type == "schemathesis.exceptions.SchemaLoadingError":
427
- message += "\n" + DISABLE_SCHEMA_VALIDATION_MESSAGE
428
- click.secho(message, fg="red")
429
-
430
-
431
- def handle_initialized(context: ExecutionContext, event: events.Initialized) -> None:
432
- """Display information about the test session."""
433
- context.operations_count = cast(int, event.operations_count) # INVARIANT: should not be `None`
434
- display_section_name("Schemathesis test session starts")
435
- versions = (
436
- f"platform {platform.system()} -- "
437
- f"Python {platform.python_version()}, "
438
- f"schemathesis-{__version__}, "
439
- f"hypothesis-{metadata.version('hypothesis')}, "
440
- f"hypothesis_jsonschema-{metadata.version('hypothesis_jsonschema')}, "
441
- f"jsonschema-{metadata.version('jsonschema')}"
442
- )
443
- click.echo(versions)
444
- click.echo(f"rootdir: {os.getcwd()}")
445
- click.echo(f"Hypothesis: {context.hypothesis_settings.show_changed()}")
446
- if event.location is not None:
447
- click.echo(f"Schema location: {event.location}")
448
- click.echo(f"Base URL: {event.base_url}")
449
- click.echo(f"Specification version: {event.specification_name}")
450
- click.echo(f"Workers: {context.workers_num}")
451
- click.secho(f"Collected API operations: {context.operations_count}", bold=True)
452
- if context.service is not None:
453
- click.secho("Schemathesis.io: ENABLED", bold=True)
454
- if context.operations_count >= 1:
455
- click.echo()
456
-
457
-
458
- TRUNCATION_PLACEHOLDER = "[...]"
459
-
460
-
461
- def handle_before_execution(context: ExecutionContext, event: events.BeforeExecution) -> None:
462
- """Display what method / path will be tested next."""
463
- # We should display execution result + percentage in the end. For example:
464
- max_length = get_terminal_width() - len(" . [XXX%]") - len(TRUNCATION_PLACEHOLDER)
465
- message = event.verbose_name
466
- if event.recursion_level > 0:
467
- message = f"{' ' * event.recursion_level}-> {message}"
468
- # This value is not `None` - the value is set in runtime before this line
469
- context.operations_count += 1 # type: ignore
470
-
471
- message = message[:max_length] + (message[max_length:] and "[...]") + " "
472
- context.current_line_length = len(message)
473
- click.echo(message, nl=False)
474
-
475
-
476
- def handle_after_execution(context: ExecutionContext, event: events.AfterExecution) -> None:
477
- """Display the execution result + current progress at the same line with the method / path names."""
478
- context.operations_processed += 1
479
- context.results.append(event.result)
480
- display_execution_result(context, event)
481
- display_percentage(context, event)
482
-
483
-
484
- def handle_finished(context: ExecutionContext, event: events.Finished) -> None:
485
- """Show the outcome of the whole testing session."""
486
- click.echo()
487
- display_hypothesis_output(context.hypothesis_output)
488
- display_errors(context, event)
489
- display_failures(context, event)
490
- display_application_logs(context, event)
491
- display_statistic(context, event)
492
- click.echo()
493
- display_summary(event)
494
-
495
-
496
- def handle_interrupted(context: ExecutionContext, event: events.Interrupted) -> None:
497
- click.echo()
498
- display_section_name("KeyboardInterrupt", "!", bold=False)
499
-
500
-
501
- def handle_internal_error(context: ExecutionContext, event: events.InternalError) -> None:
502
- display_internal_error(context, event)
503
- raise click.Abort
504
-
505
-
506
- class DefaultOutputStyleHandler(EventHandler):
507
- def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
508
- """Choose and execute a proper handler for the given event."""
509
- if isinstance(event, events.Initialized):
510
- handle_initialized(context, event)
511
- if isinstance(event, events.BeforeExecution):
512
- handle_before_execution(context, event)
513
- if isinstance(event, events.AfterExecution):
514
- context.hypothesis_output.extend(event.hypothesis_output)
515
- handle_after_execution(context, event)
516
- if isinstance(event, events.Finished):
517
- handle_finished(context, event)
518
- if isinstance(event, events.Interrupted):
519
- handle_interrupted(context, event)
520
- if isinstance(event, events.InternalError):
521
- handle_internal_error(context, event)
@@ -1,40 +0,0 @@
1
- import click
2
-
3
- from ...runner import events
4
- from ..context import ExecutionContext
5
- from ..handlers import EventHandler
6
- from . import default
7
-
8
-
9
- def handle_before_execution(context: ExecutionContext, event: events.BeforeExecution) -> None:
10
- if event.recursion_level > 0:
11
- context.operations_count += 1 # type: ignore
12
-
13
-
14
- def handle_after_execution(context: ExecutionContext, event: events.AfterExecution) -> None:
15
- context.operations_processed += 1
16
- context.results.append(event.result)
17
- context.hypothesis_output.extend(event.hypothesis_output)
18
- default.display_execution_result(context, event)
19
-
20
-
21
- class ShortOutputStyleHandler(EventHandler):
22
- def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
23
- """Short output style shows single symbols in the progress bar.
24
-
25
- Otherwise, identical to the default output style.
26
- """
27
- if isinstance(event, events.Initialized):
28
- default.handle_initialized(context, event)
29
- if isinstance(event, events.BeforeExecution):
30
- handle_before_execution(context, event)
31
- if isinstance(event, events.AfterExecution):
32
- handle_after_execution(context, event)
33
- if isinstance(event, events.Finished):
34
- if context.operations_count == context.operations_processed:
35
- click.echo()
36
- default.handle_finished(context, event)
37
- if isinstance(event, events.Interrupted):
38
- default.handle_interrupted(context, event)
39
- if isinstance(event, events.InternalError):
40
- default.handle_internal_error(context, event)
schemathesis/constants.py DELETED
@@ -1,88 +0,0 @@
1
- from enum import Enum
2
- from typing import List
3
-
4
- import pytest
5
- from packaging import version
6
-
7
- from ._compat import metadata
8
-
9
- try:
10
- __version__ = metadata.version(__package__)
11
- except metadata.PackageNotFoundError:
12
- # Local run without installation
13
- __version__ = "dev"
14
-
15
- IS_PYTEST_ABOVE_54 = version.parse(pytest.__version__) >= version.parse("5.4.0")
16
- IS_PYTEST_ABOVE_7 = version.parse(pytest.__version__) >= version.parse("7.0.0")
17
-
18
- USER_AGENT = f"schemathesis/{__version__}"
19
- SCHEMATHESIS_TEST_CASE_HEADER = "X-Schemathesis-TestCaseId"
20
- HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER = ":memory:"
21
- DISCORD_LINK = "https://discord.gg/R9ASRAmHnA"
22
- # Maximum test running time
23
- DEFAULT_DEADLINE = 15000 # pragma: no mutate
24
- DEFAULT_RESPONSE_TIMEOUT = 10000 # pragma: no mutate
25
- DEFAULT_STATEFUL_RECURSION_LIMIT = 5 # pragma: no mutate
26
- HTTP_METHODS = frozenset({"get", "put", "post", "delete", "options", "head", "patch", "trace"})
27
- RECURSIVE_REFERENCE_ERROR_MESSAGE = (
28
- "Currently, Schemathesis can't generate data for this operation due to "
29
- "recursive references in the operation definition. See more information in "
30
- "this issue - https://github.com/schemathesis/schemathesis/issues/947"
31
- )
32
- SERIALIZERS_SUGGESTION_MESSAGE = (
33
- "You can register your own serializer with `schemathesis.serializers.register` "
34
- "and Schemathesis will be able to make API calls with this media type. \n"
35
- "See https://schemathesis.readthedocs.io/en/stable/how.html#payload-serialization for more information."
36
- )
37
- FLAKY_FAILURE_MESSAGE = "[FLAKY] Schemathesis was not able to reliably reproduce this failure"
38
-
39
-
40
- class DataGenerationMethod(str, Enum):
41
- """Defines what data Schemathesis generates for tests."""
42
-
43
- # Generate data, that fits the API schema
44
- positive = "positive"
45
- # Doesn't fit the API schema
46
- negative = "negative"
47
-
48
- @classmethod
49
- def default(cls) -> "DataGenerationMethod":
50
- return cls.positive
51
-
52
- @classmethod
53
- def all(cls) -> List["DataGenerationMethod"]:
54
- return list(DataGenerationMethod)
55
-
56
- def as_short_name(self) -> str:
57
- return {
58
- DataGenerationMethod.positive: "P",
59
- DataGenerationMethod.negative: "N",
60
- }[self]
61
-
62
- @property
63
- def is_negative(self) -> bool:
64
- return self == DataGenerationMethod.negative
65
-
66
-
67
- DEFAULT_DATA_GENERATION_METHODS = (DataGenerationMethod.default(),)
68
-
69
-
70
- class CodeSampleStyle(str, Enum):
71
- """Controls the style of code samples for failure reproduction."""
72
-
73
- python = "python"
74
- curl = "curl"
75
-
76
- @classmethod
77
- def default(cls) -> "CodeSampleStyle":
78
- return cls.curl
79
-
80
- @classmethod
81
- def from_str(cls, value: str) -> "CodeSampleStyle":
82
- try:
83
- return cls[value]
84
- except KeyError:
85
- available_styles = ", ".join(cls)
86
- raise ValueError(
87
- f"Invalid value for code sample style: {value}. Available styles: {available_styles}"
88
- ) from None