schemathesis 3.25.6__py3-none-any.whl → 3.39.7__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 (146) hide show
  1. schemathesis/__init__.py +6 -6
  2. schemathesis/_compat.py +2 -2
  3. schemathesis/_dependency_versions.py +4 -2
  4. schemathesis/_hypothesis.py +369 -56
  5. schemathesis/_lazy_import.py +1 -0
  6. schemathesis/_override.py +5 -4
  7. schemathesis/_patches.py +21 -0
  8. schemathesis/_rate_limiter.py +7 -0
  9. schemathesis/_xml.py +75 -22
  10. schemathesis/auths.py +78 -16
  11. schemathesis/checks.py +21 -9
  12. schemathesis/cli/__init__.py +783 -432
  13. schemathesis/cli/__main__.py +4 -0
  14. schemathesis/cli/callbacks.py +58 -13
  15. schemathesis/cli/cassettes.py +233 -47
  16. schemathesis/cli/constants.py +8 -2
  17. schemathesis/cli/context.py +22 -5
  18. schemathesis/cli/debug.py +2 -1
  19. schemathesis/cli/handlers.py +4 -1
  20. schemathesis/cli/junitxml.py +103 -22
  21. schemathesis/cli/options.py +15 -4
  22. schemathesis/cli/output/default.py +258 -112
  23. schemathesis/cli/output/short.py +23 -8
  24. schemathesis/cli/reporting.py +79 -0
  25. schemathesis/cli/sanitization.py +6 -0
  26. schemathesis/code_samples.py +5 -3
  27. schemathesis/constants.py +1 -0
  28. schemathesis/contrib/openapi/__init__.py +1 -1
  29. schemathesis/contrib/openapi/fill_missing_examples.py +3 -1
  30. schemathesis/contrib/openapi/formats/uuid.py +2 -1
  31. schemathesis/contrib/unique_data.py +3 -3
  32. schemathesis/exceptions.py +76 -65
  33. schemathesis/experimental/__init__.py +35 -0
  34. schemathesis/extra/_aiohttp.py +1 -0
  35. schemathesis/extra/_flask.py +4 -1
  36. schemathesis/extra/_server.py +1 -0
  37. schemathesis/extra/pytest_plugin.py +17 -25
  38. schemathesis/failures.py +77 -9
  39. schemathesis/filters.py +185 -8
  40. schemathesis/fixups/__init__.py +1 -0
  41. schemathesis/fixups/fast_api.py +2 -2
  42. schemathesis/fixups/utf8_bom.py +1 -2
  43. schemathesis/generation/__init__.py +20 -36
  44. schemathesis/generation/_hypothesis.py +59 -0
  45. schemathesis/generation/_methods.py +44 -0
  46. schemathesis/generation/coverage.py +931 -0
  47. schemathesis/graphql.py +0 -1
  48. schemathesis/hooks.py +89 -12
  49. schemathesis/internal/checks.py +84 -0
  50. schemathesis/internal/copy.py +22 -3
  51. schemathesis/internal/deprecation.py +6 -2
  52. schemathesis/internal/diff.py +15 -0
  53. schemathesis/internal/extensions.py +27 -0
  54. schemathesis/internal/jsonschema.py +2 -1
  55. schemathesis/internal/output.py +68 -0
  56. schemathesis/internal/result.py +1 -1
  57. schemathesis/internal/transformation.py +11 -0
  58. schemathesis/lazy.py +138 -25
  59. schemathesis/loaders.py +7 -5
  60. schemathesis/models.py +318 -211
  61. schemathesis/parameters.py +4 -0
  62. schemathesis/runner/__init__.py +50 -15
  63. schemathesis/runner/events.py +65 -5
  64. schemathesis/runner/impl/context.py +104 -0
  65. schemathesis/runner/impl/core.py +388 -177
  66. schemathesis/runner/impl/solo.py +19 -29
  67. schemathesis/runner/impl/threadpool.py +70 -79
  68. schemathesis/runner/probes.py +11 -9
  69. schemathesis/runner/serialization.py +150 -17
  70. schemathesis/sanitization.py +5 -1
  71. schemathesis/schemas.py +170 -102
  72. schemathesis/serializers.py +7 -2
  73. schemathesis/service/ci.py +1 -0
  74. schemathesis/service/client.py +39 -6
  75. schemathesis/service/events.py +5 -1
  76. schemathesis/service/extensions.py +224 -0
  77. schemathesis/service/hosts.py +6 -2
  78. schemathesis/service/metadata.py +25 -0
  79. schemathesis/service/models.py +211 -2
  80. schemathesis/service/report.py +6 -6
  81. schemathesis/service/serialization.py +45 -71
  82. schemathesis/service/usage.py +1 -0
  83. schemathesis/specs/graphql/_cache.py +26 -0
  84. schemathesis/specs/graphql/loaders.py +25 -5
  85. schemathesis/specs/graphql/nodes.py +1 -0
  86. schemathesis/specs/graphql/scalars.py +2 -2
  87. schemathesis/specs/graphql/schemas.py +130 -100
  88. schemathesis/specs/graphql/validation.py +1 -2
  89. schemathesis/specs/openapi/__init__.py +1 -0
  90. schemathesis/specs/openapi/_cache.py +123 -0
  91. schemathesis/specs/openapi/_hypothesis.py +78 -60
  92. schemathesis/specs/openapi/checks.py +504 -25
  93. schemathesis/specs/openapi/converter.py +31 -4
  94. schemathesis/specs/openapi/definitions.py +10 -17
  95. schemathesis/specs/openapi/examples.py +126 -12
  96. schemathesis/specs/openapi/expressions/__init__.py +37 -2
  97. schemathesis/specs/openapi/expressions/context.py +1 -1
  98. schemathesis/specs/openapi/expressions/extractors.py +26 -0
  99. schemathesis/specs/openapi/expressions/lexer.py +20 -18
  100. schemathesis/specs/openapi/expressions/nodes.py +29 -6
  101. schemathesis/specs/openapi/expressions/parser.py +26 -5
  102. schemathesis/specs/openapi/formats.py +44 -0
  103. schemathesis/specs/openapi/links.py +125 -42
  104. schemathesis/specs/openapi/loaders.py +77 -36
  105. schemathesis/specs/openapi/media_types.py +34 -0
  106. schemathesis/specs/openapi/negative/__init__.py +6 -3
  107. schemathesis/specs/openapi/negative/mutations.py +21 -6
  108. schemathesis/specs/openapi/parameters.py +39 -25
  109. schemathesis/specs/openapi/patterns.py +137 -0
  110. schemathesis/specs/openapi/references.py +37 -7
  111. schemathesis/specs/openapi/schemas.py +360 -241
  112. schemathesis/specs/openapi/security.py +25 -7
  113. schemathesis/specs/openapi/serialization.py +1 -0
  114. schemathesis/specs/openapi/stateful/__init__.py +198 -70
  115. schemathesis/specs/openapi/stateful/statistic.py +198 -0
  116. schemathesis/specs/openapi/stateful/types.py +14 -0
  117. schemathesis/specs/openapi/utils.py +6 -1
  118. schemathesis/specs/openapi/validation.py +1 -0
  119. schemathesis/stateful/__init__.py +35 -21
  120. schemathesis/stateful/config.py +97 -0
  121. schemathesis/stateful/context.py +135 -0
  122. schemathesis/stateful/events.py +274 -0
  123. schemathesis/stateful/runner.py +309 -0
  124. schemathesis/stateful/sink.py +68 -0
  125. schemathesis/stateful/state_machine.py +67 -38
  126. schemathesis/stateful/statistic.py +22 -0
  127. schemathesis/stateful/validation.py +100 -0
  128. schemathesis/targets.py +33 -1
  129. schemathesis/throttling.py +25 -5
  130. schemathesis/transports/__init__.py +354 -0
  131. schemathesis/transports/asgi.py +7 -0
  132. schemathesis/transports/auth.py +25 -2
  133. schemathesis/transports/content_types.py +3 -1
  134. schemathesis/transports/headers.py +2 -1
  135. schemathesis/transports/responses.py +9 -4
  136. schemathesis/types.py +9 -0
  137. schemathesis/utils.py +11 -16
  138. schemathesis-3.39.7.dist-info/METADATA +293 -0
  139. schemathesis-3.39.7.dist-info/RECORD +160 -0
  140. {schemathesis-3.25.6.dist-info → schemathesis-3.39.7.dist-info}/WHEEL +1 -1
  141. schemathesis/specs/openapi/filters.py +0 -49
  142. schemathesis/specs/openapi/stateful/links.py +0 -92
  143. schemathesis-3.25.6.dist-info/METADATA +0 -356
  144. schemathesis-3.25.6.dist-info/RECORD +0 -134
  145. {schemathesis-3.25.6.dist-info → schemathesis-3.39.7.dist-info}/entry_points.txt +0 -0
  146. {schemathesis-3.25.6.dist-info → schemathesis-3.39.7.dist-info}/licenses/LICENSE +0 -0
