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
schemathesis/__init__.py CHANGED
@@ -3,7 +3,7 @@ from typing import Any
3
3
 
4
4
  from . import auths, checks, experimental, contrib, fixups, graphql, hooks, runner, serializers, targets # noqa: E402
5
5
  from ._lazy_import import lazy_import
6
- from .generation import DataGenerationMethod # noqa: E402
6
+ from .generation import DataGenerationMethod, GenerationConfig # noqa: E402
7
7
  from .constants import SCHEMATHESIS_VERSION # noqa: E402
8
8
  from .models import Case # noqa: E402
9
9
  from .specs import openapi # noqa: E402
schemathesis/_compat.py CHANGED
@@ -1,11 +1,6 @@
1
1
  from typing import Any, Type, Callable
2
2
  from ._lazy_import import lazy_import
3
3
 
4
- try:
5
- from importlib import metadata
6
- except ImportError:
7
- import importlib_metadata as metadata # type: ignore
8
-
9
4
 
10
5
  __all__ = [ # noqa: F822
11
6
  "JSONMixin",
@@ -13,7 +8,6 @@ __all__ = [ # noqa: F822
13
8
  "MultipleFailures",
14
9
  "get_signature",
15
10
  "get_interesting_origin",
16
- "metadata",
17
11
  "_install_hypothesis_jsonschema_compatibility_shim",
18
12
  ]
19
13
 
@@ -52,26 +46,16 @@ def _load_get_interesting_origin() -> Callable:
52
46
 
53
47
 
54
48
  def _load_multiple_failures() -> Type:
55
- from ._dependency_versions import IS_HYPOTHESIS_ABOVE_6_54
56
-
57
49
  try:
58
50
  return BaseExceptionGroup # type: ignore
59
51
  except NameError:
60
- if IS_HYPOTHESIS_ABOVE_6_54:
61
- from exceptiongroup import BaseExceptionGroup as MultipleFailures # type: ignore
62
- else:
63
- from hypothesis.errors import MultipleFailures # type: ignore
52
+ from exceptiongroup import BaseExceptionGroup as MultipleFailures # type: ignore
64
53
 
65
54
  return MultipleFailures
66
55
 
67
56
 
68
57
  def _load_get_signature() -> Callable:
69
- from ._dependency_versions import IS_HYPOTHESIS_ABOVE_6_49
70
-
71
- if IS_HYPOTHESIS_ABOVE_6_49:
72
- from hypothesis.internal.reflection import get_signature
73
- else:
74
- from inspect import getfullargspec as get_signature
58
+ from hypothesis.internal.reflection import get_signature
75
59
 
76
60
  return get_signature
77
61
 
@@ -1,18 +1,13 @@
1
1
  """Compatibility flags based on installed dependency versions."""
2
2
  from packaging import version
3
3
 
4
- from ._compat import metadata
4
+ from importlib import metadata
5
5
 
6
6
 
7
7
  WERKZEUG_VERSION = version.parse(metadata.version("werkzeug"))
8
8
  IS_WERKZEUG_ABOVE_3 = WERKZEUG_VERSION >= version.parse("3.0")
9
9
  IS_WERKZEUG_BELOW_2_1 = WERKZEUG_VERSION < version.parse("2.1.0")
10
10
 
11
- HYPOTHESIS_VERSION = version.parse(metadata.version("hypothesis"))
12
- IS_HYPOTHESIS_ABOVE_6_49 = HYPOTHESIS_VERSION >= version.parse("6.49.0")
13
- IS_HYPOTHESIS_ABOVE_6_54 = HYPOTHESIS_VERSION >= version.parse("6.54.0")
14
- IS_HYPOTHESIS_ABOVE_6_68_1 = HYPOTHESIS_VERSION >= version.parse("6.68.1")
15
-
16
11
  PYTEST_VERSION = version.parse(metadata.version("pytest"))
17
12
  IS_PYTEST_ABOVE_54 = PYTEST_VERSION >= version.parse("5.4.0")
18
13
  IS_PYTEST_ABOVE_7 = PYTEST_VERSION >= version.parse("7.0.0")
@@ -1,7 +1,8 @@
1
1
  """High-level API for creating Hypothesis tests."""
2
+ from __future__ import annotations
2
3
  import asyncio
3
4
  import warnings
4
- from typing import Any, Callable, Dict, List, Optional, Tuple
5
+ from typing import Any, Callable
5
6
 
6
7
  import hypothesis
7
8
  from hypothesis import Phase
@@ -11,7 +12,7 @@ from hypothesis.internal.reflection import proxies
11
12
  from hypothesis_jsonschema._canonicalise import HypothesisRefResolutionError
12
13
 
13
14
  from .auths import get_auth_storage_from_test
14
- from .generation import DataGenerationMethod
15
+ from .generation import DataGenerationMethod, GenerationConfig
15
16
  from .constants import DEFAULT_DEADLINE
16
17
  from .exceptions import OperationSchemaError
17
18
  from .hooks import GLOBAL_HOOK_DISPATCHER, HookContext, HookDispatcher
@@ -23,12 +24,13 @@ def create_test(
23
24
  *,
24
25
  operation: APIOperation,
25
26
  test: Callable,
26
- settings: Optional[hypothesis.settings] = None,
27
- seed: Optional[int] = None,
28
- data_generation_methods: List[DataGenerationMethod],
29
- as_strategy_kwargs: Optional[Dict[str, Any]] = None,
30
- _given_args: Tuple[GivenInput, ...] = (),
31
- _given_kwargs: Optional[Dict[str, GivenInput]] = None,
27
+ settings: hypothesis.settings | None = None,
28
+ seed: int | None = None,
29
+ data_generation_methods: list[DataGenerationMethod],
30
+ generation_config: GenerationConfig | None = None,
31
+ as_strategy_kwargs: dict[str, Any] | None = None,
32
+ _given_args: tuple[GivenInput, ...] = (),
33
+ _given_kwargs: dict[str, GivenInput] | None = None,
32
34
  ) -> Callable:
33
35
  """Create a Hypothesis test."""
34
36
  hook_dispatcher = getattr(test, "_schemathesis_hooks", None)
@@ -40,6 +42,7 @@ def create_test(
40
42
  hooks=hook_dispatcher,
41
43
  auth_storage=auth_storage,
42
44
  data_generation_method=data_generation_method,
45
+ generation_config=generation_config,
43
46
  **(as_strategy_kwargs or {}),
44
47
  )
45
48
  )
