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,351 +0,0 @@
1
- import base64
2
- import json
3
- import re
4
- import sys
5
- import threading
6
- from queue import Queue
7
- from typing import IO, Any, Dict, Generator, Iterator, List, Optional, cast
8
-
9
- import attr
10
- import click
11
- import requests
12
- from requests.cookies import RequestsCookieJar
13
- from requests.structures import CaseInsensitiveDict
14
- from yaml.emitter import Emitter
15
-
16
- from .. import constants
17
- from ..models import Request, Response
18
- from ..runner import events
19
- from ..runner.serialization import SerializedCheck, SerializedInteraction
20
- from ..types import RequestCert
21
- from .context import ExecutionContext
22
- from .handlers import EventHandler
23
-
24
- # Wait until the worker terminates
25
- WRITER_WORKER_JOIN_TIMEOUT = 1
26
-
27
-
28
- @attr.s(slots=True) # pragma: no mutate
29
- class CassetteWriter(EventHandler):
30
- """Write interactions in a YAML cassette.
31
-
32
- A low-level interface is used to write data to YAML file during the test run and reduce the delay at
33
- the end of the test run.
34
- """
35
-
36
- file_handle: click.utils.LazyFile = attr.ib() # pragma: no mutate
37
- preserve_exact_body_bytes: bool = attr.ib() # pragma: no mutate
38
- queue: Queue = attr.ib(factory=Queue) # pragma: no mutate
39
- worker: threading.Thread = attr.ib(init=False) # pragma: no mutate
40
-
41
- def __attrs_post_init__(self) -> None:
42
- self.worker = threading.Thread(
43
- target=worker,
44
- kwargs={
45
- "file_handle": self.file_handle,
46
- "preserve_exact_body_bytes": self.preserve_exact_body_bytes,
47
- "queue": self.queue,
48
- },
49
- )
50
- self.worker.start()
51
-
52
- def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
53
- if isinstance(event, events.Initialized):
54
- # In the beginning we write metadata and start `http_interactions` list
55
- self.queue.put(Initialize())
56
- if isinstance(event, events.AfterExecution):
57
- # Seed is always present at this point, the original Optional[int] type is there because `TestResult`
58
- # instance is created before `seed` is generated on the hypothesis side
59
- seed = cast(int, event.result.seed)
60
- self.queue.put(
61
- Process(
62
- seed=seed,
63
- interactions=event.result.interactions,
64
- )
65
- )
66
- if isinstance(event, events.Finished):
67
- self.shutdown()
68
-
69
- def shutdown(self) -> None:
70
- self.queue.put(Finalize())
71
- self._stop_worker()
72
-
73
- def _stop_worker(self) -> None:
74
- self.worker.join(WRITER_WORKER_JOIN_TIMEOUT)
75
-
76
-
77
- @attr.s(slots=True) # pragma: no mutate
78
- class Initialize:
79
- """Start up, the first message to make preparations before proceeding the input data."""
80
-
81
-
82
- @attr.s(slots=True) # pragma: no mutate
83
- class Process:
84
- """A new chunk of data should be processed."""
85
-
86
- seed: int = attr.ib() # pragma: no mutate
87
- interactions: List[SerializedInteraction] = attr.ib() # pragma: no mutate
88
-
89
-
90
- @attr.s(slots=True) # pragma: no mutate
91
- class Finalize:
92
- """The work is done and there will be no more messages to process."""
93
-
94
-
95
- def get_command_representation() -> str:
96
- """Get how Schemathesis was run."""
97
- # It is supposed to be executed from Schemathesis CLI, not via Click's `command.invoke`
98
- if not sys.argv[0].endswith(("schemathesis", "st")):
99
- return "<unknown entrypoint>"
100
- args = " ".join(sys.argv[1:])
101
- return f"st {args}"
102
-
103
-
104
- def worker(file_handle: click.utils.LazyFile, preserve_exact_body_bytes: bool, queue: Queue) -> None:
105
- """Write YAML to a file in an incremental manner.
106
-
107
- This implementation doesn't use `pyyaml` package and composes YAML manually as string due to the following reasons:
108
- - It is much faster. The string-based approach gives only ~2.5% time overhead when `yaml.CDumper` has ~11.2%;
109
- - Implementation complexity. We have a quite simple format where all values are strings, and it is much simpler to
110
- implement it with string composition rather than with adjusting `yaml.Serializer` to emit explicit types.
111
- Another point is that with `pyyaml` we need to emit events and handle some low-level details like providing
112
- tags, anchors to have incremental writing, with strings it is much simpler.
113
- """
114
- current_id = 1
115
- stream = file_handle.open()
116
-
117
- def format_header_values(values: List[str]) -> str:
118
- return "\n".join(f" - {json.dumps(v)}" for v in values)
119
-
120
- def format_headers(headers: Dict[str, List[str]]) -> str:
121
- return "\n".join(f" {name}:\n{format_header_values(values)}" for name, values in headers.items())
122
-
123
- def format_check_message(message: Optional[str]) -> str:
124
- return "~" if message is None else f"{repr(message)}"
125
-
126
- def format_checks(checks: List[SerializedCheck]) -> str:
127
- return "\n".join(
128
- f" - name: '{check.name}'\n status: '{check.value.name.upper()}'\n message: {format_check_message(check.message)}"
129
- for check in checks
130
- )
131
-
132
- if preserve_exact_body_bytes:
133
-
134
- def format_request_body(output: IO, request: Request) -> None:
135
- if request.body is not None:
136
- output.write(
137
- f""" body:
138
- encoding: 'utf-8'
139
- base64_string: '{request.body}'"""
140
- )
141
-
142
- def format_response_body(output: IO, response: Response) -> None:
143
- if response.body is not None:
144
- output.write(
145
- f""" body:
146
- encoding: '{response.encoding}'
147
- base64_string: '{response.body}'"""
148
- )
149
-
150
- else:
151
-
152
- def format_request_body(output: IO, request: Request) -> None:
153
- if request.body is not None:
154
- string = _safe_decode(request.body, "utf8")
155
- output.write(
156
- """ body:
157
- encoding: 'utf-8'
158
- string: """
159
- )
160
- write_double_quoted(output, string)
161
-
162
- def format_response_body(output: IO, response: Response) -> None:
163
- if response.body is not None:
164
- encoding = response.encoding or "utf8"
165
- string = _safe_decode(response.body, encoding)
166
- output.write(
167
- f""" body:
168
- encoding: '{encoding}'
169
- string: """
170
- )
171
- write_double_quoted(output, string)
172
-
173
- while True:
174
- item = queue.get()
175
- if isinstance(item, Initialize):
176
- stream.write(
177
- f"""command: '{get_command_representation()}'
178
- recorded_with: 'Schemathesis {constants.__version__}'
179
- http_interactions:"""
180
- )
181
- elif isinstance(item, Process):
182
- for interaction in item.interactions:
183
- status = interaction.status.name.upper()
184
- # Body payloads are handled via separate `stream.write` calls to avoid some allocations
185
- stream.write(
186
- f"""\n- id: '{current_id}'
187
- status: '{status}'
188
- seed: '{item.seed}'
189
- elapsed: '{interaction.response.elapsed}'
190
- recorded_at: '{interaction.recorded_at}'
191
- checks:
192
- {format_checks(interaction.checks)}
193
- request:
194
- uri: '{interaction.request.uri}'
195
- method: '{interaction.request.method}'
196
- headers:
197
- {format_headers(interaction.request.headers)}
198
- """
199
- )
200
- format_request_body(stream, interaction.request)
201
- stream.write(
202
- f"""
203
- response:
204
- status:
205
- code: '{interaction.response.status_code}'
206
- message: {json.dumps(interaction.response.message)}
207
- headers:
208
- {format_headers(interaction.response.headers)}
209
- """
210
- )
211
- format_response_body(stream, interaction.response)
212
- stream.write(
213
- f"""
214
- http_version: '{interaction.response.http_version}'"""
215
- )
216
- current_id += 1
217
- else:
218
- break
219
- file_handle.close()
220
-
221
-
222
- def _safe_decode(value: str, encoding: str) -> str:
223
- """Decode base64-encoded body bytes as a string."""
224
- return base64.b64decode(value).decode(encoding, "replace")
225
-
226
-
227
- def write_double_quoted(stream: IO, text: str) -> None:
228
- """Writes a valid YAML string enclosed in double quotes."""
229
- # Adapted from `yaml.Emitter.write_double_quoted`:
230
- # - Doesn't split the string, therefore doesn't track the current column
231
- # - Doesn't encode the input
232
- # - Allows Unicode unconditionally
233
- stream.write('"')
234
- start = end = 0
235
- length = len(text)
236
- while end <= length:
237
- ch = None
238
- if end < length:
239
- ch = text[end]
240
- if (
241
- ch is None
242
- or ch in '"\\\x85\u2028\u2029\uFEFF'
243
- or not ("\x20" <= ch <= "\x7E" or ("\xA0" <= ch <= "\uD7FF" or "\uE000" <= ch <= "\uFFFD"))
244
- ):
245
- if start < end:
246
- stream.write(text[start:end])
247
- start = end
248
- if ch is not None:
249
- # Escape character
250
- if ch in Emitter.ESCAPE_REPLACEMENTS:
251
- data = "\\" + Emitter.ESCAPE_REPLACEMENTS[ch]
252
- elif ch <= "\xFF":
253
- data = "\\x%02X" % ord(ch)
254
- elif ch <= "\uFFFF":
255
- data = "\\u%04X" % ord(ch)
256
- else:
257
- data = "\\U%08X" % ord(ch)
258
- stream.write(data)
259
- start = end + 1
260
- end += 1
261
- stream.write('"')
262
-
263
-
264
- @attr.s(slots=True) # pragma: no mutate
265
- class Replayed:
266
- interaction: Dict[str, Any] = attr.ib() # pragma: no mutate
267
- response: requests.Response = attr.ib() # pragma: no mutate
268
-
269
-
270
- def replay(
271
- cassette: Dict[str, Any],
272
- id_: Optional[str] = None,
273
- status: Optional[str] = None,
274
- uri: Optional[str] = None,
275
- method: Optional[str] = None,
276
- request_tls_verify: bool = True,
277
- request_cert: Optional[RequestCert] = None,
278
- ) -> Generator[Replayed, None, None]:
279
- """Replay saved interactions."""
280
- session = requests.Session()
281
- session.verify = request_tls_verify
282
- session.cert = request_cert
283
- for interaction in filter_cassette(cassette["http_interactions"], id_, status, uri, method):
284
- request = get_prepared_request(interaction["request"])
285
- response = session.send(request) # type: ignore
286
- yield Replayed(interaction, response)
287
-
288
-
289
- def filter_cassette(
290
- interactions: List[Dict[str, Any]],
291
- id_: Optional[str] = None,
292
- status: Optional[str] = None,
293
- uri: Optional[str] = None,
294
- method: Optional[str] = None,
295
- ) -> Iterator[Dict[str, Any]]:
296
-
297
- filters = []
298
-
299
- def id_filter(item: Dict[str, Any]) -> bool:
300
- return item["id"] == id_
301
-
302
- def status_filter(item: Dict[str, Any]) -> bool:
303
- status_ = cast(str, status)
304
- return item["status"].upper() == status_.upper()
305
-
306
- def uri_filter(item: Dict[str, Any]) -> bool:
307
- uri_ = cast(str, uri)
308
- return bool(re.search(uri_, item["request"]["uri"]))
309
-
310
- def method_filter(item: Dict[str, Any]) -> bool:
311
- method_ = cast(str, method)
312
- return bool(re.search(method_, item["request"]["method"]))
313
-
314
- if id_ is not None:
315
- filters.append(id_filter)
316
-
317
- if status is not None:
318
- filters.append(status_filter)
319
-
320
- if uri is not None:
321
- filters.append(uri_filter)
322
-
323
- if method is not None:
324
- filters.append(method_filter)
325
-
326
- def is_match(interaction: Dict[str, Any]) -> bool:
327
- return all(filter_(interaction) for filter_ in filters)
328
-
329
- return filter(is_match, interactions)
330
-
331
-
332
- def get_prepared_request(data: Dict[str, Any]) -> requests.PreparedRequest:
333
- """Create a `requests.PreparedRequest` from a serialized one."""
334
- prepared = requests.PreparedRequest()
335
- prepared.method = data["method"]
336
- prepared.url = data["uri"]
337
- prepared._cookies = RequestsCookieJar() # type: ignore
338
- if "body" in data:
339
- body = data["body"]
340
- if "base64_string" in body:
341
- content = body["base64_string"]
342
- if content:
343
- prepared.body = base64.b64decode(content)
344
- else:
345
- content = body["string"]
346
- if content:
347
- prepared.body = content.encode("utf8")
348
- # There is always 1 value in a request
349
- headers = [(key, value[0]) for key, value in data["headers"].items()]
350
- prepared.headers = CaseInsensitiveDict(headers)
351
- return prepared
@@ -1,38 +0,0 @@
1
- import os
2
- import shutil
3
- from queue import Queue
4
- from typing import List, Optional
5
-
6
- import attr
7
- import hypothesis
8
-
9
- from ..constants import CodeSampleStyle
10
- from ..runner.serialization import SerializedTestResult
11
-
12
-
13
- @attr.s(slots=True) # pragma: no mutate
14
- class ServiceContext:
15
- url: str = attr.ib() # pragma: no mutate
16
- queue: Queue = attr.ib() # pragma: no mutate
17
-
18
-
19
- @attr.s(slots=True) # pragma: no mutate
20
- class ExecutionContext:
21
- """Storage for the current context of the execution."""
22
-
23
- hypothesis_settings: hypothesis.settings = attr.ib() # pragma: no mutate
24
- hypothesis_output: List[str] = attr.ib(factory=list) # pragma: no mutate
25
- workers_num: int = attr.ib(default=1) # pragma: no mutate
26
- show_errors_tracebacks: bool = attr.ib(default=False) # pragma: no mutate
27
- validate_schema: bool = attr.ib(default=True) # pragma: no mutate
28
- operations_processed: int = attr.ib(default=0) # pragma: no mutate
29
- # It is set in runtime, from a `Initialized` event
30
- operations_count: Optional[int] = attr.ib(default=None) # pragma: no mutate
31
- current_line_length: int = attr.ib(default=0) # pragma: no mutate
32
- terminal_size: os.terminal_size = attr.ib(factory=shutil.get_terminal_size) # pragma: no mutate
33
- results: List[SerializedTestResult] = attr.ib(factory=list) # pragma: no mutate
34
- cassette_path: Optional[str] = attr.ib(default=None) # pragma: no mutate
35
- junit_xml_file: Optional[str] = attr.ib(default=None) # pragma: no mutate
36
- verbosity: int = attr.ib(default=0) # pragma: no mutate
37
- code_sample_style: CodeSampleStyle = attr.ib(default=CodeSampleStyle.default()) # pragma: no mutate
38
- service: Optional[ServiceContext] = attr.ib(default=None) # pragma: no mutate
schemathesis/cli/debug.py DELETED
@@ -1,21 +0,0 @@
1
- import json
2
-
3
- import attr
4
- from click.utils import LazyFile
5
-
6
- from ..runner import events
7
- from .handlers import EventHandler, ExecutionContext
8
-
9
-
10
- @attr.s(slots=True) # pragma: no mutate
11
- class DebugOutputHandler(EventHandler):
12
- file_handle: LazyFile = attr.ib() # pragma: no mutate
13
-
14
- def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
15
- stream = self.file_handle.open()
16
- data = event.asdict()
17
- stream.write(json.dumps(data))
18
- stream.write("\n")
19
-
20
- def shutdown(self) -> None:
21
- self.file_handle.close()
@@ -1,11 +0,0 @@
1
- from ..runner import events
2
- from .context import ExecutionContext
3
-
4
-
5
- class EventHandler:
6
- def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
7
- raise NotImplementedError
8
-
9
- def shutdown(self) -> None:
10
- # Do nothing by default
11
- pass
@@ -1,41 +0,0 @@
1
- import platform
2
- from typing import List, Optional
3
-
4
- import attr
5
- from click.utils import LazyFile
6
- from junit_xml import TestCase, TestSuite, to_xml_report_file
7
-
8
- from ..models import Status
9
- from ..runner import events
10
- from ..runner.serialization import deduplicate_failures
11
- from .handlers import EventHandler, ExecutionContext
12
-
13
-
14
- @attr.s(slots=True) # pragma: no mutate
15
- class JunitXMLHandler(EventHandler):
16
- file_handle: LazyFile = attr.ib() # pragma: no mutate
17
- test_cases: List = attr.ib(factory=list) # pragma: no mutate
18
- start_time: Optional[float] = attr.ib(default=None) # pragma: no mutate
19
-
20
- def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
21
- if isinstance(event, events.Initialized):
22
- self.start_time = event.start_time
23
- if isinstance(event, events.AfterExecution):
24
- test_case = TestCase(
25
- f"{event.result.method} {event.result.path}",
26
- elapsed_sec=event.elapsed_time,
27
- allow_multiple_subelements=True,
28
- )
29
- if event.status == Status.failure:
30
- checks = deduplicate_failures(event.result.checks)
31
- for idx, check in enumerate(checks, 1):
32
- # `check.message` is always not empty for events with `failure` status
33
- test_case.add_failure_info(message=f"{idx}. {check.message}")
34
- if event.status == Status.error:
35
- test_case.add_error_info(
36
- message=event.result.errors[-1].exception, output=event.result.errors[-1].exception_with_traceback
37
- )
38
- self.test_cases.append(test_case)
39
- if isinstance(event, events.Finished):
40
- test_suites = [TestSuite("schemathesis", test_cases=self.test_cases, hostname=platform.node())]
41
- to_xml_report_file(file_descriptor=self.file_handle, test_suites=test_suites, prettyprint=True)
@@ -1,70 +0,0 @@
1
- from enum import Enum
2
- from typing import Any, List, NoReturn, Optional, Set, Tuple, Type, Union
3
-
4
- import click
5
-
6
- from ..types import NotSet
7
-
8
-
9
- class CustomHelpMessageChoice(click.Choice):
10
- """Allows you to customize how choices are displayed in the help message."""
11
-
12
- def __init__(self, *args: Any, choices_repr: str, **kwargs: Any):
13
- super().__init__(*args, **kwargs)
14
- self.choices_repr = choices_repr
15
-
16
- def get_metavar(self, param: click.Parameter) -> str:
17
- return self.choices_repr
18
-
19
-
20
- class BaseCsvChoice(click.Choice):
21
- def parse_value(self, value: str) -> Tuple[List[str], Set[str]]:
22
- selected = [item for item in value.split(",") if item]
23
- invalid_options = set(selected) - set(self.choices)
24
- return selected, invalid_options
25
-
26
- def fail_on_invalid_options(self, invalid_options: Set[str], selected: List[str]) -> NoReturn:
27
- # Sort to keep the error output consistent with the passed values
28
- sorted_options = ", ".join(sorted(invalid_options, key=selected.index))
29
- available_options = ", ".join(self.choices)
30
- self.fail(f"invalid choice(s): {sorted_options}. Choose from {available_options}")
31
-
32
-
33
- class CsvEnumChoice(BaseCsvChoice):
34
- def __init__(self, choices: Type[Enum]):
35
- self.enum = choices
36
- super().__init__(tuple(choices.__members__))
37
-
38
- def convert( # type: ignore[return]
39
- self, value: str, param: Optional[click.core.Parameter], ctx: Optional[click.core.Context]
40
- ) -> List[Enum]:
41
- selected, invalid_options = self.parse_value(value)
42
- if not invalid_options and selected:
43
- return [self.enum[item] for item in selected]
44
- self.fail_on_invalid_options(invalid_options, selected)
45
-
46
-
47
- class CsvChoice(BaseCsvChoice):
48
- def convert(
49
- self, value: str, param: Optional[click.core.Parameter], ctx: Optional[click.core.Context]
50
- ) -> List[str]:
51
- selected, invalid_options = self.parse_value(value)
52
- if not invalid_options and selected:
53
- return selected
54
- self.fail_on_invalid_options(invalid_options, selected)
55
-
56
-
57
- not_set = NotSet()
58
-
59
-
60
- class OptionalInt(click.types.IntRange):
61
- def convert( # type: ignore
62
- self, value: str, param: Optional[click.core.Parameter], ctx: Optional[click.core.Context]
63
- ) -> Union[int, NotSet]:
64
- if value.lower() == "none":
65
- return not_set
66
- try:
67
- int(value)
68
- return super().convert(value, param, ctx)
69
- except ValueError:
70
- self.fail("%s is not a valid integer or None" % value, param, ctx)
@@ -1 +0,0 @@
1
- from . import default, short