diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 32aeae6..b4d140a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,10 +33,11 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: python -m pip install -U pip 'setuptools>=45' pytest + run: python -m pip install -U pip 'setuptools>=45' pytest pyright - name: Run tests run: | python setup.py build_ext --inplace python -m unittest discover -vv tests ./bench.sh + diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 76e254c..0000000 --- a/Pipfile +++ /dev/null @@ -1,12 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] - -[dev-packages] -mypy = "*" - -[requires] -python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 41c2c31..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,84 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "83fe814f67b2b1c52cb7d081ab85c1e9b530407e5c77e345af86192075ea0a82" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.9" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": {}, - "develop": { - "mypy": { - "hashes": [ - "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359", - "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507", - "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c", - "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f", - "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", - "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6", - "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383", - "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", - "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6", - "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca", - "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4", - "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574", - "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", - "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc", - "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee", - "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40", - "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da", - "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", - "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", - "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", - "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069", - "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72", - "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536", - "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", - "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a", - "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be", - "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438", - "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", - "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782", - "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea", - "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b", - "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==1.16.1" - }, - "mypy-extensions": { - "hashes": [ - "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", - "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" - ], - "markers": "python_version >= '3.8'", - "version": "==1.1.0" - }, - "pathspec": { - "hashes": [ - "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", - "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" - ], - "markers": "python_version >= '3.8'", - "version": "==0.12.1" - }, - "typing-extensions": { - "hashes": [ - "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", - "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af" - ], - "markers": "python_version >= '3.9'", - "version": "==4.14.0" - } - } -} diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index af1d79d..0000000 --- a/mypy.ini +++ /dev/null @@ -1,22 +0,0 @@ -[mypy] -warn_unused_configs = True -disallow_any_generics = True -disallow_subclassing_any = True -disallow_untyped_calls = True -# disallow_untyped_defs = True -disallow_incomplete_defs = True -check_untyped_defs = True -disallow_untyped_decorators = True -no_implicit_optional = True -warn_redundant_casts = True -warn_unused_ignores = True -warn_return_any = True -implicit_reexport = False -strict_equality = True - -# Options above stolen from -# mypy --help | grep -A 9 -- '--strict ' - -pretty = True -error_summary = False - diff --git a/pyproject.toml b/pyproject.toml index d02ad68..c31d3bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,3 +12,21 @@ build-frontend = "build" [[tool.cibuildwheel.overrides]] select = "*-ios*" build-frontend = "build" + +[tool.mypy] +warn_unused_configs = true +disallow_any_generics = true +disallow_subclassing_any = true +disallow_untyped_calls = true +# disallow_untyped_defs = true +disallow_incomplete_defs = true +check_untyped_defs = true +disallow_untyped_decorators = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_return_any = true +implicit_reexport = false +strict_equality = true +pretty = true +error_summary = false diff --git a/tests/test_stubs_pyright.py b/tests/test_stubs_pyright.py new file mode 100644 index 0000000..c13e7b4 --- /dev/null +++ b/tests/test_stubs_pyright.py @@ -0,0 +1,81 @@ +"""Validate the .pyi type stubs using pyright.""" + +import subprocess +import tempfile +import unittest +from pathlib import Path + + +class TestStubsPyright(unittest.TestCase): + def _run_pyright(self, source: str) -> subprocess.CompletedProcess: + """Run pyright on a temporary file with the given source. + + The file is placed under the repo root so pyright can discover the + xxhash package and its .pyi stubs via pyproject.toml. + """ + repo_root = Path(__file__).resolve().parent.parent + with tempfile.NamedTemporaryFile( + mode="w", + suffix=".py", + prefix="__pyright_check_", + dir=repo_root, + delete=False, + ) as f: + f.write(source) + tmp_path = f.name + + try: + return subprocess.run( + ["pyright", "--project", str(repo_root), tmp_path], + capture_output=True, + text=True, + cwd=repo_root, + timeout=30, + ) + finally: + Path(tmp_path).unlink(missing_ok=True) + + def test_valid_buffer_types(self): + """Valid buffer types should type-check without errors.""" + code = """\ +import xxhash + +h1 = xxhash.xxh32(b"hello") +h1.update(b"world") +xxhash.xxh32_digest(b"hello") + +h2 = xxhash.xxh32(bytearray(b"hello")) +h2.update(bytearray(b"world")) + +h3 = xxhash.xxh32(memoryview(b"hello")) +h3.update(memoryview(b"world")) + +h4 = xxhash.xxh32() +h4.update(b"test") +""" + result = self._run_pyright(code) + if result.returncode != 0: + self.fail( + f"pyright reported errors for valid buffer types:\n" + f"{result.stdout}\n{result.stderr}" + ) + + def test_str_is_rejected(self): + """str should be rejected (not a buffer type).""" + code = """\ +import xxhash +xxhash.xxh32("hello") +""" + result = self._run_pyright(code) + if result.returncode == 0: + self.fail("pyright did not reject str argument") + + def test_int_is_rejected(self): + """int should be rejected (not a buffer type).""" + code = """\ +import xxhash +xxhash.xxh32(42) +""" + result = self._run_pyright(code) + if result.returncode == 0: + self.fail("pyright did not reject int argument") diff --git a/typecheck.sh b/typecheck.sh deleted file mode 100644 index e3fde4b..0000000 --- a/typecheck.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -pipenv run python -m mypy tests/* diff --git a/xxhash/__init__.pyi b/xxhash/__init__.pyi index a566d08..a79fbf5 100644 --- a/xxhash/__init__.pyi +++ b/xxhash/__init__.pyi @@ -1,10 +1,10 @@ -import array -from typing import Union -from typing_extensions import final, Buffer +from typing import Protocol, final -# __buffer__ protocol makes this redundant on python 3.12+ -Buffer.register(array.ArrayType) -_InputType = Union[str, Buffer] +class _Buffer(Protocol): + """Objects that support the buffer protocol (PEP 688).""" + def __buffer__(self, flags: int, /) -> memoryview: ... + +_DataType = _Buffer VERSION: str XXHASH_VERSION: str @@ -12,10 +12,39 @@ XXHASH_VERSION: str VERSION_TUPLE: tuple[int, ...] algorithms_available: set[str] +algorithms_guaranteed: set[str] + +__all__: list[str] = [ + "xxh32", + "xxh32_digest", + "xxh32_intdigest", + "xxh32_hexdigest", + "xxh64", + "xxh64_digest", + "xxh64_intdigest", + "xxh64_hexdigest", + "xxh3_64", + "xxh3_64_digest", + "xxh3_64_intdigest", + "xxh3_64_hexdigest", + "xxh3_128", + "xxh3_128_digest", + "xxh3_128_intdigest", + "xxh3_128_hexdigest", + "xxh128", + "xxh128_digest", + "xxh128_intdigest", + "xxh128_hexdigest", + "VERSION", + "VERSION_TUPLE", + "XXHASH_VERSION", + "algorithms_available", + "algorithms_guaranteed", +] class _Hasher: - def __init__(self, input: _InputType = ..., seed: int = ...) -> None: ... - def update(self, input: _InputType) -> None: ... + def __init__(self, data: _DataType = ..., seed: int = ...) -> None: ... + def update(self, data: _DataType) -> None: ... def digest(self) -> bytes: ... def hexdigest(self) -> str: ... def intdigest(self) -> int: ... @@ -44,17 +73,17 @@ class xxh3_128(_Hasher): ... xxh64 = xxh3_64 xxh128 = xxh3_128 -def xxh32_digest(args: _InputType, seed: int = ...) -> bytes: ... -def xxh32_hexdigest(args: _InputType, seed: int = ...) -> str: ... -def xxh32_intdigest(args: _InputType, seed: int = ...) -> int: ... +def xxh32_digest(data: _DataType, seed: int = ...) -> bytes: ... +def xxh32_hexdigest(data: _DataType, seed: int = ...) -> str: ... +def xxh32_intdigest(data: _DataType, seed: int = ...) -> int: ... -def xxh3_64_digest(args: _InputType, seed: int = ...) -> bytes: ... -def xxh3_64_hexdigest(args: _InputType, seed: int = ...) -> str: ... -def xxh3_64_intdigest(args: _InputType, seed: int = ...) -> int: ... +def xxh3_64_digest(data: _DataType, seed: int = ...) -> bytes: ... +def xxh3_64_hexdigest(data: _DataType, seed: int = ...) -> str: ... +def xxh3_64_intdigest(data: _DataType, seed: int = ...) -> int: ... -def xxh3_128_digest(args: _InputType, seed: int = ...) -> bytes: ... -def xxh3_128_hexdigest(args: _InputType, seed: int = ...) -> str: ... -def xxh3_128_intdigest(args: _InputType, seed: int = ...) -> int: ... +def xxh3_128_digest(data: _DataType, seed: int = ...) -> bytes: ... +def xxh3_128_hexdigest(data: _DataType, seed: int = ...) -> str: ... +def xxh3_128_intdigest(data: _DataType, seed: int = ...) -> int: ... xxh64_digest = xxh3_64_digest xxh64_hexdigest = xxh3_64_hexdigest diff --git a/xxhash/version.py b/xxhash/version.py index 923f897..3c75579 100644 --- a/xxhash/version.py +++ b/xxhash/version.py @@ -1,3 +1,3 @@ -VERSION = "3.8.0.dev6" +VERSION = "3.8.0.dev7" #: Deprecated, will be removed in the next major release VERSION_TUPLE = (3, 8, 0)