schemathesis 3.19.0__py3-none-any.whl → 3.19.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 (52) hide show
  1. schemathesis/auths.py +20 -20
  2. schemathesis/cli/__init__.py +20 -20
  3. schemathesis/cli/cassettes.py +18 -18
  4. schemathesis/cli/context.py +25 -25
  5. schemathesis/cli/debug.py +3 -3
  6. schemathesis/cli/junitxml.py +4 -4
  7. schemathesis/constants.py +3 -3
  8. schemathesis/exceptions.py +9 -9
  9. schemathesis/extra/pytest_plugin.py +1 -1
  10. schemathesis/failures.py +65 -66
  11. schemathesis/filters.py +13 -13
  12. schemathesis/hooks.py +11 -11
  13. schemathesis/lazy.py +16 -16
  14. schemathesis/models.py +97 -97
  15. schemathesis/parameters.py +5 -6
  16. schemathesis/runner/events.py +55 -55
  17. schemathesis/runner/impl/core.py +26 -26
  18. schemathesis/runner/impl/solo.py +6 -7
  19. schemathesis/runner/impl/threadpool.py +5 -5
  20. schemathesis/runner/serialization.py +50 -50
  21. schemathesis/schemas.py +23 -23
  22. schemathesis/serializers.py +3 -3
  23. schemathesis/service/ci.py +25 -25
  24. schemathesis/service/client.py +2 -2
  25. schemathesis/service/events.py +12 -13
  26. schemathesis/service/hosts.py +4 -4
  27. schemathesis/service/metadata.py +14 -15
  28. schemathesis/service/models.py +12 -13
  29. schemathesis/service/report.py +30 -31
  30. schemathesis/service/serialization.py +2 -4
  31. schemathesis/specs/graphql/schemas.py +8 -8
  32. schemathesis/specs/openapi/expressions/context.py +4 -4
  33. schemathesis/specs/openapi/expressions/lexer.py +11 -12
  34. schemathesis/specs/openapi/expressions/nodes.py +16 -16
  35. schemathesis/specs/openapi/expressions/parser.py +1 -1
  36. schemathesis/specs/openapi/links.py +15 -17
  37. schemathesis/specs/openapi/negative/__init__.py +5 -5
  38. schemathesis/specs/openapi/negative/mutations.py +6 -6
  39. schemathesis/specs/openapi/parameters.py +12 -13
  40. schemathesis/specs/openapi/references.py +2 -2
  41. schemathesis/specs/openapi/schemas.py +11 -15
  42. schemathesis/specs/openapi/security.py +7 -7
  43. schemathesis/specs/openapi/stateful/links.py +4 -4
  44. schemathesis/stateful.py +19 -19
  45. schemathesis/targets.py +5 -6
  46. schemathesis/types.py +11 -13
  47. schemathesis/utils.py +2 -2
  48. {schemathesis-3.19.0.dist-info → schemathesis-3.19.1.dist-info}/METADATA +2 -3
  49. {schemathesis-3.19.0.dist-info → schemathesis-3.19.1.dist-info}/RECORD +52 -52
  50. {schemathesis-3.19.0.dist-info → schemathesis-3.19.1.dist-info}/WHEEL +0 -0
  51. {schemathesis-3.19.0.dist-info → schemathesis-3.19.1.dist-info}/entry_points.txt +0 -0
  52. {schemathesis-3.19.0.dist-info → schemathesis-3.19.1.dist-info}/licenses/LICENSE +0 -0
schemathesis/auths.py CHANGED
@@ -1,9 +1,9 @@
1
1
  """Support for custom API authentication mechanisms."""
2
2
  import threading
3
3
  import time
4
+ from dataclasses import dataclass, field
4
5
  from typing import TYPE_CHECKING, Any, Callable, Generic, List, Optional, Type, TypeVar, Union
5
6
 
6
- import attr
7
7
  import requests.auth
8
8
  from typing_extensions import Protocol, runtime_checkable
9
9
 
@@ -19,7 +19,7 @@ AUTH_STORAGE_ATTRIBUTE_NAME = "_schemathesis_auth"
19
19
  Auth = TypeVar("Auth")
20
20
 
