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
  """Expression nodes description and evaluation logic."""
2
+ from dataclasses import dataclass
2
3
  from enum import Enum, unique
3
4
  from typing import Any, Dict, Optional, Union
4
5
 
5
- import attr
6
6
  from requests.structures import CaseInsensitiveDict
7
7
 
8
8
  from ....utils import WSGIResponse
@@ -10,7 +10,7 @@ from .. import references
10
10
  from .context import ExpressionContext
11
11
 
12
12
 
13
- @attr.s(slots=True) # pragma: no mutate
13
+ @dataclass
14
14
  class Node:
15
15
  """Generic expression node."""
16
16
 
@@ -27,11 +27,11 @@ class NodeType(Enum):
27
27
  RESPONSE = "$response"
28
28
 
29
29
 
30
- @attr.s(slots=True) # pragma: no mutate
30
+ @dataclass
31
31
  class String(Node):
32
32
  """A simple string that is not evaluated somehow specifically."""
33
33
 
34
- value: str = attr.ib() # pragma: no mutate
34
+ value: str
35
35
 
36
36
  def evaluate(self, context: ExpressionContext) -> str:
37
37
  """String tokens are passed as they are.
@@ -43,7 +43,7 @@ class String(Node):
43
43
  return self.value
44
44
 
45
45
 
46
- @attr.s(slots=True) # pragma: no mutate
46
+ @dataclass
47
47
  class URL(Node):
48
48
  """A node for `$url` expression."""
49
49
 
@@ -51,7 +51,7 @@ class URL(Node):
51
51
  return context.case.get_full_url()
52
52
 
53
53
 
54
- @attr.s(slots=True) # pragma: no mutate
54
+ @dataclass
55
55
  class Method(Node):
56
56
  """A node for `$method` expression."""
57
57
 
@@ -59,7 +59,7 @@ class Method(Node):
59
59
  return context.case.operation.method.upper()
60
60
 
61
61
 
62
- @attr.s(slots=True) # pragma: no mutate
62
+ @dataclass
63
63
  class StatusCode(Node):
64
64
  """A node for `$statusCode` expression."""
65
65
 
@@ -67,12 +67,12 @@ class StatusCode(Node):
67
67
  return str(context.response.status_code)
68
68
 
69
69
 
70
- @attr.s(slots=True) # pragma: no mutate
70
+ @dataclass
71
71
  class NonBodyRequest(Node):
72
72
  """A node for `$request` expressions where location is not `body`."""
73
73
 
74
- location: str = attr.ib() # pragma: no mutate
75
- parameter: str = attr.ib() # pragma: no mutate
74
+ location: str
75
+ parameter: str
76
76
 
77
77
  def evaluate(self, context: ExpressionContext) -> str:
78
78
  container: Union[Dict, CaseInsensitiveDict] = {
@@ -85,11 +85,11 @@ class NonBodyRequest(Node):
85
85
  return container[self.parameter]
86
86
 
87
87
 
88
- @attr.s(slots=True) # pragma: no mutate
88
+ @dataclass
89
89
  class BodyRequest(Node):
90
90
  """A node for `$request` expressions where location is `body`."""
91
91
 
92
- pointer: Optional[str] = attr.ib(default=None) # pragma: no mutate
92
+ pointer: Optional[str] = None
93
93
 
94
94
  def evaluate(self, context: ExpressionContext) -> Any:
95
95
  document = context.case.body
@@ -98,21 +98,21 @@ class BodyRequest(Node):
98
98
  return references.resolve_pointer(document, self.pointer[1:])
99
99
 
100
100
 
101
- @attr.s(slots=True) # pragma: no mutate
101
+ @dataclass
102
102
  class HeaderResponse(Node):
103
103
  """A node for `$response.header` expressions."""
104
104
 
105
- parameter: str = attr.ib() # pragma: no mutate
105
+ parameter: str
106
106
 
107
107
  def evaluate(self, context: ExpressionContext) -> str:
108
108
  return context.response.headers[self.parameter]
109
109
 
110
110
 
111
- @attr.s(slots=True) # pragma: no mutate
111
+ @dataclass
112
112
  class BodyResponse(Node):
113
113
  """A node for `$response.body` expressions."""
114
114
 
115
- pointer: Optional[str] = attr.ib(default=None) # pragma: no mutate
115
+ pointer: Optional[str] = None
116
116
 
117
117
  def evaluate(self, context: ExpressionContext) -> Any:
118
118
  if isinstance(context.response, WSGIResponse):
@@ -5,7 +5,7 @@ from . import lexer, nodes
5
5
  from .errors import RuntimeExpressionError, UnknownToken
6
6
 
7
7
 
8
- @lru_cache() # pragma: no mutate
8
+ @lru_cache()
9
9
  def parse(expr: str) -> List[nodes.Node]:
10
10
  """Parse lexical tokens into concrete expression nodes."""
11
11
  return list(_parse(expr))
@@ -2,11 +2,10 @@
2
2
 
3
3
  Based on https://swagger.io/docs/specification/links/
4
4
  """
