Skip to content

Commit d7af0b7

Browse files
authored
nitypes: Enable type checking with Pyright (#27)
* nitypes: Add optional dependency group for pyright * Update poetry.lock * nitypes: Address Pyright warnings and errors * nitypes: Move pyright to lint dependency group * Update poetry.lock * github: Add pyright action
1 parent 666d16b commit d7af0b7

File tree

10 files changed

+148
-60
lines changed

10 files changed

+148
-60
lines changed

.github/workflows/check_nitypes.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,15 @@ jobs:
3333
run: poetry run mypy --platform win32
3434
- name: Bandit security checks
3535
run: poetry run bandit -c pyproject.toml -r src/nitypes
36+
- name: Add virtualenv to the path for pyright-action
37+
run: echo "$(poetry env info --path)/bin" >> $GITHUB_PATH
38+
- name: Pyright static analysis (Linux)
39+
uses: jakebailey/pyright-action@v2
40+
with:
41+
python-platform: Linux
42+
version: PATH
43+
- name: Pyright static analysis (Windows)
44+
uses: jakebailey/pyright-action@v2
45+
with:
46+
python-platform: Windows
47+
version: PATH

poetry.lock

Lines changed: 51 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ typing-extensions = ">=4.13.2"
2121
bandit = { version = ">=1.7", extras = ["toml"] }
2222
ni-python-styleguide = ">=0.4.1"
2323
mypy = ">=1.0"
24+
pyright = { version = ">=1.1.400", extras = ["nodejs"] }
2425

2526
[tool.poetry.group.test.dependencies]
2627
pytest = ">=7.2"
@@ -62,3 +63,6 @@ strict = true
6263
skips = [
6364
"B101", # assert_used
6465
]
66+
67+
[tool.pyright]
68+
include = ["src/", "tests/"]

src/nitypes/waveform/_analog.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from collections.abc import Mapping, Sequence
4-
from typing import Any, SupportsIndex, Union, overload
4+
from typing import Any, SupportsIndex, Union, cast, overload
55

66
import numpy as np
77
import numpy.typing as npt
@@ -92,7 +92,7 @@ def from_array_1d(
9292
def from_array_1d(
9393
cls,
9494
array: npt.NDArray[Any] | Sequence[Any],
95-
dtype: type[_TRaw] | np.dtype[_TRaw] = ...,
95+
dtype: type[_TRaw] | np.dtype[_TRaw],
9696
*,
9797
copy: bool = ...,
9898
start_index: SupportsIndex | None = ...,
@@ -119,7 +119,7 @@ def from_array_1d(
119119

120120
@override
121121
@classmethod
122-
def from_array_1d(
122+
def from_array_1d( # pyright: ignore[reportIncompatibleMethodOverride]
123123
cls,
124124
array: npt.NDArray[Any] | Sequence[Any],
125125
dtype: npt.DTypeLike = None,
@@ -178,7 +178,7 @@ def from_array_2d(
178178
def from_array_2d(
179179
cls,
180180
array: npt.NDArray[Any] | Sequence[Sequence[Any]],
181-
dtype: type[_TRaw] | np.dtype[_TRaw] = ...,
181+
dtype: type[_TRaw] | np.dtype[_TRaw],
182182
*,
183183
copy: bool = ...,
184184
start_index: SupportsIndex | None = ...,
@@ -205,7 +205,7 @@ def from_array_2d(
205205

206206
@override
207207
@classmethod
208-
def from_array_2d(
208+
def from_array_2d( # pyright: ignore[reportIncompatibleMethodOverride]
209209
cls,
210210
array: npt.NDArray[Any] | Sequence[Sequence[Any]],
211211
dtype: npt.DTypeLike = None,
@@ -237,15 +237,19 @@ def from_array_2d(
237237
information, and scale mode are applied to all waveforms. Consider assigning
238238
these properties after construction.
239239
"""
240-
return super(AnalogWaveform, cls).from_array_2d(
241-
array,
242-
dtype,
243-
copy=copy,
244-
start_index=start_index,
245-
sample_count=sample_count,
246-
extended_properties=extended_properties,
247-
timing=timing,
248-
scale_mode=scale_mode,
240+
# list[T] is invariant but we are using it in a covariant way here.
241+
return cast(
242+
list[AnalogWaveform[Any]],
243+
super(AnalogWaveform, cls).from_array_2d(
244+
array,
245+
dtype,
246+
copy=copy,
247+
start_index=start_index,
248+
sample_count=sample_count,
249+
extended_properties=extended_properties,
250+
timing=timing,
251+
scale_mode=scale_mode,
252+
),
249253
)
250254

251255
__slots__ = ()

src/nitypes/waveform/_complex.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from collections.abc import Mapping, Sequence
4-
from typing import Any, SupportsIndex, Union, overload
4+
from typing import Any, SupportsIndex, Union, cast, overload
55

66
import numpy as np
77
import numpy.typing as npt
@@ -80,7 +80,7 @@ def from_array_1d(
8080
def from_array_1d(
8181
cls,
8282
array: npt.NDArray[Any] | Sequence[Any],
83-
dtype: type[_TRaw] | np.dtype[_TRaw] = ...,
83+
dtype: type[_TRaw] | np.dtype[_TRaw],
8484
*,
8585
copy: bool = ...,
8686
start_index: SupportsIndex | None = ...,
@@ -107,7 +107,7 @@ def from_array_1d(
107107

108108
@override
109109
@classmethod
110-
def from_array_1d(
110+
def from_array_1d( # pyright: ignore[reportIncompatibleMethodOverride]
111111
cls,
112112
array: npt.NDArray[Any] | Sequence[Any],
113113
dtype: npt.DTypeLike = None,
@@ -166,7 +166,7 @@ def from_array_2d(
166166
def from_array_2d(
167167
cls,
168168
array: npt.NDArray[Any] | Sequence[Sequence[Any]],
169-
dtype: type[_TRaw] | np.dtype[_TRaw] = ...,
169+
dtype: type[_TRaw] | np.dtype[_TRaw],
170170
*,
171171
copy: bool = ...,
172172
start_index: SupportsIndex | None = ...,
@@ -193,7 +193,7 @@ def from_array_2d(
193193

194194
@override
195195
@classmethod
196-
def from_array_2d(
196+
def from_array_2d( # pyright: ignore[reportIncompatibleMethodOverride]
197197
cls,
198198
array: npt.NDArray[Any] | Sequence[Sequence[Any]],
199199
dtype: npt.DTypeLike = None,
@@ -225,15 +225,19 @@ def from_array_2d(
225225
information, and scale mode are applied to all waveforms. Consider assigning
226226
these properties after construction.
227227
"""
228-
return super(ComplexWaveform, cls).from_array_2d(
229-
array,
230-
dtype,
231-
copy=copy,
232-
start_index=start_index,
233-
sample_count=sample_count,
234-
extended_properties=extended_properties,
235-
timing=timing,
236-
scale_mode=scale_mode,
228+
# list[T] is invariant but we are using it in a covariant way here.
229+
return cast(
230+
list[ComplexWaveform[Any]],
231+
super(ComplexWaveform, cls).from_array_2d(
232+
array,
233+
dtype,
234+
copy=copy,
235+
start_index=start_index,
236+
sample_count=sample_count,
237+
extended_properties=extended_properties,
238+
timing=timing,
239+
scale_mode=scale_mode,
240+
),
237241
)
238242

239243
__slots__ = ()

src/nitypes/waveform/_numeric.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ def get_scaled_data( # noqa: D107 - Missing docstring in __init__ (auto-generat
424424
@overload
425425
def get_scaled_data( # noqa: D107 - Missing docstring in __init__ (auto-generated noqa)
426426
self,
427-
dtype: type[_TOtherScaled] | np.dtype[_TOtherScaled] = ...,
427+
dtype: type[_TOtherScaled] | np.dtype[_TOtherScaled],
428428
*,
429429
start_index: SupportsIndex | None = ...,
430430
sample_count: SupportsIndex | None = ...,
@@ -546,12 +546,12 @@ def _get_timing(self, requested_type: type[_TTiming]) -> _TTiming:
546546
self._converted_timing_cache[requested_type] = value
547547
return value
548548

549-
def _set_timing(self, value: _TTiming) -> None:
549+
def _set_timing(self, value: _AnyTiming) -> None:
550550
if self._timing is not value:
551551
self._timing = value
552552
self._converted_timing_cache.clear()
553553

554-
def _validate_timing(self, value: _TTiming) -> None:
554+
def _validate_timing(self, value: _AnyTiming) -> None:
555555
if value._timestamps is not None and len(value._timestamps) != self._sample_count:
556556
raise ValueError(
557557
"The number of irregular timestamps is not equal to the number of samples in the waveform.\n\n"

src/nitypes/waveform/_timing/_precision.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
from __future__ import annotations
22

33
from collections.abc import Sequence
4-
from typing import ClassVar
4+
from typing import ClassVar, final
55

66
import hightime as ht
7-
from typing_extensions import override
7+
from typing_extensions import Self, override
88

99
from nitypes.waveform._timing._base import BaseTiming
1010
from nitypes.waveform._timing._sample_interval import SampleIntervalMode
1111

1212

13+
@final
1314
class PrecisionTiming(BaseTiming[ht.datetime, ht.timedelta]):
1415
"""High-precision waveform timing using the hightime package.
1516
@@ -24,27 +25,29 @@ class PrecisionTiming(BaseTiming[ht.datetime, ht.timedelta]):
2425
"""A waveform timing object with no timestamp, time offset, or sample interval."""
2526

2627
@override
27-
@staticmethod
28+
@classmethod
2829
def create_with_no_interval( # noqa: D102 - Missing docstring in public method - override
29-
timestamp: ht.datetime | None = None, time_offset: ht.timedelta | None = None
30-
) -> PrecisionTiming:
31-
return PrecisionTiming(SampleIntervalMode.NONE, timestamp, time_offset)
30+
cls, timestamp: ht.datetime | None = None, time_offset: ht.timedelta | None = None
31+
) -> Self:
32+
return cls(SampleIntervalMode.NONE, timestamp, time_offset)
3233

3334
@override
34-
@staticmethod
35+
@classmethod
3536
def create_with_regular_interval( # noqa: D102 - Missing docstring in public method - override
37+
cls,
3638
sample_interval: ht.timedelta,
3739
timestamp: ht.datetime | None = None,
3840
time_offset: ht.timedelta | None = None,
39-
) -> PrecisionTiming:
40-
return PrecisionTiming(SampleIntervalMode.REGULAR, timestamp, time_offset, sample_interval)
41+
) -> Self:
42+
return cls(SampleIntervalMode.REGULAR, timestamp, time_offset, sample_interval)
4143

4244
@override
43-
@staticmethod
45+
@classmethod
4446
def create_with_irregular_interval( # noqa: D102 - Missing docstring in public method - override
47+
cls,
4548
timestamps: Sequence[ht.datetime],
46-
) -> PrecisionTiming:
47-
return PrecisionTiming(SampleIntervalMode.IRREGULAR, timestamps=timestamps)
49+
) -> Self:
50+
return cls(SampleIntervalMode.IRREGULAR, timestamps=timestamps)
4851

4952
@override
5053
@staticmethod

src/nitypes/waveform/_timing/_standard.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
import datetime as dt
44
from collections.abc import Sequence
5-
from typing import ClassVar
5+
from typing import ClassVar, final
66

7-
from typing_extensions import override
7+
from typing_extensions import Self, override
88

99
from nitypes.waveform._timing._base import BaseTiming
1010
from nitypes.waveform._timing._sample_interval import SampleIntervalMode
1111

1212

13+
@final
1314
class Timing(BaseTiming[dt.datetime, dt.timedelta]):
1415
"""Waveform timing using the standard datetime module.
1516
@@ -25,27 +26,29 @@ class Timing(BaseTiming[dt.datetime, dt.timedelta]):
2526
"""A waveform timing object with no timestamp, time offset, or sample interval."""
2627

2728
@override
28-
@staticmethod
29+
@classmethod
2930
def create_with_no_interval( # noqa: D102 - Missing docstring in public method - override
30-
timestamp: dt.datetime | None = None, time_offset: dt.timedelta | None = None
31-
) -> Timing:
32-
return Timing(SampleIntervalMode.NONE, timestamp, time_offset)
31+
cls, timestamp: dt.datetime | None = None, time_offset: dt.timedelta | None = None
32+
) -> Self:
33+
return cls(SampleIntervalMode.NONE, timestamp, time_offset)
3334

3435
@override
35-
@staticmethod
36+
@classmethod
3637
def create_with_regular_interval( # noqa: D102 - Missing docstring in public method - override
38+
cls,
3739
sample_interval: dt.timedelta,
3840
timestamp: dt.datetime | None = None,
3941
time_offset: dt.timedelta | None = None,
40-
) -> Timing:
41-
return Timing(SampleIntervalMode.REGULAR, timestamp, time_offset, sample_interval)
42+
) -> Self:
43+
return cls(SampleIntervalMode.REGULAR, timestamp, time_offset, sample_interval)
4244

4345
@override
44-
@staticmethod
46+
@classmethod
4547
def create_with_irregular_interval( # noqa: D102 - Missing docstring in public method - override
48+
cls,
4649
timestamps: Sequence[dt.datetime],
47-
) -> Timing:
48-
return Timing(SampleIntervalMode.IRREGULAR, timestamps=timestamps)
50+
) -> Self:
51+
return cls(SampleIntervalMode.IRREGULAR, timestamps=timestamps)
4952

5053
@override
5154
@staticmethod

0 commit comments

Comments
 (0)