[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\neggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.cache\nnosetests.xml\ncoverage.xml\n\n# Translations\n*.mo\n\n# Mr Developer\n.mr.developer.cfg\n.project\n.pydevproject\n\n# Rope\n.ropeproject\n\n# Django stuff:\n*.log\n*.pot\n\n# Sphinx documentation\ndocs/_build/\n\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: python\nmatrix:\n  include:\n    - arch: arm64\n      python: 2.7\n    - arch: amd64\n      python: 2.7\n    - arch: arm64\n      python: 3.4\n    - arch: amd64\n      python: 3.4\n    - arch: arm64\n      python: 3.5\n    - arch: amd64\n      python: 3.5\n    - arch: arm64\n      python: 3.6\n    - arch: amd64\n      python: 3.6\n    - arch: arm64\n      python: 3.7\n    - arch: amd64\n      python: 3.7\n\ninstall: python setup.py install\n\nscript: bash bin/test_travis.sh\n"
  },
  {
    "path": "CHANGELOG",
    "content": "*1.0.2*\n- use pytest for testing\n- Bug fix for windows compatibility\n\n*1.0.1*\n- better error checking so fastcache now plays well with signals.  There is a performance hit for this.  Next Release should handle this better.\n\n*1.0.0*\n\n- clru_cache now supports dynamic attributes.\n- (c)lru_cache is now threadsafe via custom reentrant locks.\n\n*0.4.3*\n\n- Fixed bug in hash computations which resulted in `stack overflow`.  The appropriate error (RuntimeError) is now returned\n\n*0.4.2*\n\n- The 'state' argument to clru_cache can now be a list or a dict\n- Slight performance improvemants\n- Fixed compiler warnings for Python 2 builds\n- Use setuptools by default.  The environment variable USE_DISTUTILS=True\n  forces the use of distutils\n\n*0.4.0*\n\nAPI change:\n\nDefault behavior of fastcache is changed to raise TypeError on\nunhashable arguments to be 100% consistent with stdlib.\n\nIntroduce a new argument 'unhashable' which controls how fastcache\nresponds to unhashable arguments:\n\t*'error' (default) - raise TypeError\n\t*'warning' - raise UserWarning and call decorated function with args\n\t*'ignore'  - call decorated function\n\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Peter Brady\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENSE\n"
  },
  {
    "path": "README.md",
    "content": "fastcache\n=========\n[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/pbrady/fastcache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n\nC implementation of Python 3 lru_cache for Python 2.6, 2.7, 3.2, 3.3, 3.4\n\nPasses all tests in the standard library for functools.lru_cache.\n\nObeys same API as Python 3.3/3.4 functools.lru_cache with 2 enhancements:\n\n1.  An additional argument `state` may be supplied which must be a `list` or `dict`.  This allows one to safely cache functions for which the result depends on some context which is not a part of the function call signature.\n2.  An additional argument `unhashable` may be supplied to control how the cached function responds to unhashable arguments.  The options are:\n  *  \"error\" (default) - Raise a `TypeError`\n  *  \"warning\"         - Raise a `UserWarning` and call the wrapped function with the supplied arguments.\n  *  \"ignore\"          - Just call the wrapped function with the supplied arguments.\n\nPerformance Warning\n-------\nAs of Python 3.5, the CPython interpreter implements `functools.lru_cache` in C.  It is generally faster than this library\ndue to its use of a more performant internal API for dictionaries (and perhaps other reasons).  Therefore this library\nis only recommended for Python 2.6-3.4\n\nInstall\n-------\n\nVia [pip](https://pypi.python.org/pypi/fastcache):\n\n    pip install fastcache\n\nManually :\n\n    git clone https://github.com/pbrady/fastcache.git\n    cd fastcache\n    python setup.py install\n\nVia [conda](http://conda.pydata.org/docs/index.html) :\n  \n  * build latest and greatest github version\n\n```bash  \ngit clone https://github.com/pbrady/fastcache.git\nconda-build fastcache\nconda install --use-local fastcache\n```\n\n  * build latest released version on pypi\n```bash\ngit clone https://github.com/conda/conda-recipes.git\nconda-build conda-recipes/fastcache\nconda install --use-local fastcache\n```\n\nTest\n----\n\n```python\n>>> import fastcache\n>>> fastcache.test()\n```\n\nTravis CI status :  [![alt text][2]][1]\n\n[2]: https://travis-ci.org/pbrady/fastcache.svg?branch=master (Travis build status)\n[1]: http://travis-ci.org/pbrady/fastcache\n\nTests include the official suite of tests from Python standard library for functools.lru_cache\n\nUse\n---\n\n    >>> from fastcache import clru_cache, __version__\n    >>> __version__\n    '0.3.3'\n    >>> @clru_cache(maxsize=325, typed=False)\n    ... def fib(n):\n    ...     \"\"\"Terrible Fibonacci number generator.\"\"\"\n    ...     return n if n < 2 else fib(n-1) + fib(n-2)\n    ...\n    >>> fib(300)\n    222232244629420445529739893461909967206666939096499764990979600\n    >>> fib.cache_info()\n    CacheInfo(hits=298, misses=301, maxsize=325, currsize=301)\n    >>> print(fib.__doc__)\n    Terrible Fibonacci number generator.\n    >>> fib.cache_clear()\n    >>> fib.cache_info()\n    CacheInfo(hits=0, misses=0, maxsize=325, currsize=0)\n    >>> fib.__wrapped__(300)\n    222232244629420445529739893461909967206666939096499764990979600\n\n\nSpeed\n-----\n\nThe speed up vs `lru_cache` provided by `functools` in 3.3 or 3.4 is 10x-30x depending on the function signature and whether one is comparing with 3.3 or 3.4.  A sample run of the benchmarking suite for 3.3 is\n\n\t>>> import sys\n\t>>> sys.version_info\n\tsys.version_info(major=3, minor=3, micro=5, releaselevel='final', serial=0)\n\t>>> from fastcache import benchmark\n\t>>> benchmark.run()\n\tTest Suite 1 :\n\n\tPrimarily tests cost of function call, hashing and cache hits.\n\tBenchmark script based on\n\t\thttp://bugs.python.org/file28400/lru_cache_bench.py\n\n\tfunction call                 speed up\n\tuntyped(i)                       11.31, typed(i)                         31.20\n\tuntyped(\"spam\", i)               16.71, typed(\"spam\", i)                 27.50\n\tuntyped(\"spam\", \"spam\", i)       14.24, typed(\"spam\", \"spam\", i)         22.62\n\tuntyped(a=i)                     13.25, typed(a=i)                       23.92\n\tuntyped(a=\"spam\", b=i)           10.51, typed(a=\"spam\", b=i)             18.58\n\tuntyped(a=\"spam\", b=\"spam\", c=i)  9.34, typed(a=\"spam\", b=\"spam\", c=i)   16.40\n\n\t\t\t\t min   mean    max\n\tuntyped    9.337 12.559 16.706\n\ttyped     16.398 23.368 31.197\n\n\n\tTest Suite 2 :\n\n\tTests millions of misses and millions of hits to quantify\n\tcache behavior when cache is full.\n\n\tfunction call                 speed up\n\tuntyped(i, j, a=\"spammy\")         8.94, typed(i, j, a=\"spammy\")          14.09\n\nA sample run of the benchmarking suite for 3.4 is\n\n\t>>> import sys\n\t>>> sys.version_info\n\tsys.version_info(major=3, minor=4, micro=1, releaselevel='final', serial=0)\n\t>>> from fastcache import benchmark\n\t>>> benchmark.run()\n\tTest Suite 1 :\n\n\tPrimarily tests cost of function call, hashing and cache hits.\n\tBenchmark script based on\n\t\thttp://bugs.python.org/file28400/lru_cache_bench.py\n\n\tfunction call                 speed up\n\tuntyped(i)                        9.74, typed(i)                         23.31\n\tuntyped(\"spam\", i)               15.21, typed(\"spam\", i)                 20.82\n\tuntyped(\"spam\", \"spam\", i)       13.35, typed(\"spam\", \"spam\", i)         17.43\n\tuntyped(a=i)                     12.27, typed(a=i)                       19.04\n\tuntyped(a=\"spam\", b=i)            9.81, typed(a=\"spam\", b=i)             14.25\n\tuntyped(a=\"spam\", b=\"spam\", c=i)  7.77, typed(a=\"spam\", b=\"spam\", c=i)   11.61\n\n\t\t\t\t min   mean    max\n\tuntyped    7.770 11.359 15.210\n\ttyped     11.608 17.743 23.311\n\n\n\tTest Suite 2 :\n\n\tTests millions of misses and millions of hits to quantify\n\tcache behavior when cache is full.\n\n\tfunction call                 speed up\n\tuntyped(i, j, a=\"spammy\")         8.27, typed(i, j, a=\"spammy\")          11.18\n"
  },
  {
    "path": "bin/test_travis.sh",
    "content": "#! /usr/bin/env bash\n\n# Exit on error\nset -e\n# Echo each command\nset -x\n\nmkdir -p empty\ncd empty\ncat << EOF | python\nimport fastcache\nif not fastcache.test():\n    raise Exception('Tests failed')\nEOF\n"
  },
  {
    "path": "build.sh",
    "content": "#!/bin/bash\n$PYTHON setup.py install\n"
  },
  {
    "path": "fastcache/__init__.py",
    "content": "\"\"\" C implementation of LRU caching.\n\nProvides 2 LRU caching function decorators:\n\nclru_cache - built-in (faster)\n           >>> from fastcache import clru_cache\n           >>> @clru_cache(maxsize=128,typed=False)\n           ... def f(a, b):\n           ...     return (a, ) + (b, )\n           ...\n           >>> type(f)\n           >>> <class 'fastcache.clru_cache'>\n\nlru_cache  - python wrapper around clru_cache (slower)\n           >>> from fastcache import lru_cache\n           >>> @lru_cache(maxsize=128,typed=False)\n           ... def f(a, b):\n           ...     return (a, ) + (b, )\n           ...\n           >>> type(f)\n           >>> <class 'function'>\n\"\"\"\n\n__version__ = \"1.1.0\"\n\n\nfrom ._lrucache import clru_cache\nfrom functools import update_wrapper\n\ndef lru_cache(maxsize=128, typed=False, state=None, unhashable='error'):\n    \"\"\"Least-recently-used cache decorator.\n\n    If *maxsize* is set to None, the LRU features are disabled and\n    the cache can grow without bound.\n\n    If *typed* is True, arguments of different types will be cached\n    separately. For example, f(3.0) and f(3) will be treated as distinct\n    calls with distinct results.\n\n    If *state* is a list or dict, the items will be incorporated into\n    argument hash.\n\n    The result of calling the cached function with unhashable (mutable)\n    arguments depends on the value of *unhashable*:\n\n        If *unhashable* is 'error', a TypeError will be raised.\n\n        If *unhashable* is 'warning', a UserWarning will be raised, and\n        the wrapped function will be called with the supplied arguments.\n        A miss will be recorded in the cache statistics.\n\n        If *unhashable* is 'ignore', the wrapped function will be called\n        with the supplied arguments. A miss will will be recorded in\n        the cache statistics.\n\n    View the cache statistics named tuple (hits, misses, maxsize, currsize)\n    with f.cache_info().  Clear the cache and statistics with\n    f.cache_clear(). Access the underlying function with f.__wrapped__.\n\n    See:  http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used\n\n    \"\"\"\n    def func_wrapper(func):\n        _cached_func = clru_cache(maxsize, typed, state, unhashable)(func)\n\n        def wrapper(*args, **kwargs):\n            return _cached_func(*args, **kwargs)\n\n        wrapper.__wrapped__ = func\n        wrapper.cache_info = _cached_func.cache_info\n        wrapper.cache_clear = _cached_func.cache_clear\n\n        return update_wrapper(wrapper,func)\n\n    return func_wrapper\n\ndef test(*args):\n    import pytest, os\n    return not pytest.main([os.path.dirname(os.path.abspath(__file__))] +\n                           list(args))\n"
  },
  {
    "path": "fastcache/benchmark.py",
    "content": "\"\"\" Benchmark against functools.lru_cache.\n\n    Benchmark script from http://bugs.python.org/file28400/lru_cache_bench.py\n    with a few modifications.\n\n    Not available for Py < 3.3.\n\"\"\"\nfrom __future__ import print_function\n\nimport sys\n\nif sys.version_info[:2] >= (3, 3):\n\n    import functools\n    import fastcache\n    import timeit\n    from itertools import count\n\n    def _untyped(*args, **kwargs):\n        pass\n\n    def _typed(*args, **kwargs):\n        pass\n\n    _py_untyped = functools.lru_cache(maxsize=100)(_untyped)\n    _c_untyped =  fastcache.clru_cache(maxsize=100)(_untyped)\n\n    _py_typed = functools.lru_cache(maxsize=100, typed=True)(_typed)\n    _c_typed =  fastcache.clru_cache(maxsize=100, typed=True)(_typed)\n\n    def _arg_gen(min=1, max=100, repeat=3):\n        for i in range(min, max):\n            for r in range(repeat):\n                for j, k in zip(range(i), count(i, -1)):\n                    yield j, k\n\n    def _print_speedup(results):\n        print('')\n        print('{:9s} {:>6s} {:>6s} {:>6s}'.format('','min', 'mean', 'max'))\n        def print_stats(name,off0, off1):\n            arr = [py[0]/c[0] for py, c in zip(results[off0::4],\n                                              results[off1::4])]\n            print('{:9s} {:6.3f} {:6.3f} {:6.3f}'.format(name,\n                                                         min(arr),\n                                                         sum(arr)/len(arr),\n                                                         max(arr)))\n        print_stats('untyped', 0, 1)\n        print_stats('typed', 2, 3)\n\n    def _print_single_speedup(res=None, init=False):\n        if init:\n            print('{:29s} {:>8s}'.format('function call', 'speed up'))\n        else:\n            print('{:32s} {:5.2f}'.format(res[0][1].split('_')[-1],\n                                          res[0][0]/res[1][0]), end = ', ')\n            print('{:32s} {:5.2f}'.format(res[2][1].split('_')[-1],\n                                          res[2][0]/res[3][0]))\n    def run():\n\n        print(\"Test Suite 1 : \", end='\\n\\n')\n        print(\"Primarily tests cost of function call, hashing and cache hits.\")\n        print(\"Benchmark script based on\")\n        print(\"    http://bugs.python.org/file28400/lru_cache_bench.py\",\n              end = '\\n\\n')\n\n        _print_single_speedup(init=True)\n\n        results = []\n        args = ['i', '\"spam\", i', '\"spam\", \"spam\", i',\n                'a=i', 'a=\"spam\", b=i', 'a=\"spam\", b=\"spam\", c=i']\n        for a in args:\n            for f in ['_py_untyped', '_c_untyped', '_py_typed', '_c_typed']:\n                s = '%s(%s)' % (f, a)\n                t = min(timeit.repeat('''\nfor i in range(100):\n    {}\n                '''.format(s),\n                        setup='from fastcache.benchmark import %s' % f,\n                        repeat=10, number=1000))\n                results.append([t, s])\n            _print_single_speedup(results[-4:])\n\n        _print_speedup(results)\n\n        print(\"\\n\\nTest Suite 2 :\", end='\\n\\n')\n        print(\"Tests millions of misses and millions of hits to quantify\")\n        print(\"cache behavior when cache is full.\", end='\\n\\n')\n        setup = \"from fastcache.benchmark import {}\\n\" + \\\n                \"from fastcache.benchmark import _arg_gen\"\n\n        results = []\n        for f in ['_py_untyped', '_c_untyped', '_py_typed', '_c_typed']:\n            s = '%s(i, j, a=\"spammy\")' % f\n            t = min(timeit.repeat('''\nfor i, j in _arg_gen():\n    %s\n            ''' % s, setup=setup.format(f),\n                                  repeat=3, number=100))\n            results.append([t, s])\n\n        _print_single_speedup(init=True)\n        _print_single_speedup(results)\n"
  },
  {
    "path": "fastcache/tests/__init__.py",
    "content": ""
  },
  {
    "path": "fastcache/tests/test_clrucache.py",
    "content": "import pytest\nimport fastcache\nimport itertools\nimport warnings\n\ntry:\n    itertools.count(start=0, step=-1)\n    count = itertools.count\nexcept TypeError:\n    def count(start=0, step=1):\n        i = step-1\n        for j, c in enumerate(itertools.count(start)):\n            yield c + i*j\n\ndef arg_gen(min=1, max=100, repeat=3):\n    for i in range(min, max):\n        for r in range(repeat):\n            for j, k in zip(range(i), count(i, -1)):\n                yield j, k\n\n@pytest.fixture(scope='module', params=[fastcache.clru_cache,\n                                        fastcache.lru_cache])\ndef cache(request):\n    param = request.param\n    return param\n\n\ndef test_function_attributes(cache):\n    \"\"\" Simple tests for attribute preservation. \"\"\"\n\n    def tfunc(a, b):\n        \"\"\"test function docstring.\"\"\"\n        return a + b\n    cfunc = cache()(tfunc)\n    assert cfunc.__doc__ == tfunc.__doc__\n    assert hasattr(cfunc, 'cache_info')\n    assert hasattr(cfunc, 'cache_clear')\n    assert hasattr(cfunc, '__wrapped__')\n\n\ndef test_function_cache(cache):\n    \"\"\" Test that cache returns appropriate values. \"\"\"\n\n    cat_tuples = [True]\n\n    def tfunc(a, b, c=None):\n        if (cat_tuples[0] == True):\n            return (a, b, c) + (c, a)\n        else:\n            return 2*a-10*b\n\n    cfunc = cache(maxsize=100, state=cat_tuples)(tfunc)\n\n    for i, j in arg_gen(max=75, repeat=5):\n        assert cfunc(i, j) == tfunc(i, j)\n\n    # change extra state\n    cat_tuples[0] = False\n\n    for i, j in arg_gen(max=75, repeat=5):\n        assert cfunc(i, j) == tfunc(i, j)\n\n    # test dict state\n    d = {}\n    cfunc = cache(maxsize=100, state=d)(tfunc)\n    cfunc(1, 2)\n    assert cfunc.cache_info().misses == 1\n    d['a'] = 42\n    cfunc(1, 2)\n    assert cfunc.cache_info().misses == 2\n    cfunc(1, 2)\n    assert cfunc.cache_info().misses == 2\n    assert cfunc.cache_info().hits == 1\n    d.clear()\n    cfunc(1, 2)\n    assert cfunc.cache_info().misses == 2\n    assert cfunc.cache_info().hits == 2\n    d['a'] = 44\n    cfunc(1, 2)\n    assert cfunc.cache_info().misses == 3\n\ndef test_memory_leaks(cache):\n    \"\"\" Longer running test to check for memory leaks. \"\"\"\n\n    def tfunc(a, b, c):\n        return (a-1, 2*c) + (10*b-1, a*b, a*b+c)\n\n    cfunc = cache(maxsize=2000)(tfunc)\n\n    for i, j in arg_gen(max=1500, repeat=5):\n        assert cfunc(i, j, c=i-j) == tfunc(i, j, c=i-j)\n\ndef test_warn_unhashable_args(cache, recwarn):\n    \"\"\" Function arguments must be hashable. \"\"\"\n\n    @cache(unhashable='warning')\n    def f(a, b):\n        return (a, ) + (b, )\n\n    with warnings.catch_warnings() :\n        warnings.simplefilter(\"always\")\n        assert f([1], 2) == f.__wrapped__([1], 2)\n        w = recwarn.pop(UserWarning)\n        assert issubclass(w.category, UserWarning)\n        assert \"Unhashable arguments cannot be cached\" in str(w.message)\n        assert w.filename\n        assert w.lineno\n\n\ndef test_ignore_unhashable_args(cache):\n    \"\"\" Function arguments must be hashable. \"\"\"\n\n    @cache(unhashable='ignore')\n    def f(a, b):\n        return (a, ) + (b, )\n\n    assert f([1], 2) == f.__wrapped__([1], 2)\n\ndef test_default_unhashable_args(cache):\n    @cache()\n    def f(a, b):\n        return (a, ) + (b, )\n\n    with pytest.raises(TypeError):\n        f([1], 2)\n\n    @cache(unhashable='error')\n    def f(a, b):\n        pass\n    with pytest.raises(TypeError):\n        f([1], 2)\n\ndef test_state_type(cache):\n    \"\"\" State must be a list or dict. \"\"\"\n    f = lambda x : x\n    with pytest.raises(TypeError):\n        cache(state=(1, ))(f)\n    with pytest.raises(TypeError):\n        cache(state=-1)(f)\n\ndef test_typed_False(cache):\n    \"\"\" Verify typed==False. \"\"\"\n\n    @cache(typed=False)\n    def cfunc(a, b):\n        return a+b\n\n    # initialize cache with integer args\n    cfunc(1, 2)\n    assert cfunc(1, 2) is cfunc(1.0, 2)\n    assert cfunc(1, 2) is cfunc(1, 2.0)\n    # test keywords\n    cfunc(1, b=2)\n    assert cfunc(1,b=2) is cfunc(1.0,b=2)\n    assert cfunc(1,b=2) is cfunc(1,b=2.0)\n\ndef test_typed_True(cache):\n    \"\"\" Verify typed==True. \"\"\"\n\n    @cache(typed=True)\n    def cfunc(a, b):\n        return a+b\n\n    assert cfunc(1, 2) is not cfunc(1.0, 2)\n    assert cfunc(1, 2) is not cfunc(1, 2.0)\n    # test keywords\n    assert cfunc(1,b=2) is not cfunc(1.0,b=2)\n    assert cfunc(1,b=2) is not cfunc(1,b=2.0)\n\ndef test_dynamic_attribute(cache):\n    f = lambda x : x\n    cfunc = cache()(f)\n    cfunc.new_attr = 5\n    assert cfunc.new_attr == 5\n"
  },
  {
    "path": "fastcache/tests/test_functools.py",
    "content": "# Copied from python src Python-3.4.0/Lib/test/test_functools.py\n\nimport abc\nimport collections\nfrom itertools import permutations\nimport pickle\nfrom random import choice\nimport sys\nimport unittest\nimport fastcache\nimport functools\n\ntry:\n    from functools import _CacheInfo\nexcept ImportError:\n    _CacheInfo = collections.namedtuple(\"CacheInfo\", \n                    [\"hits\", \"misses\", \"maxsize\", \"currsize\"])\n\nclass TestLRU(unittest.TestCase):\n\n    def test_lru(self):\n        def orig(x, y):\n            return 3 * x + y\n        f = fastcache.clru_cache(maxsize=20)(orig)\n        hits, misses, maxsize, currsize = f.cache_info()\n        self.assertEqual(maxsize, 20)\n        self.assertEqual(currsize, 0)\n        self.assertEqual(hits, 0)\n        self.assertEqual(misses, 0)\n\n        domain = range(5)\n        for i in range(1000):\n            x, y = choice(domain), choice(domain)\n            actual = f(x, y)\n            expected = orig(x, y)\n            self.assertEqual(actual, expected)\n        hits, misses, maxsize, currsize = f.cache_info()\n        self.assertTrue(hits > misses)\n        self.assertEqual(hits + misses, 1000)\n        self.assertEqual(currsize, 20)\n\n        f.cache_clear()   # test clearing\n        hits, misses, maxsize, currsize = f.cache_info()\n        self.assertEqual(hits, 0)\n        self.assertEqual(misses, 0)\n        self.assertEqual(currsize, 0)\n        f(x, y)\n        hits, misses, maxsize, currsize = f.cache_info()\n        self.assertEqual(hits, 0)\n        self.assertEqual(misses, 1)\n        self.assertEqual(currsize, 1)\n\n        # Test bypassing the cache\n        if hasattr(self, 'assertIs'):\n            self.assertIs(f.__wrapped__, orig)\n        f.__wrapped__(x, y)\n        hits, misses, maxsize, currsize = f.cache_info()\n        self.assertEqual(hits, 0)\n        self.assertEqual(misses, 1)\n        self.assertEqual(currsize, 1)\n\n        # test size zero (which means \"never-cache\")\n        @fastcache.clru_cache(0)\n        def f():\n            #nonlocal f_cnt\n            f_cnt[0] += 1\n            return 20\n        self.assertEqual(f.cache_info().maxsize, 0)\n        f_cnt = [0]\n        for i in range(5):\n            self.assertEqual(f(), 20)\n        self.assertEqual(f_cnt, [5])\n        hits, misses, maxsize, currsize = f.cache_info()\n        self.assertEqual(hits, 0)\n        self.assertEqual(misses, 5)\n        self.assertEqual(currsize, 0)\n\n        # test size one\n        @fastcache.clru_cache(1)\n        def f():\n            #nonlocal f_cnt\n            f_cnt[0] += 1\n            return 20\n        self.assertEqual(f.cache_info().maxsize, 1)\n        f_cnt[0] = 0\n        for i in range(5):\n            self.assertEqual(f(), 20)\n        self.assertEqual(f_cnt, [1])\n        hits, misses, maxsize, currsize = f.cache_info()\n        self.assertEqual(hits, 4)\n        self.assertEqual(misses, 1)\n        self.assertEqual(currsize, 1)\n\n        # test size two\n        @fastcache.clru_cache(2)\n        def f(x):\n            #nonlocal f_cnt\n            f_cnt[0] += 1\n            return x*10\n        self.assertEqual(f.cache_info().maxsize, 2)\n        f_cnt[0] = 0\n        for x in 7, 9, 7, 9, 7, 9, 8, 8, 8, 9, 9, 9, 8, 8, 8, 7:\n            #    *  *              *                          *\n            self.assertEqual(f(x), x*10)\n        self.assertEqual(f_cnt, [4])\n        hits, misses, maxsize, currsize = f.cache_info()\n        self.assertEqual(hits, 12)\n        self.assertEqual(misses, 4)\n        self.assertEqual(currsize, 2)\n\n    def test_lru_with_maxsize_none(self):\n        @fastcache.clru_cache(maxsize=None)\n        def fib(n):\n            if n < 2:\n                return n\n            return fib(n-1) + fib(n-2)\n        self.assertEqual([fib(n) for n in range(16)],\n            [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610])\n        self.assertEqual(fib.cache_info(),\n            _CacheInfo(hits=28, misses=16, maxsize=None, currsize=16))\n        fib.cache_clear()\n        self.assertEqual(fib.cache_info(),\n            _CacheInfo(hits=0, misses=0, maxsize=None, currsize=0))\n\n    def test_lru_with_exceptions(self):\n        # Verify that user_function exceptions get passed through without\n        # creating a hard-to-read chained exception.\n        # http://bugs.python.org/issue13177\n        for maxsize in (None, 128):\n            @fastcache.clru_cache(maxsize)\n            def func(i):\n                return 'abc'[i]\n            self.assertEqual(func(0), 'a')\n            try:\n                with self.assertRaises(IndexError) as cm:\n                    func(15)\n                # Does not have this attribute in Py2\n                if hasattr(cm.exception,'__context__'):\n                    self.assertIsNone(cm.exception.__context__)\n                # Verify that the previous exception did not result in a cached entry\n                with self.assertRaises(IndexError):\n                    func(15)\n            except TypeError:\n                # py26 unittest wants assertRaises called with another arg\n                if sys.version_info[:2] != (2, 6):\n                    raise\n                else:\n                    pass\n\n    def test_lru_with_types(self):\n        for maxsize in (None, 128):\n            @fastcache.clru_cache(maxsize=maxsize, typed=True)\n            def square(x):\n                return x * x\n            self.assertEqual(square(3), 9)\n            self.assertEqual(type(square(3)), type(9))\n            self.assertEqual(square(3.0), 9.0)\n            self.assertEqual(type(square(3.0)), type(9.0))\n            self.assertEqual(square(x=3), 9)\n            self.assertEqual(type(square(x=3)), type(9))\n            self.assertEqual(square(x=3.0), 9.0)\n            self.assertEqual(type(square(x=3.0)), type(9.0))\n            self.assertEqual(square.cache_info().hits, 4)\n            self.assertEqual(square.cache_info().misses, 4)\n\n    def test_lru_with_keyword_args(self):\n        @fastcache.clru_cache()\n        def fib(n):\n            if n < 2:\n                return n\n            return fib(n=n-1) + fib(n=n-2)\n        self.assertEqual(\n            [fib(n=number) for number in range(16)],\n            [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]\n        )\n        self.assertEqual(fib.cache_info(),\n            _CacheInfo(hits=28, misses=16, maxsize=128, currsize=16))\n        fib.cache_clear()\n        self.assertEqual(fib.cache_info(),\n            _CacheInfo(hits=0, misses=0, maxsize=128, currsize=0))\n\n    def test_lru_with_keyword_args_maxsize_none(self):\n        @fastcache.clru_cache(maxsize=None)\n        def fib(n):\n            if n < 2:\n                return n\n            return fib(n=n-1) + fib(n=n-2)\n        self.assertEqual([fib(n=number) for number in range(16)],\n            [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610])\n        self.assertEqual(fib.cache_info(),\n            _CacheInfo(hits=28, misses=16, maxsize=None, currsize=16))\n        fib.cache_clear()\n        self.assertEqual(fib.cache_info(),\n            _CacheInfo(hits=0, misses=0, maxsize=None, currsize=0))\n\n    def test_need_for_rlock(self):\n        # This will deadlock on an LRU cache that uses a regular lock\n\n        @fastcache.clru_cache(maxsize=10)\n        def test_func(x):\n            'Used to demonstrate a reentrant lru_cache call within a single thread'\n            return x\n\n        class DoubleEq:\n            'Demonstrate a reentrant lru_cache call within a single thread'\n            def __init__(self, x):\n                self.x = x\n            def __hash__(self):\n                return self.x\n            def __eq__(self, other):\n                if self.x == 2:\n                    test_func(DoubleEq(1))\n                return self.x == other.x\n\n        test_func(DoubleEq(1))                      # Load the cache\n        test_func(DoubleEq(2))                      # Load the cache\n        self.assertEqual(test_func(DoubleEq(2)),    # Trigger a re-entrant __eq__ call\n                         DoubleEq(2))               # Verify the correct return value\n"
  },
  {
    "path": "fastcache/tests/test_thread.py",
    "content": "\"\"\"The Python interpreter may switch between threads inbetween bytecode\nexecution.  Bytecode execution in fastcache may occur during:\n(1) Calls to make_key which will call the __hash__ methods of the args and\n(2) `PyDict_Get(Set)Item` calls rely on Python comparisons (i.e, __eq__)\n    to determine if a match has been found\n\nA good test for threadsafety is then to cache a function which takes user\ndefined Python objects that have __hash__ and __eq__ methods which live in\nPython land rather built-in land.\n\nThe test should not only ensure that the correct result is acheived (and no\nsegfaults) but also assess memory leaks.\n\nThe thread switching interval can be altered using sys.setswitchinterval.\n\"\"\"\n\nclass PythonInt:\n    \"\"\" Wrapper for an integer with python versions of __eq__ and __hash__.\"\"\"\n\n    def __init__(self, val):\n        self.value = val\n\n    def __hash__(self):\n        return hash(self.value)\n\n    def __eq__(self, other):\n        # only compare with other instances of PythonInt\n        if not isinstance(other, PythonInt):\n            raise TypeError(\"PythonInt cannot be compared to %s\" % type(other))\n        return self.value == other.value\n\nfrom random import randint\nimport unittest\nfrom fastcache import clru_cache as lru_cache\nfrom threading import Thread\ntry:\n    from sys import setswitchinterval as setinterval\nexcept ImportError:\n    from sys import setcheckinterval\n    def setinterval(i):\n        return setcheckinterval(int(i))\n\n\ndef run_threads(threads):\n    for t in threads:\n        t.start()\n    for t in threads:\n        t.join()\n\nCACHE_SIZE=301\nFIB=CACHE_SIZE-1\nRAND_MIN, RAND_MAX = 1, 10\n\n@lru_cache(maxsize=CACHE_SIZE, typed=False)\ndef fib(n):\n    \"\"\"Terrible Fibonacci number generator.\"\"\"\n    v = n.value\n    return v if v < 2 else fib(PythonInt(v-1)) + fib(PythonInt(v-2))\n\n# establish correct result from single threaded exectution\nRESULT = fib(PythonInt(FIB))\n\ndef run_fib_with_clear(r):\n    \"\"\" Run Fibonacci generator r times. \"\"\"\n    for i in range(r):\n        if randint(RAND_MIN, RAND_MAX) == RAND_MIN:\n            fib.cache_clear()\n        res = fib(PythonInt(FIB))\n        if RESULT != res:\n            raise ValueError(\"Expected %d, Got %d\" % (RESULT, res))\n\ndef run_fib_with_stats(r):\n    \"\"\" Run Fibonacci generator r times. \"\"\"\n    for i in range(r):\n        res = fib(PythonInt(FIB))\n        if RESULT != res:\n            raise ValueError(\"Expected %d, Got %d\" % (RESULT, res))\n\n\nclass Test_Threading(unittest.TestCase):\n    \"\"\" Threadsafety Tests for lru_cache. \"\"\"\n\n    def setUp(self):\n        setinterval(1e-6)\n        self.numthreads = 4\n        self.repeat = 1000\n\n    def test_thread_random_cache_clears(self):\n        \"\"\" randomly clear the cache during calls to fib. \"\"\"\n\n        threads = [Thread(target=run_fib_with_clear, args=(self.repeat, ))\n                   for _ in range(self.numthreads)]\n        run_threads(threads)\n        # if we have gotten this far no exceptions have been raised\n        self.assertEqual(0, 0)\n\n    def test_thread_cache_info(self):\n        \"\"\" Run thread safety test to make sure the cache statistics\n        are correct.\"\"\"\n        fib.cache_clear()\n        threads = [Thread(target=run_fib_with_stats, args=(self.repeat, ))\n                   for _ in range(self.numthreads)]\n        run_threads(threads)\n\n        hits, misses, maxsize, currsize = fib.cache_info()\n        self.assertEqual(misses, CACHE_SIZE)\n        self.assertEqual(currsize, CACHE_SIZE)\n"
  },
  {
    "path": "meta.yaml",
    "content": "package:\n  name: fastcache\n  version: 0.4.0\n\nsource:\n   git_url : https://github.com/pbrady/fastcache.git\n\nrequirements:\n  build:\n    - python\n    - setuptools    \n\n  run:\n    - python\n\ntest:\n  # Python imports\n  imports:\n    - fastcache\n    - fastcache.benchmark\n    - fastcache.tests\n\n\nabout:\n  home: https://github.com/pbrady/fastcache.git\n  license: MIT License\n  summary: 'C implementation of Python 3 lru_cache'\n\n# See\n# http://docs.continuum.io/conda/build.html for\n# more information about meta.yaml\n"
  },
  {
    "path": "scripts/threadsafety.py",
    "content": "from __future__ import division\n\n\"\"\"The Python interpreter may switch between threads inbetween bytecode\nexecution.  Bytecode execution in fastcache may occur during:\n(1) Calls to make_key which will call the __hash__ methods of the args and\n(2) `PyDict_Get(Set)Item` calls rely on Python comparisons (i.e, __eq__)\n    to determine if a match has been found\n\nA good test for threadsafety is then to cache a function which takes user\ndefined Python objects that have __hash__ and __eq__ methods which live in\nPython land rather built-in land.\n\nThe test should not only ensure that the correct result is acheived (and no\nsegfaults) but also assess memory leaks.\n\nThe thread switching interval can be altered using sys.setswitchinterval.\n\"\"\"\n\nclass PythonInt:\n    \"\"\" Wrapper for an integer with python versions of __eq__ and __hash__.\"\"\"\n\n    def __init__(self, val):\n        self.value = val\n\n    def __hash__(self):\n        return hash(self.value)\n\n    def __eq__(self, other):\n        # only compare with other instances of PythonInt\n        if not isinstance(other, PythonInt):\n            raise TypeError(\"PythonInt cannot be compared to %s\" % type(other))\n        return self.value == other.value\n\nfrom fastcache import clru_cache\n#from functools import lru_cache as clru_cache\nfrom random import randint\n\nCACHE_SIZE=301\nFIB=CACHE_SIZE-1\nRAND_MIN, RAND_MAX = 1, 10\n\n@clru_cache(maxsize=CACHE_SIZE, typed=False)\ndef fib(n):\n    \"\"\"Terrible Fibonacci number generator.\"\"\"\n    v = n.value\n    return v if v < 2 else fib2(PythonInt(v-1)) + fib(PythonInt(v-2))\n\n@clru_cache(maxsize=CACHE_SIZE, typed=False)\ndef fib2(n):\n    \"\"\"Terrible Fibonacci number generator.\"\"\"\n    v = n.value\n    return v if v < 2 else fib(PythonInt(v-1)) + fib2(PythonInt(v-2))\n\n\n\n# establish correct result from single threaded exectution\nRESULT = fib(PythonInt(FIB))\n\ndef run_fib_with_clear(r):\n    \"\"\" Run Fibonacci generator r times. \"\"\"\n    for i in range(r):\n        if randint(RAND_MIN, RAND_MAX) == RAND_MIN:\n            fib.cache_clear()\n            fib2.cache_clear()\n        res = fib(PythonInt(FIB))\n        if RESULT != res:\n            raise ValueError(\"Expected %d, Got %d\" % (RESULT, res))\n\ndef run_fib_with_stats(r):\n    \"\"\" Run Fibonacci generator r times. \"\"\"\n    for i in range(r):\n        res = fib(PythonInt(FIB))\n        if RESULT != res:\n            raise ValueError(\"Expected %d, Got %d\" % (RESULT, res))\n\nfrom threading import Thread\ntry:\n    from sys import setswitchinterval as setinterval\nexcept ImportError:\n    from sys import setcheckinterval\n    def setinterval(i):\n        return setcheckinterval(int(i))\n\n\ndef run_threads(threads):\n    for t in threads:\n        t.start()\n    for t in threads:\n        t.join()\n\ndef run_test(n, r, i):\n    \"\"\" Run thread safety test with n threads r times using interval i. \"\"\"\n    setinterval(i)\n    threads = [Thread(target=run_fib_with_clear, args=(r, )) for _ in range(n)]\n    run_threads(threads)\n\ndef run_test2(n, r, i):\n    \"\"\" Run thread safety test to make sure the cache statistics\n    are correct.\"\"\"\n    fib.cache_clear()\n    setinterval(i)\n    threads = [Thread(target=run_fib_with_stats, args=(r, )) for _ in range(n)]\n    run_threads(threads)\n\n    hits, misses, maxsize, currsize = fib.cache_info()\n    if misses != CACHE_SIZE//2+1:\n        raise ValueError(\"Expected %d misses, Got %d\" %\n                         (CACHE_SIZE//2+1, misses))\n    if maxsize != CACHE_SIZE:\n        raise ValueError(\"Expected %d maxsize, Got %d\" %\n                         (CACHE_SIZE, maxsize))\n    if currsize != CACHE_SIZE//2+1:\n        raise ValueError(\"Expected %d currsize, Got %d\" %\n                         (CACHE_SIZE//2+1, currsize))\n\nimport argparse\n\ndef main():\n    parser = argparse.ArgumentParser(description='Run threadsafety test.')\n    parser.add_argument('-n,--numthreads',\n                        type=int,\n                        default=2,\n                        dest='n',\n                        help='Number of threads.')\n    parser.add_argument('-r,--repeat',\n                        type=int,\n                        default=5000,\n                        dest='r',\n                        help='Number of times to repeat test.  Larger numbers '+\n                        'will make it easier to spot memory leaks.')\n    parser.add_argument('-i,--interval',\n                        type=float,\n                        default=1e-6,\n                        dest='i',\n                        help='Time in seconds for sys.setswitchinterval.')\n\n    run_test(**dict(vars(parser.parse_args())))\n    run_test2(**dict(vars(parser.parse_args())))\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "setup.cfg",
    "content": "[metadata]\ndescription-file = README.md"
  },
  {
    "path": "setup.py",
    "content": "import sys\nfrom os import getenv\n\n# use setuptools by default as per the official advice at:\n# packaging.python.org/en/latest/current.html#packaging-tool-recommendations\nuse_setuptools = True\n# set the environment variable USE_DISTUTILS=True to force the use of distutils\nuse_distutils = getenv('USE_DISTUTILS')\nif use_distutils is not None:\n    if use_distutils.lower() == 'true':\n        use_setuptools = False\n    else:\n        print(\"Value {} for USE_DISTUTILS treated as False\".\\\n              format(use_distutils))\n\nfrom distutils.command.build import build as _build\n\nif use_setuptools:\n    try:\n        from setuptools import setup, Extension\n        from setuptools.command.install import install as _install\n        from setuptools.command.build_ext import build_ext as _build_ext\n    except ImportError:\n        use_setuptools = False\n\nif not use_setuptools:\n    from distutils.core import setup, Extension\n    from distutils.command.install import install as _install\n    from distutils.command.build_ext import build_ext as _build_ext\n\nvinfo = sys.version_info[:2]\nif vinfo < (2, 6):\n    print(\"Fastcache currently requires Python 2.6 or newer.  \"+\n          \"Python {}.{} detected\".format(*vinfo))\n    sys.exit(-1)\nif vinfo[0] == 3 and vinfo < (3, 2):\n    print(\"Fastcache currently requires Python 3.2 or newer.  \"+\n          \"Python {}.{} detected\".format(*vinfo))\n    sys.exit(-1)\n\nclassifiers = [\n    'License :: OSI Approved :: MIT License',\n    'Operating System :: OS Independent',\n    'Programming Language :: Python',\n    'Programming Language :: Python :: 2',\n    'Programming Language :: Python :: 2.6',\n    'Programming Language :: Python :: 2.7',\n    'Programming Language :: Python :: 3',\n    'Programming Language :: Python :: 3.2',\n    'Programming Language :: Python :: 3.3',\n    'Programming Language :: Python :: 3.4',\n    'Programming Language :: C',\n\n]\n\nlong_description = '''\nC implementation of Python 3 functools.lru_cache.  Provides speedup of 10-30x\nover standard library.  Passes test suite from standard library for lru_cache.\n\nProvides 2 Least Recently Used caching function decorators:\n\n  clru_cache - built-in (faster)\n             >>> from fastcache import clru_cache, __version__\n             >>> __version__\n             '1.1.0'\n             >>> @clru_cache(maxsize=325, typed=False)\n             ... def fib(n):\n             ...     \"\"\"Terrible Fibonacci number generator.\"\"\"\n             ...     return n if n < 2 else fib(n-1) + fib(n-2)\n             ...\n             >>> fib(300)\n             222232244629420445529739893461909967206666939096499764990979600\n             >>> fib.cache_info()\n             CacheInfo(hits=298, misses=301, maxsize=325, currsize=301)\n             >>> print(fib.__doc__)\n             Terrible Fibonacci number generator.\n             >>> fib.cache_clear()\n             >>> fib.cache_info()\n             CacheInfo(hits=0, misses=0, maxsize=325, currsize=0)\n             >>> fib.__wrapped__(300)\n             222232244629420445529739893461909967206666939096499764990979600\n             >>> type(fib)\n             >>> <class 'fastcache.clru_cache'>\n\n  lru_cache  - python wrapper around clru_cache\n             >>> from fastcache import lru_cache\n             >>> @lru_cache(maxsize=128, typed=False)\n             ... def f(a, b):\n             ...     pass\n             ...\n             >>> type(f)\n             >>> <class 'function'>\n\n\n  (c)lru_cache(maxsize=128, typed=False, state=None, unhashable='error')\n\n      Least-recently-used cache decorator.\n\n      If *maxsize* is set to None, the LRU features are disabled and the cache\n      can grow without bound.\n\n      If *typed* is True, arguments of different types will be cached separately.\n      For example, f(3.0) and f(3) will be treated as distinct calls with\n      distinct results.\n\n      If *state* is a list or dict, the items will be incorporated into the\n      argument hash.\n\n      The result of calling the cached function with unhashable (mutable)\n      arguments depends on the value of *unhashable*:\n\n          If *unhashable* is 'error', a TypeError will be raised.\n\n          If *unhashable* is 'warning', a UserWarning will be raised, and\n          the wrapped function will be called with the supplied arguments.\n          A miss will be recorded in the cache statistics.\n\n          If *unhashable* is 'ignore', the wrapped function will be called\n          with the supplied arguments. A miss will will be recorded in\n          the cache statistics.\n\n      View the cache statistics named tuple (hits, misses, maxsize, currsize)\n      with f.cache_info().  Clear the cache and statistics with f.cache_clear().\n      Access the underlying function with f.__wrapped__.\n\n      See:  http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used\n'''\n\n# the overall logic here is that by default macros can be only be passed if\n# one does 'python setup.py build_ext --define=MYMACRO'\n# If one attempts 'build' or 'install' with the --define flag, an error will\n# appear saying that --define is not an option\n# To get around this issue, we subclass build and install to capture --define\n# as well as build_ext which will use the --define arguments passed to\n# build or install\n\ndefine_opts = []\n\nclass BuildWithDefine(_build):\n\n    _build_opts = _build.user_options\n    user_options = [\n        ('define=', 'D',\n         \"C preprocessor macros to define\"),\n    ]\n    user_options.extend(_build_opts)\n\n    def initialize_options(self):\n        _build.initialize_options(self)\n        self.define = None\n\n    def finalize_options(self):\n        _build.finalize_options(self)\n        # The argument parsing will result in self.define being a string, but\n        # it has to be a list of 2-tuples.  All the preprocessor symbols\n        # specified by the 'define' option without an '=' will be set to '1'.\n        # Multiple symbols can be separated with commas.\n        if self.define:\n            defines = self.define.split(',')\n            self.define = [(s.strip(), 1) if '=' not in s else\n                           tuple(ss.strip() for ss in s.split('='))\n                           for s in defines]\n            define_opts.extend(self.define)\n\n    def run(self):\n        _build.run(self)\n\nclass InstallWithDefine(_install):\n\n    _install_opts = _install.user_options\n    user_options = [\n        ('define=', 'D',\n         \"C preprocessor macros to define\"),\n    ]\n    user_options.extend(_install_opts)\n\n    def initialize_options(self):\n        _install.initialize_options(self)\n        self.define = None\n\n    def finalize_options(self):\n        _install.finalize_options(self)\n        # The argument parsing will result in self.define being a string, but\n        # it has to be a list of 2-tuples.  All the preprocessor symbols\n        # specified by the 'define' option without an '=' will be set to '1'.\n        # Multiple symbols can be separated with commas.\n        if self.define:\n            defines = self.define.split(',')\n            self.define = [(s.strip(), 1) if '=' not in s else\n                           tuple(ss.strip() for ss in s.split('='))\n                           for s in defines]\n            define_opts.extend(self.define)\n\n    def run(self):\n        _install.run(self)\n\nclass BuildExt(_build_ext):\n\n    def initialize_options(self):\n        _build_ext.initialize_options(self)\n\n    def finalize_options(self):\n        _build_ext.finalize_options(self)\n        if self.define is not None:\n            self.define.extend(define_opts)\n        elif define_opts:\n            self.define = define_opts\n\n    def run(self):\n        _build_ext.run(self)\n\n\nsetup(name = \"fastcache\",\n      version = \"1.1.0\",\n      description = \"C implementation of Python 3 functools.lru_cache\",\n      long_description = long_description,\n      author = \"Peter Brady\",\n      author_email = \"petertbrady@gmail.com\",\n      license = \"MIT\",\n      url = \"https://github.com/pbrady/fastcache\",\n      packages = [\"fastcache\", \"fastcache.tests\"],\n      ext_modules = [Extension(\"fastcache._lrucache\",[\"src/_lrucache.c\"])],\n      classifiers = classifiers,\n      cmdclass={\n          'build' : BuildWithDefine,\n          'install' : InstallWithDefine,\n          'build_ext' : BuildExt,\n      }\n\n)\n"
  },
  {
    "path": "src/_lrucache.c",
    "content": "#include <Python.h>\n#include \"structmember.h\"\n#include \"pythread.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#if PY_MAJOR_VERSION == 2\n#define _PY2\ntypedef long Py_hash_t;\n#endif\n\n#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION == 2\n#define _PY32\n#endif\n\n#ifdef LLTRACE\n#define TBEGIN(x, line) printf(\"Beginning Trace of %s at lineno %d....\", x);\n#define TEND(x) printf(\"Finished!\\n\")\n#else\n#define TBEGIN(x, line)\n#define TEND(x)\n#endif\n\n#ifdef WITH_THREAD\n#ifdef _PY2\ntypedef int PyLockStatus;\nstatic PyLockStatus PY_LOCK_FAILURE = 0;\nstatic PyLockStatus PY_LOCK_ACQUIRED = 1;\nstatic PyLockStatus PY_LOCK_INTR = -999999;\n#endif\n\nstatic int\nrlock_acquire(PyThread_type_lock lock, long* rlock_owner, unsigned long* rlock_count)\n{\n    long tid;\n    PyLockStatus r;\n\n    tid = PyThread_get_thread_ident();\n    if (*rlock_count > 0 && tid == (*rlock_owner)) {\n        unsigned long count = *rlock_count + 1;\n        if (count <= *rlock_count) {\n            PyErr_SetString(PyExc_OverflowError,\n                            \"Internal lock count overflowed\");\n            return -1;\n        }\n        *rlock_count = count;\n        return 1;\n    }\n    /* do/while loop from acquire_timed */\n    do {\n        /* first a simple non-blocking try without releasing the GIL */\n#ifdef _PY2\n        r = PyThread_acquire_lock(lock, 0);\n#else\n        r = PyThread_acquire_lock_timed(lock, 0, 0);\n#endif\n        if (r == PY_LOCK_FAILURE) {\n            Py_BEGIN_ALLOW_THREADS\n#ifdef _PY2\n            r = PyThread_acquire_lock(lock, 1);\n#else\n            r = PyThread_acquire_lock_timed(lock, -1, 1);\n#endif\n            Py_END_ALLOW_THREADS\n        }\n\n        if (r == PY_LOCK_INTR) {\n            /* Run signal handlers if we were interrupted.  Propagate\n             * exceptions from signal handlers, such as KeyboardInterrupt, by\n             * passing up PY_LOCK_INTR.  */\n            if (Py_MakePendingCalls() < 0) {\n                return -1;\n            }\n        }\n    } while (r == PY_LOCK_INTR);  /* Retry if we were interrupted. */\n    if (r == PY_LOCK_ACQUIRED) {\n        *rlock_owner = tid;\n        *rlock_count = 1;\n        return 1;\n    }\n    return -1;\n}\n\nstatic int\nrlock_release(PyThread_type_lock lock, long* rlock_owner, unsigned long* rlock_count)\n{\n    long tid = PyThread_get_thread_ident();\n\n    if (*rlock_count == 0 || *rlock_owner != tid) {\n        PyErr_SetString(PyExc_RuntimeError,\n                        \"cannot release un-acquired lock\");\n        return -1;\n    }\n\n    if (--(*rlock_count) == 0) {\n        *rlock_owner = 0;\n        PyThread_release_lock(lock);\n    }\n    return 1;\n}\n\n#define ACQUIRE_LOCK(obj) rlock_acquire((obj)->lock, &((obj)->rlock_owner), &((obj)->rlock_count))\n#define RELEASE_LOCK(obj) rlock_release((obj)->lock, &((obj)->rlock_owner), &((obj)->rlock_count))\n#define FREE_LOCK(obj) PyThread_free_lock((obj)->lock)\n#else\n#define ACQUIRE_LOCK(obj) 1\n#define RELEASE_LOCK(obj) 1\n#define FREE_LOCK(obj)\n#endif\n\n#define INC_RETURN(op) return Py_INCREF(op), (op)\n\n// THREAD SAFETY NOTES:\n// Python bytecode instructions are atomic but the GIL may switch between\n// threads in between instructions.\n// To make this threadsafe care needs to be taken one such that global objects\n// are left in a consistent between calls to python bytecode.\n// The relevant global objects are co->root, and co->cache_dict\n// The stats are global as well but are modified in one line: stat++\n\n/* HashedArgs -- internal *****************************************/\ntypedef struct {\n  PyObject_HEAD\n  PyObject *args;\n  Py_hash_t hashvalue;\n} HashedArgs;\n\n\nstatic void\nHashedArgs_dealloc(HashedArgs *self)\n{\n  Py_XDECREF(self->args);\n  Py_TYPE(self)->tp_free(self);\n  return;\n}\n\n\n/* return precomputed tuple hash for speed */\nstatic Py_hash_t\nHashedArgs_hash(HashedArgs *self)\n{\n  return self->hashvalue;\n}\n\n\n/* Delegate comparison to tuples */\nstatic PyObject *\nHashedArgs_richcompare(PyObject *v, PyObject *w, int op)\n{\n  HashedArgs *hv = (HashedArgs *) v;\n  HashedArgs *hw = (HashedArgs *) w;\n  PyObject *res = PyObject_RichCompare(hv->args, hw->args, op);\n  return res;\n}\n\n\nstatic PyTypeObject HashedArgs_type = {\n  PyVarObject_HEAD_INIT(NULL, 0)\n  \"_lrucache.HashedArgs\",          /* tp_name */\n  sizeof(HashedArgs),              /* tp_basicsize */\n  0,                            /* tp_itemsize */\n  (destructor)HashedArgs_dealloc,  /* tp_dealloc */\n  0,                            /* tp_print */\n  0,                            /* tp_getattr */\n  0,                            /* tp_setattr */\n  0,                            /* tp_reserved */\n  0,                            /* tp_repr */\n  0,                            /* tp_as_number */\n  0,                            /* tp_as_sequence */\n  0,                            /* tp_as_mapping */\n  (hashfunc)HashedArgs_hash,    /* tp_hash */\n  0,                            /* tp_call */\n  0,                            /* tp_str */\n  0,                            /* tp_getattro */\n  0,                            /* tp_setattro */\n  0,                            /* tp_as_buffer */\n  Py_TPFLAGS_DEFAULT,             /* tp_flags */\n  0,                              /* tp_doc */\n  0,                        /* tp_traverse */\n  0,                       /* tp_clear */\n  HashedArgs_richcompare,     /* tp_richcompare */\n};\n\n/***************************************************\n End of HashedArgs\n***************************************************/\n\n/***********************************************************\n circular doubly linked list\n************************************************************/\ntypedef struct clist{\n  PyObject_HEAD\n  struct clist *prev;\n  struct clist *next;\n  PyObject *key;\n  PyObject *result;\n} clist;\n\n\nstatic void\nclist_dealloc(clist *co)\n{\n  clist *prev = co->prev;\n  clist *next = co->next;\n\n  // THREAD SAFETY NOTES:\n  // Calls to DECREF can result in bytecode and thread switching.\n  // Do DECREF after the linked list has been modified and is in\n  // an acceptable state.\n  if(prev != co){\n    // adjust neighbor pointers\n    prev->next = next;\n    next->prev = prev;\n  }\n  co->prev = NULL;\n  co->next = NULL;\n  Py_XDECREF(co->key);\n  Py_XDECREF(co->result);\n  Py_TYPE(co)->tp_free(co);\n  return;\n}\n\n\nstatic PyTypeObject clist_type = {\n  PyVarObject_HEAD_INIT(NULL, 0)\n  \"_lrucache.clist\",   /* tp_name */\n  sizeof(clist),       /* tp_basicsize */\n  0,                       /* tp_itemsize */\n  (destructor)clist_dealloc,   /* tp_dealloc */\n  0,                       /* tp_print */\n  0,                       /* tp_getattr */\n  0,                       /* tp_setattr */\n  0,                       /* tp_reserved */\n  0,                       /* tp_repr */\n  0,                       /* tp_as_number */\n  0,                       /* tp_as_sequence */\n  0,                       /* tp_as_mapping */\n  0,                       /* tp_hash */\n  0,                       /* tp_call */\n  0,                       /* tp_str */\n  0,                       /* tp_getattro */\n  0,                       /* tp_setattro */\n  0,                       /* tp_as_buffer */\n  Py_TPFLAGS_DEFAULT,      /* tp_flags */\n};\n\n\nstatic int\ninsert_first(clist *root, PyObject *key, PyObject *result){\n  // first element will be inserted at root->next\n  clist *first = PyObject_New(clist, &clist_type);\n  clist *oldfirst = root->next;\n\n  if(!first)\n    return -1;\n\n  first->result = result;\n  // This will be the only reference to key (HashedArgs), do not INCREF\n  first->key = key;\n\n  root->next = first;\n  first->next = oldfirst;\n  first->prev = root;\n  oldfirst->prev = first;\n  // INCREF result since it will be used by clist and returned to the caller\n  return  Py_INCREF(result), 1;\n}\n\n\nstatic PyObject *\nmake_first(clist *root, clist *node){\n  // make node the first node and return new reference to result\n  // save previous first position\n  clist *oldfirst = root->next;\n\n  if (oldfirst != node) {\n    // first adjust pointers around node's position\n    node->prev->next = node->next;\n    node->next->prev = node->prev;\n\n    root->next = node;\n    node->next = oldfirst;\n    node->prev = root;\n    oldfirst->prev = node;\n  }\n  INC_RETURN(node->result);\n}\n\n/**********************************************************\n cachedobject is the actual function with the cached results\n***********************************************************/\n\n/* how will unhashable arguments be handled */\nenum unhashable {FC_ERROR, FC_WARNING, FC_IGNORE, FC_FAIL};\n\n\ntypedef struct {\n  PyObject_HEAD\n  PyObject *fn ; // original function\n  PyObject *func_module, *func_name, *func_qualname, *func_annotations;\n  PyObject *func_dict;\n  PyObject *cache_dict;\n  PyObject *ex_state;\n  int typed;\n  enum unhashable err;\n  PyObject *cinfo; // named tuple constructor\n  Py_ssize_t maxsize, hits, misses;\n  clist *root;\n  // lock for cache access\n#ifdef WITH_THREAD\n  PyThread_type_lock lock;\n  long rlock_owner;\n  unsigned long rlock_count;\n#endif\n} cacheobject ;\n\n\n#define OFF(x) offsetof(cacheobject, x)\n// attributes from wrapped function\nstatic PyMemberDef cache_memberlist[] = {\n  {\"__wrapped__\", T_OBJECT, OFF(fn), RESTRICTED | READONLY},\n  {\"__module__\",  T_OBJECT, OFF(func_module), RESTRICTED | READONLY},\n  {\"__name__\",    T_OBJECT, OFF(func_name), RESTRICTED | READONLY},\n  {\"__qualname__\",T_OBJECT, OFF(func_qualname), RESTRICTED | READONLY},\n  {\"__annotations__\", T_OBJECT, OFF(func_annotations), RESTRICTED | READONLY},\n  {NULL} /* Sentinel */\n};\n\n\n// getsetters from wrapped function\nstatic PyObject *\ncache_get_doc(cacheobject * co, void *closure)\n{\n  PyFunctionObject * fn = (PyFunctionObject *) co->fn;\n  if (fn->func_doc == NULL)\n    Py_RETURN_NONE;\n\n  INC_RETURN(fn->func_doc);\n}\n\n#if defined(_PY2) || defined (_PY32)\n\nstatic int\nrestricted(void)\n{\n#ifdef _PY2\n    if (!PyEval_GetRestricted())\n#endif\n        return 0;\n    PyErr_SetString(PyExc_RuntimeError,\n        \"function attributes not accessible in restricted mode\");\n    return 1;\n}\n\n\nstatic PyObject *\nfunc_get_dict(PyFunctionObject *op)\n{\n    if (restricted())\n        return NULL;\n    if (op->func_dict == NULL) {\n        op->func_dict = PyDict_New();\n        if (op->func_dict == NULL)\n            return NULL;\n    }\n    Py_INCREF(op->func_dict);\n    return op->func_dict;\n}\n\nstatic int\nfunc_set_dict(PyFunctionObject *op, PyObject *value)\n{\n    PyObject *tmp;\n\n    if (restricted())\n        return -1;\n    /* It is illegal to del f.func_dict */\n    if (value == NULL) {\n        PyErr_SetString(PyExc_TypeError,\n                        \"function's dictionary may not be deleted\");\n        return -1;\n    }\n    /* Can only set func_dict to a dictionary */\n    if (!PyDict_Check(value)) {\n        PyErr_SetString(PyExc_TypeError,\n                        \"setting function's dictionary to a non-dict\");\n        return -1;\n    }\n    tmp = op->func_dict;\n    Py_INCREF(value);\n    op->func_dict = value;\n    Py_XDECREF(tmp);\n    return 0;\n}\n\nstatic PyGetSetDef cache_getset[] = {\n  {\"__doc__\", (getter)cache_get_doc, NULL, NULL, NULL},\n  {\"__dict__\", (getter)func_get_dict, (setter)func_set_dict},\n  {NULL} /* Sentinel */\n};\n\n#else\n\nstatic PyGetSetDef cache_getset[] = {\n  {\"__doc__\", (getter)cache_get_doc, NULL, NULL, NULL},\n  {\"__dict__\", PyObject_GenericGetDict, PyObject_GenericSetDict},\n  {NULL} /* Sentinel */\n};\n\n#endif\n\n\n/* Bind a function to an object */\nstatic PyObject *\ncache_descr_get(PyObject *func, PyObject *obj, PyObject *type)\n{\n    if (obj == Py_None || obj == NULL)\n      INC_RETURN(func);\n\n#ifdef _PY2\n    return PyMethod_New(func, obj, type);\n#else\n    return PyMethod_New(func, obj);\n#endif\n}\n\n\nstatic void\ncache_dealloc(cacheobject *co)\n{\n  Py_CLEAR(co->fn);\n  Py_CLEAR(co->func_module);\n  Py_CLEAR(co->func_name);\n  Py_CLEAR(co->func_qualname);\n  Py_CLEAR(co->func_annotations);\n  Py_CLEAR(co->func_dict);\n  Py_CLEAR(co->cache_dict);\n  Py_CLEAR(co->ex_state);\n  Py_CLEAR(co->cinfo);\n  Py_CLEAR(co->root);\n  FREE_LOCK(co);\n  Py_TYPE(co)->tp_free(co);\n\n}\n\n\n/*\n * attempt to set hs->hashvalue to hash(hs->args)  Does not do alter any\n * reference counts.  Returns NULL on error.  If hs->hashvalue==-1 on return\n * then hs->args is Unhashable\n */\nstatic PyObject *\nset_hash_value(cacheobject *co, HashedArgs *hs)\n{\n  if ((hs->hashvalue = PyObject_Hash(hs->args)) == -1) {\n    // unhashable\n    if (co->err == FC_ERROR) {\n      return NULL;\n    }\n    // if error was something other than a TypeError, exit\n    if (!PyErr_GivenExceptionMatches(PyErr_Occurred(), PyExc_TypeError)) {\n      return NULL;\n    }\n    PyErr_Clear();\n\n    if (co->err == FC_WARNING) {\n      // try to issue warning\n      if( PyErr_WarnEx(PyExc_UserWarning,\n          \"Unhashable arguments cannot be cached\",1) < 0){\n        // warning becomes exception\n        PyErr_SetString(PyExc_TypeError,\n                        \"Cached function arguments must be hashable\");\n        return NULL;\n      }\n    }\n  }\n  // success!\n  return (PyObject *) hs;\n}\n\n// compute the hash of function args and kwargs\n// THREAD SAFTEY NOTES:\n// We access global data: co->ex_state and co->typed.\n// These data are defined at co creation time and are not\n// changed so we do not need to worry about thread safety here\nstatic PyObject *\nmake_key(cacheobject *co, PyObject *args, PyObject *kw)\n{\n  PyObject *item, *keys, *key;\n  Py_ssize_t ex_size = 0;\n  Py_ssize_t arg_size = 0;\n  Py_ssize_t kw_size = 0;\n  Py_ssize_t i, size, off;\n  HashedArgs *hs;\n  int is_list = 1;\n\n  // determine size of arguments and types\n  if (PyList_Check(co->ex_state))\n    ex_size = Py_SIZE(co->ex_state);\n  else if (PyDict_CheckExact(co->ex_state)){\n    is_list = 0;\n    ex_size = PyDict_Size(co->ex_state);\n  }\n  if (args && PyTuple_CheckExact(args))\n    arg_size = PyTuple_GET_SIZE(args);\n  if (kw && PyDict_CheckExact(kw))\n    kw_size = PyDict_Size(kw);\n\n  // allocate HashedArgs Object\n  if(!(hs = PyObject_New(HashedArgs, &HashedArgs_type)))\n    return NULL;\n\n  // total size\n  if (co->typed)\n    size = (2-is_list)*ex_size+2*arg_size+3*kw_size;\n  else\n    size = (2-is_list)*ex_size+arg_size+2*kw_size;\n  // initialize new tuple\n  if(!(hs->args = PyTuple_New(size))){\n    return NULL;\n  }\n  // incorporate extra state\n  if(is_list){\n    for(i = 0; i < ex_size; i++){\n      PyObject *tmp = PyList_GET_ITEM(co->ex_state, i);\n      PyTuple_SET_ITEM(hs->args, i, tmp);\n      Py_INCREF(tmp);\n    }\n  }\n  else if(ex_size > 0){\n    if(!(keys = PyDict_Keys(co->ex_state))){\n      Py_DECREF(hs);\n      return NULL;\n    }\n    if( PyList_Sort(keys) < 0){\n      Py_DECREF(keys);\n      Py_DECREF(hs);\n      return NULL;\n    }\n    for(i = 0; i < ex_size; i++){\n      key = PyList_GET_ITEM(keys, i);\n      Py_INCREF(key);\n      PyTuple_SET_ITEM(hs->args, 2*i, key);\n\n      if(!(item = PyDict_GetItem(co->ex_state, key))){\n        Py_DECREF(keys);\n        Py_DECREF(hs);\n        return NULL;\n      }\n      Py_INCREF(item);\n      PyTuple_SET_ITEM(hs->args, 2*i+1, item);\n    }\n    Py_DECREF(keys);\n  }\n  off = (2-is_list)*ex_size;\n\n  // incorporate arguments\n  for(i = 0; i < arg_size; i++){\n    PyObject *tmp = PyTuple_GET_ITEM(args, i);\n    PyTuple_SET_ITEM(hs->args, off+i, tmp);\n    Py_INCREF(tmp);\n    if(co->typed) {\n      off += 1;\n      tmp = (PyObject *)Py_TYPE(tmp);\n      Py_INCREF(tmp);\n      PyTuple_SET_ITEM(hs->args, off+i, tmp);\n    }\n  }\n  off += arg_size;\n\n  // incorporate keyword arguments\n  if(kw_size > 0){\n    if(!(keys = PyDict_Keys(kw))){\n      Py_DECREF(hs);\n      return NULL;\n    }\n    if( PyList_Sort(keys) < 0){\n      Py_DECREF(keys);\n      Py_DECREF(hs);\n      return NULL;\n    }\n    for(i = 0; i < kw_size; i++){\n      key = PyList_GET_ITEM(keys, i);\n      Py_INCREF(key);\n      PyTuple_SET_ITEM(hs->args, off+i, key);\n      if(!(item = PyDict_GetItem(kw, key))){\n        Py_DECREF(keys);\n        Py_DECREF(hs);\n        return NULL;\n      }\n      off += 1;\n      Py_INCREF(item);\n      PyTuple_SET_ITEM(hs->args, off+i, item);\n      if (co->typed){\n          off += 1;\n          item = (PyObject *)Py_TYPE(item);\n          Py_INCREF(item);\n          PyTuple_SET_ITEM(hs->args, off+i, item);\n      }\n    }\n    Py_DECREF(keys);\n  }\n  // check for an error we may have missed\n  if( PyErr_Occurred() ){\n    Py_DECREF(hs);\n    return NULL;\n  }\n  // set hash value\n  if( !set_hash_value(co, hs) ) {\n    Py_DECREF(hs);\n    return NULL;\n  }\n\n  return (PyObject *)hs;\n}\n\n\n/***********************************************************\n * All calls to the cached function go through cache_call\n * Handles: (1) Generation of key (via make_key)\n *          (2) Maintenance of circular doubly linked list\n *          (3) Actual updates to cache dictionary\n * THREAD SAFETY NOTES:\n * 1. The GIL may switch threads between all PyDict_Get/Set/DelItem\n *    If another thread were to call cache_clear while the dict was in\n *    an indetermined state, that could be very very bad.  Must lock all\n *    updates to cache_dict\n ***********************************************************/\nstatic PyObject *\ncache_call(cacheobject *co, PyObject *args, PyObject *kw)\n{\n  PyObject *key, *result, *link, *first;\n\n  /* no cache, just update stats and return */\n  if (co->maxsize == 0) {\n    co->misses++;\n    return PyObject_Call(co->fn, args, kw);\n  }\n\n  // generate a key from hashing the arguments\n  // THREAD SAFETY NOTES:\n  // Computing the hash will result in many potential calls to __hash__\n  // methods, allowing the GIL to switch threads.  Thus it is possible that\n  // two threads have called this function with the exact same arguments\n  // and are constructing keys\n  key = make_key(co, args, kw);\n  if (!key)\n    return NULL;\n\n  /* check for unhashable type */\n  if ( ((HashedArgs *)key)->hashvalue == -1){\n    // no locking neccessary here\n    Py_DECREF(key);\n    co->misses++;\n    return PyObject_Call(co->fn, args, kw);\n  }\n\n  /* For an unbounded cache, link is simply the result of the function call\n   * For an LRU cache, link is a pointer to a clist node */\n  if(ACQUIRE_LOCK(co) == -1){\n    Py_DECREF(key);\n    return NULL;\n  }\n  link = PyDict_GetItem(co->cache_dict, key);\n  if(PyErr_Occurred()){\n    RELEASE_LOCK(co);\n    Py_XDECREF(link);\n    Py_DECREF(key);\n    return NULL;\n  }\n  if(RELEASE_LOCK(co) == -1){\n    Py_XDECREF(link);\n    Py_DECREF(key);\n    return NULL;\n  }\n\n  if (!link){\n    result = PyObject_Call(co->fn, args, kw); // result refcount is one\n    if(PyErr_Occurred() || !result){\n      Py_XDECREF(result);\n      Py_DECREF(key);\n      return NULL;\n    }\n    /* Unbounded cache, no clist maintenance, no locks needed */\n    if (co->maxsize < 0){\n      if( PyDict_SetItem(co->cache_dict, key, result) == -1 ||\n          PyErr_Occurred()){\n        Py_DECREF(key);\n        Py_DECREF(result);\n        return NULL;\n      }\n      Py_DECREF(key);\n      return co->misses++, result;\n    }\n    /* Least Recently Used cache */\n    /* Need to reacquire the lock here and make sure that the key,result were\n     * not added to the cache while we were waiting */\n    if(ACQUIRE_LOCK(co) == -1){\n      Py_DECREF(key);\n      Py_DECREF(result);\n      return NULL;\n    }\n#ifdef WITH_THREAD\n    link = PyDict_GetItem(co->cache_dict, key);\n    if(PyErr_Occurred()){\n      RELEASE_LOCK(co);\n      Py_DECREF(key);\n      Py_DECREF(result);\n      Py_XDECREF(link);\n      return NULL;\n    }\n    if(link){\n      Py_DECREF(key);\n      if(RELEASE_LOCK(co) == -1){\n        Py_DECREF(result);\n        return NULL;\n      }\n      return co->hits++, result;\n    }\n#endif\n    /* if cache is full, repurpose the last link rather than\n     * passing it off to garbage collection.  */\n    if (((PyDictObject *)co->cache_dict)->ma_used == co->maxsize){\n      /* Note that the old key will be used to delete the link from the dictionary\n       * Be sure to INCREF old link so we don't lose it before\n       * we add it when the PyDict_DelItem occurs */\n      clist *last = co->root->prev;\n      PyObject *old_key = last->key;\n      PyObject *old_res = last->result;\n      // set new items\n      last->key = key;\n      last->result = result;\n      // bump to the front (get back the result we just set).\n      result = make_first(co->root, last);\n      // Increase ref count of repurposed link so we don't trigger GC\n      // save the first position since the global co->root->next may change\n      first = (PyObject *) co->root->next;\n      // Increases first->refcount to 2\n      if(PyDict_SetItem(co->cache_dict, key, first) == -1){\n        Py_DECREF(first);\n        Py_DECREF(first);\n        Py_DECREF(key);\n        Py_DECREF(old_key);\n        Py_DECREF(old_res);\n        Py_DECREF(result);\n        RELEASE_LOCK(co);\n        return NULL;\n      }\n      // handle deletions\n      if(PyDict_DelItem(co->cache_dict, old_key) == -1){\n        Py_DECREF(old_key);\n        Py_DECREF(old_res);\n        Py_DECREF(result);\n        RELEASE_LOCK(co);\n        return NULL;\n      }\n      // These would have been decrefed had we simply deleted the link\n      Py_DECREF(old_key);\n      Py_DECREF(old_res);\n      if(PyErr_Occurred()){\n        Py_DECREF(result);\n        RELEASE_LOCK(co);\n        return NULL;\n      }\n      if(RELEASE_LOCK(co) == -1){\n        Py_DECREF(result);\n        return NULL;\n      }\n      return co->misses++, result;\n    }\n    else {\n      if(insert_first(co->root, key, result) < 0) {\n        Py_DECREF(key);\n        Py_DECREF(result);\n        RELEASE_LOCK(co);\n        return NULL;\n      }\n      first = (PyObject *) co->root->next; // insert_first sets refcount to 1\n      // key and first count++\n      if(PyDict_SetItem(co->cache_dict, key, first) == -1 || PyErr_Occurred()){\n        Py_DECREF(first);\n        Py_DECREF(result);\n        RELEASE_LOCK(co);\n        return NULL;\n      }\n      Py_DECREF(first);\n      // Don't DECREF key here since we want both the dict and the node 'first'\n      // To be able to have a valid copy\n      co->misses++;\n      if(RELEASE_LOCK(co) == -1){\n        Py_DECREF(result);\n        return NULL;\n      }\n      return result;\n    }\n  } // link != NULL\n  else {\n    if( co->maxsize < 0){\n      Py_DECREF(key);\n      co->hits++;\n      INC_RETURN(link);\n    }\n    /* bump link to the front of the list and get result from link */\n    result = make_first(co->root, (clist *) link);\n    Py_DECREF(key);\n    co->hits++;\n    return result;\n  }\n}\n\n\nPyDoc_STRVAR(cacheclear__doc__,\n\"cache_clear(self)\\n\\\n\\n\\\nClear the cache and cache statistics.\");\nstatic PyObject *\ncache_clear(PyObject *self)\n{\n  cacheobject *co = (cacheobject *)self;\n  // delete dictionary - use a lock to keep dict in a fully determined state\n  if(ACQUIRE_LOCK(co) == -1)\n    return NULL;\n  PyDict_Clear(co->cache_dict);\n  co->hits = 0;\n  co->misses = 0;\n  if(RELEASE_LOCK(co) == -1)\n    return NULL;\n  Py_RETURN_NONE;\n}\n\n\nPyDoc_STRVAR(cacheinfo__doc__,\n\"cache_info(self)\\n\\\n\\n\\\nReport cache statistics.\");\nstatic PyObject *\ncache_info(PyObject *self)\n{\n  cacheobject * co = (cacheobject *) self;\n  if (co->maxsize >= 0)\n    return PyObject_CallFunction(co->cinfo,\"nnnn\",co->hits,\n                                 co->misses, co->maxsize,\n                                 ((PyDictObject *)co->cache_dict)->ma_used);\n  else\n    return PyObject_CallFunction(co->cinfo,\"nnOn\",co->hits,\n                                 co->misses, Py_None,\n                                 ((PyDictObject *)co->cache_dict)->ma_used);\n}\n\n\nstatic PyMethodDef cache_methods[] = {\n  {\"cache_clear\", (PyCFunction) cache_clear, METH_NOARGS,\n   cacheclear__doc__},\n  {\"cache_info\", (PyCFunction) cache_info, METH_NOARGS,\n   cacheinfo__doc__},\n  {NULL, NULL} /* sentinel */\n};\n\n\nPyDoc_STRVAR(fn_doc,\n             \"Cached function.\");\n\n\nstatic PyTypeObject cache_type = {\n    PyVarObject_HEAD_INIT(NULL, 0)\n    \"fastcache.clru_cache\",                /* tp_name */\n    sizeof(cacheobject),                       /* tp_basicsize */\n    0,                                  /* tp_itemsize */\n    /* methods */\n    (destructor)cache_dealloc,            /* tp_dealloc */\n    0,                                  /* tp_print */\n    0,                                  /* tp_getattr */\n    0,                                  /* tp_setattr */\n    0,                                  /* tp_reserved */\n    0,                                  /* tp_repr */\n    0,                                  /* tp_as_number */\n    0,                                  /* tp_as_sequence */\n    0,                                  /* tp_as_mapping */\n    0,                                  /* tp_hash */\n    (ternaryfunc)cache_call,              /* tp_call */\n    0,                                  /* tp_str */\n    0,                                  /* tp_getattro */\n    0,                                  /* tp_setattro */\n    0,                                  /* tp_as_buffer */\n    Py_TPFLAGS_DEFAULT ,                /* tp_flags */\n    fn_doc,                                  /* tp_doc */\n    0,                                  /* tp_traverse */\n    0,                                  /* tp_clear */\n    0,                                  /* tp_richcompare */\n    0,                                  /* tp_weaklistoffset */\n    0,                                  /* tp_iter */\n    0,                                  /* tp_iternext */\n    cache_methods,                      /* tp_methods */\n    cache_memberlist,                   /* tp_members */\n    cache_getset,                       /* tp_getset */\n    0,                                  /* tp_base */\n    0,                                  /* tp_dict */\n    cache_descr_get,                    /* tp_descr_get */\n    0,                                  /* tp_descr_set */\n    OFF(func_dict),                     /* tp_dictoffset */\n    0,                                  /* tp_init */\n    0,                                  /* tp_alloc */\n    0,                                  /* tp_new */\n    0,                                  /* tp_free */\n};\n\n\n/* lruobject -\n * the callable object returned by lrucache(all, my, cache, args)\n * [lrucache is known as clru_cache in python land]\n * records arguments to clru_cache and passes them along to the\n * cacheobject created when lruobject is called with a function\n * as an argument */\ntypedef struct {\n  PyObject_HEAD\n  Py_ssize_t maxsize;\n  PyObject *state;\n  int typed;\n  enum unhashable err;\n} lruobject;\n\n\nstatic void lru_dealloc(lruobject *lru)\n{\n  Py_CLEAR(lru->state);\n  Py_TYPE(lru)->tp_free(lru);\n}\n\n\nstatic PyObject *\nget_func_attr(PyObject *fo, const char *name)\n{\n  if( !PyObject_HasAttrString(fo,name))\n    Py_RETURN_NONE;\n  else{\n    PyObject *attr = PyObject_GetAttrString(fo, name);\n    if (attr == NULL)\n      return NULL;\n    return attr;\n  }\n}\n\n\n/* takes a function as an argument and returns a cacheobject */\nstatic PyObject *\nlru_call(lruobject *lru, PyObject *args, PyObject *kw)\n{\n  PyObject *fo, *mod, *nt;\n  cacheobject *co;\n\n  if(! PyArg_ParseTuple(args, \"O\", &fo))\n    return NULL;\n\n  if(! PyCallable_Check(fo)){\n    PyErr_SetString(PyExc_TypeError, \"Argument must be callable.\");\n    return NULL;\n  }\n  co = PyObject_New(cacheobject, &cache_type);\n  if (co == NULL)\n    return NULL;\n\n#ifdef WITH_THREAD\n  if ((co->lock = PyThread_allocate_lock()) == NULL){\n    Py_DECREF(co);\n    return NULL;\n  }\n  // We need to initialize the rlock count and owner here\n  co->rlock_count = 0;\n  co->rlock_owner = 0;\n#endif\n  if ((co->cache_dict = PyDict_New()) == NULL){\n    Py_DECREF(co);\n    return NULL;\n  }\n\n  // initialize circular doubly linked list\n  co->root = PyObject_New(clist, &clist_type);\n  if(co->root == NULL){\n    Py_DECREF(co);\n    return NULL;\n  }\n\n  // get namedtuple for cache_info()\n  mod = PyImport_ImportModule(\"collections\");\n  if (mod == NULL){\n    Py_DECREF(co);\n    return NULL;\n  }\n  nt = PyObject_GetAttrString(mod, \"namedtuple\");\n  if (nt == NULL){\n    Py_DECREF(co);\n    return NULL;\n  }\n  co->cinfo = PyObject_CallFunction(nt,\"ss\",\"CacheInfo\",\n                                    \"hits misses maxsize currsize\");\n  if (co->cinfo == NULL){\n    Py_DECREF(co);\n    return NULL;\n  }\n\n  co->func_dict = get_func_attr(fo, \"__dict__\");\n\n  co->fn = fo; // __wrapped__\n  Py_INCREF(co->fn);\n\n  co->func_module = get_func_attr(fo, \"__module__\");\n  co->func_annotations = get_func_attr(fo, \"__annotations__\");\n  co->func_name = get_func_attr(fo, \"__name__\");\n  co->func_qualname = get_func_attr(fo, \"__qualname__\");\n\n  co->ex_state = lru->state;\n  Py_INCREF(co->ex_state);\n  co->maxsize = lru->maxsize;\n  co->hits = 0;\n  co->misses = 0;\n  co->typed = lru->typed;\n  co->err = lru->err;\n  // start with self-referencing root node\n  co->root->prev = co->root;\n  co->root->next = co->root;\n  co->root->key = Py_None;\n  co->root->result = Py_None;\n  Py_INCREF(co->root->key);\n  Py_INCREF(co->root->result);\n\n  return (PyObject *)co;\n}\n\n\nstatic PyTypeObject lru_type = {\n    PyVarObject_HEAD_INIT(NULL, 0)\n    \"fastcache.lru\",                /* tp_name */\n    sizeof(lruobject),                       /* tp_basicsize */\n    0,                                  /* tp_itemsize */\n    /* methods */\n    (destructor)lru_dealloc,            /* tp_dealloc */\n    0,                                  /* tp_print */\n    0,                                  /* tp_getattr */\n    0,                                  /* tp_setattr */\n    0,                                  /* tp_reserved */\n    0,                                  /* tp_repr */\n    0,                                  /* tp_as_number */\n    0,                                  /* tp_as_sequence */\n    0,                                  /* tp_as_mapping */\n    0,                                  /* tp_hash */\n    (ternaryfunc)lru_call,              /* tp_call */\n    0,                                  /* tp_str */\n    0,                                  /* tp_getattro */\n    0,                                  /* tp_setattro */\n    0,                                  /* tp_as_buffer */\n    Py_TPFLAGS_DEFAULT ,                /* tp_flags */\n};\n\n\n/* helper function for processing 'unhashable' */\nenum unhashable\nprocess_uh(PyObject *arg, PyObject *(*f)(const char *))\n{\n  PyObject *uh[3] = {f(\"error\"), f(\"warning\"), f(\"ignore\")};\n  int i, j;\n  if (arg != NULL){\n\n    enum unhashable vals[3] = {FC_ERROR, FC_WARNING, FC_IGNORE};\n\n    for(i=0; i<3; i++){\n      int k = PyObject_RichCompareBool(arg, uh[i], Py_EQ);\n      if (k < 0){\n        for(j=0; j<3; j++)\n          Py_DECREF(uh[j]);\n        return FC_FAIL;\n      }\n      if (k){\n        /* DECREF objects and return value */\n        for(j=0; j<3; j++)\n          Py_DECREF(uh[j]);\n        return vals[i];\n      }\n    }\n  }\n  for(j=0; j<3; j++)\n    Py_DECREF(uh[j]);\n  PyErr_SetString(PyExc_TypeError,\n               \"Argument <unhashable> must be 'error', 'warning', or 'ignore'\");\n  return FC_FAIL;\n}\n\n\n/* LRU cache decorator */\nPyDoc_STRVAR(lrucache__doc__,\n\"clru_cache(maxsize=128, typed=False, state=None, unhashable='error')\\n\\n\"\n\"Least-recently-used cache decorator.\\n\\n\"\n\"If *maxsize* is set to None, the LRU features are disabled and the\\n\"\n\"cache can grow without bound.\\n\\n\"\n\"If *typed* is True, arguments of different types will be cached\\n\"\n\"separately.  For example, f(3.0) and f(3) will be treated as distinct\\n\"\n\"calls with distinct results.\\n\\n\"\n\"If *state* is a list or dict, the items will be incorporated into the\\n\"\n\"argument hash.\\n\\n\"\n\"The result of calling the cached function with unhashable (mutable)\\n\"\n\"arguments depends on the value of *unhashable*:\\n\\n\"\n\"    If *unhashable* is 'error', a TypeError will be raised.\\n\\n\"\n\"    If *unhashable* is 'warning', a UserWarning will be raised, and\\n\"\n\"    the wrapped function will be called with the supplied arguments.\\n\"\n\"    A miss will be recorded in the cache statistics.\\n\\n\"\n\"    If *unhashable* is 'ignore', the wrapped function will be called\\n\"\n\"    with the supplied arguments. A miss will will be recorded in\\n\"\n\"    the cache statistics.\\n\\n\"\n\"View the cache statistics named tuple (hits, misses, maxsize, currsize)\\n\"\n\"with f.cache_info().  Clear the cache and statistics with\\n\"\n\"f.cache_clear(). Access the underlying function with f.__wrapped__.\\n\\n\"\n\"See:  http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used\");\n\nstatic PyObject *\nlrucache(PyObject *self, PyObject *args, PyObject *kwargs)\n{\n  PyObject *state = Py_None;\n  int typed = 0;\n  PyObject *omaxsize = Py_False;\n  PyObject *oerr = Py_None;\n  Py_ssize_t maxsize = 128;\n  static char *kwlist[] = {\"maxsize\", \"typed\", \"state\", \"unhashable\", NULL};\n  lruobject *lru;\n  enum unhashable err;\n#if defined(_PY2) || defined (_PY32)\n  PyObject *otyped = Py_False;\n  if(! PyArg_ParseTupleAndKeywords(args, kwargs, \"|OOOO:lrucache\",\n                                   kwlist,\n                                   &omaxsize, &otyped, &state, &oerr))\n    return NULL;\n  typed = PyObject_IsTrue(otyped);\n  if (typed < -1)\n    return NULL;\n#else\n  if(! PyArg_ParseTupleAndKeywords(args, kwargs, \"|OpOO:lrucache\",\n                                   kwlist,\n                                   &omaxsize, &typed, &state, &oerr))\n    return NULL;\n#endif\n  if (omaxsize != Py_False){\n    if (omaxsize == Py_None)\n      maxsize = -1;\n#ifdef _PY2\n    else if (PyInt_Check(omaxsize)){\n      maxsize = PyInt_AsSsize_t(omaxsize);\n      if (maxsize < 0)\n        maxsize = -1;\n    }\n#endif\n    else {\n      if( ! PyLong_Check(omaxsize)){\n        PyErr_SetString(PyExc_TypeError,\n                        \"Argument <maxsize> must be an int.\");\n        return NULL;\n      }\n      maxsize = PyLong_AsSsize_t(omaxsize);\n      if (maxsize < 0)\n        maxsize = -1;\n    }\n  }\n\n  // ensure state is a list or dict\n  if (state != Py_None && !(PyList_Check(state) || PyDict_CheckExact(state))){\n    PyErr_SetString(PyExc_TypeError,\n                    \"Argument <state> must be a list or dict.\");\n    return NULL;\n  }\n\n  // check unhashable\n  if (oerr == Py_None)\n    err = FC_ERROR;\n  else{\n#ifdef _PY2\n    if(PyString_Check(oerr))\n      err = process_uh(oerr, PyString_FromString);\n    else\n#endif\n    if(PyUnicode_Check(oerr))\n      err = process_uh(oerr, PyUnicode_FromString);\n    else\n      err = process_uh(NULL, NULL); // set error properly\n  }\n  if (err == FC_FAIL)\n    return NULL;\n\n  lru = PyObject_New(lruobject, &lru_type);\n  if (lru == NULL)\n    return NULL;\n\n  lru->maxsize = maxsize;\n  lru->state = state;\n  lru->typed = typed;\n  lru->err = err;\n  Py_INCREF(lru->state);\n\n  return (PyObject *) lru;\n}\n\n\nstatic PyMethodDef lrucachemethods[] = {\n  {\"clru_cache\", (PyCFunction) lrucache, METH_VARARGS | METH_KEYWORDS,\n   lrucache__doc__},\n  {NULL, NULL} /* sentinel */\n};\n\n\n#ifndef _PY2\nstatic PyModuleDef lrucachemodule = {\n  PyModuleDef_HEAD_INIT,\n  \"_lrucache\",\n  \"Least Recently Used cache\",\n  -1,\n  lrucachemethods,\n  NULL, NULL, NULL, NULL\n};\n#endif\n\n\n#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */\n#define PyMODINIT_FUNC void\n#endif\nPyMODINIT_FUNC\n#ifdef _PY2\ninit_lrucache(void)\n{\n#define _PYINIT_ERROR_RET return\n#else\nPyInit__lrucache(void)\n{\n  PyObject *m;\n#define _PYINIT_ERROR_RET return NULL\n#endif\n\n  lru_type.tp_new = PyType_GenericNew;\n  if (PyType_Ready(&lru_type) < 0)\n    _PYINIT_ERROR_RET;\n\n  cache_type.tp_new = PyType_GenericNew;\n  if (PyType_Ready(&cache_type) < 0)\n    _PYINIT_ERROR_RET;\n\n  HashedArgs_type.tp_new = PyType_GenericNew;\n  if (PyType_Ready(&HashedArgs_type) < 0)\n    _PYINIT_ERROR_RET;\n\n  clist_type.tp_new = PyType_GenericNew;\n  if (PyType_Ready(&clist_type) < 0)\n    _PYINIT_ERROR_RET;\n\n#ifdef _PY2\n  Py_InitModule3(\"_lrucache\", lrucachemethods,\n                 \"Least recently used cache.\");\n#else\n  m = PyModule_Create(&lrucachemodule);\n  if (m == NULL)\n    return NULL;\n#endif\n\n  Py_INCREF(&lru_type);\n  Py_INCREF(&cache_type);\n  Py_INCREF(&HashedArgs_type);\n  Py_INCREF(&clist_type);\n\n#ifndef _PY2\n  return m;\n#endif\n}\n\n#ifdef __cplusplus\n}\n#endif\n"
  }
]