5
+ from dataclasses import dataclass, field
5
6
  from difflib import get_close_matches
6
7
  from typing import Any, Dict, Generator, List, NoReturn, Optional, Sequence, Tuple, Union
7
8
 
8
- import attr
9
-
10
9
  from ...models import APIOperation, Case
11
10
  from ...parameters import ParameterSet
12
11
  from ...stateful import Direction, ParsedData, StatefulTest
@@ -17,15 +16,14 @@ from .constants import LOCATION_TO_CONTAINER
17
16
  from .parameters import OpenAPI20Body, OpenAPI30Body, OpenAPIParameter
18
17
 
19
18
 
20
- @attr.s(slots=True, repr=False) # pragma: no mutate
19
+ @dataclass(repr=False)
21
20
  class Link(StatefulTest):
22
- operation: APIOperation = attr.ib() # pragma: no mutate
23
- parameters: Dict[str, Any] = attr.ib() # pragma: no mutate
24
- request_body: Any = attr.ib(default=NOT_SET) # pragma: no mutate
21
+ operation: APIOperation
22
+ parameters: Dict[str, Any]
23
+ request_body: Any = NOT_SET
25
24
 
26
- @request_body.validator
27
- def is_defined(self, attribute: attr.Attribute, value: Any) -> None:
28
- if value is not NOT_SET and not self.operation.body:
25
+ def __post_init__(self) -> None:
26
+ if self.request_body is not NOT_SET and not self.operation.body:
29
27
  # Link defines `requestBody` for a parameter that does not accept one
