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
@@ -1,8 +1,8 @@
1
1
  import threading
2
2
  import time
3
+ from dataclasses import asdict, dataclass, field
3
4
  from typing import Any, Dict, List, Optional, Union
4
5
 
5
- import attr
6
6
  from requests import exceptions
7
7
 
8
8
  from ..constants import USE_WAIT_FOR_SCHEMA_SUGGESTION_MESSAGE, DataGenerationMethod
@@ -13,7 +13,7 @@ from ..utils import current_datetime, format_exception
13
13
  from .serialization import SerializedError, SerializedTestResult
14
14
 
15
15
 
16
- @attr.s() # pragma: no mutate
16
+ @dataclass
17
17
  class ExecutionEvent:
18
18
  """Generic execution event."""
19
19
 
@@ -21,31 +21,31 @@ class ExecutionEvent:
21
21
  is_terminal = False
22
22
 
23
23
  def asdict(self, **kwargs: Any) -> Dict[str, Any]:
24
- data = attr.asdict(self, **kwargs)
24
+ data = asdict(self, **kwargs)
25
25
  # An internal tag for simpler type identification
26
26
  data["event_type"] = self.__class__.__name__
27
27
  return data
28
28
 
29
29
 
30
- @attr.s(slots=True) # pragma: no mutate
30
+ @dataclass
31
31
  class Initialized(ExecutionEvent):
32
32
  """Runner is initialized, settings are prepared, requests session is ready."""
33
33
 
34
- schema: Dict[str, Any] = attr.ib() # pragma: no mutate
34
+ schema: Dict[str, Any]
35
35
  # Total number of operations in the schema
36
- operations_count: Optional[int] = attr.ib() # pragma: no mutate
36
+ operations_count: Optional[int]
37
37
  # The place, where the API schema is located
38
- location: Optional[str] = attr.ib() # pragma: no mutate
38
+ location: Optional[str]
39
39
  # The base URL against which the tests are running
40
- base_url: str = attr.ib() # pragma: no mutate
40
+ base_url: str
41
41
  # API schema specification name
42
- specification_name: str = attr.ib() # pragma: no mutate
42
+ specification_name: str
43
43
  # Monotonic clock value when the test run started. Used to properly calculate run duration, since this clock
44
44
  # can't go backwards.
45
- start_time: float = attr.ib(factory=time.monotonic) # pragma: no mutate
45
+ start_time: float = field(default_factory=time.monotonic)
46
46
  # Datetime of the test run start
47
- started_at: str = attr.ib(factory=current_datetime) # pragma: no mutate
48
- thread_id: int = attr.ib(factory=threading.get_ident) # pragma: no mutate
47
+ started_at: str = field(default_factory=current_datetime)
48
+ thread_id: int = field(default_factory=threading.get_ident)
49
49
 
50
50
  @classmethod
