Skip to content

Commit c0f71ae

Browse files
committed
feat: add .pyi stub validation, update stubs, clean up old config
- Rename input -> data, _InputType -> _DataType in .pyi stubs - Add __all__ and algorithms_guaranteed to .pyi - Add test_stubs_pyright.py (pyright validation) - Migrate mypy.ini -> pyproject.toml [tool.mypy] - Add pyright to CI test dependencies - Remove Pipfile, Pipfile.lock, typecheck.sh - Bump version to 3.8.0.dev7
1 parent 31f04fc commit c0f71ae

9 files changed

Lines changed: 148 additions & 140 deletions

File tree

.github/workflows/test.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ jobs:
3333
python-version: ${{ matrix.python-version }}
3434

3535
- name: Install dependencies
36-
run: python -m pip install -U pip 'setuptools>=45' pytest
36+
run: python -m pip install -U pip 'setuptools>=45' pytest pyright
3737

3838
- name: Run tests
3939
run: |
4040
python setup.py build_ext --inplace
4141
python -m unittest discover -vv tests
4242
./bench.sh
43+

Pipfile

Lines changed: 0 additions & 12 deletions
This file was deleted.

Pipfile.lock

Lines changed: 0 additions & 84 deletions
This file was deleted.

mypy.ini

Lines changed: 0 additions & 22 deletions
This file was deleted.

pyproject.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,21 @@ build-frontend = "build"
1212
[[tool.cibuildwheel.overrides]]
1313
select = "*-ios*"
1414
build-frontend = "build"
15+
16+
[tool.mypy]
17+
warn_unused_configs = true
18+
disallow_any_generics = true
19+
disallow_subclassing_any = true
20+
disallow_untyped_calls = true
21+
# disallow_untyped_defs = true
22+
disallow_incomplete_defs = true
23+
check_untyped_defs = true
24+
disallow_untyped_decorators = true
25+
no_implicit_optional = true
26+
warn_redundant_casts = true
27+
warn_unused_ignores = true
28+
warn_return_any = true
29+
implicit_reexport = false
30+
strict_equality = true
31+
pretty = true
32+
error_summary = false

