Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/joblib/memory.py: 24%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

398 statements  

1""" 

2A context object for caching a function's return value each time it 

3is called with the same input arguments. 

4 

5""" 

6 

7# Author: Gael Varoquaux <gael dot varoquaux at normalesup dot org> 

8# Copyright (c) 2009 Gael Varoquaux 

9# License: BSD Style, 3 clauses. 

10 

11import asyncio 

12import datetime 

13import functools 

14import inspect 

15import logging 

16import os 

17import pathlib 

18import pydoc 

19import re 

20import textwrap 

21import time 

22import tokenize 

23import traceback 

24import warnings 

25import weakref 

26 

27from . import hashing 

28from ._store_backends import ( 

29 CacheWarning, # noqa 

30 FileSystemStoreBackend, 

31 StoreBackendBase, 

32) 

33from .func_inspect import ( 

34 filter_args, 

35 format_call, 

36 format_signature, 

37 get_func_code, 

38 get_func_name, 

39) 

40from .logger import Logger, format_time, pformat 

41 

42FIRST_LINE_TEXT = "# first line:" 

43 

44# TODO: The following object should have a data store object as a sub 

45# object, and the interface to persist and query should be separated in 

46# the data store. 

47# 

48# This would enable creating 'Memory' objects with a different logic for 

49# pickling that would simply span a MemorizedFunc with the same 

50# store (or do we want to copy it to avoid cross-talks?), for instance to 

51# implement HDF5 pickling. 

52 

53# TODO: Same remark for the logger, and probably use the Python logging 

54# mechanism. 

55 

56 

57def extract_first_line(func_code): 

58 """Extract the first line information from the function code 

59 text if available. 

60 """ 

61 if func_code.startswith(FIRST_LINE_TEXT): 

62 func_code = func_code.split("\n") 

63 first_line = int(func_code[0][len(FIRST_LINE_TEXT) :]) 

64 func_code = "\n".join(func_code[1:]) 

65 else: 

66 first_line = -1 

67 return func_code, first_line 

68 

69 

70class JobLibCollisionWarning(UserWarning): 

71 """Warn that there might be a collision between names of functions.""" 

72 

73 

74_STORE_BACKENDS = {"local": FileSystemStoreBackend} 

75 

76 

77def register_store_backend(backend_name, backend): 

78 """Extend available store backends. 

79 

80 The Memory, MemorizeResult and MemorizeFunc objects are designed to be 

81 agnostic to the type of store used behind. By default, the local file 

82 system is used but this function gives the possibility to extend joblib's 

83 memory pattern with other types of storage such as cloud storage (S3, GCS, 

84 OpenStack, HadoopFS, etc) or blob DBs. 

85 

86 Parameters 

87 ---------- 

88 backend_name: str 

89 The name identifying the store backend being registered. For example, 

90 'local' is used with FileSystemStoreBackend. 

91 backend: StoreBackendBase subclass 

92 The name of a class that implements the StoreBackendBase interface. 

93 

94 """ 

95 if not isinstance(backend_name, str): 

96 raise ValueError( 

97 "Store backend name should be a string, '{0}' given.".format(backend_name) 

98 ) 

99 if backend is None or not issubclass(backend, StoreBackendBase): 

100 raise ValueError( 

101 "Store backend should inherit StoreBackendBase, '{0}' given.".format( 

102 backend 

103 ) 

104 ) 

105 

106 _STORE_BACKENDS[backend_name] = backend 

107 

108 

109def _store_backend_factory(backend, location, verbose=0, backend_options=None): 

110 """Return the correct store object for the given location.""" 

111 if backend_options is None: 

112 backend_options = {} 

113 

114 if isinstance(location, pathlib.Path): 

115 location = str(location) 

116 

117 if isinstance(location, StoreBackendBase): 

118 return location 

119 elif isinstance(location, str): 

120 obj = None 

121 location = os.path.expanduser(location) 

122 # The location is not a local file system, we look in the 

123 # registered backends if there's one matching the given backend 

124 # name. 

125 for backend_key, backend_obj in _STORE_BACKENDS.items(): 

126 if backend == backend_key: 

127 obj = backend_obj() 

128 

129 # By default, we assume the FileSystemStoreBackend can be used if no 

130 # matching backend could be found. 

131 if obj is None: 

132 raise TypeError( 

133 "Unknown location {0} or backend {1}".format(location, backend) 

134 ) 

135 

136 # The store backend is configured with the extra named parameters, 

137 # some of them are specific to the underlying store backend. 

138 obj.configure(location, verbose=verbose, backend_options=backend_options) 

139 return obj 

140 elif location is not None: 

141 warnings.warn( 

142 "Instantiating a backend using a {} as a location is not " 

143 "supported by joblib. Returning None instead.".format( 

144 location.__class__.__name__ 

145 ), 

146 UserWarning, 

147 ) 

148 

149 return None 

150 

151 

152def _build_func_identifier(func): 

153 """Build a roughly unique identifier for the cached function.""" 

154 modules, funcname = get_func_name(func) 

155 # We reuse historical fs-like way of building a function identifier 

156 return os.path.join(*modules, funcname) 

157 

158 

159# An in-memory store to avoid looking at the disk-based function 

160# source code to check if a function definition has changed 

161_FUNCTION_HASHES = weakref.WeakKeyDictionary() 

162 

163 

164############################################################################### 

165# class `MemorizedResult` 

166############################################################################### 

167class MemorizedResult(Logger): 

