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