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,6 +1,7 @@
1
+ from __future__ import annotations
1
2
  import json
2
3
  from dataclasses import dataclass
3
- from typing import Any, ClassVar, Dict, Iterable, List, Optional, Tuple
4
+ from typing import Any, ClassVar, Iterable
4
5
 
5
6
  from ...exceptions import OperationSchemaError
6
7
  from ...models import APIOperation
@@ -15,10 +16,10 @@ class OpenAPIParameter(Parameter):
15
16
  example_field: ClassVar[str]
16
17
  examples_field: ClassVar[str]
17
18
  nullable_field: ClassVar[str]
18
- supported_jsonschema_keywords: ClassVar[Tuple[str, ...]]
19
+ supported_jsonschema_keywords: ClassVar[tuple[str, ...]]
19
20
 
20
21
  @property
21
- def description(self) -> Optional[str]:
22
+ def description(self) -> str | None:
22
23
  """A brief parameter description."""
23
24
  return self.definition.get("description")
24
25
 
@@ -87,12 +88,12 @@ class OpenAPIParameter(Parameter):
87
88
  """
88
89
  return self.definition.get("schema", {}).get("example")
89
90
 
90
- def as_json_schema(self, operation: APIOperation) -> Dict[str, Any]:
91
+ def as_json_schema(self, operation: APIOperation) -> dict[str, Any]:
91
92
  """Convert parameter's definition to JSON Schema."""
92
93
  schema = self.from_open_api_to_json_schema(operation, self.definition)
93
94
  return self.transform_keywords(schema)
94
95
 
95
- def transform_keywords(self, schema: Dict[str, Any]) -> Dict[str, Any]:
96
+ def transform_keywords(self, schema: dict[str, Any]) -> dict[str, Any]:
96
97
  """Transform Open API specific keywords into JSON Schema compatible form."""
97
98
  definition = to_json_schema_recursive(schema, self.nullable_field)
98
99
  # Headers are strings, but it is not always explicitly defined in the schema. By preparing them properly, we
@@ -106,7 +107,7 @@ class OpenAPIParameter(Parameter):
106
107
  definition.setdefault("type", "string")
107
108
  return definition
108
109
 
109
- def from_open_api_to_json_schema(self, operation: APIOperation, open_api_schema: Dict[str, Any]) -> Dict[str, Any]:
110
+ def from_open_api_to_json_schema(self, operation: APIOperation, open_api_schema: dict[str, Any]) -> dict[str, Any]:
110
111
  """Convert Open API's `Schema` to JSON Schema."""