168 """Object representing a cached value. 

169 

170 Attributes 

171 ---------- 

172 location: str 

173 The location of joblib cache. Depends on the store backend used. 

174 

175 func: function or str 

176 function whose output is cached. The string case is intended only for 

177 instantiation based on the output of repr() on another instance. 

178 (namely eval(repr(memorized_instance)) works). 

179 

180 argument_hash: str 

181 hash of the function arguments. 

182 

183 backend: str 

184 Type of store backend for reading/writing cache files. 

185 Default is 'local'. 

186 

187 mmap_mode: {None, 'r+', 'r', 'w+', 'c'} 

188 The memmapping mode used when loading from cache numpy arrays. See 

189 numpy.load for the meaning of the different values. 

190 

191 verbose: int 

192 verbosity level (0 means no message). 

193 

194 timestamp, metadata: string 

195 for internal use only. 

196 """ 

197 

198 def __init__( 

199 self, 

200 location, 

201 call_id, 

202 backend="local", 

203 mmap_mode=None, 

204 verbose=0, 

205 timestamp=None, 

206 metadata=None, 

207 ): 

208 Logger.__init__(self) 

209 self._call_id = call_id 

210 self.store_backend = _store_backend_factory( 

211 backend, 

212 location, 

213 verbose=verbose, 

214 backend_options=dict(mmap_mode=mmap_mode), 

215 ) 

216 self.mmap_mode = mmap_mode 

217 

218 if metadata is not None: 

219 self.metadata = metadata 

220 else: 

221 self.metadata = self.store_backend.get_metadata(self._call_id) 

222 

223 self.duration = self.metadata.get("duration", None) 

224 self.verbose = verbose 

225 self.timestamp = timestamp 

226 

227 @property 

228 def func(self): 

229 return self.func_id 

230 

231 @property 

232 def func_id(self): 

233 return self._call_id[0] 

234 

235 @property 

236 def args_id(self): 

237 return self._call_id[1] 

238 

239 def get(self): 

240 """Read value from cache and return it.""" 

241 try: 

242 return self.store_backend.load_item( 

243 self._call_id, 

244 timestamp=self.timestamp, 

245 metadata=self.metadata, 

246 verbose=self.verbose, 

247 ) 

248 except ValueError as exc: 

249 new_exc = KeyError( 

250 "Error while trying to load a MemorizedResult's value. " 

251 "It seems that this folder is corrupted : {}".format( 

252 os.path.join(self.store_backend.location, *self._call_id) 

253 ) 

254 ) 

255 raise new_exc from exc 

256 

257 def clear(self): 

258 """Clear value from cache""" 

259 self.store_backend.clear_item(self._call_id) 

260 

261 def __repr__(self): 

262 return '{}(location="{}", func="{}", args_id="{}")'.format( 

263 self.__class__.__name__, self.store_backend.location, *self._call_id 

264 ) 

265 

266 def __getstate__(self): 

267 state = self.__dict__.copy() 

268 state["timestamp"] = None 

269 return state 

270 

271 

272class NotMemorizedResult(object): 

273 """Class representing an arbitrary value. 

274 

275 This class is a replacement for MemorizedResult when there is no cache. 

276 """ 

277 

278 __slots__ = ("value", "valid") 

279 

280 def __init__(self, value): 

281 self.value = value 

282 self.valid = True 

283 

284 def get(self): 

285 if self.valid: 

286 return self.value 

287 else: 

288 raise KeyError("No value stored.") 

289 

290 def clear(self): 

291 self.valid = False 

292 self.value = None 

293 

294 def __repr__(self): 

295 if self.valid: 

296 return "{class_name}({value})".format( 

297 class_name=self.__class__.__name__, value=pformat(self.value) 

298 ) 

299 else: 

300 return self.__class__.__name__ + " with no value" 

301 

302 # __getstate__ and __setstate__ are required because of __slots__ 

303 def __getstate__(self): 

304 return {"valid": self.valid, "value": self.value} 

305 

306 def __setstate__(self, state): 

307 self.valid = state["valid"] 

308 self.value = state["value"] 

309 

310 

311############################################################################### 

312# class `NotMemorizedFunc` 

313############################################################################### 

314class NotMemorizedFunc(object): 

315 """No-op object decorating a function. 

316 

317 This class replaces MemorizedFunc when there is no cache. It provides an 

318 identical API but does not write anything on disk. 

319 

320 Attributes 

321 ---------- 

322 func: callable 

323 Original undecorated function. 

324 """ 

325 

326 # Should be a light as possible (for speed) 

327 def __init__(self, func): 

328 self.func = func 

329 

330 def __call__(self, *args, **kwargs): 

331 return self.func(*args, **kwargs) 

332 

333 def call_and_shelve(self, *args, **kwargs): 

334 return NotMemorizedResult(self.func(*args, **kwargs)) 

335 

336 def __repr__(self): 

337 return "{0}(func={1})".format(self.__class__.__name__, self.func) 

338 

339 def clear(self, warn=True): 

340 # Argument "warn" is for compatibility with MemorizedFunc.clear 

341 pass 

342 

343 def call(self, *args, **kwargs): 

344 return self.func(*args, **kwargs), {} 

345 

346 def check_call_in_cache(self, *args, **kwargs): 

347 return False 

348 

349 

350############################################################################### 

351# class `AsyncNotMemorizedFunc` 

352############################################################################### 

353class AsyncNotMemorizedFunc(NotMemorizedFunc): 

354 async def call_and_shelve(self, *args, **kwargs): 

