schemathesis 3.25.5__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 +793 -448
  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 +24 -4
  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 +286 -115
  23. schemathesis/cli/output/short.py +25 -6
  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 +323 -213
  61. schemathesis/parameters.py +4 -0
  62. schemathesis/runner/__init__.py +72 -22
  63. schemathesis/runner/events.py +86 -6
  64. schemathesis/runner/impl/context.py +104 -0
  65. schemathesis/runner/impl/core.py +447 -187
  66. schemathesis/runner/impl/solo.py +19 -29
  67. schemathesis/runner/impl/threadpool.py +70 -79
  68. schemathesis/{cli → runner}/probes.py +37 -25
  69. schemathesis/runner/serialization.py +150 -17
  70. schemathesis/sanitization.py +5 -1
  71. schemathesis/schemas.py +170 -102
  72. schemathesis/serializers.py +17 -4
  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 +60 -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 +79 -61
  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 +143 -31
  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 +368 -242
  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.5.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.5.dist-info/METADATA +0 -356
  144. schemathesis-3.25.5.dist-info/RECORD +0 -134
  145. {schemathesis-3.25.5.dist-info → schemathesis-3.39.7.dist-info}/entry_points.txt +0 -0
  146. {schemathesis-3.25.5.dist-info → schemathesis-3.39.7.dist-info}/licenses/LICENSE +0 -0
schemathesis/lazy.py CHANGED
@@ -1,29 +1,28 @@
1
1
  from __future__ import annotations
2
+
3
+ from contextlib import nullcontext
2
4
  from dataclasses import dataclass, field
3
5
  from inspect import signature
4
- from typing import Any, Callable, Generator
6
+ from typing import TYPE_CHECKING, Any, Callable, Generator, Type
5
7
 
6
8
  import pytest
7
- from _pytest.fixtures import FixtureRequest
8
9
  from hypothesis.core import HypothesisHandle
9
10
  from hypothesis.errors import Flaky
10
11
  from hypothesis.internal.escalation import format_exception, get_trimmed_traceback
11
12
  from hypothesis.internal.reflection import impersonate
12
- from pyrate_limiter import Limiter
13
- from pytest_subtests import SubTests, nullcontext
13
+ from pytest_subtests import SubTests
14
14
 
15
15
  from ._compat import MultipleFailures, get_interesting_origin
16
- from ._override import check_no_override_mark, CaseOverride, set_override_mark, get_override_from_mark
16
+ from ._override import CaseOverride, check_no_override_mark, get_override_from_mark, set_override_mark
17
17
  from .auths import AuthStorage
18
18
  from .code_samples import CodeSampleStyle
19
19
  from .constants import FLAKY_FAILURE_MESSAGE, NOT_SET
20
- from .generation import DataGenerationMethodInput, GenerationConfig
21
20
  from .exceptions import CheckFailed, OperationSchemaError, SkipTest, get_grouped_exception
21
+ from .filters import FilterSet, FilterValue, MatcherFunc, RegexValue, filter_set_from_components, is_deprecated
22
22
  from .hooks import HookDispatcher, HookScope
23
+ from .internal.deprecation import warn_filtration_arguments
23
24
  from .internal.result import Ok
24
- from .models import APIOperation
25
25
  from .schemas import BaseSchema
