schemathesis 3.21.2__py3-none-any.whl → 3.22.1__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 (95) hide show
  1. schemathesis/__init__.py +1 -1
  2. schemathesis/_compat.py +2 -18
  3. schemathesis/_dependency_versions.py +1 -6
  4. schemathesis/_hypothesis.py +15 -12
  5. schemathesis/_lazy_import.py +3 -2
  6. schemathesis/_xml.py +12 -11
  7. schemathesis/auths.py +88 -81
  8. schemathesis/checks.py +4 -4
  9. schemathesis/cli/__init__.py +202 -171
  10. schemathesis/cli/callbacks.py +29 -32
  11. schemathesis/cli/cassettes.py +25 -25
  12. schemathesis/cli/context.py +18 -12
  13. schemathesis/cli/junitxml.py +2 -2
  14. schemathesis/cli/options.py +10 -11
  15. schemathesis/cli/output/default.py +64 -34
  16. schemathesis/code_samples.py +10 -10
  17. schemathesis/constants.py +1 -1
  18. schemathesis/contrib/unique_data.py +2 -2
  19. schemathesis/exceptions.py +55 -42
  20. schemathesis/extra/_aiohttp.py +2 -2
  21. schemathesis/extra/_flask.py +2 -2
  22. schemathesis/extra/_server.py +3 -2
  23. schemathesis/extra/pytest_plugin.py +10 -10
  24. schemathesis/failures.py +16 -16
  25. schemathesis/filters.py +40 -41
  26. schemathesis/fixups/__init__.py +4 -3
  27. schemathesis/fixups/fast_api.py +5 -4
  28. schemathesis/generation/__init__.py +16 -4
  29. schemathesis/hooks.py +25 -25
  30. schemathesis/internal/jsonschema.py +4 -3
  31. schemathesis/internal/transformation.py +3 -2
  32. schemathesis/lazy.py +39 -31
  33. schemathesis/loaders.py +8 -8
  34. schemathesis/models.py +128 -126
  35. schemathesis/parameters.py +6 -5
  36. schemathesis/runner/__init__.py +107 -81
  37. schemathesis/runner/events.py +37 -26
  38. schemathesis/runner/impl/core.py +86 -81
  39. schemathesis/runner/impl/solo.py +19 -15
  40. schemathesis/runner/impl/threadpool.py +40 -22
  41. schemathesis/runner/serialization.py +67 -40
  42. schemathesis/sanitization.py +18 -20
  43. schemathesis/schemas.py +83 -72
  44. schemathesis/serializers.py +39 -30
  45. schemathesis/service/ci.py +20 -21
  46. schemathesis/service/client.py +29 -9
  47. schemathesis/service/constants.py +1 -0
  48. schemathesis/service/events.py +2 -2
  49. schemathesis/service/hosts.py +8 -7
  50. schemathesis/service/metadata.py +5 -0
  51. schemathesis/service/models.py +22 -4
  52. schemathesis/service/report.py +15 -15
  53. schemathesis/service/serialization.py +23 -27
  54. schemathesis/service/usage.py +8 -7
  55. schemathesis/specs/graphql/loaders.py +31 -24
  56. schemathesis/specs/graphql/nodes.py +3 -2
  57. schemathesis/specs/graphql/scalars.py +26 -2
  58. schemathesis/specs/graphql/schemas.py +38 -34
  59. schemathesis/specs/openapi/_hypothesis.py +62 -44
  60. schemathesis/specs/openapi/checks.py +10 -10
  61. schemathesis/specs/openapi/converter.py +10 -9
  62. schemathesis/specs/openapi/definitions.py +2 -2
  63. schemathesis/specs/openapi/examples.py +22 -21
  64. schemathesis/specs/openapi/expressions/nodes.py +5 -4
  65. schemathesis/specs/openapi/expressions/parser.py +7 -6
  66. schemathesis/specs/openapi/filters.py +6 -6
  67. schemathesis/specs/openapi/formats.py +2 -2
  68. schemathesis/specs/openapi/links.py +19 -21
  69. schemathesis/specs/openapi/loaders.py +133 -78
  70. schemathesis/specs/openapi/negative/__init__.py +16 -11
  71. schemathesis/specs/openapi/negative/mutations.py +11 -10
  72. schemathesis/specs/openapi/parameters.py +20 -19
  73. schemathesis/specs/openapi/references.py +21 -20
  74. schemathesis/specs/openapi/schemas.py +97 -84
  75. schemathesis/specs/openapi/security.py +25 -24
  76. schemathesis/specs/openapi/serialization.py +20 -23
  77. schemathesis/specs/openapi/stateful/__init__.py +12 -11
  78. schemathesis/specs/openapi/stateful/links.py +7 -7
  79. schemathesis/specs/openapi/utils.py +4 -3
  80. schemathesis/specs/openapi/validation.py +3 -2
  81. schemathesis/stateful/__init__.py +15 -16
  82. schemathesis/stateful/state_machine.py +9 -9
  83. schemathesis/targets.py +3 -3
  84. schemathesis/throttling.py +2 -2
  85. schemathesis/transports/auth.py +2 -2
  86. schemathesis/transports/content_types.py +5 -0
  87. schemathesis/transports/headers.py +3 -2
  88. schemathesis/transports/responses.py +1 -1
  89. schemathesis/utils.py +7 -10
  90. {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/METADATA +12 -13
  91. schemathesis-3.22.1.dist-info/RECORD +130 -0
  92. schemathesis-3.21.2.dist-info/RECORD +0 -130
  93. {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/WHEEL +0 -0
  94. {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/entry_points.txt +0 -0
  95. {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,15 +1,16 @@
1
+ from __future__ import annotations
1
2
  from typing import overload, Dict, Union, Any, List, Callable
2
3
 
3
4
  JsonValue = Union[Dict[str, Any], List, str, float, int]
4
5
 
5
6
 
6
7
  @overload
7
- def traverse_schema(schema: Dict[str, Any], callback: Callable, *args: Any, **kwargs: Any) -> Dict[str, Any]:
8
+ def traverse_schema(schema: dict[str, Any], callback: Callable, *args: Any, **kwargs: Any) -> dict[str, Any]:
8
9
  pass
9
10
 
10
11
 
11
12
  @overload
12
- def traverse_schema(schema: List, callback: Callable, *args: Any, **kwargs: Any) -> List:
13
+ def traverse_schema(schema: list, callback: Callable, *args: Any, **kwargs: Any) -> list:
13
14
  pass
14
15
 
15
16
 
@@ -23,7 +24,7 @@ def traverse_schema(schema: float, callback: Callable, *args: Any, **kwargs: Any
23
24
  pass
24
25
 
25
26
 
26
- def traverse_schema(schema: JsonValue, callback: Callable[..., Dict[str, Any]], *args: Any, **kwargs: Any) -> JsonValue:
27
+ def traverse_schema(schema: JsonValue, callback: Callable[..., dict[str, Any]], *args: Any, **kwargs: Any) -> JsonValue:
27
28
  """Apply callback recursively to the given schema."""
28
29
  if isinstance(schema, dict):
29
30
  schema = callback(schema, *args, **kwargs)
@@ -1,7 +1,8 @@
1
- from typing import Dict, Any
1
+ from __future__ import annotations
2
+ from typing import Any
2
3
 
3
4
 
4
- def merge_recursively(a: Dict[str, Any], b: Dict[str, Any]) -> Dict[str, Any]:
5
+ def merge_recursively(a: dict[str, Any], b: dict[str, Any]) -> dict[str, Any]:
5
6
  """Merge two dictionaries recursively."""
6
7
  for key in b:
7
8
  if key in a:
schemathesis/lazy.py CHANGED
@@ -1,6 +1,7 @@
1
+ from __future__ import annotations
1
2
  from dataclasses import dataclass, field
2
3
  from inspect import signature
3
- from typing import Any, Callable, Dict, Generator, Optional, Type, Union
4
+ from typing import Any, Callable, Generator
4
5
 
5
6
  import pytest
6
7
  from _pytest.fixtures import FixtureRequest
@@ -15,7 +16,7 @@ from ._compat import MultipleFailures, get_interesting_origin
15
16
  from .auths import AuthStorage
16
17
  from .code_samples import CodeSampleStyle
17
18
  from .constants import FLAKY_FAILURE_MESSAGE, NOT_SET
18
- from .generation import DataGenerationMethodInput
19
+ from .generation import DataGenerationMethodInput, GenerationConfig
19
20
  from .exceptions import CheckFailed, OperationSchemaError, SkipTest, get_grouped_exception
20
21
  from .hooks import HookDispatcher, HookScope
21
22
  from .internal.result import Ok
@@ -37,34 +38,36 @@ from .utils import (
37
38
  @dataclass
38
39
  class LazySchema:
39
40
  fixture_name: str
40
- base_url: Union[Optional[str], NotSet] = NOT_SET
41
- method: Optional[Filter] = NOT_SET
42
- endpoint: Optional[Filter] = NOT_SET
43
- tag: Optional[Filter] = NOT_SET
44
- operation_id: Optional[Filter] = NOT_SET
41
+ base_url: str | None | NotSet = NOT_SET
42
+ method: Filter | None = NOT_SET
43
+ endpoint: Filter | None = NOT_SET
44
+ tag: Filter | None = NOT_SET
45
+ operation_id: Filter | None = NOT_SET
45
46
  app: Any = NOT_SET
46
47
  hooks: HookDispatcher = field(default_factory=lambda: HookDispatcher(scope=HookScope.SCHEMA))
47
48
  auth: AuthStorage = field(default_factory=AuthStorage)
48
49
  validate_schema: bool = True
49
50
  skip_deprecated_operations: bool = False
50
- data_generation_methods: Union[DataGenerationMethodInput, NotSet] = NOT_SET
51
+ data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET
52
+ generation_config: GenerationConfig | NotSet = NOT_SET
51
53
  code_sample_style: CodeSampleStyle = CodeSampleStyle.default()
52
- rate_limiter: Optional[Limiter] = None
54
+ rate_limiter: Limiter | None = None
53
55
  sanitize_output: bool = True
54
56
 
55
- def hook(self, hook: Union[str, Callable]) -> Callable:
57
+ def hook(self, hook: str | Callable) -> Callable:
56
58
  return self.hooks.register(hook)
57
59
 
58
60
  def parametrize(
59
61
  self,
60
- method: Optional[Filter] = NOT_SET,
61
- endpoint: Optional[Filter] = NOT_SET,
62
- tag: Optional[Filter] = NOT_SET,
63
- operation_id: Optional[Filter] = NOT_SET,
64
- validate_schema: Union[bool, NotSet] = NOT_SET,
65
- skip_deprecated_operations: Union[bool, NotSet] = NOT_SET,
66
- data_generation_methods: Union[DataGenerationMethodInput, NotSet] = NOT_SET,
67
- code_sample_style: Union[str, NotSet] = NOT_SET,
62
+ method: Filter | None = NOT_SET,
63
+ endpoint: Filter | None = NOT_SET,
64
+ tag: Filter | None = NOT_SET,
65
+ operation_id: Filter | None = NOT_SET,
66
+ validate_schema: bool | NotSet = NOT_SET,
67
+ skip_deprecated_operations: bool | NotSet = NOT_SET,
68
+ data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET,
69
+ generation_config: GenerationConfig | NotSet = NOT_SET,
70
+ code_sample_style: str | NotSet = NOT_SET,
68
71
  ) -> Callable:
69
72
  if method is NOT_SET:
70
73
  method = self.method
@@ -76,6 +79,8 @@ class LazySchema:
76
79
  operation_id = self.operation_id
77
80
  if data_generation_methods is NOT_SET:
78
81
  data_generation_methods = self.data_generation_methods
82
+ if generation_config is NOT_SET:
83
+ generation_config = self.generation_config
79
84
  if isinstance(code_sample_style, str):
80
85
  _code_sample_style = CodeSampleStyle.from_str(code_sample_style)
81
86
  else:
@@ -114,6 +119,7 @@ class LazySchema:
114
119
  validate_schema=validate_schema,
115
120
  skip_deprecated_operations=skip_deprecated_operations,
116
121
  data_generation_methods=data_generation_methods,
122
+ generation_config=generation_config,
117
123
  code_sample_style=_code_sample_style,
118
124
  app=self.app,
119
125
  rate_limiter=self.rate_limiter,
@@ -183,7 +189,7 @@ def _get_partial_node_name(node_id: str, **kwargs: Any) -> str:
183
189
 
184
190
  def run_subtest(
185
191
  operation: APIOperation,
186
- fixtures: Dict[str, Any],
192
+ fixtures: dict[str, Any],
187
193
  sub_test: Callable,
188
194
  subtests: SubTests,
189
195
  ) -> None:
@@ -208,7 +214,7 @@ def run_subtest(
208
214
  exceptions.append(exception)
209
215
  raise
210
216
 
211
- def get_exception_class() -> Type[CheckFailed]:
217
+ def get_exception_class() -> type[CheckFailed]:
212
218
  return get_grouped_exception("Lazy", *failed_checks.values())
213
219
 
214
220
  sub_test.hypothesis.inner_test = collecting_wrapper # type: ignore
@@ -264,20 +270,21 @@ def get_schema(
264
270
  *,
265
271
  request: FixtureRequest,
266
272
  name: str,
267
- base_url: Union[Optional[str], NotSet] = None,
268
- method: Optional[Filter] = None,
269
- endpoint: Optional[Filter] = None,
270
- tag: Optional[Filter] = None,
271
- operation_id: Optional[Filter] = None,
273
+ base_url: str | None | NotSet = None,
274
+ method: Filter | None = None,
275
+ endpoint: Filter | None = None,
276
+ tag: Filter | None = None,
277
+ operation_id: Filter | None = None,
272
278
  app: Any = None,
273
279
  test_function: GenericTest,
274
280
  hooks: HookDispatcher,
275
- auth: Union[AuthStorage, NotSet],
276
- validate_schema: Union[bool, NotSet] = NOT_SET,
277
- skip_deprecated_operations: Union[bool, NotSet] = NOT_SET,
278
- data_generation_methods: Union[DataGenerationMethodInput, NotSet] = NOT_SET,
281
+ auth: AuthStorage | NotSet,
282
+ validate_schema: bool | NotSet = NOT_SET,
283
+ skip_deprecated_operations: bool | NotSet = NOT_SET,
284
+ data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET,
285
+ generation_config: GenerationConfig | NotSet = NOT_SET,
279
286
  code_sample_style: CodeSampleStyle,
280
- rate_limiter: Optional[Limiter],
287
+ rate_limiter: Limiter | None,
281
288
  sanitize_output: bool,
282
289
  ) -> BaseSchema:
283
290
  """Loads a schema from the fixture."""
@@ -297,13 +304,14 @@ def get_schema(
297
304
  validate_schema=validate_schema,
298
305
  skip_deprecated_operations=skip_deprecated_operations,
299
306
  data_generation_methods=data_generation_methods,
307
+ generation_config=generation_config,
300
308
  code_sample_style=code_sample_style,
301
309
  rate_limiter=rate_limiter,
302
310
  sanitize_output=sanitize_output,
303
311
  )
304
312
 
305
313
 
306
- def get_fixtures(func: Callable, request: FixtureRequest, given_kwargs: Dict[str, Any]) -> Dict[str, Any]:
314
+ def get_fixtures(func: Callable, request: FixtureRequest, given_kwargs: dict[str, Any]) -> dict[str, Any]:
307
315
  """Load fixtures, needed for the test function."""
308
316
  sig = signature(func)
309
317
  return {
schemathesis/loaders.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
  import re
3
3
  import sys
4
4
  from functools import lru_cache
5
- from typing import Callable, TypeVar, cast, TYPE_CHECKING, TextIO, Any, Dict, Type, BinaryIO
5
+ from typing import Callable, TypeVar, TYPE_CHECKING, TextIO, Any, BinaryIO
6
6
 
7
7
  from .exceptions import SchemaError, SchemaErrorType, extract_requests_exception_details
8
8
 
@@ -19,7 +19,7 @@ def load_schema_from_url(loader: Callable[[], R]) -> R:
19
19
  try:
20
20
  response = loader()
21
21
  except requests.RequestException as exc:
22
- request = cast(requests.PreparedRequest, exc.request)
22
+ url = exc.request.url if exc.request is not None else None
23
23
  if isinstance(exc, requests.exceptions.SSLError):
24
24
  type_ = SchemaErrorType.CONNECTION_SSL
25
25
  elif isinstance(exc, requests.exceptions.ConnectionError):
@@ -27,12 +27,12 @@ def load_schema_from_url(loader: Callable[[], R]) -> R:
27
27
  else:
28
28
  type_ = SchemaErrorType.NETWORK_OTHER
29
29
  message, extras = extract_requests_exception_details(exc)
30
- raise SchemaError(message=message, type=type_, url=request.url, response=exc.response, extras=extras) from exc
30
+ raise SchemaError(message=message, type=type_, url=url, response=exc.response, extras=extras) from exc
31
31
  _raise_for_status(response)
32
32
  return response
33
33
 
34
34
 
35
- def _raise_for_status(response: "GenericResponse") -> None:
35
+ def _raise_for_status(response: GenericResponse) -> None:
36
36
  from .transports.responses import get_reason
37
37
 
38
38
  status_code = response.status_code
@@ -63,8 +63,8 @@ def load_app(path: str) -> Any:
63
63
  return getattr(module, name)
64
64
 
65
65
 
66
- @lru_cache()
67
- def get_yaml_loader() -> Type[yaml.SafeLoader]:
66
+ @lru_cache
67
+ def get_yaml_loader() -> type[yaml.SafeLoader]:
68
68
  """Create a YAML loader, that doesn't parse specific tokens into Python objects."""
69
69
  import yaml
70
70
 
@@ -73,7 +73,7 @@ def get_yaml_loader() -> Type[yaml.SafeLoader]:
73
73
  except ImportError:
74
74
  from yaml import SafeLoader # type: ignore
75
75
 
76
- cls: Type[yaml.SafeLoader] = type("YAMLLoader", (SafeLoader,), {})
76
+ cls: type[yaml.SafeLoader] = type("YAMLLoader", (SafeLoader,), {})
77
77
  cls.yaml_implicit_resolvers = {
78
78
  key: [(tag, regexp) for tag, regexp in mapping if tag != "tag:yaml.org,2002:timestamp"]
79
79
  for key, mapping in cls.yaml_implicit_resolvers.copy().items()
@@ -95,7 +95,7 @@ def get_yaml_loader() -> Type[yaml.SafeLoader]:
95
95
  list("-+0123456789."),
96
96
  )
97
97
 
98
- def construct_mapping(self: SafeLoader, node: yaml.Node, deep: bool = False) -> Dict[str, Any]:
98
+ def construct_mapping(self: SafeLoader, node: yaml.Node, deep: bool = False) -> dict[str, Any]:
99
99
  if isinstance(node, yaml.MappingNode):
100
100
  self.flatten_mapping(node) # type: ignore
101
101
  mapping = {}