1+ import dataclasses
12import inspect
2- from collections .abc import Callable , Iterator
3+ from collections .abc import Callable , Iterator , Mapping
34from contextlib import ExitStack , contextmanager
4- from typing import Any , NamedTuple , TypeGuard , TypeVar , final
5+ from typing import Any , TypeGuard , TypeVar , final , overload
56
67import pytest
78from hypothesis import given
1011from hypothesis .strategies ._internal import types # noqa: PLC2701
1112
1213from returns .contrib .hypothesis .containers import strategy_from_container
14+ from returns .contrib .hypothesis .type_resolver import (
15+ StrategyFactory ,
16+ strategies_for_types ,
17+ )
1318from returns .primitives .laws import LAWS_ATTRIBUTE , Law , Lawful
1419
20+ Example_co = TypeVar ('Example_co' , covariant = True )
21+
1522
1623@final
17- class _Settings (NamedTuple ):
24+ @dataclasses .dataclass (frozen = True )
25+ class _Settings :
1826 """Settings that we provide to an end user."""
1927
2028 settings_kwargs : dict [str , Any ]
2129 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+ )
2239
2340
41+ @overload
2442def 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 ]],
2653 * ,
2754 settings_kwargs : dict [str , Any ] | None = None ,
2855 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 ,
2965) -> None :
3066 """
3167 Function to check all defined mathematical laws in a specified container.
@@ -56,6 +92,7 @@ def check_all_laws(
5692 settings = _Settings (
5793 settings_kwargs or {},
5894 use_init ,
95+ container_strategy ,
5996 )
6097
6198 for interface , laws in container_type .laws ().items ():
@@ -69,62 +106,39 @@ def check_all_laws(
69106
70107
71108@contextmanager
72- def container_strategies (
109+ def interface_strategies (
73110 container_type : type [Lawful ],
74111 * ,
75112 settings : _Settings ,
76113) -> Iterator [None ]:
77114 """
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.
79116
80117 For example, let's say we have ``Result`` type.
81118 It is a subtype of ``ContainerN``, ``MappableN``, ``BindableN``, etc.
82119 When we check this type, we need ``MappableN`` to resolve to ``Result``.
83120
84121 Can be used independently from other functions.
85122 """
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 ):
97128 yield
98- finally :
99- for interface in our_interfaces :
100- types ._global_type_lookup .pop (interface ) # noqa: SLF001
101- _clean_caches ()
102129
103130
104131@contextmanager
105132def register_container (
106133 container_type : type ['Lawful' ],
107134 * ,
108- use_init : bool ,
135+ settings : _Settings ,
109136) -> Iterator [None ]:
110137 """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+ }):
121141 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 ()
128142
129143
130144@contextmanager
@@ -240,6 +254,17 @@ def _clean_caches() -> None:
240254 st .from_type .__clear_cache () # type: ignore[attr-defined] # noqa: SLF001
241255
242256
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+
243268def _run_law (
244269 container_type : type [Lawful ],
245270 law : Law ,
@@ -252,10 +277,10 @@ def factory(source: st.DataObject) -> None:
252277 stack .enter_context (type_vars ())
253278 stack .enter_context (pure_functions ())
254279 stack .enter_context (
255- container_strategies (container_type , settings = settings ),
280+ interface_strategies (container_type , settings = settings ),
256281 )
257282 stack .enter_context (
258- register_container (container_type , use_init = settings . use_init ),
283+ register_container (container_type , settings = settings ),
259284 )
260285 source .draw (st .builds (law .definition ))
261286
0 commit comments