26
- from .types import Filter, GenericTest, NotSet
27
26
  from .utils import (
28
27
  GivenInput,
29
28
  fail_on_no_matches,
@@ -35,26 +34,130 @@ from .utils import (
35
34
  validate_given_args,
36
35
  )
37
36
 
37
+ if TYPE_CHECKING:
38
+ from _pytest.fixtures import FixtureRequest
39
+ from pyrate_limiter import Limiter
40
+
41
+ from .generation import DataGenerationMethodInput, GenerationConfig
42
+ from .internal.output import OutputConfig
43
+ from .models import APIOperation
44
+ from .types import Filter, GenericTest, NotSet
45
+
38
46
 
39
47
  @dataclass
40
48
  class LazySchema:
41
49
  fixture_name: str
42
50
  base_url: str | None | NotSet = NOT_SET
43
- method: Filter | None = NOT_SET
44
- endpoint: Filter | None = NOT_SET
45
- tag: Filter | None = NOT_SET
46
- operation_id: Filter | None = NOT_SET
47
51
  app: Any = NOT_SET
52
+ filter_set: FilterSet = field(default_factory=FilterSet)
48
53
  hooks: HookDispatcher = field(default_factory=lambda: HookDispatcher(scope=HookScope.SCHEMA))
49
54
  auth: AuthStorage = field(default_factory=AuthStorage)
50
55
  validate_schema: bool = True
51
- skip_deprecated_operations: bool = False
52
56
  data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET
53
57
  generation_config: GenerationConfig | NotSet = NOT_SET
58
+ output_config: OutputConfig | NotSet = NOT_SET
54
59
  code_sample_style: CodeSampleStyle = CodeSampleStyle.default()
55
60
  rate_limiter: Limiter | None = None
56
61
  sanitize_output: bool = True
57
62
 
63
+ def include(
64
+ self,
65
+ func: MatcherFunc | None = None,
66
+ *,
67
+ name: FilterValue | None = None,
68
+ name_regex: str | None = None,
69
+ method: FilterValue | None = None,
70
+ method_regex: str | None = None,
71
+ path: FilterValue | None = None,
72
+ path_regex: str | None = None,
73
+ tag: FilterValue | None = None,
74
+ tag_regex: RegexValue | None = None,
75
+ operation_id: FilterValue | None = None,
76
+ operation_id_regex: RegexValue | None = None,
77
+ ) -> LazySchema:
78
+ """Include only operations that match the given filters."""
79
+ filter_set = self.filter_set.clone()
80
+ filter_set.include(
81
+ func,
82
+ name=name,
83
+ name_regex=name_regex,
84
+ method=method,
85
+ method_regex=method_regex,
86
+ path=path,
87
+ path_regex=path_regex,
88
+ tag=tag,
89
+ tag_regex=tag_regex,
90
+ operation_id=operation_id,
91
+ operation_id_regex=operation_id_regex,
92
+ )
93
+ return self.__class__(
94
+ fixture_name=self.fixture_name,
95
+ base_url=self.base_url,
96
+ app=self.app,
97
+ hooks=self.hooks,
98
+ auth=self.auth,
99
+ validate_schema=self.validate_schema,
100
+ data_generation_methods=self.data_generation_methods,
101
+ generation_config=self.generation_config,
102
+ output_config=self.output_config,
103
+ code_sample_style=self.code_sample_style,
104
+ rate_limiter=self.rate_limiter,
105
+ sanitize_output=self.sanitize_output,
106
+ filter_set=filter_set,
107
+ )
108
+
109
+ def exclude(
110
+ self,
111
+ func: MatcherFunc | None = None,
112
+ *,
113
+ name: FilterValue | None = None,
114
+ name_regex: str | None = None,
115
+ method: FilterValue | None = None,
116
+ method_regex: str | None = None,
117
+ path: FilterValue | None = None,
118
+ path_regex: str | None = None,
119
+ tag: FilterValue | None = None,
120
+ tag_regex: RegexValue | None = None,
121
+ operation_id: FilterValue | None = None,
122
+ operation_id_regex: RegexValue | None = None,
123
+ deprecated: bool = False,
124
+ ) -> LazySchema:
125
+ """Exclude operations that match the given filters."""
126
+ filter_set = self.filter_set.clone()
127
+ if deprecated:
128
+ if func is None:
129
+ func = is_deprecated
130
+ else:
131
+ filter_set.exclude(is_deprecated)
132
+ filter_set.exclude(
133
+ func,
134
+ name=name,
135
+ name_regex=name_regex,
136
+ method=method,
137
+ method_regex=method_regex,
138
+ path=path,
139
+ path_regex=path_regex,
140
+ tag=tag,
141
+ tag_regex=tag_regex,
142
+ operation_id=operation_id,
143
+ operation_id_regex=operation_id_regex,
144
+ )
145
+ return self.__class__(
146
+ fixture_name=self.fixture_name,
147
+ base_url=self.base_url,
148
+ app=self.app,
149
+ hooks=self.hooks,
150
+ auth=self.auth,
151
+ validate_schema=self.validate_schema,
152
+ data_generation_methods=self.data_generation_methods,
153
+ generation_config=self.generation_config,
154
+ output_config=self.output_config,
155
+ code_sample_style=self.code_sample_style,
156
+ rate_limiter=self.rate_limiter,
157
+ sanitize_output=self.sanitize_output,
158
+ filter_set=filter_set,
159
+ )
160
+
58
161
  def hook(self, hook: str | Callable) -> Callable:
59
162
  return self.hooks.register(hook)
60
163
 
@@ -68,20 +171,19 @@ class LazySchema:
68
171
  skip_deprecated_operations: bool | NotSet = NOT_SET,
69
172
  data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET,
70
173
  generation_config: GenerationConfig | NotSet = NOT_SET,
174
+ output_config: OutputConfig | NotSet = NOT_SET,
71
175
  code_sample_style: str | NotSet = NOT_SET,
72
176
  ) -> Callable:
73
- if method is NOT_SET:
74
- method = self.method
75
- if endpoint is NOT_SET:
76
- endpoint = self.endpoint
77
- if tag is NOT_SET:
78
- tag = self.tag
79
- if operation_id is NOT_SET:
80
- operation_id = self.operation_id
177
+ for name in ("method", "endpoint", "tag", "operation_id", "skip_deprecated_operations"):
178
+ value = locals()[name]
179
+ if value is not NOT_SET:
180
+ warn_filtration_arguments(name)
81
181
  if data_generation_methods is NOT_SET:
82
182
  data_generation_methods = self.data_generation_methods
83
183
  if generation_config is NOT_SET:
84
184
  generation_config = self.generation_config
185
+ if output_config is NOT_SET:
186
+ output_config = self.output_config
85
187
  if isinstance(code_sample_style, str):
86
188
  _code_sample_style = CodeSampleStyle.from_str(code_sample_style)
87
189
  else:
@@ -121,10 +223,12 @@ class LazySchema:
121
223
  skip_deprecated_operations=skip_deprecated_operations,
122
224
  data_generation_methods=data_generation_methods,
123
225
  generation_config=generation_config,
226
+ output_config=output_config,
124
227
  code_sample_style=_code_sample_style,
125
228
  app=self.app,
126
229
  rate_limiter=self.rate_limiter,
127
230
  sanitize_output=self.sanitize_output,
231
+ filter_set=self.filter_set,
128
232
  )
129
233
  fixtures = get_fixtures(test, request, given_kwargs)
130
234
  # Changing the node id is required for better reporting - the method and path will appear there
@@ -207,7 +311,7 @@ def _copy_marks(source: Callable, target: Callable) -> None:
207
311
  target.pytestmark.extend(marks) # type: ignore
208
312
 
209
313
 
210
- def _get_capturemanager(request: FixtureRequest) -> Generator:
314
+ def _get_capturemanager(request: FixtureRequest) -> Generator | Type[nullcontext]:
211
315
  capturemanager = request.node.config.pluginmanager.get_plugin("capturemanager")
212
316
  if capturemanager is not None:
213
317
  return capturemanager.global_and_fixture_disabled
@@ -317,6 +421,7 @@ def get_schema(
317
421
  endpoint: Filter | None = None,
318
422
  tag: Filter | None = None,
319
423
  operation_id: Filter | None = None,
424
+ filter_set: FilterSet,
320
425
  app: Any = None,
321
426
  test_function: GenericTest,
322
427
  hooks: HookDispatcher,
@@ -325,6 +430,7 @@ def get_schema(
325
430
  skip_deprecated_operations: bool | NotSet = NOT_SET,
326
431
  data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET,
327
432
  generation_config: GenerationConfig | NotSet = NOT_SET,
433
+ output_config: OutputConfig | NotSet = NOT_SET,
328
434
  code_sample_style: CodeSampleStyle,
329
435
  rate_limiter: Limiter | None,
330
436
  sanitize_output: bool,
@@ -333,20 +439,27 @@ def get_schema(
333
439
  schema = request.getfixturevalue(name)
334
440
  if not isinstance(schema, BaseSchema):
335
441
  raise ValueError(f"The given schema must be an instance of BaseSchema, got: {type(schema)}")
336
- return schema.clone(
337
- base_url=base_url,
442
+
443
+ filter_set = filter_set_from_components(
444
+ include=True,
338
445
  method=method,
339
446
  endpoint=endpoint,
340
447
  tag=tag,
341
448
  operation_id=operation_id,
449
+ skip_deprecated_operations=skip_deprecated_operations,
450
+ parent=schema.filter_set.merge(filter_set),
451
+ )
452
+ return schema.clone(
453
+ base_url=base_url,
454
+ filter_set=filter_set,
342
455
  app=app,
343
456
  test_function=test_function,
344
457
  hooks=schema.hooks.merge(hooks),
345
458
  auth=auth,
346
459
  validate_schema=validate_schema,
347
- skip_deprecated_operations=skip_deprecated_operations,
348
460
  data_generation_methods=data_generation_methods,
349
461
  generation_config=generation_config,
462
+ output_config=output_config,
350
463
  code_sample_style=code_sample_style,
351
464
  rate_limiter=rate_limiter,
352
465
  sanitize_output=sanitize_output,
schemathesis/loaders.py CHANGED
@@ -1,15 +1,17 @@
1
1
  from __future__ import annotations
2
+
2
3
  import re
3
4
  import sys
4
5
  from functools import lru_cache
5
- from typing import Callable, TypeVar, TYPE_CHECKING, TextIO, Any, BinaryIO
6
+ from typing import TYPE_CHECKING, Any, BinaryIO, Callable, TextIO, TypeVar
6
7
 
7
8
  from .exceptions import SchemaError, SchemaErrorType, extract_requests_exception_details
8
9
 
9
10
  if TYPE_CHECKING:
10
- from .transports.responses import GenericResponse
11
11
  import yaml
12
12
 
13
+ from .transports.responses import GenericResponse
14
+
13
15
  R = TypeVar("R", bound="GenericResponse")
14
16
 
15
17
 
@@ -49,13 +51,13 @@ def _raise_for_status(response: GenericResponse) -> None:
49
51
  else:
50
52
  type_ = SchemaErrorType.HTTP_CLIENT_ERROR
51
53
  else:
52
- return None
54
+ return
53
55
  raise SchemaError(message=message, type=type_, url=response.request.url, response=response, extras=[])
54
56
 
55
57
 
56
58
  def load_app(path: str) -> Any:
57
59
  """Import an application from a string."""
58
- path, name = (re.split(r":(?![\\/])", path, maxsplit=1) + [""])[:2]
60
+ path, name = ([*re.split(":(?![\\\\/])", path, maxsplit=1), ""])[:2]
59
61
  __import__(path)
60
62
  # accessing the module from sys.modules returns a proper module, while `__import__`
61
63
  # may return a parent module (system dependent)
@@ -90,7 +92,7 @@ def get_yaml_loader() -> type[yaml.SafeLoader]:
90
92
  |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*
91
93
  |[-+]?\.(?:inf|Inf|INF)
92
94
  |\.(?:nan|NaN|NAN))$""",
93
- re.X,
95
+ re.VERBOSE,
94
96
  ),
95
97
  list("-+0123456789."),
96
98
  )