51
51
  def from_schema(
@@ -71,7 +71,7 @@ class CurrentOperationMixin:
71
71
  return f"{self.method} {self.path}"
72
72
 
73
73
 
74
- @attr.s(slots=True) # pragma: no mutate
74
+ @dataclass
75
75
  class BeforeExecution(CurrentOperationMixin, ExecutionEvent):
76
76
  """Happens before each tested API operation.
77
77
 
@@ -79,21 +79,21 @@ class BeforeExecution(CurrentOperationMixin, ExecutionEvent):
79
79
  """
80
80
 
81
81
  # HTTP method
82
- method: str = attr.ib() # pragma: no mutate
82
+ method: str
83
83
  # Full path, including the base path
84
- path: str = attr.ib() # pragma: no mutate
84
+ path: str
85
85
  # Specification-specific operation name
86
- verbose_name: str = attr.ib() # pragma: no mutate
86
+ verbose_name: str
87
87
  # Path without the base path
88
- relative_path: str = attr.ib() # pragma: no mutate
88
+ relative_path: str
89
89
  # The current level of recursion during stateful testing
90
- recursion_level: int = attr.ib() # pragma: no mutate
90
+ recursion_level: int
91
91
  # The way data will be generated
92
- data_generation_method: List[DataGenerationMethod] = attr.ib() # pragma: no mutate
92
+ data_generation_method: List[DataGenerationMethod]
93
93
  # A unique ID which connects events that happen during testing of the same API operation
94
94
  # It may be useful when multiple threads are involved where incoming events are not ordered
95
- correlation_id: str = attr.ib() # pragma: no mutate
96
- thread_id: int = attr.ib(factory=threading.get_ident) # pragma: no mutate
95
+ correlation_id: str
96
+ thread_id: int = field(default_factory=threading.get_ident)
97
97
 
98
98
  @classmethod
99
99
  def from_operation(
@@ -114,27 +114,27 @@ class BeforeExecution(CurrentOperationMixin, ExecutionEvent):
114
114
  )
115
115
 
116
116
 
117
- @attr.s(slots=True) # pragma: no mutate
117
+ @dataclass
118
118
  class AfterExecution(CurrentOperationMixin, ExecutionEvent):
119
119
  """Happens after each tested API operation."""
120
120
 
121
- method: str = attr.ib() # pragma: no mutate
122
- path: str = attr.ib() # pragma: no mutate
123
- relative_path: str = attr.ib() # pragma: no mutate
121
+ method: str
122
+ path: str
123
+ relative_path: str
124
124
  # Specification-specific operation name
125
- verbose_name: str = attr.ib() # pragma: no mutate
125
+ verbose_name: str
126
126
 
127
127
  # APIOperation test status - success / failure / error
128
- status: Status = attr.ib() # pragma: no mutate
128
+ status: Status
129
129
  # The way data was generated
130
- data_generation_method: List[DataGenerationMethod] = attr.ib() # pragma: no mutate
131
- result: SerializedTestResult = attr.ib() # pragma: no mutate
130
+ data_generation_method: List[DataGenerationMethod]
131
+ result: SerializedTestResult
132
132
  # Test running time
133
- elapsed_time: float = attr.ib() # pragma: no mutate
134
- correlation_id: str = attr.ib() # pragma: no mutate
135
- thread_id: int = attr.ib(factory=threading.get_ident) # pragma: no mutate
133
+ elapsed_time: float
134
+ correlation_id: str
135
+ thread_id: int = field(default_factory=threading.get_ident)
136
136
  # Captured hypothesis stdout
137
- hypothesis_output: List[str] = attr.ib(factory=list) # pragma: no mutate
137
+ hypothesis_output: List[str] = field(default_factory=list)
138
138
 
139
139
  @classmethod
140
140
  def from_result(
@@ -161,24 +161,24 @@ class AfterExecution(CurrentOperationMixin, ExecutionEvent):
161
161
  )
162
162
 
163
163
 
164
- @attr.s(slots=True) # pragma: no mutate
164
+ @dataclass
165
165
  class Interrupted(ExecutionEvent):
166
166
  """If execution was interrupted by Ctrl-C, or a received SIGTERM."""
167
167
 
168
- thread_id: int = attr.ib(factory=threading.get_ident) # pragma: no mutate
168
+ thread_id: int = field(default_factory=threading.get_ident)
169
169
 
170
170
 
171
- @attr.s(slots=True) # pragma: no mutate
171
+ @dataclass
172
172
  class InternalError(ExecutionEvent):
173
173
  """An error that happened inside the runner."""
174
174
 
175
175
  is_terminal = True
176
176
 
177
- message: str = attr.ib() # pragma: no mutate
178
- exception_type: str = attr.ib() # pragma: no mutate
179
- exception: Optional[str] = attr.ib(default=None) # pragma: no mutate
180
- exception_with_traceback: Optional[str] = attr.ib(default=None) # pragma: no mutate
181
- thread_id: int = attr.ib(factory=threading.get_ident) # pragma: no mutate
177
+ message: str
178
+ exception_type: str
179
+ exception: Optional[str] = None
180
+ exception_with_traceback: Optional[str] = None
181
+ thread_id: int = field(default_factory=threading.get_ident)
182
182
 
183
183
  @classmethod
184
184
  def from_exc(cls, exc: Exception, wait_for_schema: Optional[float] = None) -> "InternalError":
@@ -205,7 +205,7 @@ class InternalError(ExecutionEvent):
205
205
  )
