diff --git a/.github/workflows/tests_and_coverage.yml b/.github/workflows/tests_and_coverage.yml index c2fc757..80bba1f 100644 --- a/.github/workflows/tests_and_coverage.yml +++ b/.github/workflows/tests_and_coverage.yml @@ -32,7 +32,7 @@ jobs: run: pip list - name: Run tests and show coverage on the command line - run: coverage run --source=metronomes --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m + run: coverage run --source=metronomes --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=100 - name: Upload reports to codecov env: @@ -45,4 +45,4 @@ jobs: ./codecov -t ${CODECOV_TOKEN} - name: Run tests and show the branch coverage on the command line - run: coverage run --source=metronomes --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m + run: coverage run --branch --source=metronomes --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=100 diff --git a/.github/workflows/tests_and_coverage_old.yml b/.github/workflows/tests_and_coverage_old.yml index 9cf8ea5..ab55934 100644 --- a/.github/workflows/tests_and_coverage_old.yml +++ b/.github/workflows/tests_and_coverage_old.yml @@ -32,7 +32,7 @@ jobs: run: pip list - name: Run tests and show coverage on the command line - run: coverage run --source=metronomes --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m + run: coverage run --source=metronomes --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=100 - name: Upload reports to codecov env: @@ -45,4 +45,4 @@ jobs: ./codecov -t ${CODECOV_TOKEN} - name: Run tests and show the branch coverage on the command line - run: coverage run --source=metronomes --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m + run: coverage run --branch --source=metronomes --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=100 diff --git a/README.md b/README.md index a46d438..0b10371 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ This library offers the easiest way to run regular tasks. Just give it a functio - [**Logging**](#logging) - [**Error escaping**](#error-escaping) - [**Working with Cancellation Tokens**](#working-with-cancellation-tokens) -- [**Limitations**](#limitations) +- [**How to set a time limit**](#how-to-set-a-time-limit) ## Quick start @@ -247,15 +247,14 @@ metronome.start(token=TimeoutToken(1)) If you pass cancellation tokens both during metronome initialization and in the `start()` method, their limitations will be summed up. -## Limitations +## How to set a time limit -You can limit the total running time of the metronome by setting the `duration` value (in seconds): +You can specify a time limit (in seconds) for which the metronome will tick. When the specified time is over, it will simply stop. There are 2 ways to do this: when creating a metronome object or when calling the `start()` method. -```python -from time import sleep -from metronomes import Metronome +In the first way, the time will start counting from the moment the metronome object is created: -metronome = Metronome(0.2, lambda: print('go!'), duration=0.6) +```python +metronome = Metronome(0.2, lambda: print('go!'), duration=0.6) metronome.start() sleep(1) @@ -263,3 +262,17 @@ sleep(1) #> go! #> go! ``` + +In the second way, the countdown will start by calling the `start()` method: + +```python +metronome = Metronome(0.2, lambda: print('go!')) + +metronome.start(duration=0.6) +sleep(1) +#> go! +#> go! +#> go! +``` + +Choose the right way to limit time. When using the metronome in the [context manager mode](#use-it-as-a-context-manager), only the first way is available to you, in almost all other situations - the second one is preferable. diff --git a/metronomes/metronome.py b/metronomes/metronome.py index cfc4d0c..eb8f07b 100644 --- a/metronomes/metronome.py +++ b/metronomes/metronome.py @@ -29,11 +29,7 @@ def __init__(self, iteration: Union[int, float], callback: Callable[[], Any], su self.logger: LoggerProtocol = logger self.token: AbstractToken = SimpleToken(token) if duration is not None: - if duration < 0: - raise ValueError('The total duration of the metronome operation cannot be less than zero.') - elif iteration > duration: - raise ValueError('The time of one iteration cannot exceed the running time of the metronome as a whole.') - self.token = self.token + TimeoutToken(duration) + self.token += self.create_duration_token(duration) self.thread: Optional[Thread] = None self.started: bool = False self.stopped: bool = False @@ -52,13 +48,16 @@ def __exit__(self, exception_type: Optional[Type[BaseException]], exception_valu self.stop() return False - def start(self, token: AbstractToken = DefaultToken()) -> 'Metronome': + def start(self, token: AbstractToken = DefaultToken(), duration: Optional[Union[int, float]] = None) -> 'Metronome': with self.lock: if self.stopped: raise RunStoppedMetronomeError('Metronomes are disposable, you cannot restart a stopped metronome.') if self.started: raise RunAlreadyStartedMetronomeError('The metronome has already been launched.') + if duration is not None: + token += self.create_duration_token(duration) + self.thread = Thread(target=self.run_loop, args=(self.token + token,)) self.thread.daemon = True @@ -102,3 +101,11 @@ def run_loop(self, token: AbstractToken) -> None: self.sleeping_callback(sleep_time) self.stopped = True + + def create_duration_token(self, duration: Union[int, float]) -> TimeoutToken: + if duration < 0: + raise ValueError('The total duration of the metronome operation cannot be less than zero.') + elif self.iteration > duration: + raise ValueError('The time of one iteration cannot exceed the running time of the metronome as a whole.') + + return TimeoutToken(duration) diff --git a/pyproject.toml b/pyproject.toml index d0b6b83..547fd64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = 'setuptools.build_meta' [project] name = 'metronomes' -version = '0.0.9' +version = '0.0.10' authors = [ { name='Evgeniy Blinov', email='zheni-b@yandex.ru' }, ] diff --git a/tests/units/test_metronome.py b/tests/units/test_metronome.py index 571718e..38f5f4a 100644 --- a/tests/units/test_metronome.py +++ b/tests/units/test_metronome.py @@ -242,11 +242,21 @@ def test_iteration_more_than_duration(): Metronome(5, lambda: None, duration=1) +def test_iteration_more_than_duration_in_start_method(): + with pytest.raises(ValueError, match=full_match('The time of one iteration cannot exceed the running time of the metronome as a whole.')): + Metronome(5, lambda: None).start(duration=1) + + def test_duration_less_than_zero(): with pytest.raises(ValueError, match=full_match('The total duration of the metronome operation cannot be less than zero.')): Metronome(0.5, lambda: None, duration=-1) +def test_duration_less_than_zero_in_start_method(): + with pytest.raises(ValueError, match=full_match('The total duration of the metronome operation cannot be less than zero.')): + Metronome(0.5, lambda: None).start(duration=-1) + + def test_set_duration_time(): metronome = Metronome(0.00001, lambda: None, duration=0.001) @@ -258,6 +268,17 @@ def test_set_duration_time(): assert metronome.stopped +def test_set_duration_in_start_method(): + metronome = Metronome(0.00001, lambda: None) + + assert not metronome.stopped + + metronome.start(duration=0.001) + sleep(0.1) + + assert metronome.stopped + + def test_context_manager_basics(): actions = [] metronome = Metronome(0.00001, lambda: actions.append(1))