355 return NotMemorizedResult(await self.func(*args, **kwargs)) 

356 

357 

358############################################################################### 

359# class `MemorizedFunc` 

360############################################################################### 

361class MemorizedFunc(Logger): 

362 """Callable object decorating a function for caching its return value 

363 each time it is called. 

364 

365 Methods are provided to inspect the cache or clean it. 

366 

367 Attributes 

368 ---------- 

369 func: callable 

370 The original, undecorated, function. 

371 

372 location: string 

373 The location of joblib cache. Depends on the store backend used. 

374 

375 backend: str 

376 Type of store backend for reading/writing cache files. 

377 Default is 'local', in which case the location is the path to a 

378 disk storage. 

379 

380 ignore: list or None 

381 List of variable names to ignore when choosing whether to 

382 recompute. 

383 

384 mmap_mode: {None, 'r+', 'r', 'w+', 'c'} 

385 The memmapping mode used when loading from cache 

386 numpy arrays. See numpy.load for the meaning of the different 

387 values. 

388 

389 compress: boolean, integer or 2-tuple, optional 

390 Whether to zip the stored data on disk. If an integer is 

391 given, it should be between 1 and 9, and sets the amount 

392 of compression. Note that compressed arrays cannot be 

393 read by memmapping. If a 2-tuple is given, the first 

394 element must be a string corresponding to a compression 

395 method (e.g 'zlib', 'gzip', 'bz2', 'lzma' or 'xz') and 

396 the second element must be an integer from 0 to 9, 

397 corresponding to the compression level. 

398 

399 verbose: int, optional 

400 The verbosity flag, controls messages that are issued as 

401 the function is evaluated. 

402 

403 cache_validation_callback: callable, optional 

404 Callable to check if a result in cache is valid or is to be recomputed. 

405 When the function is called with arguments for which a cache exists, 

406 the callback is called with the cache entry's metadata as its sole 

407 argument. If it returns True, the cached result is returned, else the 

408 cache for these arguments is cleared and the result is recomputed. 

409 """ 

410 

411 # ------------------------------------------------------------------------ 

412 # Public interface 

413 # ------------------------------------------------------------------------ 

414 

415 def __init__( 

416 self, 

417 func, 

418 location, 

419 backend="local", 

420 ignore=None, 

421 mmap_mode=None, 

422 compress=False, 

423 verbose=1, 

424 timestamp=None, 

425 cache_validation_callback=None, 

426 ): 

427 Logger.__init__(self) 

428 self.mmap_mode = mmap_mode 

429 self.compress = compress 

430 self.func = func 

431 self.cache_validation_callback = cache_validation_callback 

432 self.func_id = _build_func_identifier(func) 

433 self.ignore = ignore if ignore is not None else [] 

434 self._verbose = verbose 

435 

436 # retrieve store object from backend type and location. 

437 self.store_backend = _store_backend_factory( 

438 backend, 

439 location, 

440 verbose=verbose, 

441 backend_options=dict(compress=compress, mmap_mode=mmap_mode), 

442 ) 

443 if self.store_backend is not None: 

444 # Create func directory on demand. 

445 self.store_backend.store_cached_func_code([self.func_id]) 

446 

447 self.timestamp = timestamp if timestamp is not None else time.time() 

448 try: 

449 functools.update_wrapper(self, func) 

450 except Exception: 

451 pass # Objects like ufunc don't like that 

452 if inspect.isfunction(func): 

453 doc = pydoc.TextDoc().document(func) 

454 # Remove blank line 

455 doc = doc.replace("\n", "\n\n", 1) 

456 # Strip backspace-overprints for compatibility with autodoc 

457 doc = re.sub("\x08.", "", doc) 

458 else: 

459 # Pydoc does a poor job on other objects 

460 doc = func.__doc__ 

461 self.__doc__ = "Memoized version of %s" % doc 

462 

463 self._func_code_info = None 

464 self._func_code_id = None 

465 

466 def _is_in_cache_and_valid(self, call_id): 

467 """Check if the function call is cached and valid for given arguments. 

468 

469 - Compare the function code with the one from the cached function, 

470 asserting if it has changed. 

471 - Check if the function call is present in the cache. 

472 - Call `cache_validation_callback` for user define cache validation. 

473 

474 Returns True if the function call is in cache and can be used, and 

475 returns False otherwise. 

476 """ 

477 # Check if the code of the function has changed 

478 if not self._check_previous_func_code(stacklevel=4): 

479 return False 

480 

481 # Check if this specific call is in the cache 

482 if not self.store_backend.contains_item(call_id): 

483 return False 

484 

485 # Call the user defined cache validation callback 

486 metadata = self.store_backend.get_metadata(call_id) 

487 if ( 

488 self.cache_validation_callback is not None 

489 and not self.cache_validation_callback(metadata) 

490 ): 

491 self.store_backend.clear_item(call_id) 

492 return False 

493 

494 return True 

495 

496 def _cached_call(self, args, kwargs, shelving): 

497 """Call wrapped function and cache result, or read cache if available. 

498 

499 This function returns the wrapped function output or a reference to 

500 the cached result. 

501 

502 Arguments: 

503 ---------- 

504 

505 args, kwargs: list and dict 

506 input arguments for wrapped function 

507 

508 shelving: bool 

509 True when called via the call_and_shelve function. 

510 

511 

512 Returns 

513 ------- 

514 output: Output of the wrapped function if shelving is false, or a 

515 MemorizedResult reference to the value if shelving is true. 

516 metadata: dict containing the metadata associated with the call. 

517 """ 

