[
  {
    "path": ".github/workflows/build-and-deploy.yml",
    "content": "name: Build and deploy\n\non: [push, pull_request]\n\njobs:\n  build_wheels:\n    name: Build wheels on ${{ matrix.os }}\n    runs-on: ${{ matrix.runs-on }}\n    strategy:\n      matrix:\n        os: [linux, windows, macos, ios, android]\n        include:\n          - os: linux\n            runs-on: ubuntu-22.04\n          - os: windows\n            runs-on: windows-2022\n          - os: macos\n            runs-on: macos-latest\n          - os: ios\n            runs-on: macos-14\n          - os: android\n            runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up QEMU\n        if: runner.os == 'Linux'\n        uses: docker/setup-qemu-action@v3\n        with:\n          platforms: all\n      - name: Enable KVM group perms\n        if: matrix.os == 'android'\n        run: |\n            echo 'KERNEL==\"kvm\", GROUP=\"kvm\", MODE=\"0666\", OPTIONS+=\"static_node=kvm\"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules\n            sudo udevadm control --reload-rules\n            sudo udevadm trigger --name-match=kvm\n      # Used to host cibuildwheel\n      - name: Build wheels\n        uses: pypa/cibuildwheel@v3.1.4\n        env:\n          # configure cibuildwheel to build native archs ('auto'), and some emulated ones\n          CIBW_PLATFORM: ${{ matrix.os }}\n          CIBW_ARCHS_LINUX: auto aarch64 ppc64le\n          CIBW_ARCHS_MACOS: x86_64 arm64 universal2\n          CIBW_ARCHS_IOS: arm64_iphoneos arm64_iphonesimulator x86_64_iphonesimulator\n          CIBW_ARCHS_ANDROID: x86_64 arm64_v8a\n          # cannot test the arm64 part for CPython 3.8 universal2/arm64, see https://github.com/pypa/cibuildwheel/pull/1169\n          CIBW_TEST_SKIP: cp38-macosx_arm64\n          # pypy will need to be enabled for cibuildwheel 3\n          CIBW_ENABLE: pypy\n      - uses: actions/upload-artifact@v4\n        with:\n          name: artifacts-${{ matrix.os }}\n          path: ./wheelhouse/*.whl\n\n  build_sdist:\n    name: Build source distribution\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Build sdist\n        run: pipx run build --sdist\n      - uses: actions/upload-artifact@v4\n        with:\n          name: artifacts-sdist\n          path: dist/*.tar.gz\n\n  pypi_upload:\n    name: Publish to PyPI\n    needs: [build_wheels, build_sdist]\n    runs-on: ubuntu-latest\n    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')\n    steps:\n    - uses: actions/setup-python@v5\n    - uses: actions/download-artifact@v4\n      with:\n        pattern: artifacts-*\n        path: dist\n        merge-multiple: true\n    - name: Publish package\n      uses: pypa/gh-action-pypi-publish@release/v1\n      with:\n        verbose: true\n        user: __token__\n        password: ${{ secrets.pypi_password }}\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\n\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-22.04\n    strategy:\n      fail-fast: false\n      matrix:\n        python:\n          [\n            \"3.9\",\n            \"3.10\",\n            \"3.11\",\n            \"3.12\",\n            \"3.13\",\n            \"3.14\",\n            \"pypy-3.9\",\n            \"pypy-3.10\",\n            \"pypy-3.11\",\n          ]\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with:\n          python-version: ${{ matrix.python }}\n          allow-prereleases: true\n      - name: Update Python tools\n        run: |\n          python -m pip install -U pip\n          python -m pip install -U setuptools\n      - name: Install lru-dict with test extras\n        run: |\n          python -m pip install .[test]\n      - name: Run tests\n        run: |\n          python -m pytest\n\n  install:\n    runs-on: ubuntu-22.04\n    strategy:\n      fail-fast: false\n      matrix:\n        python:\n          [\n            \"3.9\",\n            \"3.10\",\n            \"3.11\",\n            \"3.12\",\n            \"3.13\",\n            \"3.14\",\n            \"pypy-3.9\",\n            \"pypy-3.10\",\n            \"pypy-3.11\",\n          ]\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with:\n          python-version: ${{ matrix.python }}\n          allow-prereleases: true\n      - name: Update Python tools\n        run: |\n          python -m pip install -U pip\n          python -m pip install -U setuptools\n      - name: Install lru-dict\n        run: |\n          python -m pip install .\n      - name: Verify install\n        run: |\n          python -c \"from lru import LRU\"\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nwheelhouse/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pdm config\n.pdm-python\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) Amit Dev R\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENSE README.rst MANIFEST MANIFEST.in\ngraft src\nglobal-exclude *.pyc\nglobal-exclude *.cache"
  },
  {
    "path": "README.rst",
    "content": ".. image:: https://github.com/amitdev/lru-dict/actions/workflows/tests.yml/badge.svg\n    :target: https://github.com/amitdev/lru-dict/actions/workflows/tests.yml\n\n.. image:: https://github.com/amitdev/lru-dict/actions/workflows/build-and-deploy.yml/badge.svg\n    :target: https://github.com/amitdev/lru-dict/actions/workflows/build-and-deploy.yml\n\n.. image:: https://img.shields.io/badge/maintainers-wanted-red.svg\n\n**Note**: This project is stable and production ready, but looking for a lead maintainer. Preferably someone who uses this library and made contributions to it.\n\nLRU Dict\n========\n\nA fixed size dict like container which evicts Least Recently Used (LRU) items\nonce size limit is exceeded. There are many python implementations available\nwhich does similar things. This is a fast and efficient C implementation.\nLRU maximum capacity can be modified at run-time.\nIf you are looking for pure python version, look `else where <http://www.google.com/search?q=python+lru+dict>`_.\n\nUsage\n=====\n\nThis can be used to build a LRU cache. Usage is almost like a dict.\n\n.. code:: python3\n\n  from lru import LRU\n  l = LRU(5)         # Create an LRU container that can hold 5 items\n\n  print l.peek_first_item(), l.peek_last_item()  #return the MRU key and LRU key\n  # Would print None None\n\n  for i in range(5):\n     l[i] = str(i)\n  print l.items()    # Prints items in MRU order\n  # Would print [(4, '4'), (3, '3'), (2, '2'), (1, '1'), (0, '0')]\n\n  print l.peek_first_item(), l.peek_last_item()  #return the MRU key and LRU key\n  # Would print (4, '4') (0, '0')\n\n  l[5] = '5'         # Inserting one more item should evict the old item\n  print l.items()\n  # Would print [(5, '5'), (4, '4'), (3, '3'), (2, '2'), (1, '1')]\n\n  l[3]               # Accessing an item would make it MRU\n  print l.items()\n  # Would print [(3, '3'), (5, '5'), (4, '4'), (2, '2'), (1, '1')]\n  # Now 3 is in front\n\n  l.keys()           # Can get keys alone in MRU order\n  # Would print [3, 5, 4, 2, 1]\n\n  del l[4]           # Delete an item\n  print l.items()\n  # Would print [(3, '3'), (5, '5'), (2, '2'), (1, '1')]\n\n  print l.get_size()\n  # Would print 5\n\n  l.set_size(3)\n  print l.items()\n  # Would print [(3, '3'), (5, '5'), (2, '2')]\n  print l.get_size()\n  # Would print 3\n  print l.has_key(5)\n  # Would print True\n  print 2 in l\n  # Would print True\n\n  l.get_stats()\n  # Would print (1, 0)\n\n\n  l.update(5='0')           # Update an item\n  print l.items()\n  # Would print [(5, '0'), (3, '3'), (2, '2')]\n\n  l.clear()\n  print l.items()\n  # Would print []\n\n  def evicted(key, value):\n    print \"removing: %s, %s\" % (key, value)\n\n  l = LRU(1, callback=evicted)\n\n  l[1] = '1'\n  l[2] = '2'\n  # callback would print removing: 1, 1\n\n  l[2] = '3'\n  # doesn't call the evicted callback\n\n  print l.items()\n  # would print [(2, '3')]\n  \n  del l[2]\n  # doesn't call the evicted callback\n\n  print l.items()\n  # would print []\n\nInstall\n=======\n\n::\n\n  pip install lru-dict\n\nor\n\n::\n\n  easy_install lru_dict\n\n\nWhen to use this\n================\n\nLike mentioned above there are many python implementations of an LRU. Use this\nif you need a faster and memory efficient alternative. It is implemented with a\ndict and associated linked list to keep track of LRU order. See code for a more\ndetailed explanation. To see an indicative comparison with a pure python module,\nconsider a `benchmark <https://gist.github.com/amitdev/5773979>`_ against\n`pylru <https://pypi.python.org/pypi/pylru/>`_ (just chosen at random, it should\nbe similar with other python implementations as well).\n\n::\n\n  $ python bench.py pylru.lrucache\n  Time : 3.31 s, Memory : 453672 Kb\n  $ python bench.py lru.LRU\n  Time : 0.23 s, Memory : 124328 Kb\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools==80.9.0\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"lru-dict\"\nversion = \"1.4.1\"\ndescription = \"An Dict like LRU container.\"\nauthors = [\n    {name = \"Amit Dev\"},\n]\ndependencies = []\nrequires-python = \">=3.9\"\nreadme = \"README.rst\"\nlicense = \"MIT\"\nlicense-files = [\"LICENSE\"]\nkeywords = [\"lru\", \"dict\"]\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Intended Audience :: Developers\",\n    \"Operating System :: OS Independent\",\n    \"Operating System :: POSIX\",\n    \"Programming Language :: C\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.9\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Programming Language :: Python :: 3.14\",\n    \"Programming Language :: Python :: Implementation :: CPython\",\n    \"Topic :: Software Development :: Libraries :: Python Modules\",\n]\n[project.urls]\nHomepage = \"https://github.com/amitdev/lru-dict\"\n[project.optional-dependencies]\ntest = [\n    \"pytest\",\n]\n\n[tool.cibuildwheel]\ntest-extras = [\"test\"]\ntest-sources = [\"test\"]\ntest-command = \"python -m pytest -v\"\nxbuild-tools = []\n"
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup, Extension\n\nextensions = [\n    Extension(\"lru._lru\", [\"src/lru/_lru.c\"]),\n]\n\nargs = {\n    \"include_package_data\": True,\n    \"exclude_package_data\": {\"\": [\"*.c\"]},\n}\n\nsetup(ext_modules=extensions, **args)\n"
  },
  {
    "path": "src/lru/__init__.py",
    "content": "from ._lru import LRU as LRU  # noqa: F401\n\n__all__ = [\"LRU\"]\n"
  },
  {
    "path": "src/lru/__init__.pyi",
    "content": "from typing import (\n    Any,\n    Callable,\n    Generic,\n    Hashable,\n    Iterable,\n    TypeVar,\n    overload,\n    Protocol\n)\n\n_KT = TypeVar(\"_KT\", bound=Hashable)\n_VT = TypeVar(\"_VT\")\n_VT_co = TypeVar(\"_VT_co\", covariant=True)\n_T = TypeVar(\"_T\")\n\n\nclass __SupportsKeysAndGetItem(Protocol[_KT, _VT_co]):\n    def keys(self) -> Iterable[_KT]: ...\n    def __getitem__(self, __key: _KT) -> _VT_co: ...\n\n\nclass LRU(Generic[_KT, _VT]):\n    @overload\n    def __init__(self, size: int) -> None: ...\n    @overload\n    def __init__(self, size: int, callback: Callable[[_KT, _VT], Any]) -> None: ...\n    def clear(self) -> None: ...\n    @overload\n    def get(self, key: _KT) -> _VT | None: ...\n    @overload\n    def get(self, key: _KT, instead: _VT | _T) -> _VT | _T: ...\n    def get_size(self) -> int: ...\n    def has_key(self, key: _KT) -> bool: ...\n    def keys(self) -> list[_KT]: ...\n    def values(self) -> list[_VT]: ...\n    def items(self) -> list[tuple[_KT, _VT]]: ...\n    def peek_first_item(self) -> tuple[_KT, _VT] | None: ...\n    def peek_last_item(self) -> tuple[_KT, _VT] | None: ...\n    @overload\n    def pop(self, key: _KT) -> _VT | None: ...\n    @overload\n    def pop(self, key: _KT, default: _VT | _T) -> _VT | _T: ...\n    def popitem(self, least_recent: bool = ...) -> tuple[_KT, _VT]: ...\n    @overload\n    def setdefault(self: LRU[_KT, _T | None], key: _KT) -> _T | None: ...\n    @overload\n    def setdefault(self, key: _KT, default: _VT) -> _VT: ...\n    def set_callback(self, callback: Callable[[_KT, _VT], Any] | None) -> None: ...\n    def set_size(self, size: int) -> None: ...\n    @overload\n    def update(self, __m: __SupportsKeysAndGetItem[_KT, _VT], **kwargs: _VT) -> None: ...\n    @overload\n    def update(self, __m: Iterable[tuple[_KT, _VT]], **kwargs: _VT) -> None: ...\n    @overload\n    def update(self, **kwargs: _VT) -> None: ...\n    def get_stats(self) -> tuple[int, int]: ...\n    def __contains__(self, __o: Any) -> bool: ...\n    def __delitem__(self, key: _KT) -> None: ...\n    def __getitem__(self, item: _KT) -> _VT: ...\n    def __len__(self) -> int: ...\n    def __repr__(self) -> str: ...\n    def __setitem__(self, key: _KT, value: _VT) -> None: ...\n"
  },
  {
    "path": "src/lru/_lru.c",
    "content": "#include <Python.h>\n\n/*\n * This is a simple implementation of LRU Dict that uses a Python dict and an associated doubly linked\n * list to keep track of recently inserted/accessed items.\n *\n * Dict will store: key -> Node mapping, where Node is a linked list node.\n * The Node itself will contain the value as well as the key.\n *\n * For eg:\n *\n * >>> l = LRU(2)\n * >>> l[0] = 'foo'\n * >>> l[1] = 'bar'\n *\n * can be visualised as:\n *\n *             ---+--(hash(0)--+--hash(1)--+\n * self->dict  ...|            |           |\n *             ---+-----|------+---------|-+\n *                      |                |\n *                +-----v------+   +-----v------+\n * self->first--->|<'foo'>, <0>|-->|<'bar'>, <1>|<---self->last\n *             +--|            |<--|            |--+\n *             |  +------------+   +------------+  |\n *             v                                   v\n *           NULL                                 NULL\n *\n *  The invariant is to maintain the list to reflect the LRU order of items in the dict.\n *  self->first will point to the MRU item and self-last to LRU item. Size of list will not\n *  grow beyond size of LRU dict.\n *\n */\n\n#ifndef Py_TYPE\n #define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)\n#endif\n\n#define GET_NODE(d, key) (Node *) Py_TYPE(d)->tp_as_mapping->mp_subscript((d), (key))\n#define PUT_NODE(d, key, node) Py_TYPE(d)->tp_as_mapping->mp_ass_subscript((d), (key), ((PyObject *)node))\n\n/* If someone figures out how to enable debug builds with setuptools, you can delete this */\n#if 0\n#undef assert\n#define str(s) #s\n#define assert(v) \\\n  do {                                                                                \\\n    if (!(v)) {                                                                       \\\n      fprintf(stderr, \"Assertion failed: %s on %s:%d\\n\",                              \\\n              str(v), __FILE__, __LINE__);                                            \\\n      fflush(stderr);                                                                 \\\n      abort();                                                                        \\\n    }                                                                                 \\\n  } while(0)\n#endif\n\ntypedef struct _Node {\n    PyObject_HEAD\n    PyObject * value;\n    PyObject * key;\n    struct _Node * prev;\n    struct _Node * next;\n} Node;\n\nstatic void\nnode_dealloc(Node* self)\n{\n    Py_DECREF(self->key);\n    Py_DECREF(self->value);\n    assert(self->prev == NULL);\n    assert(self->next == NULL);\n    Py_TYPE(self)->tp_free((PyObject *)self);\n}\n\nstatic PyObject*\nnode_repr(Node* self)\n{\n    return PyObject_Repr(self->value);\n}\n\nstatic PyTypeObject NodeType = {\n    PyVarObject_HEAD_INIT(NULL, 0)\n    \"_lru.Node\",              /* tp_name */\n    sizeof(Node),            /* tp_basicsize */\n    0,                       /* tp_itemsize */\n    (destructor)node_dealloc,/* tp_dealloc */\n    0,                       /* tp_print */\n    0,                       /* tp_getattr */\n    0,                       /* tp_setattr */\n    0,                       /* tp_compare */\n    (reprfunc)node_repr,     /* 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    \"Linked List Node\",      /* 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    0,                       /* tp_methods */\n    0,                       /* tp_members */\n    0,                       /* tp_getset */\n    0,                       /* tp_base */\n    0,                       /* tp_dict */\n    0,                       /* tp_descr_get */\n    0,                       /* tp_descr_set */\n    0,                       /* tp_dictoffset */\n    0,                       /* tp_init */\n    0,                       /* tp_alloc */\n    0,                       /* tp_new */\n};\n\ntypedef struct {\n    PyObject_HEAD\n    PyObject * dict;\n    Node * first;\n    Node * last;\n    Py_ssize_t size;\n    Py_ssize_t hits;\n    Py_ssize_t misses;\n    PyObject *callback;\n} LRU;\n\n\nstatic PyObject *\nset_callback(LRU *self, PyObject *args)\n{\n    PyObject *result = NULL;\n    PyObject *temp;\n\n    if (PyArg_ParseTuple(args, \"O:set_callback\", &temp)) {\n        if (temp == Py_None) {\n            Py_XDECREF(self->callback);\n            self->callback = NULL;\n        } else if (!PyCallable_Check(temp)) {\n            PyErr_SetString(PyExc_TypeError, \"parameter must be callable\");\n            return NULL;\n        } else {\n            Py_XINCREF(temp);         /* Add a reference to new callback */\n            Py_XDECREF(self->callback);  /* Dispose of previous callback */\n            self->callback = temp;       /* Remember new callback */\n        }\n        Py_RETURN_NONE;\n    }\n    return result;\n}\n\nstatic void\nlru_remove_node(LRU *self, Node* node)\n{\n    if (self->first == node) {\n        self->first = node->next;\n    }\n    if (self->last == node) {\n        self->last = node->prev;\n    }\n    if (node->prev) {\n        node->prev->next = node->next;\n    }\n    if (node->next) {\n        node->next->prev = node->prev;\n    }\n    node->next = node->prev = NULL;\n}\n\nstatic void\nlru_add_node_at_head(LRU *self, Node* node)\n{\n    node->prev = NULL;\n    if (!self->first) {\n        self->first = self->last = node;\n        node->next = NULL;\n    } else {\n        node->next = self->first;\n        if (node->next) {\n            node->next->prev = node;\n        }\n        self->first = node;\n    }\n}\n\nstatic void\nlru_delete_last(LRU *self)\n{\n    PyObject *arglist;\n    PyObject *result;\n    Node* n = self->last;\n\n    if (!self->last)\n        return;\n\n    if (self->callback) {\n        arglist = Py_BuildValue(\"OO\", n->key, n->value);\n        lru_remove_node(self, n);\n        result = PyObject_CallObject(self->callback, arglist);\n        Py_XDECREF(result);\n        Py_DECREF(arglist);\n    }\n    else {\n        lru_remove_node(self, n);\n    }\n    PUT_NODE(self->dict, n->key, NULL);\n}\n\nstatic inline Py_ssize_t\nlru_length(LRU *self)\n{\n    return PyDict_Size(self->dict);\n}\n\nstatic PyObject *\nLRU_contains_key(LRU *self, PyObject *key)\n{\n    if (PyDict_Contains(self->dict, key))\n        Py_RETURN_TRUE;\n    Py_RETURN_FALSE;\n}\n\nstatic PyObject *\nLRU_contains(LRU *self, PyObject *args)\n{\n    PyObject *key;\n    if (!PyArg_ParseTuple(args, \"O\", &key))\n        return NULL;\n    return LRU_contains_key(self, key);\n}\n\nstatic int\nLRU_seq_contains(LRU *self, PyObject *key)\n{\n    return PyDict_Contains(self->dict, key);\n}\n\nstatic PyObject *\nlru_subscript(LRU *self, register PyObject *key)\n{\n    Node *node = GET_NODE(self->dict, key);\n    if (!node) {\n        self->misses++;\n        return NULL;\n    }\n\n    assert(PyObject_TypeCheck(node, &NodeType));\n\n    /* We don't need to move the node when it's already self->first. */\n    if (node != self->first) {\n        lru_remove_node(self, node);\n        lru_add_node_at_head(self, node);\n    }\n\n    self->hits++;\n    Py_INCREF(node->value);\n    Py_DECREF(node);\n    return node->value;\n}\n\nstatic PyObject *\nLRU_get(LRU *self, PyObject *args, PyObject *keywds)\n{\n    PyObject *key;\n    PyObject *default_obj = NULL;\n    PyObject *result;\n\n    static char *kwlist[] = {\"key\", \"default\", NULL};\n    if (!PyArg_ParseTupleAndKeywords(args, keywds, \"O|O\", kwlist, &key, &default_obj))\n        return NULL;\n\n    result = lru_subscript(self, key);\n    PyErr_Clear();  /* GET_NODE sets an exception on miss. Shut it up. */\n    if (result)\n        return result;\n\n    if (!default_obj)\n        Py_RETURN_NONE;\n\n    Py_INCREF(default_obj);\n    return default_obj;\n}\n\nstatic int\nlru_ass_sub(LRU *self, PyObject *key, PyObject *value)\n{\n    int res = 0;\n    Node *node = GET_NODE(self->dict, key);\n    PyErr_Clear();  /* GET_NODE sets an exception on miss. Shut it up. */\n\n    if (value) {\n        if (node) {\n            Py_INCREF(value);\n            Py_DECREF(node->value);\n            node->value = value;\n\n            lru_remove_node(self, node);\n            lru_add_node_at_head(self, node);\n\n            res = 0;\n        } else {\n            node = PyObject_NEW(Node, &NodeType);\n            node->key = key;\n            node->value = value;\n            node->next = node->prev = NULL;\n\n            Py_INCREF(key);\n            Py_INCREF(value);\n\n            res = PUT_NODE(self->dict, key, node);\n            if (res == 0) {\n                if (lru_length(self) > self->size) {\n                    lru_delete_last(self);\n                }\n\n                lru_add_node_at_head(self, node);\n            }\n        }\n    } else {\n        res = PUT_NODE(self->dict, key, NULL);\n        if (res == 0) {\n            assert(node && PyObject_TypeCheck(node, &NodeType));\n            lru_remove_node(self, node);\n        }\n    }\n\n    Py_XDECREF(node);\n    return res;\n}\n\nstatic PyMappingMethods LRU_as_mapping = {\n    (lenfunc)lru_length,        /*mp_length*/\n    (binaryfunc)lru_subscript,  /*mp_subscript*/\n    (objobjargproc)lru_ass_sub, /*mp_ass_subscript*/\n};\n\nstatic PyObject *\ncollect(LRU *self, PyObject * (*getterfunc)(Node *))\n{\n    register PyObject *v;\n    Node *curr;\n    int i;\n    v = PyList_New(lru_length(self));\n    if (v == NULL)\n        return NULL;\n    curr = self->first;\n    i = 0;\n\n    while (curr) {\n        PyList_SET_ITEM(v, i++, getterfunc(curr));\n        curr = curr->next;\n    }\n    assert(i == lru_length(self));\n    return v;\n}\n\nstatic PyObject *\nget_key(Node *node)\n{\n    Py_INCREF(node->key);\n    return node->key;\n}\n\nstatic PyObject *\nLRU_update(LRU *self, PyObject *args, PyObject *kwargs)\n{\n\tPyObject *key, *value;\n\tPyObject *arg = NULL;\n\tPy_ssize_t pos = 0;\n\n\tif ((PyArg_ParseTuple(args, \"|O\", &arg))) {\n\t\tif (arg && PyDict_Check(arg)) {\n\t\t\twhile (PyDict_Next(arg, &pos, &key, &value))\n\t\t\t\tlru_ass_sub(self, key, value);\n\t\t}\n\t}\n\n\tif (kwargs != NULL && PyDict_Check(kwargs)) {\n\t\twhile (PyDict_Next(kwargs, &pos, &key, &value))\n\t\t\tlru_ass_sub(self, key, value);\n\t}\n\n\tPy_RETURN_NONE;\n}\n\nstatic PyObject *\nLRU_setdefault(LRU *self, PyObject *args)\n{\n    PyObject *key;\n    PyObject *default_obj = NULL;\n    PyObject *result;\n\n    if (!PyArg_ParseTuple(args, \"O|O\", &key, &default_obj))\n        return NULL;\n\n    result = lru_subscript(self, key);\n    PyErr_Clear();\n    if (result)\n        return result;\n\n    if (!default_obj)\n        default_obj = Py_None;\n\n    if (lru_ass_sub(self, key, default_obj) != 0)\n        return NULL;\n\n    Py_INCREF(default_obj);\n    return default_obj;\n}\n\nstatic PyObject *\nLRU_pop(LRU *self, PyObject *args, PyObject *keywds)\n{\n    PyObject *key;\n    PyObject *default_obj = NULL;\n    PyObject *result;\n\n    static char *kwlist[] = {\"key\", \"default\", NULL};\n    if (!PyArg_ParseTupleAndKeywords(args, keywds, \"O|O\", kwlist, &key, &default_obj))\n        return NULL;\n\n    /* Trying to access the item by key. */\n    result = lru_subscript(self, key);\n\n    if (result)\n        /* result != NULL, delete it from dict by key */\n        lru_ass_sub(self, key, NULL);\n    else if (default_obj) {\n        /* result == NULL, i.e. key missing, and default_obj given */\n        PyErr_Clear();\n        Py_INCREF(default_obj);\n        result = default_obj;\n    }\n    /* Otherwise (key missing, and default_obj not given [i.e. == NULL]), the\n     * call to lru_subscript (at the location marked by \"Trying to access the\n     * item by key\" in the comments) has already generated the appropriate\n     * exception. */\n\n    return result;\n}\n\nstatic PyObject *\nLRU_peek_first_item(LRU *self)\n{\n    if (self->first)\n        return Py_BuildValue(\"OO\", self->first->key, self->first->value);\n    Py_RETURN_NONE;\n}\n\nstatic PyObject *\nLRU_peek_last_item(LRU *self)\n{\n    if (self->last)\n        return Py_BuildValue(\"OO\", self->last->key, self->last->value);\n    Py_RETURN_NONE;\n}\n\nstatic PyObject *\nLRU_popitem(LRU *self, PyObject *args, PyObject *kwds)\n{\n    static char *kwlist[] = {\"least_recent\", NULL};\n    int pop_least_recent = 1;\n    PyObject *result;\n\n#if PY_MAJOR_VERSION >= 3\n    if (!PyArg_ParseTupleAndKeywords(args, kwds, \"|p\", kwlist, &pop_least_recent))\n        return NULL;\n#else\n    {\n        PyObject *arg_ob = Py_True;\n        if (!PyArg_ParseTupleAndKeywords(args, kwds, \"|O\", kwlist, &arg_ob))\n            return NULL;\n        pop_least_recent = PyObject_IsTrue(arg_ob);\n        if (pop_least_recent == -1)\n            return NULL;\n    }\n#endif\n    if (pop_least_recent)\n        result = LRU_peek_last_item(self);\n    else\n        result = LRU_peek_first_item(self);\n    if (result == Py_None) {\n        PyErr_SetString(PyExc_KeyError, \"popitem(): LRU dict is empty\");\n        return NULL;\n    }\n    lru_ass_sub(self, PyTuple_GET_ITEM(result, 0), NULL);\n    Py_INCREF(result);\n    return result;\n}\n\nstatic PyObject *\nLRU_keys(LRU *self) {\n    return collect(self, get_key);\n}\n\nstatic PyObject *\nget_value(Node *node)\n{\n    Py_INCREF(node->value);\n    return node->value;\n}\n\nstatic PyObject *\nLRU_values(LRU *self)\n{\n    return collect(self, get_value);\n}\n\nstatic PyObject *\nLRU_set_callback(LRU *self, PyObject *args)\n{\n    return set_callback(self, args);\n}\n\nstatic PyObject *\nget_item(Node *node)\n{\n    PyObject *tuple = PyTuple_New(2);\n    Py_INCREF(node->key);\n    PyTuple_SET_ITEM(tuple, 0, node->key);\n    Py_INCREF(node->value);\n    PyTuple_SET_ITEM(tuple, 1, node->value);\n    return tuple;\n}\n\nstatic PyObject *\nLRU_items(LRU *self)\n{\n    return collect(self, get_item);\n}\n\nstatic PyObject *\nLRU_set_size(LRU *self, PyObject *args, PyObject *kwds)\n{\n    Py_ssize_t newSize;\n    if (!PyArg_ParseTuple(args, \"n\", &newSize)) {\n        return NULL;\n    }\n    if (newSize <= 0) {\n        PyErr_SetString(PyExc_ValueError, \"Size should be a positive number\");\n        return NULL;\n    }\n    while (lru_length(self) > newSize) {\n        lru_delete_last(self);\n    }\n    self->size = newSize;\n    Py_RETURN_NONE;\n}\n\nstatic PyObject *\nLRU_clear(LRU *self)\n{\n    Node *c = self->first;\n\n    while (c) {\n        Node* n = c;\n        c = c->next;\n        lru_remove_node(self, n);\n    }\n    PyDict_Clear(self->dict);\n\n    self->hits = 0;\n    self->misses = 0;\n    Py_RETURN_NONE;\n}\n\n\nstatic PyObject *\nLRU_get_size(LRU *self)\n{\n    return Py_BuildValue(\"i\", self->size);\n}\n\nstatic PyObject *\nLRU_get_stats(LRU *self)\n{\n    return Py_BuildValue(\"nn\", self->hits, self->misses);\n}\n\n\n/* Hack to implement \"key in lru\" */\nstatic PySequenceMethods lru_as_sequence = {\n    0,                             /* sq_length */\n    0,                             /* sq_concat */\n    0,                             /* sq_repeat */\n    0,                             /* sq_item */\n    0,                             /* sq_slice */\n    0,                             /* sq_ass_item */\n    0,                             /* sq_ass_slice */\n    (objobjproc) LRU_seq_contains, /* sq_contains */\n    0,                             /* sq_inplace_concat */\n    0,                             /* sq_inplace_repeat */\n};\n\nstatic PyMethodDef LRU_methods[] = {\n    {\"__contains__\", (PyCFunction)LRU_contains_key, METH_O | METH_COEXIST,\n                    PyDoc_STR(\"L.__contains__(key) -> Check if key is there in L\")},\n    {\"keys\", (PyCFunction)LRU_keys, METH_NOARGS,\n                    PyDoc_STR(\"L.keys() -> list of L's keys in MRU order\")},\n    {\"values\", (PyCFunction)LRU_values, METH_NOARGS,\n                    PyDoc_STR(\"L.values() -> list of L's values in MRU order\")},\n    {\"items\", (PyCFunction)LRU_items, METH_NOARGS,\n                    PyDoc_STR(\"L.items() -> list of L's items (key,value) in MRU order\")},\n    {\"has_key\",\t(PyCFunction)LRU_contains, METH_VARARGS,\n                    PyDoc_STR(\"L.has_key(key) -> Check if key is there in L\")},\n    {\"get\",\t(PyCFunction)LRU_get, METH_VARARGS | METH_KEYWORDS,\n                    PyDoc_STR(\"L.get(key, default=None) -> If L has key return its value, otherwise default\")},\n    {\"setdefault\", (PyCFunction)LRU_setdefault, METH_VARARGS,\n                    PyDoc_STR(\"L.setdefault(key, default=None) -> If L has key return its value, otherwise insert key with a value of default and return default\")},\n    {\"pop\", (PyCFunction)LRU_pop, METH_VARARGS | METH_KEYWORDS,\n                    PyDoc_STR(\"L.pop(key[, default]) -> If L has key return its value and remove it from L, otherwise return default. If default is not given and key is not in L, a KeyError is raised.\")},\n    {\"popitem\", (PyCFunction)LRU_popitem, METH_VARARGS | METH_KEYWORDS,\n                    PyDoc_STR(\"L.popitem([least_recent=True]) -> Returns and removes a (key, value) pair. The pair returned is the least-recently used if least_recent is true, or the most-recently used if false.\")},\n    {\"set_size\", (PyCFunction)LRU_set_size, METH_VARARGS,\n                    PyDoc_STR(\"L.set_size() -> set size of LRU\")},\n    {\"get_size\", (PyCFunction)LRU_get_size, METH_NOARGS,\n                    PyDoc_STR(\"L.get_size() -> get size of LRU\")},\n    {\"clear\", (PyCFunction)LRU_clear, METH_NOARGS,\n                    PyDoc_STR(\"L.clear() -> clear LRU\")},\n    {\"get_stats\", (PyCFunction)LRU_get_stats, METH_NOARGS,\n                    PyDoc_STR(\"L.get_stats() -> returns a tuple with cache hits and misses\")},\n    {\"peek_first_item\", (PyCFunction)LRU_peek_first_item, METH_NOARGS,\n                    PyDoc_STR(\"L.peek_first_item() -> returns the MRU item (key,value) without changing key order\")},\n    {\"peek_last_item\", (PyCFunction)LRU_peek_last_item, METH_NOARGS,\n                    PyDoc_STR(\"L.peek_last_item() -> returns the LRU item (key,value) without changing key order\")},\n    {\"update\", (PyCFunction)LRU_update, METH_VARARGS | METH_KEYWORDS,\n                    PyDoc_STR(\"L.update() -> update value for key in LRU\")},\n    {\"set_callback\", (PyCFunction)LRU_set_callback, METH_VARARGS,\n                    PyDoc_STR(\"L.set_callback(callback) -> set a callback to call when an item is evicted.\")},\n    {NULL,\tNULL},\n};\n\nstatic PyObject*\nLRU_repr(LRU* self)\n{\n    return PyObject_Repr(self->dict);\n}\n\nstatic int\nLRU_init(LRU *self, PyObject *args, PyObject *kwds)\n{\n    static char *kwlist[] = {\"size\", \"callback\", NULL};\n    PyObject *callback = NULL;\n    self->callback = NULL;\n    if (!PyArg_ParseTupleAndKeywords(args, kwds, \"n|O\", kwlist, &self->size, &callback)) {\n        return -1;\n    }\n\n    if (callback && callback != Py_None) {\n        if (!PyCallable_Check(callback)) {\n            PyErr_SetString(PyExc_TypeError, \"parameter must be callable\");\n            return -1;\n        }\n        Py_XINCREF(callback);\n        self->callback = callback;\n    }\n\n    if ((Py_ssize_t)self->size <= 0) {\n        PyErr_SetString(PyExc_ValueError, \"Size should be a positive number\");\n        return -1;\n    }\n    self->dict = PyDict_New();\n    self->first = self->last = NULL;\n    self->hits = 0;\n    self->misses = 0;\n    return 0;\n}\n\nstatic void\nLRU_dealloc(LRU *self)\n{\n    if (self->dict) {\n        LRU_clear(self);\n        Py_CLEAR(self->dict);\n        Py_XDECREF(self->callback);\n    }\n    Py_TYPE(self)->tp_free((PyObject *)self);\n}\n\nPyDoc_STRVAR(lru_doc,\n\"LRU(size, callback=None) -> new LRU dict that can store up to size elements\\n\"\n\"An LRU dict behaves like a standard dict, except that it stores only fixed\\n\"\n\"set of elements. Once the size overflows, it evicts least recently used\\n\"\n\"items.  If a callback is set it will call the callback with the evicted key\\n\"\n\" and item.\\n\\n\"\n\"Eg:\\n\"\n\">>> l = LRU(3)\\n\"\n\">>> for i in range(5):\\n\"\n\">>>   l[i] = str(i)\\n\"\n\">>> l.keys()\\n\"\n\"[2,3,4]\\n\\n\"\n\"Note: An LRU(n) can be thought of as a dict that will have the most\\n\"\n\"recently accessed n items.\\n\");\n\nstatic PyTypeObject LRUType = {\n    PyVarObject_HEAD_INIT(NULL, 0)\n    \"_lru.LRU\",               /* tp_name */\n    sizeof(LRU),             /* tp_basicsize */\n    0,                       /* tp_itemsize */\n    (destructor)LRU_dealloc, /* tp_dealloc */\n    0,                       /* tp_print */\n    0,                       /* tp_getattr */\n    0,                       /* tp_setattr */\n    0,                       /* tp_compare */\n    (reprfunc)LRU_repr,      /* tp_repr */\n    0,                       /* tp_as_number */\n    &lru_as_sequence,        /* tp_as_sequence */\n    &LRU_as_mapping,         /* 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 | Py_TPFLAGS_BASETYPE,      /* tp_flags */\n    lru_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    LRU_methods,             /* tp_methods */\n    0,                       /* tp_members */\n    0,                       /* tp_getset */\n    0,                       /* tp_base */\n    0,                       /* tp_dict */\n    0,                       /* tp_descr_get */\n    0,                       /* tp_descr_set */\n    0,                       /* tp_dictoffset */\n    (initproc)LRU_init,      /* tp_init */\n    0,                       /* tp_alloc */\n    PyType_GenericNew,       /* tp_new */\n};\n\n#if PY_MAJOR_VERSION >= 3\n  static struct PyModuleDef moduledef = {\n    PyModuleDef_HEAD_INIT,\n    \"_lru\",            /* m_name */\n    lru_doc,          /* m_doc */\n    -1,               /* m_size */\n    NULL,             /* m_methods */\n    NULL,             /* m_reload */\n    NULL,             /* m_traverse */\n    NULL,             /* m_clear */\n    NULL,             /* m_free */\n  };\n#endif\n\nstatic PyObject *\nmoduleinit(void)\n{\n    PyObject *m;\n\n    LRUType.tp_base = &PyBaseObject_Type;\n\n    if (PyType_Ready(&NodeType) < 0)\n        return NULL;\n\n    if (PyType_Ready(&LRUType) < 0)\n        return NULL;\n\n    #if PY_MAJOR_VERSION >= 3\n        m = PyModule_Create(&moduledef);\n    #else\n        m = Py_InitModule3(\"_lru\", NULL, lru_doc);\n    #endif\n\n    if (m == NULL)\n        return NULL;\n\n    Py_INCREF(&NodeType);\n    Py_INCREF(&LRUType);\n    PyModule_AddObject(m, \"LRU\", (PyObject *) &LRUType);\n\n    return m;\n}\n\n#if PY_MAJOR_VERSION < 3\n    PyMODINIT_FUNC\n    init_lru(void)\n    {\n        moduleinit();\n    }\n#else\n    PyMODINIT_FUNC\n    PyInit__lru(void)\n    {\n        return moduleinit();\n    }\n#endif\n"
  },
  {
    "path": "src/lru/py.typed",
    "content": ""
  },
  {
    "path": "test/test_lru.py",
    "content": "import gc\nimport random\nimport sys\nimport unittest\nfrom lru import LRU\n\nSIZES = [1, 2, 10, 1000]\n\n# Only available on debug python builds.\ngettotalrefcount = getattr(sys, 'gettotalrefcount', lambda: 0)\n\n\nclass TestLRU(unittest.TestCase):\n\n    def setUp(self):\n        gc.collect()\n        self._before_count = gettotalrefcount()\n\n    def tearDown(self):\n        after_count = gettotalrefcount()\n        self.assertEqual(self._before_count, after_count)\n\n    def _check_kvi(self, valid_keys, l):\n        valid_keys = list(valid_keys)\n        valid_vals = list(map(str, valid_keys))\n        self.assertEqual(valid_keys, l.keys())\n        self.assertEqual(valid_vals, l.values())\n        self.assertEqual(list(zip(valid_keys, valid_vals)), l.items())\n\n    def test_invalid_size(self):\n        self.assertRaises(ValueError, LRU, -1)\n        self.assertRaises(ValueError, LRU, 0)\n\n    def test_empty(self):\n        l = LRU(1)\n        self.assertEqual([], l.keys())\n        self.assertEqual([], l.values())\n\n    def test_add_within_size(self):\n        for size in SIZES:\n            l = LRU(size)\n            for i in range(size):\n                l[i] = str(i)\n            self._check_kvi(range(size - 1, -1, -1), l)\n\n    def test_delete_multiple_within_size(self):\n        for size in SIZES:\n            l = LRU(size)\n            for i in range(size):\n                l[i] = str(i)\n            for i in range(0, size, 2):\n                del l[i]\n            self._check_kvi(range(size - 1, 0, -2), l)\n            for i in range(0, size, 2):\n                with self.assertRaises(KeyError):\n                    l[i]\n\n    def test_delete_multiple(self):\n        for size in SIZES:\n            l = LRU(size)\n            n = size * 2\n            for i in range(n):\n                l[i] = str(i)\n            for i in range(size, n, 2):\n                del l[i]\n            self._check_kvi(range(n - 1, size, -2), l)\n            for i in range(0, size):\n                with self.assertRaises(KeyError):\n                    l[i]\n            for i in range(size, n, 2):\n                with self.assertRaises(KeyError):\n                    l[i]\n\n    def test_add_multiple(self):\n        for size in SIZES:\n            l = LRU(size)\n            for i in range(size):\n                l[i] = str(i)\n            l[size] = str(size)\n            self._check_kvi(range(size, 0, -1), l)\n\n    def test_access_within_size(self):\n        for size in SIZES:\n            l = LRU(size)\n            for i in range(size):\n                l[i] = str(i)\n            for i in range(size):\n                self.assertEqual(l[i], str(i))\n                self.assertEqual(l.get(i, None), str(i))\n\n    def test_contains(self):\n        for size in SIZES:\n            l = LRU(size)\n            for i in range(size):\n                l[i] = str(i)\n            for i in range(size):\n                self.assertTrue(i in l)\n\n    def test_access(self):\n        for size in SIZES:\n            l = LRU(size)\n            n = size * 2\n            for i in range(n):\n                l[i] = str(i)\n            self._check_kvi(range(n - 1, size - 1, -1), l)\n            for i in range(size, n):\n                self.assertEqual(l[i], str(i))\n                self.assertEqual(l.get(i, None), str(i))\n\n    def test_update(self):\n        l = LRU(2)\n        l['a'] = 1\n        self.assertEqual(l['a'], 1)\n        l.update(a=2)\n        self.assertEqual(l['a'], 2)\n        l['b'] = 2\n        self.assertEqual(l['b'], 2)\n        l.update(b=3)\n        self.assertEqual(('b', 3), l.peek_first_item())\n        self.assertEqual(l['a'], 2)\n        self.assertEqual(l['b'], 3)\n        l.update({'a': 1, 'b': 2})\n        self.assertEqual(('b', 2), l.peek_first_item())\n        self.assertEqual(l['a'], 1)\n        self.assertEqual(l['b'], 2)\n        l.update()\n        self.assertEqual(('b', 2), l.peek_first_item())\n        l.update(a=2)\n        self.assertEqual(('a', 2), l.peek_first_item())\n\n    def test_peek_first_item(self):\n        l = LRU(2)\n        self.assertEqual(None, l.peek_first_item())\n        l[1] = '1'\n        l[2] = '2'\n        self.assertEqual((2, '2'), l.peek_first_item())\n\n    def test_peek_last_item(self):\n        l = LRU(2)\n        self.assertEqual(None, l.peek_last_item())\n        l[1] = '1'\n        l[2] = '2'\n        self.assertEqual((1, '1'), l.peek_last_item())\n\n    def test_overwrite(self):\n        l = LRU(1)\n        l[1] = '2'\n        l[1] = '1'\n        self.assertEqual('1', l[1])\n        self._check_kvi([1], l)\n\n    def test_has_key(self):\n        for size in SIZES:\n            l = LRU(size)\n            for i in range(2 * size):\n                l[i] = str(i)\n                self.assertTrue(l.has_key(i))\n            for i in range(size, 2 * size):\n                self.assertTrue(l.has_key(i))\n            for i in range(size):\n                self.assertFalse(l.has_key(i))\n\n    def test_capacity_get(self):\n        for size in SIZES:\n            l = LRU(size)\n            self.assertTrue(size == l.get_size())\n\n    def test_capacity_set(self):\n        for size in SIZES:\n            l = LRU(size)\n            for i in range(size + 5):\n                l[i] = str(i)\n            l.set_size(size + 10)\n            self.assertTrue(size + 10 == l.get_size())\n            self.assertTrue(len(l) == size)\n            for i in range(size + 20):\n                l[i] = str(i)\n            self.assertTrue(len(l) == size + 10)\n            l.set_size(size + 10 - 1)\n            self.assertTrue(len(l) == size + 10 - 1)\n\n    def test_unhashable(self):\n        l = LRU(1)\n        self.assertRaises(TypeError, lambda: l[{'a': 'b'}])\n        with self.assertRaises(TypeError):\n            l[['1']] = '2'\n        with self.assertRaises(TypeError):\n            del l[{'1': '1'}]\n\n    def test_clear(self):\n        for size in SIZES:\n            l = LRU(size)\n            for i in range(size + 5):\n                l[i] = str(i)\n            l.clear()\n            for i in range(size):\n                l[i] = str(i)\n            for i in range(size):\n                _ = l[random.randint(0, size - 1)]\n            l.clear()\n            self.assertTrue(len(l) == 0)\n\n    def test_get_and_del(self):\n        l = LRU(2)\n        l[1] = '1'\n        self.assertEqual('1', l.get(1))\n        self.assertEqual('1', l.get(2, '1'))\n        self.assertIsNone(l.get(2))\n        self.assertEqual('1', l[1])\n        self.assertRaises(KeyError, lambda: l['2'])\n        with self.assertRaises(KeyError):\n            del l['2']\n\n    def test_setdefault(self):\n        l = LRU(2)\n        l[1] = '1'\n        val = l.setdefault(1)\n        self.assertEqual('1', val)\n        self.assertEqual((1, 0), l.get_stats())\n        val = l.setdefault(2, '2')\n        self.assertEqual('2', val)\n        self.assertEqual((1, 1), l.get_stats())\n        self.assertEqual(val, l[2])\n        l.clear()\n        val = 'long string' * 512\n        l.setdefault(1, val)\n        l[2] = '2'\n        l[3] = '3'\n        self.assertTrue(val)\n\n    def test_pop(self):\n        l = LRU(2)\n        v = '2' * 4096\n        l[1] = '1'\n        l[2] = v\n        val = l.pop(1)\n        self.assertEqual('1', val)\n        self.assertEqual((1, 0), l.get_stats())\n        val = l.pop(2, 'not used')\n        self.assertEqual(v, val)\n        del val\n        self.assertTrue(v)\n        self.assertEqual((2, 0), l.get_stats())\n        val = l.pop(3, '3' * 4096)\n        self.assertEqual('3' * 4096, val)\n        self.assertEqual((2, 1), l.get_stats())\n        self.assertEqual(0, len(l))\n        with self.assertRaises(KeyError) as ke:\n            l.pop(4)\n            self.assertEqual(4, ke.args[0])  # type: ignore\n        self.assertEqual((2, 2), l.get_stats())\n        self.assertEqual(0, len(l))\n        with self.assertRaises(TypeError):\n            l.pop()  # type: ignore\n\n    def test_popitem(self):\n        l = LRU(3)\n        l[1] = '1'\n        l[2] = '2'\n        l[3] = '3'\n        k, v = l.popitem()\n        self.assertEqual((1, '1'), (k, v))\n        k, v = l.popitem(least_recent=False)\n        self.assertEqual((3, '3'), (k, v))\n        self.assertEqual((2, '2'), l.popitem(True))\n        with self.assertRaises(KeyError) as ke:\n            l.popitem()\n            self.assertEqual('popitem(): LRU dict is empty', ke.args[0])  # type: ignore\n        self.assertEqual((0, 0), l.get_stats())\n\n    def test_stats(self):\n        for size in SIZES:\n            l = LRU(size)\n            for i in range(size):\n                l[i] = str(i)\n\n            self.assertTrue(l.get_stats() == (0, 0))\n\n            val = l[0]\n            self.assertTrue(l.get_stats() == (1, 0))\n\n            val = l.get(0, None)\n            self.assertTrue(l.get_stats() == (2, 0))\n\n            val = l.get(-1, None)\n            self.assertTrue(l.get_stats() == (2, 1))\n\n            try:\n                val = l[-1]\n            except:\n                pass\n\n            self.assertTrue(l.get_stats() == (2, 2))\n\n            l.clear()\n            self.assertTrue(len(l) == 0)\n            self.assertTrue(l.get_stats() == (0, 0))\n\n    def test_lru(self):\n        l = LRU(1)\n        l['a'] = 1\n        l['a']\n        self.assertEqual(l.keys(), ['a'])\n        l['b'] = 2\n        self.assertEqual(l.keys(), ['b'])\n\n        l = LRU(2)\n        l['a'] = 1\n        l['b'] = 2\n        self.assertEqual(len(l), 2)\n        l['a']                  # Testing the first one\n        l['c'] = 3\n        self.assertEqual(sorted(l.keys()), ['a', 'c'])\n        l['c']\n        self.assertEqual(sorted(l.keys()), ['a', 'c'])\n\n        l = LRU(3)\n        l['a'] = 1\n        l['b'] = 2\n        l['c'] = 3\n        self.assertEqual(len(l), 3)\n        l['b']                  # Testing the middle one\n        l['d'] = 4\n        self.assertEqual(sorted(l.keys()), ['b', 'c', 'd'])\n        l['d']                  # Testing the last one\n        self.assertEqual(sorted(l.keys()), ['b', 'c', 'd'])\n        l['e'] = 5\n        self.assertEqual(sorted(l.keys()), ['b', 'd', 'e'])\n\n    def test_callback(self):\n\n        counter = [0]\n\n        first_key = 'a'\n        first_value = 1\n\n        def callback(key, value):\n            self.assertEqual(key, first_key)\n            self.assertEqual(value, first_value)\n            counter[0] += 1\n\n        l = LRU(1, callback=callback)\n        l[first_key] = first_value\n        l['b'] = 1              # test calling the callback\n\n        self.assertEqual(counter[0], 1)\n        self.assertEqual(l.keys(), ['b'])\n\n        l['b'] = 2              # doesn't call callback\n        self.assertEqual(counter[0], 1)\n        self.assertEqual(l.keys(), ['b'])\n        self.assertEqual(l.values(), [2])\n\n        l = LRU(1, callback=callback)\n        l[first_key] = first_value\n\n        l.set_callback(None)\n        l['c'] = 1              # doesn't call callback\n        self.assertEqual(counter[0], 1)\n        self.assertEqual(l.keys(), ['c'])\n\n        l.set_callback(callback)\n        del l['c']              # doesn't call callback\n        self.assertEqual(counter[0], 1)\n        self.assertEqual(l.keys(), [])\n\n        l = LRU(2, callback=callback)\n        l['a'] = 1              # test calling the callback\n        l['b'] = 2              # test calling the callback\n\n        self.assertEqual(counter[0], 1)\n        self.assertEqual(l.keys(), ['b', 'a'])\n        l.set_size(1)\n        self.assertEqual(counter[0], 2)  # callback invoked\n        self.assertEqual(l.keys(), ['b'])\n\n    def test_subclassing(self):\n        class LRUChild(LRU):\n            ...\n\n        l = LRUChild(1)\n        l['a'] = 1\n        self.assertEqual(l['a'], 1)\n        self.assertEqual(l.keys(), ['a'])\n        self.assertEqual(l.values(), [1])\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  }
]