21
21
 
22
- @attr.s(slots=True) # pragma: no mutate
22
+ @dataclass
23
23
  class AuthContext:
24
24
  """Holds state relevant for the authentication process.
25
25
 
@@ -27,8 +27,8 @@ class AuthContext:
27
27
  :ivar app: Optional Python application if the WSGI / ASGI integration is used.
28
28
  """
29
29
 
30
- operation: "APIOperation" = attr.ib() # pragma: no mutate
31
- app: Optional[Any] = attr.ib() # pragma: no mutate
30
+ operation: "APIOperation"
31
+ app: Optional[Any]
32
32
 
33
33
 
34
34
  @runtime_checkable
@@ -51,19 +51,19 @@ class AuthProvider(Protocol):
51
51
  """
52
52
 
53
53
 
54
- @attr.s(slots=True)
54
+ @dataclass
55
55
  class CacheEntry(Generic[Auth]):
56
56
  """Cached auth data."""
57
57
 
58
- data: Auth = attr.ib()
59
- expires: float = attr.ib()
58
+ data: Auth
59
+ expires: float
60
60
 
61
61
 
62
- @attr.s(slots=True)
62
+ @dataclass
63
63
  class RequestsAuth(Generic[Auth]):
64
64
  """Provider that sets auth data via `requests` auth instance."""
65
65
 
66
- auth: requests.auth.AuthBase = attr.ib()
66
+ auth: requests.auth.AuthBase
67
67
 
68
68
  def get(self, _: AuthContext) -> Optional[Auth]:
69
69
  return self.auth # type: ignore[return-value]
@@ -72,16 +72,16 @@ class RequestsAuth(Generic[Auth]):
72
72
  case._auth = self.auth
73
73
 
74
74
 
75
- @attr.s(slots=True)
75
+ @dataclass
76
76
  class CachingAuthProvider(Generic[Auth]):
77
77
  """Caches the underlying auth provider."""
78
78
 
79
- provider: AuthProvider = attr.ib()
80
- refresh_interval: int = attr.ib(default=DEFAULT_REFRESH_INTERVAL)
81
- cache_entry: Optional[CacheEntry[Auth]] = attr.ib(default=None)
79
+ provider: AuthProvider
80
+ refresh_interval: int = DEFAULT_REFRESH_INTERVAL
81
+ cache_entry: Optional[CacheEntry[Auth]] = None
82
82
  # The timer exists here to simplify testing
83
- timer: Callable[[], float] = attr.ib(default=time.monotonic)
84
- _refresh_lock: threading.Lock = attr.ib(factory=threading.Lock)
83
+ timer: Callable[[], float] = time.monotonic
84
+ _refresh_lock: threading.Lock = field(default_factory=threading.Lock)
85
85
 
86
86
  def get(self, context: AuthContext) -> Optional[Auth]:
87
87
  """Get cached auth value."""
@@ -200,12 +200,12 @@ class FilterableRequestsAuth(Protocol):
200
200
  pass
201
201
 
202
202
 
203
- @attr.s(slots=True)
203
+ @dataclass
204
204
  class SelectiveAuthProvider(Generic[Auth]):
205
205
  """Applies auth depending on the configured filters."""
206
206
 
207
- provider: AuthProvider = attr.ib()
208
- filter_set: FilterSet = attr.ib()
207
+ provider: AuthProvider
208
+ filter_set: FilterSet
209
209
 
210
210
  def get(self, context: AuthContext) -> Optional[Auth]:
211
211
  if self.filter_set.match(context):
@@ -216,11 +216,11 @@ class SelectiveAuthProvider(Generic[Auth]):
216
216
  self.provider.set(case, data, context)
217
217
 
218
218
 
219
- @attr.s(slots=True)
219
+ @dataclass
220
220
  class AuthStorage(Generic[Auth]):
221
221
  """Store and manage API authentication."""
222
222
 
223
- providers: List[AuthProvider] = attr.ib(factory=list)
223
+ providers: List[AuthProvider] = field(default_factory=list)
224
224
 
225
225
  @property
226
226
  def is_defined(self) -> bool:
@@ -4,12 +4,12 @@ import os
4
4
  import sys
5
5
  import traceback
6
6
  from collections import defaultdict
7
+ from dataclasses import dataclass
7
8
  from enum import Enum
8
9
  from queue import Queue
9
10
  from typing import Any, Callable, Dict, Generator, Iterable, List, NoReturn, Optional, Tuple, Union, cast
10
11
  from urllib.parse import urlparse
11
12
 
12
- import attr
13
13
  import click
14
14
  import hypothesis
15
15
  import requests
@@ -799,33 +799,33 @@ def prepare_request_cert(cert: Optional[str], key: Optional[str]) -> Optional[Re
799
799
  return cert
800
800
 
801
801
 
802
- @attr.s(slots=True)
802
+ @dataclass
803
803
  class LoaderConfig:
804
804
  """Container for API loader parameters.