518 args_id = self._get_args_id(*args, **kwargs) 

519 call_id = (self.func_id, args_id) 

520 _, func_name = get_func_name(self.func) 

521 func_info = self.store_backend.get_cached_func_info([self.func_id]) 

522 location = func_info["location"] 

523 

524 if self._verbose >= 20: 

525 logging.basicConfig(level=logging.INFO) 

526 _, signature = format_signature(self.func, *args, **kwargs) 

527 self.info( 

528 textwrap.dedent( 

529 f""" 

530 Querying {func_name} with signature 

531 {signature}. 

532 

533 (argument hash {args_id}) 

534 

535 The store location is {location}. 

536 """ 

537 ) 

538 ) 

539 

540 # Compare the function code with the previous to see if the 

541 # function code has changed and check if the results are present in 

542 # the cache. 

543 if self._is_in_cache_and_valid(call_id): 

544 if shelving: 

545 return self._get_memorized_result(call_id), {} 

546 

547 try: 

548 start_time = time.time() 

549 output = self._load_item(call_id) 

550 if self._verbose > 4: 

551 self._print_duration( 

552 time.time() - start_time, context="cache loaded " 

553 ) 

554 return output, {} 

555 except Exception: 

556 # XXX: Should use an exception logger 

557 _, signature = format_signature(self.func, *args, **kwargs) 

558 self.warn( 

559 "Exception while loading results for {}\n {}".format( 

560 signature, traceback.format_exc() 

561 ) 

562 ) 

563 

564 if self._verbose > 10: 

565 self.warn( 

566 f"Computing func {func_name}, argument hash {args_id} " 

567 f"in location {location}" 

568 ) 

569 

570 # Returns the output but not the metadata 

571 return self._call(call_id, args, kwargs, shelving) 

572 

573 @property 

574 def func_code_info(self): 

575 # 3-tuple property containing: the function source code, source file, 

576 # and first line of the code inside the source file 

577 if hasattr(self.func, "__code__"): 

578 if self._func_code_id is None: 

579 self._func_code_id = id(self.func.__code__) 

580 elif id(self.func.__code__) != self._func_code_id: 

581 # Be robust to dynamic reassignments of self.func.__code__ 

582 self._func_code_info = None 

583 

584 if self._func_code_info is None: 

585 # Cache the source code of self.func . Provided that get_func_code 

586 # (which should be called once on self) gets called in the process 

587 # in which self.func was defined, this caching mechanism prevents 

588 # undesired cache clearing when the cached function is called in 

589 # an environment where the introspection utilities get_func_code 

590 # relies on do not work (typically, in joblib child processes). 

591 # See #1035 for more info 

592 # TODO (pierreglaser): do the same with get_func_name? 

593 self._func_code_info = get_func_code(self.func) 

594 return self._func_code_info 

595 

596 def call_and_shelve(self, *args, **kwargs): 

597 """Call wrapped function, cache result and return a reference. 

598 

599 This method returns a reference to the cached result instead of the 

600 result itself. The reference object is small and picklable, allowing 

601 to send or store it easily. Call .get() on reference object to get 

602 result. 

603 

604 Returns 

605 ------- 

606 cached_result: MemorizedResult or NotMemorizedResult 

607 reference to the value returned by the wrapped function. The 

608 class "NotMemorizedResult" is used when there is no cache 

609 activated (e.g. location=None in Memory). 

610 """ 

611 # Return the wrapped output, without the metadata 

612 return self._cached_call(args, kwargs, shelving=True)[0] 

613 

614 def __call__(self, *args, **kwargs): 

615 # Return the output, without the metadata 

616 return self._cached_call(args, kwargs, shelving=False)[0] 

617 

618 def __getstate__(self): 

619 # Make sure self.func's source is introspected prior to being pickled - 

620 # code introspection utilities typically do not work inside child 

621 # processes 

622 _ = self.func_code_info 

623 

624 # We don't store the timestamp when pickling, to avoid the hash 

625 # depending from it. 

626 state = self.__dict__.copy() 

627 state["timestamp"] = None 

628 

629 # Invalidate the code id as id(obj) will be different in the child 

630 state["_func_code_id"] = None 

631 

632 return state 

633 

634 def check_call_in_cache(self, *args, **kwargs): 

635 """Check if the function call is cached and valid for given arguments. 

636 

637 Does not call the function or do any work besides function inspection 

638 and argument hashing. 

639 

640 - Compare the function code with the one from the cached function, 

641 asserting if it has changed. 

642 - Check if the function call is present in the cache. 

643 - Call `cache_validation_callback` for user define cache validation. 

644 

645 Returns 

646 ------- 

647 is_call_in_cache: bool 

648 Whether or not the function call is in cache and can be used. 

649 """ 

650 call_id = (self.func_id, self._get_args_id(*args, **kwargs)) 

651 return self._is_in_cache_and_valid(call_id) 

652 

653 # ------------------------------------------------------------------------ 

654 # Private interface 

655 # ------------------------------------------------------------------------ 

656 

657 def _get_args_id(self, *args, **kwargs): 

658 """Return the input parameter hash of a result.""" 

659 return hashing.hash( 

660 filter_args(self.func, self.ignore, args, kwargs), 

661 coerce_mmap=self.mmap_mode is not None, 

662 ) 

663 

664 def _hash_func(self): 

665 """Hash a function to key the online cache""" 