206
206
 
207
207
 
208
- @attr.s(slots=True) # pragma: no mutate
208
+ @dataclass
209
209
  class Finished(ExecutionEvent):
210
210
  """The final event of the run.
211
211
 
@@ -214,23 +214,23 @@ class Finished(ExecutionEvent):
214
214
 
215
215
  is_terminal = True
216
216
 
217
- passed_count: int = attr.ib() # pragma: no mutate
218
- skipped_count: int = attr.ib() # pragma: no mutate
219
- failed_count: int = attr.ib() # pragma: no mutate
220
- errored_count: int = attr.ib() # pragma: no mutate
217
+ passed_count: int
218
+ skipped_count: int
219
+ failed_count: int
220
+ errored_count: int
221
221
 
222
- has_failures: bool = attr.ib() # pragma: no mutate
223
- has_errors: bool = attr.ib() # pragma: no mutate
224
- has_logs: bool = attr.ib() # pragma: no mutate
225
- is_empty: bool = attr.ib() # pragma: no mutate
226
- generic_errors: List[SerializedError] = attr.ib() # pragma: no mutate
227
- warnings: List[str] = attr.ib() # pragma: no mutate
222
+ has_failures: bool
223
+ has_errors: bool
224
+ has_logs: bool
225
+ is_empty: bool
226
+ generic_errors: List[SerializedError]
227
+ warnings: List[str]
228
228
 
229
- total: Dict[str, Dict[Union[str, Status], int]] = attr.ib() # pragma: no mutate
229
+ total: Dict[str, Dict[Union[str, Status], int]]
230
230
 
231
231
  # Total test run execution time
232
- running_time: float = attr.ib() # pragma: no mutate
233
- thread_id: int = attr.ib(factory=threading.get_ident) # pragma: no mutate
232
+ running_time: float
233
+ thread_id: int = field(default_factory=threading.get_ident)
234
234
 
235
235
  @classmethod
236
236
  def from_results(cls, results: TestResultSet, running_time: float) -> "Finished":
@@ -4,11 +4,11 @@ import time
4
4
  import unittest
5
5
  import uuid
6
6
  from contextlib import contextmanager
7
+ from dataclasses import dataclass, field
7
8
  from types import TracebackType
8
9
  from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Type, Union, cast
9
10
  from warnings import WarningMessage, catch_warnings
10
11
 
11
- import attr
12
12
  import hypothesis
13
13
  import requests
14
14
  from _pytest.logging import LogCaptureHandler, catching_logs
@@ -58,27 +58,27 @@ def _should_count_towards_stop(event: events.ExecutionEvent) -> bool:
58
58
  return isinstance(event, events.AfterExecution) and event.status in (Status.error, Status.failure)
59
59
 
60
60
 
61
- @attr.s # pragma: no mutate
61
+ @dataclass
62
62
  class BaseRunner:
63
- schema: BaseSchema = attr.ib() # pragma: no mutate
64
- checks: Iterable[CheckFunction] = attr.ib() # pragma: no mutate
65
- max_response_time: Optional[int] = attr.ib() # pragma: no mutate
66
- targets: Iterable[Target] = attr.ib() # pragma: no mutate
67
- hypothesis_settings: hypothesis.settings = attr.ib() # pragma: no mutate
68
- auth: Optional[RawAuth] = attr.ib(default=None) # pragma: no mutate
69
- auth_type: Optional[str] = attr.ib(default=None) # pragma: no mutate
70
- headers: Optional[Dict[str, Any]] = attr.ib(default=None) # pragma: no mutate
71
- request_timeout: Optional[int] = attr.ib(default=None) # pragma: no mutate
72
- store_interactions: bool = attr.ib(default=False) # pragma: no mutate
73
- seed: Optional[int] = attr.ib(default=None) # pragma: no mutate
74
- exit_first: bool = attr.ib(default=False) # pragma: no mutate
75
- max_failures: Optional[int] = attr.ib(default=None) # pragma: no mutate
76
- started_at: str = attr.ib(factory=current_datetime) # pragma: no mutate
77
- dry_run: bool = attr.ib(default=False) # pragma: no mutate
78
- stateful: Optional[Stateful] = attr.ib(default=None) # pragma: no mutate
79
- stateful_recursion_limit: int = attr.ib(default=DEFAULT_STATEFUL_RECURSION_LIMIT) # pragma: no mutate
80
- count_operations: bool = attr.ib(default=True) # pragma: no mutate
81
- _failures_counter: int = attr.ib(default=0)
63
+ schema: BaseSchema
64
+ checks: Iterable[CheckFunction]
65
+ max_response_time: Optional[int]
66
+ targets: Iterable[Target]
67
+ hypothesis_settings: hypothesis.settings
68
+ auth: Optional[RawAuth] = None
69
+ auth_type: Optional[str] = None
70
+ headers: Optional[Dict[str, Any]] = None
71
+ request_timeout: Optional[int] = None
72
+ store_interactions: bool = False
73
+ seed: Optional[int] = None
74
+ exit_first: bool = False
75
+ max_failures: Optional[int] = None
76
+ started_at: str = field(default_factory=current_datetime)
77
+ dry_run: bool = False
78
+ stateful: Optional[Stateful] = None
79
+ stateful_recursion_limit: int = DEFAULT_STATEFUL_RECURSION_LIMIT
80
+ count_operations: bool = True
81
+ _failures_counter: int = 0
82
82
 
83
83
  def execute(self) -> "EventStream":
84
84
  """Common logic for all runners."""
@@ -199,15 +199,15 @@ class BaseRunner:
199
199
  )
200
200
 
201
201
 
202
- @attr.s(slots=True) # pragma: no mutate
202
+ @dataclass
203
203
  class EventStream:
204
204
  """Schemathesis event stream.
