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
@@ -2,7 +2,9 @@
2
2
 
3
3
  These are basic entities that describe what data could be sent to the API.
4
4
  """
5
+
5
6
  from __future__ import annotations
7
+
6
8
  from dataclasses import dataclass, field
7
9
  from typing import TYPE_CHECKING, Any, Generator, Generic, TypeVar
8
10
 
@@ -53,6 +55,8 @@ class ParameterSet(Generic[P]):
53
55
 
54
56
  items: list[P] = field(default_factory=list)
55
57
 
58
+ def _repr_pretty_(self, *args: Any, **kwargs: Any) -> None: ...
59
+
56
60
  def add(self, parameter: P) -> None:
57
61
  """Add a new parameter."""
58
62
  self.items.append(parameter)
@@ -4,7 +4,6 @@ from random import Random
4
4
  from typing import TYPE_CHECKING, Any, Callable, Generator, Iterable
5
5
  from urllib.parse import urlparse
6
6
 
7
- from .._override import CaseOverride
8
7
  from ..constants import (
9
8
  DEFAULT_DEADLINE,
10
9
  DEFAULT_STATEFUL_RECURSION_LIMIT,
@@ -12,6 +11,7 @@ from ..constants import (
12
11
  )
13
12
  from ..exceptions import SchemaError
14
13
  from ..generation import DEFAULT_DATA_GENERATION_METHODS, DataGenerationMethod, GenerationConfig
14
+ from ..internal.checks import CheckConfig
15
15
  from ..internal.datetime import current_datetime
16
16
  from ..internal.deprecation import deprecated_function
17
17
  from ..internal.validation import file_exists
@@ -19,17 +19,20 @@ from ..loaders import load_app
19
19
  from ..specs.graphql import loaders as gql_loaders
20
20
  from ..specs.openapi import loaders as oas_loaders
21
21
  from ..targets import DEFAULT_TARGETS, Target
22
+ from ..transports import RequestConfig
22
23
  from ..transports.auth import get_requests_auth
23
24
  from ..types import Filter, NotSet, RawAuth, RequestCert
25
+ from . import events
24
26
  from .probes import ProbeConfig
25
27
 
26
28
  if TYPE_CHECKING:
27
29
  import hypothesis
28
30
 
31
+ from .._override import CaseOverride
29
32
  from ..models import CheckFunction
30
33
  from ..schemas import BaseSchema
34
+ from ..service.client import ServiceClient
31
35
  from ..stateful import Stateful
32
- from . import events
33
36
  from .impl import BaseRunner
34
37
 
35
38
 
@@ -78,6 +81,7 @@ def prepare(
78
81
  hypothesis_suppress_health_check: list[hypothesis.HealthCheck] | None = None,
79
82
  hypothesis_verbosity: hypothesis.Verbosity | None = None,
80
83
  probe_config: ProbeConfig | None = None,
84
+ service_client: ServiceClient | None = None,
81
85
  ) -> Generator[events.ExecutionEvent, None, None]:
82
86
  """Prepare a generator that will run test cases against the given API definition."""
83
87
  from ..checks import DEFAULT_CHECKS
@@ -132,6 +136,7 @@ def prepare(
132
136
  count_operations=count_operations,
133
137
  count_links=count_links,
134
138
  probe_config=probe_config,
139
+ service_client=service_client,
135
140
  )
136
141
 
137
142
 
@@ -193,6 +198,7 @@ def execute_from_schema(
193
198
  count_operations: bool = True,
194
199
  count_links: bool = True,
195
200
  probe_config: ProbeConfig | None = None,
201
+ service_client: ServiceClient | None,
196
202
  ) -> Generator[events.ExecutionEvent, None, None]:
197
203
  """Execute tests for the given schema.
198
204
 
@@ -243,6 +249,7 @@ def execute_from_schema(
243
249
  count_operations=count_operations,
244
250
  count_links=count_links,
245
251
  probe_config=probe_config,
252
+ service_client=service_client,
246
253
  ).execute()
247
254
  except SchemaError as error:
248
255
  yield events.InternalError.from_schema_error(error)