666 func_code_h = hash(getattr(self.func, "__code__", None)) 

667 return id(self.func), hash(self.func), func_code_h 

668 

669 def _write_func_code(self, func_code, first_line): 

670 """Write the function code and the filename to a file.""" 

671 # We store the first line because the filename and the function 

672 # name is not always enough to identify a function: people 

673 # sometimes have several functions named the same way in a 

674 # file. This is bad practice, but joblib should be robust to bad 

675 # practice. 

676 func_code = "%s %i\n%s" % (FIRST_LINE_TEXT, first_line, func_code) 

677 self.store_backend.store_cached_func_code([self.func_id], func_code) 

678 

679 # Also store in the in-memory store of function hashes 

680 is_named_callable = ( 

681 hasattr(self.func, "__name__") and self.func.__name__ != "<lambda>" 

682 ) 

683 if is_named_callable: 

684 # Don't do this for lambda functions or strange callable 

685 # objects, as it ends up being too fragile 

686 func_hash = self._hash_func() 

687 try: 

688 _FUNCTION_HASHES[self.func] = func_hash 

689 except TypeError: 

690 # Some callable are not hashable 

691 pass 

692 

693 def _check_previous_func_code(self, stacklevel=2): 

694 """ 

695 stacklevel is the depth a which this function is called, to 

696 issue useful warnings to the user. 

697 """ 

698 # First check if our function is in the in-memory store. 

699 # Using the in-memory store not only makes things faster, but it 

700 # also renders us robust to variations of the files when the 

701 # in-memory version of the code does not vary 

702 try: 

703 if self.func in _FUNCTION_HASHES: 

704 # We use as an identifier the id of the function and its 

705 # hash. This is more likely to falsely change than have hash 

706 # collisions, thus we are on the safe side. 

707 func_hash = self._hash_func() 

708 if func_hash == _FUNCTION_HASHES[self.func]: 

709 return True 

710 except TypeError: 

711 # Some callables are not hashable 

712 pass 

713 

714 # Here, we go through some effort to be robust to dynamically 

715 # changing code and collision. We cannot inspect.getsource 

716 # because it is not reliable when using IPython's magic "%run". 

717 func_code, source_file, first_line = self.func_code_info 

718 try: 

719 old_func_code, old_first_line = extract_first_line( 

720 self.store_backend.get_cached_func_code([self.func_id]) 

721 ) 

722 except (IOError, OSError): # some backend can also raise OSError 

723 self._write_func_code(func_code, first_line) 

724 return False 

725 if old_func_code == func_code: 

726 return True 

727 

728 # We have differing code, is this because we are referring to 

729 # different functions, or because the function we are referring to has 

730 # changed? 

731 

732 _, func_name = get_func_name( 

733 self.func, resolv_alias=False, win_characters=False 

734 ) 

735 if old_first_line == first_line == -1 or func_name == "<lambda>": 

736 if not first_line == -1: 

737 func_description = "{0} ({1}:{2})".format( 

738 func_name, source_file, first_line 

739 ) 

740 else: 

741 func_description = func_name 

742 warnings.warn( 

743 JobLibCollisionWarning( 

744 "Cannot detect name collisions for function '{0}'".format( 

745 func_description 

746 ) 

747 ), 

748 stacklevel=stacklevel, 

749 ) 

750 

751 # Fetch the code at the old location and compare it. If it is the 

752 # same than the code store, we have a collision: the code in the 

753 # file has not changed, but the name we have is pointing to a new 

754 # code block. 

755 if not old_first_line == first_line and source_file is not None: 

756 if os.path.exists(source_file): 

757 _, func_name = get_func_name(self.func, resolv_alias=False) 

758 num_lines = len(func_code.split("\n")) 

759 with tokenize.open(source_file) as f: 

760 on_disk_func_code = f.readlines()[ 

761 old_first_line - 1 : old_first_line - 1 + num_lines - 1 

762 ] 

763 on_disk_func_code = "".join(on_disk_func_code) 

764 possible_collision = ( 

765 on_disk_func_code.rstrip() == old_func_code.rstrip() 

766 ) 

767 else: 

768 possible_collision = source_file.startswith("<doctest ") 

769 if possible_collision: 

770 warnings.warn( 

771 JobLibCollisionWarning( 

772 "Possible name collisions between functions " 

773 "'%s' (%s:%i) and '%s' (%s:%i)" 

774 % ( 

775 func_name, 

776 source_file, 

777 old_first_line, 

778 func_name, 

779 source_file, 

780 first_line, 

781 ) 

782 ), 

783 stacklevel=stacklevel, 

784 ) 

785 

786 # The function has changed, wipe the cache directory. 

787 # XXX: Should be using warnings, and giving stacklevel 

788 if self._verbose > 10: 

789 _, func_name = get_func_name(self.func, resolv_alias=False) 

790 self.warn( 

791 "Function {0} (identified by {1}) has changed.".format( 

792 func_name, self.func_id 

793 ) 

794 ) 

795 self.clear(warn=True) 

796 return False 

797 

798 def clear(self, warn=True): 

799 """Empty the function's cache.""" 

800 func_id = self.func_id 

801 if self._verbose > 0 and warn: 

802 self.warn("Clearing function cache identified by %s" % func_id) 

803 self.store_backend.clear_path( 

804 [ 

805 func_id, 

806 ] 

807 ) 

808 

809 func_code, _, first_line = self.func_code_info 

810 self._write_func_code(func_code, first_line) 

811 