205
205
 
206
206
  Provides an API to control the execution flow.
207
207
  """
208
208
 
209
- generator: Generator[events.ExecutionEvent, None, None] = attr.ib() # pragma: no mutate
210
- stop_event: threading.Event = attr.ib() # pragma: no mutate
209
+ generator: Generator[events.ExecutionEvent, None, None]
210
+ stop_event: threading.Event
211
211
 
212
212
  def __next__(self) -> events.ExecutionEvent:
213
213
  return next(self.generator)
@@ -553,7 +553,7 @@ def add_cases(case: Case, response: GenericResponse, test: Callable, *args: Any)
553
553
  test(_case, *args)
554
554
 
555
555
 
556
- @attr.s(slots=True) # pragma: no mutate
556
+ @dataclass
557
557
  class ErrorCollector:
558
558
  """Collect exceptions that are not related to failed checks.
559
559
 
@@ -566,7 +566,7 @@ class ErrorCollector:
566
566
  function signatures, which are used by Hypothesis.
567
567
  """
568
568
 
569
- errors: List[Exception] = attr.ib() # pragma: no mutate
569
+ errors: List[Exception]
570
570
 
571
571
  def __enter__(self) -> "ErrorCollector":
572
572
  return self
@@ -1,8 +1,7 @@
1
1
  import threading
2
+ from dataclasses import dataclass
2
3
  from typing import Generator, Optional, Union
3
4
 
4
- import attr
5
-
6
5
  from ...models import TestResultSet
7
6
  from ...types import RequestCert
8
7
  from ...utils import get_requests_auth
@@ -10,12 +9,12 @@ from .. import events
10
9
  from .core import BaseRunner, asgi_test, get_session, network_test, wsgi_test
11
10
 
12
11
 
13
- @attr.s(slots=True) # pragma: no mutate
12
+ @dataclass
14
13
  class SingleThreadRunner(BaseRunner):
15
14
  """Fast runner that runs tests sequentially in the main thread."""
16
15
 
17
- request_tls_verify: Union[bool, str] = attr.ib(default=True) # pragma: no mutate
18
- request_cert: Optional[RequestCert] = attr.ib(default=None) # pragma: no mutate
16
+ request_tls_verify: Union[bool, str] = True
17
+ request_cert: Optional[RequestCert] = None
19
18
 
20
19
  def _execute(
21
20
  self, results: TestResultSet, stop_event: threading.Event
@@ -47,7 +46,7 @@ class SingleThreadRunner(BaseRunner):
47
46
  )
48
47
 
49
48
 
50
- @attr.s(slots=True) # pragma: no mutate
49
+ @dataclass
51
50
  class SingleThreadWSGIRunner(SingleThreadRunner):
52
51
  def _execute_impl(self, results: TestResultSet) -> Generator[events.ExecutionEvent, None, None]:
53
52
  yield from self._run_tests(
@@ -67,7 +66,7 @@ class SingleThreadWSGIRunner(SingleThreadRunner):
67
66
  )
68
67
 
69
68
 
70
- @attr.s(slots=True) # pragma: no mutate
69
+ @dataclass
71
70
  class SingleThreadASGIRunner(SingleThreadRunner):
72
71
  def _execute_impl(self, results: TestResultSet) -> Generator[events.ExecutionEvent, None, None]:
73
72
  yield from self._run_tests(
@@ -2,10 +2,10 @@ import ctypes
2
2
  import queue
3
3
  import threading
4
4
  import time
5
+ from dataclasses import dataclass
5
6
  from queue import Queue
6
7
  from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Union, cast
7
8
 
8
- import attr
9
9
  import hypothesis
10
10
 
11
11
  from ..._hypothesis import create_test
@@ -201,13 +201,13 @@ def stop_worker(thread_id: int) -> None:
201
201
  ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread_id), ctypes.py_object(SystemExit))
202
202
 
203
203
 
204
- @attr.s(slots=True) # pragma: no mutate
204
+ @dataclass
205
205
  class ThreadPoolRunner(BaseRunner):
206
206
  """Spread different tests among multiple worker threads."""
207
207
 
208
- workers_num: int = attr.ib(default=2) # pragma: no mutate
209
- request_tls_verify: Union[bool, str] = attr.ib(default=True) # pragma: no mutate
210
- request_cert: Optional[RequestCert] = attr.ib(default=None) # pragma: no mutate
208
+ workers_num: int = 2
209
+ request_tls_verify: Union[bool, str] = True
210
+ request_cert: Optional[RequestCert] = None
211
211
 
212
212
  def _execute(
213
213
  self, results: TestResultSet, stop_event: threading.Event
@@ -3,9 +3,9 @@
3
3
  They all consist of primitive types and don't have references to schemas, app, etc.
4
4
  """
5
5
  import logging
6
+ from dataclasses import dataclass, field
6
7
  from typing import Any, Dict, List, Optional, Set, Tuple
7
8
 
8
- import attr
9
9
  import requests
10
10
 
11
11
  from ..exceptions import FailureContext, InternalError, make_unique_by_key
@@ -13,17 +13,17 @@ from ..models import Case, Check, Interaction, Request, Response, Status, TestRe
13
13
  from ..utils import IGNORED_HEADERS, WSGIResponse, format_exception
14
14
 
15
15
 
16
- @attr.s(slots=True) # pragma: no mutate
16
+ @dataclass
17
17
  class SerializedCase:
18
- requests_code: str = attr.ib()
19
- curl_code: str = attr.ib()
20
- path_template: str = attr.ib()
21
- path_parameters: Optional[Dict[str, Any]] = attr.ib()
22
- query: Optional[Dict[str, Any]] = attr.ib()
23
- cookies: Optional[Dict[str, Any]] = attr.ib()
24
- verbose_name: str = attr.ib()
25
- data_generation_method: Optional[str] = attr.ib()
26
- media_type: Optional[str] = attr.ib()
18
+ requests_code: str
19
+ curl_code: str
20
+ path_template: str
21
+ path_parameters: Optional[Dict[str, Any]]
22
+ query: Optional[Dict[str, Any]]
23
+ cookies: Optional[Dict[str, Any]]
24
+ verbose_name: str
25
+ data_generation_method: Optional[str]
26
+ media_type: Optional[str]
27
27
 
