Skip to content

Commit

Permalink
Improve types beyond what typeshed has.
Browse files Browse the repository at this point in the history
- Typeshed has argument types as `bytes` to substitute for buffers since buffers didn't have a representation in the type system for a long time. Now that `collections.abc.Buffer` exists, there's a better alternative.
 - This means that memoryview, bytarray, etc. won't show up as type-checker errors when given as inputs.
- Tests actually type-check as a result. No changes to logic were made, just type annotations.
- TODO: Consider upstreaming to typeshed?

Reference material:
- https://docs.python.org/3/library/typing.html#typing.ByteString
- https://docs.python.org/3/library/collections.abc.html#collections.abc.Buffer
- https://peps.python.org/pep-0688
 - "No special meaning for bytes" mentions why these were annotated with `bytes` before.
- https://typing.readthedocs.io/en/latest/source/modernizing.html#typing-bytestring
  • Loading branch information
Sachaa-Thanasius committed Apr 24, 2024
1 parent e1526b6 commit c867062
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 33 deletions.
55 changes: 28 additions & 27 deletions audioop/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
from typing_extensions import TypeAlias
from collections.abc import Buffer
from typing import TypeAlias

_AdpcmState: TypeAlias = tuple[int, int]
_RatecvState: TypeAlias = tuple[int, tuple[tuple[int, int], ...]]

class error(Exception): ...

def add(fragment1: bytes, fragment2: bytes, width: int, /) -> bytes: ...
def adpcm2lin(fragment: bytes, width: int, state: _AdpcmState | None, /) -> tuple[bytes, _AdpcmState]: ...
def alaw2lin(fragment: bytes, width: int, /) -> bytes: ...
def avg(fragment: bytes, width: int, /) -> int: ...
def avgpp(fragment: bytes, width: int, /) -> int: ...
def bias(fragment: bytes, width: int, bias: int, /) -> bytes: ...
def byteswap(fragment: bytes, width: int, /) -> bytes: ...
def cross(fragment: bytes, width: int, /) -> int: ...
def findfactor(fragment: bytes, reference: bytes, /) -> float: ...
def findfit(fragment: bytes, reference: bytes, /) -> tuple[int, float]: ...
def findmax(fragment: bytes, length: int, /) -> int: ...
def getsample(fragment: bytes, width: int, index: int, /) -> int: ...
def lin2adpcm(fragment: bytes, width: int, state: _AdpcmState | None, /) -> tuple[bytes, _AdpcmState]: ...
def lin2alaw(fragment: bytes, width: int, /) -> bytes: ...
def lin2lin(fragment: bytes, width: int, newwidth: int, /) -> bytes: ...
def lin2ulaw(fragment: bytes, width: int, /) -> bytes: ...
def max(fragment: bytes, width: int, /) -> int: ...
def maxpp(fragment: bytes, width: int, /) -> int: ...
def minmax(fragment: bytes, width: int, /) -> tuple[int, int]: ...
def mul(fragment: bytes, width: int, factor: float, /) -> bytes: ...
def add(fragment1: Buffer, fragment2: Buffer, width: int, /) -> bytes: ...
def adpcm2lin(fragment: Buffer, width: int, state: _AdpcmState | None, /) -> tuple[bytes, _AdpcmState]: ...
def alaw2lin(fragment: Buffer, width: int, /) -> bytes: ...
def avg(fragment: Buffer, width: int, /) -> int: ...
def avgpp(fragment: Buffer, width: int, /) -> int: ...
def bias(fragment: Buffer, width: int, bias: int, /) -> bytes: ...
def byteswap(fragment: Buffer, width: int, /) -> bytes: ...
def cross(fragment: Buffer, width: int, /) -> int: ...
def findfactor(fragment: Buffer, reference: Buffer, /) -> float: ...
def findfit(fragment: Buffer, reference: Buffer, /) -> tuple[int, float]: ...
def findmax(fragment: Buffer, length: int, /) -> int: ...
def getsample(fragment: Buffer, width: int, index: int, /) -> int: ...
def lin2adpcm(fragment: Buffer, width: int, state: _AdpcmState | None, /) -> tuple[bytes, _AdpcmState]: ...
def lin2alaw(fragment: Buffer, width: int, /) -> bytes: ...
def lin2lin(fragment: Buffer, width: int, newwidth: int, /) -> bytes: ...
def lin2ulaw(fragment: Buffer, width: int, /) -> bytes: ...
def max(fragment: Buffer, width: int, /) -> int: ...
def maxpp(fragment: Buffer, width: int, /) -> int: ...
def minmax(fragment: Buffer, width: int, /) -> tuple[int, int]: ...
def mul(fragment: Buffer, width: int, factor: float, /) -> bytes: ...
def ratecv(
fragment: bytes,
fragment: Buffer,
width: int,
nchannels: int,
inrate: int,
Expand All @@ -36,9 +37,9 @@ def ratecv(
weightB: int = 0,
/,
) -> tuple[bytes, _RatecvState]: ...
def reverse(fragment: bytes, width: int, /) -> bytes: ...
def rms(fragment: bytes, width: int, /) -> int: ...
def tomono(fragment: bytes, width: int, lfactor: float, rfactor: float, /) -> bytes: ...
def tostereo(fragment: bytes, width: int, lfactor: float, rfactor: float, /) -> bytes: ...
def ulaw2lin(fragment: bytes, width: int, /) -> bytes: ...
def reverse(fragment: Buffer, width: int, /) -> bytes: ...
def rms(fragment: Buffer, width: int, /) -> int: ...
def tomono(fragment: Buffer, width: int, lfactor: float, rfactor: float, /) -> bytes: ...
def tostereo(fragment: Buffer, width: int, lfactor: float, rfactor: float, /) -> bytes: ...
def ulaw2lin(fragment: Buffer, width: int, /) -> bytes: ...
# pyright: ignore [reportShadowedImports]
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ audioop = ["*.c", "*.h"]

[tool.pyright]
include = ["audioop"]
ignore = ["audioop/__init__.py", "tests"]
ignore = [
"audioop/__init__.py", # Pyright can't see the shared object being imported from.
]
pythonVersion = "3.13"
typeCheckingMode = "strict"

reportPrivateUsage = "none"
reportMissingTypeStubs = "none"
reportUnnecessaryTypeIgnoreComment = "warning"
11 changes: 7 additions & 4 deletions tests/test_audioop.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import sys
import unittest
import audioop
import audioop # pyright: ignore[reportShadowedImports]
from collections.abc import Callable


def pack(width, data):
def pack(width: int, data: tuple[int, ...]) -> bytes:
return b''.join(v.to_bytes(width, sys.byteorder, signed=True) for v in data)

def unpack(width, data):
def unpack(width: int, data: bytes) -> list[int]:
return [int.from_bytes(data[i: i + width], sys.byteorder, signed=True)
for i in range(0, len(data), width)]

packs = {w: (lambda *data, width=w: pack(width, data)) for w in (1, 2, 3, 4)}
packs: dict[int, Callable[[*tuple[int, ...]], bytes]] = {
w: (lambda *data, width=w: pack(width, data)) for w in (1, 2, 3, 4)
}
maxvalues = {w: (1 << (8 * w - 1)) - 1 for w in (1, 2, 3, 4)}
minvalues = {w: -1 << (8 * w - 1) for w in (1, 2, 3, 4)}

Expand Down

0 comments on commit c867062

Please sign in to comment.