@@ -92,7 +95,7 @@ def remove_explain_phase(settings: hypothesis.settings) -> hypothesis.settings:
92
95
  return settings
93
96
 
94
97
 
95
- def _get_hypothesis_settings(test: Callable) -> Optional[hypothesis.settings]:
98
+ def _get_hypothesis_settings(test: Callable) -> hypothesis.settings | None:
96
99
  return getattr(test, "_hypothesis_internal_use_settings", None)
97
100
 
98
101
 
@@ -106,10 +109,10 @@ def make_async_test(test: Callable) -> Callable:
106
109
  return async_run
107
110
 
108
111
 
109
- def add_examples(test: Callable, operation: APIOperation, hook_dispatcher: Optional[HookDispatcher] = None) -> Callable:
112
+ def add_examples(test: Callable, operation: APIOperation, hook_dispatcher: HookDispatcher | None = None) -> Callable:
110
113
  """Add examples to the Hypothesis test, if they are specified in the schema."""
111
114
  try:
112
- examples: List[Case] = [get_single_example(strategy) for strategy in operation.get_strategies_from_examples()]
115
+ examples: list[Case] = [get_single_example(strategy) for strategy in operation.get_strategies_from_examples()]
113
116
  except (OperationSchemaError, HypothesisRefResolutionError, Unsatisfiable):
114
117
  # Invalid schema:
115
118
  # In this case, the user didn't pass `--validate-schema=false` and see an error in the output anyway,
@@ -143,6 +146,6 @@ def get_single_example(strategy: st.SearchStrategy[Case]) -> Case:
143
146
  def example_generating_inner_function(ex: Case) -> None:
144
147
  examples.append(ex)
145
148
 
146
- examples: List[Case] = []
149
+ examples: list[Case] = []
147
150
  example_generating_inner_function()
148
151
  return examples[0]
@@ -1,7 +1,8 @@
1
- from typing import Dict, Any, Callable
1
+ from __future__ import annotations
2
+ from typing import Any, Callable
2
3
 
3
4
 
4
- def lazy_import(module: str, name: str, imports: Dict[str, Callable[[], Any]], _globals: Dict[str, Any]) -> Any:
5
+ def lazy_import(module: str, name: str, imports: dict[str, Callable[[], Any]], _globals: dict[str, Any]) -> Any:
5
6
  value = _globals.get(name)
