Skip to content

Commit c7a35a6

Browse files
authored
Merge pull request amitdev#58 from RF-Tar-Railt/feat/stubs
Feat: Add Python Stubs
2 parents e68d991 + 10273f2 commit c7a35a6

File tree

8 files changed

+257
-42
lines changed

8 files changed

+257
-42
lines changed

.gitignore

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,132 @@
1-
.DS_Store
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
pip-wheel-metadata/
24+
share/python-wheels/
25+
*.egg-info/
26+
.installed.cfg
27+
*.egg
28+
MANIFEST
29+
30+
# PyInstaller
31+
# Usually these files are written by a python script from a template
32+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
33+
*.manifest
34+
*.spec
35+
36+
# Installer logs
37+
pip-log.txt
38+
pip-delete-this-directory.txt
39+
40+
# Unit test / coverage reports
41+
htmlcov/
42+
.tox/
43+
.nox/
44+
.coverage
45+
.coverage.*
46+
.cache
47+
nosetests.xml
48+
coverage.xml
49+
*.cover
50+
*.py,cover
51+
.hypothesis/
52+
.pytest_cache/
53+
54+
# Translations
55+
*.mo
56+
*.pot
57+
58+
# Django stuff:
59+
*.log
60+
local_settings.py
61+
db.sqlite3
62+
db.sqlite3-journal
63+
64+
# Flask stuff:
65+
instance/
66+
.webassets-cache
67+
68+
# Scrapy stuff:
69+
.scrapy
70+
71+
# Sphinx documentation
72+
docs/_build/
73+
74+
# PyBuilder
75+
target/
76+
77+
# Jupyter Notebook
78+
.ipynb_checkpoints
79+
80+
# IPython
81+
profile_default/
82+
ipython_config.py
83+
84+
# pyenv
85+
.python-version
86+
87+
# pipenv
88+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
90+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
91+
# install all needed dependencies.
92+
#Pipfile.lock
93+
94+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
95+
__pypackages__/
96+
97+
# Celery stuff
98+
celerybeat-schedule
99+
celerybeat.pid
100+
101+
# SageMath parsed files
102+
*.sage.py
103+
104+
# Environments
105+
.env
106+
.venv
107+
env/
108+
venv/
109+
ENV/
110+
env.bak/
111+
venv.bak/
112+
113+
# Spyder project settings
114+
.spyderproject
115+
.spyproject
116+
117+
# Rope project settings
118+
.ropeproject
119+
120+
# mkdocs documentation
121+
/site
122+
123+
# mypy
124+
.mypy_cache/
125+
.dmypy.json
126+
dmypy.json
127+
128+
# Pyre type checker
129+
.pyre/
130+
131+
# pdm config
132+
.pdm-python

MANIFEST.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
include LICENSE README.rst MANIFEST MANIFEST.in
2+
graft src
3+
global-exclude *.pyc
4+
global-exclude *.cache

pyproject.toml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
[project]
2+
name = "lru-dict"
3+
version = "1.2.0"
4+
description = "An Dict like LRU container."
5+
authors = [
6+
{name = "Amit Dev"},
7+
]
8+
dependencies = []
9+
requires-python = ">=3.8"
10+
readme = "README.rst"
11+
license = {text = "MIT"}
12+
keywords = ["lru", "dict"]
13+
classifiers = [
14+
"Development Status :: 5 - Production/Stable",
15+
"Intended Audience :: Developers",
16+
"License :: OSI Approved :: MIT License",
17+
"Operating System :: OS Independent",
18+
"Operating System :: POSIX",
19+
"Programming Language :: C",
20+
"Programming Language :: Python :: 3",
21+
"Programming Language :: Python :: 3.8",
22+
"Programming Language :: Python :: 3.9",
23+
"Programming Language :: Python :: 3.10",
24+
"Programming Language :: Python :: 3.11",
25+
"Programming Language :: Python :: 3.12",
26+
"Programming Language :: Python :: Implementation :: CPython",
27+
"Topic :: Software Development :: Libraries :: Python Modules",
28+
]
29+
[project.urls]
30+
Homepage = "https://github.com/amitdev/lru-dict"
31+
[project.optional-dependencies]
32+
test = [
33+
"pytest",
34+
]
35+
36+
[build-system]
37+
requires = ["setuptools>=61", "wheel"]
38+
build-backend = "setuptools.build_meta"

