Skip to content

Commit 2aa0772

Browse files
authored
Fix Pyright errors (#605)
1 parent d755232 commit 2aa0772

File tree

6 files changed

+74
-38
lines changed

6 files changed

+74
-38
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Run static analysis tools
2+
on: [push, pull_request]
3+
jobs:
4+
static-analysis:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- name: Set up Python
8+
uses: actions/setup-python@v5
9+
with:
10+
python-version: "3"
11+
- name: Double-check Python version
12+
run: |
13+
python --version
14+
- name: Clone Git repository
15+
uses: actions/checkout@v4
16+
- name: Install sounddevice module
17+
run: |
18+
python -m pip install .
19+
- name: Install Pyright and NumPy
20+
run: |
21+
python -m pip install pyright numpy
22+
- name: Check sounddevice module with Pyright
23+
run: |
24+
python -m pyright src/sounddevice.py
25+
- name: Install dependencies for examples
26+
run: |
27+
python -m pip install ffmpeg-python matplotlib soundfile
28+
- name: Check examples with Pyright
29+
run: |
30+
python -m pyright examples/*.py

examples/play_long_file.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def callback(outdata, frames, time, status):
9696
callback=callback, finished_callback=event.set)
9797
with stream:
9898
timeout = args.blocksize * args.buffersize / f.samplerate
99-
while len(data):
99+
while len(data): # type: ignore
100100
data = f.read(args.blocksize)
101101
q.put(data, timeout=timeout)
102102
event.wait() # Wait until playback is finished

examples/play_long_file_raw.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def callback(outdata, frames, time, status):
8787
callback=callback, finished_callback=event.set)
8888
with stream:
8989
timeout = args.blocksize * args.buffersize / f.samplerate
90-
while data:
90+
while data: # type: ignore
9191
data = f.buffer_read(args.blocksize, dtype='float32')
9292
q.put(data, timeout=timeout)
9393
event.wait() # Wait until playback is finished

examples/rec_gui.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,6 @@ def validate(self):
7575

7676
class RecGui(tk.Tk):
7777

78-
stream = None
79-
8078
def __init__(self):
8179
super().__init__()
8280

@@ -122,7 +120,7 @@ def __init__(self):
122120
self.update_gui()
123121

124122
def create_stream(self, device=None):
125-
if self.stream is not None:
123+
with contextlib.suppress(AttributeError):
126124
self.stream.close()
127125
self.stream = sd.InputStream(
128126
device=device, channels=1, callback=self.audio_callback)
@@ -205,7 +203,7 @@ def on_settings(self, *args):
205203
def init_buttons(self):
206204
self.rec_button['text'] = 'record'
207205
self.rec_button['command'] = self.on_rec
208-
if self.stream:
206+
if hasattr(self, 'stream'):
209207
self.rec_button['state'] = 'normal'
210208
self.settings_button['state'] = 'normal'
211209

examples/spectrogram.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def int_or_str(text):
3232
print(sd.query_devices())
3333
parser.exit(0)
3434
parser = argparse.ArgumentParser(
35-
description=__doc__ + '\n\nSupported keys:' + usage_line,
35+
description=__doc__ + '\n\nSupported keys:' + usage_line, # type: ignore
3636
formatter_class=argparse.RawDescriptionHelpFormatter,
3737
parents=[parser])
3838
parser.add_argument(

src/sounddevice.py

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
break
7070
else:
7171
raise OSError('PortAudio library not found')
72-
_lib = _ffi.dlopen(_libname)
72+
_lib: ... = _ffi.dlopen(_libname)
7373
except OSError:
7474
if _platform.system() == 'Darwin':
7575
_libname = 'libportaudio.dylib'
@@ -87,9 +87,9 @@
8787
import _sounddevice_data
8888
_libname = _os.path.join(
8989
next(iter(_sounddevice_data.__path__)), 'portaudio-binaries', _libname)
90-
_lib = _ffi.dlopen(_libname)
90+
_lib: ... = _ffi.dlopen(_libname)
9191

92-
_sampleformats = {
92+
_sampleformats: ... = {
9393
'float32': _lib.paFloat32,
9494
'int32': _lib.paInt32,
9595
'int24': _lib.paInt24,
@@ -457,7 +457,7 @@ def get_stream():
457457
raise RuntimeError('play()/rec()/playrec() was not called yet')
458458

459459

460-
def query_devices(device=None, kind=None):
460+
def query_devices(device=None, kind=None) -> ...:
461461
"""Return information about available devices.
462462
463463
Information and capabilities of PortAudio devices.
@@ -575,7 +575,7 @@ def query_devices(device=None, kind=None):
575575
if not info:
576576
raise PortAudioError(f'Error querying device {device}')
577577
assert info.structVersion == 2
578-
name_bytes = _ffi.string(info.name)
578+
name_bytes = _ffi_string(info.name)
579579
try:
580580
# We don't know beforehand if DirectSound and MME device names use
581581
# 'utf-8' or 'mbcs' encoding. Let's try 'utf-8' first, because it more
@@ -610,7 +610,7 @@ def query_devices(device=None, kind=None):
610610
return device_dict
611611

612612

613-
def query_hostapis(index=None):
613+
def query_hostapis(index=None) -> ...:
614614
"""Return information about available host APIs.
615615
616616
Parameters
@@ -655,7 +655,7 @@ def query_hostapis(index=None):
655655
raise PortAudioError(f'Error querying host API {index}')
656656
assert info.structVersion == 1
657657
return {
658-
'name': _ffi.string(info.name).decode(),
658+
'name': _ffi_string(info.name).decode(),
659659
'devices': [_lib.Pa_HostApiDeviceIndexToDeviceIndex(index, i)
660660
for i in range(info.deviceCount)],
661661
'default_input_device': info.defaultInputDevice,
@@ -725,7 +725,7 @@ def get_portaudio_version():
725725
(1899, 'PortAudio V19-devel (built Feb 15 2014 23:28:00)')
726726
727727
"""
728-
return _lib.Pa_GetVersion(), _ffi.string(_lib.Pa_GetVersionText()).decode()
728+
return _lib.Pa_GetVersion(), _ffi_string(_lib.Pa_GetVersionText()).decode()
729729

730730

731731
class _StreamBase:
@@ -1208,7 +1208,7 @@ def _raw_read(self, frames):
12081208
"""
12091209
channels, _ = _split(self._channels)
12101210
samplesize, _ = _split(self._samplesize)
1211-
data = _ffi.new('signed char[]', channels * samplesize * frames)
1211+
data = _ffi.new('signed char[]', channels * samplesize * frames) # type: ignore
12121212
err = _lib.Pa_ReadStream(self._ptr, data, frames)
12131213
if err == _lib.paInputOverflowed:
12141214
overflowed = True
@@ -1306,7 +1306,7 @@ def _raw_write(self, data):
13061306
pass # input is not a buffer
13071307
_, samplesize = _split(self._samplesize)
13081308
_, channels = _split(self._channels)
1309-
samples, remainder = divmod(len(data), samplesize)
1309+
samples, remainder = divmod(len(data), samplesize) # type: ignore
13101310
if remainder:
13111311
raise ValueError('len(data) not divisible by samplesize')
13121312
frames, remainder = divmod(samples, channels)
@@ -2056,7 +2056,7 @@ class _InputOutputPair:
20562056
_indexmapping = {'input': 0, 'output': 1}
20572057

20582058
def __init__(self, parent, default_attr):
2059-
self._pair = [None, None]
2059+
self._pair: ... = [None, None]
20602060
self._parent = parent
20612061
self._default_attr = default_attr
20622062

@@ -2117,7 +2117,7 @@ class default:
21172117
_pairs = 'device', 'channels', 'dtype', 'latency', 'extra_settings'
21182118
# The class attributes listed in _pairs are only provided here for static
21192119
# analysis tools and for the docs. They're overwritten in __init__().
2120-
device = None, None
2120+
device: ... = (None, None)
21212121
"""Index or query string of default input/output device.
21222122
21232123
If not overwritten, this is queried from PortAudio.
@@ -2127,15 +2127,17 @@ class default:
21272127
`default`, `query_devices()`, the *device* argument of `Stream`
21282128
21292129
"""
2130-
channels = _default_channels = None, None
2130+
_default_channels = None, None
2131+
channels: ... = _default_channels
21312132
"""Default number of input/output channels.
21322133
21332134
See Also
21342135
--------
21352136
`default`, `query_devices()`, the *channels* argument of `Stream`
21362137
21372138
"""
2138-
dtype = _default_dtype = 'float32', 'float32'
2139+
_default_dtype = 'float32', 'float32'
2140+
dtype: ... = _default_dtype
21392141
"""Default data type used for input/output samples.
21402142
21412143
The types ``'float32'``, ``'int32'``, ``'int16'``, ``'int8'`` and
@@ -2151,25 +2153,27 @@ class default:
21512153
`default`, `numpy:numpy.dtype`, the *dtype* argument of `Stream`
21522154
21532155
"""
2154-
latency = _default_latency = 'high', 'high'
2156+
_default_latency = 'high', 'high'
2157+
latency: ... = _default_latency
21552158
"""See the *latency* argument of `Stream`."""
2156-
extra_settings = _default_extra_settings = None, None
2159+
_default_extra_settings = None, None
2160+
extra_settings: ... = _default_extra_settings
21572161
"""Host-API-specific input/output settings.
21582162
21592163
See Also
21602164
--------
21612165
AsioSettings, CoreAudioSettings, WasapiSettings
21622166
21632167
"""
2164-
samplerate = None
2168+
samplerate: ... = None
21652169
"""Sampling frequency in Hertz (= frames per second).
21662170
21672171
See Also
21682172
--------
21692173
`default`, `query_devices()`
21702174
21712175
"""
2172-
blocksize = _lib.paFramesPerBufferUnspecified
2176+
blocksize: ... = _lib.paFramesPerBufferUnspecified
21732177
"""See the *blocksize* argument of `Stream`."""
21742178
clip_off = False
21752179
"""Disable clipping.
@@ -2229,10 +2233,12 @@ def _default_device(self):
22292233
_lib.Pa_GetDefaultOutputDevice())
22302234

22312235
@property
2232-
def hostapi(self):
2236+
def hostapi(self): # type: ignore
22332237
"""Index of the default host API (read-only)."""
22342238
return _check(_lib.Pa_GetDefaultHostApi())
22352239

2240+
hostapi: int
2241+
22362242
def reset(self):
22372243
"""Reset all attributes to their "factory default"."""
22382244
vars(self).clear()
@@ -2241,7 +2247,8 @@ def reset(self):
22412247

22422248
if not hasattr(_ffi, 'I_AM_FAKE'):
22432249
# This object shadows the 'default' class, except when building the docs.
2244-
default = default()
2250+
_default_instance: ... = default()
2251+
default = _default_instance
22452252

22462253

22472254
class PortAudioError(Exception):
@@ -2511,13 +2518,10 @@ def __init__(self, exclusive=False, auto_convert=False, explicit_sample_format=F
25112518
class _CallbackContext:
25122519
"""Helper class for reuse in play()/rec()/playrec() callbacks."""
25132520

2514-
blocksize = None
2515-
data = None
2516-
out = None
25172521
frame = 0
2522+
frames: int
25182523
input_channels = output_channels = None
25192524
input_dtype = output_dtype = None
2520-
input_mapping = output_mapping = None
25212525
silent_channels = None
25222526

25232527
def __init__(self, loop=False):
@@ -2561,7 +2565,7 @@ def check_data(self, data, mapping, device):
25612565
if len(mapping) + len(silent_channels) != channels:
25622566
raise ValueError('each channel may only appear once in mapping')
25632567

2564-
self.data = data
2568+
self.data: np.typing.NDArray = data
25652569
self.output_channels = channels
25662570
self.output_dtype = dtype
25672571
self.output_mapping = mapping
@@ -2645,8 +2649,8 @@ def callback_exit(self):
26452649
def finished_callback(self):
26462650
self.event.set()
26472651
# Drop temporary audio buffers to free memory
2648-
self.data = None
2649-
self.out = None
2652+
del self.data
2653+
del self.out
26502654
# Drop CFFI objects to avoid reference cycles
26512655
self.stream._callback = None
26522656
self.stream._finished_callback = None
@@ -2679,6 +2683,10 @@ def wait(self, ignore_errors=True):
26792683
return self.status if self.status else None
26802684

26812685

2686+
def _ffi_string(cdata) -> bytes:
2687+
return _ffi.string(cdata) # type: ignore
2688+
2689+
26822690
def _remove_self(d):
26832691
"""Return a copy of d without the 'self' entry."""
26842692
d = d.copy()
@@ -2715,7 +2723,7 @@ def _check_dtype(dtype):
27152723

27162724

27172725
def _get_stream_parameters(kind, device, channels, dtype, latency,
2718-
extra_settings, samplerate):
2726+
extra_settings: ..., samplerate):
27192727
"""Get parameters for one direction (input or output) of a stream."""
27202728
assert kind in ('input', 'output')
27212729
if device is None:
@@ -2753,7 +2761,7 @@ def _get_stream_parameters(kind, device, channels, dtype, latency,
27532761
latency = info['default_' + latency + '_' + kind + '_latency']
27542762
if samplerate is None:
27552763
samplerate = info['default_samplerate']
2756-
parameters = _ffi.new('PaStreamParameters*', (
2764+
parameters: ... = _ffi.new('PaStreamParameters*', (
27572765
device, channels, sampleformat, latency,
27582766
extra_settings._streaminfo if extra_settings else _ffi.NULL))
27592767
return parameters, dtype, samplesize, samplerate
@@ -2809,7 +2817,7 @@ def _check(err, msg=''):
28092817
if err >= 0:
28102818
return err
28112819

2812-
errormsg = _ffi.string(_lib.Pa_GetErrorText(err)).decode()
2820+
errormsg = _ffi_string(_lib.Pa_GetErrorText(err)).decode()
28132821
if msg:
28142822
errormsg = f'{msg}: {errormsg}'
28152823

@@ -2820,7 +2828,7 @@ def _check(err, msg=''):
28202828
# in scenarios where multiple APIs are being used simultaneously.
28212829
info = _lib.Pa_GetLastHostErrorInfo()
28222830
host_api = _lib.Pa_HostApiTypeIdToHostApiIndex(info.hostApiType)
2823-
hosterror_text = _ffi.string(info.errorText).decode()
2831+
hosterror_text = _ffi_string(info.errorText).decode()
28242832
hosterror_info = host_api, info.errorCode, hosterror_text
28252833
raise PortAudioError(errormsg, err, hosterror_info)
28262834

0 commit comments

Comments
 (0)