6
7
  if value is not None:
7
8
  return value
schemathesis/_xml.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """XML serialization."""
2
+ from __future__ import annotations
2
3
  from io import StringIO
3
4
  from typing import Any, Dict, List, Union
4
5
  from xml.etree import ElementTree
@@ -12,7 +13,7 @@ DEFAULT_TAG_NAME = "data"
12
13
  NAMESPACE_URL = "http://example.com/schema"
13
14
 
14
15
 
15
- def _to_xml(value: Any, raw_schema: Dict[str, Any], resolved_schema: Dict[str, Any]) -> Dict[str, Any]:
16
+ def _to_xml(value: Any, raw_schema: dict[str, Any], resolved_schema: dict[str, Any]) -> dict[str, Any]:
16
17
  """Serialize a generated Python object as an XML string.
17
18
 
18
19
  Schemas may contain additional information for fine-tuned XML serialization.
@@ -26,7 +27,7 @@ def _to_xml(value: Any, raw_schema: Dict[str, Any], resolved_schema: Dict[str, A
26
27
  tag = _get_xml_tag(raw_schema, resolved_schema)
27
28
  buffer = StringIO()
28
29
  # Collect all namespaces to ensure that all child nodes with prefixes have proper namespaces in their parent nodes
29
- namespace_stack: List[str] = []
30
+ namespace_stack: list[str] = []
30
31
  _write_xml(buffer, value, tag, resolved_schema, namespace_stack)
31
32
  data = buffer.getvalue()
32
33
  if not is_valid_xml(data):
@@ -47,7 +48,7 @@ def is_valid_xml(data: str) -> bool:
47
48
  return False
48
49
 
49
50
 
50
- def _get_xml_tag(raw_schema: Dict[str, Any], resolved_schema: Dict[str, Any]) -> str:
51
+ def _get_xml_tag(raw_schema: dict[str, Any], resolved_schema: dict[str, Any]) -> str:
51
52
  # On the top level we need to detect the proper XML tag, in other cases it is known from object properties
52
53
  if resolved_schema.get("xml", {}).get("name"):
53
54
  return resolved_schema["xml"]["name"]
@@ -60,7 +61,7 @@ def _get_xml_tag(raw_schema: Dict[str, Any], resolved_schema: Dict[str, Any]) ->
60
61
  return DEFAULT_TAG_NAME
61
62
 
62
63
 
63
- def _write_xml(buffer: StringIO, value: JSON, tag: str, schema: Dict[str, Any], namespace_stack: List[str]) -> None:
64
+ def _write_xml(buffer: StringIO, value: JSON, tag: str, schema: dict[str, Any], namespace_stack: list[str]) -> None:
64
65
  if isinstance(value, dict):
65
66
  _write_object(buffer, value, tag, schema, namespace_stack)
66
67
  elif isinstance(value, list):
@@ -69,7 +70,7 @@ def _write_xml(buffer: StringIO, value: JSON, tag: str, schema: Dict[str, Any],
69
70
  _write_primitive(buffer, value, tag, schema, namespace_stack)
70
71
 
71
72
 
72
- def _validate_prefix(options: Dict[str, Any], namespace_stack: List[str]) -> None:
73
+ def _validate_prefix(options: dict[str, Any], namespace_stack: list[str]) -> None:
73
74
  try:
74
75
  prefix = options["prefix"]
75
76
  if prefix not in namespace_stack:
@@ -78,17 +79,17 @@ def _validate_prefix(options: Dict[str, Any], namespace_stack: List[str]) -> Non
78
79
  pass
79
80
 
80
81
 
81
- def push_namespace_if_any(namespace_stack: List[str], options: Dict[str, Any]) -> None:
82
+ def push_namespace_if_any(namespace_stack: list[str], options: dict[str, Any]) -> None:
82
83
  if "namespace" in options and "prefix" in options:
83
84
  namespace_stack.append(options["prefix"])
84
85
 
85
86
 
86
- def pop_namespace_if_any(namespace_stack: List[str], options: Dict[str, Any]) -> None:
87
+ def pop_namespace_if_any(namespace_stack: list[str], options: dict[str, Any]) -> None:
87
88
  if "namespace" in options and "prefix" in options:
88
89
  namespace_stack.pop()
89
90
 
90
91
 
91
- def _write_object(buffer: StringIO, obj: Dict[str, JSON], tag: str, schema: Dict[str, Any], stack: List[str]) -> None:
92
+ def _write_object(buffer: StringIO, obj: dict[str, JSON], tag: str, schema: dict[str, Any], stack: list[str]) -> None:
92
93
  options = schema.get("xml", {})
93
94
  push_namespace_if_any(stack, options)
94
95
  if "prefix" in options:
@@ -122,7 +123,7 @@ def _write_object(buffer: StringIO, obj: Dict[str, JSON], tag: str, schema: Dict
122
123
  pop_namespace_if_any(stack, options)
123
124
 
124
125
 
125
- def _write_array(buffer: StringIO, obj: List[JSON], tag: str, schema: Dict[str, Any], stack: List[str]) -> None:
126
+ def _write_array(buffer: StringIO, obj: list[JSON], tag: str, schema: dict[str, Any], stack: list[str]) -> None:
126
127
  options = schema.get("xml", {})
127
128
  push_namespace_if_any(stack, options)
128
129
  if options.get("prefix"):
@@ -153,7 +154,7 @@ def _write_array(buffer: StringIO, obj: List[JSON], tag: str, schema: Dict[str,
153
154
 
154
155
 
155
156
  def _write_primitive(
156
- buffer: StringIO, obj: Primitive, tag: str, schema: Dict[str, Any], namespace_stack: List[str]
157
+ buffer: StringIO, obj: Primitive, tag: str, schema: dict[str, Any], namespace_stack: list[str]
157
158
  ) -> None:
158
159
  xml_options = schema.get("xml", {})
159
160
  # There is no need for modifying the namespace stack, as we know that this function is terminal - it do not recurse
@@ -165,7 +166,7 @@ def _write_primitive(
165
166
  buffer.write(f">{obj}</{tag}>")
166
167
 
167
168
 
168
- def _write_namespace(buffer: StringIO, options: Dict[str, Any]) -> None:
169
+ def _write_namespace(buffer: StringIO, options: dict[str, Any]) -> None:
169
170
  buffer.write(" xmlns")
170
171
  if "prefix" in options:
171
172
  buffer.write(f":{options['prefix']}")
schemathesis/auths.py CHANGED
@@ -5,9 +5,16 @@ import threading
5
5
  import time
6
6
  import warnings
7
7
  from dataclasses import dataclass, field
8
- from typing import TYPE_CHECKING, Any, Callable, Generic, List, Optional, Type, TypeVar, Union, overload
9
-
10
- from typing_extensions import Protocol, runtime_checkable
8
+ from typing import (
9
+ TYPE_CHECKING,
10
+ Any,
11
+ Callable,
12
+ Generic,
13
+ TypeVar,
14
+ overload,
15
+ runtime_checkable,
16
+ Protocol,
17
+ )
11
18
 
12
19
  from .exceptions import UsageError
13
20
  from .filters import FilterSet, FilterValue, MatcherFunc, attach_filter_chain
@@ -30,15 +37,15 @@ class AuthContext:
30
37
  :ivar app: Optional Python application if the WSGI / ASGI integration is used.
31
38
  """
