diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 0bd3ccb..ffebef9 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -5,31 +5,43 @@ on: [push, pull_request] jobs: build_wheels: name: Build wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.runs-on }} strategy: matrix: - os: [ubuntu-22.04, windows-2022, macOS-11] + os: [linux, windows, macos, ios] + 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-latest steps: - uses: actions/checkout@v4 - name: Set up QEMU if: runner.os == 'Linux' - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 with: platforms: all # Used to host cibuildwheel - name: Build wheels - uses: pypa/cibuildwheel@v2.16.2 + uses: pypa/cibuildwheel@v3.0.0 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_TEST_REQUIRES: .[test] - CIBW_TEST_COMMAND: pytest {project} - # arm64 cannot be tested on a x86_64 CI runner - CIBW_TEST_SKIP: "*-macosx_arm64" - - uses: actions/upload-artifact@v3 + CIBW_ARCHS_IOS: arm64_iphoneos arm64_iphonesimulator x86_64_iphonesimulator + # 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: @@ -39,8 +51,9 @@ jobs: - uses: actions/checkout@v4 - name: Build sdist run: pipx run build --sdist - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: + name: artifacts-sdist path: dist/*.tar.gz pypi_upload: @@ -49,11 +62,12 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') steps: - - uses: actions/setup-python@v4 - - uses: actions/download-artifact@v3 + - uses: actions/setup-python@v5 + - uses: actions/download-artifact@v4 with: name: artifact path: dist + merge-multiple: true - name: Publish package uses: pypa/gh-action-pypi-publish@release/v1 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d697072..6d98324 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,47 +6,69 @@ jobs: test: runs-on: ubuntu-22.04 strategy: + fail-fast: false matrix: python: [ - "3.8", "3.9", "3.10", "3.11", "3.12", - "pypy-3.8", + "3.13", + "3.14", "pypy-3.9", "pypy-3.10", + "pypy-3.11", ] steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - - run: pip install .[test] - - run: pytest + 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.8", "3.9", "3.10", "3.11", "3.12", - "pypy-3.8", + "3.13", + "3.14", "pypy-3.9", "pypy-3.10", + "pypy-3.11", ] steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - - run: pip install . - - run: python -c "from lru import LRU" + 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" diff --git a/.gitignore b/.gitignore index ec6c1b9..80745a6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ parts/ sdist/ var/ wheels/ +wheelhouse/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ @@ -129,4 +130,4 @@ dmypy.json .pyre/ # pdm config -.pdm-python \ No newline at end of file +.pdm-python diff --git a/pyproject.toml b/pyproject.toml index 5fbb05b..2290e19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,28 +1,33 @@ +[build-system] +requires = ["setuptools==80.9.0"] +build-backend = "setuptools.build_meta" + [project] name = "lru-dict" -version = "1.3.0" +version = "1.4.1" description = "An Dict like LRU container." authors = [ {name = "Amit Dev"}, ] dependencies = [] -requires-python = ">=3.8" +requires-python = ">=3.9" readme = "README.rst" -license = {text = "MIT"} +license = "MIT" +license-files = ["LICENSE"] keywords = ["lru", "dict"] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Operating System :: POSIX", "Programming Language :: C", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "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", ] @@ -33,6 +38,8 @@ test = [ "pytest", ] -[build-system] -requires = ["setuptools>=61", "wheel"] -build-backend = "setuptools.build_meta" +[tool.cibuildwheel] +test-extras = ["test"] +test-sources = ["test"] +test-command = "python -m pytest -v" +xbuild-tools = [] diff --git a/src/lru/_lru.c b/src/lru/_lru.c index a908d9a..ea05b16 100644 --- a/src/lru/_lru.c +++ b/src/lru/_lru.c @@ -69,7 +69,7 @@ node_dealloc(Node* self) Py_DECREF(self->value); assert(self->prev == NULL); assert(self->next == NULL); - PyObject_Del((PyObject*)self); + Py_TYPE(self)->tp_free((PyObject *)self); } static PyObject* @@ -199,18 +199,19 @@ lru_delete_last(LRU *self) 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); } - - lru_remove_node(self, n); + else { + lru_remove_node(self, n); + } PUT_NODE(self->dict, n->key, NULL); } -static Py_ssize_t +static inline Py_ssize_t lru_length(LRU *self) { return PyDict_Size(self->dict); @@ -219,11 +220,9 @@ lru_length(LRU *self) static PyObject * LRU_contains_key(LRU *self, PyObject *key) { - if (PyDict_Contains(self->dict, key)) { + if (PyDict_Contains(self->dict, key)) Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } + Py_RETURN_FALSE; } static PyObject * @@ -280,9 +279,8 @@ LRU_get(LRU *self, PyObject *args, PyObject *keywds) if (result) return result; - if (!default_obj) { + if (!default_obj) Py_RETURN_NONE; - } Py_INCREF(default_obj); return default_obj; @@ -449,29 +447,17 @@ LRU_pop(LRU *self, PyObject *args, PyObject *keywds) static PyObject * LRU_peek_first_item(LRU *self) { - if (self->first) { - PyObject *tuple = PyTuple_New(2); - Py_INCREF(self->first->key); - PyTuple_SET_ITEM(tuple, 0, self->first->key); - Py_INCREF(self->first->value); - PyTuple_SET_ITEM(tuple, 1, self->first->value); - return tuple; - } - else Py_RETURN_NONE; + 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) { - PyObject *tuple = PyTuple_New(2); - Py_INCREF(self->last->key); - PyTuple_SET_ITEM(tuple, 0, self->last->key); - Py_INCREF(self->last->value); - PyTuple_SET_ITEM(tuple, 1, self->last->value); - return tuple; - } - else Py_RETURN_NONE; + if (self->last) + return Py_BuildValue("OO", self->last->key, self->last->value); + Py_RETURN_NONE; } static PyObject * @@ -690,10 +676,10 @@ LRU_dealloc(LRU *self) { if (self->dict) { LRU_clear(self); - Py_DECREF(self->dict); + Py_CLEAR(self->dict); Py_XDECREF(self->callback); } - PyObject_Del((PyObject*)self); + Py_TYPE(self)->tp_free((PyObject *)self); } PyDoc_STRVAR(lru_doc, @@ -731,7 +717,7 @@ static PyTypeObject LRUType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ lru_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ @@ -749,7 +735,7 @@ static PyTypeObject LRUType = { 0, /* tp_dictoffset */ (initproc)LRU_init, /* tp_init */ 0, /* tp_alloc */ - 0, /* tp_new */ + PyType_GenericNew, /* tp_new */ }; #if PY_MAJOR_VERSION >= 3 @@ -771,11 +757,11 @@ moduleinit(void) { PyObject *m; - NodeType.tp_new = PyType_GenericNew; + LRUType.tp_base = &PyBaseObject_Type; + if (PyType_Ready(&NodeType) < 0) return NULL; - LRUType.tp_new = PyType_GenericNew; if (PyType_Ready(&LRUType) < 0) return NULL; diff --git a/test/test_lru.py b/test/test_lru.py index 8a9e16e..43be6dd 100644 --- a/test/test_lru.py +++ b/test/test_lru.py @@ -374,6 +374,16 @@ def callback(key, value): 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()