Skip to content

Commit

Permalink
Merge branch 'release/3.4.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
wolph committed Oct 29, 2022
2 parents 1aa3a08 + 3d1a09a commit ec05016
Show file tree
Hide file tree
Showing 14 changed files with 112 additions and 59 deletions.
18 changes: 12 additions & 6 deletions .github/workflows/main.yml
Expand Up @@ -14,9 +14,9 @@ jobs:
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11-dev']

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand All @@ -32,19 +32,25 @@ jobs:
- name: pytest
run: py.test

docs:
docs_and_lint:
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v3
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools
pip install -e '.[docs]'
pip install -e '.[docs,tests]' pyright flake8 mypy
- name: build docs
run: make html
working-directory: docs/
- name: flake8
run: flake8 -v python_utils setup.py
- name: mypy
run: mypy python_utils setup.py
- name: pyright
run: pyright
22 changes: 14 additions & 8 deletions _python_utils_tests/test_time.py
Expand Up @@ -71,6 +71,9 @@ def test_timeout_generator(

@pytest.mark.asyncio
async def test_aio_generator_timeout_detector():
# Make pyright happy
i = None

async def generator():
for i in range(10):
await asyncio.sleep(i / 100.0)
Expand Down Expand Up @@ -106,52 +109,55 @@ async def generator():

@pytest.mark.asyncio
async def test_aio_generator_timeout_detector_decorator():
# Make pyright happy
i = None

# Test regular timeout with reraise
@python_utils.aio_generator_timeout_detector_decorator(timeout=0.05)
async def generator():
async def generator_timeout():
for i in range(10):
await asyncio.sleep(i / 100.0)
yield i

with pytest.raises(asyncio.TimeoutError):
async for i in generator():
async for i in generator_timeout():
pass

# Test regular timeout with clean exit
@python_utils.aio_generator_timeout_detector_decorator(
timeout=0.05, on_timeout=None
)
async def generator():
async def generator_clean():
for i in range(10):
await asyncio.sleep(i / 100.0)
yield i

async for i in generator():
async for i in generator_clean():
pass

assert i == 4

# Test total timeout with reraise
@python_utils.aio_generator_timeout_detector_decorator(total_timeout=0.1)
async def generator():
async def generator_reraise():
for i in range(10):
await asyncio.sleep(i / 100.0)
yield i

with pytest.raises(asyncio.TimeoutError):
async for i in generator():
async for i in generator_reraise():
pass

# Test total timeout with clean exit
@python_utils.aio_generator_timeout_detector_decorator(
total_timeout=0.1, on_timeout=None
)
async def generator():
async def generator_clean_total():
for i in range(10):
await asyncio.sleep(i / 100.0)
yield i

async for i in generator():
async for i in generator_clean_total():
pass

assert i == 4
8 changes: 8 additions & 0 deletions pyrightconfig.json
@@ -0,0 +1,8 @@
{
"include": [
"python_utils"
],
"exclude": [
"python_utils/types.py",
],
}
2 changes: 1 addition & 1 deletion python_utils/__about__.py
Expand Up @@ -7,4 +7,4 @@
)
__url__: str = 'https://github.com/WoLpH/python-utils'
# Omit type info due to automatic versioning script
__version__ = '3.3.3'
__version__ = '3.4.0'
9 changes: 8 additions & 1 deletion python_utils/aio.py
Expand Up @@ -5,8 +5,15 @@
import asyncio
import itertools

from . import types

async def acount(start=0, step=1, delay=0, stop=None):

async def acount(
start: float = 0,
step: float = 1,
delay: float = 0,
stop: types.Optional[float] = None,
) -> types.AsyncIterator[float]:
'''Asyncio version of itertools.count()'''
for item in itertools.count(start, step): # pragma: no branch
if stop is not None and item >= stop:
Expand Down
14 changes: 7 additions & 7 deletions python_utils/containers.py
Expand Up @@ -31,8 +31,8 @@ def __init__(
self,
key_cast: KT_cast = None,
value_cast: VT_cast = None,
*args,
**kwargs,
*args: DictUpdateArgs,
**kwargs: VT,
) -> None:
self._value_cast = value_cast
self._key_cast = key_cast
Expand All @@ -53,7 +53,7 @@ def __setitem__(self, key: Any, value: Any) -> None:
return super().__setitem__(key, value)


class CastedDict(CastedDictBase):
class CastedDict(CastedDictBase[KT, VT]):
'''
Custom dictionary that casts keys and values to the specified typing.
Expand Down Expand Up @@ -88,14 +88,14 @@ class CastedDict(CastedDictBase):
{1: 2, '3': '4', '5': '6', '7': '8'}
'''

def __setitem__(self, key, value):
def __setitem__(self, key: Any, value: Any) -> None:
if self._value_cast is not None:
value = self._value_cast(value)

super().__setitem__(key, value)


class LazyCastedDict(CastedDictBase):
class LazyCastedDict(CastedDictBase[KT, VT]):
'''
Custom dictionary that casts keys and lazily casts values to the specified
typing. Note that the values are cast only when they are accessed and
Expand Down Expand Up @@ -141,13 +141,13 @@ class LazyCastedDict(CastedDictBase):
'4'
'''

def __setitem__(self, key, value):
def __setitem__(self, key: Any, value: Any) -> None:
if self._key_cast is not None:
key = self._key_cast(key)

super().__setitem__(key, value)