111
112
  return {
112
113
  key: value
@@ -137,7 +138,7 @@ class OpenAPI20Parameter(OpenAPIParameter):
137
138
  # is required, which is not relevant because these parameters are later constructed
138
139
  # into an "object" schema, and the value of this keyword is used there.
139
140
  # The following keywords are relevant only for non-body parameters.
140
- supported_jsonschema_keywords: ClassVar[Tuple[str, ...]] = (
141
+ supported_jsonschema_keywords: ClassVar[tuple[str, ...]] = (
141
142
  "$ref",
142
143
  "type", # only as a string
143
144
  "format",
@@ -211,7 +212,7 @@ class OpenAPI30Parameter(OpenAPIParameter):
211
212
  def is_header(self) -> bool:
212
213
  return self.location in ("header", "cookie")
213
214
 
214
- def from_open_api_to_json_schema(self, operation: APIOperation, open_api_schema: Dict[str, Any]) -> Dict[str, Any]:
215
+ def from_open_api_to_json_schema(self, operation: APIOperation, open_api_schema: dict[str, Any]) -> dict[str, Any]:
215
216
  open_api_schema = get_parameter_schema(operation, open_api_schema)
216
217
  return super().from_open_api_to_json_schema(operation, open_api_schema)
217
218
 
@@ -263,7 +264,7 @@ class OpenAPI20Body(OpenAPIBody, OpenAPI20Parameter):
263
264
  # NOTE. For Open API 2.0 bodies, we still give `x-example` precedence over the schema-level `example` field to keep
264
265
  # the precedence rules consistent.
265
266
 
266
- def as_json_schema(self, operation: APIOperation) -> Dict[str, Any]:
267
+ def as_json_schema(self, operation: APIOperation) -> dict[str, Any]:
267
268
  """Convert body definition to JSON Schema."""
268
269
  # `schema` is required in Open API 2.0 when the `in` keyword is `body`
269
270
  schema = self.definition["schema"]
@@ -290,14 +291,14 @@ class OpenAPI30Body(OpenAPIBody, OpenAPI30Parameter):
290
291
  # The `required` keyword is located above the schema for concrete media-type;
291
292
  # Therefore, it is passed here explicitly
292
293
  required: bool = False
293
- description: Optional[str] = None
294
+ description: str | None = None
294
295
 
295
- def as_json_schema(self, operation: APIOperation) -> Dict[str, Any]:
296
+ def as_json_schema(self, operation: APIOperation) -> dict[str, Any]:
296
297
  """Convert body definition to JSON Schema."""
297
298
  schema = get_media_type_schema(self.definition)
298
299
  return self.transform_keywords(schema)
299
300
 
300
- def transform_keywords(self, schema: Dict[str, Any]) -> Dict[str, Any]:
301
+ def transform_keywords(self, schema: dict[str, Any]) -> dict[str, Any]:
301
302
  definition = super().transform_keywords(schema)
302
303
  if self.is_form:
303
304
  # It significantly reduces the "filtering" part of data generation.
@@ -318,17 +319,17 @@ class OpenAPI30Body(OpenAPIBody, OpenAPI30Parameter):
318
319
  class OpenAPI20CompositeBody(OpenAPIBody, OpenAPI20Parameter):
319
320
  """A special container to abstract over multiple `formData` parameters."""
320
321
 
321
- definition: List[OpenAPI20Parameter]
322
+ definition: list[OpenAPI20Parameter]
322
323
 
323
324
  @classmethod
324
- def from_parameters(cls, *parameters: Dict[str, Any], media_type: str) -> "OpenAPI20CompositeBody":
325
+ def from_parameters(cls, *parameters: dict[str, Any], media_type: str) -> OpenAPI20CompositeBody:
325
326
  return cls(
326
327
  definition=[OpenAPI20Parameter(parameter) for parameter in parameters],
327
328
  media_type=media_type,
328
329
  )
329
330
 
330
331
  @property
331
- def description(self) -> Optional[str]:
332
+ def description(self) -> str | None:
332
333
  return None
333
334
 
334
335
  @property
@@ -344,12 +345,12 @@ class OpenAPI20CompositeBody(OpenAPIBody, OpenAPI20Parameter):
344
345
  def _schema_example(self) -> Any:
345
346
  return {parameter.name: parameter._schema_example for parameter in self.definition if parameter._schema_example}
346
347
 
347
- def as_json_schema(self, operation: APIOperation) -> Dict[str, Any]:
348
+ def as_json_schema(self, operation: APIOperation) -> dict[str, Any]:
348
349
  """The composite body is transformed into an "object" JSON Schema."""
349
350
  return parameters_to_json_schema(operation, self.definition)
350
351
 
351
352
 
352
- def parameters_to_json_schema(operation: APIOperation, parameters: Iterable[OpenAPIParameter]) -> Dict[str, Any]:
353
+ def parameters_to_json_schema(operation: APIOperation, parameters: Iterable[OpenAPIParameter]) -> dict[str, Any]:
353
354
  """Create an "object" JSON schema from a list of Open API parameters.
354
355
 
355
356
  :param List[OpenAPIParameter] parameters: A list of Open API parameters related to the same location. All of
@@ -402,7 +403,7 @@ MISSING_SCHEMA_OR_CONTENT_MESSAGE = (
402
403
  )
403
404
 
404
405
 
405
- def get_parameter_schema(operation: APIOperation, data: Dict[str, Any]) -> Dict[str, Any]:
406
+ def get_parameter_schema(operation: APIOperation, data: dict[str, Any]) -> dict[str, Any]:
406
407
  """Extract `schema` from Open API 3.0 `Parameter`."""
407
408
  # In Open API 3.0, there could be "schema" or "content" field. They are mutually exclusive.
408
409
  if "schema" in data:
@@ -423,7 +424,7 @@ def get_parameter_schema(operation: APIOperation, data: Dict[str, Any]) -> Dict[
423
424
  return get_media_type_schema(media_type_object)
424
425
 
425
426
 
426
- def get_media_type_schema(definition: Dict[str, Any]) -> Dict[str, Any]:
427
+ def get_media_type_schema(definition: dict[str, Any]) -> dict[str, Any]:
427
428
  """Extract `schema` from Open API 3.0 `MediaType`."""
428
429
  # The `schema` keyword is optional, and we treat it as the payload could be any value of the specified media type
429
430
  # Note, the main reason to have this function is to have an explicit name for the action we're doing.
@@ -1,5 +1,6 @@
1
+ from __future__ import annotations
1
2
  from functools import lru_cache
2
- from typing import Any, Callable, Dict, List, Optional, Tuple, Union, overload
3
+ from typing import Any, Callable, Dict, Union, overload
3
4
  from urllib.request import urlopen
4
5
 
5
6
  import jsonschema
@@ -16,20 +17,20 @@ from .utils import get_type
16
17
  RECURSION_DEPTH_LIMIT = 100
17
18
 
18
19
 
19
- def load_file_impl(location: str, opener: Callable) -> Dict[str, Any]:
20
+ def load_file_impl(location: str, opener: Callable) -> dict[str, Any]:
20
21
  """Load a schema from the given file."""
21
22
  with opener(location) as fd:
22
23
  return load_yaml(fd)
23
24
 
24
25
 
25
- @lru_cache()
26
- def load_file(location: str) -> Dict[str, Any]:
26
+ @lru_cache
27
+ def load_file(location: str) -> dict[str, Any]:
27
28
  """Load a schema from the given file."""
28
29
  return load_file_impl(location, open)
29
30
 
30
31
 
31
- @lru_cache()
32
- def load_file_uri(location: str) -> Dict[str, Any]:
32
+ @lru_cache
33
+ def load_file_uri(location: str) -> dict[str, Any]:
33
34
  """Load a schema from the given file uri."""
34
35
  return load_file_impl(location, urlopen)
35
36
 
@@ -53,11 +54,11 @@ class InliningResolver(jsonschema.RefResolver):
53
54
  super().__init__(*args, **kwargs)
54
55
 
55
56
  @overload
56
- def resolve_all(self, item: Dict[str, Any], recursion_level: int = 0) -> Dict[str, Any]:
57
+ def resolve_all(self, item: dict[str, Any], recursion_level: int = 0) -> dict[str, Any]:
57
58
  pass
58
59
 
59
60
  @overload
60
- def resolve_all(self, item: List, recursion_level: int = 0) -> List:
61
+ def resolve_all(self, item: list, recursion_level: int = 0) -> list:
61
62
  pass
62
63
 
63
64
  def resolve_all(self, item: JSONType, recursion_level: int = 0) -> JSONType:
@@ -79,7 +80,7 @@ class InliningResolver(jsonschema.RefResolver):
79
80
  return [self.resolve_all(sub_item, recursion_level) for sub_item in item]
80
81
  return item
81
82
 
82
- def resolve_in_scope(self, definition: Dict[str, Any], scope: str) -> Tuple[List[str], Dict[str, Any]]:
83
+ def resolve_in_scope(self, definition: dict[str, Any], scope: str) -> tuple[list[str], dict[str, Any]]:
83
84
  scopes = [scope]
84
85
  # if there is `$ref` then we have a scope change that should be used during validation later to
85
86
  # resolve nested references correctly
@@ -105,7 +106,7 @@ class ConvertingResolver(InliningResolver):
105
106
  self.nullable_name = nullable_name
106
107
  self.is_response_schema = is_response_schema
107
108
 
108
- def resolve(self, ref: str) -> Tuple[str, Any]:
109
+ def resolve(self, ref: str) -> tuple[str, Any]:
109
110
  url, document = super().resolve(ref)
110
111
  document = to_json_schema_recursive(
111
112
  document, nullable_name=self.nullable_name, is_response_schema=self.is_response_schema
@@ -113,14 +114,14 @@ class ConvertingResolver(InliningResolver):
113
114
  return url, document
114
115
 
115
116
 
116
- def remove_optional_references(schema: Dict[str, Any]) -> None:
117
+ def remove_optional_references(schema: dict[str, Any]) -> None:
117
118
  """Remove optional parts of the schema that contain references.
118
119
 
119
120
  It covers only the most popular cases, as removing all optional parts is complicated.
120
121
  We might fall back to filtering out invalid cases in the future.
121
122
  """
122
123
 
123
- def clean_properties(s: Dict[str, Any]) -> None:
124
+ def clean_properties(s: dict[str, Any]) -> None:
124
125
  properties = s["properties"]
125
126
  required = s.get("required", [])
126
127
  for name, value in list(properties.items()):
@@ -132,7 +133,7 @@ def remove_optional_references(schema: Dict[str, Any]) -> None:
132
133
  else:
133
134
  stack.append(value)
134
135
 
135
- def clean_items(s: Dict[str, Any]) -> None:
136
+ def clean_items(s: dict[str, Any]) -> None:
136
137
  items = s["items"]
137
138
  min_items = s.get("minItems", 0)
138
139
  if not min_items:
@@ -141,25 +142,25 @@ def remove_optional_references(schema: Dict[str, Any]) -> None:
141
142
  if isinstance(items, list) and any_ref(items):
142
143
  force_empty_list(s)
143
144
 
144
- def clean_additional_properties(s: Dict[str, Any]) -> None:
145
+ def clean_additional_properties(s: dict[str, Any]) -> None:
145
146
  additional_properties = s["additionalProperties"]
146
147
  if isinstance(additional_properties, dict) and "$ref" in additional_properties:
147
148
  s["additionalProperties"] = False
148
149
 
149
- def force_empty_list(s: Dict[str, Any]) -> None:
150
+ def force_empty_list(s: dict[str, Any]) -> None:
150
151
  del s["items"]
151
152
  s["maxItems"] = 0
152
153
 
153
- def any_ref(i: List[Dict[str, Any]]) -> bool:
154
+ def any_ref(i: list[dict[str, Any]]) -> bool:
154
155
  return any("$ref" in item for item in i)
155
156
 
156
- def contains_ref(s: Dict[str, Any]) -> bool:
157
+ def contains_ref(s: dict[str, Any]) -> bool:
157
158
  if "$ref" in s:
158
159
  return True
159
160
  i = s.get("items")
160
161
  return (isinstance(i, dict) and "$ref" in i) or isinstance(i, list) and any_ref(i)
161
162
 
162
- def can_elide(s: Dict[str, Any]) -> bool:
163
+ def can_elide(s: dict[str, Any]) -> bool:
163
164
  # Whether this schema could be dropped from a list of schemas
164
165
  type_ = get_type(s)
165
166
  if type_ == ["object"]:
@@ -168,7 +169,7 @@ def remove_optional_references(schema: Dict[str, Any]) -> None:
168
169
  # Has at least one keyword -> should not be removed
169
170
  return not any(k in ALL_KEYWORDS for k in s)
170
171
 
171
- def on_single_item_combinators(s: Dict[str, Any]) -> List[str]:
172
+ def on_single_item_combinators(s: dict[str, Any]) -> list[str]:
172
173
  # Schema example:
173
174
  # {
174
175
  # "type": "object",
@@ -203,7 +204,7 @@ def remove_optional_references(schema: Dict[str, Any]) -> None:
203
204
  del definition[k]
204
205
 
205
206
 
206
- def resolve_pointer(document: Any, pointer: str) -> Optional[Union[Dict, List, str, int, float]]:
207
+ def resolve_pointer(document: Any, pointer: str) -> dict | list | str | int | float | None:
207
208
  """Implementation is adapted from Rust's `serde-json` crate.
208
209
 
209
210
  Ref: https://github.com/serde-rs/json/blob/master/src/value/mod.rs#L751