32
39
 
33
- operation: "APIOperation"
34
- app: Optional[Any]
40
+ operation: APIOperation
41
+ app: Any | None
35
42
 
36
43
 
37
44
  @runtime_checkable
38
45
  class AuthProvider(Generic[Auth], Protocol):
39
46
  """Get authentication data for an API and set it on the generated test cases."""
40
47
 
41
- def get(self, case: "Case", context: AuthContext) -> Optional[Auth]:
48
+ def get(self, case: Case, context: AuthContext) -> Auth | None:
42
49
  """Get the authentication data.
43
50
 
44
51
  :param Case case: Generated test case.
@@ -46,7 +53,7 @@ class AuthProvider(Generic[Auth], Protocol):
46
53
  :return: Any authentication data you find useful for your use case. For example, it could be an access token.
47
54
  """
48
55
 
49
- def set(self, case: "Case", data: Auth, context: AuthContext) -> None:
56
+ def set(self, case: Case, data: Auth, context: AuthContext) -> None:
50
57
  """Set authentication data on a generated test case.
51
58
 
52
59
  :param Optional[Auth] data: Authentication data you got from the ``get`` method.
@@ -69,10 +76,10 @@ class RequestsAuth(Generic[Auth]):
69
76
 
70
77
  auth: requests.auth.AuthBase
71
78
 
72
- def get(self, _: "Case", __: AuthContext) -> Optional[Auth]:
79
+ def get(self, _: Case, __: AuthContext) -> Auth | None:
73
80
  return self.auth # type: ignore[return-value]
74
81
 
75
- def set(self, case: "Case", _: Auth, __: AuthContext) -> None:
82
+ def set(self, case: Case, _: Auth, __: AuthContext) -> None:
76
83
  case._auth = self.auth
77
84
 
78
85
 
@@ -82,12 +89,12 @@ class CachingAuthProvider(Generic[Auth]):
82
89
 
83
90
  provider: AuthProvider
84
91
  refresh_interval: int = DEFAULT_REFRESH_INTERVAL
85
- cache_entry: Optional[CacheEntry[Auth]] = None
92
+ cache_entry: CacheEntry[Auth] | None = None
86
93
  # The timer exists here to simplify testing
87
94
  timer: Callable[[], float] = time.monotonic
88
95
  _refresh_lock: threading.Lock = field(default_factory=threading.Lock)
89
96
 
90
- def get(self, case: "Case", context: AuthContext) -> Optional[Auth]:
97
+ def get(self, case: Case, context: AuthContext) -> Auth | None:
91
98
  """Get cached auth value."""
92
99
  if self.cache_entry is None or self.timer() >= self.cache_entry.expires:
93
100
  with self._refresh_lock:
@@ -100,7 +107,7 @@ class CachingAuthProvider(Generic[Auth]):
100
107
  return data
101
108
  return self.cache_entry.data
102
109
 
103
- def set(self, case: "Case", data: Auth, context: AuthContext) -> None:
110
+ def set(self, case: Case, data: Auth, context: AuthContext) -> None:
104
111
  """Set auth data on the `Case` instance.
