1
+ import dataclasses
1
2
import inspect
2
- from collections .abc import Callable , Iterator
3
+ from collections .abc import Callable , Iterator , Mapping
3
4
from contextlib import ExitStack , contextmanager
4
- from typing import Any , NamedTuple , TypeGuard , TypeVar , final
5
+ from typing import Any , TypeGuard , TypeVar , final , overload
5
6
6
7
import pytest
7
8
from hypothesis import given
10
11
from hypothesis .strategies ._internal import types # noqa: PLC2701
11
12
12
13
from returns .contrib .hypothesis .containers import strategy_from_container
14
+ from returns .contrib .hypothesis .type_resolver import (
15
+ StrategyFactory ,
16
+ strategies_for_types ,
17
+ )
13
18
from returns .primitives .laws import LAWS_ATTRIBUTE , Law , Lawful
14
19
20
+ Example_co = TypeVar ('Example_co' , covariant = True )
21
+
15
22
16
23
@final
17
- class _Settings (NamedTuple ):
24
+ @dataclasses .dataclass (frozen = True )
25
+ class _Settings :
18
26
"""Settings that we provide to an end user."""
19
27
20
28
settings_kwargs : dict [str , Any ]
21
29
use_init : bool
30
+ container_strategy : StrategyFactory | None
31
+
32
+ def __post_init__ (self ) -> None :
33
+ """Check that the settings are mutually compatible."""
34
+ if self .use_init and self .container_strategy is not None :
35
+ raise AssertionError (
36
+ 'Expected only one of `use_init` and'
37
+ ' `container_strategy` to be truthy'
38
+ )
22
39
23
40
41
+ @overload
24
42
def check_all_laws (
25
- container_type : type [Lawful ],
43
+ container_type : type [Lawful [Example_co ]],
44
+ * ,
45
+ settings_kwargs : dict [str , Any ] | None = None ,
46
+ container_strategy : StrategyFactory [Example_co ] | None = None ,
47
+ ) -> None : ...
48
+
49
+
50
+ @overload
51
+ def check_all_laws (
52
+ container_type : type [Lawful [Example_co ]],
26
53
* ,
27
54
settings_kwargs : dict [str , Any ] | None = None ,
28
55
use_init : bool = False ,
56
+ ) -> None : ...
57
+
58
+
59
+ def check_all_laws (
60
+ container_type : type [Lawful [Example_co ]],
61
+ * ,
62
+ settings_kwargs : dict [str , Any ] | None = None ,
63
+ use_init : bool = False ,
64
+ container_strategy : StrategyFactory [Example_co ] | None = None ,
29
65
) -> None :
30
66
"""
31
67
Function to check all defined mathematical laws in a specified container.
@@ -56,6 +92,7 @@ def check_all_laws(
56
92
settings = _Settings (
57
93
settings_kwargs or {},
58
94
use_init ,
95
+ container_strategy ,
59
96
)
60
97
61
98
for interface , laws in container_type .laws ().items ():
@@ -69,62 +106,39 @@ def check_all_laws(
69
106
70
107
71
108
@contextmanager
72
- def container_strategies (
109
+ def interface_strategies (
73
110
container_type : type [Lawful ],
74
111
* ,
75
112
settings : _Settings ,
76
113
) -> Iterator [None ]:
77
114
"""
78
- Registers all types inside a container to resolve to a correct strategy.
115
+ Make all interfaces of a container resolve to the container's strategy.
79
116
80
117
For example, let's say we have ``Result`` type.
81
118
It is a subtype of ``ContainerN``, ``MappableN``, ``BindableN``, etc.
82
119
When we check this type, we need ``MappableN`` to resolve to ``Result``.
83
120
84
121
Can be used independently from other functions.
85
122
"""
86
- our_interfaces = lawful_interfaces (container_type )
87
- for interface in our_interfaces :
88
- st .register_type_strategy (
89
- interface ,
90
- strategy_from_container (
91
- container_type ,
92
- use_init = settings .use_init ,
93
- ),
94
- )
95
-
96
- try :
123
+ mapping : Mapping [type [object ], StrategyFactory ] = {
124
+ interface : _strategy_for_container (container_type , settings )
125
+ for interface in lawful_interfaces (container_type )
126
+ }
127
+ with strategies_for_types (mapping ):
97
128
yield
98
- finally :
99
- for interface in our_interfaces :
100
- types ._global_type_lookup .pop (interface ) # noqa: SLF001
101
- _clean_caches ()
102
129
103
130
104
131
@contextmanager
105
132
def register_container (
106
133
container_type : type ['Lawful' ],
107
134
* ,
108
- use_init : bool ,
135
+ settings : _Settings ,
109
136
) -> Iterator [None ]:
110
137
"""Temporary registers a container if it is not registered yet."""
111
- used = types ._global_type_lookup .pop (container_type , None ) # noqa: SLF001
112
- st .register_type_strategy (
113
- container_type ,
114
- strategy_from_container (
115
- container_type ,
116
- use_init = use_init ,
117
- ),
118
- )
119
-
120
- try :
138
+ with strategies_for_types ({
139
+ container_type : _strategy_for_container (container_type , settings )
140
+ }):
121
141
yield
122
- finally :
123
- types ._global_type_lookup .pop (container_type ) # noqa: SLF001
124
- if used :
125
- st .register_type_strategy (container_type , used )
126
- else :
127
- _clean_caches ()
128
142
129
143
130
144
@contextmanager
@@ -240,6 +254,17 @@ def _clean_caches() -> None:
240
254
st .from_type .__clear_cache () # type: ignore[attr-defined] # noqa: SLF001
241
255
242
256
257
+ def _strategy_for_container (
258
+ container_type : type [Lawful ],
259
+ settings : _Settings ,
260
+ ) -> StrategyFactory :
261
+ return (
262
+ strategy_from_container (container_type , use_init = settings .use_init )
263
+ if settings .container_strategy is None
264
+ else settings .container_strategy
265
+ )
266
+
267
+
243
268
def _run_law (
244
269
container_type : type [Lawful ],
245
270
law : Law ,
@@ -252,10 +277,10 @@ def factory(source: st.DataObject) -> None:
252
277
stack .enter_context (type_vars ())
253
278
stack .enter_context (pure_functions ())
254
279
stack .enter_context (
255
- container_strategies (container_type , settings = settings ),
280
+ interface_strategies (container_type , settings = settings ),
256
281
)
257
282
stack .enter_context (
258
- register_container (container_type , use_init = settings . use_init ),
283
+ register_container (container_type , settings = settings ),
259
284
)
260
285
source .draw (st .builds (law .definition ))
261
286
0 commit comments