@@ -1,50 +1,42 @@
1
1
  from __future__ import annotations
2
- import threading
2
+
3
3
  from dataclasses import dataclass
4
- from typing import Generator
4
+ from typing import TYPE_CHECKING, Generator
5
5
 
6
- from ...models import TestResultSet
7
- from ...types import RequestCert
8
6
  from ...transports.auth import get_requests_auth
9
7
  from .. import events
10
8
  from .core import BaseRunner, asgi_test, get_session, network_test, wsgi_test
11
9
 
10
+ if TYPE_CHECKING:
11
+ from .. import events
12
+ from .context import RunnerContext
13
+
12
14
 
13
15
  @dataclass
14
16
  class SingleThreadRunner(BaseRunner):
15
17
  """Fast runner that runs tests sequentially in the main thread."""
16
18
 
17
- request_tls_verify: bool | str = True
18
- request_proxy: str | None = None
19
- request_cert: RequestCert | None = None
20
-
21
- def _execute(
22
- self, results: TestResultSet, stop_event: threading.Event
23
- ) -> Generator[events.ExecutionEvent, None, None]:
24
- for event in self._execute_impl(results):
19
+ def _execute(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
20
+ for event in self._execute_impl(ctx):
25
21
  yield event
26
- if stop_event.is_set() or self._should_stop(event):
22
+ if ctx.is_stopped or self._should_stop(event):
27
23
  break
28
24
 
29
- def _execute_impl(self, results: TestResultSet) -> Generator[events.ExecutionEvent, None, None]:
25
+ def _execute_impl(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
30
26
  auth = get_requests_auth(self.auth, self.auth_type)
31
27
  with get_session(auth) as session:
32
28
  yield from self._run_tests(
33
29
  maker=self.schema.get_all_tests,
34
- template=network_test,
30
+ test_func=network_test,
35
31
  settings=self.hypothesis_settings,
36
32
  generation_config=self.generation_config,
37
- seed=self.seed,
38
33
  checks=self.checks,
39
34
  max_response_time=self.max_response_time,
40
35
  targets=self.targets,
41
- results=results,
36
+ ctx=ctx,
42
37
  session=session,
43
38
  headers=self.headers,
44
- request_timeout=self.request_timeout,
45
- request_tls_verify=self.request_tls_verify,
46
- request_proxy=self.request_proxy,
47
- request_cert=self.request_cert,
39
+ request_config=self.request_config,
48
40
  store_interactions=self.store_interactions,
49
41
  dry_run=self.dry_run,
50
42
  )
@@ -52,17 +44,16 @@ class SingleThreadRunner(BaseRunner):
52
44
 
53
45
  @dataclass
54
46
  class SingleThreadWSGIRunner(SingleThreadRunner):
55
- def _execute_impl(self, results: TestResultSet) -> Generator[events.ExecutionEvent, None, None]:
47
+ def _execute_impl(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
56
48
  yield from self._run_tests(
57
49
  maker=self.schema.get_all_tests,
58
- template=wsgi_test,
50
+ test_func=wsgi_test,
59
51
  settings=self.hypothesis_settings,
60
52
  generation_config=self.generation_config,
61
- seed=self.seed,
62
53
  checks=self.checks,
63
54
  max_response_time=self.max_response_time,
64
55
  targets=self.targets,
65
- results=results,
56
+ ctx=ctx,
66
57
  auth=self.auth,
67
58
  auth_type=self.auth_type,
68
59
  headers=self.headers,
@@ -73,17 +64,16 @@ class SingleThreadWSGIRunner(SingleThreadRunner):
73
64
 
74
65
  @dataclass
75
66
  class SingleThreadASGIRunner(SingleThreadRunner):
76
- def _execute_impl(self, results: TestResultSet) -> Generator[events.ExecutionEvent, None, None]:
67
+ def _execute_impl(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
77
68
  yield from self._run_tests(
78
69
  maker=self.schema.get_all_tests,
79
- template=asgi_test,
70
+ test_func=asgi_test,
80
71
  settings=self.hypothesis_settings,
81
72
  generation_config=self.generation_config,
82
- seed=self.seed,
83
73
  checks=self.checks,
84
74
  max_response_time=self.max_response_time,
85
75
  targets=self.targets,
86
- results=results,
76
+ ctx=ctx,
87
77
  headers=self.headers,
88
78
  store_interactions=self.store_interactions,
89
79
  dry_run=self.dry_run,
@@ -1,29 +1,37 @@
1
1
  from __future__ import annotations
2
+
2
3
  import ctypes
3
4
  import queue
4
5
  import threading
5
6
  import time
7
+ import warnings
6
8
  from dataclasses import dataclass
7
9
  from queue import Queue
8
- from typing import Any, Callable, Generator, Iterable, cast
10
+ from typing import TYPE_CHECKING, Any, Callable, Generator, Iterable, cast
9
11
 
10
- import hypothesis
12
+ from hypothesis.errors import HypothesisWarning
11
13
 
12
14
  from ..._hypothesis import create_test
13
- from ...generation import DataGenerationMethod, GenerationConfig
14
15
  from ...internal.result import Ok
15
- from ...models import CheckFunction, TestResultSet
16
16
  from ...stateful import Feedback, Stateful
17
- from ...targets import Target
18
17
  from ...transports.auth import get_requests_auth
19
- from ...types import RawAuth, RequestCert
20
18
  from ...utils import capture_hypothesis_output
21
19
  from .. import events
22
20
  from .core import BaseRunner, asgi_test, get_session, handle_schema_error, network_test, run_test, wsgi_test
23
21
 
22
+ if TYPE_CHECKING:
23
+ import hypothesis
24
+
25
+ from ...generation import DataGenerationMethod, GenerationConfig
26
+ from ...internal.checks import CheckFunction
27
+ from ...targets import Target
28
+ from ...types import RawAuth
29
+ from .context import RunnerContext
30
+
24
31
 
25
32
  def _run_task(
26
- test_template: Callable,
33
+ *,
34
+ test_func: Callable,
27
35
  tasks_queue: Queue,
28
36
  events_queue: Queue,
29
37
  generator_done: threading.Event,
@@ -32,13 +40,13 @@ def _run_task(
32
40
  data_generation_methods: Iterable[DataGenerationMethod],
33
41
  settings: hypothesis.settings,
34
42
  generation_config: GenerationConfig,
35
- seed: int | None,
36
- results: TestResultSet,
43
+ ctx: RunnerContext,
37
44
  stateful: Stateful | None,
38
45
  stateful_recursion_limit: int,
39
46
  headers: dict[str, Any] | None = None,
40
47
  **kwargs: Any,
41
48
  ) -> None:
49
+ warnings.filterwarnings("ignore", message="The recursion limit will not be reset", category=HypothesisWarning)
42
50
  as_strategy_kwargs = {}
43
51
  if headers is not None:
44
52
  as_strategy_kwargs["headers"] = {key: value for key, value in headers.items() if key.lower() != "user-agent"}
@@ -47,10 +55,10 @@ def _run_task(
47
55
  if recursion_level > stateful_recursion_limit:
48
56
  return
49
57
  for _result in maker(
50
- test_template,
58
+ test_func,
51
59
  settings=settings,
52
60
  generation_config=generation_config,
53
- seed=seed,
61
+ seed=ctx.seed,
54
62
  as_strategy_kwargs=as_strategy_kwargs,
55
63
  ):
56
64
  # `result` is always `Ok` here
@@ -62,7 +70,7 @@ def _run_task(
62
70
  checks,
63
71
  data_generation_methods,
64
72
  targets,
65
- results,
73
+ ctx=ctx,
66
74
  recursion_level=recursion_level,
67
75
  feedback=feedback,
68
76
  headers=headers,
@@ -85,9 +93,9 @@ def _run_task(
85
93
  operation = result.ok()
86
94
  test_function = create_test(
87
95
  operation=operation,
88
- test=test_template,
96
+ test=test_func,
89
97
  settings=settings,
90
- seed=seed,
98
+ seed=ctx.seed,
91
99
  data_generation_methods=list(data_generation_methods),
92
100
  generation_config=generation_config,
93
101
  as_strategy_kwargs=as_strategy_kwargs,
@@ -97,7 +105,7 @@ def _run_task(
97
105
  # `feedback.get_stateful_tests`
98
106
  _run_tests(lambda *_, **__: (items,)) # noqa: B023
99
107
  else:
100
- for event in handle_schema_error(result.err(), results, data_generation_methods, 0):
108
+ for event in handle_schema_error(result.err(), ctx, data_generation_methods, 0):
101
109
  events_queue.put(event)
102
110
 
103
111
 
@@ -113,8 +121,7 @@ def thread_task(
113
121
  auth: RawAuth | None,
114
122
  auth_type: str | None,
115
123
  headers: dict[str, Any] | None,
116
- seed: int | None,
117
- results: TestResultSet,
124
+ ctx: RunnerContext,
118
125
  stateful: Stateful | None,
119
126
  stateful_recursion_limit: int,
120
127
  kwargs: Any,
@@ -126,17 +133,16 @@ def thread_task(
126
133
  prepared_auth = get_requests_auth(auth, auth_type)
127
134
  with get_session(prepared_auth) as session:
128
135
  _run_task(
129
- network_test,
130
- tasks_queue,
131
- events_queue,
132
- generator_done,
133
- checks,
134
- targets,
135
- data_generation_methods,
136
- settings,
137
- generation_config,
138
- seed,
139
- results,
136
+ test_func=network_test,
137
+ tasks_queue=tasks_queue,
138
+ events_queue=events_queue,
139
+ generator_done=generator_done,
140
+ checks=checks,
141
+ targets=targets,
142
+ data_generation_methods=data_generation_methods,
143
+ settings=settings,
144
+ generation_config=generation_config,
145
+ ctx=ctx,
140
146
  stateful=stateful,
141
147
  stateful_recursion_limit=stateful_recursion_limit,
142
148
  session=session,
@@ -154,24 +160,22 @@ def wsgi_thread_task(
154
160
  data_generation_methods: Iterable[DataGenerationMethod],
155
161
  settings: hypothesis.settings,
156
162
  generation_config: GenerationConfig,
157
- seed: int | None,
158
- results: TestResultSet,
163
+ ctx: RunnerContext,
159
164
  stateful: Stateful | None,
160
165
  stateful_recursion_limit: int,
161
166
  kwargs: Any,
162
167
  ) -> None:
163
168
  _run_task(
164
- wsgi_test,
165
- tasks_queue,
166
- events_queue,
167
- generator_done,
168
- checks,
169
- targets,
170
- data_generation_methods,
171
- settings,
172
- generation_config,
173
- seed,
174
- results,
169
+ test_func=wsgi_test,
170
+ tasks_queue=tasks_queue,
171
+ events_queue=events_queue,
172
+ generator_done=generator_done,
173
+ checks=checks,
174
+ targets=targets,
175
+ data_generation_methods=data_generation_methods,
176
+ settings=settings,
177
+ generation_config=generation_config,
178
+ ctx=ctx,
175
179
  stateful=stateful,
176
180
  stateful_recursion_limit=stateful_recursion_limit,
177
181
  **kwargs,
@@ -188,24 +192,22 @@ def asgi_thread_task(
188
192
  settings: hypothesis.settings,
189
193
  generation_config: GenerationConfig,
190
194
  headers: dict[str, Any] | None,
191
- seed: int | None,
192
- results: TestResultSet,
195
+ ctx: RunnerContext,
193
196
  stateful: Stateful | None,
194
197
  stateful_recursion_limit: int,
195
198
  kwargs: Any,
196
199
  ) -> None:
197
200
  _run_task(
198
- asgi_test,
199
- tasks_queue,
200
- events_queue,
201
- generator_done,
202
- checks,
203
- targets,
204
- data_generation_methods,
205
- settings,
206
- generation_config,
207
- seed,
208
- results,
201
+ test_func=asgi_test,
202
+ tasks_queue=tasks_queue,
203
+ events_queue=events_queue,
204
+ generator_done=generator_done,
205
+ checks=checks,
206
+ targets=targets,
207
+ data_generation_methods=data_generation_methods,
208
+ settings=settings,
209
+ generation_config=generation_config,
210
+ ctx=ctx,
209
211
  stateful=stateful,
210
212
  stateful_recursion_limit=stateful_recursion_limit,
211
213
  headers=headers,
@@ -223,13 +225,8 @@ class ThreadPoolRunner(BaseRunner):
223
225
  """Spread different tests among multiple worker threads."""
224
226
 
225
227
  workers_num: int = 2
226
- request_tls_verify: bool | str = True
227
- request_proxy: str | None = None
228
- request_cert: RequestCert | None = None
229
228
 
230
- def _execute(
231
- self, results: TestResultSet, stop_event: threading.Event
232
- ) -> Generator[events.ExecutionEvent, None, None]:
229
+ def _execute(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
233
230
  """All events come from a queue where different workers push their events."""
234
231
  # Instead of generating all tests at once, we do it when there is a free worker to pick it up
235
232
  # This is extremely important for memory consumption when testing large schemas
@@ -237,7 +234,7 @@ class ThreadPoolRunner(BaseRunner):
237
234
  # It would be better to have a separate producer thread and communicate via threading events.
238
235
  # Though it is a bit more complex, so the current solution is suboptimal in terms of resources utilization,
239
236
  # but good enough and easy enough to implement.
240
- tasks_generator = iter(self.schema.get_all_operations())
237
+ tasks_generator = iter(self.schema.get_all_operations(generation_config=self.generation_config))
241
238
  generator_done = threading.Event()
242
239
  tasks_queue: Queue = Queue()
243
240
  # Add at least `workers_num` tasks first, so all workers are busy
@@ -250,7 +247,7 @@ class ThreadPoolRunner(BaseRunner):
250
247
  break
251
248
  # Events are pushed by workers via a separate queue
252
249
  events_queue: Queue = Queue()
253
- workers = self._init_workers(tasks_queue, events_queue, results, generator_done)
250
+ workers = self._init_workers(tasks_queue, events_queue, ctx, generator_done)
254
251
 
255
252
  def stop_workers() -> None:
256
253
  for worker in workers:
@@ -269,12 +266,12 @@ class ThreadPoolRunner(BaseRunner):
269
266
  is_finished = all(not worker.is_alive() for worker in workers)
270
267
  while not events_queue.empty():
271
268
  event = events_queue.get()
272
- if stop_event.is_set() or isinstance(event, events.Interrupted) or self._should_stop(event):
269
+ if ctx.is_stopped or isinstance(event, events.Interrupted) or self._should_stop(event):
273
270
  # We could still have events in the queue, but ignore them to keep the logic simple
274
271
  # for now, could be improved in the future to show more info in such corner cases
275
272
  stop_workers()
276
273
  is_finished = True
277
- if stop_event.is_set():
274
+ if ctx.is_stopped:
278
275
  # Discard the event. The invariant is: the next event after `stream.stop()` is `Finished`
279
276
  break
280
277
  yield event
@@ -291,13 +288,13 @@ class ThreadPoolRunner(BaseRunner):
291
288
  yield events.Interrupted()
292
289
 
293
290
  def _init_workers(
294
- self, tasks_queue: Queue, events_queue: Queue, results: TestResultSet, generator_done: threading.Event
291
+ self, tasks_queue: Queue, events_queue: Queue, ctx: RunnerContext, generator_done: threading.Event
295
292
  ) -> list[threading.Thread]:
296
293
  """Initialize & start workers that will execute tests."""
297
294
  workers = [
298
295
  threading.Thread(
299
296
  target=self._get_task(),
300
- kwargs=self._get_worker_kwargs(tasks_queue, events_queue, results, generator_done),
297
+ kwargs=self._get_worker_kwargs(tasks_queue, events_queue, ctx, generator_done),
301
298
  name=f"schemathesis_{num}",
302
299
  )
303
300
  for num in range(self.workers_num)
@@ -310,7 +307,7 @@ class ThreadPoolRunner(BaseRunner):
310
307
  return thread_task
311
308
 
312
309
  def _get_worker_kwargs(
313
- self, tasks_queue: Queue, events_queue: Queue, results: TestResultSet, generator_done: threading.Event
310
+ self, tasks_queue: Queue, events_queue: Queue, ctx: RunnerContext, generator_done: threading.Event
314
311
  ) -> dict[str, Any]:
315
312
  return {
316
313
  "tasks_queue": tasks_queue,
@@ -323,16 +320,12 @@ class ThreadPoolRunner(BaseRunner):
323
320
  "auth": self.auth,
324
321
  "auth_type": self.auth_type,
325
322
  "headers": self.headers,
326
- "seed": self.seed,
327
- "results": results,
323
+ "ctx": ctx,
328
324
  "stateful": self.stateful,
329
325
  "stateful_recursion_limit": self.stateful_recursion_limit,
330
326
  "data_generation_methods": self.schema.data_generation_methods,
331
327
  "kwargs": {
332
- "request_timeout": self.request_timeout,
333
- "request_tls_verify": self.request_tls_verify,
334
- "request_proxy": self.request_proxy,
335
- "request_cert": self.request_cert,
328
+ "request_config": self.request_config,
336
329
  "store_interactions": self.store_interactions,
337
330
  "max_response_time": self.max_response_time,
338
331
  "dry_run": self.dry_run,
@@ -345,7 +338,7 @@ class ThreadPoolWSGIRunner(ThreadPoolRunner):
345
338
  return wsgi_thread_task
346
339
 
347
340
  def _get_worker_kwargs(
348
- self, tasks_queue: Queue, events_queue: Queue, results: TestResultSet, generator_done: threading.Event
341
+ self, tasks_queue: Queue, events_queue: Queue, ctx: RunnerContext, generator_done: threading.Event
349
342
  ) -> dict[str, Any]:
350
343
  return {
351
344
  "tasks_queue": tasks_queue,
@@ -355,8 +348,7 @@ class ThreadPoolWSGIRunner(ThreadPoolRunner):
355
348
  "targets": self.targets,
356
349
  "settings": self.hypothesis_settings,
357
350
  "generation_config": self.generation_config,
358
- "seed": self.seed,
359
- "results": results,
351
+ "ctx": ctx,
360
352
  "stateful": self.stateful,
361
353
  "stateful_recursion_limit": self.stateful_recursion_limit,
362
354
  "data_generation_methods": self.schema.data_generation_methods,
@@ -376,7 +368,7 @@ class ThreadPoolASGIRunner(ThreadPoolRunner):
376
368
  return asgi_thread_task
377
369
 
378
370
  def _get_worker_kwargs(
379
- self, tasks_queue: Queue, events_queue: Queue, results: TestResultSet, generator_done: threading.Event
371
+ self, tasks_queue: Queue, events_queue: Queue, ctx: RunnerContext, generator_done: threading.Event
380
372
  ) -> dict[str, Any]:
381
373
  return {
382
374
  "tasks_queue": tasks_queue,
@@ -387,8 +379,7 @@ class ThreadPoolASGIRunner(ThreadPoolRunner):
387
379
  "settings": self.hypothesis_settings,
388
380
  "generation_config": self.generation_config,
389
381
  "headers": self.headers,
390
- "seed": self.seed,
391
- "results": results,
382
+ "ctx": ctx,
392
383
  "stateful": self.stateful,
393
384
  "stateful_recursion_limit": self.stateful_recursion_limit,
394
385
  "data_generation_methods": self.schema.data_generation_methods,
@@ -5,23 +5,24 @@ the application supports certain inputs. This is done to avoid false positives i
5
5
  For example, certail web servers do not support NULL bytes in headers, in such cases, the generated test case
6
6
  will not reach the tested application at all.
7
7
  """
8
+
8
9
  from __future__ import annotations
9
10
 
10
11
  import enum
11
12
  import warnings
12
- from dataclasses import asdict, dataclass
13
+ from dataclasses import asdict, dataclass, field
13
14
  from typing import TYPE_CHECKING, Any
14
15
 
15
16
  from ..constants import USER_AGENT
16
17
  from ..exceptions import format_exception
17
18
  from ..models import Request, Response
18
19
  from ..sanitization import sanitize_request, sanitize_response
20
+ from ..transports import RequestConfig
19
21
  from ..transports.auth import get_requests_auth
20
22
 
21
23
  if TYPE_CHECKING:
22
24
  import requests
23
25
 
24
- from ..types import RequestCert
25
26
  from ..schemas import BaseSchema
26
27
 
27
28
 
@@ -31,9 +32,7 @@ HEADER_NAME = "X-Schemathesis-Probe"
31
32
  @dataclass
32
33
  class ProbeConfig:
33
34
  base_url: str | None = None
34
- request_tls_verify: bool | str = True
35
- request_proxy: str | None = None
36
- request_cert: RequestCert | None = None
35
+ request: RequestConfig = field(default_factory=RequestConfig)
37
36
  auth: tuple[str, str] | None = None
38
37
  auth_type: str | None = None
39
38
  headers: dict[str, str] | None = None
@@ -136,9 +135,12 @@ def send(probe: Probe, session: requests.Session, schema: BaseSchema, config: Pr
136
135
  request = probe.prepare_request(session, Request(), schema, config)
137
136
  request.headers[HEADER_NAME] = probe.name
138
137
  request.headers["User-Agent"] = USER_AGENT
138
+ kwargs: dict[str, Any] = {"timeout": config.request.prepared_timeout or 2}
139
+ if config.request.proxy is not None:
140
+ kwargs["proxies"] = {"all": config.request.proxy}
139
141
  with warnings.catch_warnings():
140
142
  warnings.simplefilter("ignore", InsecureRequestWarning)
141
- response = session.send(request, timeout=2)
143
+ response = session.send(request, **kwargs)
142
144
  except MissingSchema:
143
145
  # In-process ASGI/WSGI testing will have local URLs and requires extra handling
144
146
  # which is not currently implemented
@@ -156,9 +158,9 @@ def run(schema: BaseSchema, config: ProbeConfig) -> list[ProbeRun]:
156
158
 
157
159
  session = Session()
158
160
  session.headers.update(config.headers or {})
159
- session.verify = config.request_tls_verify
160
- if config.request_cert is not None:
161
- session.cert = config.request_cert
161
+ session.verify = config.request.tls_verify
162
+ if config.request.cert is not None:
163
+ session.cert = config.request.cert
162
164
  if config.auth is not None:
163
165
  session.auth = get_requests_auth(config.auth, config.auth_type)
164
166