105
112
 
106
113
  This implementation delegates this to the actual provider.
@@ -111,33 +118,33 @@ class CachingAuthProvider(Generic[Auth]):
111
118
  class FilterableRegisterAuth(Protocol):
112
119
  """Protocol that adds filters to the return value of `register`."""
113
120
 
114
- def __call__(self, provider_class: Type[AuthProvider]) -> Type[AuthProvider]:
121
+ def __call__(self, provider_class: type[AuthProvider]) -> type[AuthProvider]:
115
122
  pass
116
123
 
117
124
  def apply_to(
118
125
  self,
119
- func: Optional[MatcherFunc] = None,
126
+ func: MatcherFunc | None = None,
120
127
  *,
121
- name: Optional[FilterValue] = None,
122
- name_regex: Optional[str] = None,
123
- method: Optional[FilterValue] = None,
124
- method_regex: Optional[str] = None,
125
- path: Optional[FilterValue] = None,
126
- path_regex: Optional[str] = None,
127
- ) -> "FilterableRegisterAuth":
128
+ name: FilterValue | None = None,
129
+ name_regex: str | None = None,
130
+ method: FilterValue | None = None,
131
+ method_regex: str | None = None,
132
+ path: FilterValue | None = None,
133
+ path_regex: str | None = None,
134
+ ) -> FilterableRegisterAuth:
128
135
  pass
129
136
 
130
137
  def skip_for(
131
138
  self,
132
- func: Optional[MatcherFunc] = None,
139
+ func: MatcherFunc | None = None,
133
140
  *,
134
- name: Optional[FilterValue] = None,
135
- name_regex: Optional[str] = None,
136
- method: Optional[FilterValue] = None,
137
- method_regex: Optional[str] = None,
138
- path: Optional[FilterValue] = None,
139
- path_regex: Optional[str] = None,
140
- ) -> "FilterableRegisterAuth":
141
+ name: FilterValue | None = None,
142
+ name_regex: str | None = None,
143
+ method: FilterValue | None = None,
144
+ method_regex: str | None = None,
145
+ path: FilterValue | None = None,
146
+ path_regex: str | None = None,
147
+ ) -> FilterableRegisterAuth:
141
148
  pass
142
149
 
143
150
 
@@ -149,28 +156,28 @@ class FilterableApplyAuth(Protocol):
149
156
 
