99import os
1010import pickle # for local caching
1111from datetime import datetime
12- from typing import Any , Dict , Mapping , Optional , Tuple , Union
12+ from typing import Any , Dict , Optional , Tuple , Union
1313
1414import portalocker # to lock on pickle cache IO
1515from watchdog .events import PatternMatchingEventHandler
@@ -68,21 +68,22 @@ def on_modified(self, event) -> None:
6868
6969 def __init__ (
7070 self ,
71- hash_func : HashFunc ,
72- pickle_reload : bool ,
73- cache_dir : str ,
74- separate_files : bool ,
75- wait_for_calc_timeout : int ,
71+ hash_func : Optional [ HashFunc ] ,
72+ pickle_reload : Optional [ bool ] ,
73+ cache_dir : Optional [ Union [ str , os . PathLike ]] ,
74+ separate_files : Optional [ bool ] ,
75+ wait_for_calc_timeout : Optional [ int ] ,
7676 ):
7777 super ().__init__ (hash_func , wait_for_calc_timeout )
78- self .cache = None
78+ self ._cache_dict : Dict [ str , CacheEntry ] = {}
7979 self .reload = _update_with_defaults (pickle_reload , "pickle_reload" )
8080 self .cache_dir = os .path .expanduser (
8181 _update_with_defaults (cache_dir , "cache_dir" )
8282 )
8383 self .separate_files = _update_with_defaults (
8484 separate_files , "separate_files"
8585 )
86+ self ._cache_used_fpath = ""
8687
8788 @property
8889 def cache_fname (self ) -> str :
@@ -110,27 +111,30 @@ def _convert_legacy_cache_entry(
110111 _condition = entry .get ("condition" , None ),
111112 )
112113
113- def _load_cache (self ) -> Mapping [str , CacheEntry ]:
114+ def _load_cache_dict (self ) -> Dict [str , CacheEntry ]:
114115 try :
115116 with portalocker .Lock (self .cache_fpath , mode = "rb" ) as cf :
116117 cache = pickle .load (cf ) # noqa: S301
118+ self ._cache_used_fpath = str (self .cache_fpath )
117119 except (FileNotFoundError , EOFError ):
118120 cache = {}
119121 return {
120122 k : _PickleCore ._convert_legacy_cache_entry (v )
121123 for k , v in cache .items ()
122124 }
123125
124- def _reload_cache (self ) -> None :
126+ def get_cache_dict (self , reload : bool = False ) -> Dict [str , CacheEntry ]:
127+ if self ._cache_used_fpath != self .cache_fpath :
128+ # force reload if the cache file has changed
129+ # this change is dies to using different wrapped function
130+ reload = True
131+ if self ._cache_dict and not (self .reload or reload ):
132+ return self ._cache_dict
125133 with self .lock :
126- self .cache = self ._load_cache ()
134+ self ._cache_dict = self ._load_cache_dict ()
135+ return self ._cache_dict
127136
128- def _get_cache (self ) -> Dict [str , CacheEntry ]:
129- if not self .cache :
130- self ._reload_cache ()
131- return self .cache
132-
133- def _get_cache_by_key (
137+ def _load_cache_by_key (
134138 self , key = None , hash_str = None
135139 ) -> Optional [CacheEntry ]:
136140 fpath = self .cache_fpath
@@ -152,35 +156,42 @@ def _clear_being_calculated_all_cache_files(self) -> None:
152156 path , name = os .path .split (self .cache_fpath )
153157 for subpath in os .listdir (path ):
154158 if subpath .startswith (name ):
155- entry = self ._get_cache_by_key (hash_str = subpath .split ("_" )[- 1 ])
159+ entry = self ._load_cache_by_key (
160+ hash_str = subpath .split ("_" )[- 1 ]
161+ )
156162 if entry is not None :
157- entry .being_calculated = False
163+ entry ._processing = False
158164 self ._save_cache (entry , hash_str = subpath .split ("_" )[- 1 ])
159165
160166 def _save_cache (
161- self , cache , key : str = None , hash_str : str = None
167+ self ,
168+ cache : Union [Dict [str , CacheEntry ], CacheEntry ],
169+ separate_file_key : Optional [str ] = None ,
170+ hash_str : Optional [str ] = None ,
162171 ) -> None :
172+ if separate_file_key and not isinstance (cache , CacheEntry ):
173+ raise ValueError (
174+ "`separate_file_key` should only be used with a CacheEntry"
175+ )
163176 fpath = self .cache_fpath
164- if key is not None :
165- fpath += f"_{ key } "
177+ if separate_file_key is not None :
178+ fpath += f"_{ separate_file_key } "
166179 elif hash_str is not None :
167180 fpath += f"_{ hash_str } "
168181 with self .lock :
169- self .cache = cache
170- with portalocker .Lock (fpath , mode = "wb" ) as cache_file :
171- pickle .dump (cache , cache_file , protocol = 4 )
172- if key is None :
173- self ._reload_cache ()
182+ with portalocker .Lock (fpath , mode = "wb" ) as cf :
183+ pickle .dump (cache , cf , protocol = 4 )
184+ # the same as check for separate_file, but changed for typing
185+ if isinstance (cache , dict ):
186+ self ._cache_dict = cache
187+ self ._cache_used_fpath = str (self .cache_fpath )
174188
175189 def get_entry_by_key (
176190 self , key : str , reload : bool = False
177- ) -> Tuple [str , CacheEntry ]:
178- with self .lock :
179- if self .separate_files :
180- return key , self ._get_cache_by_key (key )
181- if self .reload or reload :
182- self ._reload_cache ()
183- return key , self ._get_cache ().get (key , None )
191+ ) -> Tuple [str , Optional [CacheEntry ]]:
192+ if self .separate_files :
193+ return key , self ._load_cache_by_key (key )
194+ return key , self .get_cache_dict (reload ).get (key )
184195
185196 def set_entry (self , key : str , func_res : Any ) -> None :
186197 key_data = CacheEntry (
@@ -195,7 +206,7 @@ def set_entry(self, key: str, func_res: Any) -> None:
195206 return # pragma: no cover
196207
197208 with self .lock :
198- cache = self ._get_cache ()
209+ cache = self .get_cache_dict ()
199210 cache [key ] = key_data
200211 self ._save_cache (cache )
201212
@@ -207,21 +218,23 @@ def mark_entry_being_calculated_separate_files(self, key: str) -> None:
207218 stale = False ,
208219 _processing = True ,
209220 ),
210- key = key ,
221+ separate_file_key = key ,
211222 )
212223
213- def mark_entry_not_calculated_separate_files (self , key : str ) -> None :
224+ def _mark_entry_not_calculated_separate_files (self , key : str ) -> None :
214225 _ , entry = self .get_entry_by_key (key )
226+ if entry is None :
227+ return # that's ok, we don't need an entry in that case
215228 entry ._processing = False
216- self ._save_cache (entry , key = key )
229+ self ._save_cache (entry , separate_file_key = key )
217230
218231 def mark_entry_being_calculated (self , key : str ) -> None :
219232 if self .separate_files :
220233 self .mark_entry_being_calculated_separate_files (key )
221234 return # pragma: no cover
222235
223236 with self .lock :
224- cache = self ._get_cache ()
237+ cache = self .get_cache_dict ()
225238 if key in cache :
226239 cache [key ]._processing = True
227240 else :
@@ -235,24 +248,23 @@ def mark_entry_being_calculated(self, key: str) -> None:
235248
236249 def mark_entry_not_calculated (self , key : str ) -> None :
237250 if self .separate_files :
238- self .mark_entry_not_calculated_separate_files (key )
251+ self ._mark_entry_not_calculated_separate_files (key )
239252 with self .lock :
240- cache = self ._get_cache ()
253+ cache = self .get_cache_dict ()
241254 # that's ok, we don't need an entry in that case
242255 if isinstance (cache , dict ) and key in cache :
243256 cache [key ]._processing = False
244257 self ._save_cache (cache )
245258
246259 def wait_on_entry_calc (self , key : str ) -> Any :
247260 if self .separate_files :
248- entry = self ._get_cache_by_key (key )
261+ entry = self ._load_cache_by_key (key )
249262 filename = f"{ self .cache_fname } _{ key } "
250263 else :
251264 with self .lock :
252- self ._reload_cache ()
253- entry = self ._get_cache ()[key ]
265+ entry = self .get_cache_dict ()[key ]
254266 filename = self .cache_fname
255- if not entry ._processing :
267+ if entry and not entry ._processing :
256268 return entry .value
257269 event_handler = _PickleCore .CacheChangeHandler (
258270 filename = filename , core = self , key = key
@@ -280,7 +292,7 @@ def clear_being_calculated(self) -> None:
280292 return # pragma: no cover
281293
282294 with self .lock :
283- cache = self ._get_cache ()
295+ cache = self .get_cache_dict ()
284296 for key in cache :
285297 cache [key ]._processing = False
286298 self ._save_cache (cache )
0 commit comments