diff --git a/CITATION.cff b/CITATION.cff index bc61fd7..9c3570a 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -8,15 +8,12 @@ identifiers: - type: doi value: 10.5281/zenodo.10799219 description: This is the collection of archived snapshots of all versions of thread. - - type: doi - value: 10.5281/zenodo.10808243 - description: This is the archived snapshot of version 1.0.1 of thread. - type: url - value: https://github.com/python-thread/thread/releases/tag/v1.0.1 - description: The GitHub release URL of tag v1.0.1. + value: https://github.com/python-thread/thread/releases/tag/v1.1.0 + description: The GitHub release URL of tag v1.1.0. - type: url - value: https://pypi.org/project/thread/1.0.1 - description: The PyPI release URL of tag v1.0.1. + value: https://pypi.org/project/thread/1.1.0 + description: The PyPI release URL of tag v1.1.0. cff-version: 1.2.0 date-released: 2024-03-07 keywords: @@ -35,6 +32,6 @@ repository-code: https://github.com/python-thread/thread repository-artifact: https://pypi.org/project/thread title: thread type: software -version: 1.0.1 +version: 1.1.0 url: https://thread.ngjx.org diff --git a/README.md b/README.md index f9efdb3..dd695f4 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ Our docs are [here!](https://thread.ngjx.org) ## Roadmap -- [x] v1.0.0 Release +- [x] v1.1.0 Release - [ ] Bug fixes - [ ] New features - [ ] Testing diff --git a/pyproject.toml b/pyproject.toml index 0a33f26..4349309 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "thread" -version = "1.0.1" +version = "1.1.0" description = "Threading module extension" authors = ["Alex "] license = "BSD-3-Clause" diff --git a/src/thread/__init__.py b/src/thread/__init__.py index ee6bf80..0691236 100644 --- a/src/thread/__init__.py +++ b/src/thread/__init__.py @@ -1,6 +1,6 @@ """ ## Thread Library -Documentation at https://thread.ngjx.org/docs/v1.0.0 +Documentation at https://thread.ngjx.org/docs/v1.1.0 --- @@ -18,7 +18,7 @@ """ -__version__ = '1.0.1' +__version__ = '1.1.0' # Export Core diff --git a/src/thread/_types.py b/src/thread/_types.py index 3f81b10..7f973ae 100644 --- a/src/thread/_types.py +++ b/src/thread/_types.py @@ -1,11 +1,17 @@ """ ## Types -Documentation: https://thread.ngjx.org/docs/v1.0.0 +Documentation: https://thread.ngjx.org/docs/v1.1.0 """ -from typing import Any, Literal, Callable, Union -from typing_extensions import ParamSpec, TypeVar, Concatenate +from typing import Any, Literal, Callable, Union, Sized +from typing_extensions import ( + ParamSpec, + TypeVar, + Concatenate, + Protocol, + runtime_checkable, +) # Descriptive Types @@ -33,5 +39,26 @@ HookFunction = Callable[[_Target_T], Union[Any, None]] -_Dataset_T = TypeVar('_Dataset_T') +_Dataset_T = TypeVar('_Dataset_T', covariant=True) DatasetFunction = Callable[Concatenate[_Dataset_T, _Target_P], _Target_T] + + +# Protocols +@runtime_checkable +class SupportsLength(Sized, Protocol): + pass + + +_SupportsGetItem_T = TypeVar('_SupportsGetItem_T') + + +@runtime_checkable +class SupportsGetItem(Protocol[_SupportsGetItem_T]): + __getitem__: Callable[..., _SupportsGetItem_T] + + +# Looks like having this inherit __getitem__ from SupportsGetItem breaks isinstance checks in python3.12 +# Thus we explicitly define it +@runtime_checkable +class SupportsLengthGetItem(Sized, Protocol[_SupportsGetItem_T]): + __getitem__: Callable[..., _SupportsGetItem_T] diff --git a/src/thread/decorators/_processor.py b/src/thread/decorators/_processor.py index 505d58c..55383a7 100644 --- a/src/thread/decorators/_processor.py +++ b/src/thread/decorators/_processor.py @@ -1,14 +1,20 @@ """ ## Processor -Documentation: https://thread.ngjx.org/docs/v1.0.0 +Documentation: https://thread.ngjx.org/docs/v1.1.0 """ from functools import wraps from ..thread import ParallelProcessing -from .._types import Overflow_In, Data_In -from typing import Callable, Mapping, Sequence, Optional, Union, overload +from .._types import ( + Overflow_In, + Data_In, + SupportsGetItem, + SupportsLength, + SupportsLengthGetItem, +) +from typing import Any, Callable, Mapping, Sequence, Optional, Union, overload from typing_extensions import ParamSpec, TypeVar, Concatenate @@ -16,10 +22,13 @@ _TargetP = ParamSpec('_TargetP') _DataT = TypeVar('_DataT') TargetFunction = Callable[Concatenate[_DataT, _TargetP], _TargetT] +Dataset = Union[ + SupportsLengthGetItem[_DataT], SupportsGetItem[_DataT], SupportsLength, Any +] NoParamReturn = Callable[ - Concatenate[Sequence[_DataT], _TargetP], + Concatenate[Dataset[_DataT], _TargetP], ParallelProcessing[_TargetP, _TargetT, _DataT], ] WithParamReturn = Callable[ @@ -27,7 +36,7 @@ NoParamReturn[_DataT, _TargetP, _TargetT], ] FullParamReturn = Callable[ - Concatenate[Sequence[_DataT], _TargetP], + Concatenate[Dataset[_DataT], _TargetP], ParallelProcessing[_TargetP, _TargetT, _DataT], ] @@ -35,8 +44,7 @@ @overload def processor( __function: TargetFunction[_DataT, _TargetP, _TargetT], -) -> NoParamReturn[_DataT, _TargetP, _TargetT]: - ... +) -> NoParamReturn[_DataT, _TargetP, _TargetT]: ... @overload @@ -47,8 +55,7 @@ def processor( ignore_errors: Sequence[type[Exception]] = (), suppress_errors: bool = False, **overflow_kwargs: Overflow_In, -) -> WithParamReturn[_DataT, _TargetP, _TargetT]: - ... +) -> WithParamReturn[_DataT, _TargetP, _TargetT]: ... @overload @@ -60,8 +67,7 @@ def processor( ignore_errors: Sequence[type[Exception]] = (), suppress_errors: bool = False, **overflow_kwargs: Overflow_In, -) -> FullParamReturn[_DataT, _TargetP, _TargetT]: - ... +) -> FullParamReturn[_DataT, _TargetP, _TargetT]: ... def processor( @@ -106,8 +112,7 @@ def processor( You can also pass keyword arguments to change the thread behaviour, it otherwise follows the defaults of `thread.Thread` >>> @thread.threaded(daemon = True) - >>> def myfunction(): - ... ... + >>> def myfunction(): ... Args will be ordered infront of function-parsed args parsed into `thread.Thread.args` >>> @thread.threaded(args = (1)) @@ -142,7 +147,7 @@ def wrapper( @wraps(__function) def wrapped( - data: Sequence[_DataT], + data: Dataset[_DataT], *parsed_args: _TargetP.args, **parsed_kwargs: _TargetP.kwargs, ) -> ParallelProcessing[_TargetP, _TargetT, _DataT]: diff --git a/src/thread/decorators/_threaded.py b/src/thread/decorators/_threaded.py index 0f3a06c..06c14a3 100644 --- a/src/thread/decorators/_threaded.py +++ b/src/thread/decorators/_threaded.py @@ -1,7 +1,7 @@ """ ## Threaded -Documentation: https://thread.ngjx.org/docs/v1.0.0 +Documentation: https://thread.ngjx.org/docs/v1.1.0 """ from functools import wraps @@ -23,8 +23,7 @@ @overload -def threaded(__function: TargetFunction[P, T]) -> NoParamReturn[P, T]: - ... +def threaded(__function: TargetFunction[P, T]) -> NoParamReturn[P, T]: ... @overload @@ -35,8 +34,7 @@ def threaded( ignore_errors: Sequence[type[Exception]] = (), suppress_errors: bool = False, **overflow_kwargs: Overflow_In, -) -> WithParamReturn[P, T]: - ... +) -> WithParamReturn[P, T]: ... @overload @@ -48,8 +46,7 @@ def threaded( ignore_errors: Sequence[type[Exception]] = (), suppress_errors: bool = False, **overflow_kwargs: Overflow_In, -) -> FullParamReturn[P, T]: - ... +) -> FullParamReturn[P, T]: ... def threaded( @@ -90,8 +87,7 @@ def threaded( You can also pass keyword arguments to change the thread behaviour, it otherwise follows the defaults of `thread.Thread` >>> @thread.threaded(daemon = True) - >>> def myfunction(): - ... ... + >>> def myfunction(): ... Args will be ordered infront of function-parsed args parsed into `thread.Thread.args` >>> @thread.threaded(args = (1)) diff --git a/src/thread/exceptions.py b/src/thread/exceptions.py index 184ee18..dcc3751 100644 --- a/src/thread/exceptions.py +++ b/src/thread/exceptions.py @@ -1,7 +1,7 @@ """ ## Thread Exceptions -Documentation: https://thread.ngjx.org/docs/v1.0.0 +Documentation: https://thread.ngjx.org/docs/v1.1.0 """ import traceback diff --git a/src/thread/thread.py b/src/thread/thread.py index c273337..1c2d7d2 100644 --- a/src/thread/thread.py +++ b/src/thread/thread.py @@ -3,10 +3,12 @@ ```py class Thread: ... + + class ParallelProcessing: ... ``` -Documentation: https://thread.ngjx.org/docs/v1.0.0 +Documentation: https://thread.ngjx.org/docs/v1.1.0 """ import sys @@ -31,9 +33,23 @@ class ParallelProcessing: ... DatasetFunction, _Dataset_T, HookFunction, + SupportsLength, + SupportsGetItem, + SupportsLengthGetItem, +) +from typing_extensions import Generic +from typing import ( + Any, + List, + Optional, + Union, + Mapping, + Sequence, + Tuple, + Callable, + Generator, + overload, ) -from typing_extensions import Generic, ParamSpec -from typing import List, Optional, Union, Mapping, Sequence, Tuple, Generator Threads: set['Thread'] = set() @@ -273,11 +289,11 @@ def kill(self, yielding: bool = False, timeout: float = 5) -> bool: self.status = 'Kill Scheduled' - res: int = ctypes.pythonapi.PyThreadState_SetAsyncExc( + res: Optional[int] = self.ident and ctypes.pythonapi.PyThreadState_SetAsyncExc( ctypes.c_long(self.ident), ctypes.py_object(SystemExit) ) - if res == 0: + if not res or res == 0: raise ValueError('Thread IDENT does not exist') elif res > 1: # Unexpected behaviour, something seriously went wrong @@ -313,9 +329,6 @@ def start(self) -> None: super().start() -_P = ParamSpec('_P') - - class _ThreadWorker: progress: float thread: Thread @@ -333,23 +346,107 @@ class ParallelProcessing(Generic[_Target_P, _Target_T, _Dataset_T]): Type-Safe and provides more functionality on top """ + _length: int + _retrieve_value: Callable[[Any, int], _Dataset_T] _threads: List[_ThreadWorker] _completed: int status: ThreadStatus function: TargetFunction - dataset: Sequence[Data_In] + dataset: Union[ + SupportsLengthGetItem[_Dataset_T], + SupportsGetItem[_Dataset_T], + SupportsLength, + Any, + ] max_threads: int overflow_args: Sequence[Overflow_In] overflow_kwargs: Mapping[str, Overflow_In] + # Has __len__ and __getitem__ + @overload + def __init__( + self, + function: DatasetFunction[_Dataset_T, _Target_P, _Target_T], + dataset: SupportsLengthGetItem[_Dataset_T], + max_threads: int = 8, + *overflow_args: Overflow_In, + _get_value: Optional[ + Callable[[SupportsLengthGetItem[_Dataset_T], int], _Dataset_T] + ] = None, + _length: Optional[ + Union[int, Callable[[SupportsLengthGetItem[_Dataset_T]], int]] + ] = None, + **overflow_kwargs: Overflow_In, + ) -> None: ... + + # Has __len__, require _get_value to be set + @overload + def __init__( + self, + function: DatasetFunction[_Dataset_T, _Target_P, _Target_T], + dataset: SupportsLength, + max_threads: int = 8, + *overflow_args: Overflow_In, + _get_value: Callable[[SupportsLength, int], _Dataset_T], + _length: Optional[Union[int, Callable[[SupportsLength], int]]] = None, + **overflow_kwargs: Overflow_In, + ) -> None: ... + + # Has __getitem__, require _length to be set + @overload + def __init__( + self, + function: DatasetFunction[_Dataset_T, _Target_P, _Target_T], + dataset: SupportsGetItem[_Dataset_T], + max_threads: int = 8, + *overflow_args: Overflow_In, + _get_value: Optional[ + Callable[[SupportsGetItem[_Dataset_T], int], _Dataset_T] + ] = None, + _length: Union[int, Callable[[SupportsGetItem[_Dataset_T]], int]], + **overflow_kwargs: Overflow_In, + ) -> None: ... + + # Does not support __getitem__ and __len__ + @overload + def __init__( + self, + function: DatasetFunction[_Dataset_T, _Target_P, _Target_T], + dataset: Any, + max_threads: int = 8, + *overflow_args: Overflow_In, + _get_value: Callable[[Any, int], _Dataset_T], + _length: Union[int, Callable[[Any], int]], + **overflow_kwargs: Overflow_In, + ) -> None: ... + def __init__( self, function: DatasetFunction[_Dataset_T, _Target_P, _Target_T], - dataset: Sequence[_Dataset_T], + dataset: Union[ + SupportsLengthGetItem[_Dataset_T], + SupportsGetItem[_Dataset_T], + SupportsLength, + Any, + ], max_threads: int = 8, *overflow_args: Overflow_In, + _get_value: Optional[ + Union[ + Callable[[SupportsLengthGetItem[_Dataset_T], int], _Dataset_T], + Callable[[SupportsGetItem[_Dataset_T], int], _Dataset_T], + Callable[[SupportsLength, int], _Dataset_T], + Callable[[Any, int], _Dataset_T], + ] + ] = None, + _length: Optional[ + Union[ + int, + Callable[[Any], int], + ] + ] = None, **overflow_kwargs: Overflow_In, ) -> None: """ @@ -364,16 +461,69 @@ def __init__( :param dataset: This should be an iterable sequence of data entries :param max_threads: This should be an integer value of the max threads allowed :param *: These are arguments parsed to `threading.Thread` and `Thread` + :param _get_value: This should be a function that takes in the dataset and the index and returns the data entry + :param _length: This should be an integer or a function that takes in the dataset and returns the length :param **: These are arguments parsed to `thread.Thread` and `Thread` Raises ------ - AssertionError: invalid `dataset` - AssertionError: invalid `max_threads` + ValueError: `max_threads` is 0 or negative + ValueError: `_length` is not an integer + ValueError: empty dataset or `_length` is/returned 0 + TypeError: missing `_length` + TypeError: missing `_get_value` """ - assert len(dataset) > 0, 'dataset cannot be empty' - assert 0 <= max_threads, 'max_threads cannot be set to 0' + if max_threads <= 0: + raise ValueError('`max_threads` must be greater than 0') + + # Impose requirements + if isinstance(dataset, SupportsLengthGetItem): + _length = _length(dataset) if callable(_length) else _length + length = len(dataset) if _length is None else _length + + get_value = _get_value or dataset.__class__.__getitem__ + elif isinstance(dataset, SupportsLength): + if not _get_value: + raise TypeError( + '`_get_value` must be set if `dataset` does not support `__getitem__`' + ) + _length = _length(dataset) if callable(_length) else _length + length = len(dataset) if _length is None else _length + + get_value = _get_value + + elif isinstance(dataset, SupportsGetItem): + if not _length: + raise TypeError( + '`_length` must be set if `dataset` does not support `__len__`' + ) + length = _length(dataset) if callable(_length) else _length + + get_value = _get_value or dataset.__class__.__getitem__ + + else: + if not _length: + raise TypeError( + '`_length` must be set if `dataset` does not support `__len__`' + ) + if not _get_value: + raise TypeError( + '`_get_value` must be set if `dataset` does not support `__getitem__`' + ) + + length = _length(dataset) if callable(_length) else _length + get_value = _get_value + + if not isinstance(length, int): + raise TypeError('`_length` must be an integer') + if length <= 0: + raise ValueError('dataset cannot be empty') + if not get_value: + raise TypeError('`_get_value` must be set') + + self._length = length + self._retrieve_value = get_value self._threads = [] self._completed = 0 @@ -503,7 +653,7 @@ def start(self) -> None: raise exceptions.ThreadStillRunningError() self.status = 'Running' - max_threads = min(self.max_threads, len(self.dataset)) + max_threads = min(self.max_threads, self._length) parsed_args = self.overflow_kwargs.get('args', []) name_format = ( @@ -514,13 +664,16 @@ def start(self) -> None: } i = 0 - for chunkStart, chunkEnd in chunk_split(len(self.dataset), max_threads): + for chunkStart, chunkEnd in chunk_split(self._length, max_threads): chunk_thread = Thread( target=self.function, args=[ i, chunkEnd - chunkStart, - (self.dataset[x] for x in range(chunkStart, chunkEnd)), + ( + self._retrieve_value(self.dataset, x) + for x in range(chunkStart, chunkEnd) + ), *parsed_args, *self.overflow_args, ], @@ -533,7 +686,7 @@ def start(self) -> None: # Handle abrupt exit -def service_shutdown(signum, frame): +def service_shutdown(signum, _): if Settings.GRACEFUL_EXIT_ENABLED: if Settings.VERBOSITY > 'quiet': print('\nCaught signal %d' % signum) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..76fc82d --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,3 @@ +from src.thread import Settings + +Settings.set_verbosity('quiet') diff --git a/tests/test_dataframe_compatibility.py b/tests/test_dataframe_compatibility.py new file mode 100644 index 0000000..82a7baf --- /dev/null +++ b/tests/test_dataframe_compatibility.py @@ -0,0 +1,390 @@ +import typing +import pytest +from src.thread import ParallelProcessing + + +class DummyLengthOnly: + length: typing.Any + + def __init__(self, length: typing.Any): + self.length = length + + def __len__(self) -> typing.Any: + return self.length + + +class DummyGetOnly: + dataset: list + + def __init__(self, dataset: list): + self.dataset = dataset + + def __getitem__(self, i: int) -> typing.Any: + return self.dataset[i] + + +class DummySequenceLike(DummyLengthOnly, DummyGetOnly): + length: typing.Any + dataset: list + + def __init__(self, length: typing.Any, dataset: list): + DummyLengthOnly.__init__(self, length) + DummyGetOnly.__init__(self, dataset) + + +class DummyUnlikeSequence1: + def __init__(self) -> None: ... + + +class DummyUnlikeSequence2: + def __init__(self) -> None: ... + def __str__(self) -> str: + return 'invalid' + + +# >>>>>>>>>> Length Only <<<<<<<<<< # +def test_LO_init() -> None: + ParallelProcessing( + function=lambda x: x, + dataset=DummyLengthOnly(10), + _get_value=lambda *_: _, + ) + + +def test_LO_init_missingGetValueError_nothing() -> None: + with pytest.raises(TypeError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyLengthOnly(10), # type: ignore + ) + + +def test_LO_init_missingGetValueError_lengthNum() -> None: + with pytest.raises(TypeError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyLengthOnly(10), # type: ignore + _length=1, + ) + + +def test_LO_init_missingGetValueError_lengthFunc() -> None: + with pytest.raises(TypeError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyLengthOnly(10), # type: ignore + _length=lambda _: 1, + ) + + +def test_LO_init_invalidLengthValueError_negative() -> None: + with pytest.raises(ValueError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyLengthOnly(-10), + _get_value=lambda *_: _, + ) + + +def test_LO_init_invalidLengthValueError_zero() -> None: + with pytest.raises(ValueError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyLengthOnly(0), + _get_value=lambda *_: _, + ) + + +def test_LO_init_nonIntLengthError_numLike() -> None: + with pytest.raises(TypeError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyLengthOnly(10.5), + _get_value=lambda *_: _, + ) + + +def test_LO_init_nonIntLengthError() -> None: + with pytest.raises(TypeError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyLengthOnly('10'), + _get_value=lambda *_: _, + ) + + +def test_LO_enforceTypes() -> None: + def validate(x, i): + assert isinstance(x, DummyLengthOnly) + assert isinstance(i, int) + + process = ParallelProcessing( + function=lambda x: x, + dataset=DummyLengthOnly(10), + _get_value=validate, + ) + process.start() + process.join() + + +def test_LO_len() -> None: + process = ParallelProcessing( + function=lambda x: x, + dataset=DummyLengthOnly(10), + _get_value=lambda *_: _, + ) + assert process._length == 10 + + +# >>>>>>>>>> Get Only <<<<<<<<<< # +def test_GO_init_int() -> None: + ParallelProcessing(function=lambda x: x, dataset=DummyGetOnly([1, 2, 3]), _length=3) + + +def test_GO_init_func() -> None: + ParallelProcessing( + function=lambda x: x, dataset=DummyGetOnly([1, 2, 3]), _length=lambda _: 3 + ) + + +def test_GO_init_missingLengthError() -> None: + with pytest.raises(TypeError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyGetOnly([1, 2, 3]), # type: ignore + ) + + +def test_GO_init_nonIntLengthError_strLike() -> None: + with pytest.raises(TypeError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyGetOnly([1, 2, 3]), + _length='10', # type: ignore + ) + + +def test_GO_init_nonIntLengthError_numLike() -> None: + with pytest.raises(TypeError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyGetOnly([1, 2, 3]), + _length=10.5, # type: ignore + ) + + +def test_GO_init_nonIntLengthError_negative() -> None: + with pytest.raises(ValueError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyGetOnly([1, 2, 3]), + _length=-10, # type: ignore + ) + + +def test_GO_enforceTypes() -> None: + def validate(x, i): + assert isinstance(x, DummyGetOnly) + assert isinstance(i, int) + + process = ParallelProcessing( + function=lambda x: x, + dataset=DummyGetOnly([1, 2, 3]), + _length=3, + _get_value=validate, + ) + process.start() + process.join() + + +def test_GO_len() -> None: + process = ParallelProcessing( + function=lambda x: x, + dataset=DummyGetOnly([1, 2, 3]), + _length=3, + _get_value=lambda *_: _, + ) + assert process._length == 3 + + +def test_GO_get() -> None: + def get(*_): + return _ + + process = ParallelProcessing( + function=lambda x: x, + dataset=DummyGetOnly([1, 2, 3]), + _length=3, + _get_value=get, + ) + assert process._retrieve_value == get + + +# >>>>>>>> Sequence Like <<<<<<<< # +def test_SO_init() -> None: + ParallelProcessing( + function=lambda x: x, + dataset=DummySequenceLike(10, list(range(10))), + ) + + +def test_SO_init_list() -> None: + ParallelProcessing( + function=lambda x: x, + dataset=[1, 2, 3], + ) + + +def test_SO_init_tuple() -> None: + ParallelProcessing( + function=lambda x: x, + dataset=(1, 2, 3), + ) + + +def test_SO_init_set() -> None: + with pytest.raises(TypeError): + ParallelProcessing( + function=lambda x: x, + dataset=set([1, 2, 3]), # type: ignore + ) + + +def test_SO_init_dict() -> None: + ParallelProcessing( + function=lambda x: x, + dataset={1: 1, 2: 2, 3: 3}, # type: ignore + ) + + +def test_SO_init_str() -> None: + ParallelProcessing( + function=lambda x: x, + dataset='123', + ) + + +def test_SO_init_withLength() -> None: + ParallelProcessing( + function=lambda x: x, + dataset=DummySequenceLike(10, list(range(10))), + _length=10, + ) + + +def test_SO_init_withGet() -> None: + ParallelProcessing( + function=lambda x: x, + dataset=DummySequenceLike(10, list(range(10))), + _get_value=lambda *_: _, + ) + + +def test_SO_init_withLengthAndGet() -> None: + ParallelProcessing( + function=lambda x: x, + dataset=DummySequenceLike(10, list(range(10))), + _length=10, + _get_value=lambda *_: _, + ) + + +def test_SO_len() -> None: + process = ParallelProcessing( + function=lambda x: x, + dataset=DummySequenceLike(10, list(range(10))), + ) + assert process._length == 10 + + +def test_SO_enforceTypes() -> None: + def validate(x, i): + assert isinstance(x, DummySequenceLike) + assert isinstance(i, int) + + process = ParallelProcessing( + function=lambda x: x, + dataset=DummySequenceLike(10, list(range(10))), + _get_value=validate, + ) + process.start() + process.join() + + +# >>>>>>>>>> Unlike Sequence <<<<<<<<<< # +def test_UO_init_clean() -> None: + with pytest.raises(TypeError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyUnlikeSequence1(), # type: ignore + ) + + +def test_UO_init_withOtherMethods() -> None: + with pytest.raises(TypeError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyUnlikeSequence2(), # type: ignore + ) + + +def test_UO_init_onlyLength() -> None: + with pytest.raises(TypeError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyUnlikeSequence1(), # type: ignore + _length=10, + ) + + +def test_UO_init_onlyGet() -> None: + with pytest.raises(TypeError): + ParallelProcessing( + function=lambda x: x, + dataset=DummyUnlikeSequence1(), # type: ignore + _get_value=lambda *_: _, + ) + + +def test_UO_init_onlyLengthAndGet() -> None: + ParallelProcessing( + function=lambda x: x, + dataset=DummyUnlikeSequence1(), # type: ignore + _length=10, + _get_value=lambda *_: _, + ) + + +def test_UO_lengthInt() -> None: + process = ParallelProcessing( + function=lambda x: x, + dataset=DummyUnlikeSequence1(), + _length=10, + _get_value=lambda *_: _, + ) + assert process._length == 10 + + +def test_UO_lengthFunc() -> None: + process = ParallelProcessing( + function=lambda x: x, + dataset=DummyUnlikeSequence1(), + _length=lambda _: 10, + _get_value=lambda *_: _, + ) + assert process._length == 10 + + +def test_UO_enforceTypes() -> None: + def validate(x, i): + assert isinstance(x, DummyUnlikeSequence1) + assert isinstance(i, int) + + process = ParallelProcessing( + function=lambda x: x, + dataset=DummyUnlikeSequence1(), + _length=10, + _get_value=validate, + ) + process.start() + process.join() diff --git a/tests/test_thread.py b/tests/test_thread.py index a5dd6e4..d2abcb2 100644 --- a/tests/test_thread.py +++ b/tests/test_thread.py @@ -14,12 +14,6 @@ def _dummy_raiseException(x: Exception, delay: float = 0): raise x -def _dummy_iterative(itemCount: int, pTime: float = 0.1, delay: float = 0): - time.sleep(delay) - for i in range(itemCount): - time.sleep(pTime) - - # >>>>>>>>>> General Use <<<<<<<<<< # def test_threadCreation(): """This test is for testing parsing of args and kwargs and `.join()` method""" @@ -127,7 +121,7 @@ def test_raises_HookError(): """This test should raise""" new = Thread(target=_dummy_target_raiseToPower, args=[4, 2], daemon=True) - def newhook(x: int): + def newhook(_: int): raise RuntimeError() new.add_hook(newhook) diff --git a/tests/test_verbosity.py b/tests/test_verbosity.py index cc6929a..11d34b7 100644 --- a/tests/test_verbosity.py +++ b/tests/test_verbosity.py @@ -48,19 +48,19 @@ def test_geTrue(): # >>>>>>>>>> Raising <<<<<<<<<< # def test_ltRaise(): with pytest.raises(ValueError): - Verbosity(0) < Exception() + return Verbosity(0) < Exception() def test_leRaise(): with pytest.raises(ValueError): - Verbosity(0) <= Exception() + return Verbosity(0) <= Exception() def test_gtRaise(): with pytest.raises(ValueError): - Verbosity(1) > Exception() + return Verbosity(1) > Exception() def test_geRaise(): with pytest.raises(ValueError): - Verbosity(1) >= Exception() + return Verbosity(1) >= Exception()