150
157
  def apply_to(
151
158
  self,
152
- func: Optional[MatcherFunc] = None,
159
+ func: MatcherFunc | None = None,
153
160
  *,
154
- name: Optional[FilterValue] = None,
155
- name_regex: Optional[str] = None,
156
- method: Optional[FilterValue] = None,
157
- method_regex: Optional[str] = None,
158
- path: Optional[FilterValue] = None,
159
- path_regex: Optional[str] = None,
160
- ) -> "FilterableApplyAuth":
161
+ name: FilterValue | None = None,
162
+ name_regex: str | None = None,
163
+ method: FilterValue | None = None,
164
+ method_regex: str | None = None,
165
+ path: FilterValue | None = None,
166
+ path_regex: str | None = None,
167
+ ) -> FilterableApplyAuth:
161
168
  pass
162
169
 
163
170
  def skip_for(
164
171
  self,
165
- func: Optional[MatcherFunc] = None,
172
+ func: MatcherFunc | None = None,
166
173
  *,
167
- name: Optional[FilterValue] = None,
168
- name_regex: Optional[str] = None,
169
- method: Optional[FilterValue] = None,
170
- method_regex: Optional[str] = None,
171
- path: Optional[FilterValue] = None,
172
- path_regex: Optional[str] = None,
173
- ) -> "FilterableApplyAuth":
174
+ name: FilterValue | None = None,
175
+ name_regex: str | None = None,
176
+ method: FilterValue | None = None,
177
+ method_regex: str | None = None,
178
+ path: FilterValue | None = None,
179
+ path_regex: str | None = None,
180
+ ) -> FilterableApplyAuth:
174
181
  pass
175
182
 
176
183
 
@@ -179,28 +186,28 @@ class FilterableRequestsAuth(Protocol):
179
186
 
180
187
  def apply_to(
181
188
  self,
182
- func: Optional[MatcherFunc] = None,
189
+ func: MatcherFunc | None = None,
183
190
  *,
184
- name: Optional[FilterValue] = None,
185
- name_regex: Optional[str] = None,
186
- method: Optional[FilterValue] = None,
187
- method_regex: Optional[str] = None,
188
- path: Optional[FilterValue] = None,
189
- path_regex: Optional[str] = None,
190
- ) -> "FilterableRequestsAuth":
191
+ name: FilterValue | None = None,
192
+ name_regex: str | None = None,
193
+ method: FilterValue | None = None,
194
+ method_regex: str | None = None,
195
+ path: FilterValue | None = None,
196
+ path_regex: str | None = None,
197
+ ) -> FilterableRequestsAuth:
191
198
  pass
192
199
 
193
200
  def skip_for(
194
201
  self,
195
- func: Optional[MatcherFunc] = None,
202
+ func: MatcherFunc | None = None,
196
203
  *,
197
- name: Optional[FilterValue] = None,
198
- name_regex: Optional[str] = None,
199
- method: Optional[FilterValue] = None,
200
- method_regex: Optional[str] = None,
201
- path: Optional[FilterValue] = None,
202
- path_regex: Optional[str] = None,
203
- ) -> "FilterableRequestsAuth":
204
+ name: FilterValue | None = None,
205
+ name_regex: str | None = None,
206
+ method: FilterValue | None = None,
207
+ method_regex: str | None = None,
208
+ path: FilterValue | None = None,
209
+ path_regex: str | None = None,
210
+ ) -> FilterableRequestsAuth:
204
211
  pass
205
212
 
206
213
 
@@ -211,12 +218,12 @@ class SelectiveAuthProvider(Generic[Auth]):
211
218
  provider: AuthProvider
212
219
  filter_set: FilterSet
213
220
 
214
- def get(self, case: "Case", context: AuthContext) -> Optional[Auth]:
221
+ def get(self, case: Case, context: AuthContext) -> Auth | None:
215
222
  if self.filter_set.match(context):
216
223
  return _provider_get(self.provider, case, context)
217
224
  return None
218
225
 
219
- def set(self, case: "Case", data: Auth, context: AuthContext) -> None:
226
+ def set(self, case: Case, data: Auth, context: AuthContext) -> None:
220
227
  self.provider.set(case, data, context)
221
228
 
222
229
 
@@ -224,7 +231,7 @@ class SelectiveAuthProvider(Generic[Auth]):
224
231
  class AuthStorage(Generic[Auth]):
225
232
  """Store and manage API authentication."""
226
233
 