tests/test_stubs_pyright.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""Validate the .pyi type stubs using pyright."""
2+
3+
import subprocess
4+
import tempfile
5+
import unittest
6+
from pathlib import Path
7+
8+
9+
class TestStubsPyright(unittest.TestCase):
10+
def _run_pyright(self, source: str) -> subprocess.CompletedProcess:
11+
"""Run pyright on a temporary file with the given source.
12+
13+
The file is placed under the repo root so pyright can discover the
14+
xxhash package and its .pyi stubs via pyproject.toml.
15+
"""
16+
repo_root = Path(__file__).resolve().parent.parent
17+
with tempfile.NamedTemporaryFile(
18+
mode="w",
19+
suffix=".py",
20+
prefix="__pyright_check_",
21+
dir=repo_root,
22+
delete=False,
23+
) as f:
24+
f.write(source)
25+
tmp_path = f.name
26+
27+
try:
28+
return subprocess.run(
29+
["pyright", "--project", str(repo_root), tmp_path],
30+
capture_output=True,
31+
text=True,
32+
cwd=repo_root,
33+
timeout=30,
34+
)
35+
finally:
36+
Path(tmp_path).unlink(missing_ok=True)
37+
38+
def test_valid_buffer_types(self):
39+
"""Valid buffer types should type-check without errors."""
40+
code = """\
41+
import xxhash
42+
43+
h1 = xxhash.xxh32(b"hello")
44+
h1.update(b"world")
45+
xxhash.xxh32_digest(b"hello")
46+
47+
h2 = xxhash.xxh32(bytearray(b"hello"))
48+
h2.update(bytearray(b"world"))
49+
50+
h3 = xxhash.xxh32(memoryview(b"hello"))
51+
h3.update(memoryview(b"world"))
52+
53+
h4 = xxhash.xxh32()
54+
h4.update(b"test")
55+
"""
56+
result = self._run_pyright(code)
57+
if result.returncode != 0:
58+
self.fail(
59+
f"pyright reported errors for valid buffer types:\n"
60+
f"{result.stdout}\n{result.stderr}"
61+
)
62+
63+
def test_str_is_rejected(self):
64+
"""str should be rejected (not a buffer type)."""
65+
code = """\
66+
import xxhash
67+
xxhash.xxh32("hello")
68+
"""
69+
result = self._run_pyright(code)
70+
if result.returncode == 0:
71+
self.fail("pyright did not reject str argument")
72+
73+
def test_int_is_rejected(self):
74+
"""int should be rejected (not a buffer type)."""
75+
code = """\
76+
import xxhash
77+
xxhash.xxh32(42)
78+
"""
79+
result = self._run_pyright(code)
80+
if result.returncode == 0:
81+
self.fail("pyright did not reject int argument")

typecheck.sh

Lines changed: 0 additions & 3 deletions
This file was deleted.

xxhash/__init__.pyi

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,50 @@
1-
import array
2-
from typing import Union
3-
from typing_extensions import final, Buffer
1+
from typing import Protocol, final
42

5-
# __buffer__ protocol makes this redundant on python 3.12+
6-
Buffer.register(array.ArrayType)
7-
_InputType = Union[str, Buffer]
3+
class _Buffer(Protocol):
4+
"""Objects that support the buffer protocol (PEP 688)."""
5+
def __buffer__(self, flags: int, /) -> memoryview: ...
6+
7+
_DataType = _Buffer
88

99
VERSION: str
1010
XXHASH_VERSION: str
1111
#: Deprecated, will be removed in the next major release
1212
VERSION_TUPLE: tuple[int, ...]
1313

1414
algorithms_available: set[str]
15+
algorithms_guaranteed: set[str]
16+
17+
__all__: list[str] = [
18+
"xxh32",
19+
"xxh32_digest",
20+
"xxh32_intdigest",
21+
"xxh32_hexdigest",
22+
"xxh64",
23+
"xxh64_digest",
24+
"xxh64_intdigest",
25+
"xxh64_hexdigest",
26+
"xxh3_64",
27+
"xxh3_64_digest",
28+
"xxh3_64_intdigest",
29+
"xxh3_64_hexdigest",
30+
"xxh3_128",
31+
"xxh3_128_digest",
32+
"xxh3_128_intdigest",
33+
"xxh3_128_hexdigest",
34+
"xxh128",
35+
"xxh128_digest",
36+
"xxh128_intdigest",
37+
"xxh128_hexdigest",
38+
"VERSION",
39+
"VERSION_TUPLE",
40+
"XXHASH_VERSION",
41+
"algorithms_available",
42+
"algorithms_guaranteed",
43+
]
1544

1645
class _Hasher:
17-
def __init__(self, input: _InputType = ..., seed: int = ...) -> None: ...
18-
def update(self, input: _InputType) -> None: ...
46+
def __init__(self, data: _DataType = ..., seed: int = ...) -> None: ...
47+
def update(self, data: _DataType) -> None: ...
1948
def digest(self) -> bytes: ...
2049
def hexdigest(self) -> str: ...
2150
def intdigest(self) -> int: ...
@@ -44,17 +73,17 @@ class xxh3_128(_Hasher): ...
4473
xxh64 = xxh3_64
4574
xxh128 = xxh3_128
4675

47-
def xxh32_digest(args: _InputType, seed: int = ...) -> bytes: ...
48-
def xxh32_hexdigest(args: _InputType, seed: int = ...) -> str: ...
49-
def xxh32_intdigest(args: _InputType, seed: int = ...) -> int: ...
76+
def xxh32_digest(data: _DataType, seed: int = ...) -> bytes: ...
77+
def xxh32_hexdigest(data: _DataType, seed: int = ...) -> str: ...
78+
def xxh32_intdigest(data: _DataType, seed: int = ...) -> int: ...
5079

51-
def xxh3_64_digest(args: _InputType, seed: int = ...) -> bytes: ...
52-
def xxh3_64_hexdigest(args: _InputType, seed: int = ...) -> str: ...
53-
def xxh3_64_intdigest(args: _InputType, seed: int = ...) -> int: ...
80+
def xxh3_64_digest(data: _DataType, seed: int = ...) -> bytes: ...
81+
def xxh3_64_hexdigest(data: _DataType, seed: int = ...) -> str: ...
82+
def xxh3_64_intdigest(data: _DataType, seed: int = ...) -> int: ...
5483

55-
def xxh3_128_digest(args: _InputType, seed: int = ...) -> bytes: ...
56-
def xxh3_128_hexdigest(args: _InputType, seed: int = ...) -> str: ...
57-
def xxh3_128_intdigest(args: _InputType, seed: int = ...) -> int: ...
84+
def xxh3_128_digest(data: _DataType, seed: int = ...) -> bytes: ...
85+
def xxh3_128_hexdigest(data: _DataType, seed: int = ...) -> str: ...
86+
def xxh3_128_intdigest(data: _DataType, seed: int = ...) -> int: ...
5887

5988
xxh64_digest = xxh3_64_digest
6089
xxh64_hexdigest = xxh3_64_hexdigest

xxhash/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
VERSION = "3.8.0.dev6"
1+
VERSION = "3.8.0.dev7"
22
#: Deprecated, will be removed in the next major release
33
VERSION_TUPLE = (3, 8, 0)

0 commit comments

Comments
 (0)