From ba03600dff3ec95d0ed1b360f028956d6b4b4f81 Mon Sep 17 00:00:00 2001 From: RF-Tar-Railt <3165388245@qq.com> Date: Fri, 3 Nov 2023 12:03:40 +0800 Subject: [PATCH] Feat: Add Python stub --- .gitignore | 133 +++++++++++++++++++++++++++++++++++++++- MANIFEST.in | 3 + pyproject.toml | 39 ++++++++++++ setup.py | 41 +++---------- src/lru/__init__.py | 3 + src/lru/__init__.pyi | 63 +++++++++++++++++++ lru.c => src/lru/_lru.c | 8 +-- test/test_lru.py | 6 +- 8 files changed, 256 insertions(+), 40 deletions(-) create mode 100644 pyproject.toml create mode 100644 src/lru/__init__.py create mode 100644 src/lru/__init__.pyi rename lru.c => src/lru/_lru.c (99%) diff --git a/.gitignore b/.gitignore index e43b0f9..ec6c1b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,132 @@ -.DS_Store +# 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/ +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 \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 5f59047..34da6ea 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,4 @@ include LICENSE README.rst MANIFEST MANIFEST.in +graft src +global-exclude *.pyc +global-exclude *.cache \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9ef1d62 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,39 @@ +[project] +name = "lru-dict" +version = "1.2.0" +description = "An Dict like LRU container." +authors = [ + {name = "Amit Dev"}, +] +dependencies = [] +requires-python = ">=3.7" +readme = "README.rst" +license = {text = "MIT"} +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.7", + "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 :: Implementation :: CPython", + "Topic :: Software Development :: Libraries :: Python Modules", +] +[project.urls] +Homepage = "https://github.com/amitdev/lru-dict" +[project.optional-dependencies] +test = [ + "pytest", +] + +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py index 99c090c..f51b667 100644 --- a/setup.py +++ b/setup.py @@ -1,35 +1,12 @@ from setuptools import setup, Extension -module1 = Extension('lru', - sources = ['lru.c']) +extensions = [ + Extension("lru._lru", ["src/lru/_lru.c"]), +] -setup (name = 'lru-dict', - version = '1.2.0', - description = 'An Dict like LRU container.', - long_description = open('README.rst').read(), - long_description_content_type="text/x-rst", - author='Amit Dev', - url='https://github.com/amitdev/lru-dict', - license='MIT', - keywords='lru, dict', - ext_modules = [module1], - 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 :: Implementation :: CPython', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - extras_require={ - 'test': ['pytest'], - }, -) +args = { + "include_package_data": True, + "exclude_package_data": {"": ["*.c"]}, +} + +setup(ext_modules=extensions, **args) diff --git a/src/lru/__init__.py b/src/lru/__init__.py new file mode 100644 index 0000000..2fe1926 --- /dev/null +++ b/src/lru/__init__.py @@ -0,0 +1,3 @@ +from ._lru import LRU as LRU # noqa: F401 + +__all__ = ["LRU"] diff --git a/src/lru/__init__.pyi b/src/lru/__init__.pyi new file mode 100644 index 0000000..d284112 --- /dev/null +++ b/src/lru/__init__.pyi @@ -0,0 +1,63 @@ +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: ... diff --git a/lru.c b/src/lru/_lru.c similarity index 99% rename from lru.c rename to src/lru/_lru.c index 33c18ab..8f0f219 100644 --- a/lru.c +++ b/src/lru/_lru.c @@ -80,7 +80,7 @@ node_repr(Node* self) static PyTypeObject NodeType = { PyVarObject_HEAD_INIT(NULL, 0) - "lru.Node", /* tp_name */ + "_lru.Node", /* tp_name */ sizeof(Node), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)node_dealloc,/* tp_dealloc */ @@ -711,7 +711,7 @@ PyDoc_STRVAR(lru_doc, static PyTypeObject LRUType = { PyVarObject_HEAD_INIT(NULL, 0) - "lru.LRU", /* tp_name */ + "_lru.LRU", /* tp_name */ sizeof(LRU), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)LRU_dealloc, /* tp_dealloc */ @@ -753,7 +753,7 @@ static PyTypeObject LRUType = { #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, - "lru", /* m_name */ + "_lru", /* m_name */ lru_doc, /* m_doc */ -1, /* m_size */ NULL, /* m_methods */ @@ -780,7 +780,7 @@ moduleinit(void) #if PY_MAJOR_VERSION >= 3 m = PyModule_Create(&moduledef); #else - m = Py_InitModule3("lru", NULL, lru_doc); + m = Py_InitModule3("_lru", NULL, lru_doc); #endif if (m == NULL) diff --git a/test/test_lru.py b/test/test_lru.py index a385795..8a9e16e 100644 --- a/test/test_lru.py +++ b/test/test_lru.py @@ -247,11 +247,11 @@ def test_pop(self): self.assertEqual(0, len(l)) with self.assertRaises(KeyError) as ke: l.pop(4) - self.assertEqual(4, ke.args[0]) + 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() + l.pop() # type: ignore def test_popitem(self): l = LRU(3) @@ -265,7 +265,7 @@ def test_popitem(self): 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]) + self.assertEqual('popitem(): LRU dict is empty', ke.args[0]) # type: ignore self.assertEqual((0, 0), l.get_stats()) def test_stats(self):