def __getitem__(self, key) -> VT:
def __getitem__(self, key: Any) -> VT:
if self._key_cast is not None:
key = self._key_cast(key)

Expand Down
4 changes: 2 additions & 2 deletions python_utils/converters.py
Expand Up @@ -10,7 +10,7 @@ def to_int(
input_: typing.Optional[str] = None,
default: int = 0,
exception: types.ExceptionsType = (ValueError, TypeError),
regexp: types.O[types.Pattern] = None,
regexp: types.Optional[types.Pattern] = None,
) -> int:
r'''
Convert the given input to an integer or return default
Expand Down Expand Up @@ -100,7 +100,7 @@ def to_float(
input_: str,
default: int = 0,
exception: types.ExceptionsType = (ValueError, TypeError),
regexp: types.O[types.Pattern] = None,
regexp: types.Optional[types.Pattern] = None,
) -> types.Number:
r'''
Convert the given `input_` to an integer or return default
Expand Down
6 changes: 4 additions & 2 deletions python_utils/generators.py
Expand Up @@ -9,7 +9,7 @@ async def abatcher(
generator: types.AsyncIterator,
batch_size: types.Optional[int] = None,
interval: types.Optional[types.delta_type] = None,
):
) -> types.AsyncIterator[list]:
'''
Asyncio generator wrapper that returns items with a given batch size or
interval (whichever is reached first).
Expand Down Expand Up @@ -64,7 +64,9 @@ async def abatcher(
next_yield = time.perf_counter() + interval_s


def batcher(iterable, batch_size):
def batcher(
iterable: types.Iterable, batch_size: int = 10
) -> types.Iterator[list]:
'''
Generator wrapper that returns items with a given batch size
'''
Expand Down
8 changes: 4 additions & 4 deletions python_utils/loguru.py
@@ -1,15 +1,15 @@
from __future__ import annotations

from . import logger

import loguru

from . import logger as logger_module

__all__ = ['Logurud']


class Logurud(logger.LoggerBase):
class Logurud(logger_module.LoggerBase):
logger: loguru.Logger

def __new__(cls, *args, **kwargs):
cls.logger: loguru.Loguru = loguru.logger.opt(depth=1)
cls.logger: loguru.Logger = loguru.logger.opt(depth=1)
return super().__new__(cls)
Empty file added python_utils/py.typed
Empty file.
38 changes: 21 additions & 17 deletions python_utils/terminal.py
Expand Up @@ -3,8 +3,11 @@

from . import converters

Dimensions = typing.Tuple[int, int]
OptionalDimensions = typing.Optional[Dimensions]

def get_terminal_size() -> typing.Tuple[int, int]: # pragma: no cover

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

try:
w, h = _get_terminal_size_linux()
if w and h:
return w, h
# The method can return None so we don't unpack it
wh = _get_terminal_size_linux()
if wh is not None and all(wh):
return wh
except Exception: # pragma: no cover
pass

try:
# Windows detection doesn't always work, let's try anyhow
w, h = _get_terminal_size_windows()
if w and h:
return w, h
wh = _get_terminal_size_windows()
if wh is not None and all(wh):
return wh
except Exception: # pragma: no cover
pass

try:
# needed for window's python in cygwin's xterm!
w, h = _get_terminal_size_tput()
if w and h:
return w, h
wh = _get_terminal_size_tput()
if wh is not None and all(wh):
return wh
except Exception: # pragma: no cover
pass

return 79, 24


def _get_terminal_size_windows(): # pragma: no cover
def _get_terminal_size_windows() -> OptionalDimensions: # pragma: no cover
res = None
try:
from ctypes import windll, create_string_buffer
from ctypes import windll, create_string_buffer # type: ignore

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


def _get_terminal_size_tput(): # pragma: no cover
def _get_terminal_size_tput() -> OptionalDimensions: # pragma: no cover
# get terminal width src: http://stackoverflow.com/questions/263890/
try:
import subprocess
Expand All @@ -141,19 +145,19 @@ def _get_terminal_size_tput(): # pragma: no cover
return None


def _get_terminal_size_linux(): # pragma: no cover
def _get_terminal_size_linux() -> OptionalDimensions: # pragma: no cover
def ioctl_GWINSZ(fd):
try:
import fcntl
import termios
import struct

size = struct.unpack(
'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')
return struct.unpack(
'hh',
fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'), # type: ignore
)
except Exception:
return None
return size

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

Expand Down
6 changes: 3 additions & 3 deletions python_utils/types.py
@@ -1,18 +1,18 @@
import datetime
import decimal
from typing import * # pragma: no cover
from typing import * # type: ignore # pragma: no cover

# import * does not import Pattern
from typing import Pattern

# Quickhand for optional because it gets so much use. If only Python had
# support for an optional type shorthand such as `SomeType?` instead of
# `Optional[SomeType]`.
from typing import Optional as O
from typing import Optional as O # noqa

# Since the Union operator is only supported for Python 3.10, we'll create a
# shorthand for it.
from typing import Union as U
from typing import Union as U # noqa

Scope = Dict[str, Any]
OptionalScope = O[Scope]
Expand Down
7 changes: 7 additions & 0 deletions setup.cfg
Expand Up @@ -32,4 +32,11 @@ sign = 1
[flake8]
per-file-ignores =
python_utils/types.py: F403,F405
ignore = W391, W503, E741, E203
exclude =
docs

[mypy]
files =
python_utils,
_python_utils_tests

0 comments on commit ec05016

Please sign in to comment.