Skip to content

Commit 8732cbf

Browse files
committed
added more type hints, full pyright and mypy support and added py.typed file
1 parent 3bbb885 commit 8732cbf

13 files changed

Lines changed: 107 additions & 54 deletions

File tree

.github/workflows/main.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
- name: pytest
3333
run: py.test
3434

35-
docs:
35+
docs_and_lint:
3636
runs-on: ubuntu-latest
3737
timeout-minutes: 2
3838
steps:
@@ -44,7 +44,13 @@ jobs:
4444
- name: Install dependencies
4545
run: |
4646
python -m pip install --upgrade pip setuptools
47-
pip install -e '.[docs]'
47+
pip install -e '.[docs,tests]' pyright flake8 mypy
4848
- name: build docs
4949
run: make html
5050
working-directory: docs/
51+
- name: flake8
52+
run: flake8 -v python_utils setup.py
53+
- name: mypy
54+
run: mypy python_utils setup.py
55+
- name: pyright
56+
run: pyright

_python_utils_tests/test_time.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ def test_timeout_generator(
7171

7272
@pytest.mark.asyncio
7373
async def test_aio_generator_timeout_detector():
74+
# Make pyright happy
75+
i = None
76+
7477
async def generator():
7578
for i in range(10):
7679
await asyncio.sleep(i / 100.0)
@@ -106,52 +109,55 @@ async def generator():
106109

107110
@pytest.mark.asyncio
108111
async def test_aio_generator_timeout_detector_decorator():
112+
# Make pyright happy
113+
i = None
114+
109115
# Test regular timeout with reraise
110116
@python_utils.aio_generator_timeout_detector_decorator(timeout=0.05)
111-
async def generator():
117+
async def generator_timeout():
112118
for i in range(10):
113119
await asyncio.sleep(i / 100.0)
114120
yield i
115121

116122
with pytest.raises(asyncio.TimeoutError):
117-
async for i in generator():
123+
async for i in generator_timeout():
118124
pass
119125

120126
# Test regular timeout with clean exit
121127
@python_utils.aio_generator_timeout_detector_decorator(
122128
timeout=0.05, on_timeout=None
123129
)
124-
async def generator():
130+
async def generator_clean():
125131
for i in range(10):
126132
await asyncio.sleep(i / 100.0)
127133
yield i
128134

129-
async for i in generator():
135+
async for i in generator_clean():
130136
pass
131137

132138
assert i == 4
133139

134140
# Test total timeout with reraise
135141
@python_utils.aio_generator_timeout_detector_decorator(total_timeout=0.1)
136-
async def generator():
142+
async def generator_reraise():
137143
for i in range(10):
138144
await asyncio.sleep(i / 100.0)
139145
yield i
140146

141147
with pytest.raises(asyncio.TimeoutError):
142-
async for i in generator():
148+
async for i in generator_reraise():
143149
pass
144150

145151
# Test total timeout with clean exit
146152
@python_utils.aio_generator_timeout_detector_decorator(
147153
total_timeout=0.1, on_timeout=None
148154
)
149-
async def generator():
155+
async def generator_clean_total():
150156
for i in range(10):
151157
await asyncio.sleep(i / 100.0)
152158
yield i
153159

154-
async for i in generator():
160+
async for i in generator_clean_total():
155161
pass
156162

157163
assert i == 4

pyrightconfig.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"include": [
3+
"python_utils"
4+
],
5+
"exclude": [
6+
"python_utils/types.py",
7+
],
8+
}

python_utils/aio.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,15 @@
55
import asyncio
66
import itertools
77

8+
from . import types
89

9-
async def acount(start=0, step=1, delay=0, stop=None):
10+
11+
async def acount(
12+
start: float = 0,
13+
step: float = 1,
14+
delay: float = 0,
15+
stop: types.Optional[float] = None,
16+
) -> types.AsyncIterator[float]:
1017
'''Asyncio version of itertools.count()'''
1118
for item in itertools.count(start, step): # pragma: no branch
1219
if stop is not None and item >= stop:

python_utils/containers.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ def __init__(
3131
self,
3232
key_cast: KT_cast = None,
3333
value_cast: VT_cast = None,
34-
*args,
35-
**kwargs,
34+
*args: DictUpdateArgs,
35+
**kwargs: VT,
3636
) -> None:
3737
self._value_cast = value_cast
3838
self._key_cast = key_cast
@@ -53,7 +53,7 @@ def __setitem__(self, key: Any, value: Any) -> None:
5353
return super().__setitem__(key, value)
5454

5555

56-
class CastedDict(CastedDictBase):
56+
class CastedDict(CastedDictBase[KT, VT]):
5757
'''
5858
Custom dictionary that casts keys and values to the specified typing.
5959
@@ -88,14 +88,14 @@ class CastedDict(CastedDictBase):
8888
{1: 2, '3': '4', '5': '6', '7': '8'}
8989
'''
9090

91-
def __setitem__(self, key, value):
91+
def __setitem__(self, key: Any, value: Any) -> None:
9292
if self._value_cast is not None:
9393
value = self._value_cast(value)
9494

9595
super().__setitem__(key, value)
9696

9797

98-
class LazyCastedDict(CastedDictBase):
98+
class LazyCastedDict(CastedDictBase[KT, VT]):
9999
'''
100100
Custom dictionary that casts keys and lazily casts values to the specified
101101
typing. Note that the values are cast only when they are accessed and
@@ -141,13 +141,13 @@ class LazyCastedDict(CastedDictBase):
141141
'4'
142142
'''
143143