setup.py

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,12 @@
11
from setuptools import setup, Extension
22

3-
module1 = Extension('lru',
4-
sources = ['lru.c'])
3+
extensions = [
4+
Extension("lru._lru", ["src/lru/_lru.c"]),
5+
]
56

6-
setup (name = 'lru-dict',
7-
version = '1.2.0',
8-
description = 'An Dict like LRU container.',
9-
long_description = open('README.rst').read(),
10-
long_description_content_type="text/x-rst",
11-
author='Amit Dev',
12-
url='https://github.com/amitdev/lru-dict',
13-
license='MIT',
14-
keywords='lru, dict',
15-
ext_modules = [module1],
16-
classifiers=[
17-
'Development Status :: 5 - Production/Stable',
18-
'Intended Audience :: Developers',
19-
'License :: OSI Approved :: MIT License',
20-
'Operating System :: OS Independent',
21-
'Operating System :: POSIX',
22-
'Programming Language :: C',
23-
'Programming Language :: Python :: 3',
24-
'Programming Language :: Python :: 3.8',
25-
'Programming Language :: Python :: 3.9',
26-
'Programming Language :: Python :: 3.10',
27-
'Programming Language :: Python :: 3.11',
28-
'Programming Language :: Python :: 3.12',
29-
'Programming Language :: Python :: Implementation :: CPython',
30-
'Topic :: Software Development :: Libraries :: Python Modules',
31-
],
32-
extras_require={
33-
'test': ['pytest'],
34-
},
35-
)
7+
args = {
8+
"include_package_data": True,
9+
"exclude_package_data": {"": ["*.c"]},
10+
}
11+
12+
setup(ext_modules=extensions, **args)

src/lru/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from ._lru import LRU as LRU # noqa: F401
2+
3+
__all__ = ["LRU"]

src/lru/__init__.pyi

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from typing import (
2+
Any,
3+
Callable,
4+
Generic,
5+
Hashable,
6+
Iterable,
7+
TypeVar,
8+
overload,
9+
Protocol
10+
)
11+
12+
_KT = TypeVar("_KT", bound=Hashable)
13+
_VT = TypeVar("_VT")
14+
_VT_co = TypeVar("_VT_co", covariant=True)
15+
_T = TypeVar("_T")
16+
17+
18+
class __SupportsKeysAndGetItem(Protocol[_KT, _VT_co]):
19+
def keys(self) -> Iterable[_KT]: ...
20+
def __getitem__(self, __key: _KT) -> _VT_co: ...
21+
22+
23+
class LRU(Generic[_KT, _VT]):
24+
@overload
25+
def __init__(self, size: int) -> None: ...
26+
@overload
27+
def __init__(self, size: int, callback: Callable[[_KT, _VT], Any]) -> None: ...
28+
def clear(self) -> None: ...
29+
@overload
30+
def get(self, key: _KT) -> _VT | None: ...
31+
@overload
32+
def get(self, key: _KT, instead: _VT | _T) -> _VT | _T: ...
33+
def get_size(self) -> int: ...
34+
def has_key(self, key: _KT) -> bool: ...
35+
def keys(self) -> list[_KT]: ...
36+
def values(self) -> list[_VT]: ...
37+
def items(self) -> list[tuple[_KT, _VT]]: ...
38+
def peek_first_item(self) -> tuple[_KT, _VT] | None: ...
39+
def peek_last_item(self) -> tuple[_KT, _VT] | None: ...
40+
@overload
41+
def pop(self, key: _KT) -> _VT | None: ...
42+
@overload
43+
def pop(self, key: _KT, default: _VT | _T) -> _VT | _T: ...
44+
def popitem(self, least_recent: bool = ...) -> tuple[_KT, _VT]: ...
45+
@overload
46+
def setdefault(self: LRU[_KT, _T | None], key: _KT) -> _T | None: ...
47+
@overload
48+
def setdefault(self, key: _KT, default: _VT) -> _VT: ...
49+
def set_callback(self, callback: Callable[[_KT, _VT], Any] | None) -> None: ...
50+
def set_size(self, size: int) -> None: ...
51+
@overload
52+
def update(self, __m: __SupportsKeysAndGetItem[_KT, _VT], **kwargs: _VT) -> None: ...
53+
@overload
54+
def update(self, __m: Iterable[tuple[_KT, _VT]], **kwargs: _VT) -> None: ...
55+
@overload
56+
def update(self, **kwargs: _VT) -> None: ...
57+
def get_stats(self) -> tuple[int, int]: ...
58+
def __contains__(self, __o: Any) -> bool: ...
59+
def __delitem__(self, key: _KT) -> None: ...
60+
def __getitem__(self, item: _KT) -> _VT: ...
61+
def __len__(self) -> int: ...
62+
def __repr__(self) -> str: ...
63+
def __setitem__(self, key: _KT, value: _VT) -> None: ...