805
805
 
806
806
  The main goal is to avoid too many parameters in function signatures.
807
807
  """
808
808
 
809
- schema_location: str = attr.ib() # pragma: no mutate
810
- app: Any = attr.ib() # pragma: no mutate
811
- base_url: Optional[str] = attr.ib() # pragma: no mutate
812
- validate_schema: bool = attr.ib() # pragma: no mutate
813
- skip_deprecated_operations: bool = attr.ib() # pragma: no mutate
814
- data_generation_methods: Tuple[DataGenerationMethod, ...] = attr.ib() # pragma: no mutate
815
- force_schema_version: Optional[str] = attr.ib() # pragma: no mutate
816
- request_tls_verify: Union[bool, str] = attr.ib() # pragma: no mutate
817
- request_cert: Optional[RequestCert] = attr.ib() # pragma: no mutate
818
- wait_for_schema: Optional[float] = attr.ib() # pragma: no mutate
819
- rate_limit: Optional[str] = attr.ib()
809
+ schema_location: str
810
+ app: Any
811
+ base_url: Optional[str]
812
+ validate_schema: bool
813
+ skip_deprecated_operations: bool
814
+ data_generation_methods: Tuple[DataGenerationMethod, ...]
815
+ force_schema_version: Optional[str]
816
+ request_tls_verify: Union[bool, str]
817
+ request_cert: Optional[RequestCert]
818
+ wait_for_schema: Optional[float]
819
+ rate_limit: Optional[str]
820
820
  # Network request parameters
821
- auth: Optional[Tuple[str, str]] = attr.ib() # pragma: no mutate
822
- auth_type: Optional[str] = attr.ib() # pragma: no mutate
823
- headers: Optional[Dict[str, str]] = attr.ib() # pragma: no mutate
821
+ auth: Optional[Tuple[str, str]]
822
+ auth_type: Optional[str]
823
+ headers: Optional[Dict[str, str]]
824
824
  # Schema filters
825
- endpoint: Optional[Filter] = attr.ib() # pragma: no mutate
826
- method: Optional[Filter] = attr.ib() # pragma: no mutate
827
- tag: Optional[Filter] = attr.ib() # pragma: no mutate
828
- operation_id: Optional[Filter] = attr.ib() # pragma: no mutate
825
+ endpoint: Optional[Filter]
826
+ method: Optional[Filter]
827
+ tag: Optional[Filter]
828
+ operation_id: Optional[Filter]
829
829
 
830
830
 
831
831
  def into_event_stream(
@@ -3,10 +3,10 @@ import json
3
3
  import re
4
4
  import sys
5
5
  import threading
6
+ from dataclasses import dataclass, field
6
7
  from queue import Queue
7
8
  from typing import IO, Any, Dict, Generator, Iterator, List, Optional, cast
8
9
 
9
- import attr
10
10
  import click
11
11
  import requests
12
12
  from requests.cookies import RequestsCookieJar
@@ -25,7 +25,7 @@ from .handlers import EventHandler
25
25
  WRITER_WORKER_JOIN_TIMEOUT = 1
26
26
 
27
27
 
28
- @attr.s(slots=True) # pragma: no mutate
28
+ @dataclass
29
29
  class CassetteWriter(EventHandler):
30
30
  """Write interactions in a YAML cassette.
31
31
 
@@ -33,12 +33,12 @@ class CassetteWriter(EventHandler):
33
33
  the end of the test run.
34
34
  """
35
35
 
36
- file_handle: click.utils.LazyFile = attr.ib() # pragma: no mutate
37
- preserve_exact_body_bytes: bool = attr.ib() # pragma: no mutate
38
- queue: Queue = attr.ib(factory=Queue) # pragma: no mutate
39
- worker: threading.Thread = attr.ib(init=False) # pragma: no mutate
36
+ file_handle: click.utils.LazyFile
37
+ preserve_exact_body_bytes: bool
38
+ queue: Queue = field(default_factory=Queue)
39
+ worker: threading.Thread = field(init=False)
40
40
 
41
- def __attrs_post_init__(self) -> None:
41
+ def __post_init__(self) -> None:
42
42
  self.worker = threading.Thread(
43
43
  target=worker,
44
44
  kwargs={
@@ -80,23 +80,23 @@ class CassetteWriter(EventHandler):
80
80
  self.worker.join(WRITER_WORKER_JOIN_TIMEOUT)
81
81
 
82
82
 
83
- @attr.s(slots=True) # pragma: no mutate
83
+ @dataclass
84
84
  class Initialize:
85
85
  """Start up, the first message to make preparations before proceeding the input data."""
86
86
 
87
87
 
88
- @attr.s(slots=True) # pragma: no mutate
88
+ @dataclass
89
89
  class Process:
90
90
  """A new chunk of data should be processed."""
91
91
 
92
- seed: int = attr.ib() # pragma: no mutate
93
- correlation_id: str = attr.ib() # pragma: no mutate
94
- thread_id: int = attr.ib() # pragma: no mutate
95
- data_generation_method: constants.DataGenerationMethod = attr.ib() # pragma: no mutate
96
- interactions: List[SerializedInteraction] = attr.ib() # pragma: no mutate
92
+ seed: int
93
+ correlation_id: str
94
+ thread_id: int
95
+ data_generation_method: constants.DataGenerationMethod
96
+ interactions: List[SerializedInteraction]
97
97
 
98
98
 
99
- @attr.s(slots=True) # pragma: no mutate
99
+ @dataclass
100
100
  class Finalize:
101
101
  """The work is done and there will be no more messages to process."""
102
102
 
@@ -274,10 +274,10 @@ def write_double_quoted(stream: IO, text: str) -> None:
274
274
  stream.write('"')
275
275
 
276
276
 
277
- @attr.s(slots=True) # pragma: no mutate
277
+ @dataclass
278
278
  class Replayed:
279
- interaction: Dict[str, Any] = attr.ib() # pragma: no mutate
280
- response: requests.Response = attr.ib() # pragma: no mutate
279
+ interaction: Dict[str, Any]
280
+ response: requests.Response
281
281
 
282
282
 
283
283
  def replay(
@@ -1,46 +1,46 @@
1
1
  import os
2
2
  import shutil
3
+ from dataclasses import dataclass, field
3
4
  from queue import Queue
4
5
  from typing import List, Optional, Union
5
6
 
6
- import attr
7
7
  import hypothesis
8
8
 
9
9
  from ..constants import CodeSampleStyle
10
10
  from ..runner.serialization import SerializedTestResult
11
11
 
12
12
 
13
- @attr.s(slots=True) # pragma: no mutate
13
+ @dataclass
14
14
  class ServiceReportContext:
15
- queue: Queue = attr.ib() # pragma: no mutate
16
- service_base_url: str = attr.ib() # pragma: no mutate
15
+ queue: Queue
16
+ service_base_url: str
17
17
 
18
18
 
19
- @attr.s(slots=True) # pragma: no mutate
19
+ @dataclass
20
20
  class FileReportContext:
21
- queue: Queue = attr.ib() # pragma: no mutate
22
- filename: str = attr.ib(default=None) # pragma: no mutate
21
+ queue: Queue
22
+ filename: Optional[str] = None
23
23
 
24
24
 
25
- @attr.s(slots=True) # pragma: no mutate
25
+ @dataclass
26
26
  class ExecutionContext:
27
27
  """Storage for the current context of the execution."""
28
28
 
29
- hypothesis_settings: hypothesis.settings = attr.ib() # pragma: no mutate
30
- hypothesis_output: List[str] = attr.ib(factory=list) # pragma: no mutate
31
- workers_num: int = attr.ib(default=1) # pragma: no mutate
32
- rate_limit: Optional[str] = attr.ib(default=None) # pragma: no mutate
33
- show_errors_tracebacks: bool = attr.ib(default=False) # pragma: no mutate
34
- validate_schema: bool = attr.ib(default=True) # pragma: no mutate
35
- operations_processed: int = attr.ib(default=0) # pragma: no mutate
29
+ hypothesis_settings: hypothesis.settings
30
+ hypothesis_output: List[str] = field(default_factory=list)
31
+ workers_num: int = 1
32
+ rate_limit: Optional[str] = None
33
+ show_errors_tracebacks: bool = False
34
+ validate_schema: bool = True
35
+ operations_processed: int = 0
36
36
  # It is set in runtime, from a `Initialized` event
37
- operations_count: Optional[int] = attr.ib(default=None) # pragma: no mutate
38
- current_line_length: int = attr.ib(default=0) # pragma: no mutate
39
- terminal_size: os.terminal_size = attr.ib(factory=shutil.get_terminal_size) # pragma: no mutate
40
- results: List[SerializedTestResult] = attr.ib(factory=list) # pragma: no mutate
41
- cassette_path: Optional[str] = attr.ib(default=None) # pragma: no mutate
42
- junit_xml_file: Optional[str] = attr.ib(default=None) # pragma: no mutate
43
- is_interrupted: bool = attr.ib(default=False) # pragma: no mutate
44
- verbosity: int = attr.ib(default=0) # pragma: no mutate
45
- code_sample_style: CodeSampleStyle = attr.ib(default=CodeSampleStyle.default()) # pragma: no mutate
46
- report: Optional[Union[ServiceReportContext, FileReportContext]] = attr.ib(default=None) # pragma: no mutate
37
+ operations_count: Optional[int] = None
38
+ current_line_length: int = 0
39
+ terminal_size: os.terminal_size = field(default_factory=shutil.get_terminal_size)
40
+ results: List[SerializedTestResult] = field(default_factory=list)
41
+ cassette_path: Optional[str] = None
42
+ junit_xml_file: Optional[str] = None
43
+ is_interrupted: bool = False
44
+ verbosity: int = 0
45
+ code_sample_style: CodeSampleStyle = CodeSampleStyle.default()
46
+ report: Optional[Union[ServiceReportContext, FileReportContext]] = None
schemathesis/cli/debug.py CHANGED
@@ -1,15 +1,15 @@
1
1
  import json
2
+ from dataclasses import dataclass
2
3
 
3
- import attr
4
4
  from click.utils import LazyFile
5
5
 
6
6
  from ..runner import events
7
7
  from .handlers import EventHandler, ExecutionContext
8
8
 
9
9
 
10
- @attr.s(slots=True) # pragma: no mutate
10
+ @dataclass
11
11
  class DebugOutputHandler(EventHandler):
12
- file_handle: LazyFile = attr.ib() # pragma: no mutate
12
+ file_handle: LazyFile
13
13
 
14
14
  def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
15
15
  stream = self.file_handle.open()
@@ -1,7 +1,7 @@
1
1
  import platform
2
+ from dataclasses import dataclass, field
2
3
  from typing import List
3
4
 
4
- import attr
5
5
  from click.utils import LazyFile
6
6
  from junit_xml import TestCase, TestSuite, to_xml_report_file
7
7
 
@@ -11,10 +11,10 @@ from ..runner.serialization import deduplicate_failures
11
11
  from .handlers import EventHandler, ExecutionContext
12
12
 
13
13
 
14
- @attr.s(slots=True) # pragma: no mutate
14
+ @dataclass
15
15
  class JunitXMLHandler(EventHandler):
16
- file_handle: LazyFile = attr.ib() # pragma: no mutate
17
- test_cases: List = attr.ib(factory=list) # pragma: no mutate
16
+ file_handle: LazyFile
17
+ test_cases: List = field(default_factory=list)
18
18
 
19
19
  def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
20
20
  if isinstance(event, events.AfterExecution):
schemathesis/constants.py CHANGED
@@ -20,9 +20,9 @@ SCHEMATHESIS_TEST_CASE_HEADER = "X-Schemathesis-TestCaseId"
20
20
  HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER = ":memory:"
21
21
  DISCORD_LINK = "https://discord.gg/R9ASRAmHnA"
22
22
  # Maximum test running time
23
- DEFAULT_DEADLINE = 15000 # pragma: no mutate
24
- DEFAULT_RESPONSE_TIMEOUT = 10000 # pragma: no mutate
25
- DEFAULT_STATEFUL_RECURSION_LIMIT = 5 # pragma: no mutate
23
+ DEFAULT_DEADLINE = 15000
24
+ DEFAULT_RESPONSE_TIMEOUT = 10000
25
+ DEFAULT_STATEFUL_RECURSION_LIMIT = 5
26
26
  HTTP_METHODS = frozenset({"get", "put", "post", "delete", "options", "head", "patch", "trace"})
27
27
  RECURSIVE_REFERENCE_ERROR_MESSAGE = (
28
28
  "Currently, Schemathesis can't generate data for this operation due to "
@@ -1,8 +1,8 @@
1
+ from dataclasses import dataclass
1
2
  from hashlib import sha1
2
3
  from json import JSONDecodeError
3
4
  from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, NoReturn, Optional, Tuple, Type, Union
4
5
 
5
- import attr
6
6
  import hypothesis.errors
7
7
  import requests
8
8
  from jsonschema import ValidationError
@@ -139,15 +139,15 @@ def get_timeout_error(deadline: Union[float, int]) -> Type[CheckFailed]:
139
139
  return _get_hashed_exception("TimeoutError", str(deadline))
140
140
 
141
141
 
142
- @attr.s(slots=True)
142
+ @dataclass
143
143
  class InvalidSchema(Exception):
144
144
  """Schema associated with an API operation contains an error."""
145
145
 
146
146
  __module__ = "builtins"
147
- message: Optional[str] = attr.ib(default=None)
148
- path: Optional[str] = attr.ib(default=None)
149
- method: Optional[str] = attr.ib(default=None)
150
- full_path: Optional[str] = attr.ib(default=None)
147
+ message: Optional[str] = None
148
+ path: Optional[str] = None
149
+ method: Optional[str] = None
150
+ full_path: Optional[str] = None
151
151
 
152
152
  def as_failing_test_function(self) -> Callable:
153
153
  """Create a test function that will fail.
