Skip to content

Commit

Permalink
Merge branch 'release/3.9.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
wolph committed Sep 25, 2024
2 parents a895709 + 108c0f5 commit 68c9703
Show file tree
Hide file tree
Showing 32 changed files with 1,390 additions and 364 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ exclude_lines =
@overload
@types.overload
@typing.overload
types.Protocol
24 changes: 13 additions & 11 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,47 @@ jobs:
timeout-minutes: 4
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11']
python-version: ['3.9', '3.10', '3.11', '3.12'] # Maybe soon?, '3.13']

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools flake8
python -m pip install --upgrade pip setuptools ruff
pip install -e '.[tests]'
- name: Get versions
run: |
python -V
pip freeze
- name: flake8
run: flake8 -v python_utils setup.py
- name: ruff
run: ruff check --output-format=github
- name: pytest
run: py.test

docs_and_lint:
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools
pip install -e '.[docs,tests]' pyright flake8 mypy
pip install -e '.[docs,tests]' pyright ruff mypy
- name: build docs
run: make html
working-directory: docs/
- name: flake8
run: flake8 -v python_utils setup.py
- name: ruff
run: ruff check --output-format=github
- name: mypy
run: mypy python_utils setup.py
- name: pyright
Expand Down
35 changes: 35 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"
# You can also specify other tool versions:
# nodejs: "20"
# rust: "1.70"
# golang: "1.20"

# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
# builder: "dirhtml"
# Fail on all warnings to avoid broken references
# fail_on_warning: true

# Optionally build your docs in additional formats such as PDF and ePub
formats:
- pdf
- epub

# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: docs/requirements.txt
10 changes: 8 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,14 @@ Run tests
$ py.test
```

Note that this won't run `flake8` yet, so once all the tests succeed you can run `flake8` to check for code style errors.
Note that this won't run `ruff` yet, so once all the tests succeed you can run `ruff check` to check for code style errors.

```bash
$ flake8
$ ruff check
```

Lastly we test the types using `pyright`:

```bash
$ pyright
```
46 changes: 36 additions & 10 deletions _python_utils_tests/test_aio.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,68 @@
from datetime import datetime
import pytest
import asyncio

import pytest

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


@pytest.mark.asyncio
async def test_acount(monkeypatch: pytest.MonkeyPatch):
async def test_acount(monkeypatch: pytest.MonkeyPatch) -> None:
sleeps: types.List[float] = []

async def mock_sleep(delay: float):
async def mock_sleep(delay: float) -> None:
sleeps.append(delay)

monkeypatch.setattr(asyncio, 'sleep', mock_sleep)

async for i in acount(delay=1, stop=3.5):
print('i', i, datetime.now())
async for _i in acount(delay=1, stop=3.5):
pass

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


@pytest.mark.asyncio
async def test_acontainer():
async def async_gen():
async def test_acontainer() -> None:
async def async_gen() -> types.AsyncIterable[int]:
yield 1
yield 2
yield 3

async def empty_gen():
async def empty_gen() -> types.AsyncIterable[int]:
if False:
yield 1

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}
assert await acontainer(async_gen, list) == [1, 2, 3]
assert await acontainer(async_gen(), list) == [1, 2, 3]
assert await acontainer(async_gen, tuple) == (1, 2, 3)
assert await acontainer(async_gen(), tuple) == (1, 2, 3)
assert await acontainer(empty_gen) == []
assert await acontainer(empty_gen()) == []
assert await acontainer(empty_gen, set) == set()
assert await acontainer(empty_gen(), set) == set()
assert await acontainer(empty_gen, list) == list()
assert await acontainer(empty_gen(), list) == list()
assert await acontainer(empty_gen, tuple) == tuple()
assert await acontainer(empty_gen(), tuple) == tuple()


@pytest.mark.asyncio
async def test_adict() -> None:
async def async_gen() -> types.AsyncIterable[types.Tuple[int, int]]:
yield 1, 2
yield 3, 4
yield 5, 6

async def empty_gen() -> types.AsyncIterable[types.Tuple[int, int]]:
if False:
yield 1, 2

assert await adict(async_gen) == {1: 2, 3: 4, 5: 6}
assert await adict(async_gen()) == {1: 2, 3: 4, 5: 6}
assert await adict(empty_gen) == {}
assert await adict(empty_gen()) == {}
29 changes: 15 additions & 14 deletions _python_utils_tests/test_decorators.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import typing
from unittest.mock import MagicMock

import pytest

from python_utils.decorators import sample, wraps_classmethod

T = typing.TypeVar('T')


@pytest.fixture
def random(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
Expand All @@ -14,7 +17,7 @@ def random(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
return mock


def test_sample_called(random: MagicMock):
def test_sample_called(random: MagicMock) -> None:
demo_function = MagicMock()
decorated = sample(0.5)(demo_function)
random.return_value = 0.4
Expand All @@ -28,7 +31,7 @@ def test_sample_called(random: MagicMock):
assert demo_function.call_count == 3


def test_sample_not_called(random: MagicMock):
def test_sample_not_called(random: MagicMock) -> None:
demo_function = MagicMock()
decorated = sample(0.5)(demo_function)
random.return_value = 0.5
Expand All @@ -40,31 +43,29 @@ def test_sample_not_called(random: MagicMock):

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

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


def test_wraps_classmethod(): # type: ignore
def test_wraps_classmethod() -> None:
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
some_class.some_classmethod = MagicMock() # type: ignore[method-assign]
wrapped_method = wraps_classmethod(SomeClass.some_classmethod)(
some_class.some_classmethod
)
wrapped_method(123)
some_class.some_classmethod.assert_called_with(123) # type: ignore
some_class.some_classmethod.assert_called_with(123)


def test_wraps_annotated_classmethod(): # type: ignore
def test_wraps_annotated_classmethod() -> None:
some_class = SomeClass()
some_class.some_annotated_classmethod = MagicMock()
some_class.some_annotated_classmethod = MagicMock() # type: ignore[method-assign]
wrapped_method = wraps_classmethod(SomeClass.some_annotated_classmethod)(
some_class.some_annotated_classmethod
)
wrapped_method(123) # type: ignore
wrapped_method(123)
some_class.some_annotated_classmethod.assert_called_with(123)
10 changes: 5 additions & 5 deletions _python_utils_tests/test_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


@pytest.mark.asyncio
async def test_abatcher():
async def test_abatcher() -> None:
async for batch in python_utils.abatcher(python_utils.acount(stop=9), 3):
assert len(batch) == 3

Expand All @@ -28,8 +28,8 @@ async def test_abatcher_timed() -> None:


@pytest.mark.asyncio
async def test_abatcher_timed_with_timeout():
async def generator():
async def test_abatcher_timed_with_timeout() -> None:
async def generator() -> types.AsyncIterator[int]:
# Test if the timeout is respected
yield 0
yield 1
Expand Down Expand Up @@ -57,12 +57,12 @@ async def generator():
await batcher.__anext__()


def test_batcher():
def test_batcher() -> None:
batch = []
for batch in python_utils.batcher(range(9), 3):
assert len(batch) == 3

for batch in python_utils.batcher(range(4), 3):
pass
assert batch is not None

assert len(batch) == 1
22 changes: 11 additions & 11 deletions _python_utils_tests/test_import.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
from python_utils import import_, types


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


def relative_import(level: int):
def relative_import(level: int) -> None:
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_


def test_import_globals_without_inspection():
locals_ = {}
globals_ = {'__name__': __name__}
def test_import_globals_without_inspection() -> None:
locals_: types.Dict[str, types.Any] = {}
globals_: types.Dict[str, types.Any] = {'__name__': __name__}
import_.import_global(
'python_utils.formatters', locals_=locals_, globals_=globals_
)
assert 'camel_to_underscore' in globals_


def test_import_globals_single_method():
locals_ = {}
globals_ = {'__name__': __name__}
def test_import_globals_single_method() -> None:
locals_: types.Dict[str, types.Any] = {}
globals_: types.Dict[str, types.Any] = {'__name__': __name__}
import_.import_global(
'python_utils.formatters',
['camel_to_underscore'],
Expand All @@ -34,19 +34,19 @@ def test_import_globals_single_method():
assert 'camel_to_underscore' in globals_


def test_import_globals_with_inspection():
def test_import_globals_with_inspection() -> None:
import_.import_global('python_utils.formatters')
assert 'camel_to_underscore' in globals()


def test_import_globals_missing_module():
def test_import_globals_missing_module() -> None:
import_.import_global(
'python_utils.spam', exceptions=ImportError, locals_=locals()
)
assert 'camel_to_underscore' in globals()


def test_import_locals_missing_module():
def test_import_locals_missing_module() -> None:
import_.import_global(
'python_utils.spam', exceptions=ImportError, globals_=globals()
)
Expand Down
4 changes: 2 additions & 2 deletions _python_utils_tests/test_logger.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# mypy: disable-error-code=misc
import pytest

from python_utils.loguru import Logurud


loguru = pytest.importorskip('loguru')


def test_logurud():
def test_logurud() -> None:
class MyClass(Logurud):
pass

Expand Down
2 changes: 1 addition & 1 deletion _python_utils_tests/test_python_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from python_utils import __about__


def test_definitions():
def test_definitions() -> None:
# The setup.py requires this so we better make sure they exist :)
assert __about__.__version__
assert __about__.__author__
Expand Down
Loading

0 comments on commit 68c9703

Please sign in to comment.