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,5 +1,7 @@
1
1
  from __future__ import annotations
2
- from typing import Dict, TYPE_CHECKING
2
+
3
+ from functools import lru_cache
4
+ from typing import TYPE_CHECKING
3
5
 
4
6
 
5
7
  from ...exceptions import UsageError
@@ -8,7 +10,7 @@ if TYPE_CHECKING:
8
10
  import graphql
9
11
  from hypothesis import strategies as st
10
12
 
11
- CUSTOM_SCALARS: Dict[str, st.SearchStrategy[graphql.ValueNode]] = {}
13
+ CUSTOM_SCALARS: dict[str, st.SearchStrategy[graphql.ValueNode]] = {}
12
14
 
13
15
 
14
16
  def scalar(name: str, strategy: st.SearchStrategy[graphql.ValueNode]) -> None:
@@ -24,3 +26,25 @@ def scalar(name: str, strategy: st.SearchStrategy[graphql.ValueNode]) -> None:
24
26
  if not isinstance(strategy, SearchStrategy):
25
27
  raise UsageError(f"{strategy!r} must be a Hypothesis strategy which generates AST nodes matching this scalar")
26
28
  CUSTOM_SCALARS[name] = strategy
29
+
30
+
31
+ @lru_cache
32
+ def get_extra_scalar_strategies() -> dict[str, st.SearchStrategy]:
33
+ """Get all extra GraphQL strategies."""
34
+ from . import nodes
35
+ from hypothesis import strategies as st
36
+
37
+ dates = st.dates().map(str)
38
+ times = st.times().map("%sZ".__mod__)
39
+
40
+ return {
41
+ "Date": dates.map(nodes.String),
42
+ "Time": times.map(nodes.String),
43
+ "DateTime": st.tuples(dates, times).map("T".join).map(nodes.String),
44
+ "IP": st.ip_addresses().map(str).map(nodes.String),
45
+ "IPv4": st.ip_addresses(v=4).map(str).map(nodes.String),
46
+ "IPv6": st.ip_addresses(v=6).map(str).map(nodes.String),
47
+ "BigInt": st.integers().map(nodes.Int),
48
+ "Long": st.integers(min_value=-(2**63), max_value=2**63 - 1).map(nodes.Int),
49
+ "UUID": st.uuids().map(str).map(nodes.String),
50
+ }
@@ -5,15 +5,9 @@ from enum import unique
5
5
  from typing import (
6
6
  Any,
7
7
  Callable,
8
- Dict,
9
8
  Generator,
10
- List,
11
- Optional,
12
9
  Sequence,
13
- Tuple,
14
- Type,
15
10
  TypeVar,
16
- Union,
17
11
  cast,
18
12
  TYPE_CHECKING,
19
13
  )
@@ -29,7 +23,7 @@ from requests.structures import CaseInsensitiveDict
29
23
  from ... import auths
30
24
  from ...auths import AuthStorage
31
25
  from ...checks import not_a_server_error
32
- from ...generation import DataGenerationMethod
26
+ from ...generation import DataGenerationMethod, GenerationConfig
33
27
  from ...exceptions import OperationSchemaError
34
28
  from ...constants import NOT_SET