@@ -233,10 +233,10 @@ class InvalidRegularExpression(Exception):
233
233
  __module__ = "builtins"
234
234
 
235
235
 
236
- @attr.s # pragma: no mutate
236
+ @dataclass
237
237
  class HTTPError(Exception):
238
- response: "GenericResponse" = attr.ib() # pragma: no mutate
239
- url: str = attr.ib() # pragma: no mutate
238
+ response: "GenericResponse"
239
+ url: str
240
240
 
241
241
  @classmethod
242
242
  def raise_for_status(cls, response: requests.Response) -> None:
@@ -222,7 +222,7 @@ def skip_unnecessary_hypothesis_output() -> Generator:
222
222
  yield
223
223
 
224
224
 
225
- @hookimpl(hookwrapper=True) # pragma: no mutate
225
+ @hookimpl(hookwrapper=True)
226
226
  def pytest_pyfunc_call(pyfuncitem): # type:ignore
227
227
  """It is possible to have a Hypothesis exception in runtime.
228
228
 
schemathesis/failures.py CHANGED
@@ -1,9 +1,8 @@
1
+ from dataclasses import dataclass
1
2
  from typing import Any, Dict, List, Optional, Tuple, Union
2
3
 
3
- import attr
4
4
 
5
-
6
- @attr.s(slots=True, repr=False) # pragma: no mutate
5
+ # @dataclass(repr=False)
7
6
  class FailureContext:
8
7
  """Additional data specific to certain failure kind."""
9
8
 
@@ -18,36 +17,36 @@ class FailureContext:
18
17
  return (check_message or self.message,)
19
18
 
20
19
 
21
- @attr.s(slots=True, repr=False)
20
+ @dataclass(repr=False)
22
21
  class ValidationErrorContext(FailureContext):
23
22
  """Additional information about JSON Schema validation errors."""
24
23
 
25
- validation_message: str = attr.ib()
26
- schema_path: List[Union[str, int]] = attr.ib()
27
- schema: Union[Dict[str, Any], bool] = attr.ib()
28
- instance_path: List[Union[str, int]] = attr.ib()
29
- instance: Union[None, bool, float, str, list, Dict[str, Any]] = attr.ib()
30
- title: str = attr.ib(default="Non-conforming response payload")
31
- message: str = attr.ib(default="Response does not conform to the defined schema")
32
- type: str = attr.ib(default="json_schema")
24
+ validation_message: str
25
+ schema_path: List[Union[str, int]]
26
+ schema: Union[Dict[str, Any], bool]
27
+ instance_path: List[Union[str, int]]
28
+ instance: Union[None, bool, float, str, list, Dict[str, Any]]
29
+ title: str = "Non-conforming response payload"
30
+ message: str = "Response does not conform to the defined schema"
31
+ type: str = "json_schema"
33
32
 
34
33
  def unique_by_key(self, check_message: Optional[str]) -> Tuple[str, ...]:
35
34
  # Deduplicate by JSON Schema path. All errors that happened on this sub-schema will be deduplicated
36
35
  return ("/".join(map(str, self.schema_path)),)
37
36
 
38
37
 
39
- @attr.s(slots=True, repr=False)
38
+ @dataclass(repr=False)
40
39
  class JSONDecodeErrorContext(FailureContext):
41
40
  """Failed to decode JSON."""
42
41
 
43
- validation_message: str = attr.ib()
44
- document: str = attr.ib()
45
- position: int = attr.ib()
46
- lineno: int = attr.ib()
47
- colno: int = attr.ib()
48
- title: str = attr.ib(default="JSON deserialization error")
49
- message: str = attr.ib(default="Response is not a valid JSON")
50
- type: str = attr.ib(default="json_decode")
42
+ validation_message: str
43
+ document: str
44
+ position: int
45
+ lineno: int
46
+ colno: int
47
+ title: str = "JSON deserialization error"
48
+ message: str = "Response is not a valid JSON"
49
+ type: str = "json_decode"
51
50
 
52
51
  def unique_by_key(self, check_message: Optional[str]) -> Tuple[str, ...]:
53
52
  # Treat different JSON decoding failures as the same issue
@@ -56,90 +55,90 @@ class JSONDecodeErrorContext(FailureContext):
56
55
  return (self.title,)
57
56
 
58
57
 
59
- @attr.s(slots=True, repr=False)
58
+ @dataclass(repr=False)
60
59
  class ServerError(FailureContext):
61
- status_code: int = attr.ib()
62
- title: str = attr.ib(default="Internal server error")
63
- message: str = attr.ib(default="Server got itself in trouble")
64
- type: str = attr.ib(default="server_error")
60
+ status_code: int
61
+ title: str = "Internal server error"
62
+ message: str = "Server got itself in trouble"
63
+ type: str = "server_error"
65
64
 
66
65
 
67
- @attr.s(slots=True, repr=False)
66
+ @dataclass(repr=False)
68
67
  class MissingContentType(FailureContext):
69
68
  """Content type header is missing."""
70
69
 
71
- media_types: List[str] = attr.ib()
72
- title: str = attr.ib(default="Missing Content-Type header")
73
- message: str = attr.ib(default="Response is missing the `Content-Type` header")
74
- type: str = attr.ib(default="missing_content_type")
70
+ media_types: List[str]
71
+ title: str = "Missing Content-Type header"
72
+ message: str = "Response is missing the `Content-Type` header"
73
+ type: str = "missing_content_type"
75
74
 
76
75
 
77
- @attr.s(slots=True, repr=False)
76
+ @dataclass(repr=False)
78
77
  class UndefinedContentType(FailureContext):
79
78
  """Response has Content-Type that is not defined in the schema."""
80
79
 
81
- content_type: str = attr.ib()
82
- defined_content_types: List[str] = attr.ib()
83
- title: str = attr.ib(default="Undefined Content-Type")
84
- message: str = attr.ib(default="Response has `Content-Type` that is not declared in the schema")
85
- type: str = attr.ib(default="undefined_content_type")
80
+ content_type: str
81
+ defined_content_types: List[str]
82
+ title: str = "Undefined Content-Type"
83
+ message: str = "Response has `Content-Type` that is not declared in the schema"
84
+ type: str = "undefined_content_type"
86
85
 
87
86
 
88
- @attr.s(slots=True, repr=False)
87
+ @dataclass(repr=False)
89
88
  class UndefinedStatusCode(FailureContext):
90
89
  """Response has a status code that is not defined in the schema."""
91
90
 
92
91
  # Response's status code
93
- status_code: int = attr.ib()
92
+ status_code: int
94
93
  # Status codes as defined in schema
95
- defined_status_codes: List[str] = attr.ib()
94
+ defined_status_codes: List[str]
96
95
  # Defined status code with expanded wildcards
97
- allowed_status_codes: List[int] = attr.ib()
98
- title: str = attr.ib(default="Undefined status code")
99
- message: str = attr.ib(default="Response has a status code that is not declared in the schema")
100
- type: str = attr.ib(default="undefined_status_code")
96
+ allowed_status_codes: List[int]
97
+ title: str = "Undefined status code"
98
+ message: str = "Response has a status code that is not declared in the schema"
99
+ type: str = "undefined_status_code"
101
100
 
102
101
 
103
- @attr.s(slots=True, repr=False)
102
+ @dataclass(repr=False)
104
103
  class MissingHeaders(FailureContext):
105
104
  """Some required headers are missing."""
106
105
 
107
- missing_headers: List[str] = attr.ib()
108
- title: str = attr.ib(default="Missing required headers")
109
- message: str = attr.ib(default="Response is missing headers required by the schema")
110
- type: str = attr.ib(default="missing_headers")
106
+ missing_headers: List[str]
107
+ title: str = "Missing required headers"
108
+ message: str = "Response is missing headers required by the schema"
109
+ type: str = "missing_headers"
111
110
 
112
111
 
113
- @attr.s(slots=True, repr=False)
112
+ @dataclass(repr=False)
114
113
  class MalformedMediaType(FailureContext):
115
114
  """Media type name is malformed.
116
115
 
117
116
  Example: `application-json` instead of `application/json`
118
117
  """
119
118
 
120
- actual: str = attr.ib()
121
- defined: str = attr.ib()
122
- title: str = attr.ib(default="Malformed media type name")
123
- message: str = attr.ib(default="Media type name is not valid")
124
- type: str = attr.ib(default="malformed_media_type")
119
+ actual: str
120
+ defined: str
121
+ title: str = "Malformed media type name"
122
+ message: str = "Media type name is not valid"
123
+ type: str = "malformed_media_type"
125
124
 
126
125
 
127
- @attr.s(slots=True, repr=False)
126
+ @dataclass(repr=False)
128
127
  class ResponseTimeExceeded(FailureContext):
129
128
  """Response took longer than expected."""
130
129
 
131
- elapsed: float = attr.ib()
132
- deadline: int = attr.ib()
133
- title: str = attr.ib(default="Response time exceeded")
134
- message: str = attr.ib(default="Response time exceeds the deadline")
135
- type: str = attr.ib(default="response_time_exceeded")
130
+ elapsed: float
131
+ deadline: int
132
+ title: str = "Response time exceeded"
133
+ message: str = "Response time exceeds the deadline"
134
+ type: str = "response_time_exceeded"
136
135
 
137
136
 
138
- @attr.s(slots=True, repr=False)
137
+ @dataclass(repr=False)
139
138
  class RequestTimeout(FailureContext):
140
139
  """Request took longer than timeout."""
141
140
 
142
- timeout: int = attr.ib()
143
- title: str = attr.ib(default="Request timeout")
144
- message: str = attr.ib(default="The request timed out")
145
- type: str = attr.ib(default="request_timeout")
141
+ timeout: int
142
+ title: str = "Request timeout"
143
+ message: str = "The request timed out"
144
+ type: str = "request_timeout"