227
- providers: List[AuthProvider] = field(default_factory=list)
234
+ providers: list[AuthProvider] = field(default_factory=list)
228
235
 
229
236
  @property
230
237
  def is_defined(self) -> bool:
@@ -235,25 +242,25 @@ class AuthStorage(Generic[Auth]):
235
242
  def __call__(
236
243
  self,
237
244
  *,
238
- refresh_interval: Optional[int] = DEFAULT_REFRESH_INTERVAL,
245
+ refresh_interval: int | None = DEFAULT_REFRESH_INTERVAL,
239
246
  ) -> FilterableRegisterAuth:
240
247
  pass
241
248
 
242
249
  @overload
243
250
  def __call__(
244
251
  self,
245
- provider_class: Type[AuthProvider],
252
+ provider_class: type[AuthProvider],
246
253
  *,
247
- refresh_interval: Optional[int] = DEFAULT_REFRESH_INTERVAL,
254
+ refresh_interval: int | None = DEFAULT_REFRESH_INTERVAL,
248
255
  ) -> FilterableApplyAuth:
249
256
  pass
250
257
 
251
258
  def __call__(
252
259
  self,
253
- provider_class: Optional[Type[AuthProvider]] = None,
260
+ provider_class: type[AuthProvider] | None = None,
254
261
  *,
255
- refresh_interval: Optional[int] = DEFAULT_REFRESH_INTERVAL,
256
- ) -> Union[FilterableRegisterAuth, FilterableApplyAuth]:
262
+ refresh_interval: int | None = DEFAULT_REFRESH_INTERVAL,
263
+ ) -> FilterableRegisterAuth | FilterableApplyAuth:
257
264
  if provider_class is not None:
258
265
  return self.apply(provider_class, refresh_interval=refresh_interval)
259
266
  return self.register(refresh_interval=refresh_interval)
@@ -274,8 +281,8 @@ class AuthStorage(Generic[Auth]):
274
281
  def _set_provider(
275
282
  self,
276
283
  *,
277
- provider_class: Type[AuthProvider],
278
- refresh_interval: Optional[int] = DEFAULT_REFRESH_INTERVAL,
284
+ provider_class: type[AuthProvider],
285
+ refresh_interval: int | None = DEFAULT_REFRESH_INTERVAL,
279
286
  filter_set: FilterSet,
280
287
  ) -> None:
281
288
  if not issubclass(provider_class, AuthProvider):
@@ -294,7 +301,7 @@ class AuthStorage(Generic[Auth]):
294
301
  provider = SelectiveAuthProvider(provider, filter_set)
295
302
  self.providers.append(provider)
296
303
 
297
- def register(self, *, refresh_interval: Optional[int] = DEFAULT_REFRESH_INTERVAL) -> FilterableRegisterAuth:
304
+ def register(self, *, refresh_interval: int | None = DEFAULT_REFRESH_INTERVAL) -> FilterableRegisterAuth:
298
305
  """Register a new auth provider.
299
306
 
300
307
  .. code-block:: python
@@ -315,7 +322,7 @@ class AuthStorage(Generic[Auth]):
315
322
  """
316
323
  filter_set = FilterSet()
317
324
 
318
- def wrapper(provider_class: Type[AuthProvider]) -> Type[AuthProvider]:
325
+ def wrapper(provider_class: type[AuthProvider]) -> type[AuthProvider]:
319
326
  self._set_provider(provider_class=provider_class, refresh_interval=refresh_interval, filter_set=filter_set)
320
327
  return provider_class
321
328
 
@@ -332,7 +339,7 @@ class AuthStorage(Generic[Auth]):
332
339
  self.providers = []
333
340
 
334
341
  def apply(
335
- self, provider_class: Type[AuthProvider], *, refresh_interval: Optional[int] = DEFAULT_REFRESH_INTERVAL
342
+ self, provider_class: type[AuthProvider], *, refresh_interval: int | None = DEFAULT_REFRESH_INTERVAL
336
343
  ) -> FilterableApplyAuth:
