schemathesis 3.35.4__py3-none-any.whl → 3.36.0__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 (85) hide show
  1. schemathesis/__init__.py +5 -5
  2. schemathesis/_hypothesis.py +12 -6
  3. schemathesis/_override.py +4 -4
  4. schemathesis/auths.py +1 -1
  5. schemathesis/checks.py +8 -5
  6. schemathesis/cli/__init__.py +23 -26
  7. schemathesis/cli/callbacks.py +6 -4
  8. schemathesis/cli/cassettes.py +67 -41
  9. schemathesis/cli/context.py +7 -6
  10. schemathesis/cli/junitxml.py +1 -1
  11. schemathesis/cli/options.py +7 -4
  12. schemathesis/cli/output/default.py +5 -5
  13. schemathesis/cli/reporting.py +4 -2
  14. schemathesis/code_samples.py +4 -3
  15. schemathesis/contrib/unique_data.py +1 -2
  16. schemathesis/exceptions.py +4 -3
  17. schemathesis/extra/_flask.py +4 -1
  18. schemathesis/extra/pytest_plugin.py +6 -3
  19. schemathesis/failures.py +2 -1
  20. schemathesis/filters.py +2 -2
  21. schemathesis/generation/__init__.py +2 -2
  22. schemathesis/generation/_hypothesis.py +1 -1
  23. schemathesis/generation/coverage.py +53 -12
  24. schemathesis/graphql.py +0 -1
  25. schemathesis/hooks.py +3 -3
  26. schemathesis/internal/checks.py +53 -0
  27. schemathesis/lazy.py +10 -7
  28. schemathesis/loaders.py +3 -3
  29. schemathesis/models.py +59 -23
  30. schemathesis/runner/__init__.py +12 -6
  31. schemathesis/runner/events.py +1 -1
  32. schemathesis/runner/impl/context.py +72 -0
  33. schemathesis/runner/impl/core.py +105 -67
  34. schemathesis/runner/impl/solo.py +17 -20
  35. schemathesis/runner/impl/threadpool.py +65 -72
  36. schemathesis/runner/serialization.py +4 -3
  37. schemathesis/sanitization.py +2 -1
  38. schemathesis/schemas.py +20 -22
  39. schemathesis/serializers.py +2 -0
  40. schemathesis/service/client.py +1 -1
  41. schemathesis/service/events.py +4 -1
  42. schemathesis/service/extensions.py +2 -2
  43. schemathesis/service/hosts.py +4 -2
  44. schemathesis/service/models.py +3 -3
  45. schemathesis/service/report.py +3 -3
  46. schemathesis/service/serialization.py +4 -2
  47. schemathesis/specs/graphql/loaders.py +5 -4
  48. schemathesis/specs/graphql/schemas.py +13 -8
  49. schemathesis/specs/openapi/checks.py +76 -27
  50. schemathesis/specs/openapi/definitions.py +1 -5
  51. schemathesis/specs/openapi/examples.py +92 -2
  52. schemathesis/specs/openapi/expressions/__init__.py +7 -0
  53. schemathesis/specs/openapi/expressions/extractors.py +4 -1
  54. schemathesis/specs/openapi/expressions/nodes.py +5 -3
  55. schemathesis/specs/openapi/links.py +4 -4
  56. schemathesis/specs/openapi/loaders.py +6 -5
  57. schemathesis/specs/openapi/negative/__init__.py +5 -3
  58. schemathesis/specs/openapi/negative/mutations.py +5 -4
  59. schemathesis/specs/openapi/parameters.py +4 -2
  60. schemathesis/specs/openapi/schemas.py +28 -13
  61. schemathesis/specs/openapi/security.py +6 -4
  62. schemathesis/specs/openapi/stateful/__init__.py +2 -2
  63. schemathesis/specs/openapi/stateful/statistic.py +3 -3
  64. schemathesis/specs/openapi/stateful/types.py +3 -2
  65. schemathesis/stateful/__init__.py +3 -3
  66. schemathesis/stateful/config.py +2 -1
  67. schemathesis/stateful/context.py +13 -3
  68. schemathesis/stateful/events.py +3 -3
  69. schemathesis/stateful/runner.py +24 -6
  70. schemathesis/stateful/sink.py +1 -1
  71. schemathesis/stateful/state_machine.py +7 -6
  72. schemathesis/stateful/statistic.py +3 -1
  73. schemathesis/stateful/validation.py +10 -5
  74. schemathesis/transports/__init__.py +2 -2
  75. schemathesis/transports/asgi.py +7 -0
  76. schemathesis/transports/auth.py +2 -1
  77. schemathesis/transports/content_types.py +1 -1
  78. schemathesis/transports/responses.py +2 -1
  79. schemathesis/utils.py +4 -2
  80. {schemathesis-3.35.4.dist-info → schemathesis-3.36.0.dist-info}/METADATA +1 -1
  81. schemathesis-3.36.0.dist-info/RECORD +157 -0
  82. schemathesis-3.35.4.dist-info/RECORD +0 -154
  83. {schemathesis-3.35.4.dist-info → schemathesis-3.36.0.dist-info}/WHEEL +0 -0
  84. {schemathesis-3.35.4.dist-info → schemathesis-3.36.0.dist-info}/entry_points.txt +0 -0
  85. {schemathesis-3.35.4.dist-info → schemathesis-3.36.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,26 +7,31 @@ 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, TestResultSet
19
16
  from ...stateful import Feedback, Stateful
20
- from ...targets import Target
21
17
  from ...transports.auth import get_requests_auth
22
- from ...types import RawAuth
23
18
  from ...utils import capture_hypothesis_output
24
19
  from .. import events
25
20
  from .core import BaseRunner, asgi_test, get_session, handle_schema_error, network_test, run_test, wsgi_test
26
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
+
27
31
 
28
32
  def _run_task(
29
- test_template: Callable,
33
+ *,
34
+ test_func: Callable,
30
35
  tasks_queue: Queue,
31
36
  events_queue: Queue,
32
37
  generator_done: threading.Event,
@@ -35,8 +40,7 @@ def _run_task(
35
40
  data_generation_methods: Iterable[DataGenerationMethod],
36
41
  settings: hypothesis.settings,
37
42
  generation_config: GenerationConfig,
38
- seed: int | None,
39
- results: TestResultSet,
43
+ ctx: RunnerContext,
40
44
  stateful: Stateful | None,
41
45
  stateful_recursion_limit: int,
42
46
  headers: dict[str, Any] | None = None,
@@ -51,10 +55,10 @@ def _run_task(
51
55
  if recursion_level > stateful_recursion_limit:
52
56
  return
53
57
  for _result in maker(
54
- test_template,
58
+ test_func,
55
59
  settings=settings,
56
60
  generation_config=generation_config,
57
- seed=seed,
61
+ seed=ctx.seed,
58
62
  as_strategy_kwargs=as_strategy_kwargs,
59
63
  ):
60
64
  # `result` is always `Ok` here
@@ -66,7 +70,7 @@ def _run_task(
66
70
  checks,
67
71
  data_generation_methods,
68
72
  targets,
69
- results,
73
+ ctx=ctx,
70
74
  recursion_level=recursion_level,
71
75
  feedback=feedback,
72
76
  headers=headers,
@@ -89,9 +93,9 @@ def _run_task(
89
93
  operation = result.ok()
90
94
  test_function = create_test(
91
95
  operation=operation,
92
- test=test_template,
96
+ test=test_func,
93
97
  settings=settings,
94
- seed=seed,
98
+ seed=ctx.seed,
95
99
  data_generation_methods=list(data_generation_methods),
96
100
  generation_config=generation_config,
97
101
  as_strategy_kwargs=as_strategy_kwargs,
@@ -101,7 +105,7 @@ def _run_task(
101
105
  # `feedback.get_stateful_tests`
102
106
  _run_tests(lambda *_, **__: (items,)) # noqa: B023
103
107
  else:
104
- 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):
105
109
  events_queue.put(event)
106
110
 
107
111
 
@@ -117,8 +121,7 @@ def thread_task(
117
121
  auth: RawAuth | None,
118
122
  auth_type: str | None,
119
123
  headers: dict[str, Any] | None,
120
- seed: int | None,
121
- results: TestResultSet,
124
+ ctx: RunnerContext,
122
125
  stateful: Stateful | None,
123
126
  stateful_recursion_limit: int,
124
127
  kwargs: Any,
@@ -130,17 +133,16 @@ def thread_task(
130
133
  prepared_auth = get_requests_auth(auth, auth_type)
131
134
  with get_session(prepared_auth) as session:
132
135
  _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
- seed,
143
- 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,
144
146
  stateful=stateful,
145
147
  stateful_recursion_limit=stateful_recursion_limit,
146
148
  session=session,
@@ -158,24 +160,22 @@ def wsgi_thread_task(
158
160
  data_generation_methods: Iterable[DataGenerationMethod],
159
161
  settings: hypothesis.settings,
160
162
  generation_config: GenerationConfig,
161
- seed: int | None,
162
- results: TestResultSet,
163
+ ctx: RunnerContext,
163
164
  stateful: Stateful | None,
164
165
  stateful_recursion_limit: int,
165
166
  kwargs: Any,
166
167
  ) -> None:
167
168
  _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
- seed,
178
- 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,
179
179
  stateful=stateful,
180
180
  stateful_recursion_limit=stateful_recursion_limit,
181
181
  **kwargs,
@@ -192,24 +192,22 @@ def asgi_thread_task(
192
192
  settings: hypothesis.settings,
193
193
  generation_config: GenerationConfig,
194
194
  headers: dict[str, Any] | None,
195
- seed: int | None,
196
- results: TestResultSet,
195
+ ctx: RunnerContext,
197
196
  stateful: Stateful | None,
198
197
  stateful_recursion_limit: int,
199
198
  kwargs: Any,
200
199
  ) -> None:
201
200
  _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
- seed,
212
- 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,
213
211
  stateful=stateful,
214
212
  stateful_recursion_limit=stateful_recursion_limit,
215
213
  headers=headers,
@@ -228,9 +226,7 @@ class ThreadPoolRunner(BaseRunner):
228
226
 
229
227
  workers_num: int = 2
230
228
 
231
- def _execute(
232
- self, results: TestResultSet, stop_event: threading.Event
233
- ) -> Generator[events.ExecutionEvent, None, None]:
229
+ def _execute(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
234
230
  """All events come from a queue where different workers push their events."""
235
231
  # Instead of generating all tests at once, we do it when there is a free worker to pick it up
236
232
  # This is extremely important for memory consumption when testing large schemas
@@ -238,7 +234,7 @@ class ThreadPoolRunner(BaseRunner):
238
234
  # It would be better to have a separate producer thread and communicate via threading events.
239
235
  # Though it is a bit more complex, so the current solution is suboptimal in terms of resources utilization,
240
236
  # but good enough and easy enough to implement.
241
- tasks_generator = iter(self.schema.get_all_operations())
237
+ tasks_generator = iter(self.schema.get_all_operations(generation_config=self.generation_config))
242
238
  generator_done = threading.Event()
243
239
  tasks_queue: Queue = Queue()
244
240
  # Add at least `workers_num` tasks first, so all workers are busy
@@ -251,7 +247,7 @@ class ThreadPoolRunner(BaseRunner):
251
247
  break
252
248
  # Events are pushed by workers via a separate queue
253
249
  events_queue: Queue = Queue()
254
- workers = self._init_workers(tasks_queue, events_queue, results, generator_done)
250
+ workers = self._init_workers(tasks_queue, events_queue, ctx, generator_done)
255
251
 
256
252
  def stop_workers() -> None:
257
253
  for worker in workers:
@@ -270,12 +266,12 @@ class ThreadPoolRunner(BaseRunner):
270
266
  is_finished = all(not worker.is_alive() for worker in workers)
271
267
  while not events_queue.empty():
272
268
  event = events_queue.get()
273
- 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):
274
270
  # We could still have events in the queue, but ignore them to keep the logic simple
275
271
  # for now, could be improved in the future to show more info in such corner cases
276
272
  stop_workers()
277
273
  is_finished = True
278
- if stop_event.is_set():
274
+ if ctx.is_stopped:
279
275
  # Discard the event. The invariant is: the next event after `stream.stop()` is `Finished`
280
276
  break
281
277
  yield event
@@ -292,13 +288,13 @@ class ThreadPoolRunner(BaseRunner):
292
288
  yield events.Interrupted()
293
289
 
294
290
  def _init_workers(
295
- 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
296
292
  ) -> list[threading.Thread]:
297
293
  """Initialize & start workers that will execute tests."""
298
294
  workers = [
299
295
  threading.Thread(
300
296
  target=self._get_task(),
301
- 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),
302
298
  name=f"schemathesis_{num}",
303
299
  )
304
300
  for num in range(self.workers_num)
@@ -311,7 +307,7 @@ class ThreadPoolRunner(BaseRunner):
311
307
  return thread_task
312
308
 
313
309
  def _get_worker_kwargs(
314
- 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
315
311
  ) -> dict[str, Any]:
316
312
  return {
317
313
  "tasks_queue": tasks_queue,
@@ -324,8 +320,7 @@ class ThreadPoolRunner(BaseRunner):
324
320
  "auth": self.auth,
325
321
  "auth_type": self.auth_type,
326
322
  "headers": self.headers,
327
- "seed": self.seed,
328
- "results": results,
323
+ "ctx": ctx,
329
324
  "stateful": self.stateful,
330
325
  "stateful_recursion_limit": self.stateful_recursion_limit,
331
326
  "data_generation_methods": self.schema.data_generation_methods,
@@ -343,7 +338,7 @@ class ThreadPoolWSGIRunner(ThreadPoolRunner):
343
338
  return wsgi_thread_task
344
339
 
345
340
  def _get_worker_kwargs(
346
- 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
347
342
  ) -> dict[str, Any]:
348
343
  return {
349
344
  "tasks_queue": tasks_queue,
@@ -353,8 +348,7 @@ class ThreadPoolWSGIRunner(ThreadPoolRunner):
353
348
  "targets": self.targets,
354
349
  "settings": self.hypothesis_settings,
355
350
  "generation_config": self.generation_config,
356
- "seed": self.seed,
357
- "results": results,
351
+ "ctx": ctx,
358
352
  "stateful": self.stateful,
359
353
  "stateful_recursion_limit": self.stateful_recursion_limit,
360
354
  "data_generation_methods": self.schema.data_generation_methods,
@@ -374,7 +368,7 @@ class ThreadPoolASGIRunner(ThreadPoolRunner):
374
368
  return asgi_thread_task
375
369
 
376
370
  def _get_worker_kwargs(
377
- 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
378
372
  ) -> dict[str, Any]:
379
373
  return {
380
374
  "tasks_queue": tasks_queue,
@@ -385,8 +379,7 @@ class ThreadPoolASGIRunner(ThreadPoolRunner):
385
379
  "settings": self.hypothesis_settings,
386
380
  "generation_config": self.generation_config,
387
381
  "headers": self.headers,
388
- "seed": self.seed,
389
- "results": results,
382
+ "ctx": ctx,
390
383
  "stateful": self.stateful,
391
384
  "stateful_recursion_limit": self.stateful_recursion_limit,
392
385
  "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
@@ -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
- sanitize_value(interaction.response.headers, config=config)
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)
@@ -241,7 +241,7 @@ class BaseSchema(Mapping):
241
241
  raise NotImplementedError
242
242
 
243
243
  def get_all_operations(
244
- self, hooks: HookDispatcher | None = None
244
+ self, hooks: HookDispatcher | None = None, generation_config: GenerationConfig | None = None
245
245
  ) -> Generator[Result[APIOperation, OperationSchemaError], None, None]:
246
246
  raise NotImplementedError
247
247
 
@@ -276,7 +276,7 @@ class BaseSchema(Mapping):
276
276
  _given_kwargs: dict[str, GivenInput] | None = None,
277
277
  ) -> Generator[Result[tuple[APIOperation, Callable], OperationSchemaError], None, None]:
278
278
  """Generate all operations and Hypothesis tests for them."""
279
- for result in self.get_all_operations(hooks=hooks):
279
+ for result in self.get_all_operations(hooks=hooks, generation_config=generation_config):
280
280
  if isinstance(result, Ok):
281
281
  operation = result.ok()
282
282
  _as_strategy_kwargs: dict[str, Any] | None
@@ -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,
@@ -43,6 +43,8 @@ class Binary(str):
43
43
 
44
44
  data: bytes
45
45
 
46
+ __slots__ = ("data",)
47
+
46
48
 
47
49
  @dataclass
48
50
  class SerializerContext:
@@ -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:
@@ -1,9 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING
4
5
 
5
6
  from ..exceptions import format_exception
6
- from . import ci
7
+
8
+ if TYPE_CHECKING:
9
+ from . import ci
7
10
 
8
11
 
9
12
  class Event:
@@ -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, Optional
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) -> Optional[st.SearchStrategy]:
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
@@ -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:
@@ -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
- elif data["type"] == "string_formats":
227
+ if data["type"] == "string_formats":
228
228
  return OpenApiStringFormatsExtension.from_dict(formats=data["items"])
229
- elif data["type"] == "scalars":
229
+ if data["type"] == "scalars":
230
230
  return GraphQLScalarsExtension.from_dict(scalars=data["items"])
231
- elif data["type"] == "media_types":
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
 
@@ -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
- from ..stateful import events as stateful_events
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,11 +139,12 @@ def from_url(
139
139
  interval=WAIT_FOR_SCHEMA_INTERVAL,
140
140
  )
141
141
  def _load_schema(_uri: str, **_kwargs: Any) -> requests.Response:
142
- return requests.post(_uri, **kwargs)
142
+ return requests.post(_uri, **_kwargs)
143
143
 
144
144
  else:
145
145
  _load_schema = requests.post
146
146
 
147
+ kwargs.setdefault("timeout", DEFAULT_RESPONSE_TIMEOUT / 1000)
147
148
  response = load_schema_from_url(lambda: _load_schema(url, **kwargs))
148
149
  raw_schema = extract_schema_from_response(response)
149
150
  return from_dict(
@@ -356,8 +357,8 @@ def from_asgi(
356
357
 
357
358
 
358
359
  def get_loader_for_app(app: Any) -> Callable:
359
- from starlette.applications import Starlette
360
+ from ...transports.asgi import is_asgi_app
360
361
 
361
- if isinstance(app, Starlette):
362
+ if is_asgi_app(app):
362
363
  return from_asgi
363
364
  return from_wsgi