Skip to content

Commit

Permalink
Merge pull request #64 from alphatwirl/dev
Browse files Browse the repository at this point in the history
Support Python 3.9
  • Loading branch information
TaiSakuma authored Sep 21, 2024
2 parents e282fdc + eee188e commit 763e508
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12"]
jupyter: ["true", "false"]

steps:
Expand Down
2 changes: 2 additions & 0 deletions atpbar/callback.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from collections.abc import Iterator
from contextlib import contextmanager
from threading import Lock, current_thread, main_thread
Expand Down
2 changes: 2 additions & 0 deletions atpbar/funcs.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import atexit
import contextlib
import multiprocessing
Expand Down
2 changes: 2 additions & 0 deletions atpbar/machine.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import abc
from collections.abc import Iterator
from contextlib import AbstractContextManager, contextmanager
Expand Down
6 changes: 5 additions & 1 deletion atpbar/stream/type.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import sys
from enum import Enum
from multiprocessing import Queue
from typing import TypeAlias

if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias

class FD(Enum):
STDOUT = 1
Expand Down
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "atpbar"
dynamic = ["version"]
description = "Progress bars for threading and multiprocessing tasks"
readme = "README.md"
requires-python = ">=3.10"
requires-python = ">=3.9"
license = ""
authors = [{ name = "Tai Sakuma", email = "[email protected]" }]
classifiers = [
Expand All @@ -16,11 +16,14 @@ classifiers = [
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: BSD License",
"Programming Language :: Python",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]

dependencies = [
"typing_extensions>=4.12; python_version < '3.10'",
]
[project.optional-dependencies]
jupyter = ["ipywidgets>=8.1"]
tests = [
Expand Down
9 changes: 9 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import os
import sys
from pathlib import Path

import pytest
from hypothesis import settings

if os.getenv('GITHUB_ACTIONS') == 'true':
settings.register_profile('ci', deadline=None)
settings.load_profile('ci')


def pytest_ignore_collect(collection_path: Path, config: pytest.Config) -> bool:
if collection_path.name == 'test_state.py' and sys.version_info < (3, 10):
return True
return False
10 changes: 10 additions & 0 deletions tests/stream/output/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import sys
from pathlib import Path

import pytest


def pytest_ignore_collect(collection_path: Path, config: pytest.Config) -> bool:
if collection_path.name == 'test_print.py' and sys.version_info < (3, 10):
return True
return False
165 changes: 165 additions & 0 deletions tests/test_state_py39.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import sys
from unittest.mock import MagicMock, Mock, call

import pytest
from hypothesis import given, settings
from hypothesis import strategies as st

from atpbar.machine import (
Active,
Callback,
Disabled,
Initial,
ProgressReporter,
Registered,
State,
Yielded,
)

pytestmark = pytest.mark.skipif(sys.version_info >= (3, 10), reason='for Python 3.9')


class StatefulTest:
def __init__(self) -> None:
self.callback = MagicMock(spec=Callback)
self.reporter = Mock(spec=ProgressReporter)
self.callback.reporter = self.reporter
self.state: State = Initial(callback=self.callback)

def prepare_reporter(self) -> None:
prev = self.state
self.callback.reset_mock()
self.state = prev.prepare_reporter()
if isinstance(prev, Initial):
assert isinstance(self.state, Active)
assert self.callback.mock_calls == [call.on_active()]
else:
assert self.state is prev
assert self.callback.mock_calls == []

def register_reporter(self) -> None:
prev = self.state
self.callback.reset_mock()
reporter = Mock(spec=ProgressReporter)
self.state = prev.register_reporter(reporter)
assert isinstance(self.state, Registered)
assert self.callback.mock_calls == [call.on_registered(reporter)]

def disable(self) -> None:
prev = self.state
self.callback.reset_mock()
self.state = prev.disable()
assert isinstance(self.state, Disabled)
assert self.callback.mock_calls == [call.on_disabled()]

def fetch_reporter(self) -> None:
prev = self.state
self.callback.reset_mock()

with prev.fetch_reporter() as reporter:
if isinstance(prev, Active):
assert self.callback.mock_calls == [
call.fetch_reporter_in_active(),
call.fetch_reporter_in_active().__enter__(),
]
assert (
reporter
is self.callback.fetch_reporter_in_active.return_value.__enter__.return_value
)
elif isinstance(prev, Yielded):
assert self.callback.mock_calls == [
call.fetch_reporter_in_yielded(),
call.fetch_reporter_in_yielded().__enter__(),
]
assert (
reporter
is self.callback.fetch_reporter_in_yielded.return_value.__enter__.return_value
)
elif isinstance(prev, Registered):
assert self.callback.mock_calls == [
call.fetch_reporter_in_registered(),
call.fetch_reporter_in_registered().__enter__(),
]
assert (
reporter
is self.callback.fetch_reporter_in_registered.return_value.__enter__.return_value
)
elif isinstance(prev, Disabled):
assert self.callback.mock_calls == [
call.fetch_reporter_in_disabled(),
call.fetch_reporter_in_disabled().__enter__(),
]
assert (
reporter
is self.callback.fetch_reporter_in_disabled.return_value.__enter__.return_value
)
else:
assert self.callback.mock_calls == []
assert reporter is self.reporter

def on_yielded(self) -> None:
prev = self.state
self.callback.reset_mock()
self.state = prev.on_yielded()
if isinstance(prev, Active):
assert isinstance(self.state, Yielded)
else:
assert self.state is prev
assert self.callback.mock_calls == []

def on_resumed(self) -> None:
prev = self.state
self.callback.reset_mock()
self.state = prev.on_resumed()
if isinstance(prev, Yielded):
assert isinstance(self.state, Active)
assert self.state is prev._active
else:
assert self.state is prev
assert self.callback.mock_calls == []

def flush(self) -> None:
prev = self.state
self.callback.reset_mock()
self.state = prev.flush()
if isinstance(prev, Initial):
assert isinstance(self.state, Active)
assert self.callback.mock_calls == [call.on_active()]
elif isinstance(prev, Active):
assert self.state is prev
assert self.callback.mock_calls == [call.flush_in_active()]
else:
assert self.state is prev
assert self.callback.mock_calls == []

def shutdown(self) -> None:
prev = self.state
self.callback.reset_mock()
self.state = prev.shutdown()
assert isinstance(self.state, Initial)
if isinstance(prev, Active):
assert self.callback.mock_calls == [call.shutdown_in_active()]
else:
assert self.callback.mock_calls == []


@settings(max_examples=500)
@given(data=st.data())
def test_state(data: st.DataObject) -> None:
test = StatefulTest()

METHODS = [
test.prepare_reporter,
test.register_reporter,
test.disable,
test.fetch_reporter,
test.on_yielded,
test.on_resumed,
test.flush,
test.shutdown,
]

methods = data.draw(st.lists(st.sampled_from(METHODS)))

for method in methods:
method()

0 comments on commit 763e508

Please sign in to comment.