337
344
  """Register auth provider only on one test function.
338
345
 
@@ -366,7 +373,7 @@ class AuthStorage(Generic[Auth]):
366
373
  return wrapper # type: ignore[return-value]
367
374
 
368
375
  @classmethod
369
- def add_auth_storage(cls, test: GenericTest) -> "AuthStorage":
376
+ def add_auth_storage(cls, test: GenericTest) -> AuthStorage:
370
377
  """Attach a new auth storage instance to the test if it is not already present."""
371
378
  if not hasattr(test, AUTH_STORAGE_ATTRIBUTE_NAME):
372
379
  setattr(test, AUTH_STORAGE_ATTRIBUTE_NAME, cls())
@@ -374,18 +381,18 @@ class AuthStorage(Generic[Auth]):
374
381
  raise UsageError(f"`{test.__name__}` has already been decorated with `apply`.")
375
382
  return getattr(test, AUTH_STORAGE_ATTRIBUTE_NAME)
376
383
 
377
- def set(self, case: "Case", context: AuthContext) -> None:
384
+ def set(self, case: Case, context: AuthContext) -> None:
378
385
  """Set authentication data on a generated test case."""
379
386
  if not self.is_defined:
380
387
  raise UsageError("No auth provider is defined.")
381
388
  for provider in self.providers:
382
- data: Optional[Auth] = _provider_get(provider, case, context)
389
+ data: Auth | None = _provider_get(provider, case, context)
383
390
  if data is not None:
384
391
  provider.set(case, data, context)
385
392
  break
386
393
 
387
394
 
388
- def _provider_get(auth_provider: AuthProvider, case: "Case", context: AuthContext) -> Optional[Auth]:
395
+ def _provider_get(auth_provider: AuthProvider, case: Case, context: AuthContext) -> Auth | None:
389
396
  # A shim to provide a compatibility layer between previously used convention for `AuthProvider.get`
390
397
  # where it used to accept a single `context` argument
391
398
  method = auth_provider.get
@@ -404,7 +411,7 @@ def _provider_get(auth_provider: AuthProvider, case: "Case", context: AuthContex
404
411
  return method(case, context)
405
412
 
406
413
 
407
- def set_on_case(case: "Case", context: AuthContext, auth_storage: Optional[AuthStorage]) -> None:
414
+ def set_on_case(case: Case, context: AuthContext, auth_storage: AuthStorage | None) -> None:
408
415
  """Set authentication data on this case.
409
416
 
410
417
  If there is no auth defined, then this function is no-op.
@@ -417,7 +424,7 @@ def set_on_case(case: "Case", context: AuthContext, auth_storage: Optional[AuthS
417
424
  GLOBAL_AUTH_STORAGE.set(case, context)
418
425
 
419
426
 
420
- def get_auth_storage_from_test(test: GenericTest) -> Optional[AuthStorage]:
427
+ def get_auth_storage_from_test(test: GenericTest) -> AuthStorage | None:
421
428
  """Extract the currently attached auth storage from a test function."""
422
429
  return getattr(test, AUTH_STORAGE_ATTRIBUTE_NAME, None)
423
430
 
schemathesis/checks.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import TYPE_CHECKING, Optional, Tuple
2
+ from typing import TYPE_CHECKING
3
3
 
4
4
  from . import failures
5
5
  from .exceptions import get_server_error
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
15
15
  from .models import Case, CheckFunction
16
16
 
17
17
 
18
- def not_a_server_error(response: GenericResponse, case: Case) -> Optional[bool]:
18
+ def not_a_server_error(response: GenericResponse, case: Case) -> bool | None:
19
19
  """A check to verify that the response is not a server-side error."""
20
20
  status_code = response.status_code
21
21
  if status_code >= 500:
@@ -24,14 +24,14 @@ def not_a_server_error(response: GenericResponse, case: Case) -> Optional[bool]:
24
24
  return None
25
25
 
26
26
 
27
- DEFAULT_CHECKS: Tuple["CheckFunction", ...] = (not_a_server_error,)
27
+ DEFAULT_CHECKS: tuple[CheckFunction, ...] = (not_a_server_error,)
28
28
  OPTIONAL_CHECKS = (
29
29
  status_code_conformance,
30
30
  content_type_conformance,
31
31
  response_headers_conformance,
32
32
  response_schema_conformance,
33
33
  )
34
- ALL_CHECKS: Tuple["CheckFunction", ...] = DEFAULT_CHECKS + OPTIONAL_CHECKS
34
+ ALL_CHECKS: tuple[CheckFunction, ...] = DEFAULT_CHECKS + OPTIONAL_CHECKS
35
35
 
36
36
 
37
37
  def register(check: CheckFunction) -> CheckFunction: