Skip to content

feat: add .pyi stub validation with stubtest and pyright#151

Merged
ifduyue merged 1 commit into
masterfrom
next
May 16, 2026
Merged

feat: add .pyi stub validation with stubtest and pyright#151
ifduyue merged 1 commit into
masterfrom
next

Conversation

@ifduyue
Copy link
Copy Markdown
Owner

@ifduyue ifduyue commented May 15, 2026

Changes

  • Rename inputdata parameter in .pyi stubs
  • Rename _InputType_DataType (Buffer-only, str is rejected by C code)
  • Add __all__ to .pyi matching runtime exports
  • Add __init__.py missing algorithms_guaranteed to stubs
  • tests/test_stubs.py — stubtest validation (runtime vs .pyi conformance)
  • tests/test_stubs_pyright.py — pyright validation (type-level correctness)
  • Migrate mypy.inipyproject.toml [tool.mypy]
  • Remove Pipfile, Pipfile.lock, typecheck.sh (no longer needed)
  • Add mypy and pyright to CI test dependencies

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 15, 2026

Merging this PR will not alter performance

🎉 Hooray! pytest-codspeed just leveled up to 5.0.2!

A heads-up, this is a breaking change and it might affect your current performance baseline a bit. But here's the exciting part - it's packed with new, cool features and promises improved result stability 🥳!
Curious about what's new? Visit our releases page to delve into all the awesome details about this new version.

✅ 30 untouched benchmarks
⏩ 90 skipped benchmarks1


Comparing next (fcd50e7) with master (31f04fc)

Open in CodSpeed

Footnotes

  1. 90 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request modernizes the project's type-checking infrastructure by migrating mypy configuration to pyproject.toml and adding automated stub validation tests using stubtest and pyright. The type stubs in xxhash/init.pyi have been updated to utilize a custom buffer protocol and explicit exports. Feedback suggests using the tempfile module in tests to prevent potential race conditions and adding positional-only markers to various function signatures in the stubs to correctly match the underlying C extension's behavior.

Comment thread tests/test_stubs_pyright.py Outdated

def _run_pyright(self, source: str) -> subprocess.CompletedProcess:
"""Run pyright on a temporary file with the given source."""
tmp = Path(__file__).resolve().parent / "__pyright_check_tmp.py"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a fixed filename like __pyright_check_tmp.py in the test directory can lead to race conditions if tests are run in parallel and leaves clutter if the process is interrupted. It is better to use the tempfile module to create a unique temporary file.

Comment thread xxhash/__init__.pyi
Comment on lines +46 to +47
def __init__(self, data: _DataType = ..., seed: int = ...) -> None: ...
def update(self, data: _DataType) -> None: ...
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The xxhash C extension methods do not support keyword arguments. Adding the positional-only marker / ensures that type checkers will correctly flag invalid usage like xxh32(data=b'...'), which currently results in a TypeError at runtime.

Suggested change
def __init__(self, data: _DataType = ..., seed: int = ...) -> None: ...
def update(self, data: _DataType) -> None: ...
def __init__(self, data: _DataType = ..., seed: int = ..., /) -> None: ...
def update(self, data: _DataType, /) -> None: ...

Comment thread xxhash/__init__.pyi
Comment on lines +76 to +78
def xxh32_digest(data: _DataType, seed: int = ...) -> bytes: ...
def xxh32_hexdigest(data: _DataType, seed: int = ...) -> str: ...
def xxh32_intdigest(data: _DataType, seed: int = ...) -> int: ...
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These module-level functions are positional-only at runtime. Adding the / marker improves the accuracy of the stubs and prevents users from attempting to use keyword arguments that are not supported by the underlying C implementation.

Suggested change
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 xxh32_digest(data: _DataType, seed: int = ..., /) -> bytes: ...
def xxh32_hexdigest(data: _DataType, seed: int = ..., /) -> str: ...
def xxh32_intdigest(data: _DataType, seed: int = ..., /) -> int: ...

Comment thread xxhash/__init__.pyi
Comment on lines +80 to +82
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: ...
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These functions are positional-only at runtime. Adding the / marker ensures type-level correctness.

Suggested change
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_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: ...

Comment thread xxhash/__init__.pyi
Comment on lines +84 to +86
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: ...
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These functions are positional-only at runtime. Adding the / marker ensures type-level correctness.

Suggested change
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: ...
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: ...

@ifduyue
Copy link
Copy Markdown
Owner Author

ifduyue commented May 15, 2026

@gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request migrates the project's Mypy configuration to pyproject.toml, removes legacy Pipfile and run_mypy.sh files, and introduces new test suites for stub validation using mypy.stubtest and pyright. The type stubs in xxhash/init.pyi were updated to use a custom buffer protocol and standardized parameter names. Feedback suggests that the custom _Buffer protocol should be replaced with typing_extensions.Buffer to maintain compatibility with Python versions prior to 3.12, where built-in types do not explicitly define the buffer method in their stubs.