812 def call(self, *args, **kwargs): 

813 """Force the execution of the function with the given arguments. 

814 

815 The output values will be persisted, i.e., the cache will be updated 

816 with any new values. 

817 

818 Parameters 

819 ---------- 

820 *args: arguments 

821 The arguments. 

822 **kwargs: keyword arguments 

823 Keyword arguments. 

824 

825 Returns 

826 ------- 

827 output : object 

828 The output of the function call. 

829 metadata : dict 

830 The metadata associated with the call. 

831 """ 

832 call_id = (self.func_id, self._get_args_id(*args, **kwargs)) 

833 

834 # Return the output and the metadata 

835 return self._call(call_id, args, kwargs) 

836 

837 def _call(self, call_id, args, kwargs, shelving=False): 

838 # Return the output and the metadata 

839 self._before_call(args, kwargs) 

840 start_time = time.time() 

841 output = self.func(*args, **kwargs) 

842 return self._after_call(call_id, args, kwargs, shelving, output, start_time) 

843 

844 def _before_call(self, args, kwargs): 

845 if self._verbose > 0: 

846 print(format_call(self.func, args, kwargs)) 

847 

848 def _after_call(self, call_id, args, kwargs, shelving, output, start_time): 

849 self.store_backend.dump_item(call_id, output, verbose=self._verbose) 

850 duration = time.time() - start_time 

851 if self._verbose > 0: 

852 self._print_duration(duration) 

853 metadata = self._persist_input(duration, call_id, args, kwargs) 

854 if shelving: 

855 return self._get_memorized_result(call_id, metadata), metadata 

856 

857 if self.mmap_mode is not None: 

858 # Memmap the output at the first call to be consistent with 

859 # later calls 

860 output = self._load_item(call_id, metadata) 

861 return output, metadata 

862 

863 def _persist_input(self, duration, call_id, args, kwargs, this_duration_limit=0.5): 

864 """Save a small summary of the call using json format in the 

865 output directory. 

866 

867 output_dir: string 

868 directory where to write metadata. 

869 

870 duration: float 

871 time taken by hashing input arguments, calling the wrapped 

872 function and persisting its output. 

873 

874 args, kwargs: list and dict 

875 input arguments for wrapped function 

876 

877 this_duration_limit: float 

878 Max execution time for this function before issuing a warning. 

879 """ 

880 start_time = time.time() 

881 argument_dict = filter_args(self.func, self.ignore, args, kwargs) 

882 

883 input_repr = dict((k, repr(v)) for k, v in argument_dict.items()) 

884 # This can fail due to race-conditions with multiple 

885 # concurrent joblibs removing the file or the directory 

886 metadata = { 

887 "duration": duration, 

888 "input_args": input_repr, 

889 "time": start_time, 

890 } 

891 

892 self.store_backend.store_metadata(call_id, metadata) 

893 

894 this_duration = time.time() - start_time 

895 if this_duration > this_duration_limit: 

896 # This persistence should be fast. It will not be if repr() takes 

897 # time and its output is large, because json.dump will have to 

898 # write a large file. This should not be an issue with numpy arrays 

899 # for which repr() always output a short representation, but can 

900 # be with complex dictionaries. Fixing the problem should be a 

901 # matter of replacing repr() above by something smarter. 

902 warnings.warn( 

903 "Persisting input arguments took %.2fs to run." 

904 "If this happens often in your code, it can cause " 

905 "performance problems " 

906 "(results will be correct in all cases). " 

907 "The reason for this is probably some large input " 

908 "arguments for a wrapped function." % this_duration, 

909 stacklevel=5, 

910 ) 

911 return metadata 

912 

913 def _get_memorized_result(self, call_id, metadata=None): 

914 return MemorizedResult( 

915 self.store_backend, 

916 call_id, 

917 metadata=metadata, 

918 timestamp=self.timestamp, 

919 verbose=self._verbose - 1, 

920 ) 

921 

922 def _load_item(self, call_id, metadata=None): 

923 return self.store_backend.load_item( 

924 call_id, metadata=metadata, timestamp=self.timestamp, verbose=self._verbose 

925 ) 

926 

927 def _print_duration(self, duration, context=""): 

928 _, name = get_func_name(self.func) 

929 msg = f"{name} {context}- {format_time(duration)}" 

930 print(max(0, (80 - len(msg))) * "_" + msg) 

931 

932 # ------------------------------------------------------------------------ 

933 # Private `object` interface 

934 # ------------------------------------------------------------------------ 

935 

936 def __repr__(self): 

937 return "{class_name}(func={func}, location={location})".format( 

938 class_name=self.__class__.__name__, 

939 func=self.func, 

940 location=self.store_backend.location, 

941 ) 

942 

943 

944############################################################################### 

945# class `AsyncMemorizedFunc` 

946############################################################################### 

947class AsyncMemorizedFunc(MemorizedFunc): 

948 async def __call__(self, *args, **kwargs): 

949 out = self._cached_call(args, kwargs, shelving=False) 

950 out = await out if asyncio.iscoroutine(out) else out 

951 return out[0] # Don't return metadata 

952 

953 async def call_and_shelve(self, *args, **kwargs): 

954 out = self._cached_call(args, kwargs, shelving=True) 

955 out = await out if asyncio.iscoroutine(out) else out 

956 return out[0] # Don't return metadata 

957 

958 async def call(self, *args, **kwargs): 

959 out = super().call(*args, **kwargs) 

960 return await out if asyncio.iscoroutine(out) else out 