28
28
  @classmethod
29
29
  def from_case(cls, case: Case, headers: Optional[Dict[str, Any]]) -> "SerializedCase":
@@ -42,21 +42,21 @@ class SerializedCase:
42
42
  )
43
43
 
44
44
 
45
- @attr.s(slots=True) # pragma: no mutate
45
+ @dataclass
46
46
  class SerializedCheck:
47
47
  # Check name
48
- name: str = attr.ib() # pragma: no mutate
48
+ name: str
49
49
  # Check result
50
- value: Status = attr.ib() # pragma: no mutate
51
- request: Request = attr.ib() # pragma: no mutate
52
- response: Optional[Response] = attr.ib() # pragma: no mutate
50
+ value: Status
51
+ request: Request
52
+ response: Optional[Response]
53
53
  # Generated example
54
- example: SerializedCase = attr.ib() # pragma: no mutate
55
- message: Optional[str] = attr.ib(default=None) # pragma: no mutate
54
+ example: SerializedCase
55
+ message: Optional[str] = None
56
56
  # Failure-specific context
57
- context: Optional[FailureContext] = attr.ib(default=None) # pragma: no mutate
57
+ context: Optional[FailureContext] = None
58
58
  # Cases & responses that were made before this one
59
- history: List["SerializedHistoryEntry"] = attr.ib(factory=list) # pragma: no mutate
59
+ history: List["SerializedHistoryEntry"] = field(default_factory=list)
60
60
 
61
61
  @classmethod
62
62
  def from_check(cls, check: Check) -> "SerializedCheck":
@@ -100,18 +100,18 @@ class SerializedCheck:
100
100
  )
101
101
 
102
102
 
103
- @attr.s(slots=True) # pragma: no mutate
103
+ @dataclass
104
104
  class SerializedHistoryEntry:
105
- case: SerializedCase = attr.ib()
106
- response: Response = attr.ib()
105
+ case: SerializedCase
106
+ response: Response
107
107
 
108
108
 
109
- @attr.s(slots=True) # pragma: no mutate
109
+ @dataclass
110
110
  class SerializedError:
111
- exception: str = attr.ib() # pragma: no mutate
112
- exception_with_traceback: str = attr.ib() # pragma: no mutate
113
- example: Optional[SerializedCase] = attr.ib() # pragma: no mutate
114
- title: Optional[str] = attr.ib() # pragma: no mutate
111
+ exception: str
112
+ exception_with_traceback: str
113
+ example: Optional[SerializedCase]
114
+ title: Optional[str]
115
115
 
116
116
  @classmethod
117
117
  def from_error(
@@ -125,13 +125,13 @@ class SerializedError:
125
125
  )
126
126
 
127
127
 
128
- @attr.s(slots=True) # pragma: no mutate
128
+ @dataclass
129
129
  class SerializedInteraction:
130
- request: Request = attr.ib() # pragma: no mutate
131
- response: Response = attr.ib() # pragma: no mutate
132
- checks: List[SerializedCheck] = attr.ib() # pragma: no mutate
133
- status: Status = attr.ib() # pragma: no mutate
134
- recorded_at: str = attr.ib() # pragma: no mutate
130
+ request: Request
131
+ response: Response
132
+ checks: List[SerializedCheck]
133
+ status: Status
134
+ recorded_at: str
135
135
 
136
136
  @classmethod
137
137
  def from_interaction(cls, interaction: Interaction) -> "SerializedInteraction":
@@ -144,23 +144,23 @@ class SerializedInteraction:
144
144
  )
145
145
 
146
146
 
147
- @attr.s(slots=True) # pragma: no mutate
147
+ @dataclass
148
148
  class SerializedTestResult:
149
- method: str = attr.ib() # pragma: no mutate
150
- path: str = attr.ib() # pragma: no mutate
151
- verbose_name: str = attr.ib() # pragma: no mutate
152
- has_failures: bool = attr.ib() # pragma: no mutate
153
- has_errors: bool = attr.ib() # pragma: no mutate
154
- has_logs: bool = attr.ib() # pragma: no mutate
155
- is_errored: bool = attr.ib() # pragma: no mutate
156
- is_flaky: bool = attr.ib() # pragma: no mutate
157
- is_skipped: bool = attr.ib() # pragma: no mutate
158
- seed: Optional[int] = attr.ib() # pragma: no mutate
159
- data_generation_method: List[str] = attr.ib() # pragma: no mutate
160
- checks: List[SerializedCheck] = attr.ib() # pragma: no mutate
161
- logs: List[str] = attr.ib() # pragma: no mutate
162
- errors: List[SerializedError] = attr.ib() # pragma: no mutate
163
- interactions: List[SerializedInteraction] = attr.ib() # pragma: no mutate
149
+ method: str
150
+ path: str
151
+ verbose_name: str
152
+ has_failures: bool
153
+ has_errors: bool
154
+ has_logs: bool
155
+ is_errored: bool
156
+ is_flaky: bool
157
+ is_skipped: bool
158
+ seed: Optional[int]
159
+ data_generation_method: List[str]
160
+ checks: List[SerializedCheck]
161
+ logs: List[str]
162
+ errors: List[SerializedError]
163
+ interactions: List[SerializedInteraction]
164
164
 
165
165
  @classmethod
166
166
  def from_test_result(cls, result: TestResult) -> "SerializedTestResult":
schemathesis/schemas.py CHANGED
@@ -8,6 +8,7 @@ They give only static definitions of paths.
8
8
  """
9
9
  from collections.abc import Mapping
10
10
  from contextlib import nullcontext
11
+ from dataclasses import dataclass, field
11
12
  from difflib import get_close_matches
12
13
  from functools import lru_cache
13
14
  from typing import (
@@ -29,7 +30,6 @@ from typing import (
29
30
  )
30
31
  from urllib.parse import quote, unquote, urljoin, urlparse, urlsplit, urlunsplit
31
32
 
32
- import attr
33
33
  import hypothesis
34
34
  from hypothesis.strategies import SearchStrategy
35
35
  from pyrate_limiter import Limiter
@@ -77,29 +77,29 @@ C = TypeVar("C", bound=Case)
77
77
 
78
78
  @lru_cache()
79
79
  def get_full_path(base_path: str, path: str) -> str:
80
- return unquote(urljoin(base_path, quote(path.lstrip("/")))) # pragma: no mutate
80
+ return unquote(urljoin(base_path, quote(path.lstrip("/"))))
81
81
 
82
82
 
83
- @attr.s(eq=False) # pragma: no mutate
83
+ @dataclass(eq=False)
84
84
  class BaseSchema(Mapping):
85
- raw_schema: Dict[str, Any] = attr.ib() # pragma: no mutate
86
- location: Optional[str] = attr.ib(default=None) # pragma: no mutate
87
- base_url: Optional[str] = attr.ib(default=None) # pragma: no mutate
88
- method: Optional[Filter] = attr.ib(default=None) # pragma: no mutate
89
- endpoint: Optional[Filter] = attr.ib(default=None) # pragma: no mutate
90
- tag: Optional[Filter] = attr.ib(default=None) # pragma: no mutate
91
- operation_id: Optional[Filter] = attr.ib(default=None) # pragma: no mutate
92
- app: Any = attr.ib(default=None) # pragma: no mutate
93
- hooks: HookDispatcher = attr.ib(factory=lambda: HookDispatcher(scope=HookScope.SCHEMA)) # pragma: no mutate
94
- auth: AuthStorage = attr.ib(factory=AuthStorage) # pragma: no mutate
95
- test_function: Optional[GenericTest] = attr.ib(default=None) # pragma: no mutate
96
- validate_schema: bool = attr.ib(default=True) # pragma: no mutate
97
- skip_deprecated_operations: bool = attr.ib(default=False) # pragma: no mutate
98
- data_generation_methods: List[DataGenerationMethod] = attr.ib(
99
- default=DEFAULT_DATA_GENERATION_METHODS
100
- ) # pragma: no mutate
101
- code_sample_style: CodeSampleStyle = attr.ib(default=CodeSampleStyle.default()) # pragma: no mutate
102
- rate_limiter: Optional[Limiter] = attr.ib(default=None)
85
+ raw_schema: Dict[str, Any]
86
+ location: Optional[str] = None
87
+ base_url: Optional[str] = None
88
+ method: Optional[Filter] = None
89
+ endpoint: Optional[Filter] = None
90
+ tag: Optional[Filter] = None
91
+ operation_id: Optional[Filter] = None
92
+ app: Any = None
93
+ hooks: HookDispatcher = field(default_factory=lambda: HookDispatcher(scope=HookScope.SCHEMA))
94
+ auth: AuthStorage = field(default_factory=AuthStorage)
95
+ test_function: Optional[GenericTest] = None
96
+ validate_schema: bool = True
97
+ skip_deprecated_operations: bool = False
98
+ data_generation_methods: List[DataGenerationMethod] = field(
99
+ default_factory=lambda: list(DEFAULT_DATA_GENERATION_METHODS)
100
+ )
101
+ code_sample_style: CodeSampleStyle = CodeSampleStyle.default()
102
+ rate_limiter: Optional[Limiter] = None
103
103
 
104
104
  def __iter__(self) -> Iterator[str]:
105
105
  return iter(self.operations)
@@ -120,7 +120,7 @@ class BaseSchema(Mapping):
120
120
  def hook(self, hook: Union[str, Callable]) -> Callable:
121
121
  return self.hooks.register(hook)
122
122
 
123
- @property # pragma: no mutate
123
+ @property
124
124
  def verbose_name(self) -> str:
125
125
  raise NotImplementedError
126
126
 
@@ -152,7 +152,7 @@ class BaseSchema(Mapping):
152
152
  def get_base_url(self) -> str:
153
153
  base_url = self.base_url
154
154
  if base_url is not None:
155
- return base_url.rstrip("/") # pragma: no mutate
155
+ return base_url.rstrip("/")
156
156
  return self._build_base_url()
157
157
 
158
158
  @property
@@ -1,9 +1,9 @@
1
1
  import binascii
2
2
  import os
3
+ from dataclasses import dataclass
3
4
  from io import BytesIO
4
5
  from typing import TYPE_CHECKING, Any, Callable, Collection, Dict, Generator, Optional, Type
5
6
 
6
- import attr
7
7
  import yaml
8
8
  from typing_extensions import Protocol, runtime_checkable
9
9
 
@@ -22,14 +22,14 @@ except ImportError:
22
22
  SERIALIZERS: Dict[str, Type["Serializer"]] = {}
23
23
 
24
24
 
25
- @attr.s(slots=True) # pragma: no mutate
25
+ @dataclass
26
26
  class SerializerContext:
27
27
  """The context for serialization process.
28
28
 
29
29
  :ivar Case case: Generated example that is being processed.
30
30
  """
31
31
 
32
- case: "Case" = attr.ib() # pragma: no mutate
32
+ case: "Case"
33
33
 
34
34
 
35
35
  @runtime_checkable