Comment thread xxhash/__init__.pyi
Comment on lines +1 to +7
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a custom Protocol to define the buffer protocol is problematic for compatibility with Python versions prior to 3.12. In those versions, built-in types like bytes, bytearray, and memoryview do not explicitly define the __buffer__ method in their stubs, which will cause type checkers like mypy to report errors when these types are passed where _Buffer is expected.

It is recommended to use typing_extensions.Buffer (which backports collections.abc.Buffer from PEP 688). Type checkers have special-case handling for this type to ensure it correctly matches all buffer-providing types across all supported Python versions.

from typing import final
from typing_extensions import Buffer

_DataType = Buffer

@ifduyue
Copy link
Copy Markdown
Owner Author

ifduyue commented May 15, 2026

@gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request migrates mypy configuration to pyproject.toml, removes legacy environment files, and updates the type stubs in xxhash/__init__.pyi to use a custom buffer protocol. It also adds a new test suite to validate these stubs using pyright. Feedback indicates that the custom _Buffer protocol based on PEP 688 will break type checking on Python versions earlier than 3.12, suggesting a return to typing_extensions.Buffer. Additionally, it is recommended to handle cases where the pyright executable is missing in the new test suite to avoid hard failures.

Comment thread xxhash/__init__.pyi
Comment on lines +1 to +7
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The custom _Buffer protocol defined here is based on PEP 688, which was introduced in Python 3.12. On older Python versions (like 3.9, which this project appears to support), built-in types like bytes, bytearray, and memoryview do not have a Python-accessible __buffer__ method, so they will not match this protocol in type checkers like mypy or pyright unless special-cased.

Furthermore, array.array (which was explicitly supported in the previous version of the stubs) also lacks this method. Using typing_extensions.Buffer is the recommended way to represent the buffer protocol across all supported Python versions, as it is specifically recognized by type checkers to include all buffer-like types (including C-level implementations).

Suggested change
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
import array
from typing import final
from typing_extensions import Buffer
# __buffer__ protocol makes this redundant on python 3.12+
Buffer.register(array.ArrayType)
_DataType = Buffer

Comment on lines +27 to +34
try:
return subprocess.run(
["pyright", "--project", str(repo_root), tmp_path],
capture_output=True,
text=True,
cwd=repo_root,
timeout=30,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test will fail with a FileNotFoundError if the pyright executable is not installed in the environment. It is better to catch this exception and skip the test gracefully, especially for local development environments where pyright might not be globally available.

Suggested change
try:
return subprocess.run(
["pyright", "--project", str(repo_root), tmp_path],
capture_output=True,
text=True,
cwd=repo_root,
timeout=30,
)
try:
return subprocess.run(
["pyright", "--project", str(repo_root), tmp_path],
capture_output=True,
text=True,
cwd=repo_root,
timeout=30,
)
except FileNotFoundError:
self.skipTest("pyright executable not found")

- 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
@ifduyue ifduyue force-pushed the next branch 2 times, most recently from 2c13672 to fcd50e7 Compare May 15, 2026 16:17
@ifduyue
Copy link
Copy Markdown
Owner Author

ifduyue commented May 15, 2026

@gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request migrates the mypy configuration to pyproject.toml, removes legacy Pipfile and mypy configuration files, and updates the type stubs in xxhash/__init__.pyi. The stub updates include renaming parameters to data, adding __all__, and replacing typing_extensions.Buffer with a custom _Buffer protocol based on PEP 688. Additionally, a new test suite has been added to validate these stubs using pyright. Feedback was provided regarding the custom _Buffer protocol, noting that it may cause type-checking errors on Python versions earlier than 3.12 and suggesting the use of typing_extensions.Buffer for better compatibility.

Comment thread xxhash/__init__.pyi
Comment on lines +1 to +7
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a custom Protocol with __buffer__ to represent the buffer protocol is based on PEP 688, which is fully supported in Python 3.12+. For older Python versions, built-in types like bytes, bytearray, and array.array do not have a __buffer__ method in their type stubs within typeshed. This can lead to false-positive type errors in type checkers (like mypy or pyright) when they are configured to target Python versions prior to 3.12.

Since this project appears to support Python 3.9+, it is recommended to use typing_extensions.Buffer. This is the standard, backported way to represent the buffer protocol across all supported Python versions and is correctly recognized by modern type checkers for all built-in buffer types.

from typing import final
from typing_extensions import Buffer

_DataType = Buffer

@ifduyue ifduyue merged commit c0f71ae into master May 16, 2026
81 checks passed
@ifduyue ifduyue deleted the next branch May 16, 2026 08:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant