Git Product home page Git Product logo

cachier's Introduction

Cachier

PyPI-Status PePy stats PyPI-Versions Build-Status Codecov Codefactor code quality LICENCE

Persistent, stale-free, local and cross-machine caching for Python functions.

from cachier import cachier
import datetime

@cachier(stale_after=datetime.timedelta(days=3))
def foo(arg1, arg2):
  """foo now has a persistent cache, triggering recalculation for values stored more than 3 days."""
  return {'arg1': arg1, 'arg2': arg2}

Install cachier with:

pip install cachier

For the latest version supporting Python 2.7 please use:

pip install 'cachier==1.2.8'
  • Pure Python.
  • Compatible with Python 3.8+ (Python 2.7 was discontinued in version 1.2.8).
  • Supported and tested on Linux, OS X and Windows.
  • A simple interface.
  • Defining "shelf life" for cached values.
  • Local caching using pickle files.
  • Cross-machine caching using MongoDB.
  • Thread-safety.

Cachier is NOT:

  • Meant as a transient cache. Python's @lru_cache is better.
  • Especially fast. It is meant to replace function calls that take more than... a second, say (overhead is around 1 millisecond).

Cachier provides a decorator which you can wrap around your functions to give them a persistent cache. The positional and keyword arguments to the wrapped function must be hashable (i.e. Python's immutable built-in objects, not mutable containers). Also, notice that since objects which are instances of user-defined classes are hashable but all compare unequal (their hash value is their id), equal objects across different sessions will not yield identical keys.

You can add a default, pickle-based, persistent cache to your function - meaning it will last across different Python kernels calling the wrapped function - by decorating it with the cachier decorator (notice the ()!).

from cachier import cachier

@cachier()
def foo(arg1, arg2):
  """Your function now has a persistent cache mapped by argument values!"""
  return {'arg1': arg1, 'arg2': arg2}

Class and object methods can also be cached. Cachier will automatically ignore the self parameter when determining the cache key for an object method. This means that methods will be cached across all instances of an object, which may not be what you want.

from cachier import cachier

class Foo():
  @staticmethod
  @cachier()
  def good_static_usage(arg_1, arg_2):
    return arg_1 + arg_2

  # Instance method does not depend on object's internal state, so good to cache
  @cachier()
  def good_usage_1(self, arg_1, arg_2):
    return arg_1 + arg_2

  # Instance method is calling external service, probably okay to cache
  @cachier()
  def good_usage_2(self, arg_1, arg_2):
    result = self.call_api(arg_1, arg_2)
    return result

  # Instance method relies on object attribute, NOT good to cache
  @cachier()
  def bad_usage(self, arg_1, arg_2):
    return arg_1 + arg_2 + self.arg_3

The Cachier wrapper adds a clear_cache() function to each wrapped function. To reset the cache of the wrapped function simply call this method:

foo.clear_cache()

Settings can be globally configured across all Cachier wrappers through the use of the set_default_params function. This function takes the same keyword parameters as the ones defined in the decorator, which can be passed all at once or with multiple calls. Parameters given directly to a decorator take precedence over any values set by this function.

The following parameters will only be applied to decorators defined after set_default_params is called:

  • hash_func
  • backend
  • mongetter
  • cache_dir
  • pickle_reload
  • separate_files

These parameters can be changed at any time and they will apply to all decorators:

  • allow_none
  • caching_enabled
  • stale_after
  • next_time
  • wait_for_calc_timeout

The current defaults can be fetched by calling get_default_params.

To limit the number of threads Cachier is allowed to spawn, set the CACHIER_MAX_WORKERS with the desired number. The default is 8, so to enable Cachier to spawn even more threads, you'll have to set a higher limit explicitly.

Caching can be turned off across all decorators by calling disable_caching, and then re-activated by calling enable_caching.

These functions are convenience wrappers around the caching_enabled default setting.

You can set any duration as the shelf life of cached return values of a function by providing a corresponding timedelta object to the stale_after parameter:

import datetime

@cachier(stale_after=datetime.timedelta(weeks=2))
def bar(arg1, arg2):
  return {'arg1': arg1, 'arg2': arg2}

Now when a cached value matching the given arguments is found the time of its calculation is checked; if more than stale_after time has since passed, the function will be run again for the same arguments and the new value will be cached and returned.

This is useful for lengthy calculations that depend on a dynamic data source.

Sometimes you may want your function to trigger a calculation when it encounters a stale result, but still not wait on it if it's not that critical. In that case, you can set next_time to True to have your function trigger a recalculation in a separate thread, but return the currently cached stale value:

@cachier(next_time=True)

Further function calls made while the calculation is being performed will not trigger redundant calculations.

As mentioned above, the positional and keyword arguments to the wrapped function must be hashable (i.e. Python's immutable built-in objects, not mutable containers). To get around this limitation the hash_func parameter of the cachier decorator can be provided with a callable that gets the args and kwargs from the decorated function and returns a hash key for them.

def calculate_hash(args, kwds):
  key = ...  # compute a hash key here based on arguments
  return key

@cachier(hash_func=calculate_hash)
def calculate_super_complex_stuff(custom_obj):
  # amazing code goes here

See here for an example:

Question: How to work with unhashable arguments

If you want to load a value into the cache without calling the underlying function, this can be done with the precache_value function.

@cachier()
def add(arg1, arg2):
  return arg1 + arg2

add.precache_value(2, 2, value_to_cache=5)

result = add(2, 2)
print(result)  # prints 5

Cachier also accepts several keyword arguments in the calls of the function it wraps rather than in the decorator call, allowing you to modify its behaviour for a specific function call.

You can have cachier ignore any existing cache for a specific function call by passing cachier__skip_cache=True to the function call. The cache will neither be checked nor updated with the new return value.

@cachier()
def sum(first_num, second_num):
  return first_num + second_num

def main():
  print(sum(5, 3, cachier__skip_cache=True))

You can have cachier overwrite an existing cache entry - if one exists - for a specific function call by passing cachier__overwrite_cache=True to the function call. The cache will not be checked but will be updated with the new return value.

You can have cachier print out a detailed explanation of the logic of a specific call by passing cachier__verbose=True to the function call. This can be useful if you are not sure why a certain function result is, or is not, returned.

By default, cachier does not cache None values. You can override this behaviour by passing allow_none=True to the function call.

The default core for Cachier is pickle based, meaning each function will store its cache is a separate pickle file in the ~/.cachier directory. Naturally, this kind of cache is both machine-specific and user-specific.

You can configure cachier to use another directory by providing the cache_dir parameter with the path to that directory:

@cachier(cache_dir='~/.temp/.cache')

You can slightly optimise pickle-based caching if you know your code will only be used in a single thread environment by setting:

@cachier(pickle_reload=False)

This will prevent reading the cache file on each cache read, speeding things up a bit, while also nullifying inter-thread functionality (the code is still thread safe, but different threads will have different versions of the cache at times, and will sometime make unnecessary function calls).

Setting the optional argument separate_files to True will cause the cache to be stored in several files: A file per argument set, per function. This can help if your per-function cache files become too large.

from cachier import cachier

@cachier(separate_files=True)
def foo(arg1, arg2):
  """Your function now has a persistent cache mapped by argument values, split across several files, per argument set"""
  return {'arg1': arg1, 'arg2': arg2}

You can get the fully qualified path to the directory of cache files used by cachier (~/.cachier by default) by calling the cache_dpath() function:

>>> foo.cache_dpath()
    "/home/bigus/.cachier/"

You can set a MongoDB-based cache by assigning mongetter with a callable that returns a pymongo.Collection object with writing permissions:

  from pymongo import MongoClient

  def my_mongetter():
      client = MongoClient(get_cachier_db_auth_uri())
      db_obj = client['cachier_db']
      if 'someapp_cachier_db' not in db_obj.list_collection_names():
          db_obj.create_collection('someapp_cachier_db')
      return db_obj['someapp_cachier_db']

@cachier(mongetter=my_mongetter)

This allows you to have a cross-machine, albeit slower, cache. This functionality requires that the installation of the pymongo python package.

In certain cases the MongoDB backend might leave a deadlock behind, blocking all subsequent requests from being processed. If you encounter this issue, supply the wait_for_calc_timeout with a reasonable number of seconds; calls will then wait at most this number of seconds before triggering a recalculation.

@cachier(mongetter=False, wait_for_calc_timeout=2)

You can set an in-memory cache by assigning the backend parameter with 'memory':

@cachier(backend='memory')

Note, however, that cachier's in-memory core is simple, and has no monitoring or cap on cache size, and can thus lead to memory errors on large return values - it is mainly intended to be used with future multi-core functionality. As a rule, Python's built-in lru_cache is a much better stand-alone solution.

Current maintainers are Shay Palachy Affek ([email protected], @shaypal5) and Judson Neer (@lordjabez); You are more than welcome to approach them for help. Contributions are very welcomed! :)

Clone:

git clone [email protected]:python-cachier/cachier.git

Install in development mode with test dependencies:

cd cachier
pip install -e . -r tests/requirements.txt

To run the tests, call the pytest command in the repository's root, or:

python -m pytest

To run only MongoDB core related tests, use:

pytest -m mongo

To run only memory core related tests, use:

pytest -m memory

To run all tests EXCEPT MongoDB core related tests, use:

pytest -m "not mongo"

To run all tests EXCEPT memory core AND MongoDB core related tests, use:

pytest -m "not (mongo or memory)"

Note to developers: By default, all MongoDB tests are run against a mocked MongoDB instance, provided by the pymongo_inmemory package. To run them against a live MongoDB instance, the CACHIER_TEST_VS_DOCKERIZED_MONGO environment variable is set to True in the test environment of this repository (and additional environment variables are populated with the appropriate credentials), used by the GitHub Action running tests on every commit and pull request.

Contributors are not expected to run these tests against a live MongoDB instance when developing, as credentials for the testing instance used will NOT be shared, but rather use the testing against the in-memory MongoDB instance as a good proxy.

HOWEVER, the tests run against a live MongoDB instance when you submit a PR are the determining tests for deciding whether your code functions correctly against MongoDB.

This project is documented using the numpy docstring conventions, which were chosen as they are perhaps the most widely-spread conventions that are both supported by common tools such as Sphinx and result in human-readable docstrings (in my personal opinion, of course). When documenting code you add to this project, please follow these conventions.

Additionally, if you update this README.rst file, use python setup.py checkdocs to validate it compiles.

Created by Shay Palachy Affek ([email protected]), which currently assists in maintenance.

Current lead developer/contributor: Jirka Borovec (@Borda on GitHub).

Other major contributors:

  • Jirka Borovec - Arg order independence, args-to-kwargs for less unique keys and numerous development and CI contributions.
  • Judson Neer - Precaching, method caching support and numerous improvements and bugfixes.
  • cthoyt - Base memory core implementation.
  • amarczew - The hash_func kwarg.
  • non-senses - The wait_for_calc_timeout kwarg.
  • Elad Rapaport - Multi-file Pickle core, a.k.a separate_files (released on v1.5.3).
  • John Didion - Support for pickle-based caching for cases where two identically-named methods of different classes are defined in the same module.

Notable bugfixers:

cachier's People

Contributors

amarczew avatar borda avatar cthoyt avatar dependabot[bot] avatar dolftax avatar dphi avatar erap129 avatar jall avatar jamt9000 avatar jdanford avatar jmrichardson avatar lordjabez avatar louismartin avatar mayli avatar michaelrazum avatar mikeage avatar pre-commit-ci[bot] avatar shaypal5 avatar thewchan avatar twinters avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cachier's Issues

Add an S3 backend

For me, the most probable use case in the near future is an an S3-backed persistent cache.

Cachier cannot cache class methods

class A: @cachier() def B(self): do something
Rewrite the cache every time, instead of reading the cache, the expiration time is set immediately, I doubt whether it is because the self parameter will change every time, how can I solve it?

RuntimeError: Cannot add watch, already scheduled

It seems that when running cachier, I randomly get these errors:

2021-09-17 09:05:32,357 - fsevents - ERROR - Unhandled exception in FSEventsEmitter
Traceback (most recent call last):
  File "/Users/lorenz/.../signals/.venv/lib/python3.9/site-packages/watchdog/observers/fsevents.py", line 315, in run
    _fsevents.add_watch(self, self.watch, self.events_callback, self.pathnames)
RuntimeError: Cannot add watch <ObservedWatch: path=/Users/lorenz/.cachier/, is_recursive=True> - it is already scheduled

My current solution is to delete the cache, assuming it is corrupt, and then start from scratch.

The most similar error I found on the net was the one reported in spyder-ide/spyder#14803. There, the solution was to unwatch on switching directories/projects.

adler32 hash is too small ==> Many collisions

Hi,
I just updated to the cachier 1.3.3 with the adler32 hashing and I have many bugs related to hash collision.
For instance I have a bunch of 2950 paths that hash to only 1195 unique hash values which causes my method to return a lot of erroneous values (i.e. cached values corresponding to another path).

Here's a simple illustration:

from zlib import adler32
import pickle

hashes = [adler32(pickle.dumps(str(i))) & 0xffffffff for i in range(1000)]
print(len(hashes), len(set(hashes)))

Output: 1000 424

The computed hash seems to be too short (10 digits) and not hexadecimal.
There might be a better alternative with longer hexadecimal hashes.

Patch version increment broke public api

There is apparently no longer a possibility to specify a backend in versions after 1.5.0. Why is this the case?

_______________________________________________ ERROR collecting tests/test_bioversions.py _______________________________________________
tests/test_bioversions.py:10: in <module>
    import bioversions
.tox/py/lib/python3.9/site-packages/bioversions/__init__.py:5: in <module>
    from .sources import get_rows, get_version, resolve  # noqa:F401
.tox/py/lib/python3.9/site-packages/bioversions/sources/__init__.py:12: in <module>
    from .biofacquim import BiofacquimGetter
.tox/py/lib/python3.9/site-packages/bioversions/sources/biofacquim.py:5: in <module>
    from bioversions.utils import Getter, VersionType, get_soup
.tox/py/lib/python3.9/site-packages/bioversions/utils.py:58: in <module>
    refresh_daily = cachier(
E   TypeError: cachier() got an unexpected keyword argument 'backend'
======================================================== short test summary info =========================================================
ERROR tests/test_bioversions.py - TypeError: cachier() got an unexpected keyword argument 'backend'

Bug?: PickleCore hangs on wait_on_entry_calc

My program is hanging on _PickleCore::wait_on_entry_calc, because the entry states that the value is currently being calculated. entry['time'] is two days ago. I am fairly sure that no process is currently calculating this value. The calculation probably started two days ago, and then I aborted the program, so now I suspect that cachier left an inconsistent state behind.

In such cases, it might be helpful to either use wait_for_calc_timeout for the pickle core as well, or have an extra option like max_calc_timeout that checks the age of the entry allegedly still being calculated (in my case about 48 hours) and triggers re-calculation if this age is above that threshold.

Additional info:
I am running cachier 1.5.3 on linux, using the decorator @cachier(separate_files=True) on my function, which is called from different multiprocessing processes.

Add support for Windows

Hello,

It doesn't work on Windows due to lack of fcntln :

pip install cachier

In [1]: from cachier import cache
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-1-4d9fcdd09a5c> in <module>()
----> 1 from cachier import cache

c:\users\x\appdata\local\programs\python\python35\lib\site-packages\cachier\__init__.py in <module>()
----> 1 from .core import cachier
      2 from ._version import get_versions
      3 __version__ = get_versions()['version']
      4 del get_versions

c:\users\x\appdata\local\programs\python\python35\lib\site-packages\cachier\core.py in <module>()
     26     from concurrent.futures import ThreadPoolExecutor
     27 import time   # to sleep when waiting on Mongo cache
---> 28 import fcntl  # to lock on pickle cache IO
     29
     30 import pymongo

ImportError: No module named 'fcntl'

If you opt not to fix it you should mention it in the README and setup files

Any thoughts on supporting something better than pickling?

Pickling is about as slow as it can get, some benchmarks here.

Any future plans for adding support for something faster, like msgpack, or even json?

I realize that some choices will require the cachier-wrapped functions to restrict the data that they return, but given how slow pickle is, and that the point of caching is at least in part to speed things up, and specially not to slow them down - I think this would be a good feature.

Any thoughts on this?

Using cachier with network exceptions?

Hi, thanks for the great package! I'm using cachier to cache calls to Reddit and Pushshift, and sometimes there's a networked based exception. Perhaps I get rate limited or there's something else.

  1. I try to include reasonable time.sleep(N) in my queries, but if I'm pulling from cache, there's no need for that. Any suggestions for how to rate limit only if it's not in cache?
  2. How often does cachier save it's state? If I get an uncaught network exception on a long data pull, are all the previous cached queries lost?

Support for coroutines (asyncio)

Hello,

I was trying to use cachier for caching the result of functions, in particular, HTTP requests.

I am using asyncio throughout my code. I noticed that cachier is fully synchronous, as seen here when calling _calc_entry.

It would be great to have support for caching result of coroutines, using await.

Maybe an async PickeCore / decorator? I am not clear on the best way to accomplish this with minimal code duplication, though.

Clearing cache based on method parameters

The ability to cache a method based on the function parameters to that method is extremely useful. However, at the moment, if I need to invalidate the cache for a specific set of arguments, I have to clear the entire cache for that method.

Are there any future plans for allowing individual sections of the cache to be cleared based on method parameters?

Cross-machine cache

I was going through the docs for cachier. And I am really interested in using this in my project.
There are two features listed in the doc that confuse me a bit:
Local caching using pickle files.
Cross-machine caching using MongoDB.
What does this mean ?

Some context: I have 3 pods(servers) and a single NFS based disk storage mounted on them.
Since cachier is thread safe, I was hoping that this suits my purpose of having persistent cache on the NFS storage. I want to use pickle based caching since dbs like sqllite/mongoDb are not recommended for NFS based storage.

Does it support my purpose ?
Is saving as pickle objects not suited for cross machine caching by cachier ?
What does it mean to have cross-machine caching using MongoDb?
What does local caching using pickle files mean ?

Cache not invalidated when keyword argument is a function

Hi, here is a follow up to #70. I am not very experienced in Python, so I hope this doesn't waste anyone's time ๐Ÿคž.
I believe functions are hashable and the cache should be invalidated when a hashable input changes. Do you agree? Does not seem to be the case, even though the hash changes:

from cachier import cachier

def my_fun24(x):
    return x

print(hash(my_fun24))
#>>> 8786991674287

@cachier()
def x(f):
    return my_fun24(1)

print(x(my_fun24))
#>>> 1

# let's change the function...
def my_fun24(x):
    return 2 * x

# ... and verify the hash changes
print(hash(my_fun24))
#>>> 8786992186206

# but `x()` is using the cached value apparently
print(x(my_fun24))
#>>> 1

Signature change does not invalidate cache on keyword arguments when matched by position

Thanks for this cool package, it saved so much of my time with zero hassle ๐Ÿฅณ. Then, I edited some function definition in my code and re-ran my .py file. I found that the cache was not invalidated. Here is a reproducible example in one session that should show the same behavior I think. It shows:

  1. Renaming arguments does not invalidate the cache.
  2. Changing order or arguments does not invalidate the cache.
  3. The same also appears when adding or removing arguments.
from cachier import cachier

args = (1, 2)
@cachier()
def g(foo, bar):
    return 'bar'

print(g(*args))
#>>> bar

# let's change the argument name: bar -> bars
@cachier()
def g(foo, bars):
    return 'foo bar'

# now I expect 'foo bar'. Not happening.
print(g(*args))
#>>> bar


# let's reverse the argument order:
@cachier()
def g(bars, foo):
    return 'foo bar'

# now I also expect 'foo bar'. No luck.
print(g(*args))
#>>> bar

It seems as the cache is not invalidated when the arguments change here although I would expect that. Do you agree? I am using cachier 1.5.0.

Travis CI only checks python 3.7.5 on macOS

Hi! I notice that at in .travis.yaml it is claimed that cachier is tested against Python 3.6.5 on macOS 10.13.

However, on a corresponding build on Travis, if you look in the logs, you can see that it's actually using python 3.7.5. This is the same for another build that claims to use python 3.7.3, but also is using 3.7.5

I'm not sure where this changed.

Accessing the cached data from another code ( file )

Hey

great work you have put in . lovely effort.
i was just checking if it fits for me

so i had query that ( since it create a pickle file )

if i use the caching from one file or code so the cached folder and files are created.

is there a way to use that cached data from another file or code ?
for example : a flask script to update the cache and another one to execute the needful by reading the values from the cached folder.

thank you

Support for source code check

Hi, I have an idea for a feature that would really be helpful, especially in a data science experimentation workflow.

Add an boolean argument to the wrapper function, for instance inspect_source. When it is set to True, use the inspect module to look at the source code of the function and use its hash the same way you do for the function arguments.

Would help a lot !

Support exclude `self` in classmethod

Hello,

I found cachier failed with a unpickable Class , becase that class contain database session .
It is no need to cache self in a class method .

Maybe you can add an option to pup the first argumernt .

Add a timeout for "waiting for running calculation"

It can apparently happen that the pickle core leaves an inconsistent state behind (stating that an entry is currently being calculated, which is no longer true), when the calculating process crashes or is aborted by the user. Similar scenarios could probably occur with other persistence cores as well.

It might be helpful to either use wait_for_calc_timeout for the pickle core as well, or have an extra option like max_calc_timeout that checks the age of a cache entry allegedly still being calculated and triggers re-calculation if this age is above that threshold. It might be reasonable to specify a high default value for this (like 24 hours), or maybe to keep this feature disabled by default to not cause confusion for new users.

In situations where this new timeout was reached (e.g. a method really takes 26 hours to calculate its value, and after 25 hours it was called again, so now there are two processes calculating on the same input), it can happen that the decorated method is calculating result values for the same input multiple times. To keep the results of the cache consistent, I would suggest that whichever invocation finishes first should store its result, and all the others should detect the existing result, discard their own calculation result, and return the cached value. Ideally, if the decorated function is pure, the results should not differ. But if they can differ (e.g. some randomized process is being used, or some time information is incorporated into the result), consistency might be more important.

To reproduce the problem: #82 (comment)

See #82 for more info

Numpy array default hasher

I cache a function which is called with large numpy arrays. There is an efficient way to hash them with https://github.com/ifduyue/python-xxhash

I think it's a common use case, so I can merge my method to the default hasher in this library.

The only problem is that hash collisions are possible, and the cache would then work incorrectly, but it's very rare, and also the existing functionality isn't broken. I can also add an option to not hash them but just convert to string with array.tobytes(), which prevents collisions but is only is suited for small arrays.

The same can be done for regular python lists (with xxhash or str(list)).

If you want, I can make a PR with this.

Handle function defaults correctly

Right now, if you call some with function without specifying a default parameter, cache remembers it as 'parameter not set'. If you change this default value in function definition, and call again without specifying this parameter, cache will incorrectly return the function output for the old value.
Also, if you sometimes use the default and sometimes specify the value, the function will be be computed two times, needlessly.

It can be easily fixed by replacing this code:
https://github.com/shaypal5/cachier/blob/5b0bff9af3dbd251ae657e4f6f27f269de977768/cachier/pickle_core.py#L152
with:

bound = inspect.signature(function).bind(*args, **kwargs)
bound.apply_defaults()
key = tuple((k, value) for k, value in sorted(bound.arguments.items()))

If you want this improvement I can do a PR.

The cache file is too large

If a function is repeatedly called with different parameters, I find that the file in the cache directory still has only one file named after the function name. This leads to a huge file size when the function is called many times, so it reads
And the speed of rewriting is not as good as when the cache is not used.

OSError: inotify instance limit reached

When using cachier with some long-running functions, I get the following error:

OSError: inotify instance limit reached

A fuller stack trace is in the details below. I have tried my best to obscure out proprietary information, so please forgive me if it kind of messes up the syntax.

What would be the best next step here to debug? (No pressure on your side, @shaypal5, I have no problems debugging on my own, just wanted to know your thoughts.)

---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
 in 
----> 1 s = my_long_func("arg")

~/path/to/my/env/lib/python3.7/site-packages/cachier/core.py in func_wrapper(*args, **kwds)
169 return _calc_entry(core, key, func, args, kwds)
170 _print('No entry found. No current calc. Calling like a boss.')
--> 171 return _calc_entry(core, key, func, args, kwds)
172
173 def clear_cache():

~/path/to/my/env/lib/python3.7/site-packages/cachier/core.py in _calc_entry(core, key, func, args, kwds)
65 core.mark_entry_being_calculated(key)
66 # _get_executor().submit(core.mark_entry_being_calculated, key)
---> 67 func_res = func(*args, **kwds)
68 core.set_entry(key, func_res)
69 # _get_executor().submit(core.set_entry, key, func_res)

~/path/to/my/src.py in my_long_func.py(kwarg, another_arg)
47 @cachier(stale_after=timedelta(weeks=1))
48 def my_long_func.py(kwarg: str, another_arg: bool = False):
---> 49 res_df = wrapped_func(kwarg=kwarg, another_arg="string")
50 if another_arg:
51 return res_df.query("valid_data == True")

~/path/to/my/env/lib/python3.7/site-packages/cachier/core.py in func_wrapper(*args, **kwds)
165 _print('No value but being calculated. Waiting.')
166 try:
--> 167 return core.wait_on_entry_calc(key)
168 except RecalculationNeeded:
169 return _calc_entry(core, key, func, args, kwds)

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
191 if observer.isAlive():
192 # print('Timedout waiting. Starting again...')
--> 193 return self.wait_on_entry_calc(key)
194 # print("Returned value: {}".format(event_handler.value))
195 return event_handler.value

~/path/to/my/env/lib/python3.7/site-packages/cachier/pickle_core.py in wait_on_entry_calc(self, key)
187 recursive=True
188 )
--> 189 observer.start()
190 observer.join(timeout=1.0)
191 if observer.isAlive():

~/path/to/my/env/lib/python3.7/site-packages/watchdog/observers/api.py in start(self)
253 def start(self):
254 for emitter in self._emitters:
--> 255 emitter.start()
256 super(BaseObserver, self).start()
257

~/path/to/my/env/lib/python3.7/site-packages/watchdog/utils/init.py in start(self)
108
109 def start(self):
--> 110 self.on_thread_start()
111 threading.Thread.start(self)
112

~/path/to/my/env/lib/python3.7/site-packages/watchdog/observers/inotify.py in on_thread_start(self)
119 def on_thread_start(self):
120 path = unicode_paths.encode(self.watch.path)
--> 121 self._inotify = InotifyBuffer(path, self.watch.is_recursive)
122
123 def on_thread_stop(self):

~/path/to/my/env/lib/python3.7/site-packages/watchdog/observers/inotify_buffer.py in init(self, path, recursive)
33 BaseThread.init(self)
34 self._queue = DelayedQueue(self.delay)
---> 35 self._inotify = Inotify(path, recursive)
36 self.start()
37

~/path/to/my/env/lib/python3.7/site-packages/watchdog/observers/inotify_c.py in init(self, path, recursive, event_mask)
186 inotify_fd = inotify_init()
187 if inotify_fd == -1:
--> 188 Inotify._raise_error()
189 self._inotify_fd = inotify_fd
190 self._lock = threading.Lock()

~/path/to/my/env/lib/python3.7/site-packages/watchdog/observers/inotify_c.py in _raise_error()
413 raise OSError("inotify watch limit reached")
414 elif err == errno.EMFILE:
--> 415 raise OSError("inotify instance limit reached")
416 else:
417 raise OSError(os.strerror(err))

</pre>
</details>

Not working if function returns None

I just tried out cachier for the first run and couldn't at first understand why it was not working and the reason seems to be that caching is not done if the function returns None. Is this intended?

Consider the following example:

from time import sleep
from timeit import default_timer as timer

from cachier import cachier


@cachier()
def func():
    sleep(1)


t0 = timer()
func()
t1 = timer()
func()
t2 = timer()
print(t1 - t0)
print(t2 - t1)

where no caching is done.
However, changing the function to be:

@cachier()
def func():
    sleep(1)
    return 1

enables the caching

Add a redis backend

redis is the king of cache, a backend for it can be cool
(on my todo list, down on the bottom of it)

Pickle Core should use different default path for virtual environments

The nature of virtual envs is to support different versions. pickling to home essentially breaks the ability to cache pickles of objects from different versions. This can lead to very strange exceptions like fsnotify exceeding its limits.

Therefore I propose to have a path inside the venv if we are in a virtual environment.

Deletion stale cache values to mitigate cache size issues

Well, my use case is:
I have a function that queries the database, the input is the database alias and the query.
It's working great, but I just saw that the cache size will grow indefinitely, right?
My idea was to keep track of the stale date, and check for all elements of the object if any was already "staled", and delete it.
Just to keep the filesize at a minimum.

I never worked with Classes like this project, but I can try something.

What do you think?
I'm using it wrong?

Deadlock when using MongoDB as a backend

Issue:

In certain cases the MongoDB backend might leave a deadlock behind, blocking all subsequent requests from being processed.

Explanation:

In https://github.com/shaypal5/cachier/blob/17d1b7cc9c57dabb0c40bf7ef748bd68d4a3628c/cachier/core.py#L64 ๐Ÿ‘

def _calc_entry(core, key, func, args, kwds):
    try:
        core.mark_entry_being_calculated(key)
        # _get_executor().submit(core.mark_entry_being_calculated, key)
        func_res = func(*args, **kwds)
        core.set_entry(key, func_res)
        # _get_executor().submit(core.set_entry, key, func_res)
        return func_res
    finally:
        core.mark_entry_not_calculated(key)

The happy path procedure is:

  1. Mark the entry as being calculated
  2. Call the decorated function
    3a. If no exception has been thrown, set the result in cache.
    3b. If an exception has been thrown, cleanup the entry in the cache.

The buggy path:

  1. Mark the entry as being calculated
  2. Call the decorated function.
  3. While running the decorated function any major issue occurs. Imagine losing MongoDB connection or the service being stopped with Ctrl-C or Ctrl-D.
  4. Restart the service again, call the decorated function again.
  5. The entry still exists in DB (because core.mark_entry_not_calculated(key) was never called); resulting in an infinite loop in this line.
    def wait_on_entry_calc(self, key):
        while True:
            time.sleep(MONGO_SLEEP_DURATION_IN_SEC)
            key, entry = self.get_entry_by_key(key)
            if entry is None:
                raise RecalculationNeeded()
            if entry is not None and not entry['being_calculated']:
                return entry['value']

Possible solutions:

  1. Avoid creating the lock to begin with; which will make calls to the decorated function to pile up.
  2. Set a timeout so the code will loop for a specific period of time, after which it will assume the lock is a deadlock and proceed to call the decorated function. This approach has been submitted in a PR.

Please don't hesitate in reaching out if you need more clarifications or some modifications in the PR.

Thanks

Nic

Allow to globally specify the location of the cache dir

In addition to configuring the cache file path for each wrapped function, a way to globally change the location of the general cache directory for all cachier caches is required.

A possible API:

>>> import cachier
>>> cachier.get_global_cache_dpath()
'/home/myuser/.cachier'
>>> cachier.set_global_cache_dpath('/home/opt/caches/')
>>> cachier.get_global_cache_dpath()
'/home/opt/caches/'

Consider using `functools` for argument hashing

Looking at how arg and kwarg hashing is done now, it is a little different from how the stdlib does it with functools.cache (see https://github.com/python/cpython/blob/3.10/Lib/functools.py#L448)

I think this builtin method is

  • more efficient
  • more bugfree (note the kwd_mark that separates the args from kwargs)
  • better maintained

What about switching to just using functools._make_key as the default hash function? Important to consider if the order of kwargs is important, they decided it was NOT, but I think swapping to this would be breaking for users here.

As a side note, monogo_core.get_entry() and memory_core.get_entry() have duplicated hashing logic that I think could get factored out?

Built-in hash() method is not deterministic, which breaks cachier since #17

Cachier is broken for me since #17 for string arguments (probably for other built-in objects as well).
This comes from the fact that the built-in hash() method is not deterministic between runs in python.
Therefore calling it on the arguments of the function for caching purpose does not work between multiple runs because it will hash to different values.

The problematic call to hash() is here.

Repro

Run this script twice in a row and it will re-execute the method test() each time without relying on the cache:

import time
from cachier import cachier

@cachier(pickle_reload=False)
def test(text):
    time.sleep(1)
    print(text)
    print(hash(text))
    return 0

start_time = time.time()
text = 'foo'
print(hash(text))
test(text)
print(time.time() - start_time)

Add "In Memory" cache backend.

This looks like a solid cache tool, but lack of an in memory backend to both avoid filesystem interaction and not require an external dependency like running mongodb, is a pretty big hinderance for me.

Support persistence when code is imported w/ importlib module

@shaoyucheng used cachier directly in his business code, and found it was not working. He then set up some test code to verify, and it worked well as expected. He finally found the key point was his business code was running through importlib module and the underlying framework codes generated a uuid for every module imported at every running time which disturbed the unique module name obtained by cachier.

  1. It's a point worthy of attention if your running mechanism is similar to his.
  2. It might be worthwhile supporting this functionality.

Annoying MongoDB warning message

If you don't have pymongo installed, you get the following warning message whenever you use Cachier:

Cachier warning: pymongo was not found. MongoDB cores will not work.

There should be some way to turn this off.

Multiple concurrent writers (and readers) with shared NFS mount

I'm seeing an issue with cachier where I'm getting Bad File Descriptor errors. Just want to make sure I'm not completely abusing it here, I have multiple writers (and readers) accessing a shared cache directory via NFS.

Am I way off the mark here for thinking this would ever work correctly?

Cannot pickle over 4GB

Hi, Great package, finally found this one that I was able to use for my purposes. I am running into an issue using 3.7 python and pickle not being able to save over 4GB. I have forked and updated line 139 of pickle_core.py to use protocol 4:

  pickle.dump(cache, cache_file, protocol=4)

However, I am not sure if earlier version of python would accept this hard coded protocol which is why I didn't do a PR. I am wondering if you could update your package to include this somehow?

Thanks again for for cachier :)

Python 2 compatibility

I cannot get it to run in Python 2.
The first complaint is about line 458 in core.py (due to)

        def func_wrapper(
                *args,

I also notices some super() calls [1] [2], which are only supported in Python 3.

Notify successful fresh cache retrieval

Hi, I'd like to propose an enhancement similar to verbose_cache.

I will call the new keyword argument notify_fresh. The wrapped function will return a tuple where the second value indicates if the value was fresh from cache.

This will enable logic outside the scope of the cached function to make decisions based on whether the data came from the cache.

For example, let's pretend we have a snobby server that hates being asked too many questions in rapid succession. If we get our answer fresh from the cache, then we can ask the next question right away:

@cachier()
def small_talk_to_snobby_server(query):
    response = "I'm fine, thank you!"
    return response

if __name__ == "__main__":
    response, is_fresh = small_talk_to_snobby_server(query="How are you?", notify_fresh=True)
    if not is_fresh:
        sleep(60)
    response = small_talk_to_snobby_server(query="What are you up to these days?")

Some self-critique

I find it rather clunky and API-breaking to make functions return a different data type than expected. I do not know any other way around this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.