12
12
import warnings
13
13
from pathlib import Path
14
14
15
+ import _hypothesis_globals
15
16
import hypothesis
16
17
from hypothesis .errors import HypothesisSideeffectWarning
17
18
@@ -26,14 +27,9 @@ def set_hypothesis_home_dir(directory):
26
27
27
28
28
29
def storage_directory (* names , intent_to_write = True ):
29
- if intent_to_write and sideeffect_should_warn ():
30
- warnings .warn (
31
- "Accessing the storage directory during import or initialization is "
32
- "discouraged, as it may cause the .hypothesis directory to be created "
33
- "even if hypothesis is not actually used. Typically, the fix will be "
34
- "to defer initialization of strategies." ,
35
- HypothesisSideeffectWarning ,
36
- stacklevel = 2 ,
30
+ if intent_to_write :
31
+ check_sideeffect_during_initialization (
32
+ f"accessing storage for { '/' .join (names )} "
37
33
)
38
34
39
35
global __hypothesis_home_directory
@@ -45,30 +41,43 @@ def storage_directory(*names, intent_to_write=True):
45
41
return __hypothesis_home_directory .joinpath (* names )
46
42
47
43
48
- def _sideeffect_never_warn ():
49
- return False
50
-
51
-
52
- if os .environ .get ("HYPOTHESIS_WARN_SIDEEFFECT" ):
53
-
54
- def sideeffect_should_warn ():
55
- return True
44
+ _first_postinit_what = None
56
45
57
- else :
58
46
59
- def sideeffect_should_warn ():
60
- if hasattr (hypothesis , "_is_importing" ):
61
- return True
62
- else :
63
- # We are no longer importing, patch this method to always return False from now on.
64
- global sideeffect_should_warn
65
- sideeffect_should_warn = _sideeffect_never_warn
66
- return False
67
-
68
-
69
- def has_sideeffect_should_warn_been_called_after_import ():
70
- """We warn automatically if sideeffects are induced during import.
71
- For sideeffects during initialization but after import, e.g. in pytest
72
- plugins, this method can be used to show a catch-all warning at
73
- start of session."""
74
- return sideeffect_should_warn == _sideeffect_never_warn
47
+ def check_sideeffect_during_initialization (what : str , extra : str = "" ) -> None :
48
+ """Called from locations that should not be executed during initialization, for example
49
+ touching disk or materializing lazy/deferred strategies from plugins. If initialization
50
+ is in progress, a warning is emitted."""
51
+ global _first_postinit_what
52
+ # This is not a particularly hot path, but neither is it doing productive work, so we want to
53
+ # minimize the cost by returning immediately. The drawback is that we require
54
+ # notice_initialization_restarted() to be called if in_initialization changes away from zero.
55
+ if _first_postinit_what is not None :
56
+ return
57
+ elif _hypothesis_globals .in_initialization :
58
+ # Note: -Werror is insufficient under pytest, as doesn't take effect until
59
+ # test session start.
60
+ warnings .warn (
61
+ f"Slow code in plugin: avoid { what } at import time! Set PYTHONWARNINGS=error "
62
+ "to get a traceback and show which plugin is responsible." + extra ,
63
+ HypothesisSideeffectWarning ,
64
+ stacklevel = 3 ,
65
+ )
66
+ else :
67
+ _first_postinit_what = what
68
+
69
+
70
+ def notice_initialization_restarted (warn : bool = True ) -> None :
71
+ """Reset _first_postinit_what, so that we don't think we're in post-init. Additionally, if it
72
+ was set that means that there has been a sideeffect that we haven't warned about, so do that
73
+ now (the warning text will be correct, and we also hint that the stacktrace can be improved).
74
+ """
75
+ global _first_postinit_what
76
+ if _first_postinit_what is not None :
77
+ what = _first_postinit_what
78
+ _first_postinit_what = None
79
+ if warn :
80
+ check_sideeffect_during_initialization (
81
+ what ,
82
+ " Additionally, set HYPOTHESIS_EXTEND_INITIALIZATION=1 to pinpoint the exact location." ,
83
+ )
0 commit comments