Skip to content

Commit 54d2912

Browse files
committed
Merge branch 'release/3.7.0'
2 parents f88e50d + 8a2e94f commit 54d2912

13 files changed

+334
-62
lines changed

_python_utils_tests/test_aio.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
import pytest
33
import asyncio
44

5+
56
from python_utils import types
6-
from python_utils.aio import acount
7+
from python_utils.aio import acount, acontainer
78

89

910
@pytest.mark.asyncio
@@ -20,3 +21,16 @@ async def mock_sleep(delay: float):
2021

2122
assert len(sleeps) == 4
2223
assert sum(sleeps) == 4
24+
25+
26+
@pytest.mark.asyncio
27+
async def test_acontainer():
28+
async def async_gen():
29+
yield 1
30+
yield 2
31+
yield 3
32+
33+
assert await acontainer(async_gen) == [1, 2, 3]
34+
assert await acontainer(async_gen()) == [1, 2, 3]
35+
assert await acontainer(async_gen, set) == {1, 2, 3}
36+
assert await acontainer(async_gen(), set) == {1, 2, 3}

_python_utils_tests/test_containers.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from python_utils import containers
44

55

6-
def test_unique_list_ignore():
7-
a = containers.UniqueList()
6+
def test_unique_list_ignore() -> None:
7+
a: containers.UniqueList[int] = containers.UniqueList()
88
a.append(1)
99
a.append(1)
1010
assert a == [1]
@@ -16,8 +16,10 @@ def test_unique_list_ignore():
1616
a[3] = 5
1717

1818

19-
def test_unique_list_raise():
20-
a = containers.UniqueList(*range(20), on_duplicate='raise')
19+
def test_unique_list_raise() -> None:
20+
a: containers.UniqueList[int] = containers.UniqueList(
21+
*range(20), on_duplicate='raise'
22+
)
2123
with pytest.raises(ValueError):
2224
a[10:20:2] = [1, 2, 3, 4, 5]
2325

_python_utils_tests/test_decorators.py

+38-6
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,69 @@
22

33
import pytest
44

5-
from python_utils.decorators import sample
5+
from python_utils.decorators import sample, wraps_classmethod
66

77

88
@pytest.fixture
9-
def random(monkeypatch):
9+
def random(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
1010
mock = MagicMock()
1111
monkeypatch.setattr(
12-
"python_utils.decorators.random.random", mock, raising=True
12+
'python_utils.decorators.random.random', mock, raising=True
1313
)
1414
return mock
1515

1616

17-
def test_sample_called(random):
17+
def test_sample_called(random: MagicMock):
1818
demo_function = MagicMock()
1919
decorated = sample(0.5)(demo_function)
2020
random.return_value = 0.4
2121
decorated()
2222
random.return_value = 0.0
2323
decorated()
2424
args = [1, 2]
25-
kwargs = {"1": 1, "2": 2}
25+
kwargs = {'1': 1, '2': 2}
2626
decorated(*args, **kwargs)
2727
demo_function.assert_called_with(*args, **kwargs)
2828
assert demo_function.call_count == 3
2929

3030

31-
def test_sample_not_called(random):
31+
def test_sample_not_called(random: MagicMock):
3232
demo_function = MagicMock()
3333
decorated = sample(0.5)(demo_function)
3434
random.return_value = 0.5
3535
decorated()
3636
random.return_value = 1.0
3737
decorated()
3838
assert demo_function.call_count == 0
39+
40+
41+
class SomeClass:
42+
@classmethod
43+
def some_classmethod(cls, arg): # type: ignore
44+
return arg # type: ignore
45+
46+
@classmethod
47+
def some_annotated_classmethod(cls, arg: int) -> int:
48+
return arg
49+
50+
51+
def test_wraps_classmethod(): # type: ignore
52+
some_class = SomeClass()
53+
some_class.some_classmethod = MagicMock()
54+
wrapped_method = wraps_classmethod( # type: ignore
55+
SomeClass.some_classmethod # type: ignore
56+
)( # type: ignore
57+
some_class.some_classmethod # type: ignore
58+
)
59+
wrapped_method(123)
60+
some_class.some_classmethod.assert_called_with(123) # type: ignore
61+
62+
63+
def test_wraps_classmethod(): # type: ignore
64+
some_class = SomeClass()
65+
some_class.some_annotated_classmethod = MagicMock()
66+
wrapped_method = wraps_classmethod(SomeClass.some_annotated_classmethod)(
67+
some_class.some_annotated_classmethod
68+
)
69+
wrapped_method(123) # type: ignore
70+
some_class.some_annotated_classmethod.assert_called_with(123)

_python_utils_tests/test_generators.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pytest
44

55
import python_utils
6+
from python_utils import types
67

78

89
@pytest.mark.asyncio
@@ -16,7 +17,7 @@ async def test_abatcher():
1617

1718
@pytest.mark.asyncio
1819
async def test_abatcher_timed():
19-
batches = []
20+
batches: types.List[types.List[int]] = []
2021
async for batch in python_utils.abatcher(
2122
python_utils.acount(stop=10, delay=0.08), interval=0.1
2223
):

_python_utils_tests/test_import.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
from python_utils import import_
1+
from python_utils import import_, types
22

33

44
def test_import_globals_relative_import():
55
for i in range(-1, 5):
66
relative_import(i)
77

88

9-
def relative_import(level):
10-
locals_ = {}
9+
def relative_import(level: int):
10+
locals_: types.Dict[str, types.Any] = {}
1111
globals_ = {'__name__': 'python_utils.import_'}
1212
import_.import_global('.formatters', locals_=locals_, globals_=globals_)
1313
assert 'camel_to_underscore' in globals_

_python_utils_tests/test_logger.py

+1
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ class MyClass(Logurud):
1515
my_class.info('info')
1616
my_class.warning('warning')
1717
my_class.error('error')
18+
my_class.critical('critical')
1819
my_class.exception('exception')
1920
my_class.log(0, 'log')

_python_utils_tests/test_time.py

+19-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pytest
66

77
import python_utils
8+
from python_utils import types
89

910

1011
@pytest.mark.parametrize(
@@ -25,7 +26,12 @@
2526
)
2627
@pytest.mark.asyncio
2728
async def test_aio_timeout_generator(
28-
timeout, interval, interval_multiplier, maximum_interval, iterable, result
29+
timeout: float,
30+
interval: float,
31+
interval_multiplier: float,
32+
maximum_interval: float,
33+
iterable: types.AsyncIterable[types.Any],
34+
result: int,
2935
):
3036
i = None
3137
async for i in python_utils.aio_timeout_generator(
@@ -40,21 +46,30 @@ async def test_aio_timeout_generator(
4046
'timeout,interval,interval_multiplier,maximum_interval,iterable,result',
4147
[
4248
(0.01, 0.006, 0.5, 0.01, 'abc', 'c'),
43-
(0.01, 0.006, 0.5, 0.01, itertools.count, 2),
49+
(0.01, 0.006, 0.5, 0.01, itertools.count, 2), # type: ignore
4450
(0.01, 0.006, 0.5, 0.01, itertools.count(), 2),
4551
(0.01, 0.006, 1.0, None, 'abc', 'c'),
4652
(
4753
timedelta(seconds=0.01),
4854
timedelta(seconds=0.006),
4955
2.0,
5056
timedelta(seconds=0.01),
51-
itertools.count,
57+
itertools.count, # type: ignore
5258
2,
5359
),
5460
],
5561
)
5662
def test_timeout_generator(
57-
timeout, interval, interval_multiplier, maximum_interval, iterable, result
63+
timeout: float,
64+
interval: float,
65+
interval_multiplier: float,
66+
maximum_interval: float,
67+
iterable: types.Union[
68+
str,
69+
types.Iterable[types.Any],
70+
types.Callable[..., types.Iterable[types.Any]],
71+
],
72+
result: int,
5873
):
5974
i = None
6075
for i in python_utils.timeout_generator(

pyproject.toml

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ target-version = ['py37', 'py38', 'py39', 'py310', 'py311']
44
skip-string-normalization = true
55

66
[tool.pyright]
7-
include = ['python_utils']
8-
strict = ['python_utils', '_python_utils_tests/test_aio.py']
7+
# include = ['python_utils']
8+
include = ['python_utils', '_python_utils_tests']
9+
strict = ['python_utils', '_python_utils_tests']
910
# The terminal file is very OS specific and dependent on imports so we're skipping it from type checking
1011
ignore = ['python_utils/terminal.py']
11-
pythonVersion = '3.8'
12+
pythonVersion = '3.8'

python_utils/__about__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
)
88
__url__: str = 'https://github.com/WoLpH/python-utils'
99
# Omit type info due to automatic versioning script
10-
__version__ = '3.6.1'
10+
__version__ = '3.7.0'

python_utils/aio.py

+30-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from . import types
99

1010
_N = types.TypeVar('_N', int, float)
11+
_T = types.TypeVar('_T')
1112

1213

1314
async def acount(
@@ -21,5 +22,33 @@ async def acount(
2122
if stop is not None and item >= stop:
2223
break
2324

24-
yield types.cast(_N, item)
25+
yield item
2526
await asyncio.sleep(delay)
27+
28+
29+
async def acontainer(
30+
iterable: types.Union[
31+
types.AsyncIterable[_T],
32+
types.Callable[..., types.AsyncIterable[_T]],
33+
],
34+
container: types.Callable[[types.Iterable[_T]], types.Iterable[_T]] = list,
35+
) -> types.Iterable[_T]:
36+
'''
37+
Asyncio version of list()/set()/tuple()/etc() using an async for loop
38+
39+
So instead of doing `[item async for item in iterable]` you can do
40+
`await acontainer(iterable)`.
41+
42+
'''
43+
iterable_: types.AsyncIterable[_T]
44+
if callable(iterable):
45+
iterable_ = iterable()
46+
else:
47+
iterable_ = iterable
48+
49+
item: _T
50+
items: types.List[_T] = []
51+
async for item in iterable_:
52+
items.append(item)
53+
54+
return container(items)

0 commit comments

Comments
 (0)