144-
def __setitem__(self, key, value):
144+
def __setitem__(self, key: Any, value: Any) -> None:
145145
if self._key_cast is not None:
146146
key = self._key_cast(key)
147147

148148
super().__setitem__(key, value)
149149

150-
def __getitem__(self, key) -> VT:
150+
def __getitem__(self, key: Any) -> VT:
151151
if self._key_cast is not None:
152152
key = self._key_cast(key)
153153

python_utils/converters.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def to_int(
1010
input_: typing.Optional[str] = None,
1111
default: int = 0,
1212
exception: types.ExceptionsType = (ValueError, TypeError),
13-
regexp: types.O[types.Pattern] = None,
13+
regexp: types.Optional[types.Pattern] = None,
1414
) -> int:
1515
r'''
1616
Convert the given input to an integer or return default
@@ -100,7 +100,7 @@ def to_float(
100100
input_: str,
101101
default: int = 0,
102102
exception: types.ExceptionsType = (ValueError, TypeError),
103-
regexp: types.O[types.Pattern] = None,
103+
regexp: types.Optional[types.Pattern] = None,
104104
) -> types.Number:
105105
r'''
106106
Convert the given `input_` to an integer or return default

python_utils/generators.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ async def abatcher(
99
generator: types.AsyncIterator,
1010
batch_size: types.Optional[int] = None,
1111
interval: types.Optional[types.delta_type] = None,
12-
):
12+
) -> types.AsyncIterator[list]:
1313
'''
1414
Asyncio generator wrapper that returns items with a given batch size or
1515
interval (whichever is reached first).
@@ -64,7 +64,9 @@ async def abatcher(
6464
next_yield = time.perf_counter() + interval_s
6565

6666

67-
def batcher(iterable, batch_size):
67+
def batcher(
68+
iterable: types.Iterable, batch_size: int = 10
69+
) -> types.Iterator[list]:
6870
'''
6971
Generator wrapper that returns items with a given batch size
7072
'''

python_utils/loguru.py

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

3-
from . import logger
4-
53
import loguru
64

5+
from . import logger as logger_module
6+
77
__all__ = ['Logurud']
88

99

10-
class Logurud(logger.LoggerBase):
10+
class Logurud(logger_module.LoggerBase):
1111
logger: loguru.Logger
1212

1313
def __new__(cls, *args, **kwargs):
14-
cls.logger: loguru.Loguru = loguru.logger.opt(depth=1)
14+
cls.logger: loguru.Logger = loguru.logger.opt(depth=1)
1515
return super().__new__(cls)

python_utils/py.typed

Whitespace-only changes.

python_utils/terminal.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33

44
from . import converters
55

6+
Dimensions = typing.Tuple[int, int]
7+
OptionalDimensions = typing.Optional[Dimensions]
68

7-
def get_terminal_size() -> typing.Tuple[int, int]: # pragma: no cover
9+
10+
def get_terminal_size() -> Dimensions: # pragma: no cover
811
'''Get the current size of your terminal
912
1013
Multiple returns are not always a good idea, but in this case it greatly
@@ -62,35 +65,36 @@ def get_terminal_size() -> typing.Tuple[int, int]: # pragma: no cover
6265
pass
6366

6467
try:
65-
w, h = _get_terminal_size_linux()
66-
if w and h:
67-
return w, h
68+
# The method can return None so we don't unpack it
69+
wh = _get_terminal_size_linux()
70+
if wh is not None and all(wh):
71+
return wh
6872
except Exception: # pragma: no cover
6973
pass
7074

7175
try:
7276
# Windows detection doesn't always work, let's try anyhow
73-
w, h = _get_terminal_size_windows()
74-
if w and h:
75-
return w, h
77+
wh = _get_terminal_size_windows()
78+
if wh is not None and all(wh):
79+
return wh
7680
except Exception: # pragma: no cover
7781
pass
7882

7983
try:
8084
# needed for window's python in cygwin's xterm!
81-
w, h = _get_terminal_size_tput()
82-
if w and h:
83-
return w, h
85+
wh = _get_terminal_size_tput()
86+
if wh is not None and all(wh):
87+
return wh
8488
except Exception: # pragma: no cover
8589
pass
8690

8791
return 79, 24
8892

8993

90-
def _get_terminal_size_windows(): # pragma: no cover
94+
def _get_terminal_size_windows() -> OptionalDimensions: # pragma: no cover
9195
res = None
9296
try:
93-
from ctypes import windll, create_string_buffer
97+
from ctypes import windll, create_string_buffer # type: ignore
9498

9599
# stdin handle is -10
96100
# stdout handle is -11
@@ -115,7 +119,7 @@ def _get_terminal_size_windows(): # pragma: no cover
115119
return None
116120

117121

118-
def _get_terminal_size_tput(): # pragma: no cover
122+
def _get_terminal_size_tput() -> OptionalDimensions: # pragma: no cover
119123
# get terminal width src: http://stackoverflow.com/questions/263890/
120124
try:
121125
import subprocess
@@ -141,19 +145,19 @@ def _get_terminal_size_tput(): # pragma: no cover
141145
return None
142146

143147

144-
def _get_terminal_size_linux(): # pragma: no cover
148+
def _get_terminal_size_linux() -> OptionalDimensions: # pragma: no cover
145149
def ioctl_GWINSZ(fd):
146150
try:
147151
import fcntl
148152
import termios
149153
import struct
150154

151-
size = struct.unpack(
152-
'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')
155+
return struct.unpack(
156+
'hh',
157+
fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'), # type: ignore
153158
)
154159
except Exception:
155160
return None
156-
return size
157161

158162
size = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
159163

0 commit comments

Comments
 (0)