lru.c renamed to src/lru/_lru.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ node_repr(Node* self)
8080

8181
static PyTypeObject NodeType = {
8282
PyVarObject_HEAD_INIT(NULL, 0)
83-
"lru.Node", /* tp_name */
83+
"_lru.Node", /* tp_name */
8484
sizeof(Node), /* tp_basicsize */
8585
0, /* tp_itemsize */
8686
(destructor)node_dealloc,/* tp_dealloc */
@@ -711,7 +711,7 @@ PyDoc_STRVAR(lru_doc,
711711

712712
static PyTypeObject LRUType = {
713713
PyVarObject_HEAD_INIT(NULL, 0)
714-
"lru.LRU", /* tp_name */
714+
"_lru.LRU", /* tp_name */
715715
sizeof(LRU), /* tp_basicsize */
716716
0, /* tp_itemsize */
717717
(destructor)LRU_dealloc, /* tp_dealloc */
@@ -753,7 +753,7 @@ static PyTypeObject LRUType = {
753753
#if PY_MAJOR_VERSION >= 3
754754
static struct PyModuleDef moduledef = {
755755
PyModuleDef_HEAD_INIT,
756-
"lru", /* m_name */
756+
"_lru", /* m_name */
757757
lru_doc, /* m_doc */
758758
-1, /* m_size */
759759
NULL, /* m_methods */
@@ -780,7 +780,7 @@ moduleinit(void)
780780
#if PY_MAJOR_VERSION >= 3
781781
m = PyModule_Create(&moduledef);
782782
#else
783-
m = Py_InitModule3("lru", NULL, lru_doc);
783+
m = Py_InitModule3("_lru", NULL, lru_doc);
784784
#endif
785785

786786
if (m == NULL)
@@ -795,13 +795,13 @@ moduleinit(void)
795795

796796
#if PY_MAJOR_VERSION < 3
797797
PyMODINIT_FUNC
798-
initlru(void)
798+
init_lru(void)
799799
{
800800
moduleinit();
801801
}
802802
#else
803803
PyMODINIT_FUNC
804-
PyInit_lru(void)
804+
PyInit__lru(void)
805805
{
806806
return moduleinit();
807807
}

test/test_lru.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,11 +247,11 @@ def test_pop(self):
247247
self.assertEqual(0, len(l))
248248
with self.assertRaises(KeyError) as ke:
249249
l.pop(4)
250-
self.assertEqual(4, ke.args[0])
250+
self.assertEqual(4, ke.args[0]) # type: ignore
251251
self.assertEqual((2, 2), l.get_stats())
252252
self.assertEqual(0, len(l))
253253
with self.assertRaises(TypeError):
254-
l.pop()
254+
l.pop() # type: ignore
255255

256256
def test_popitem(self):
257257
l = LRU(3)
@@ -265,7 +265,7 @@ def test_popitem(self):
265265
self.assertEqual((2, '2'), l.popitem(True))
266266
with self.assertRaises(KeyError) as ke:
267267
l.popitem()
268-
self.assertEqual('popitem(): LRU dict is empty', ke.args[0])
268+
self.assertEqual('popitem(): LRU dict is empty', ke.args[0]) # type: ignore
269269
self.assertEqual((0, 0), l.get_stats())
270270

271271
def test_stats(self):

0 commit comments

Comments
 (0)