@@ -273,7 +280,7 @@ def load_schema(
273
280
  operation_id: Filter | None = None,
274
281
  ) -> BaseSchema:
275
282
  """Load schema via specified loader and parameters."""
276
- loader_options = {
283
+ loader_options: dict[str, Any] = {
277
284
  key: value
278
285
  for key, value in (
279
286
  ("base_url", base_url),
@@ -339,8 +346,10 @@ def from_schema(
339
346
  request_cert: RequestCert | None = None,
340
347
  seed: int | None = None,
341
348
  exit_first: bool = False,
349
+ no_failfast: bool = False,
342
350
  max_failures: int | None = None,
343
351
  started_at: str | None = None,
352
+ unique_data: bool = False,
344
353
  dry_run: bool = False,
345
354
  store_interactions: bool = False,
346
355
  stateful: Stateful | None = None,
@@ -348,11 +357,13 @@ def from_schema(
348
357
  count_operations: bool = True,
349
358
  count_links: bool = True,
350
359
  probe_config: ProbeConfig | None = None,
360
+ checks_config: CheckConfig | None = None,
361
+ service_client: ServiceClient | None = None,
351
362
  ) -> BaseRunner:
352
363
  import hypothesis
353
- from starlette.applications import Starlette
354
364
 
355
365
  from ..checks import DEFAULT_CHECKS
366
+ from ..transports.asgi import is_asgi_app
356
367
  from .impl import (
357
368
  SingleThreadASGIRunner,
358
369
  SingleThreadRunner,
@@ -363,10 +374,16 @@ def from_schema(
363
374
  )
364
375
 
365
376
  checks = checks or DEFAULT_CHECKS
377
+ checks_config = checks_config or CheckConfig()
366
378
  probe_config = probe_config or ProbeConfig()
367
379
 
368
380
  hypothesis_settings = hypothesis_settings or hypothesis.settings(deadline=DEFAULT_DEADLINE)
369
- generation_config = generation_config or GenerationConfig()
381
+ request_config = RequestConfig(
382
+ timeout=request_timeout,
383
+ tls_verify=request_tls_verify,
384
+ proxy=request_proxy,
385
+ cert=request_cert,
386
+ )
370
387
 
371
388
  # Use the same seed for all tests unless `derandomize=True` is used
372
389
  if seed is None and not hypothesis_settings.derandomize:
@@ -388,13 +405,12 @@ def from_schema(
388
405
  headers=headers,
389
406
  seed=seed,
390
407
  workers_num=workers_num,
391
- request_timeout=request_timeout,
392
- request_tls_verify=request_tls_verify,
393
- request_proxy=request_proxy,
394
- request_cert=request_cert,
408
+ request_config=request_config,
395
409
  exit_first=exit_first,
410
+ no_failfast=no_failfast,
396
411
  max_failures=max_failures,
397
412
  started_at=started_at,
413
+ unique_data=unique_data,
398
414
  dry_run=dry_run,
399
415
  store_interactions=store_interactions,
400
416
  stateful=stateful,
@@ -402,8 +418,10 @@ def from_schema(
402
418
  count_operations=count_operations,
403
419
  count_links=count_links,
404
420
  probe_config=probe_config,
421
+ checks_config=checks_config,
422
+ service_client=service_client,
405
423
  )
406
- if isinstance(schema.app, Starlette):
424
+ if is_asgi_app(schema.app):
407
425
  return ThreadPoolASGIRunner(
408
426
  schema=schema,
409
427
  checks=checks,
@@ -417,8 +435,10 @@ def from_schema(
417
435
  headers=headers,
418
436
  seed=seed,
419
437
  exit_first=exit_first,
438
+ no_failfast=no_failfast,
420
439
  max_failures=max_failures,
421
440
  started_at=started_at,
441
+ unique_data=unique_data,
422
442
  dry_run=dry_run,
423
443
  store_interactions=store_interactions,
424
444
  stateful=stateful,
@@ -426,6 +446,8 @@ def from_schema(
426
446
  count_operations=count_operations,
427
447
  count_links=count_links,
428
448
  probe_config=probe_config,
449
+ checks_config=checks_config,
450
+ service_client=service_client,
429
451
  )
430
452
  return ThreadPoolWSGIRunner(
431
453
  schema=schema,
@@ -441,8 +463,10 @@ def from_schema(
441
463
  seed=seed,
442
464
  workers_num=workers_num,
443
465
  exit_first=exit_first,
466
+ no_failfast=no_failfast,
444
467
  max_failures=max_failures,
445
468
  started_at=started_at,
469
+ unique_data=unique_data,
446
470
  dry_run=dry_run,
447
471
  store_interactions=store_interactions,
448
472
  stateful=stateful,
@@ -450,6 +474,8 @@ def from_schema(
450
474
  count_operations=count_operations,
451
475
  count_links=count_links,
452
476
  probe_config=probe_config,
477
+ checks_config=checks_config,
478
+ service_client=service_client,
453
479
  )
454
480
  if not schema.app:
455
481
  return SingleThreadRunner(
@@ -464,13 +490,12 @@ def from_schema(
464
490
  override=override,
465
491
  headers=headers,
466
492
  seed=seed,
467
- request_timeout=request_timeout,
468
- request_tls_verify=request_tls_verify,
469
- request_proxy=request_proxy,
470
- request_cert=request_cert,
493
+ request_config=request_config,
471
494
  exit_first=exit_first,
495
+ no_failfast=no_failfast,
472
496
  max_failures=max_failures,
473
497
  started_at=started_at,
498
+ unique_data=unique_data,
474
499
  dry_run=dry_run,
475
500
  store_interactions=store_interactions,
476
501
  stateful=stateful,
@@ -478,8 +503,10 @@ def from_schema(
478
503
  count_operations=count_operations,
479
504
  count_links=count_links,
480
505
  probe_config=probe_config,
506
+ checks_config=checks_config,
507
+ service_client=service_client,
481
508
  )
482
- if isinstance(schema.app, Starlette):
509
+ if is_asgi_app(schema.app):
483
510
  return SingleThreadASGIRunner(
484
511
  schema=schema,
485
512
  checks=checks,
@@ -493,8 +520,10 @@ def from_schema(
493
520
  headers=headers,
494
521
  seed=seed,
495
522
  exit_first=exit_first,
523
+ no_failfast=no_failfast,
496
524
  max_failures=max_failures,
497
525
  started_at=started_at,
526
+ unique_data=unique_data,
498
527
  dry_run=dry_run,
499
528
  store_interactions=store_interactions,
500
529
  stateful=stateful,
@@ -502,6 +531,8 @@ def from_schema(
502
531
  count_operations=count_operations,
503
532
  count_links=count_links,
504
533
  probe_config=probe_config,
534
+ checks_config=checks_config,
535
+ service_client=service_client,
505
536
  )
506
537
  return SingleThreadWSGIRunner(
507
538
  schema=schema,
@@ -516,8 +547,10 @@ def from_schema(
516
547
  headers=headers,
517
548
  seed=seed,
518
549
  exit_first=exit_first,
550
+ no_failfast=no_failfast,
519
551
  max_failures=max_failures,
520
552
  started_at=started_at,
553
+ unique_data=unique_data,
521
554
  dry_run=dry_run,
522
555
  store_interactions=store_interactions,
523
556
  stateful=stateful,
@@ -525,6 +558,8 @@ def from_schema(
525
558
  count_operations=count_operations,
526
559
  count_links=count_links,
527
560
  probe_config=probe_config,
561
+ checks_config=checks_config,
562
+ service_client=service_client,
528
563
  )
529
564
 
530
565
 
@@ -1,19 +1,22 @@
1
1
  from __future__ import annotations
2
+
2
3
  import enum
3
4
  import threading
4
5
  import time
5
6
  from dataclasses import asdict, dataclass, field
6
- from typing import Any, TYPE_CHECKING
7
+ from typing import TYPE_CHECKING, Any
7
8
 
9
+ from ..exceptions import RuntimeErrorType, SchemaError, SchemaErrorType, format_exception
8
10
  from ..internal.datetime import current_datetime
9
- from ..generation import DataGenerationMethod
10
- from ..exceptions import SchemaError, SchemaErrorType, format_exception, RuntimeErrorType
11
+ from ..internal.result import Err, Ok, Result
11
12
  from .serialization import SerializedError, SerializedTestResult
12
13
 
13
-
14
14
  if TYPE_CHECKING:
15
+ from ..generation import DataGenerationMethod
15
16
  from ..models import APIOperation, Status, TestResult, TestResultSet
16
- from ..schemas import BaseSchema
17
+ from ..schemas import BaseSchema, Specification
18
+ from ..service.models import AnalysisResult
19
+ from ..stateful import events
17
20
  from . import probes
18
21
 
19
22
 
@@ -36,6 +39,7 @@ class Initialized(ExecutionEvent):
36
39
  """Runner is initialized, settings are prepared, requests session is ready."""
37
40
 
38
41
  schema: dict[str, Any]
42
+ specification: Specification
39
43
  # Total number of operations in the schema
40
44
  operations_count: int | None
41
45
  # Total number of links in the schema
@@ -45,6 +49,8 @@ class Initialized(ExecutionEvent):
45
49
  seed: int | None
46
50
  # The base URL against which the tests are running
47
51
  base_url: str
52
+ # The base path part of every operation
53
+ base_path: str
48
54
  # API schema specification name
49
55
  specification_name: str
50
56
  # Monotonic clock value when the test run started. Used to properly calculate run duration, since this clock
@@ -68,10 +74,12 @@ class Initialized(ExecutionEvent):
68
74
  """Computes all needed data from a schema instance."""
69
75
  return cls(
70
76
  schema=schema.raw_schema,
77
+ specification=schema.specification,
71
78
  operations_count=schema.operations_count if count_operations else None,
72
79
  links_count=schema.links_count if count_links else None,
73
80
  location=schema.location,
74
81
  base_url=schema.get_base_url(),
82
+ base_path=schema.base_path,
75
83
  start_time=start_time or time.monotonic(),
76
84
  started_at=started_at or current_datetime(),
77
85
  specification_name=schema.verbose_name,
@@ -93,6 +101,35 @@ class AfterProbing(ExecutionEvent):
93
101
  return {"probes": [probe.serialize() for probe in probes], "events_type": self.__class__.__name__}
94
102
 
95
103
 
104
+ @dataclass
105
+ class BeforeAnalysis(ExecutionEvent):
106
+ pass
107
+
108
+
109
+ @dataclass
110
+ class AfterAnalysis(ExecutionEvent):
111
+ analysis: Result[AnalysisResult, Exception] | None
112
+
113
+ def _serialize(self) -> dict[str, Any]:
114
+ from ..service.models import AnalysisSuccess
115
+
116
+ data = {}
117
+ if isinstance(self.analysis, Ok):
118
+ result = self.analysis.ok()
119
+ if isinstance(result, AnalysisSuccess):
120
+ data["analysis_id"] = result.id
121
+ else:
122
+ data["error"] = result.message
123
+ elif isinstance(self.analysis, Err):
124
+ data["error"] = format_exception(self.analysis.err())
125
+ return data
126
+
127
+ def asdict(self, **kwargs: Any) -> dict[str, Any]:
128
+ data = self._serialize()
129
+ data["event_type"] = self.__class__.__name__
130
+ return data
131
+
132
+
96
133
  class CurrentOperationMixin:
97
134
  method: str
98
135
  path: str
@@ -275,6 +312,29 @@ class InternalError(ExecutionEvent):
275
312
  )
276
313
 
277
314
 
315
+ @dataclass
316
+ class StatefulEvent(ExecutionEvent):
317
+ """Represents an event originating from the state machine runner."""
318
+
319
+ data: events.StatefulEvent
320
+
321
+ __slots__ = ("data",)
322
+
323
+ def asdict(self, **kwargs: Any) -> dict[str, Any]:
324
+ return {"data": self.data.asdict(**kwargs), "event_type": self.__class__.__name__}
325
+
326
+
327
+ @dataclass
328
+ class AfterStatefulExecution(ExecutionEvent):
329
+ """Happens after the stateful test run."""
330
+
331
+ status: Status
332
+ result: SerializedTestResult
333
+ elapsed_time: float
334
+ data_generation_method: list[DataGenerationMethod]
335
+ thread_id: int = field(default_factory=threading.get_ident)
336
+
337
+
278
338
  @dataclass
279
339
  class Finished(ExecutionEvent):
280
340
  """The final event of the run.
@@ -0,0 +1,104 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from ...constants import NOT_SET
7
+ from ...internal.checks import CheckConfig
8
+ from ...models import TestResult, TestResultSet
9
+
10
+ if TYPE_CHECKING:
11
+ import threading
12
+
13
+ from ..._override import CaseOverride
14
+ from ...exceptions import OperationSchemaError
15
+ from ...models import Case
16
+ from ...types import NotSet, RawAuth
17
+
18
+
19
+ @dataclass
20
+ class RunnerContext:
21
+ """Holds context shared for a test run."""
22
+
23
+ data: TestResultSet
24
+ auth: RawAuth | None
25
+ seed: int | None
26
+ stop_event: threading.Event
27
+ unique_data: bool
28
+ outcome_cache: dict[int, BaseException | None]
29
+ checks_config: CheckConfig
30
+ override: CaseOverride | None
31
+ no_failfast: bool
32
+
33
+ __slots__ = (
34
+ "data",
35
+ "auth",
36
+ "seed",
37
+ "stop_event",
38
+ "unique_data",
39
+ "outcome_cache",
40
+ "checks_config",
41
+ "override",
42
+ "no_failfast",
43
+ )
44
+
45
+ def __init__(
46
+ self,
47
+ *,
48
+ seed: int | None,
49
+ auth: RawAuth | None,
50
+ stop_event: threading.Event,
51
+ unique_data: bool,
52
+ checks_config: CheckConfig,
53
+ override: CaseOverride | None,
54
+ no_failfast: bool,
55
+ ) -> None:
56
+ self.data = TestResultSet(seed=seed)
57
+ self.auth = auth
58
+ self.seed = seed
59
+ self.stop_event = stop_event
60
+ self.outcome_cache = {}
61
+ self.unique_data = unique_data
62
+ self.checks_config = checks_config
63
+ self.override = override
64
+ self.no_failfast = no_failfast
65
+
66
+ def _repr_pretty_(self, *args: Any, **kwargs: Any) -> None: ...
67
+
68
+ @property
69
+ def is_stopped(self) -> bool:
70
+ return self.stop_event.is_set()
71
+
72
+ @property
73
+ def has_all_not_found(self) -> bool:
74
+ """Check if all responses are 404."""
75
+ has_not_found = False
76
+ for entry in self.data.results:
77
+ for check in entry.checks:
78
+ if check.response is not None:
79
+ if check.response.status_code == 404:
80
+ has_not_found = True
81
+ else:
82
+ # There are non-404 responses, no reason to check any other response
83
+ return False
84
+ # Only happens if all responses are 404, or there are no responses at all.
85
+ # In the first case, it returns True, for the latter - False
86
+ return has_not_found
87
+
88
+ def add_result(self, result: TestResult) -> None:
89
+ self.data.append(result)
90
+
91
+ def add_generic_error(self, error: OperationSchemaError) -> None:
92
+ self.data.generic_errors.append(error)
93
+
94
+ def add_warning(self, message: str) -> None:
95
+ self.data.add_warning(message)
96
+
97
+ def cache_outcome(self, case: Case, outcome: BaseException | None) -> None:
98
+ self.outcome_cache[hash(case)] = outcome
99
+
100
+ def get_cached_outcome(self, case: Case) -> BaseException | None | NotSet:
101
+ return self.outcome_cache.get(hash(case), NOT_SET)
102
+
103
+
104
+ ALL_NOT_FOUND_WARNING_MESSAGE = "All API responses have a 404 status code. Did you specify the proper API location?"