35
29
  from ...hooks import (
@@ -44,7 +38,7 @@ from ...models import APIOperation, Case, CheckFunction, OperationDefinition
44
38
  from ...schemas import BaseSchema
45
39
  from ...stateful import Stateful, StatefulTest
46
40
  from ...types import Body, Cookies, Headers, NotSet, PathParameters, Query
47
- from .scalars import CUSTOM_SCALARS
41
+ from .scalars import CUSTOM_SCALARS, get_extra_scalar_strategies
48
42
 
49
43
  if TYPE_CHECKING:
50
44
  from ...transports.responses import GenericResponse
@@ -58,12 +52,10 @@ class RootType(enum.Enum):
58
52
 
59
53
  @dataclass(repr=False)
60
54
  class GraphQLCase(Case):
61
- def as_requests_kwargs(
62
- self, base_url: Optional[str] = None, headers: Optional[Dict[str, str]] = None
63
- ) -> Dict[str, Any]:
55
+ def as_requests_kwargs(self, base_url: str | None = None, headers: dict[str, str] | None = None) -> dict[str, Any]:
64
56
  final_headers = self._get_headers(headers)
65
57
  base_url = self._get_base_url(base_url)
66
- kwargs: Dict[str, Any] = {"method": self.method, "url": base_url, "headers": final_headers}
58
+ kwargs: dict[str, Any] = {"method": self.method, "url": base_url, "headers": final_headers}
67
59
  # There is no direct way to have bytes here, but it is a useful pattern to support.
68
60
  # It also unifies GraphQLCase with its Open API counterpart where bytes may come from external examples
69
61
  if isinstance(self.body, bytes):
@@ -74,7 +66,7 @@ class GraphQLCase(Case):
74
66
  kwargs["json"] = {"query": self.body}
75
67
  return kwargs
76
68
 
77
- def as_werkzeug_kwargs(self, headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
69
+ def as_werkzeug_kwargs(self, headers: dict[str, str] | None = None) -> dict[str, Any]:
78
70
  final_headers = self._get_headers(headers)
79
71
  return {
80
72
  "method": self.method,
@@ -88,10 +80,10 @@ class GraphQLCase(Case):
88
80
  def validate_response(
89
81
  self,
90
82
  response: GenericResponse,
91
- checks: Tuple[CheckFunction, ...] = (),
92
- additional_checks: Tuple[CheckFunction, ...] = (),
93
- excluded_checks: Tuple[CheckFunction, ...] = (),
94
- code_sample_style: Optional[str] = None,
83
+ checks: tuple[CheckFunction, ...] = (),
84
+ additional_checks: tuple[CheckFunction, ...] = (),
85
+ excluded_checks: tuple[CheckFunction, ...] = (),
86
+ code_sample_style: str | None = None,
95
87
  ) -> None:
96
88
  checks = checks or (not_a_server_error,)
97
89
  checks += additional_checks
@@ -101,8 +93,8 @@ class GraphQLCase(Case):
101
93
  def call_asgi(
102
94
  self,
103
95
  app: Any = None,
104
- base_url: Optional[str] = None,
105
- headers: Optional[Dict[str, str]] = None,
96
+ base_url: str | None = None,
97
+ headers: dict[str, str] | None = None,
106
98
  **kwargs: Any,
107
99
  ) -> requests.Response:
108
100
  return super().call_asgi(app=app, base_url=base_url, headers=headers, **kwargs)
@@ -163,8 +155,13 @@ class GraphQLSchema(BaseSchema):
163
155
  total += len(type_def["fields"])
164
156
  return total
165
157
 
158
+ @property
159
+ def links_count(self) -> int:
160
+ # Links are not supported for GraphQL
161
+ return 0
162
+
166
163
  def get_all_operations(
167
- self, hooks: Optional[HookDispatcher] = None
164
+ self, hooks: HookDispatcher | None = None
168
165
  ) -> Generator[Result[APIOperation, OperationSchemaError], None, None]:
169
166
  schema = self.client_schema
170
167
  for root_type, operation_type in (
@@ -205,9 +202,10 @@ class GraphQLSchema(BaseSchema):
205
202
  def get_case_strategy(
206
203
  self,
207
204
  operation: APIOperation,
208
- hooks: Optional[HookDispatcher] = None,
209
- auth_storage: Optional[AuthStorage] = None,
205
+ hooks: HookDispatcher | None = None,
206
+ auth_storage: AuthStorage | None = None,
210
207
  data_generation_method: DataGenerationMethod = DataGenerationMethod.default(),
208
+ generation_config: GenerationConfig | None = None,
211
209
  **kwargs: Any,
212
210
  ) -> SearchStrategy:
213
211
  return get_case_strategy(
@@ -216,28 +214,29 @@ class GraphQLSchema(BaseSchema):
216
214
  hooks=hooks,
217
215
  auth_storage=auth_storage,
218
216
  data_generation_method=data_generation_method,
217
+ generation_config=generation_config,
219
218
  **kwargs,
220
219
  )
221
220
 
222
- def get_strategies_from_examples(self, operation: APIOperation) -> List[SearchStrategy[Case]]:
221
+ def get_strategies_from_examples(self, operation: APIOperation) -> list[SearchStrategy[Case]]:
223
222
  return []
224
223
 
225
224
  def get_stateful_tests(
226
- self, response: GenericResponse, operation: APIOperation, stateful: Optional[Stateful]
225
+ self, response: GenericResponse, operation: APIOperation, stateful: Stateful | None
227
226
  ) -> Sequence[StatefulTest]:
228
227
  return []
229
228
 
230
229
  def make_case(
231
230
  self,
232
231
  *,
233
- case_cls: Type[C],
232
+ case_cls: type[C],
234
233
  operation: APIOperation,
235
- path_parameters: Optional[PathParameters] = None,
236
- headers: Optional[Headers] = None,
237
- cookies: Optional[Cookies] = None,
238
- query: Optional[Query] = None,
239
- body: Union[Body, NotSet] = NOT_SET,
240
- media_type: Optional[str] = None,
234
+ path_parameters: PathParameters | None = None,
235
+ headers: Headers | None = None,
236
+ cookies: Cookies | None = None,
237
+ query: Query | None = None,
238
+ body: Body | NotSet = NOT_SET,
239
+ media_type: str | None = None,
241
240
  ) -> C:
242
241
  return case_cls(
243
242
  operation=operation,
@@ -255,9 +254,10 @@ def get_case_strategy(
255
254
  draw: Callable,
256
255
  operation: APIOperation,
257
256
  client_schema: graphql.GraphQLSchema,
258
- hooks: Optional[HookDispatcher] = None,
259
- auth_storage: Optional[AuthStorage] = None,
257
+ hooks: HookDispatcher | None = None,
258
+ auth_storage: AuthStorage | None = None,
260
259
  data_generation_method: DataGenerationMethod = DataGenerationMethod.default(),
260
+ generation_config: GenerationConfig | None = None,
261
261
  **kwargs: Any,
262
262
  ) -> Any:
263
263
  definition = cast(GraphQLOperationDefinition, operation.definition)
@@ -266,11 +266,15 @@ def get_case_strategy(
266
266
  RootType.MUTATION: gql_st.mutations,
267
267
  }[definition.root_type]
268
268
  hook_context = HookContext(operation)
269
+ generation_config = generation_config or GenerationConfig()
270
+ custom_scalars = {**get_extra_scalar_strategies(), **CUSTOM_SCALARS}
269
271
  strategy = strategy_factory(
270
272
  client_schema,
271
273
  fields=[definition.field_name],
272
- custom_scalars=CUSTOM_SCALARS,
274
+ custom_scalars=custom_scalars,
273
275
  print_ast=_noop, # type: ignore
276
+ allow_x00=generation_config.allow_x00,
277
+ codec=generation_config.codec,
274
278
  )
275
279
  strategy = apply_to_all_dispatchers(operation, hook_context, hooks, strategy, "body").map(graphql.print_ast)
276
280
  body = draw(strategy)
@@ -1,10 +1,11 @@
1
+ from __future__ import annotations
1
2
  import re
2
3
  import string
3
4
  from base64 import b64encode
4
5
  from contextlib import suppress
5
6
  from dataclasses import dataclass
6
7
  from functools import lru_cache
7
- from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
8
+ from typing import Any, Callable, Dict, Iterable, Optional
8
9
  from urllib.parse import quote_plus
9
10
  from weakref import WeakKeyDictionary
10
11
 
@@ -16,7 +17,7 @@ from requests.structures import CaseInsensitiveDict
16
17
  from ...constants import NOT_SET
17
18
  from .formats import STRING_FORMATS
18
19
  from ... import auths, serializers
19
- from ...generation import DataGenerationMethod
20
+ from ...generation import DataGenerationMethod, GenerationConfig
20
21
  from ...internal.copy import fast_deepcopy
21
22
  from ...exceptions import SerializationNotPossible, BodyInGetRequestError
22
23
  from ...hooks import HookContext, HookDispatcher, apply_to_all_dispatchers
@@ -34,14 +35,14 @@ from .utils import is_header_location
34
35
  HEADER_FORMAT = "_header_value"
35
36
  PARAMETERS = frozenset(("path_parameters", "headers", "cookies", "query", "body"))
36
37
  SLASH = "/"
37
- StrategyFactory = Callable[[Dict[str, Any], str, str, Optional[str]], st.SearchStrategy]
38
+ StrategyFactory = Callable[[Dict[str, Any], str, str, Optional[str], GenerationConfig], st.SearchStrategy]
38
39
 
39
40
 
40
- @lru_cache()
41
- def get_default_format_strategies() -> Dict[str, st.SearchStrategy]:
41
+ @lru_cache
42
+ def get_default_format_strategies() -> dict[str, st.SearchStrategy]:
42
43
  """Get all default "format" strategies."""
43
44
 
44
- def make_basic_auth_str(item: Tuple[str, str]) -> str:
45
+ def make_basic_auth_str(item: tuple[str, str]) -> str:
45
46
  return _basic_auth_str(*item)
46
47
 
47
48
  latin1_text = st.text(alphabet=st.characters(min_codepoint=0, max_codepoint=255))
@@ -63,7 +64,7 @@ def get_default_format_strategies() -> Dict[str, st.SearchStrategy]:
63
64
  }
64
65
 
65
66
 
66
- def is_valid_header(headers: Dict[str, Any]) -> bool:
67
+ def is_valid_header(headers: dict[str, Any]) -> bool:
67
68
  """Verify if the generated headers are valid."""
68
69
  for name, value in headers.items():
69
70
  if not is_latin_1_encodable(value):
@@ -83,7 +84,7 @@ def is_illegal_surrogate(item: Any) -> bool:
83
84
  return isinstance(item, str) and bool(has_surrogate_pair(item))
84
85
 
85
86
 
86
- def is_valid_query(query: Dict[str, Any]) -> bool:
87
+ def is_valid_query(query: dict[str, Any]) -> bool:
87
88
  """Surrogates are not allowed in a query string.
88
89
 
89
90
  `requests` and `werkzeug` will fail to send it to the application.
@@ -98,13 +99,14 @@ def is_valid_query(query: Dict[str, Any]) -> bool:
98
99
  def get_case_strategy(
99
100
  draw: Callable,
100
101
  operation: APIOperation,
101
- hooks: Optional[HookDispatcher] = None,
102
- auth_storage: Optional[auths.AuthStorage] = None,
102
+ hooks: HookDispatcher | None = None,
103
+ auth_storage: auths.AuthStorage | None = None,
103
104
  generator: DataGenerationMethod = DataGenerationMethod.default(),
104
- path_parameters: Union[NotSet, Dict[str, Any]] = NOT_SET,
105
- headers: Union[NotSet, Dict[str, Any]] = NOT_SET,
106
- cookies: Union[NotSet, Dict[str, Any]] = NOT_SET,
107
- query: Union[NotSet, Dict[str, Any]] = NOT_SET,
105
+ generation_config: GenerationConfig | None = None,
106
+ path_parameters: NotSet | dict[str, Any] = NOT_SET,
107
+ headers: NotSet | dict[str, Any] = NOT_SET,
108
+ cookies: NotSet | dict[str, Any] = NOT_SET,
109
+ query: NotSet | dict[str, Any] = NOT_SET,
108
110
  body: Any = NOT_SET,
109
111
  ) -> Any:
110
112
  """A strategy that creates `Case` instances.
@@ -123,10 +125,14 @@ def get_case_strategy(
123
125
 
124
126
  context = HookContext(operation)
125
127
 
126
- path_parameters_ = generate_parameter("path", path_parameters, operation, draw, context, hooks, generator)
127
- headers_ = generate_parameter("header", headers, operation, draw, context, hooks, generator)
128
- cookies_ = generate_parameter("cookie", cookies, operation, draw, context, hooks, generator)
129
- query_ = generate_parameter("query", query, operation, draw, context, hooks, generator)
128
+ generation_config = generation_config or GenerationConfig()
129
+
130
+ path_parameters_ = generate_parameter(
131
+ "path", path_parameters, operation, draw, context, hooks, generator, generation_config
132
+ )
133
+ headers_ = generate_parameter("header", headers, operation, draw, context, hooks, generator, generation_config)
134
+ cookies_ = generate_parameter("cookie", cookies, operation, draw, context, hooks, generator, generation_config)
135
+ query_ = generate_parameter("query", query, operation, draw, context, hooks, generator, generation_config)
130
136
 
131
137
  media_type = None
132
138
  if body is NOT_SET:
@@ -143,7 +149,7 @@ def get_case_strategy(
143
149
  else:
144
150
  candidates = operation.body.items
145
151
  parameter = draw(st.sampled_from(candidates))
146
- strategy = _get_body_strategy(parameter, strategy_factory, operation)
152
+ strategy = _get_body_strategy(parameter, strategy_factory, operation, generation_config)
147
153
  strategy = apply_hooks(operation, context, hooks, strategy, "body")
148
154
  # Parameter may have a wildcard media type. In this case, choose any supported one
149
155
  possible_media_types = sorted(serializers.get_matching_media_types(parameter.media_type))
@@ -198,6 +204,7 @@ def _get_body_strategy(
198
204
  parameter: OpenAPIBody,
199
205
  strategy_factory: StrategyFactory,
200
206
  operation: APIOperation,
207
+ generation_config: GenerationConfig,
201
208
  ) -> st.SearchStrategy:
202
209
  # The cache key relies on object ids, which means that the parameter should not be mutated
203
210
  # Note, the parent schema is not included as each parameter belong only to one schema
@@ -205,7 +212,7 @@ def _get_body_strategy(
205
212
  return _BODY_STRATEGIES_CACHE[parameter][strategy_factory]
206
213
  schema = parameter.as_json_schema(operation)
207
214
  schema = operation.schema.prepare_schema(schema)
208
- strategy = strategy_factory(schema, operation.verbose_name, "body", parameter.media_type)
215
+ strategy = strategy_factory(schema, operation.verbose_name, "body", parameter.media_type, generation_config)
209
216
  if not parameter.is_required:
210
217
  strategy |= st.just(NOT_SET)
211
218
  _BODY_STRATEGIES_CACHE.setdefault(parameter, {})[strategy_factory] = strategy
@@ -213,24 +220,25 @@ def _get_body_strategy(
213
220
 
214
221
 
215
222
  def get_parameters_value(
216
- value: Union[NotSet, Dict[str, Any]],
223
+ value: NotSet | dict[str, Any],
217
224
  location: str,
218
225
  draw: Callable,
219
226
  operation: APIOperation,
220
227
  context: HookContext,
221
- hooks: Optional[HookDispatcher],
228
+ hooks: HookDispatcher | None,
222
229
  strategy_factory: StrategyFactory,
223
- ) -> Optional[Dict[str, Any]]:
230
+ generation_config: GenerationConfig,
231
+ ) -> dict[str, Any] | None:
224
232
  """Get the final value for the specified location.
225
233
 
226
234
  If the value is not set, then generate it from the relevant strategy. Otherwise, check what is missing in it and
227
235
  generate those parts.
228
236
  """
229
237
  if isinstance(value, NotSet) or not value:
230
- strategy = get_parameters_strategy(operation, strategy_factory, location)
238
+ strategy = get_parameters_strategy(operation, strategy_factory, location, generation_config)
231
239
  strategy = apply_hooks(operation, context, hooks, strategy, location)
232
240
  return draw(strategy)
233
- strategy = get_parameters_strategy(operation, strategy_factory, location, exclude=value.keys())
241
+ strategy = get_parameters_strategy(operation, strategy_factory, location, generation_config, exclude=value.keys())
234
242
  strategy = apply_hooks(operation, context, hooks, strategy, location)
235
243
  new = draw(strategy)
236
244
  if new is not None:
@@ -249,7 +257,7 @@ class ValueContainer:
249
257
 
250
258
  value: Any
251
259
  location: str
252
- generator: Optional[DataGenerationMethod]
260
+ generator: DataGenerationMethod | None
253
261
 
254
262
  @property
255
263
  def is_generated(self) -> bool:
@@ -257,19 +265,20 @@ class ValueContainer:
257
265
  return self.generator is not None and (self.location == "body" or self.value is not None)
258
266
 
259
267
 
260
- def any_negated_values(values: List[ValueContainer]) -> bool:
268
+ def any_negated_values(values: list[ValueContainer]) -> bool:
261
269
  """Check if any generated values are negated."""
262
270
  return any(value.generator == DataGenerationMethod.negative for value in values if value.is_generated)
263
271
 
264
272
 
265
273
  def generate_parameter(
266
274
  location: str,
267
- explicit: Union[NotSet, Dict[str, Any]],
275
+ explicit: NotSet | dict[str, Any],
268
276
  operation: APIOperation,
269
277
  draw: Callable,
270
278
  context: HookContext,
271
- hooks: Optional[HookDispatcher],
279
+ hooks: HookDispatcher | None,
272
280
  generator: DataGenerationMethod,
281
+ generation_config: GenerationConfig,
273
282
  ) -> ValueContainer:
274
283
  """Generate a value for a parameter.
275
284
 
@@ -285,8 +294,10 @@ def generate_parameter(
285
294
  generator = DataGenerationMethod.positive
286
295
  else:
287
296
  strategy_factory = DATA_GENERATION_METHOD_TO_STRATEGY_FACTORY[generator]
288
- value = get_parameters_value(explicit, location, draw, operation, context, hooks, strategy_factory)
289
- used_generator: Optional[DataGenerationMethod] = generator
297
+ value = get_parameters_value(
298
+ explicit, location, draw, operation, context, hooks, strategy_factory, generation_config
299
+ )
300
+ used_generator: DataGenerationMethod | None = generator
290
301
  if value == explicit:
291
302
  # When we pass `explicit`, then its parts are excluded from generation of the final value
292
303
  # If the final value is the same, then other parameters were generated at all
@@ -319,6 +330,7 @@ def get_parameters_strategy(
319
330
  operation: APIOperation,
320
331
  strategy_factory: StrategyFactory,
321
332
  location: str,
333
+ generation_config: GenerationConfig,
322
334
  exclude: Iterable[str] = (),
323
335
  ) -> st.SearchStrategy:
324
336
  """Create a new strategy for the case's component from the API operation parameters."""
@@ -345,7 +357,7 @@ def get_parameters_strategy(
345
357
  # Nothing to negate - all properties were excluded
346
358
  strategy = st.none()
347
359
  else:
348
- strategy = strategy_factory(schema, operation.verbose_name, location, None)
360
+ strategy = strategy_factory(schema, operation.verbose_name, location, None, generation_config)
349
361
  serialize = operation.get_parameter_serializer(location)
350
362
  if serialize is not None:
351
363
  strategy = strategy.map(serialize)
@@ -374,7 +386,7 @@ def get_parameters_strategy(
374
386
  return st.none()
375
387
 
376
388
 
377
- def jsonify_python_specific_types(value: Dict[str, Any]) -> Dict[str, Any]:
389
+ def jsonify_python_specific_types(value: dict[str, Any]) -> dict[str, Any]:
378
390
  """Convert Python-specific values to their JSON equivalents."""
379
391
  stack: list = [value]
380
392
  while stack:
@@ -395,11 +407,12 @@ def jsonify_python_specific_types(value: Dict[str, Any]) -> Dict[str, Any]:
395
407
 
396
408
 
397
409
  def make_positive_strategy(
398
- schema: Dict[str, Any],
410
+ schema: dict[str, Any],
399
411
  operation_name: str,
400
412
  location: str,
401
- media_type: Optional[str],
402
- custom_formats: Optional[Dict[str, st.SearchStrategy]] = None,
413
+ media_type: str | None,
414
+ generation_config: GenerationConfig,
415
+ custom_formats: dict[str, st.SearchStrategy] | None = None,
403
416
  ) -> st.SearchStrategy:
404
417
  """Strategy for generating values that fit the schema."""
405
418
  if is_header_location(location):
@@ -410,21 +423,25 @@ def make_positive_strategy(
410
423
  if list(sub_schema) == ["type"] and sub_schema["type"] == "string":
411
424
  sub_schema.setdefault("format", HEADER_FORMAT)
412
425
  return from_schema(
413
- schema, custom_formats={**get_default_format_strategies(), **STRING_FORMATS, **(custom_formats or {})}
426
+ schema,
427
+ custom_formats={**get_default_format_strategies(), **STRING_FORMATS, **(custom_formats or {})},
428
+ allow_x00=generation_config.allow_x00,
429
+ codec=generation_config.codec,
414
430
  )
415
431
 
416
432
 
417
- def _can_skip_header_filter(schema: Dict[str, Any]) -> bool:
433
+ def _can_skip_header_filter(schema: dict[str, Any]) -> bool:
418
434
  # All headers should contain HEADER_FORMAT in order to avoid header filter
419
435
  return all(sub_schema.get("format") == HEADER_FORMAT for sub_schema in schema.get("properties", {}).values())
420
436
 
421
437
 
422
438
  def make_negative_strategy(
423
- schema: Dict[str, Any],
439
+ schema: dict[str, Any],
424
440
  operation_name: str,
425
441
  location: str,
426
- media_type: Optional[str],
427
- custom_formats: Optional[Dict[str, st.SearchStrategy]] = None,
442
+ media_type: str | None,
443
+ generation_config: GenerationConfig,
444
+ custom_formats: dict[str, st.SearchStrategy] | None = None,
428
445
  ) -> st.SearchStrategy:
429
446
  return negative_schema(
430
447
  schema,
@@ -432,6 +449,7 @@ def make_negative_strategy(
432
449
  location=location,
433
450
  media_type=media_type,
434
451
  custom_formats={**get_default_format_strategies(), **STRING_FORMATS, **(custom_formats or {})},
452
+ generation_config=generation_config,
435
453
  )
436
454
 
437
455
 
@@ -441,7 +459,7 @@ DATA_GENERATION_METHOD_TO_STRATEGY_FACTORY = {
441
459
  }
442
460
 
443
461
 
444
- def is_valid_path(parameters: Dict[str, Any]) -> bool:
462
+ def is_valid_path(parameters: dict[str, Any]) -> bool:
445
463
  """Empty strings ("") are excluded from path by urllib3.
446
464
 
447
465
  A path containing to "/" or "%2F" will lead to ambiguous path resolution in
@@ -459,7 +477,7 @@ def is_valid_path(parameters: Dict[str, Any]) -> bool:
459
477
  )
460
478
 
461
479
 
462
- def quote_all(parameters: Dict[str, Any]) -> Dict[str, Any]:
480
+ def quote_all(parameters: dict[str, Any]) -> dict[str, Any]:
463
481
  """Apply URL quotation for all values in a dictionary."""
464
482
  # Even though, "." is an unreserved character, it has a special meaning in "." and ".." strings.
465
483
  # It will change the path:
@@ -481,7 +499,7 @@ def quote_all(parameters: Dict[str, Any]) -> Dict[str, Any]:
481
499
  def apply_hooks(
482
500
  operation: APIOperation,
483
501
  context: HookContext,
484
- hooks: Optional[HookDispatcher],
502
+ hooks: HookDispatcher | None,
485
503
  strategy: st.SearchStrategy,
486
504
  location: str,
487
505
  ) -> st.SearchStrategy:
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import TYPE_CHECKING, Any, Dict, Generator, NoReturn, Optional, Union
2
+ from typing import TYPE_CHECKING, Any, Generator, NoReturn
3
3
 
4
4
  from ... import failures
5
5
  from ...exceptions import (
@@ -17,11 +17,11 @@ if TYPE_CHECKING:
17
17
  from ...models import Case
18
18
 
19
19
 
20
- def status_code_conformance(response: GenericResponse, case: "Case") -> Optional[bool]:
20
+ def status_code_conformance(response: GenericResponse, case: Case) -> bool | None:
21
21
  from .schemas import BaseOpenAPISchema
22
22
 
23
23
  if not isinstance(case.operation.schema, BaseOpenAPISchema):
24
- raise TypeError("This check can be used only with Open API schemas")
24
+ return True
25
25
  responses = case.operation.definition.raw.get("responses", {})
26
26
  # "default" can be used as the default response object for all HTTP codes that are not covered individually
27
27
  if "default" in responses:
@@ -43,16 +43,16 @@ def status_code_conformance(response: GenericResponse, case: "Case") -> Optional
43
43
  return None # explicitly return None for mypy
44
44
 
45
45
 
46
- def _expand_responses(responses: Dict[Union[str, int], Any]) -> Generator[int, None, None]:
46
+ def _expand_responses(responses: dict[str | int, Any]) -> Generator[int, None, None]:
47
47
  for code in responses:
48
48
  yield from expand_status_code(code)
49
49
 
50
50
 
51
- def content_type_conformance(response: GenericResponse, case: "Case") -> Optional[bool]:
51
+ def content_type_conformance(response: GenericResponse, case: Case) -> bool | None:
52
52
  from .schemas import BaseOpenAPISchema
53
53
 
54
54
  if not isinstance(case.operation.schema, BaseOpenAPISchema):
55
- raise TypeError("This check can be used only with Open API schemas")
55
+ return True
56
56
  documented_content_types = case.operation.schema.get_content_types(case.operation, response)
57
57
  if not documented_content_types:
58
58
  return None
@@ -96,11 +96,11 @@ def _reraise_malformed_media_type(exc: ValueError, location: str, actual: str, d
96
96
  ) from exc
97
97
 
98
98
 
99
- def response_headers_conformance(response: GenericResponse, case: "Case") -> Optional[bool]:
99
+ def response_headers_conformance(response: GenericResponse, case: Case) -> bool | None:
100
100
  from .schemas import BaseOpenAPISchema
101
101
 
102
102
  if not isinstance(case.operation.schema, BaseOpenAPISchema):
103
- raise TypeError("This check can be used only with Open API schemas")
103
+ return True
104
104
  defined_headers = case.operation.schema.get_headers(case.operation, response)
105
105
  if not defined_headers:
106
106
  return None
@@ -121,9 +121,9 @@ def response_headers_conformance(response: GenericResponse, case: "Case") -> Opt
121
121
  )
122
122
 
123
123
 
124
- def response_schema_conformance(response: GenericResponse, case: "Case") -> None:
124
+ def response_schema_conformance(response: GenericResponse, case: Case) -> bool | None:
125
125
  from .schemas import BaseOpenAPISchema
126
126
 
127
127
  if not isinstance(case.operation.schema, BaseOpenAPISchema):
128
- raise TypeError("This check can be used only with Open API schemas")
128
+ return True
129
129
  return case.operation.validate_response(response)
@@ -1,13 +1,14 @@
1
+ from __future__ import annotations
1
2
  from itertools import chain
2
- from typing import Any, Callable, Dict, List
3
+ from typing import Any, Callable
3
4
 
4
5
  from ...internal.jsonschema import traverse_schema
5
6
  from ...internal.copy import fast_deepcopy
6
7
 
7
8
 
8
9
  def to_json_schema(
9
- schema: Dict[str, Any], *, nullable_name: str, copy: bool = True, is_response_schema: bool = False
10
- ) -> Dict[str, Any]:
10
+ schema: dict[str, Any], *, nullable_name: str, copy: bool = True, is_response_schema: bool = False
11
+ ) -> dict[str, Any]:
11
12
  """Convert Open API parameters to JSON Schema.
12
13
 
13
14
  NOTE. This function is applied to all keywords (including nested) during a schema resolving, thus it is not recursive.
@@ -32,7 +33,7 @@ def to_json_schema(
32
33
  return schema
33
34
 
34
35
 
35
- def rewrite_properties(schema: Dict[str, Any], predicate: Callable[[Dict[str, Any]], bool]) -> None:
36
+ def rewrite_properties(schema: dict[str, Any], predicate: Callable[[dict[str, Any]], bool]) -> None:
36
37
  required = schema.get("required", [])
37
38
  forbidden = []
38
39
  for name, subschema in list(schema.get("properties", {}).items()):
@@ -49,7 +50,7 @@ def rewrite_properties(schema: Dict[str, Any], predicate: Callable[[Dict[str, An
49
50
  schema.pop("properties", None)
50
51
 
51
52
 
52
- def forbid_properties(schema: Dict[str, Any], forbidden: List[str]) -> None:
53
+ def forbid_properties(schema: dict[str, Any], forbidden: list[str]) -> None:
53
54
  """Explicitly forbid properties via the `not` keyword."""
54
55
  not_schema = schema.setdefault("not", {})
55
56
  already_forbidden = not_schema.setdefault("required", [])
@@ -57,15 +58,15 @@ def forbid_properties(schema: Dict[str, Any], forbidden: List[str]) -> None:
57
58
  not_schema["required"] = list(set(chain(already_forbidden, forbidden)))
58
59
 
59
60
 
60
- def is_write_only(schema: Dict[str, Any]) -> bool:
61
+ def is_write_only(schema: dict[str, Any]) -> bool:
61
62
  return schema.get("writeOnly", False) or schema.get("x-writeOnly", False)
62
63
 
63
64
 
64
- def is_read_only(schema: Dict[str, Any]) -> bool:
65
+ def is_read_only(schema: dict[str, Any]) -> bool:
65
66
  return schema.get("readOnly", False)
66
67
 
67
68
 
68
69
  def to_json_schema_recursive(
69
- schema: Dict[str, Any], nullable_name: str, is_response_schema: bool = False
70
- ) -> Dict[str, Any]:
70
+ schema: dict[str, Any], nullable_name: str, is_response_schema: bool = False
71
+ ) -> dict[str, Any]:
71
72
  return traverse_schema(schema, to_json_schema, nullable_name=nullable_name, is_response_schema=is_response_schema)
@@ -1,6 +1,6 @@
1
1
  # These schemas are copied from https://github.com/OAI/OpenAPI-Specification/tree/master/schemas
2
2
  from __future__ import annotations
3
- from typing import Any, Dict, TYPE_CHECKING
3
+ from typing import Any, TYPE_CHECKING
4
4
 
5
5
  from ..._lazy_import import lazy_import
6
6
 
@@ -1919,7 +1919,7 @@ _imports = {
1919
1919
  }
1920
1920
 
1921
1921
 
1922
- def make_validator(schema: Dict[str, Any]) -> Validator:
1922
+ def make_validator(schema: dict[str, Any]) -> Validator:
1923
1923
  import jsonschema
1924
1924
 
1925
1925
  return jsonschema.validators.validator_for(schema)(schema)