30
28
  raise ValueError(
31
29
  f"Request body is not defined in API operation {self.operation.method.upper()} {self.operation.path}"
@@ -159,21 +157,21 @@ def get_links(response: GenericResponse, operation: APIOperation, field: str) ->
159
157
  return [Link.from_definition(name, definition, operation) for name, definition in links.items()]
160
158
 
161
159
 
162
- @attr.s(slots=True, repr=False) # pragma: no mutate
160
+ @dataclass(repr=False)
163
161
  class OpenAPILink(Direction):
164
162
  """Alternative approach to link processing.
165
163
 
166
164
  NOTE. This class will replace `Link` in the future.
167
165
  """
168
166
 
169
- name: str = attr.ib() # pragma: no mutate
170
- status_code: str = attr.ib() # pragma: no mutate
171
- definition: Dict[str, Any] = attr.ib() # pragma: no mutate
172
- operation: APIOperation = attr.ib() # pragma: no mutate
173
- parameters: List[Tuple[Optional[str], str, str]] = attr.ib(init=False) # pragma: no mutate
174
- body: Union[Dict[str, Any], NotSet] = attr.ib(init=False) # pragma: no mutate
167
+ name: str
168
+ status_code: str
169
+ definition: Dict[str, Any]
170
+ operation: APIOperation
171
+ parameters: List[Tuple[Optional[str], str, str]] = field(init=False)
172
+ body: Union[Dict[str, Any], NotSet] = field(init=False)
175
173
 
176
- def __attrs_post_init__(self) -> None:
174
+ def __post_init__(self) -> None:
177
175
  self.parameters = [
178
176
  normalize_parameter(parameter, expression)
179
177
  for parameter, expression in self.definition.get("parameters", {}).items()
@@ -1,8 +1,8 @@
1
+ from dataclasses import dataclass
1
2
  from functools import lru_cache
2
3
  from typing import Any, Dict, Optional, Tuple
3
4
  from urllib.parse import urlencode
4
5
 
5
- import attr
6
6
  import jsonschema
7
7
  from hypothesis import strategies as st
8
8
  from hypothesis_jsonschema import from_schema
@@ -12,16 +12,16 @@ from .mutations import MutationContext
12
12
  from .types import Draw, Schema
13
13
 
14
14
 
15
- @attr.s(slots=True, hash=False)
15
+ @dataclass
16
16
  class CacheKey:
17
17
  """A cache key for API Operation / location.
18
18
 
19
19
  Carries the schema around but don't use it for hashing to simplify LRU cache usage.
20
20
  """
21
21
 
22
- operation_name: str = attr.ib()
23
- location: str = attr.ib()
24
- schema: Schema = attr.ib()
22
+ operation_name: str
23
+ location: str
24
+ schema: Schema
25
25
 
26
26
  def __hash__(self) -> int:
27
27
  return hash((self.operation_name, self.location))
@@ -1,9 +1,9 @@
1
1
  """Schema mutations."""
2
2
  import enum
3
+ from dataclasses import dataclass
3
4
  from functools import wraps
4
5
  from typing import Any, Callable, List, Optional, Sequence, Set, Tuple, TypeVar
5
6
 
6
- import attr
7
7
  from hypothesis import reject
8
8
  from hypothesis import strategies as st
9
9
  from hypothesis.strategies._internal.featureflags import FeatureStrategy
@@ -58,17 +58,17 @@ TYPE_SPECIFIC_KEYS = {
58
58
  }
59
59
 
60
60
 
61
- @attr.s(slots=True)
61
+ @dataclass
62
62
  class MutationContext:
63
63
  """Meta information about the current mutation state."""
64
64
 
65
65
  # The original schema
66
- keywords: Schema = attr.ib() # only keywords
67
- non_keywords: Schema = attr.ib() # everything else
66
+ keywords: Schema # only keywords
67
+ non_keywords: Schema # everything else
68
68
  # Schema location within API operation (header, query, etc)
69
- location: str = attr.ib()
69
+ location: str
70
70
  # Payload media type, if available
71
- media_type: Optional[str] = attr.ib()
71
+ media_type: Optional[str]
72
72
 
73
73
  @property
74
74
  def is_header_location(self) -> bool:
@@ -1,15 +1,14 @@
1
1
  import json
2
+ from dataclasses import dataclass
2
3
  from typing import Any, ClassVar, Dict, Iterable, List, Optional, Tuple
3
4
 
4
- import attr
5
-
6
5
  from ...exceptions import InvalidSchema
7
6
  from ...models import APIOperation
8
7
  from ...parameters import Parameter
9
8
  from .converter import to_json_schema_recursive
10
9
 
11
10
 
12
- @attr.s(eq=False)
11
+ @dataclass(eq=False)
13
12
  class OpenAPIParameter(Parameter):
14
13
  """A single Open API operation parameter."""
15
14
 
@@ -122,7 +121,7 @@ class OpenAPIParameter(Parameter):
122
121
  return json.dumps(self.as_json_schema(operation), sort_keys=True)
123
122
 
124
123
 
125
- @attr.s(eq=False)
124
+ @dataclass(eq=False)
126
125
  class OpenAPI20Parameter(OpenAPIParameter):
127
126
  """Open API 2.0 parameter.
128
127
 
@@ -167,7 +166,7 @@ class OpenAPI20Parameter(OpenAPIParameter):
167
166
  return None
168
167
 
169
168
 
170
- @attr.s(eq=False)
169
+ @dataclass(eq=False)
171
170
  class OpenAPI30Parameter(OpenAPIParameter):
172
171
  """Open API 3.0 parameter.
173
172
 
@@ -217,9 +216,9 @@ class OpenAPI30Parameter(OpenAPIParameter):
217
216
  return super().from_open_api_to_json_schema(operation, open_api_schema)
218
217
 
219
218
 
220
- @attr.s(eq=False)
219
+ @dataclass(eq=False)
221
220
  class OpenAPIBody(OpenAPIParameter):
222
- media_type: str = attr.ib()
221
+ media_type: str
223
222
 
224
223
  @property
225
224
  def location(self) -> str:
@@ -231,7 +230,7 @@ class OpenAPIBody(OpenAPIParameter):
231
230
  return "body"
232
231
 
233
232
 
234
- @attr.s(slots=True, eq=False)
233
+ @dataclass(eq=False)
235
234
  class OpenAPI20Body(OpenAPIBody, OpenAPI20Parameter):
236
235
  """Open API 2.0 body variant."""
237
236
 
@@ -280,7 +279,7 @@ class OpenAPI20Body(OpenAPIBody, OpenAPI20Parameter):
280
279
  FORM_MEDIA_TYPES = ("multipart/form-data", "application/x-www-form-urlencoded")
281
280
 
282
281
 
283
- @attr.s(slots=True, eq=False)
282
+ @dataclass(eq=False)
284
283
  class OpenAPI30Body(OpenAPIBody, OpenAPI30Parameter):
285
284
  """Open API 3.0 body variant.
286
285
 
@@ -290,8 +289,8 @@ class OpenAPI30Body(OpenAPIBody, OpenAPI30Parameter):
290
289
 
291
290
  # The `required` keyword is located above the schema for concrete media-type;
292
291
  # Therefore, it is passed here explicitly
293
- required: bool = attr.ib(default=False)
294
- description: Optional[str] = attr.ib(default=None)
292
+ required: bool = False
293
+ description: Optional[str] = None
295
294
 
296
295
  def as_json_schema(self, operation: APIOperation) -> Dict[str, Any]:
297
296
  """Convert body definition to JSON Schema."""
@@ -315,11 +314,11 @@ class OpenAPI30Body(OpenAPIBody, OpenAPI30Parameter):
315
314
  return self.required
316
315
 
317
316
 
318
- @attr.s(slots=True, eq=False)
317
+ @dataclass(eq=False)
319
318
  class OpenAPI20CompositeBody(OpenAPIBody, OpenAPI20Parameter):
320
319
  """A special container to abstract over multiple `formData` parameters."""
321
320
 
322
- definition: List[OpenAPI20Parameter] = attr.ib()
321
+ definition: List[OpenAPI20Parameter]
323
322
 
324
323
  @classmethod
325
324
  def from_parameters(cls, *parameters: Dict[str, Any], media_type: str) -> "OpenAPI20CompositeBody":
@@ -52,11 +52,11 @@ class InliningResolver(jsonschema.RefResolver):
52
52
  )
53
53
  super().__init__(*args, **kwargs)
54
54
 
55
- @overload # pragma: no mutate
55
+ @overload
56
56
  def resolve_all(self, item: Dict[str, Any], recursion_level: int = 0) -> Dict[str, Any]:
57
57
  pass
58
58
 
59
- @overload # pragma: no mutate
59
+ @overload
60
60
  def resolve_all(self, item: List, recursion_level: int = 0) -> List:
61
61
  pass
62
62
 
@@ -2,6 +2,7 @@ import itertools
2
2
  import json
3
3
  from collections import defaultdict
4
4
  from contextlib import ExitStack, contextmanager
5
+ from dataclasses import dataclass, field
5
6
  from difflib import get_close_matches
6
7
  from hashlib import sha1
7
8
  from json import JSONDecodeError
@@ -23,7 +24,6 @@ from typing import (
23
24
  )
24
25
  from urllib.parse import urlsplit
25
26
 
26
- import attr
27
27
  import jsonschema
28
28
  import requests
29
29
  from hypothesis.strategies import SearchStrategy
@@ -82,24 +82,20 @@ SCHEMA_ERROR_MESSAGE = "Schema parsing failed. Please check your schema."
82
82
  SCHEMA_PARSING_ERRORS = (KeyError, AttributeError, jsonschema.exceptions.RefResolutionError)
83
83
 
84
84
 
85
- @attr.s(eq=False, repr=False)
85
+ @dataclass(eq=False, repr=False)
86
86
  class BaseOpenAPISchema(BaseSchema):
87
- nullable_name: str
88
- links_field: str
89
- header_required_field: str
90
- security: BaseSecurityProcessor
91
- component_locations: ClassVar[Tuple[Tuple[str, ...], ...]] = ()
92
- _operations_by_id: Dict[str, APIOperation]
93
- _inline_reference_cache: Dict[str, Any]
87
+ nullable_name: ClassVar[str] = ""
88
+ links_field: ClassVar[str] = ""
89
+ header_required_field: ClassVar[str] = ""
90
+ security: ClassVar[BaseSecurityProcessor] = None # type: ignore
91
+ _operations_by_id: Dict[str, APIOperation] = field(init=False)
92
+ _inline_reference_cache: Dict[str, Any] = field(default_factory=dict)
94
93
  # Inline references cache can be populated from multiple threads, therefore we need some synchronisation to avoid
95
94
  # excessive resolving
96
- _inline_reference_cache_lock: RLock
97
-
98
- def __attrs_post_init__(self) -> None:
99
- self._inline_reference_cache = {}
100
- self._inline_reference_cache_lock = RLock()
95
+ _inline_reference_cache_lock: RLock = field(default_factory=RLock)
96
+ component_locations: ClassVar[Tuple[Tuple[str, ...], ...]] = ()
101
97
 
102
- @property # pragma: no mutate
98
+ @property
103
99
  def spec_version(self) -> str:
104
100
  raise NotImplementedError
105
101
 
@@ -1,17 +1,17 @@
1
1
  """Processing of ``securityDefinitions`` or ``securitySchemes`` keywords."""
2
+ from dataclasses import dataclass
2
3
  from typing import Any, ClassVar, Dict, Generator, List, Tuple, Type
3
4
 
4
- import attr
5
5
  from jsonschema import RefResolver
6
6
 
7
7
  from ...models import APIOperation
8
8
  from .parameters import OpenAPI20Parameter, OpenAPI30Parameter, OpenAPIParameter
9
9
 
10
10
 
11
- @attr.s(slots=True) # pragma: no mutate
11
+ @dataclass
12
12
  class BaseSecurityProcessor:
13
- api_key_locations: Tuple[str, ...] = ("header", "query")
14
- http_security_name = "basic"
13
+ api_key_locations: ClassVar[Tuple[str, ...]] = ("header", "query")
14
+ http_security_name: ClassVar[str] = "basic"
15
15
  parameter_cls: ClassVar[Type[OpenAPIParameter]] = OpenAPI20Parameter
16
16
 
17
17
  def process_definitions(self, schema: Dict[str, Any], operation: APIOperation, resolver: RefResolver) -> None:
@@ -112,10 +112,10 @@ def make_api_key_schema(definition: Dict[str, Any], **kwargs: Any) -> Dict[str,
112
112
  SwaggerSecurityProcessor = BaseSecurityProcessor
113
113
 
114
114
 
115
- @attr.s(slots=True) # pragma: no mutate
115
+ @dataclass
116
116
  class OpenAPISecurityProcessor(BaseSecurityProcessor):
117
- api_key_locations = ("header", "cookie", "query")
118
- http_security_name = "http"
117
+ api_key_locations: ClassVar[Tuple[str, ...]] = ("header", "cookie", "query")
118
+ http_security_name: ClassVar[str] = "http"
119
119
  parameter_cls: ClassVar[Type[OpenAPIParameter]] = OpenAPI30Parameter
120
120
 
121
121
  def get_security_definitions(self, schema: Dict[str, Any], resolver: RefResolver) -> Dict[str, Any]:
@@ -1,6 +1,6 @@
1
+ from dataclasses import dataclass
1
2
  from typing import TYPE_CHECKING, Callable, Dict, List, Tuple
2
3
 
3
- import attr
4
4
  import hypothesis.strategies as st
5
5
  from requests.structures import CaseInsensitiveDict
6
6
 
@@ -14,10 +14,10 @@ if TYPE_CHECKING:
14
14
  FilterFunction = Callable[[StepResult], bool]
15
15
 
16
16
 
17
- @attr.s(slots=True)
17
+ @dataclass
18
18
  class Connection:
19
- source: str = attr.ib()
20
- strategy: st.SearchStrategy[Tuple[StepResult, OpenAPILink]] = attr.ib()
19
+ source: str
20
+ strategy: st.SearchStrategy[Tuple[StepResult, OpenAPILink]]
21
21
 
22
22
 
23
23
  APIOperationConnections = Dict[str, List[Connection]]
schemathesis/stateful.py CHANGED
@@ -1,9 +1,9 @@
1
1
  import enum
2
2
  import json
3
3
  import time
4
+ from dataclasses import dataclass, field
4
5
  from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, Generator, List, Optional, Tuple, Type
5
6
 
6
- import attr
7
7
  import hypothesis
8
8
  from hypothesis.stateful import RuleBasedStateMachine
9
9
  from hypothesis.stateful import run_state_machine_as_test as _run_state_machine_as_test
@@ -25,15 +25,15 @@ class Stateful(enum.Enum):
25
25
  links = 2
26
26
 
27
27
 
28
- @attr.s(slots=True, hash=False) # pragma: no mutate
28
+ @dataclass
29
29
  class ParsedData:
30
30
  """A structure that holds information parsed from a test outcome.
31
31
 
32
32
  It is used later to create a new version of an API operation that will reuse this data.
33
33
  """
34
34
 
35
- parameters: Dict[str, Any] = attr.ib() # pragma: no mutate
36
- body: Any = attr.ib(default=NOT_SET) # pragma: no mutate
35
+ parameters: Dict[str, Any]
36
+ body: Any = NOT_SET
37
37
 
38
38
  def __hash__(self) -> int:
39
39
  """Custom hash simplifies deduplication of parsed data."""
@@ -48,11 +48,11 @@ class ParsedData:
48
48
  return value
49
49
 
50
50
 
51
- @attr.s(slots=True) # pragma: no mutate
51
+ @dataclass
52
52
  class StatefulTest:
53
53
  """A template for a test that will be executed after another one by reusing the outcomes from it."""
54
54
 
55
- name: str = attr.ib() # pragma: no mutate
55
+ name: str
56
56
 
57
57
  def parse(self, case: Case, response: GenericResponse) -> ParsedData:
58
58
  raise NotImplementedError
@@ -61,12 +61,12 @@ class StatefulTest:
61
61
  raise NotImplementedError
62
62
 
63
63
 
64
- @attr.s(slots=True) # pragma: no mutate
64
+ @dataclass
65
65
  class StatefulData:
66
66
  """Storage for data that will be used in later tests."""
67
67
 
68
- stateful_test: StatefulTest = attr.ib() # pragma: no mutate
69
- container: List[ParsedData] = attr.ib(factory=list) # pragma: no mutate
68
+ stateful_test: StatefulTest
69
+ container: List[ParsedData] = field(default_factory=list)
70
70
 
71
71
  def make_operation(self) -> APIOperation:
72
72
  return self.stateful_test.make_operation(self.container)
@@ -77,16 +77,16 @@ class StatefulData:
77
77
  self.container.append(parsed)
78
78
 
79
79
 
80
- @attr.s(slots=True) # pragma: no mutate
80
+ @dataclass
81
81
  class Feedback:
82
82
  """Handler for feedback from tests.
83
83
 
84
84
  Provides a way to control runner's behavior from tests.
85
85
  """
86
86
 
87
- stateful: Optional[Stateful] = attr.ib() # pragma: no mutate
88
- operation: APIOperation = attr.ib(repr=False) # pragma: no mutate
89
- stateful_tests: Dict[str, StatefulData] = attr.ib(factory=dict, repr=False) # pragma: no mutate
87
+ stateful: Optional[Stateful]
88
+ operation: APIOperation = field(repr=False)
89
+ stateful_tests: Dict[str, StatefulData] = field(default_factory=dict, repr=False)
90
90
 
91
91
  def add_test_case(self, case: Case, response: GenericResponse) -> None:
92
92
  """Store test data to reuse it in the future additional tests."""
@@ -117,13 +117,13 @@ class Feedback:
117
117
  yield Ok((operation, test_function))
118
118
 
119
119
 
120
- @attr.s(slots=True) # pragma: no mutate
120
+ @dataclass
121
121
  class StepResult:
122
122
  """Output from a single transition of a state machine."""
123
123
 
124
- response: GenericResponse = attr.ib() # pragma: no mutate
125
- case: Case = attr.ib() # pragma: no mutate
126
- elapsed: float = attr.ib() # pragma: no mutate
124
+ response: GenericResponse
125
+ case: Case
126
+ elapsed: float
127
127
 
128
128
 
129
129
  class Direction:
@@ -148,11 +148,11 @@ def _print_case(case: Case, kwargs: Dict[str, Any]) -> str:
148
148
  return f"{operation}.make_case({', '.join(data)})"
149
149
 
150
150
 
151
- @attr.s(slots=True, repr=False) # pragma: no mutate
151
+ @dataclass(repr=False)
152
152
  class _DirectionWrapper:
153
153
  """Purely to avoid modification of `Direction.__repr__`."""
154
154
 
155
- direction: Direction = attr.ib() # pragma: no mutate
155
+ direction: Direction
156
156
 
157
157
  def __repr__(self) -> str:
158
158
  path = self.direction.operation.path
schemathesis/targets.py CHANGED
@@ -1,14 +1,13 @@
1
+ from dataclasses import dataclass
1
2
  from typing import TYPE_CHECKING, Callable, Tuple
2
3
 
3
- import attr
4
-
5
4
  from .utils import GenericResponse
6
5
 
7
6
  if TYPE_CHECKING:
8
7
  from .models import Case
9
8
 
10
9
 
11
- @attr.s(slots=True) # pragma: no mutate
10
+ @dataclass
12
11
  class TargetContext:
13
12
  """Context for targeted testing.
14
13
 
@@ -17,9 +16,9 @@ class TargetContext:
17
16
  :ivar float response_time: API response time.
18
17
  """
19
18
 
20
- case: "Case" = attr.ib() # pragma: no mutate
21
- response: GenericResponse = attr.ib() # pragma: no mutate
22
- response_time: float = attr.ib() # pragma: no mutate
19
+ case: "Case"
20
+ response: GenericResponse
21
+ response_time: float
23
22
 
24
23
 
25
24
  def response_time(context: TargetContext) -> float:
schemathesis/types.py CHANGED
@@ -7,15 +7,15 @@ if TYPE_CHECKING:
7
7
  from . import DataGenerationMethod
8
8
  from .hooks import HookContext
9
9
 
10
- PathLike = Union[Path, str] # pragma: no mutate
10
+ PathLike = Union[Path, str]
11
11
 
12
- Query = Dict[str, Any] # pragma: no mutate
12
+ Query = Dict[str, Any]
13
13
  # Body can be of any Python type that corresponds to JSON Schema types + `bytes`
14
- Body = Union[List, Dict[str, Any], str, int, float, bool, bytes] # pragma: no mutate
15
- PathParameters = Dict[str, Any] # pragma: no mutate
16
- Headers = Dict[str, Any] # pragma: no mutate
17
- Cookies = Dict[str, Any] # pragma: no mutate
18
- FormData = Dict[str, Any] # pragma: no mutate
14
+ Body = Union[List, Dict[str, Any], str, int, float, bool, bytes]
15
+ PathParameters = Dict[str, Any]
16
+ Headers = Dict[str, Any]
17
+ Cookies = Dict[str, Any]
18
+ FormData = Dict[str, Any]
19
19
 
20
20
 
21
21
  class NotSet:
@@ -26,13 +26,11 @@ RequestCert = Union[str, Tuple[str, str]]
26
26
 
27
27
 
28
28
  # A filter for path / method
29
- Filter = Union[str, List[str], Tuple[str], Set[str], NotSet] # pragma: no mutate
29
+ Filter = Union[str, List[str], Tuple[str], Set[str], NotSet]
30
30
 
31
- Hook = Union[
32
- Callable[[SearchStrategy], SearchStrategy], Callable[[SearchStrategy, "HookContext"], SearchStrategy]
33
- ] # pragma: no mutate
31
+ Hook = Union[Callable[[SearchStrategy], SearchStrategy], Callable[[SearchStrategy, "HookContext"], SearchStrategy]]
34
32
 
35
- RawAuth = Tuple[str, str] # pragma: no mutate
33
+ RawAuth = Tuple[str, str]
36
34
  # Generic test with any arguments and no return
37
- GenericTest = Callable[..., None] # pragma: no mutate
35
+ GenericTest = Callable[..., None]
38
36
  DataGenerationMethodInput = Union["DataGenerationMethod", Iterable["DataGenerationMethod"]]
schemathesis/utils.py CHANGED
@@ -80,7 +80,7 @@ def is_latin_1_encodable(value: str) -> bool:
80
80
 
81
81
 
82
82
  # Adapted from http.client._is_illegal_header_value
83
- INVALID_HEADER_RE = re.compile(r"\n(?![ \t])|\r(?![ \t\n])") # pragma: no mutate
83
+ INVALID_HEADER_RE = re.compile(r"\n(?![ \t])|\r(?![ \t\n])")
84
84
 
85
85
 
86
86
  def has_invalid_characters(name: str, value: str) -> bool:
@@ -258,7 +258,7 @@ def get_requests_auth(auth: Optional[RawAuth], auth_type: Optional[str]) -> Opti
258
258
  return auth
259
259
 
260
260
 
261
- GenericResponse = Union[requests.Response, WSGIResponse] # pragma: no mutate
261
+ GenericResponse = Union[requests.Response, WSGIResponse]
262
262
 
263
263
 
264
264
  def copy_response(response: GenericResponse) -> GenericResponse: