Skip to content

Commit

Permalink
Merge branch 'release/3.7.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
wolph committed Jun 22, 2023
2 parents f88e50d + 8a2e94f commit 54d2912
Show file tree
Hide file tree
Showing 13 changed files with 334 additions and 62 deletions.
16 changes: 15 additions & 1 deletion _python_utils_tests/test_aio.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import pytest
import asyncio


from python_utils import types
from python_utils.aio import acount
from python_utils.aio import acount, acontainer


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

assert len(sleeps) == 4
assert sum(sleeps) == 4


@pytest.mark.asyncio
async def test_acontainer():
async def async_gen():
yield 1
yield 2
yield 3

assert await acontainer(async_gen) == [1, 2, 3]
assert await acontainer(async_gen()) == [1, 2, 3]
assert await acontainer(async_gen, set) == {1, 2, 3}
assert await acontainer(async_gen(), set) == {1, 2, 3}
10 changes: 6 additions & 4 deletions _python_utils_tests/test_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from python_utils import containers


def test_unique_list_ignore():
a = containers.UniqueList()
def test_unique_list_ignore() -> None:
a: containers.UniqueList[int] = containers.UniqueList()
a.append(1)
a.append(1)
assert a == [1]
Expand All @@ -16,8 +16,10 @@ def test_unique_list_ignore():
a[3] = 5


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

Expand Down
44 changes: 38 additions & 6 deletions _python_utils_tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,69 @@

import pytest

from python_utils.decorators import sample
from python_utils.decorators import sample, wraps_classmethod


@pytest.fixture
def random(monkeypatch):
def random(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
mock = MagicMock()
monkeypatch.setattr(
"python_utils.decorators.random.random", mock, raising=True
'python_utils.decorators.random.random', mock, raising=True
)
return mock


def test_sample_called(random):
def test_sample_called(random: MagicMock):
demo_function = MagicMock()
decorated = sample(0.5)(demo_function)
random.return_value = 0.4
decorated()
random.return_value = 0.0
decorated()
args = [1, 2]
kwargs = {"1": 1, "2": 2}
kwargs = {'1': 1, '2': 2}
decorated(*args, **kwargs)
demo_function.assert_called_with(*args, **kwargs)
assert demo_function.call_count == 3


def test_sample_not_called(random):
def test_sample_not_called(random: MagicMock):
demo_function = MagicMock()
decorated = sample(0.5)(demo_function)
random.return_value = 0.5
decorated()
random.return_value = 1.0
decorated()
assert demo_function.call_count == 0


class SomeClass:
@classmethod
def some_classmethod(cls, arg): # type: ignore
return arg # type: ignore

@classmethod
def some_annotated_classmethod(cls, arg: int) -> int:
return arg


def test_wraps_classmethod(): # type: ignore
some_class = SomeClass()
some_class.some_classmethod = MagicMock()
wrapped_method = wraps_classmethod( # type: ignore
SomeClass.some_classmethod # type: ignore
)( # type: ignore
some_class.some_classmethod # type: ignore
)
wrapped_method(123)
some_class.some_classmethod.assert_called_with(123) # type: ignore


def test_wraps_classmethod(): # type: ignore
some_class = SomeClass()
some_class.some_annotated_classmethod = MagicMock()
wrapped_method = wraps_classmethod(SomeClass.some_annotated_classmethod)(
some_class.some_annotated_classmethod
)
wrapped_method(123) # type: ignore
some_class.some_annotated_classmethod.assert_called_with(123)
3 changes: 2 additions & 1 deletion _python_utils_tests/test_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest

import python_utils
from python_utils import types


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

@pytest.mark.asyncio
async def test_abatcher_timed():
batches = []
batches: types.List[types.List[int]] = []
async for batch in python_utils.abatcher(
python_utils.acount(stop=10, delay=0.08), interval=0.1
):
Expand Down
6 changes: 3 additions & 3 deletions _python_utils_tests/test_import.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from python_utils import import_
from python_utils import import_, types


def test_import_globals_relative_import():
for i in range(-1, 5):
relative_import(i)


def relative_import(level):
locals_ = {}
def relative_import(level: int):
locals_: types.Dict[str, types.Any] = {}
globals_ = {'__name__': 'python_utils.import_'}
import_.import_global('.formatters', locals_=locals_, globals_=globals_)
assert 'camel_to_underscore' in globals_
Expand Down
1 change: 1 addition & 0 deletions _python_utils_tests/test_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ class MyClass(Logurud):
my_class.info('info')
my_class.warning('warning')
my_class.error('error')
my_class.critical('critical')
my_class.exception('exception')
my_class.log(0, 'log')
23 changes: 19 additions & 4 deletions _python_utils_tests/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import pytest

import python_utils
from python_utils import types


@pytest.mark.parametrize(
Expand All @@ -25,7 +26,12 @@
)
@pytest.mark.asyncio
async def test_aio_timeout_generator(
timeout, interval, interval_multiplier, maximum_interval, iterable, result
timeout: float,
interval: float,
interval_multiplier: float,
maximum_interval: float,
iterable: types.AsyncIterable[types.Any],
result: int,
):
i = None
async for i in python_utils.aio_timeout_generator(
Expand All @@ -40,21 +46,30 @@ async def test_aio_timeout_generator(
'timeout,interval,interval_multiplier,maximum_interval,iterable,result',
[
(0.01, 0.006, 0.5, 0.01, 'abc', 'c'),
(0.01, 0.006, 0.5, 0.01, itertools.count, 2),
(0.01, 0.006, 0.5, 0.01, itertools.count, 2), # type: ignore
(0.01, 0.006, 0.5, 0.01, itertools.count(), 2),
(0.01, 0.006, 1.0, None, 'abc', 'c'),
(
timedelta(seconds=0.01),
timedelta(seconds=0.006),
2.0,
timedelta(seconds=0.01),
itertools.count,
itertools.count, # type: ignore
2,
),
],
)
def test_timeout_generator(
timeout, interval, interval_multiplier, maximum_interval, iterable, result
timeout: float,
interval: float,
interval_multiplier: float,
maximum_interval: float,
iterable: types.Union[
str,
types.Iterable[types.Any],
types.Callable[..., types.Iterable[types.Any]],
],
result: int,
):
i = None
for i in python_utils.timeout_generator(
Expand Down
7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ target-version = ['py37', 'py38', 'py39', 'py310', 'py311']
skip-string-normalization = true

[tool.pyright]
include = ['python_utils']
strict = ['python_utils', '_python_utils_tests/test_aio.py']
# include = ['python_utils']
include = ['python_utils', '_python_utils_tests']
strict = ['python_utils', '_python_utils_tests']
# The terminal file is very OS specific and dependent on imports so we're skipping it from type checking
ignore = ['python_utils/terminal.py']
pythonVersion = '3.8'
pythonVersion = '3.8'
2 changes: 1 addition & 1 deletion python_utils/__about__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
)
__url__: str = 'https://github.com/WoLpH/python-utils'
# Omit type info due to automatic versioning script
__version__ = '3.6.1'
__version__ = '3.7.0'
31 changes: 30 additions & 1 deletion python_utils/aio.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from . import types

_N = types.TypeVar('_N', int, float)
_T = types.TypeVar('_T')


async def acount(
Expand All @@ -21,5 +22,33 @@ async def acount(
if stop is not None and item >= stop:
break

yield types.cast(_N, item)
yield item
await asyncio.sleep(delay)


async def acontainer(
iterable: types.Union[
types.AsyncIterable[_T],
types.Callable[..., types.AsyncIterable[_T]],
],
container: types.Callable[[types.Iterable[_T]], types.Iterable[_T]] = list,
) -> types.Iterable[_T]:
'''
Asyncio version of list()/set()/tuple()/etc() using an async for loop
So instead of doing `[item async for item in iterable]` you can do
`await acontainer(iterable)`.
'''
iterable_: types.AsyncIterable[_T]
if callable(iterable):
iterable_ = iterable()
else:
iterable_ = iterable

item: _T
items: types.List[_T] = []
async for item in iterable_:
items.append(item)

return container(items)

0 comments on commit 54d2912

Please sign in to comment.