Repository: amitdev/lru-dict
Branch: master
Commit: 6b2ee068bd64
Files: 13
Total size: 48.2 KB
Directory structure:
gitextract_prc6blzv/
├── .github/
│ └── workflows/
│ ├── build-and-deploy.yml
│ └── tests.yml
├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.rst
├── pyproject.toml
├── setup.py
├── src/
│ └── lru/
│ ├── __init__.py
│ ├── __init__.pyi
│ ├── _lru.c
│ └── py.typed
└── test/
└── test_lru.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/build-and-deploy.yml
================================================
name: Build and deploy
on: [push, pull_request]
jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.runs-on }}
strategy:
matrix:
os: [linux, windows, macos, ios, android]
include:
- os: linux
runs-on: ubuntu-22.04
- os: windows
runs-on: windows-2022
- os: macos
runs-on: macos-latest
- os: ios
runs-on: macos-14
- os: android
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
if: runner.os == 'Linux'
uses: docker/setup-qemu-action@v3
with:
platforms: all
- name: Enable KVM group perms
if: matrix.os == 'android'
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
# Used to host cibuildwheel
- name: Build wheels
uses: pypa/cibuildwheel@v3.1.4
env:
# configure cibuildwheel to build native archs ('auto'), and some emulated ones
CIBW_PLATFORM: ${{ matrix.os }}
CIBW_ARCHS_LINUX: auto aarch64 ppc64le
CIBW_ARCHS_MACOS: x86_64 arm64 universal2
CIBW_ARCHS_IOS: arm64_iphoneos arm64_iphonesimulator x86_64_iphonesimulator
CIBW_ARCHS_ANDROID: x86_64 arm64_v8a
# cannot test the arm64 part for CPython 3.8 universal2/arm64, see https://github.com/pypa/cibuildwheel/pull/1169
CIBW_TEST_SKIP: cp38-macosx_arm64
# pypy will need to be enabled for cibuildwheel 3
CIBW_ENABLE: pypy
- uses: actions/upload-artifact@v4
with:
name: artifacts-${{ matrix.os }}
path: ./wheelhouse/*.whl
build_sdist:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build sdist
run: pipx run build --sdist
- uses: actions/upload-artifact@v4
with:
name: artifacts-sdist
path: dist/*.tar.gz
pypi_upload:
name: Publish to PyPI
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
steps:
- uses: actions/setup-python@v5
- uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: dist
merge-multiple: true
- name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1
with:
verbose: true
user: __token__
password: ${{ secrets.pypi_password }}
================================================
FILE: .github/workflows/tests.yml
================================================
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
python:
[
"3.9",
"3.10",
"3.11",
"3.12",
"3.13",
"3.14",
"pypy-3.9",
"pypy-3.10",
"pypy-3.11",
]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
allow-prereleases: true
- name: Update Python tools
run: |
python -m pip install -U pip
python -m pip install -U setuptools
- name: Install lru-dict with test extras
run: |
python -m pip install .[test]
- name: Run tests
run: |
python -m pytest
install:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
python:
[
"3.9",
"3.10",
"3.11",
"3.12",
"3.13",
"3.14",
"pypy-3.9",
"pypy-3.10",
"pypy-3.11",
]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
allow-prereleases: true
- name: Update Python tools
run: |
python -m pip install -U pip
python -m pip install -U setuptools
- name: Install lru-dict
run: |
python -m pip install .
- name: Verify install
run: |
python -c "from lru import LRU"
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
wheelhouse/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pdm config
.pdm-python
================================================
FILE: LICENSE
================================================
Copyright (c) Amit Dev R
Permission 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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE 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.
================================================
FILE: MANIFEST.in
================================================
include LICENSE README.rst MANIFEST MANIFEST.in
graft src
global-exclude *.pyc
global-exclude *.cache
================================================
FILE: README.rst
================================================
.. image:: https://github.com/amitdev/lru-dict/actions/workflows/tests.yml/badge.svg
:target: https://github.com/amitdev/lru-dict/actions/workflows/tests.yml
.. image:: https://github.com/amitdev/lru-dict/actions/workflows/build-and-deploy.yml/badge.svg
:target: https://github.com/amitdev/lru-dict/actions/workflows/build-and-deploy.yml
.. image:: https://img.shields.io/badge/maintainers-wanted-red.svg
**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.
LRU Dict
========
A fixed size dict like container which evicts Least Recently Used (LRU) items
once size limit is exceeded. There are many python implementations available
which does similar things. This is a fast and efficient C implementation.
LRU maximum capacity can be modified at run-time.
If you are looking for pure python version, look `else where <http://www.google.com/search?q=python+lru+dict>`_.
Usage
=====
This can be used to build a LRU cache. Usage is almost like a dict.
.. code:: python3
from lru import LRU
l = LRU(5) # Create an LRU container that can hold 5 items
print l.peek_first_item(), l.peek_last_item() #return the MRU key and LRU key
# Would print None None
for i in range(5):
l[i] = str(i)
print l.items() # Prints items in MRU order
# Would print [(4, '4'), (3, '3'), (2, '2'), (1, '1'), (0, '0')]
print l.peek_first_item(), l.peek_last_item() #return the MRU key and LRU key
# Would print (4, '4') (0, '0')
l[5] = '5' # Inserting one more item should evict the old item
print l.items()
# Would print [(5, '5'), (4, '4'), (3, '3'), (2, '2'), (1, '1')]
l[3] # Accessing an item would make it MRU
print l.items()
# Would print [(3, '3'), (5, '5'), (4, '4'), (2, '2'), (1, '1')]
# Now 3 is in front
l.keys() # Can get keys alone in MRU order
# Would print [3, 5, 4, 2, 1]
del l[4] # Delete an item
print l.items()
# Would print [(3, '3'), (5, '5'), (2, '2'), (1, '1')]
print l.get_size()
# Would print 5
l.set_size(3)
print l.items()
# Would print [(3, '3'), (5, '5'), (2, '2')]
print l.get_size()
# Would print 3
print l.has_key(5)
# Would print True
print 2 in l
# Would print True
l.get_stats()
# Would print (1, 0)
l.update(5='0') # Update an item
print l.items()
# Would print [(5, '0'), (3, '3'), (2, '2')]
l.clear()
print l.items()
# Would print []
def evicted(key, value):
print "removing: %s, %s" % (key, value)
l = LRU(1, callback=evicted)
l[1] = '1'
l[2] = '2'
# callback would print removing: 1, 1
l[2] = '3'
# doesn't call the evicted callback
print l.items()
# would print [(2, '3')]
del l[2]
# doesn't call the evicted callback
print l.items()
# would print []
Install
=======
::
pip install lru-dict
or
::
easy_install lru_dict
When to use this
================
Like mentioned above there are many python implementations of an LRU. Use this
if you need a faster and memory efficient alternative. It is implemented with a
dict and associated linked list to keep track of LRU order. See code for a more
detailed explanation. To see an indicative comparison with a pure python module,
consider a `benchmark <https://gist.github.com/amitdev/5773979>`_ against
`pylru <https://pypi.python.org/pypi/pylru/>`_ (just chosen at random, it should
be similar with other python implementations as well).
::
$ python bench.py pylru.lrucache
Time : 3.31 s, Memory : 453672 Kb
$ python bench.py lru.LRU
Time : 0.23 s, Memory : 124328 Kb
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["setuptools==80.9.0"]
build-backend = "setuptools.build_meta"
[project]
name = "lru-dict"
version = "1.4.1"
description = "An Dict like LRU container."
authors = [
{name = "Amit Dev"},
]
dependencies = []
requires-python = ">=3.9"
readme = "README.rst"
license = "MIT"
license-files = ["LICENSE"]
keywords = ["lru", "dict"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Operating System :: POSIX",
"Programming Language :: C",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Software Development :: Libraries :: Python Modules",
]
[project.urls]
Homepage = "https://github.com/amitdev/lru-dict"
[project.optional-dependencies]
test = [
"pytest",
]
[tool.cibuildwheel]
test-extras = ["test"]
test-sources = ["test"]
test-command = "python -m pytest -v"
xbuild-tools = []
================================================
FILE: setup.py
================================================
from setuptools import setup, Extension
extensions = [
Extension("lru._lru", ["src/lru/_lru.c"]),
]
args = {
"include_package_data": True,
"exclude_package_data": {"": ["*.c"]},
}
setup(ext_modules=extensions, **args)
================================================
FILE: src/lru/__init__.py
================================================
from ._lru import LRU as LRU # noqa: F401
__all__ = ["LRU"]
================================================
FILE: src/lru/__init__.pyi
================================================
from typing import (
Any,
Callable,
Generic,
Hashable,
Iterable,
TypeVar,
overload,
Protocol
)
_KT = TypeVar("_KT", bound=Hashable)
_VT = TypeVar("_VT")
_VT_co = TypeVar("_VT_co", covariant=True)
_T = TypeVar("_T")
class __SupportsKeysAndGetItem(Protocol[_KT, _VT_co]):
def keys(self) -> Iterable[_KT]: ...
def __getitem__(self, __key: _KT) -> _VT_co: ...
class LRU(Generic[_KT, _VT]):
@overload
def __init__(self, size: int) -> None: ...
@overload
def __init__(self, size: int, callback: Callable[[_KT, _VT], Any]) -> None: ...
def clear(self) -> None: ...
@overload
def get(self, key: _KT) -> _VT | None: ...
@overload
def get(self, key: _KT, instead: _VT | _T) -> _VT | _T: ...
def get_size(self) -> int: ...
def has_key(self, key: _KT) -> bool: ...
def keys(self) -> list[_KT]: ...
def values(self) -> list[_VT]: ...
def items(self) -> list[tuple[_KT, _VT]]: ...
def peek_first_item(self) -> tuple[_KT, _VT] | None: ...
def peek_last_item(self) -> tuple[_KT, _VT] | None: ...
@overload
def pop(self, key: _KT) -> _VT | None: ...
@overload
def pop(self, key: _KT, default: _VT | _T) -> _VT | _T: ...
def popitem(self, least_recent: bool = ...) -> tuple[_KT, _VT]: ...
@overload
def setdefault(self: LRU[_KT, _T | None], key: _KT) -> _T | None: ...
@overload
def setdefault(self, key: _KT, default: _VT) -> _VT: ...
def set_callback(self, callback: Callable[[_KT, _VT], Any] | None) -> None: ...
def set_size(self, size: int) -> None: ...
@overload
def update(self, __m: __SupportsKeysAndGetItem[_KT, _VT], **kwargs: _VT) -> None: ...
@overload
def update(self, __m: Iterable[tuple[_KT, _VT]], **kwargs: _VT) -> None: ...
@overload
def update(self, **kwargs: _VT) -> None: ...
def get_stats(self) -> tuple[int, int]: ...
def __contains__(self, __o: Any) -> bool: ...
def __delitem__(self, key: _KT) -> None: ...
def __getitem__(self, item: _KT) -> _VT: ...
def __len__(self) -> int: ...
def __repr__(self) -> str: ...
def __setitem__(self, key: _KT, value: _VT) -> None: ...
================================================
FILE: src/lru/_lru.c
================================================
#include <Python.h>
/*
* This is a simple implementation of LRU Dict that uses a Python dict and an associated doubly linked
* list to keep track of recently inserted/accessed items.
*
* Dict will store: key -> Node mapping, where Node is a linked list node.
* The Node itself will contain the value as well as the key.
*
* For eg:
*
* >>> l = LRU(2)
* >>> l[0] = 'foo'
* >>> l[1] = 'bar'
*
* can be visualised as:
*
* ---+--(hash(0)--+--hash(1)--+
* self->dict ...| | |
* ---+-----|------+---------|-+
* | |
* +-----v------+ +-----v------+
* self->first--->|<'foo'>, <0>|-->|<'bar'>, <1>|<---self->last
* +--| |<--| |--+
* | +------------+ +------------+ |
* v v
* NULL NULL
*
* The invariant is to maintain the list to reflect the LRU order of items in the dict.
* self->first will point to the MRU item and self-last to LRU item. Size of list will not
* grow beyond size of LRU dict.
*
*/
#ifndef Py_TYPE
#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
#endif
#define GET_NODE(d, key) (Node *) Py_TYPE(d)->tp_as_mapping->mp_subscript((d), (key))
#define PUT_NODE(d, key, node) Py_TYPE(d)->tp_as_mapping->mp_ass_subscript((d), (key), ((PyObject *)node))
/* If someone figures out how to enable debug builds with setuptools, you can delete this */
#if 0
#undef assert
#define str(s) #s
#define assert(v) \
do { \
if (!(v)) { \
fprintf(stderr, "Assertion failed: %s on %s:%d\n", \
str(v), __FILE__, __LINE__); \
fflush(stderr); \
abort(); \
} \
} while(0)
#endif
typedef struct _Node {
PyObject_HEAD
PyObject * value;
PyObject * key;
struct _Node * prev;
struct _Node * next;
} Node;
static void
node_dealloc(Node* self)
{
Py_DECREF(self->key);
Py_DECREF(self->value);
assert(self->prev == NULL);
assert(self->next == NULL);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyObject*
node_repr(Node* self)
{
return PyObject_Repr(self->value);
}
static PyTypeObject NodeType = {
PyVarObject_HEAD_INIT(NULL, 0)
"_lru.Node", /* tp_name */
sizeof(Node), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)node_dealloc,/* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
(reprfunc)node_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"Linked List Node", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
};
typedef struct {
PyObject_HEAD
PyObject * dict;
Node * first;
Node * last;
Py_ssize_t size;
Py_ssize_t hits;
Py_ssize_t misses;
PyObject *callback;
} LRU;
static PyObject *
set_callback(LRU *self, PyObject *args)
{
PyObject *result = NULL;
PyObject *temp;
if (PyArg_ParseTuple(args, "O:set_callback", &temp)) {
if (temp == Py_None) {
Py_XDECREF(self->callback);
self->callback = NULL;
} else if (!PyCallable_Check(temp)) {
PyErr_SetString(PyExc_TypeError, "parameter must be callable");
return NULL;
} else {
Py_XINCREF(temp); /* Add a reference to new callback */
Py_XDECREF(self->callback); /* Dispose of previous callback */
self->callback = temp; /* Remember new callback */
}
Py_RETURN_NONE;
}
return result;
}
static void
lru_remove_node(LRU *self, Node* node)
{
if (self->first == node) {
self->first = node->next;
}
if (self->last == node) {
self->last = node->prev;
}
if (node->prev) {
node->prev->next = node->next;
}
if (node->next) {
node->next->prev = node->prev;
}
node->next = node->prev = NULL;
}
static void
lru_add_node_at_head(LRU *self, Node* node)
{
node->prev = NULL;
if (!self->first) {
self->first = self->last = node;
node->next = NULL;
} else {
node->next = self->first;
if (node->next) {
node->next->prev = node;
}
self->first = node;
}
}
static void
lru_delete_last(LRU *self)
{
PyObject *arglist;
PyObject *result;
Node* n = self->last;
if (!self->last)
return;
if (self->callback) {
arglist = Py_BuildValue("OO", n->key, n->value);
lru_remove_node(self, n);
result = PyObject_CallObject(self->callback, arglist);
Py_XDECREF(result);
Py_DECREF(arglist);
}
else {
lru_remove_node(self, n);
}
PUT_NODE(self->dict, n->key, NULL);
}
static inline Py_ssize_t
lru_length(LRU *self)
{
return PyDict_Size(self->dict);
}
static PyObject *
LRU_contains_key(LRU *self, PyObject *key)
{
if (PyDict_Contains(self->dict, key))
Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
static PyObject *
LRU_contains(LRU *self, PyObject *args)
{
PyObject *key;
if (!PyArg_ParseTuple(args, "O", &key))
return NULL;
return LRU_contains_key(self, key);
}
static int
LRU_seq_contains(LRU *self, PyObject *key)
{
return PyDict_Contains(self->dict, key);
}
static PyObject *
lru_subscript(LRU *self, register PyObject *key)
{
Node *node = GET_NODE(self->dict, key);
if (!node) {
self->misses++;
return NULL;
}
assert(PyObject_TypeCheck(node, &NodeType));
/* We don't need to move the node when it's already self->first. */
if (node != self->first) {
lru_remove_node(self, node);
lru_add_node_at_head(self, node);
}
self->hits++;
Py_INCREF(node->value);
Py_DECREF(node);
return node->value;
}
static PyObject *
LRU_get(LRU *self, PyObject *args, PyObject *keywds)
{
PyObject *key;
PyObject *default_obj = NULL;
PyObject *result;
static char *kwlist[] = {"key", "default", NULL};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|O", kwlist, &key, &default_obj))
return NULL;
result = lru_subscript(self, key);
PyErr_Clear(); /* GET_NODE sets an exception on miss. Shut it up. */
if (result)
return result;
if (!default_obj)
Py_RETURN_NONE;
Py_INCREF(default_obj);
return default_obj;
}
static int
lru_ass_sub(LRU *self, PyObject *key, PyObject *value)
{
int res = 0;
Node *node = GET_NODE(self->dict, key);
PyErr_Clear(); /* GET_NODE sets an exception on miss. Shut it up. */
if (value) {
if (node) {
Py_INCREF(value);
Py_DECREF(node->value);
node->value = value;
lru_remove_node(self, node);
lru_add_node_at_head(self, node);
res = 0;
} else {
node = PyObject_NEW(Node, &NodeType);
node->key = key;
node->value = value;
node->next = node->prev = NULL;
Py_INCREF(key);
Py_INCREF(value);
res = PUT_NODE(self->dict, key, node);
if (res == 0) {
if (lru_length(self) > self->size) {
lru_delete_last(self);
}
lru_add_node_at_head(self, node);
}
}
} else {
res = PUT_NODE(self->dict, key, NULL);
if (res == 0) {
assert(node && PyObject_TypeCheck(node, &NodeType));
lru_remove_node(self, node);
}
}
Py_XDECREF(node);
return res;
}
static PyMappingMethods LRU_as_mapping = {
(lenfunc)lru_length, /*mp_length*/
(binaryfunc)lru_subscript, /*mp_subscript*/
(objobjargproc)lru_ass_sub, /*mp_ass_subscript*/
};
static PyObject *
collect(LRU *self, PyObject * (*getterfunc)(Node *))
{
register PyObject *v;
Node *curr;
int i;
v = PyList_New(lru_length(self));
if (v == NULL)
return NULL;
curr = self->first;
i = 0;
while (curr) {
PyList_SET_ITEM(v, i++, getterfunc(curr));
curr = curr->next;
}
assert(i == lru_length(self));
return v;
}
static PyObject *
get_key(Node *node)
{
Py_INCREF(node->key);
return node->key;
}
static PyObject *
LRU_update(LRU *self, PyObject *args, PyObject *kwargs)
{
PyObject *key, *value;
PyObject *arg = NULL;
Py_ssize_t pos = 0;
if ((PyArg_ParseTuple(args, "|O", &arg))) {
if (arg && PyDict_Check(arg)) {
while (PyDict_Next(arg, &pos, &key, &value))
lru_ass_sub(self, key, value);
}
}
if (kwargs != NULL && PyDict_Check(kwargs)) {
while (PyDict_Next(kwargs, &pos, &key, &value))
lru_ass_sub(self, key, value);
}
Py_RETURN_NONE;
}
static PyObject *
LRU_setdefault(LRU *self, PyObject *args)
{
PyObject *key;
PyObject *default_obj = NULL;
PyObject *result;
if (!PyArg_ParseTuple(args, "O|O", &key, &default_obj))
return NULL;
result = lru_subscript(self, key);
PyErr_Clear();
if (result)
return result;
if (!default_obj)
default_obj = Py_None;
if (lru_ass_sub(self, key, default_obj) != 0)
return NULL;
Py_INCREF(default_obj);
return default_obj;
}
static PyObject *
LRU_pop(LRU *self, PyObject *args, PyObject *keywds)
{
PyObject *key;
PyObject *default_obj = NULL;
PyObject *result;
static char *kwlist[] = {"key", "default", NULL};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|O", kwlist, &key, &default_obj))
return NULL;
/* Trying to access the item by key. */
result = lru_subscript(self, key);
if (result)
/* result != NULL, delete it from dict by key */
lru_ass_sub(self, key, NULL);
else if (default_obj) {
/* result == NULL, i.e. key missing, and default_obj given */
PyErr_Clear();
Py_INCREF(default_obj);
result = default_obj;
}
/* Otherwise (key missing, and default_obj not given [i.e. == NULL]), the
* call to lru_subscript (at the location marked by "Trying to access the
* item by key" in the comments) has already generated the appropriate
* exception. */
return result;
}
static PyObject *
LRU_peek_first_item(LRU *self)
{
if (self->first)
return Py_BuildValue("OO", self->first->key, self->first->value);
Py_RETURN_NONE;
}
static PyObject *
LRU_peek_last_item(LRU *self)
{
if (self->last)
return Py_BuildValue("OO", self->last->key, self->last->value);
Py_RETURN_NONE;
}
static PyObject *
LRU_popitem(LRU *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"least_recent", NULL};
int pop_least_recent = 1;
PyObject *result;
#if PY_MAJOR_VERSION >= 3
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|p", kwlist, &pop_least_recent))
return NULL;
#else
{
PyObject *arg_ob = Py_True;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, &arg_ob))
return NULL;
pop_least_recent = PyObject_IsTrue(arg_ob);
if (pop_least_recent == -1)
return NULL;
}
#endif
if (pop_least_recent)
result = LRU_peek_last_item(self);
else
result = LRU_peek_first_item(self);
if (result == Py_None) {
PyErr_SetString(PyExc_KeyError, "popitem(): LRU dict is empty");
return NULL;
}
lru_ass_sub(self, PyTuple_GET_ITEM(result, 0), NULL);
Py_INCREF(result);
return result;
}
static PyObject *
LRU_keys(LRU *self) {
return collect(self, get_key);
}
static PyObject *
get_value(Node *node)
{
Py_INCREF(node->value);
return node->value;
}
static PyObject *
LRU_values(LRU *self)
{
return collect(self, get_value);
}
static PyObject *
LRU_set_callback(LRU *self, PyObject *args)
{
return set_callback(self, args);
}
static PyObject *
get_item(Node *node)
{
PyObject *tuple = PyTuple_New(2);
Py_INCREF(node->key);
PyTuple_SET_ITEM(tuple, 0, node->key);
Py_INCREF(node->value);
PyTuple_SET_ITEM(tuple, 1, node->value);
return tuple;
}
static PyObject *
LRU_items(LRU *self)
{
return collect(self, get_item);
}
static PyObject *
LRU_set_size(LRU *self, PyObject *args, PyObject *kwds)
{
Py_ssize_t newSize;
if (!PyArg_ParseTuple(args, "n", &newSize)) {
return NULL;
}
if (newSize <= 0) {
PyErr_SetString(PyExc_ValueError, "Size should be a positive number");
return NULL;
}
while (lru_length(self) > newSize) {
lru_delete_last(self);
}
self->size = newSize;
Py_RETURN_NONE;
}
static PyObject *
LRU_clear(LRU *self)
{
Node *c = self->first;
while (c) {
Node* n = c;
c = c->next;
lru_remove_node(self, n);
}
PyDict_Clear(self->dict);
self->hits = 0;
self->misses = 0;
Py_RETURN_NONE;
}
static PyObject *
LRU_get_size(LRU *self)
{
return Py_BuildValue("i", self->size);
}
static PyObject *
LRU_get_stats(LRU *self)
{
return Py_BuildValue("nn", self->hits, self->misses);
}
/* Hack to implement "key in lru" */
static PySequenceMethods lru_as_sequence = {
0, /* sq_length */
0, /* sq_concat */
0, /* sq_repeat */
0, /* sq_item */
0, /* sq_slice */
0, /* sq_ass_item */
0, /* sq_ass_slice */
(objobjproc) LRU_seq_contains, /* sq_contains */
0, /* sq_inplace_concat */
0, /* sq_inplace_repeat */
};
static PyMethodDef LRU_methods[] = {
{"__contains__", (PyCFunction)LRU_contains_key, METH_O | METH_COEXIST,
PyDoc_STR("L.__contains__(key) -> Check if key is there in L")},
{"keys", (PyCFunction)LRU_keys, METH_NOARGS,
PyDoc_STR("L.keys() -> list of L's keys in MRU order")},
{"values", (PyCFunction)LRU_values, METH_NOARGS,
PyDoc_STR("L.values() -> list of L's values in MRU order")},
{"items", (PyCFunction)LRU_items, METH_NOARGS,
PyDoc_STR("L.items() -> list of L's items (key,value) in MRU order")},
{"has_key", (PyCFunction)LRU_contains, METH_VARARGS,
PyDoc_STR("L.has_key(key) -> Check if key is there in L")},
{"get", (PyCFunction)LRU_get, METH_VARARGS | METH_KEYWORDS,
PyDoc_STR("L.get(key, default=None) -> If L has key return its value, otherwise default")},
{"setdefault", (PyCFunction)LRU_setdefault, METH_VARARGS,
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")},
{"pop", (PyCFunction)LRU_pop, METH_VARARGS | METH_KEYWORDS,
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.")},
{"popitem", (PyCFunction)LRU_popitem, METH_VARARGS | METH_KEYWORDS,
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.")},
{"set_size", (PyCFunction)LRU_set_size, METH_VARARGS,
PyDoc_STR("L.set_size() -> set size of LRU")},
{"get_size", (PyCFunction)LRU_get_size, METH_NOARGS,
PyDoc_STR("L.get_size() -> get size of LRU")},
{"clear", (PyCFunction)LRU_clear, METH_NOARGS,
PyDoc_STR("L.clear() -> clear LRU")},
{"get_stats", (PyCFunction)LRU_get_stats, METH_NOARGS,
PyDoc_STR("L.get_stats() -> returns a tuple with cache hits and misses")},
{"peek_first_item", (PyCFunction)LRU_peek_first_item, METH_NOARGS,
PyDoc_STR("L.peek_first_item() -> returns the MRU item (key,value) without changing key order")},
{"peek_last_item", (PyCFunction)LRU_peek_last_item, METH_NOARGS,
PyDoc_STR("L.peek_last_item() -> returns the LRU item (key,value) without changing key order")},
{"update", (PyCFunction)LRU_update, METH_VARARGS | METH_KEYWORDS,
PyDoc_STR("L.update() -> update value for key in LRU")},
{"set_callback", (PyCFunction)LRU_set_callback, METH_VARARGS,
PyDoc_STR("L.set_callback(callback) -> set a callback to call when an item is evicted.")},
{NULL, NULL},
};
static PyObject*
LRU_repr(LRU* self)
{
return PyObject_Repr(self->dict);
}
static int
LRU_init(LRU *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"size", "callback", NULL};
PyObject *callback = NULL;
self->callback = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "n|O", kwlist, &self->size, &callback)) {
return -1;
}
if (callback && callback != Py_None) {
if (!PyCallable_Check(callback)) {
PyErr_SetString(PyExc_TypeError, "parameter must be callable");
return -1;
}
Py_XINCREF(callback);
self->callback = callback;
}
if ((Py_ssize_t)self->size <= 0) {
PyErr_SetString(PyExc_ValueError, "Size should be a positive number");
return -1;
}
self->dict = PyDict_New();
self->first = self->last = NULL;
self->hits = 0;
self->misses = 0;
return 0;
}
static void
LRU_dealloc(LRU *self)
{
if (self->dict) {
LRU_clear(self);
Py_CLEAR(self->dict);
Py_XDECREF(self->callback);
}
Py_TYPE(self)->tp_free((PyObject *)self);
}
PyDoc_STRVAR(lru_doc,
"LRU(size, callback=None) -> new LRU dict that can store up to size elements\n"
"An LRU dict behaves like a standard dict, except that it stores only fixed\n"
"set of elements. Once the size overflows, it evicts least recently used\n"
"items. If a callback is set it will call the callback with the evicted key\n"
" and item.\n\n"
"Eg:\n"
">>> l = LRU(3)\n"
">>> for i in range(5):\n"
">>> l[i] = str(i)\n"
">>> l.keys()\n"
"[2,3,4]\n\n"
"Note: An LRU(n) can be thought of as a dict that will have the most\n"
"recently accessed n items.\n");
static PyTypeObject LRUType = {
PyVarObject_HEAD_INIT(NULL, 0)
"_lru.LRU", /* tp_name */
sizeof(LRU), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)LRU_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
(reprfunc)LRU_repr, /* tp_repr */
0, /* tp_as_number */
&lru_as_sequence, /* tp_as_sequence */
&LRU_as_mapping, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
lru_doc, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
LRU_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)LRU_init, /* tp_init */
0, /* tp_alloc */
PyType_GenericNew, /* tp_new */
};
#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"_lru", /* m_name */
lru_doc, /* m_doc */
-1, /* m_size */
NULL, /* m_methods */
NULL, /* m_reload */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL, /* m_free */
};
#endif
static PyObject *
moduleinit(void)
{
PyObject *m;
LRUType.tp_base = &PyBaseObject_Type;
if (PyType_Ready(&NodeType) < 0)
return NULL;
if (PyType_Ready(&LRUType) < 0)
return NULL;
#if PY_MAJOR_VERSION >= 3
m = PyModule_Create(&moduledef);
#else
m = Py_InitModule3("_lru", NULL, lru_doc);
#endif
if (m == NULL)
return NULL;
Py_INCREF(&NodeType);
Py_INCREF(&LRUType);
PyModule_AddObject(m, "LRU", (PyObject *) &LRUType);
return m;
}
#if PY_MAJOR_VERSION < 3
PyMODINIT_FUNC
init_lru(void)
{
moduleinit();
}
#else
PyMODINIT_FUNC
PyInit__lru(void)
{
return moduleinit();
}
#endif
================================================
FILE: src/lru/py.typed
================================================
================================================
FILE: test/test_lru.py
================================================
import gc
import random
import sys
import unittest
from lru import LRU
SIZES = [1, 2, 10, 1000]
# Only available on debug python builds.
gettotalrefcount = getattr(sys, 'gettotalrefcount', lambda: 0)
class TestLRU(unittest.TestCase):
def setUp(self):
gc.collect()
self._before_count = gettotalrefcount()
def tearDown(self):
after_count = gettotalrefcount()
self.assertEqual(self._before_count, after_count)
def _check_kvi(self, valid_keys, l):
valid_keys = list(valid_keys)
valid_vals = list(map(str, valid_keys))
self.assertEqual(valid_keys, l.keys())
self.assertEqual(valid_vals, l.values())
self.assertEqual(list(zip(valid_keys, valid_vals)), l.items())
def test_invalid_size(self):
self.assertRaises(ValueError, LRU, -1)
self.assertRaises(ValueError, LRU, 0)
def test_empty(self):
l = LRU(1)
self.assertEqual([], l.keys())
self.assertEqual([], l.values())
def test_add_within_size(self):
for size in SIZES:
l = LRU(size)
for i in range(size):
l[i] = str(i)
self._check_kvi(range(size - 1, -1, -1), l)
def test_delete_multiple_within_size(self):
for size in SIZES:
l = LRU(size)
for i in range(size):
l[i] = str(i)
for i in range(0, size, 2):
del l[i]
self._check_kvi(range(size - 1, 0, -2), l)
for i in range(0, size, 2):
with self.assertRaises(KeyError):
l[i]
def test_delete_multiple(self):
for size in SIZES:
l = LRU(size)
n = size * 2
for i in range(n):
l[i] = str(i)
for i in range(size, n, 2):
del l[i]
self._check_kvi(range(n - 1, size, -2), l)
for i in range(0, size):
with self.assertRaises(KeyError):
l[i]
for i in range(size, n, 2):
with self.assertRaises(KeyError):
l[i]
def test_add_multiple(self):
for size in SIZES:
l = LRU(size)
for i in range(size):
l[i] = str(i)
l[size] = str(size)
self._check_kvi(range(size, 0, -1), l)
def test_access_within_size(self):
for size in SIZES:
l = LRU(size)
for i in range(size):
l[i] = str(i)
for i in range(size):
self.assertEqual(l[i], str(i))
self.assertEqual(l.get(i, None), str(i))
def test_contains(self):
for size in SIZES:
l = LRU(size)
for i in range(size):
l[i] = str(i)
for i in range(size):
self.assertTrue(i in l)
def test_access(self):
for size in SIZES:
l = LRU(size)
n = size * 2
for i in range(n):
l[i] = str(i)
self._check_kvi(range(n - 1, size - 1, -1), l)
for i in range(size, n):
self.assertEqual(l[i], str(i))
self.assertEqual(l.get(i, None), str(i))
def test_update(self):
l = LRU(2)
l['a'] = 1
self.assertEqual(l['a'], 1)
l.update(a=2)
self.assertEqual(l['a'], 2)
l['b'] = 2
self.assertEqual(l['b'], 2)
l.update(b=3)
self.assertEqual(('b', 3), l.peek_first_item())
self.assertEqual(l['a'], 2)
self.assertEqual(l['b'], 3)
l.update({'a': 1, 'b': 2})
self.assertEqual(('b', 2), l.peek_first_item())
self.assertEqual(l['a'], 1)
self.assertEqual(l['b'], 2)
l.update()
self.assertEqual(('b', 2), l.peek_first_item())
l.update(a=2)
self.assertEqual(('a', 2), l.peek_first_item())
def test_peek_first_item(self):
l = LRU(2)
self.assertEqual(None, l.peek_first_item())
l[1] = '1'
l[2] = '2'
self.assertEqual((2, '2'), l.peek_first_item())
def test_peek_last_item(self):
l = LRU(2)
self.assertEqual(None, l.peek_last_item())
l[1] = '1'
l[2] = '2'
self.assertEqual((1, '1'), l.peek_last_item())
def test_overwrite(self):
l = LRU(1)
l[1] = '2'
l[1] = '1'
self.assertEqual('1', l[1])
self._check_kvi([1], l)
def test_has_key(self):
for size in SIZES:
l = LRU(size)
for i in range(2 * size):
l[i] = str(i)
self.assertTrue(l.has_key(i))
for i in range(size, 2 * size):
self.assertTrue(l.has_key(i))
for i in range(size):
self.assertFalse(l.has_key(i))
def test_capacity_get(self):
for size in SIZES:
l = LRU(size)
self.assertTrue(size == l.get_size())
def test_capacity_set(self):
for size in SIZES:
l = LRU(size)
for i in range(size + 5):
l[i] = str(i)
l.set_size(size + 10)
self.assertTrue(size + 10 == l.get_size())
self.assertTrue(len(l) == size)
for i in range(size + 20):
l[i] = str(i)
self.assertTrue(len(l) == size + 10)
l.set_size(size + 10 - 1)
self.assertTrue(len(l) == size + 10 - 1)
def test_unhashable(self):
l = LRU(1)
self.assertRaises(TypeError, lambda: l[{'a': 'b'}])
with self.assertRaises(TypeError):
l[['1']] = '2'
with self.assertRaises(TypeError):
del l[{'1': '1'}]
def test_clear(self):
for size in SIZES:
l = LRU(size)
for i in range(size + 5):
l[i] = str(i)
l.clear()
for i in range(size):
l[i] = str(i)
for i in range(size):
_ = l[random.randint(0, size - 1)]
l.clear()
self.assertTrue(len(l) == 0)
def test_get_and_del(self):
l = LRU(2)
l[1] = '1'
self.assertEqual('1', l.get(1))
self.assertEqual('1', l.get(2, '1'))
self.assertIsNone(l.get(2))
self.assertEqual('1', l[1])
self.assertRaises(KeyError, lambda: l['2'])
with self.assertRaises(KeyError):
del l['2']
def test_setdefault(self):
l = LRU(2)
l[1] = '1'
val = l.setdefault(1)
self.assertEqual('1', val)
self.assertEqual((1, 0), l.get_stats())
val = l.setdefault(2, '2')
self.assertEqual('2', val)
self.assertEqual((1, 1), l.get_stats())
self.assertEqual(val, l[2])
l.clear()
val = 'long string' * 512
l.setdefault(1, val)
l[2] = '2'
l[3] = '3'
self.assertTrue(val)
def test_pop(self):
l = LRU(2)
v = '2' * 4096
l[1] = '1'
l[2] = v
val = l.pop(1)
self.assertEqual('1', val)
self.assertEqual((1, 0), l.get_stats())
val = l.pop(2, 'not used')
self.assertEqual(v, val)
del val
self.assertTrue(v)
self.assertEqual((2, 0), l.get_stats())
val = l.pop(3, '3' * 4096)
self.assertEqual('3' * 4096, val)
self.assertEqual((2, 1), l.get_stats())
self.assertEqual(0, len(l))
with self.assertRaises(KeyError) as ke:
l.pop(4)
self.assertEqual(4, ke.args[0]) # type: ignore
self.assertEqual((2, 2), l.get_stats())
self.assertEqual(0, len(l))
with self.assertRaises(TypeError):
l.pop() # type: ignore
def test_popitem(self):
l = LRU(3)
l[1] = '1'
l[2] = '2'
l[3] = '3'
k, v = l.popitem()
self.assertEqual((1, '1'), (k, v))
k, v = l.popitem(least_recent=False)
self.assertEqual((3, '3'), (k, v))
self.assertEqual((2, '2'), l.popitem(True))
with self.assertRaises(KeyError) as ke:
l.popitem()
self.assertEqual('popitem(): LRU dict is empty', ke.args[0]) # type: ignore
self.assertEqual((0, 0), l.get_stats())
def test_stats(self):
for size in SIZES:
l = LRU(size)
for i in range(size):
l[i] = str(i)
self.assertTrue(l.get_stats() == (0, 0))
val = l[0]
self.assertTrue(l.get_stats() == (1, 0))
val = l.get(0, None)
self.assertTrue(l.get_stats() == (2, 0))
val = l.get(-1, None)
self.assertTrue(l.get_stats() == (2, 1))
try:
val = l[-1]
except:
pass
self.assertTrue(l.get_stats() == (2, 2))
l.clear()
self.assertTrue(len(l) == 0)
self.assertTrue(l.get_stats() == (0, 0))
def test_lru(self):
l = LRU(1)
l['a'] = 1
l['a']
self.assertEqual(l.keys(), ['a'])
l['b'] = 2
self.assertEqual(l.keys(), ['b'])
l = LRU(2)
l['a'] = 1
l['b'] = 2
self.assertEqual(len(l), 2)
l['a'] # Testing the first one
l['c'] = 3
self.assertEqual(sorted(l.keys()), ['a', 'c'])
l['c']
self.assertEqual(sorted(l.keys()), ['a', 'c'])
l = LRU(3)
l['a'] = 1
l['b'] = 2
l['c'] = 3
self.assertEqual(len(l), 3)
l['b'] # Testing the middle one
l['d'] = 4
self.assertEqual(sorted(l.keys()), ['b', 'c', 'd'])
l['d'] # Testing the last one
self.assertEqual(sorted(l.keys()), ['b', 'c', 'd'])
l['e'] = 5
self.assertEqual(sorted(l.keys()), ['b', 'd', 'e'])
def test_callback(self):
counter = [0]
first_key = 'a'
first_value = 1
def callback(key, value):
self.assertEqual(key, first_key)
self.assertEqual(value, first_value)
counter[0] += 1
l = LRU(1, callback=callback)
l[first_key] = first_value
l['b'] = 1 # test calling the callback
self.assertEqual(counter[0], 1)
self.assertEqual(l.keys(), ['b'])
l['b'] = 2 # doesn't call callback
self.assertEqual(counter[0], 1)
self.assertEqual(l.keys(), ['b'])
self.assertEqual(l.values(), [2])
l = LRU(1, callback=callback)
l[first_key] = first_value
l.set_callback(None)
l['c'] = 1 # doesn't call callback
self.assertEqual(counter[0], 1)
self.assertEqual(l.keys(), ['c'])
l.set_callback(callback)
del l['c'] # doesn't call callback
self.assertEqual(counter[0], 1)
self.assertEqual(l.keys(), [])
l = LRU(2, callback=callback)
l['a'] = 1 # test calling the callback
l['b'] = 2 # test calling the callback
self.assertEqual(counter[0], 1)
self.assertEqual(l.keys(), ['b', 'a'])
l.set_size(1)
self.assertEqual(counter[0], 2) # callback invoked
self.assertEqual(l.keys(), ['b'])
def test_subclassing(self):
class LRUChild(LRU):
...
l = LRUChild(1)
l['a'] = 1
self.assertEqual(l['a'], 1)
self.assertEqual(l.keys(), ['a'])
self.assertEqual(l.values(), [1])
if __name__ == '__main__':
unittest.main()
gitextract_prc6blzv/
├── .github/
│ └── workflows/
│ ├── build-and-deploy.yml
│ └── tests.yml
├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.rst
├── pyproject.toml
├── setup.py
├── src/
│ └── lru/
│ ├── __init__.py
│ ├── __init__.pyi
│ ├── _lru.c
│ └── py.typed
└── test/
└── test_lru.py
SYMBOL INDEX (103 symbols across 3 files)
FILE: src/lru/__init__.pyi
class __SupportsKeysAndGetItem (line 18) | class __SupportsKeysAndGetItem(Protocol[_KT, _VT_co]):
method keys (line 19) | def keys(self) -> Iterable[_KT]: ...
method __getitem__ (line 20) | def __getitem__(self, __key: _KT) -> _VT_co: ...
class LRU (line 23) | class LRU(Generic[_KT, _VT]):
method __init__ (line 25) | def __init__(self, size: int) -> None: ...
method __init__ (line 27) | def __init__(self, size: int, callback: Callable[[_KT, _VT], Any]) -> ...
method clear (line 28) | def clear(self) -> None: ...
method get (line 30) | def get(self, key: _KT) -> _VT | None: ...
method get (line 32) | def get(self, key: _KT, instead: _VT | _T) -> _VT | _T: ...
method get_size (line 33) | def get_size(self) -> int: ...
method has_key (line 34) | def has_key(self, key: _KT) -> bool: ...
method keys (line 35) | def keys(self) -> list[_KT]: ...
method values (line 36) | def values(self) -> list[_VT]: ...
method items (line 37) | def items(self) -> list[tuple[_KT, _VT]]: ...
method peek_first_item (line 38) | def peek_first_item(self) -> tuple[_KT, _VT] | None: ...
method peek_last_item (line 39) | def peek_last_item(self) -> tuple[_KT, _VT] | None: ...
method pop (line 41) | def pop(self, key: _KT) -> _VT | None: ...
method pop (line 43) | def pop(self, key: _KT, default: _VT | _T) -> _VT | _T: ...
method popitem (line 44) | def popitem(self, least_recent: bool = ...) -> tuple[_KT, _VT]: ...
method setdefault (line 46) | def setdefault(self: LRU[_KT, _T | None], key: _KT) -> _T | None: ...
method setdefault (line 48) | def setdefault(self, key: _KT, default: _VT) -> _VT: ...
method set_callback (line 49) | def set_callback(self, callback: Callable[[_KT, _VT], Any] | None) -> ...
method set_size (line 50) | def set_size(self, size: int) -> None: ...
method update (line 52) | def update(self, __m: __SupportsKeysAndGetItem[_KT, _VT], **kwargs: _V...
method update (line 54) | def update(self, __m: Iterable[tuple[_KT, _VT]], **kwargs: _VT) -> Non...
method update (line 56) | def update(self, **kwargs: _VT) -> None: ...
method get_stats (line 57) | def get_stats(self) -> tuple[int, int]: ...
method __contains__ (line 58) | def __contains__(self, __o: Any) -> bool: ...
method __delitem__ (line 59) | def __delitem__(self, key: _KT) -> None: ...
method __getitem__ (line 60) | def __getitem__(self, item: _KT) -> _VT: ...
method __len__ (line 61) | def __len__(self) -> int: ...
method __repr__ (line 62) | def __repr__(self) -> str: ...
method __setitem__ (line 63) | def __setitem__(self, key: _KT, value: _VT) -> None: ...
FILE: src/lru/_lru.c
type Node (line 57) | typedef struct _Node {
function node_dealloc (line 65) | static void
function PyObject (line 75) | static PyObject*
type LRU (line 122) | typedef struct {
function PyObject (line 134) | static PyObject *
function lru_remove_node (line 157) | static void
function lru_add_node_at_head (line 175) | static void
function lru_delete_last (line 191) | static void
function Py_ssize_t (line 214) | static inline Py_ssize_t
function PyObject (line 220) | static PyObject *
function PyObject (line 228) | static PyObject *
function LRU_seq_contains (line 237) | static int
function PyObject (line 243) | static PyObject *
function PyObject (line 266) | static PyObject *
function lru_ass_sub (line 289) | static int
function PyObject (line 342) | static PyObject *
function PyObject (line 362) | static PyObject *
function PyObject (line 369) | static PyObject *
function PyObject (line 391) | static PyObject *
function PyObject (line 416) | static PyObject *
function PyObject (line 447) | static PyObject *
function PyObject (line 455) | static PyObject *
function PyObject (line 463) | static PyObject *
function PyObject (line 496) | static PyObject *
function PyObject (line 501) | static PyObject *
function PyObject (line 508) | static PyObject *
function PyObject (line 514) | static PyObject *
function PyObject (line 520) | static PyObject *
function PyObject (line 531) | static PyObject *
function PyObject (line 537) | static PyObject *
function PyObject (line 555) | static PyObject *
function PyObject (line 573) | static PyObject *
function PyObject (line 579) | static PyObject *
function PyObject (line 638) | static PyObject*
function LRU_init (line 644) | static int
function LRU_dealloc (line 674) | static void
type PyModuleDef (line 742) | struct PyModuleDef
function PyObject (line 755) | static PyObject *
function PyMODINIT_FUNC (line 785) | PyMODINIT_FUNC
function PyMODINIT_FUNC (line 791) | PyMODINIT_FUNC
FILE: test/test_lru.py
class TestLRU (line 13) | class TestLRU(unittest.TestCase):
method setUp (line 15) | def setUp(self):
method tearDown (line 19) | def tearDown(self):
method _check_kvi (line 23) | def _check_kvi(self, valid_keys, l):
method test_invalid_size (line 30) | def test_invalid_size(self):
method test_empty (line 34) | def test_empty(self):
method test_add_within_size (line 39) | def test_add_within_size(self):
method test_delete_multiple_within_size (line 46) | def test_delete_multiple_within_size(self):
method test_delete_multiple (line 58) | def test_delete_multiple(self):
method test_add_multiple (line 74) | def test_add_multiple(self):
method test_access_within_size (line 82) | def test_access_within_size(self):
method test_contains (line 91) | def test_contains(self):
method test_access (line 99) | def test_access(self):
method test_update (line 110) | def test_update(self):
method test_peek_first_item (line 131) | def test_peek_first_item(self):
method test_peek_last_item (line 138) | def test_peek_last_item(self):
method test_overwrite (line 145) | def test_overwrite(self):
method test_has_key (line 152) | def test_has_key(self):
method test_capacity_get (line 163) | def test_capacity_get(self):
method test_capacity_set (line 168) | def test_capacity_set(self):
method test_unhashable (line 182) | def test_unhashable(self):
method test_clear (line 190) | def test_clear(self):
method test_get_and_del (line 203) | def test_get_and_del(self):
method test_setdefault (line 214) | def test_setdefault(self):
method test_pop (line 231) | def test_pop(self):
method test_popitem (line 256) | def test_popitem(self):
method test_stats (line 271) | def test_stats(self):
method test_lru (line 299) | def test_lru(self):
method test_callback (line 330) | def test_callback(self):
method test_subclassing (line 377) | def test_subclassing(self):
Condensed preview — 13 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (52K chars).
[
{
"path": ".github/workflows/build-and-deploy.yml",
"chars": 2743,
"preview": "name: Build and deploy\n\non: [push, pull_request]\n\njobs:\n build_wheels:\n name: Build wheels on ${{ matrix.os }}\n r"
},
{
"path": ".github/workflows/tests.yml",
"chars": 1646,
"preview": "name: Tests\n\non: [push, pull_request]\n\njobs:\n test:\n runs-on: ubuntu-22.04\n strategy:\n fail-fast: false\n "
},
{
"path": ".gitignore",
"chars": 1837,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": "LICENSE",
"chars": 1049,
"preview": "Copyright (c) Amit Dev R\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software "
},
{
"path": "MANIFEST.in",
"chars": 101,
"preview": "include LICENSE README.rst MANIFEST MANIFEST.in\ngraft src\nglobal-exclude *.pyc\nglobal-exclude *.cache"
},
{
"path": "README.rst",
"chars": 3676,
"preview": ".. image:: https://github.com/amitdev/lru-dict/actions/workflows/tests.yml/badge.svg\n :target: https://github.com/ami"
},
{
"path": "pyproject.toml",
"chars": 1269,
"preview": "[build-system]\nrequires = [\"setuptools==80.9.0\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"lru-dict\"\nve"
},
{
"path": "setup.py",
"chars": 233,
"preview": "from setuptools import setup, Extension\n\nextensions = [\n Extension(\"lru._lru\", [\"src/lru/_lru.c\"]),\n]\n\nargs = {\n \""
},
{
"path": "src/lru/__init__.py",
"chars": 62,
"preview": "from ._lru import LRU as LRU # noqa: F401\n\n__all__ = [\"LRU\"]\n"
},
{
"path": "src/lru/__init__.pyi",
"chars": 2182,
"preview": "from typing import (\n Any,\n Callable,\n Generic,\n Hashable,\n Iterable,\n TypeVar,\n overload,\n Prot"
},
{
"path": "src/lru/_lru.c",
"chars": 22932,
"preview": "#include <Python.h>\n\n/*\n * This is a simple implementation of LRU Dict that uses a Python dict and an associated doubly "
},
{
"path": "src/lru/py.typed",
"chars": 0,
"preview": ""
},
{
"path": "test/test_lru.py",
"chars": 11658,
"preview": "import gc\nimport random\nimport sys\nimport unittest\nfrom lru import LRU\n\nSIZES = [1, 2, 10, 1000]\n\n# Only available on de"
}
]
About this extraction
This page contains the full source code of the amitdev/lru-dict GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 13 files (48.2 KB), approximately 13.8k tokens, and a symbol index with 103 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.