961 

962 async def _call(self, call_id, args, kwargs, shelving=False): 

963 self._before_call(args, kwargs) 

964 start_time = time.time() 

965 output = await self.func(*args, **kwargs) 

966 return self._after_call(call_id, args, kwargs, shelving, output, start_time) 

967 

968 

969############################################################################### 

970# class `Memory` 

971############################################################################### 

972class Memory(Logger): 

973 """A context object for caching a function's return value each time it 

974 is called with the same input arguments. 

975 

976 All values are cached on the filesystem, in a deep directory 

977 structure. 

978 

979 Read more in the :ref:`User Guide <memory>`. 

980 

981 Parameters 

982 ---------- 

983 location: str, pathlib.Path or None 

984 The path of the base directory to use as a data store 

985 or None. If None is given, no caching is done and 

986 the Memory object is completely transparent. This option 

987 replaces cachedir since version 0.12. 

988 

989 backend: str, optional, default='local' 

990 Type of store backend for reading/writing cache files. 

991 The 'local' backend is using regular filesystem operations to 

992 manipulate data (open, mv, etc) in the backend. 

993 

994 mmap_mode: {None, 'r+', 'r', 'w+', 'c'}, optional 

995 The memmapping mode used when loading from cache 

996 numpy arrays. See numpy.load for the meaning of the 

997 arguments. 

998 

999 compress: boolean, integer or 2-tuple, optional 

1000 Whether to zip the stored data on disk. If an integer is 

1001 given, it should be between 1 and 9, and sets the amount 

1002 of compression. Note that compressed arrays cannot be 

1003 read by memmapping. If a 2-tuple is given, the first 

1004 element must be a string corresponding to a compression 

1005 method (e.g 'zlib', 'gzip', 'bz2', 'lzma' or 'xz') and 

1006 the second element must be an integer from 0 to 9, 

1007 corresponding to the compression level. 

1008 

1009 verbose: int, optional 

1010 Verbosity flag, controls the debug messages that are issued 

1011 as functions are evaluated. 

1012 

1013 backend_options: dict, optional 

1014 Contains a dictionary of named parameters used to configure 

1015 the store backend. 

1016 """ 

1017 

1018 # ------------------------------------------------------------------------ 

1019 # Public interface 

1020 # ------------------------------------------------------------------------ 

1021 

1022 def __init__( 

1023 self, 

1024 location=None, 

1025 backend="local", 

1026 mmap_mode=None, 

1027 compress=False, 

1028 verbose=1, 

1029 backend_options=None, 

1030 ): 

1031 Logger.__init__(self) 

1032 self._verbose = verbose 

1033 self.mmap_mode = mmap_mode 

1034 self.timestamp = time.time() 

1035 self.backend = backend 

1036 self.compress = compress 

1037 if backend_options is None: 

1038 backend_options = {} 

1039 self.backend_options = backend_options 

1040 

1041 if compress and mmap_mode is not None: 

1042 warnings.warn("Compressed results cannot be memmapped", stacklevel=2) 

1043 

1044 self.location = location 

1045 if isinstance(location, str): 

1046 location = os.path.join(location, "joblib") 

1047 

1048 self.store_backend = _store_backend_factory( 

1049 backend, 

1050 location, 

1051 verbose=self._verbose, 

1052 backend_options=dict( 

1053 compress=compress, mmap_mode=mmap_mode, **backend_options 

1054 ), 

1055 ) 

1056 

1057 def cache( 

1058 self, 

1059 func=None, 

1060 ignore=None, 

1061 verbose=None, 

1062 mmap_mode=False, 

1063 cache_validation_callback=None, 

1064 ): 

1065 """Decorates the given function func to only compute its return 

1066 value for input arguments not cached on disk. 

1067 

1068 Parameters 

1069 ---------- 

1070 func: callable, optional 

1071 The function to be decorated 

1072 ignore: list of strings 

1073 A list of arguments name to ignore in the hashing 

1074 verbose: integer, optional 

1075 The verbosity mode of the function. By default that 

1076 of the memory object is used. 

1077 mmap_mode: {None, 'r+', 'r', 'w+', 'c'}, optional 

1078 The memmapping mode used when loading from cache 

1079 numpy arrays. See numpy.load for the meaning of the 

1080 arguments. By default that of the memory object is used. 

1081 cache_validation_callback: callable, optional 

1082 Callable to validate whether or not the cache is valid. When 

1083 the cached function is called with arguments for which a cache 

1084 exists, this callable is called with the metadata of the cached 

1085 result as its sole argument. If it returns True, then the 

1086 cached result is returned, else the cache for these arguments 

1087 is cleared and recomputed. 

1088 

1089 Returns 

1090 ------- 

1091 decorated_func: MemorizedFunc object 

1092 The returned object is a MemorizedFunc object, that is 

1093 callable (behaves like a function), but offers extra 

1094 methods for cache lookup and management. See the 

1095 documentation for :class:`joblib.memory.MemorizedFunc`. 

1096 """ 

1097 if cache_validation_callback is not None and not callable( 

1098 cache_validation_callback 

1099 ): 

1100 raise ValueError( 

1101 "cache_validation_callback needs to be callable. " 

1102 f"Got {cache_validation_callback}." 

1103 ) 

1104 if func is None: 

1105 # Partial application, to be able to specify extra keyword 

1106 # arguments in decorators 

1107 return functools.partial( 

1108 self.cache, 

1109 ignore=ignore, 

1110 mmap_mode=mmap_mode, 

1111 verbose=verbose, 

1112 cache_validation_callback=cache_validation_callback, 

1113 ) 

1114 if self.store_backend is None: 

1115 cls = ( 

1116 AsyncNotMemorizedFunc 

1117 if inspect.iscoroutinefunction(func) 

1118 else NotMemorizedFunc 

1119 ) 

1120 return cls(func) 

1121 if verbose is None: 

1122 verbose = self._verbose 

1123 if mmap_mode is False: 

1124 mmap_mode = self.mmap_mode 

1125 if isinstance(func, MemorizedFunc): 

1126 func = func.func 

1127 cls = AsyncMemorizedFunc if inspect.iscoroutinefunction(func) else MemorizedFunc 

1128 return cls( 

1129 func, 

1130 location=self.store_backend, 

1131 backend=self.backend, 

1132 ignore=ignore, 

1133 mmap_mode=mmap_mode, 

1134 compress=self.compress, 

1135 verbose=verbose, 

1136 timestamp=self.timestamp, 

1137 cache_validation_callback=cache_validation_callback, 

1138 ) 

1139 

1140 def clear(self, warn=True): 

1141 """Erase the complete cache directory.""" 

1142 if warn: 

1143 self.warn("Flushing completely the cache") 

1144 if self.store_backend is not None: 

1145 self.store_backend.clear() 

1146 

1147 # As the cache is completely clear, make sure the _FUNCTION_HASHES 

1148 # cache is also reset. Else, for a function that is present in this 

1149 # table, results cached after this clear will be have cache miss 

1150 # as the function code is not re-written. 

1151 _FUNCTION_HASHES.clear() 

1152 

1153 def reduce_size(self, bytes_limit=None, items_limit=None, age_limit=None): 

1154 """Remove cache elements to make the cache fit its limits. 

1155 

1156 The limitation can impose that the cache size fits in ``bytes_limit``, 

1157 that the number of cache items is no more than ``items_limit``, and 

1158 that all files in cache are not older than ``age_limit``. 

1159 

1160 Parameters 

1161 ---------- 

1162 bytes_limit: int | str, optional 

1163 Limit in bytes of the size of the cache. By default, the size of 

1164 the cache is unlimited. When reducing the size of the cache, 

1165 ``joblib`` keeps the most recently accessed items first. If a 

1166 str is passed, it is converted to a number of bytes using units 

1167 { K | M | G} for kilo, mega, giga. 

1168 

1169 items_limit: int, optional 

1170 Number of items to limit the cache to. By default, the number of 

1171 items in the cache is unlimited. When reducing the size of the 

1172 cache, ``joblib`` keeps the most recently accessed items first. 

1173 

1174 age_limit: datetime.timedelta, optional 

1175 Maximum age of items to limit the cache to. When reducing the size 

1176 of the cache, any items last accessed more than the given length of 

1177 time ago are deleted. Example: to remove files older than 5 days, 

1178 use datetime.timedelta(days=5). Negative timedelta are not 

1179 accepted. 

1180 """ 

1181 if self.store_backend is None: 

1182 # No cached results, this function does nothing. 

1183 return 

1184 

1185 if bytes_limit is None and items_limit is None and age_limit is None: 

1186 # No limitation to impose, returning 

1187 return 

1188 

1189 # Defers the actual limits enforcing to the store backend. 

1190 self.store_backend.enforce_store_limits(bytes_limit, items_limit, age_limit) 

1191 

1192 def eval(self, func, *args, **kwargs): 

1193 """Eval function func with arguments `*args` and `**kwargs`, 

1194 in the context of the memory. 

1195 

1196 This method works similarly to the builtin `apply`, except 

1197 that the function is called only if the cache is not 

1198 up to date. 

1199 

1200 """ 

1201 if self.store_backend is None: 

1202 return func(*args, **kwargs) 

1203 return self.cache(func)(*args, **kwargs) 

1204 

1205 # ------------------------------------------------------------------------ 

1206 # Private `object` interface 

1207 # ------------------------------------------------------------------------ 

1208 

1209 def __repr__(self): 

1210 return "{class_name}(location={location})".format( 

1211 class_name=self.__class__.__name__, 

1212 location=( 

1213 None if self.store_backend is None else self.store_backend.location 

1214 ), 

1215 ) 

1216 

1217 def __getstate__(self): 

1218 """We don't store the timestamp when pickling, to avoid the hash 

1219 depending from it. 

1220 """ 

1221 state = self.__dict__.copy() 

1222 state["timestamp"] = None 

1223 return state 

1224 

1225 

1226############################################################################### 

1227# cache_validation_callback helpers 

1228############################################################################### 

1229 

1230 

1231def expires_after( 

1232 days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0 

1233): 

1234 """Helper cache_validation_callback to force recompute after a duration. 

1235 

1236 Parameters 

1237 ---------- 

1238 days, seconds, microseconds, milliseconds, minutes, hours, weeks: numbers 

1239 argument passed to a timedelta. 

1240 """ 

1241 delta = datetime.timedelta( 

1242 days=days, 

1243 seconds=seconds, 

1244 microseconds=microseconds, 

1245 milliseconds=milliseconds, 

1246 minutes=minutes, 

1247 hours=hours, 

1248 weeks=weeks, 

1249 ) 

1250 

1251 def cache_validation_callback(metadata): 

1252 computation_age = time.time() - metadata["time"] 

1253 return computation_age < delta.total_seconds() 

1254 

1255 return cache_validation_callback