schemathesis 3.35.3__py3-none-any.whl → 3.35.5__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.
- schemathesis/__init__.py +5 -5
- schemathesis/_hypothesis.py +12 -6
- schemathesis/_override.py +4 -4
- schemathesis/auths.py +1 -1
- schemathesis/cli/__init__.py +19 -13
- schemathesis/cli/callbacks.py +6 -4
- schemathesis/cli/cassettes.py +67 -41
- schemathesis/cli/context.py +7 -6
- schemathesis/cli/junitxml.py +1 -1
- schemathesis/cli/options.py +7 -4
- schemathesis/cli/output/default.py +5 -5
- schemathesis/cli/reporting.py +4 -2
- schemathesis/code_samples.py +4 -3
- schemathesis/exceptions.py +4 -3
- schemathesis/extra/_flask.py +4 -1
- schemathesis/extra/pytest_plugin.py +6 -3
- schemathesis/failures.py +2 -1
- schemathesis/filters.py +2 -2
- schemathesis/generation/__init__.py +2 -2
- schemathesis/generation/_hypothesis.py +1 -1
- schemathesis/generation/coverage.py +5 -5
- schemathesis/graphql.py +0 -1
- schemathesis/hooks.py +3 -3
- schemathesis/lazy.py +10 -7
- schemathesis/loaders.py +3 -3
- schemathesis/models.py +39 -15
- schemathesis/runner/__init__.py +5 -5
- schemathesis/runner/events.py +1 -1
- schemathesis/runner/impl/context.py +58 -0
- schemathesis/runner/impl/core.py +54 -61
- schemathesis/runner/impl/solo.py +17 -20
- schemathesis/runner/impl/threadpool.py +65 -71
- schemathesis/runner/serialization.py +4 -3
- schemathesis/sanitization.py +2 -1
- schemathesis/schemas.py +18 -20
- schemathesis/serializers.py +2 -0
- schemathesis/service/client.py +1 -1
- schemathesis/service/events.py +4 -1
- schemathesis/service/extensions.py +2 -2
- schemathesis/service/hosts.py +4 -2
- schemathesis/service/models.py +3 -3
- schemathesis/service/report.py +3 -3
- schemathesis/service/serialization.py +4 -2
- schemathesis/specs/graphql/loaders.py +4 -3
- schemathesis/specs/graphql/schemas.py +4 -3
- schemathesis/specs/openapi/definitions.py +1 -5
- schemathesis/specs/openapi/examples.py +92 -2
- schemathesis/specs/openapi/expressions/__init__.py +7 -0
- schemathesis/specs/openapi/expressions/extractors.py +4 -1
- schemathesis/specs/openapi/expressions/nodes.py +5 -3
- schemathesis/specs/openapi/links.py +4 -4
- schemathesis/specs/openapi/loaders.py +5 -4
- schemathesis/specs/openapi/negative/__init__.py +5 -3
- schemathesis/specs/openapi/negative/mutations.py +5 -4
- schemathesis/specs/openapi/parameters.py +11 -8
- schemathesis/specs/openapi/schemas.py +9 -10
- schemathesis/specs/openapi/security.py +6 -4
- schemathesis/specs/openapi/stateful/__init__.py +2 -2
- schemathesis/specs/openapi/stateful/statistic.py +3 -3
- schemathesis/specs/openapi/stateful/types.py +3 -2
- schemathesis/stateful/__init__.py +3 -3
- schemathesis/stateful/config.py +1 -1
- schemathesis/stateful/context.py +3 -3
- schemathesis/stateful/events.py +3 -3
- schemathesis/stateful/runner.py +5 -4
- schemathesis/stateful/sink.py +1 -1
- schemathesis/stateful/state_machine.py +5 -5
- schemathesis/stateful/statistic.py +3 -1
- schemathesis/stateful/validation.py +1 -1
- schemathesis/transports/__init__.py +2 -2
- schemathesis/transports/asgi.py +7 -0
- schemathesis/transports/auth.py +2 -1
- schemathesis/transports/content_types.py +1 -1
- schemathesis/transports/responses.py +2 -1
- schemathesis/utils.py +4 -2
- {schemathesis-3.35.3.dist-info → schemathesis-3.35.5.dist-info}/METADATA +1 -1
- schemathesis-3.35.5.dist-info/RECORD +156 -0
- schemathesis-3.35.3.dist-info/RECORD +0 -154
- {schemathesis-3.35.3.dist-info → schemathesis-3.35.5.dist-info}/WHEEL +0 -0
- {schemathesis-3.35.3.dist-info → schemathesis-3.35.5.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.35.3.dist-info → schemathesis-3.35.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,26 +7,32 @@ import time
|
|
|
7
7
|
import warnings
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from queue import Queue
|
|
10
|
-
from typing import Any, Callable, Generator, Iterable, cast
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Callable, Generator, Iterable, cast
|
|
11
11
|
|
|
12
|
-
import hypothesis
|
|
13
12
|
from hypothesis.errors import HypothesisWarning
|
|
14
13
|
|
|
15
14
|
from ..._hypothesis import create_test
|
|
16
|
-
from ...generation import DataGenerationMethod, GenerationConfig
|
|
17
15
|
from ...internal.result import Ok
|
|
18
|
-
from ...models import CheckFunction
|
|
16
|
+
from ...models import CheckFunction
|
|
19
17
|
from ...stateful import Feedback, Stateful
|
|
20
|
-
from ...targets import Target
|
|
21
18
|
from ...transports.auth import get_requests_auth
|
|
22
|
-
from ...types import RawAuth
|
|
23
19
|
from ...utils import capture_hypothesis_output
|
|
24
20
|
from .. import events
|
|
25
21
|
from .core import BaseRunner, asgi_test, get_session, handle_schema_error, network_test, run_test, wsgi_test
|
|
26
22
|
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from .context import RunnerContext
|
|
25
|
+
import hypothesis
|
|
26
|
+
|
|
27
|
+
from ...generation import DataGenerationMethod, GenerationConfig
|
|
28
|
+
from ...models import CheckFunction
|
|
29
|
+
from ...targets import Target
|
|
30
|
+
from ...types import RawAuth
|
|
31
|
+
|
|
27
32
|
|
|
28
33
|
def _run_task(
|
|
29
|
-
|
|
34
|
+
*,
|
|
35
|
+
test_func: Callable,
|
|
30
36
|
tasks_queue: Queue,
|
|
31
37
|
events_queue: Queue,
|
|
32
38
|
generator_done: threading.Event,
|
|
@@ -35,8 +41,7 @@ def _run_task(
|
|
|
35
41
|
data_generation_methods: Iterable[DataGenerationMethod],
|
|
36
42
|
settings: hypothesis.settings,
|
|
37
43
|
generation_config: GenerationConfig,
|
|
38
|
-
|
|
39
|
-
results: TestResultSet,
|
|
44
|
+
ctx: RunnerContext,
|
|
40
45
|
stateful: Stateful | None,
|
|
41
46
|
stateful_recursion_limit: int,
|
|
42
47
|
headers: dict[str, Any] | None = None,
|
|
@@ -51,10 +56,10 @@ def _run_task(
|
|
|
51
56
|
if recursion_level > stateful_recursion_limit:
|
|
52
57
|
return
|
|
53
58
|
for _result in maker(
|
|
54
|
-
|
|
59
|
+
test_func,
|
|
55
60
|
settings=settings,
|
|
56
61
|
generation_config=generation_config,
|
|
57
|
-
seed=seed,
|
|
62
|
+
seed=ctx.seed,
|
|
58
63
|
as_strategy_kwargs=as_strategy_kwargs,
|
|
59
64
|
):
|
|
60
65
|
# `result` is always `Ok` here
|
|
@@ -66,7 +71,7 @@ def _run_task(
|
|
|
66
71
|
checks,
|
|
67
72
|
data_generation_methods,
|
|
68
73
|
targets,
|
|
69
|
-
|
|
74
|
+
ctx=ctx,
|
|
70
75
|
recursion_level=recursion_level,
|
|
71
76
|
feedback=feedback,
|
|
72
77
|
headers=headers,
|
|
@@ -89,9 +94,9 @@ def _run_task(
|
|
|
89
94
|
operation = result.ok()
|
|
90
95
|
test_function = create_test(
|
|
91
96
|
operation=operation,
|
|
92
|
-
test=
|
|
97
|
+
test=test_func,
|
|
93
98
|
settings=settings,
|
|
94
|
-
seed=seed,
|
|
99
|
+
seed=ctx.seed,
|
|
95
100
|
data_generation_methods=list(data_generation_methods),
|
|
96
101
|
generation_config=generation_config,
|
|
97
102
|
as_strategy_kwargs=as_strategy_kwargs,
|
|
@@ -101,7 +106,7 @@ def _run_task(
|
|
|
101
106
|
# `feedback.get_stateful_tests`
|
|
102
107
|
_run_tests(lambda *_, **__: (items,)) # noqa: B023
|
|
103
108
|
else:
|
|
104
|
-
for event in handle_schema_error(result.err(),
|
|
109
|
+
for event in handle_schema_error(result.err(), ctx, data_generation_methods, 0):
|
|
105
110
|
events_queue.put(event)
|
|
106
111
|
|
|
107
112
|
|
|
@@ -117,8 +122,7 @@ def thread_task(
|
|
|
117
122
|
auth: RawAuth | None,
|
|
118
123
|
auth_type: str | None,
|
|
119
124
|
headers: dict[str, Any] | None,
|
|
120
|
-
|
|
121
|
-
results: TestResultSet,
|
|
125
|
+
ctx: RunnerContext,
|
|
122
126
|
stateful: Stateful | None,
|
|
123
127
|
stateful_recursion_limit: int,
|
|
124
128
|
kwargs: Any,
|
|
@@ -130,17 +134,16 @@ def thread_task(
|
|
|
130
134
|
prepared_auth = get_requests_auth(auth, auth_type)
|
|
131
135
|
with get_session(prepared_auth) as session:
|
|
132
136
|
_run_task(
|
|
133
|
-
network_test,
|
|
134
|
-
tasks_queue,
|
|
135
|
-
events_queue,
|
|
136
|
-
generator_done,
|
|
137
|
-
checks,
|
|
138
|
-
targets,
|
|
139
|
-
data_generation_methods,
|
|
140
|
-
settings,
|
|
141
|
-
generation_config,
|
|
142
|
-
|
|
143
|
-
results,
|
|
137
|
+
test_func=network_test,
|
|
138
|
+
tasks_queue=tasks_queue,
|
|
139
|
+
events_queue=events_queue,
|
|
140
|
+
generator_done=generator_done,
|
|
141
|
+
checks=checks,
|
|
142
|
+
targets=targets,
|
|
143
|
+
data_generation_methods=data_generation_methods,
|
|
144
|
+
settings=settings,
|
|
145
|
+
generation_config=generation_config,
|
|
146
|
+
ctx=ctx,
|
|
144
147
|
stateful=stateful,
|
|
145
148
|
stateful_recursion_limit=stateful_recursion_limit,
|
|
146
149
|
session=session,
|
|
@@ -158,24 +161,22 @@ def wsgi_thread_task(
|
|
|
158
161
|
data_generation_methods: Iterable[DataGenerationMethod],
|
|
159
162
|
settings: hypothesis.settings,
|
|
160
163
|
generation_config: GenerationConfig,
|
|
161
|
-
|
|
162
|
-
results: TestResultSet,
|
|
164
|
+
ctx: RunnerContext,
|
|
163
165
|
stateful: Stateful | None,
|
|
164
166
|
stateful_recursion_limit: int,
|
|
165
167
|
kwargs: Any,
|
|
166
168
|
) -> None:
|
|
167
169
|
_run_task(
|
|
168
|
-
wsgi_test,
|
|
169
|
-
tasks_queue,
|
|
170
|
-
events_queue,
|
|
171
|
-
generator_done,
|
|
172
|
-
checks,
|
|
173
|
-
targets,
|
|
174
|
-
data_generation_methods,
|
|
175
|
-
settings,
|
|
176
|
-
generation_config,
|
|
177
|
-
|
|
178
|
-
results,
|
|
170
|
+
test_func=wsgi_test,
|
|
171
|
+
tasks_queue=tasks_queue,
|
|
172
|
+
events_queue=events_queue,
|
|
173
|
+
generator_done=generator_done,
|
|
174
|
+
checks=checks,
|
|
175
|
+
targets=targets,
|
|
176
|
+
data_generation_methods=data_generation_methods,
|
|
177
|
+
settings=settings,
|
|
178
|
+
generation_config=generation_config,
|
|
179
|
+
ctx=ctx,
|
|
179
180
|
stateful=stateful,
|
|
180
181
|
stateful_recursion_limit=stateful_recursion_limit,
|
|
181
182
|
**kwargs,
|
|
@@ -192,24 +193,22 @@ def asgi_thread_task(
|
|
|
192
193
|
settings: hypothesis.settings,
|
|
193
194
|
generation_config: GenerationConfig,
|
|
194
195
|
headers: dict[str, Any] | None,
|
|
195
|
-
|
|
196
|
-
results: TestResultSet,
|
|
196
|
+
ctx: RunnerContext,
|
|
197
197
|
stateful: Stateful | None,
|
|
198
198
|
stateful_recursion_limit: int,
|
|
199
199
|
kwargs: Any,
|
|
200
200
|
) -> None:
|
|
201
201
|
_run_task(
|
|
202
|
-
asgi_test,
|
|
203
|
-
tasks_queue,
|
|
204
|
-
events_queue,
|
|
205
|
-
generator_done,
|
|
206
|
-
checks,
|
|
207
|
-
targets,
|
|
208
|
-
data_generation_methods,
|
|
209
|
-
settings,
|
|
210
|
-
generation_config,
|
|
211
|
-
|
|
212
|
-
results,
|
|
202
|
+
test_func=asgi_test,
|
|
203
|
+
tasks_queue=tasks_queue,
|
|
204
|
+
events_queue=events_queue,
|
|
205
|
+
generator_done=generator_done,
|
|
206
|
+
checks=checks,
|
|
207
|
+
targets=targets,
|
|
208
|
+
data_generation_methods=data_generation_methods,
|
|
209
|
+
settings=settings,
|
|
210
|
+
generation_config=generation_config,
|
|
211
|
+
ctx=ctx,
|
|
213
212
|
stateful=stateful,
|
|
214
213
|
stateful_recursion_limit=stateful_recursion_limit,
|
|
215
214
|
headers=headers,
|
|
@@ -228,9 +227,7 @@ class ThreadPoolRunner(BaseRunner):
|
|
|
228
227
|
|
|
229
228
|
workers_num: int = 2
|
|
230
229
|
|
|
231
|
-
def _execute(
|
|
232
|
-
self, results: TestResultSet, stop_event: threading.Event
|
|
233
|
-
) -> Generator[events.ExecutionEvent, None, None]:
|
|
230
|
+
def _execute(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
|
|
234
231
|
"""All events come from a queue where different workers push their events."""
|
|
235
232
|
# Instead of generating all tests at once, we do it when there is a free worker to pick it up
|
|
236
233
|
# This is extremely important for memory consumption when testing large schemas
|
|
@@ -251,7 +248,7 @@ class ThreadPoolRunner(BaseRunner):
|
|
|
251
248
|
break
|
|
252
249
|
# Events are pushed by workers via a separate queue
|
|
253
250
|
events_queue: Queue = Queue()
|
|
254
|
-
workers = self._init_workers(tasks_queue, events_queue,
|
|
251
|
+
workers = self._init_workers(tasks_queue, events_queue, ctx, generator_done)
|
|
255
252
|
|
|
256
253
|
def stop_workers() -> None:
|
|
257
254
|
for worker in workers:
|
|
@@ -270,12 +267,12 @@ class ThreadPoolRunner(BaseRunner):
|
|
|
270
267
|
is_finished = all(not worker.is_alive() for worker in workers)
|
|
271
268
|
while not events_queue.empty():
|
|
272
269
|
event = events_queue.get()
|
|
273
|
-
if
|
|
270
|
+
if ctx.is_stopped or isinstance(event, events.Interrupted) or self._should_stop(event):
|
|
274
271
|
# We could still have events in the queue, but ignore them to keep the logic simple
|
|
275
272
|
# for now, could be improved in the future to show more info in such corner cases
|
|
276
273
|
stop_workers()
|
|
277
274
|
is_finished = True
|
|
278
|
-
if
|
|
275
|
+
if ctx.is_stopped:
|
|
279
276
|
# Discard the event. The invariant is: the next event after `stream.stop()` is `Finished`
|
|
280
277
|
break
|
|
281
278
|
yield event
|
|
@@ -292,13 +289,13 @@ class ThreadPoolRunner(BaseRunner):
|
|
|
292
289
|
yield events.Interrupted()
|
|
293
290
|
|
|
294
291
|
def _init_workers(
|
|
295
|
-
self, tasks_queue: Queue, events_queue: Queue,
|
|
292
|
+
self, tasks_queue: Queue, events_queue: Queue, ctx: RunnerContext, generator_done: threading.Event
|
|
296
293
|
) -> list[threading.Thread]:
|
|
297
294
|
"""Initialize & start workers that will execute tests."""
|
|
298
295
|
workers = [
|
|
299
296
|
threading.Thread(
|
|
300
297
|
target=self._get_task(),
|
|
301
|
-
kwargs=self._get_worker_kwargs(tasks_queue, events_queue,
|
|
298
|
+
kwargs=self._get_worker_kwargs(tasks_queue, events_queue, ctx, generator_done),
|
|
302
299
|
name=f"schemathesis_{num}",
|
|
303
300
|
)
|
|
304
301
|
for num in range(self.workers_num)
|
|
@@ -311,7 +308,7 @@ class ThreadPoolRunner(BaseRunner):
|
|
|
311
308
|
return thread_task
|
|
312
309
|
|
|
313
310
|
def _get_worker_kwargs(
|
|
314
|
-
self, tasks_queue: Queue, events_queue: Queue,
|
|
311
|
+
self, tasks_queue: Queue, events_queue: Queue, ctx: RunnerContext, generator_done: threading.Event
|
|
315
312
|
) -> dict[str, Any]:
|
|
316
313
|
return {
|
|
317
314
|
"tasks_queue": tasks_queue,
|
|
@@ -324,8 +321,7 @@ class ThreadPoolRunner(BaseRunner):
|
|
|
324
321
|
"auth": self.auth,
|
|
325
322
|
"auth_type": self.auth_type,
|
|
326
323
|
"headers": self.headers,
|
|
327
|
-
"
|
|
328
|
-
"results": results,
|
|
324
|
+
"ctx": ctx,
|
|
329
325
|
"stateful": self.stateful,
|
|
330
326
|
"stateful_recursion_limit": self.stateful_recursion_limit,
|
|
331
327
|
"data_generation_methods": self.schema.data_generation_methods,
|
|
@@ -343,7 +339,7 @@ class ThreadPoolWSGIRunner(ThreadPoolRunner):
|
|
|
343
339
|
return wsgi_thread_task
|
|
344
340
|
|
|
345
341
|
def _get_worker_kwargs(
|
|
346
|
-
self, tasks_queue: Queue, events_queue: Queue,
|
|
342
|
+
self, tasks_queue: Queue, events_queue: Queue, ctx: RunnerContext, generator_done: threading.Event
|
|
347
343
|
) -> dict[str, Any]:
|
|
348
344
|
return {
|
|
349
345
|
"tasks_queue": tasks_queue,
|
|
@@ -353,8 +349,7 @@ class ThreadPoolWSGIRunner(ThreadPoolRunner):
|
|
|
353
349
|
"targets": self.targets,
|
|
354
350
|
"settings": self.hypothesis_settings,
|
|
355
351
|
"generation_config": self.generation_config,
|
|
356
|
-
"
|
|
357
|
-
"results": results,
|
|
352
|
+
"ctx": ctx,
|
|
358
353
|
"stateful": self.stateful,
|
|
359
354
|
"stateful_recursion_limit": self.stateful_recursion_limit,
|
|
360
355
|
"data_generation_methods": self.schema.data_generation_methods,
|
|
@@ -374,7 +369,7 @@ class ThreadPoolASGIRunner(ThreadPoolRunner):
|
|
|
374
369
|
return asgi_thread_task
|
|
375
370
|
|
|
376
371
|
def _get_worker_kwargs(
|
|
377
|
-
self, tasks_queue: Queue, events_queue: Queue,
|
|
372
|
+
self, tasks_queue: Queue, events_queue: Queue, ctx: RunnerContext, generator_done: threading.Event
|
|
378
373
|
) -> dict[str, Any]:
|
|
379
374
|
return {
|
|
380
375
|
"tasks_queue": tasks_queue,
|
|
@@ -385,8 +380,7 @@ class ThreadPoolASGIRunner(ThreadPoolRunner):
|
|
|
385
380
|
"settings": self.hypothesis_settings,
|
|
386
381
|
"generation_config": self.generation_config,
|
|
387
382
|
"headers": self.headers,
|
|
388
|
-
"
|
|
389
|
-
"results": results,
|
|
383
|
+
"ctx": ctx,
|
|
390
384
|
"stateful": self.stateful,
|
|
391
385
|
"stateful_recursion_limit": self.stateful_recursion_limit,
|
|
392
386
|
"data_generation_methods": self.schema.data_generation_methods,
|
|
@@ -15,7 +15,6 @@ from ..code_samples import get_excluded_headers
|
|
|
15
15
|
from ..exceptions import (
|
|
16
16
|
BodyInGetRequestError,
|
|
17
17
|
DeadlineExceeded,
|
|
18
|
-
FailureContext,
|
|
19
18
|
InternalError,
|
|
20
19
|
InvalidRegularExpression,
|
|
21
20
|
OperationSchemaError,
|
|
@@ -27,7 +26,6 @@ from ..exceptions import (
|
|
|
27
26
|
format_exception,
|
|
28
27
|
make_unique_by_key,
|
|
29
28
|
)
|
|
30
|
-
from ..generation import DataGenerationMethod
|
|
31
29
|
from ..models import Case, Check, Interaction, Request, Response, Status, TestPhase, TestResult, TransitionId
|
|
32
30
|
from ..transports import deserialize_payload, serialize_payload
|
|
33
31
|
|
|
@@ -35,6 +33,9 @@ if TYPE_CHECKING:
|
|
|
35
33
|
import hypothesis.errors
|
|
36
34
|
from requests.structures import CaseInsensitiveDict
|
|
37
35
|
|
|
36
|
+
from ..failures import FailureContext
|
|
37
|
+
from ..generation import DataGenerationMethod
|
|
38
|
+
|
|
38
39
|
|
|
39
40
|
@dataclass
|
|
40
41
|
class SerializedCase:
|
|
@@ -389,7 +390,7 @@ def _scalar_name_from_error(exception: hypothesis.errors.InvalidArgument) -> str
|
|
|
389
390
|
@dataclass
|
|
390
391
|
class SerializedInteraction:
|
|
391
392
|
request: Request
|
|
392
|
-
response: Response
|
|
393
|
+
response: Response | None
|
|
393
394
|
checks: list[SerializedCheck]
|
|
394
395
|
status: Status
|
|
395
396
|
data_generation_method: DataGenerationMethod
|
schemathesis/sanitization.py
CHANGED
|
@@ -246,6 +246,7 @@ def sanitize_serialized_case(case: SerializedCase, *, config: Config | None = No
|
|
|
246
246
|
|
|
247
247
|
def sanitize_serialized_interaction(interaction: SerializedInteraction, *, config: Config | None = None) -> None:
|
|
248
248
|
sanitize_request(interaction.request, config=config)
|
|
249
|
-
|
|
249
|
+
if interaction.response is not None:
|
|
250
|
+
sanitize_value(interaction.response.headers, config=config)
|
|
250
251
|
for check in interaction.checks:
|
|
251
252
|
sanitize_serialized_check(check, config=config)
|
schemathesis/schemas.py
CHANGED
|
@@ -18,10 +18,6 @@ from typing import (
|
|
|
18
18
|
)
|
|
19
19
|
from urllib.parse import quote, unquote, urljoin, urlparse, urlsplit, urlunsplit
|
|
20
20
|
|
|
21
|
-
import hypothesis
|
|
22
|
-
from hypothesis.strategies import SearchStrategy
|
|
23
|
-
from pyrate_limiter import Limiter
|
|
24
|
-
|
|
25
21
|
from ._dependency_versions import IS_PYRATE_LIMITER_ABOVE_3
|
|
26
22
|
from ._hypothesis import create_test
|
|
27
23
|
from .auths import AuthStorage
|
|
@@ -48,25 +44,29 @@ from .internal.deprecation import warn_filtration_arguments
|
|
|
48
44
|
from .internal.output import OutputConfig
|
|
49
45
|
from .internal.result import Ok, Result
|
|
50
46
|
from .models import APIOperation, Case
|
|
51
|
-
from .stateful import Stateful, StatefulTest
|
|
52
|
-
from .stateful.state_machine import APIStateMachine
|
|
53
|
-
from .types import (
|
|
54
|
-
Body,
|
|
55
|
-
Cookies,
|
|
56
|
-
Filter,
|
|
57
|
-
FormData,
|
|
58
|
-
GenericTest,
|
|
59
|
-
Headers,
|
|
60
|
-
NotSet,
|
|
61
|
-
PathParameters,
|
|
62
|
-
Query,
|
|
63
|
-
Specification,
|
|
64
|
-
)
|
|
65
47
|
from .utils import PARAMETRIZE_MARKER, GivenInput, given_proxy
|
|
66
48
|
|
|
67
49
|
if TYPE_CHECKING:
|
|
50
|
+
import hypothesis
|
|
51
|
+
from hypothesis.strategies import SearchStrategy
|
|
52
|
+
from pyrate_limiter import Limiter
|
|
53
|
+
|
|
54
|
+
from .stateful import Stateful, StatefulTest
|
|
55
|
+
from .stateful.state_machine import APIStateMachine
|
|
68
56
|
from .transports import Transport
|
|
69
57
|
from .transports.responses import GenericResponse
|
|
58
|
+
from .types import (
|
|
59
|
+
Body,
|
|
60
|
+
Cookies,
|
|
61
|
+
Filter,
|
|
62
|
+
FormData,
|
|
63
|
+
GenericTest,
|
|
64
|
+
Headers,
|
|
65
|
+
NotSet,
|
|
66
|
+
PathParameters,
|
|
67
|
+
Query,
|
|
68
|
+
Specification,
|
|
69
|
+
)
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
C = TypeVar("C", bound=Case)
|
|
@@ -510,7 +510,6 @@ class BaseSchema(Mapping):
|
|
|
510
510
|
**kwargs: Any,
|
|
511
511
|
) -> SearchStrategy:
|
|
512
512
|
"""Build a strategy for generating test cases for all defined API operations."""
|
|
513
|
-
assert len(self) > 0, "No API operations found"
|
|
514
513
|
strategies = [
|
|
515
514
|
operation.ok().as_strategy(
|
|
516
515
|
hooks=hooks,
|
|
@@ -548,7 +547,6 @@ class APIOperationMap(Mapping):
|
|
|
548
547
|
**kwargs: Any,
|
|
549
548
|
) -> SearchStrategy:
|
|
550
549
|
"""Build a strategy for generating test cases for all API operations defined in this subset."""
|
|
551
|
-
assert len(self._data) > 0, "No API operations found"
|
|
552
550
|
strategies = [
|
|
553
551
|
operation.as_strategy(
|
|
554
552
|
hooks=hooks,
|
schemathesis/serializers.py
CHANGED
schemathesis/service/client.py
CHANGED
|
@@ -11,7 +11,6 @@ import requests
|
|
|
11
11
|
from requests.adapters import HTTPAdapter, Retry
|
|
12
12
|
|
|
13
13
|
from ..constants import USER_AGENT
|
|
14
|
-
from .ci import CIProvider
|
|
15
14
|
from .constants import CI_PROVIDER_HEADER, REPORT_CORRELATION_ID_HEADER, REQUEST_TIMEOUT, UPLOAD_SOURCE_HEADER
|
|
16
15
|
from .metadata import Metadata, collect_dependency_versions
|
|
17
16
|
from .models import (
|
|
@@ -29,6 +28,7 @@ from .models import (
|
|
|
29
28
|
|
|
30
29
|
if TYPE_CHECKING:
|
|
31
30
|
from ..runner import probes
|
|
31
|
+
from .ci import CIProvider
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
def response_hook(response: requests.Response, **_kwargs: Any) -> None:
|
schemathesis/service/events.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import base64
|
|
4
4
|
import re
|
|
5
5
|
from ipaddress import IPv4Network, IPv6Network
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Callable
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
7
7
|
|
|
8
8
|
from ..graphql import nodes
|
|
9
9
|
from ..internal.result import Err, Ok, Result
|
|
@@ -75,7 +75,7 @@ def _apply_media_types_extension(extension: MediaTypesExtension) -> None:
|
|
|
75
75
|
_apply_simple_extension(extension, extension.media_types, media_types.register_media_type)
|
|
76
76
|
|
|
77
77
|
|
|
78
|
-
def _find_built_in_strategy(name: str) ->
|
|
78
|
+
def _find_built_in_strategy(name: str) -> st.SearchStrategy | None:
|
|
79
79
|
"""Find a built-in Hypothesis strategy by its name."""
|
|
80
80
|
from hypothesis import provisional as pr
|
|
81
81
|
from hypothesis import strategies as st
|
schemathesis/service/hosts.py
CHANGED
|
@@ -6,14 +6,16 @@ import enum
|
|
|
6
6
|
import tempfile
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
10
|
|
|
11
11
|
import tomli
|
|
12
12
|
import tomli_w
|
|
13
13
|
|
|
14
|
-
from ..types import PathLike
|
|
15
14
|
from .constants import DEFAULT_HOSTNAME, DEFAULT_HOSTS_PATH, HOSTS_FORMAT_VERSION
|
|
16
15
|
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from ..types import PathLike
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
@dataclass
|
|
19
21
|
class HostData:
|
schemathesis/service/models.py
CHANGED
|
@@ -224,11 +224,11 @@ Extension = Union[
|
|
|
224
224
|
def extension_from_dict(data: dict[str, Any]) -> Extension:
|
|
225
225
|
if data["type"] == "schema_patches":
|
|
226
226
|
return SchemaPatchesExtension(patches=data["patches"])
|
|
227
|
-
|
|
227
|
+
if data["type"] == "string_formats":
|
|
228
228
|
return OpenApiStringFormatsExtension.from_dict(formats=data["items"])
|
|
229
|
-
|
|
229
|
+
if data["type"] == "scalars":
|
|
230
230
|
return GraphQLScalarsExtension.from_dict(scalars=data["items"])
|
|
231
|
-
|
|
231
|
+
if data["type"] == "media_types":
|
|
232
232
|
return MediaTypesExtension.from_dict(media_types=data["items"])
|
|
233
233
|
return UnknownExtension(type=data["type"])
|
|
234
234
|
|
schemathesis/service/report.py
CHANGED
|
@@ -12,21 +12,21 @@ from io import BytesIO
|
|
|
12
12
|
from queue import Queue
|
|
13
13
|
from typing import TYPE_CHECKING, Any
|
|
14
14
|
|
|
15
|
-
import click
|
|
16
|
-
|
|
17
15
|
from ..cli.handlers import EventHandler
|
|
18
16
|
from ..runner.events import Initialized, InternalError, Interrupted
|
|
19
17
|
from . import ci, events, usage
|
|
20
18
|
from .constants import REPORT_FORMAT_VERSION, STOP_MARKER, WORKER_JOIN_TIMEOUT
|
|
21
|
-
from .hosts import HostData
|
|
22
19
|
from .metadata import Metadata
|
|
23
20
|
from .models import UploadResponse
|
|
24
21
|
from .serialization import serialize_event
|
|
25
22
|
|
|
26
23
|
if TYPE_CHECKING:
|
|
24
|
+
import click
|
|
25
|
+
|
|
27
26
|
from ..cli.context import ExecutionContext
|
|
28
27
|
from ..runner.events import ExecutionEvent
|
|
29
28
|
from .client import ServiceClient
|
|
29
|
+
from .hosts import HostData
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
@dataclass
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import asdict
|
|
4
|
-
from typing import Any, Callable, Dict, Optional, TypeVar, cast
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, TypeVar, cast
|
|
5
5
|
|
|
6
6
|
from ..internal.transformation import merge_recursively
|
|
7
7
|
from ..runner import events
|
|
8
8
|
from ..runner.serialization import _serialize_check
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..stateful import events as stateful_events
|
|
10
12
|
|
|
11
13
|
S = TypeVar("S", bound=events.ExecutionEvent)
|
|
12
14
|
SerializeFunc = Callable[[S], Optional[Dict[str, Any]]]
|
|
@@ -7,7 +7,7 @@ from json import JSONDecodeError
|
|
|
7
7
|
from typing import IO, TYPE_CHECKING, Any, Callable, Dict, NoReturn, cast
|
|
8
8
|
|
|
9
9
|
from ...code_samples import CodeSampleStyle
|
|
10
|
-
from ...constants import WAIT_FOR_SCHEMA_INTERVAL
|
|
10
|
+
from ...constants import DEFAULT_RESPONSE_TIMEOUT, WAIT_FOR_SCHEMA_INTERVAL
|
|
11
11
|
from ...exceptions import SchemaError, SchemaErrorType
|
|
12
12
|
from ...generation import (
|
|
13
13
|
DEFAULT_DATA_GENERATION_METHODS,
|
|
@@ -139,6 +139,7 @@ def from_url(
|
|
|
139
139
|
interval=WAIT_FOR_SCHEMA_INTERVAL,
|
|
140
140
|
)
|
|
141
141
|
def _load_schema(_uri: str, **_kwargs: Any) -> requests.Response:
|
|
142
|
+
_kwargs.setdefault("timeout", DEFAULT_RESPONSE_TIMEOUT / 1000)
|
|
142
143
|
return requests.post(_uri, **kwargs)
|
|
143
144
|
|
|
144
145
|
else:
|
|
@@ -356,8 +357,8 @@ def from_asgi(
|
|
|
356
357
|
|
|
357
358
|
|
|
358
359
|
def get_loader_for_app(app: Any) -> Callable:
|
|
359
|
-
from
|
|
360
|
+
from ...transports.asgi import is_asgi_app
|
|
360
361
|
|
|
361
|
-
if
|
|
362
|
+
if is_asgi_app(app):
|
|
362
363
|
return from_asgi
|
|
363
364
|
return from_wsgi
|
|
@@ -22,12 +22,10 @@ from urllib.parse import urlsplit, urlunsplit
|
|
|
22
22
|
|
|
23
23
|
import graphql
|
|
24
24
|
from hypothesis import strategies as st
|
|
25
|
-
from hypothesis.strategies import SearchStrategy
|
|
26
25
|
from hypothesis_graphql import strategies as gql_st
|
|
27
26
|
from requests.structures import CaseInsensitiveDict
|
|
28
27
|
|
|
29
28
|
from ... import auths
|
|
30
|
-
from ...auths import AuthStorage
|
|
31
29
|
from ...checks import not_a_server_error
|
|
32
30
|
from ...constants import NOT_SET
|
|
33
31
|
from ...exceptions import OperationNotFound, OperationSchemaError
|
|
@@ -42,13 +40,16 @@ from ...hooks import (
|
|
|
42
40
|
from ...internal.result import Ok, Result
|
|
43
41
|
from ...models import APIOperation, Case, CheckFunction, OperationDefinition
|
|
44
42
|
from ...schemas import APIOperationMap, BaseSchema
|
|
45
|
-
from ...stateful import Stateful, StatefulTest
|
|
46
43
|
from ...types import Body, Cookies, Headers, NotSet, PathParameters, Query
|
|
47
44
|
from ..openapi.constants import LOCATION_TO_CONTAINER
|
|
48
45
|
from ._cache import OperationCache
|
|
49
46
|
from .scalars import CUSTOM_SCALARS, get_extra_scalar_strategies
|
|
50
47
|
|
|
51
48
|
if TYPE_CHECKING:
|
|
49
|
+
from hypothesis.strategies import SearchStrategy
|
|
50
|
+
|
|
51
|
+
from ...auths import AuthStorage
|
|
52
|
+
from ...stateful import Stateful, StatefulTest
|
|
52
53
|
from ...transports.responses import GenericResponse
|
|
53
54
|
|
|
54
55
|
|
|
@@ -1907,11 +1907,7 @@ _VALIDATORS = [
|
|
|
1907
1907
|
"OPENAPI_31_VALIDATOR",
|
|
1908
1908
|
]
|
|
1909
1909
|
|
|
1910
|
-
__all__ = [
|
|
1911
|
-
"SWAGGER_20",
|
|
1912
|
-
"OPENAPI_30",
|
|
1913
|
-
"OPENAPI_31",
|
|
1914
|
-
] + _VALIDATORS
|
|
1910
|
+
__all__ = ["SWAGGER_20", "OPENAPI_30", "OPENAPI_31", *_VALIDATORS]
|
|
1915
1911
|
|
|
1916
1912
|
_imports = {
|
|
1917
1913
|
"SWAGGER_20_VALIDATOR": lambda: make_validator(SWAGGER_20),
|