# About
**oclock** is a Python 3 package. Its main goal is to provide a simple way to create timed loops with constant time intervals and no drift. It also provides various other timing tools and a GUI time.
### Timed loops
No drift, timed loops are based on the `Timer` class,
- either using the `checkpt()` method at a location in a for/while loop acting as a reference point to maintain constant duration from one loop to the next,
- or using the `@loop` or `@interactiveloop` decorators that use `Timer` and `checkpt()` in the background.
The timing (interval) and execution (pause/stop etc.) can be modified in real time thanks to cancellable sleeping times.
### Other tools
- `Event`: class mimicking `threading.Event()` but with much better sleeping time accuracy.
- `Countdown`: a class that starts a GUI countdown timer.
- `parse_time()` function: returns a `datetime.timedelta` from a time string (e.g. `':2:25'`).
- `measure_time()` and `measure_duration()` functions: are context managers for measuring time and execution times / time uncertainty of encapsulated commands.
- `after()` allows the user to run a function after a pre-defined waiting time.
- Note that the `Timer` class can also be used as a regular chronometer with its methods `pause()`, `resume()`, `stop()` etc.
# Quick start
## Install
```bash
pip install oclock
```
## Timed Loops
The `Timer` class is mostly designed to create loops of constant duration without drift, while allowing immediate modification/cancellation (sleep time interruption) in threaded environments. It can also be used as a regular chronometer.
Below are some peudo-code quick-start examples. For complete, working examples, see:
- *Examples.ipynb* notebook (https://github.com/ovinc/oclock/blob/master/Example.ipynb)
- *example.py* script (https://github.com/ovinc/oclock/blob/master/example.py)
### Constant-duration loops
The most basic use of the `Timer()` class in Python code to create a loop of constant duration is:
```python
from oclock import Timer
timer = Timer(interval=2) # Loops will be of total duration 2 seconds
while condition:
my_function() # can be of any duration between 0 and 2 seconds
timer.checkpt()
```
The `checkpt()` method waits the adequate amount of time to make the loop of constant duration, without drift (using a target regularly spaced in time); `condition` can include timer methods and attributes, e.g. `timer.elapsed_time < max_time`.
Note that if *my_function()* takes longer to execute than the required time interval, the Timer class does not try to compensate the extra time by making the next loop shorter. It just aims at making the total duration of the next loop be the requested interval again (see *Behavior when interval is exceeded* section below).
The same behavior can be achieved using the `@loop` decorator:
```python
from oclock import loop
timer = Timer(interval=2) # Loops will be of total duration 2 seconds
@loop(timer)
def my_function():
...
```
Then, calling `my_function()` will execute the contents of the function in a repeated manner.
The `@loop` execution exits automatically if the timer is stopped.
Thus, It is useful to include a condition in `my_function` to exit the loop when needed, e.g.
```python
if timer.elapsed_time > t_max:
timer.stop()
```
### Interactive modification/cancellation
The timer is also modifiable (change time interval) and cancellable in real time (i.e. even when the timer is in a `checkpt()` waiting phase). To do so, it must be accessed by another thread that runs concurrently. For example:
```python
from oclock import Timer
from threading import Thread
def user_input(timer):
"""Threaded command line input to change time interval or exit timer."""
while not timer.is_stopped:
a = input()
try:
dt = float(a)
except ValueError: # immediately cancel timer & exit all loops/threads
timer.stop()
else: # immediately change interval to input value
timer.interval = dt
timer = Timer(interval=2)
Thread(target=user_input, args=(exit_event, timer)).start()
while not timer.is_stopped:
my_function()
timer.checkpt()
```
During operation, the `Timer` object can be paused, resumed, stopped and reset using the corresponding `Timer` methods (see *Regular Timer* paragraph below). The *oclock* module also provides a simple command line interface to create a timed loop for a function and interact with it dynamically using the `@interactiveloop` decorator:
```python
from oclock import interactiveloop
@interactiveloop(interval=2)
def my_function():
...
```
Now when `my_function()` is called, an interactive CLI thread starts at the same time where the user can pause/resume/reset/stop the timer in real time, change its interval, and print timing information.
### Regular Timer
Although not its main purpose, the `Timer` class can be used as a regular chronometer with the following methods (no need to be in a threaded environment, although the methods below whould work and be cancellable in a threaded environment):
```python
from oclock import Timer
# The timer starts counting time immediately upon instantiation.
timer = Timer()
# Temporarily pause, then resume timer
timer.pause()
timer.resume()
# Stop and restart timer completely
timer.stop()
timer.reset() # note: can be called without calling stop() first
# At any time, the elapsed time and total pause duration can be accessed with
timer.elapsed_time
timer.pause_time
timer.total_time # sum of the two other times
```
**Important Note**: Do not use the `checkpt()` method after a `pause()` call if not in a threaded environment, this will cause the program to be in a perpetual waiting state. In a threaded environment, call `resume()` to unpause.
### Details
See *Timer Class details* section below for all methods, properties and attributes and the *Development* section below for accuracy information.
## Event class
The `oclock.Event` class mimicks `threading.Event` (https://docs.python.org/3/library/threading.html#event-objects) but provides much better sleep time accuracy.
Available methods are the same as for `threading.Event`:
- `set()`
- `clear()`
- `is_set()`
- `wait()`
Below are comments from Chris D. who originally posted the code for this class on StackOverflow (see *Contributors* at the end of this file):
> Internally, it uses a combination of a time.sleep() loop and a busy loop for greatly increased precision. The sleep loop runs in a separate thread so that the blocking wait() call in the main thread can still be immediately interrupted. When the set() method is called, the sleep thread should terminate shortly afterwards. Also, in order to minimize CPU utilization, I made sure that the busy loop will never run for more than 3 milliseconds.
## Countdown GUI
A simple graphical countdown timer based on the `Timer` class. It can be used either as a python main program from a shell, or as a function in Python code or console.
![](https://raw.githubusercontent.com/ovinc/oclock/master/media/countdown.gif)
From a terminal:
```bash
python -m oclock 1:45:00 # start timer of duration 1 hour 45 minutes
python -m oclock 1:45: # exactly the same as above
python -m oclock 00:02:00 # start 2-minute timer
python -m oclock :2: # exactly the same as above
python -m oclock 00:00:05 # start 5-second timer
python -m oclock ::5 # exactly the same as above
```
In python:
```python
from oclock import Countdown
Countdown('1:45:') # start timer of duration 1 hour 45 minutes
```
(the inputs are of the same format as from the terminal, see above).
When countdown is finished, 'Done' is displayed for 5 seconds in the GUI while the console displays *Countdown finished* and emits a sound. Then the time passed since the end of countdown is displayed as a negative value in red. The program stops when the GUI window is closed.
## Parse time function
The `parse_time()` function is used in the argument parsing of the countdown GUI from a terminal (see above). It transforms a string in the form `'h:m:s'` into a `datetime.timedelta` object. Inputs of the form e.g. `'::5'` or `:2:`, `'3:30:'` are acceptable for 5 seconds, 2 minutes, and 3.5 hours, respectively.
```python
>>> parse_time(':2:30')
datetime.timedelta(seconds=150)
```
## Context managers to record timing of commands
The `measure_time()` function is a context manager that saves the timing info of the encapsulated commands. This can be e.g. used in scientific experiments to get the time and time uncertainty associated with a measurement.
```python
from oclock import measure_time, measure_duration
# ----------------------------------------------------------------------------
# Examples where one just wants to get timing info ---------------------------
# ----------------------------------------------------------------------------
with measure_time() as timing:
my_function()
print(timing)
# Out: {'time (unix)': 1604780958.0705943, 'dt (s)': 0.6218999624252319}
# (dt (s) is half the total duration. To get the total duration instead, do:)
with measure_duration() as duration:
my_function()
print(duration)
# Out: {'duration (s)': 1.1689763555421325}
# ----------------------------------------------------------------------------
# Example where the timing info is directly added to a data dictionary -------
# ----------------------------------------------------------------------------
with measure_time() as data:
measurement = my_function() # returns e.g. 3.618
data['measurement'] = measurement
print(data)
# Out: {'measurement': 3.618,
# 'time (unix)': 1604780958.0705943,
# 'dt (s)': 0.6218999624252319}
```
## Execute function after waiting time
```python
from oclock import after
def my_function():
print('Hello')
return 3.14
after('::2', my_function)
# Note: there are options to pass args/kwargs to function
# and also to not block console during waiting.
# (see docstring of after() function)
```
# Timer Class details
## Instantiation
```python
from oclock import Timer
timer = Timer(interval=1, name='Timer', warnings=False, precise=False)
```
Parameters:
- `interval` (float): timer interval in seconds
- `name` (str): optional name for description purposes (repr and warnings)
- `warnings` (bool): If True, prints warning when time interval exceeded
- `precise` (bool) if True, increase time precision (useful for Windows)
*Note:* The `precise=True` option uses a custom `Event` class to replace `threading.Event`, originally written by Chris D. (see below).
## Methods
```python
timer.checkpt() # Reference point for constant-duration loops, see above
timer.pause() # Immediately pause timer and put checkpt() in waiting phase
timer.resume() # Restart the elapsed time counter and unlock checkpt()
timer.stop() # Stop counting time and exit checkpt()
timer.reset() # Stop and restart timer immediately
# Change timer interval
timer.set_interval(...) # immediately, equivalent to timer.interval = ...
timer.set_interval(..., immediate=False) # wait next checkpt
```
## Properties (settable)
```python
timer.interval # get interval (in s)
timer.interval += 1 # increase interval by 1 second
timer.interval = 10 # set interval to 10 seconds.
timer.warnings # get current status of warnings
timer.warnings = True # activate warnings if time between checkpts too short
timer.name # optional name (for repr and warnings)
timer.name = 'Countdown timer' # (can also be set during instantiation)
```
## Attributes (read-only)
```python
# Most useful attributes
timer.elapsed_time # Time in seconds since init or last reset
timer.pause_time # total time (in s) the timer has been paused.
timer.total_time # Sum of the last two
```
## Background attributes and methods
(mostly for development)
```python
timer.now() # Reference time used by all methods
timer.start_time # Ref. time corresponding to start/reset of timer
timer.next_checkpt_release # Ref. time at which next checkpt waittime is over
timer.interval_exceeded # (bool) True if loop contents take longer to execute than requested interval
```
## Notes
- As mentioned previously, methods (and interval setting) take effect immediately, even if the timer is in a waiting phase. It is however possible to wait for the next checkpt to apply a new timer interval, by using the `immediate=False` option in `set_interval()` (see example in the *Examples.ipynb* notebook).
- After calling `pause()`, the `checkpt()` command blocks until `resume()` is called, however in the current version after `stop()` the `checkpt()` becomes non-blocking (equivalent to a `pass`), so that all following lines will be executed immediately and without any waiting time (i.e. as fast as possible if within a loop), until `timer.reset()` is called again. This means that it is useful to pin the condition of the loop to the stopping of the timer (see examples).
## Timer accuracy
See *performance.py* file of the module for functions to test the behavior and accuracy of the timer. In particular:
```python
from oclock.performance import performance_test
performance_test(dt=0.01, nloops=1000, fmax=0.99, plot=True, warnings=False, precise=True)
```
tests the timing on 1000 loops of requested duration 0.01 second (10ms), using within the loop a function sleeping for a random amount of time between 0 and 0.99 dt (with `plot=True` option to see the results on a *matplotlib* graph, and `warnings=False` to not have a printed warning when the execution time of the nested commands exceed the target duration of the loop); `precise=True` uses the timer in precise mode.
The *AccuracyTests.md* file gathers some accuracy results in Unix and Windows environments. In summary:
- with **Unix**, time fluctuations are < 0.5 ms with the regular timer, and on the order of 0.01 ms (standard deviation) with the precise timer
- with **Windows**, the regular timer fails quickly as frame rate is increased, due to fluctuations in the ~ 10 ms range. However the precise timer performs even better than in Unix, with fluctuations of less than 0.01 ms (standard deviation).
## Behavior when interval is exceeded
As explained above, it the contents of the loop take longer to execute than the requested time interval, the Timer simply moves on to the next loop but does not try to compensate for the extra time by making the next loop shorter:
![](https://raw.githubusercontent.com/ovinc/oclock/master/media/img/timer_interval_exceeded.png)
# Development
Install the package by cloning the GitHub repo (https://github.com/ovinc/oclock.git) and install in editable mode from the root of the repo:
```
pip install -e .
```
## Testing
Package requirements to run the tests:
- pytest
- numpy
General testing is done with *pytest* (from the root of the repository):
```bash
pytest
```
(**Note**: close the interactive countdown window at the end of the pytest run to finish the test.)
Additional testing of interactive command line for real-time timer control can be done by running the example file from the root of the repository.
```bash
python -m example
```
See also *Accuracy Test* paragraph above to run performance tests for constant-duration loops with the `Timer` class.
## Contributing
Issues and Pull requests must be submitted on GitHub (https://github.com/ovinc/oclock) with commits (preferably squashed into a single commit) in branch *authors*.
Version number is automatically extracted from git tag using *setuptools_scm*. Git tags are added by the repo's maintainer.
# Requirements
Python 3.x
(Tests only made from python 3.5 to python 3.9 included)
# Author
Olivier Vincent
(ovinc.py@gmail.com)
# Contributors
The `oclock.Event` class was originally written by Chris D.
(https://stackoverflow.com/questions/48984512/making-a-timer-timeout-inaccuracy-of-threading-event-wait-python-3-6)
# License
GNU GPLv3, see *LICENSE* file
Raw data
{
"_id": null,
"home_page": "https://github.com/ovinc/oclock",
"name": "oclock",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.5",
"maintainer_email": "",
"keywords": "timing,loops,constant duration,cancellable,modifiable,countdown,context manager,gui",
"author": "Olivier Vincent",
"author_email": "ovinc.py@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/5c/f3/ca065b32b4a5bdee80045ab7d8163b3b89e271598dad207c92ada889fc13/oclock-1.4.0.tar.gz",
"platform": null,
"description": "# About\n\n**oclock** is a Python 3 package. Its main goal is to provide a simple way to create timed loops with constant time intervals and no drift. It also provides various other timing tools and a GUI time.\n\n### Timed loops\n\nNo drift, timed loops are based on the `Timer` class,\n- either using the `checkpt()` method at a location in a for/while loop acting as a reference point to maintain constant duration from one loop to the next,\n- or using the `@loop` or `@interactiveloop` decorators that use `Timer` and `checkpt()` in the background.\n\nThe timing (interval) and execution (pause/stop etc.) can be modified in real time thanks to cancellable sleeping times.\n\n### Other tools\n\n- `Event`: class mimicking `threading.Event()` but with much better sleeping time accuracy.\n- `Countdown`: a class that starts a GUI countdown timer.\n- `parse_time()` function: returns a `datetime.timedelta` from a time string (e.g. `':2:25'`).\n- `measure_time()` and `measure_duration()` functions: are context managers for measuring time and execution times / time uncertainty of encapsulated commands.\n- `after()` allows the user to run a function after a pre-defined waiting time.\n- Note that the `Timer` class can also be used as a regular chronometer with its methods `pause()`, `resume()`, `stop()` etc.\n\n# Quick start\n\n## Install\n\n```bash\npip install oclock\n```\n\n## Timed Loops\n\nThe `Timer` class is mostly designed to create loops of constant duration without drift, while allowing immediate modification/cancellation (sleep time interruption) in threaded environments. It can also be used as a regular chronometer.\n\nBelow are some peudo-code quick-start examples. For complete, working examples, see:\n- *Examples.ipynb* notebook (https://github.com/ovinc/oclock/blob/master/Example.ipynb)\n- *example.py* script (https://github.com/ovinc/oclock/blob/master/example.py)\n\n\n### Constant-duration loops\n\nThe most basic use of the `Timer()` class in Python code to create a loop of constant duration is:\n```python\nfrom oclock import Timer\ntimer = Timer(interval=2) # Loops will be of total duration 2 seconds\nwhile condition:\n my_function() # can be of any duration between 0 and 2 seconds\n timer.checkpt()\n```\nThe `checkpt()` method waits the adequate amount of time to make the loop of constant duration, without drift (using a target regularly spaced in time); `condition` can include timer methods and attributes, e.g. `timer.elapsed_time < max_time`.\n\nNote that if *my_function()* takes longer to execute than the required time interval, the Timer class does not try to compensate the extra time by making the next loop shorter. It just aims at making the total duration of the next loop be the requested interval again (see *Behavior when interval is exceeded* section below).\n\nThe same behavior can be achieved using the `@loop` decorator:\n```python\nfrom oclock import loop\ntimer = Timer(interval=2) # Loops will be of total duration 2 seconds\n@loop(timer)\ndef my_function():\n ...\n```\nThen, calling `my_function()` will execute the contents of the function in a repeated manner.\n\nThe `@loop` execution exits automatically if the timer is stopped.\nThus, It is useful to include a condition in `my_function` to exit the loop when needed, e.g.\n```python\nif timer.elapsed_time > t_max:\n timer.stop()\n```\n\n### Interactive modification/cancellation\n\nThe timer is also modifiable (change time interval) and cancellable in real time (i.e. even when the timer is in a `checkpt()` waiting phase). To do so, it must be accessed by another thread that runs concurrently. For example:\n\n```python\nfrom oclock import Timer\nfrom threading import Thread\n\ndef user_input(timer):\n \"\"\"Threaded command line input to change time interval or exit timer.\"\"\"\n while not timer.is_stopped:\n a = input()\n try:\n dt = float(a)\n except ValueError: # immediately cancel timer & exit all loops/threads\n timer.stop()\n else: # immediately change interval to input value\n timer.interval = dt\n\ntimer = Timer(interval=2)\nThread(target=user_input, args=(exit_event, timer)).start()\n\nwhile not timer.is_stopped:\n my_function()\n timer.checkpt()\n```\nDuring operation, the `Timer` object can be paused, resumed, stopped and reset using the corresponding `Timer` methods (see *Regular Timer* paragraph below). The *oclock* module also provides a simple command line interface to create a timed loop for a function and interact with it dynamically using the `@interactiveloop` decorator:\n```python\nfrom oclock import interactiveloop\n@interactiveloop(interval=2)\ndef my_function():\n ...\n```\nNow when `my_function()` is called, an interactive CLI thread starts at the same time where the user can pause/resume/reset/stop the timer in real time, change its interval, and print timing information.\n\n### Regular Timer\n\nAlthough not its main purpose, the `Timer` class can be used as a regular chronometer with the following methods (no need to be in a threaded environment, although the methods below whould work and be cancellable in a threaded environment):\n\n```python\nfrom oclock import Timer\n\n# The timer starts counting time immediately upon instantiation.\ntimer = Timer()\n\n# Temporarily pause, then resume timer\ntimer.pause()\ntimer.resume()\n\n# Stop and restart timer completely\ntimer.stop()\ntimer.reset() # note: can be called without calling stop() first\n\n# At any time, the elapsed time and total pause duration can be accessed with\ntimer.elapsed_time\ntimer.pause_time\ntimer.total_time # sum of the two other times\n```\n\n**Important Note**: Do not use the `checkpt()` method after a `pause()` call if not in a threaded environment, this will cause the program to be in a perpetual waiting state. In a threaded environment, call `resume()` to unpause.\n\n### Details\n\nSee *Timer Class details* section below for all methods, properties and attributes and the *Development* section below for accuracy information.\n\n\n## Event class\n\nThe `oclock.Event` class mimicks `threading.Event` (https://docs.python.org/3/library/threading.html#event-objects) but provides much better sleep time accuracy.\n\nAvailable methods are the same as for `threading.Event`:\n- `set()`\n- `clear()`\n- `is_set()`\n- `wait()`\n\n\nBelow are comments from Chris D. who originally posted the code for this class on StackOverflow (see *Contributors* at the end of this file):\n\n> Internally, it uses a combination of a time.sleep() loop and a busy loop for greatly increased precision. The sleep loop runs in a separate thread so that the blocking wait() call in the main thread can still be immediately interrupted. When the set() method is called, the sleep thread should terminate shortly afterwards. Also, in order to minimize CPU utilization, I made sure that the busy loop will never run for more than 3 milliseconds.\n\n\n## Countdown GUI\n\nA simple graphical countdown timer based on the `Timer` class. It can be used either as a python main program from a shell, or as a function in Python code or console.\n\n![](https://raw.githubusercontent.com/ovinc/oclock/master/media/countdown.gif)\n\nFrom a terminal:\n```bash\npython -m oclock 1:45:00 # start timer of duration 1 hour 45 minutes\npython -m oclock 1:45: # exactly the same as above\npython -m oclock 00:02:00 # start 2-minute timer\npython -m oclock :2: # exactly the same as above\npython -m oclock 00:00:05 # start 5-second timer\npython -m oclock ::5 # exactly the same as above\n```\n\nIn python:\n```python\nfrom oclock import Countdown\nCountdown('1:45:') # start timer of duration 1 hour 45 minutes\n```\n(the inputs are of the same format as from the terminal, see above).\n\nWhen countdown is finished, 'Done' is displayed for 5 seconds in the GUI while the console displays *Countdown finished* and emits a sound. Then the time passed since the end of countdown is displayed as a negative value in red. The program stops when the GUI window is closed.\n\n## Parse time function\n\nThe `parse_time()` function is used in the argument parsing of the countdown GUI from a terminal (see above). It transforms a string in the form `'h:m:s'` into a `datetime.timedelta` object. Inputs of the form e.g. `'::5'` or `:2:`, `'3:30:'` are acceptable for 5 seconds, 2 minutes, and 3.5 hours, respectively.\n```python\n>>> parse_time(':2:30')\ndatetime.timedelta(seconds=150)\n```\n\n## Context managers to record timing of commands\n\nThe `measure_time()` function is a context manager that saves the timing info of the encapsulated commands. This can be e.g. used in scientific experiments to get the time and time uncertainty associated with a measurement.\n```python\nfrom oclock import measure_time, measure_duration\n\n# ----------------------------------------------------------------------------\n# Examples where one just wants to get timing info ---------------------------\n# ----------------------------------------------------------------------------\n\nwith measure_time() as timing:\n my_function()\nprint(timing)\n\n# Out: {'time (unix)': 1604780958.0705943, 'dt (s)': 0.6218999624252319}\n\n# (dt (s) is half the total duration. To get the total duration instead, do:)\n\nwith measure_duration() as duration:\n my_function()\nprint(duration)\n\n# Out: {'duration (s)': 1.1689763555421325}\n\n# ----------------------------------------------------------------------------\n# Example where the timing info is directly added to a data dictionary -------\n# ----------------------------------------------------------------------------\n\nwith measure_time() as data:\n measurement = my_function() # returns e.g. 3.618\n data['measurement'] = measurement\nprint(data)\n\n# Out: {'measurement': 3.618,\n# 'time (unix)': 1604780958.0705943,\n# 'dt (s)': 0.6218999624252319}\n```\n\n\n## Execute function after waiting time\n\n```python\nfrom oclock import after\n\ndef my_function():\n print('Hello')\n return 3.14\n\nafter('::2', my_function)\n\n# Note: there are options to pass args/kwargs to function\n# and also to not block console during waiting.\n# (see docstring of after() function)\n```\n\n\n# Timer Class details\n\n## Instantiation\n\n```python\nfrom oclock import Timer\ntimer = Timer(interval=1, name='Timer', warnings=False, precise=False)\n```\n\nParameters:\n- `interval` (float): timer interval in seconds\n- `name` (str): optional name for description purposes (repr and warnings)\n- `warnings` (bool): If True, prints warning when time interval exceeded\n- `precise` (bool) if True, increase time precision (useful for Windows)\n\n*Note:* The `precise=True` option uses a custom `Event` class to replace `threading.Event`, originally written by Chris D. (see below).\n\n## Methods\n\n```python\ntimer.checkpt() # Reference point for constant-duration loops, see above\n\ntimer.pause() # Immediately pause timer and put checkpt() in waiting phase\ntimer.resume() # Restart the elapsed time counter and unlock checkpt()\n\ntimer.stop() # Stop counting time and exit checkpt()\ntimer.reset() # Stop and restart timer immediately\n\n# Change timer interval\ntimer.set_interval(...) # immediately, equivalent to timer.interval = ...\ntimer.set_interval(..., immediate=False) # wait next checkpt\n```\n\n## Properties (settable)\n\n```python\ntimer.interval # get interval (in s)\ntimer.interval += 1 # increase interval by 1 second\ntimer.interval = 10 # set interval to 10 seconds.\n\ntimer.warnings # get current status of warnings\ntimer.warnings = True # activate warnings if time between checkpts too short\n\ntimer.name # optional name (for repr and warnings)\ntimer.name = 'Countdown timer' # (can also be set during instantiation)\n```\n\n## Attributes (read-only)\n\n```python\n# Most useful attributes\ntimer.elapsed_time # Time in seconds since init or last reset\ntimer.pause_time # total time (in s) the timer has been paused.\ntimer.total_time # Sum of the last two\n```\n\n## Background attributes and methods\n(mostly for development)\n```python\ntimer.now() # Reference time used by all methods\ntimer.start_time # Ref. time corresponding to start/reset of timer\ntimer.next_checkpt_release # Ref. time at which next checkpt waittime is over\ntimer.interval_exceeded # (bool) True if loop contents take longer to execute than requested interval\n```\n\n## Notes\n\n- As mentioned previously, methods (and interval setting) take effect immediately, even if the timer is in a waiting phase. It is however possible to wait for the next checkpt to apply a new timer interval, by using the `immediate=False` option in `set_interval()` (see example in the *Examples.ipynb* notebook).\n\n- After calling `pause()`, the `checkpt()` command blocks until `resume()` is called, however in the current version after `stop()` the `checkpt()` becomes non-blocking (equivalent to a `pass`), so that all following lines will be executed immediately and without any waiting time (i.e. as fast as possible if within a loop), until `timer.reset()` is called again. This means that it is useful to pin the condition of the loop to the stopping of the timer (see examples).\n\n\n## Timer accuracy\n\nSee *performance.py* file of the module for functions to test the behavior and accuracy of the timer. In particular:\n```python\nfrom oclock.performance import performance_test\nperformance_test(dt=0.01, nloops=1000, fmax=0.99, plot=True, warnings=False, precise=True)\n```\ntests the timing on 1000 loops of requested duration 0.01 second (10ms), using within the loop a function sleeping for a random amount of time between 0 and 0.99 dt (with `plot=True` option to see the results on a *matplotlib* graph, and `warnings=False` to not have a printed warning when the execution time of the nested commands exceed the target duration of the loop); `precise=True` uses the timer in precise mode.\n\nThe *AccuracyTests.md* file gathers some accuracy results in Unix and Windows environments. In summary:\n\n- with **Unix**, time fluctuations are < 0.5 ms with the regular timer, and on the order of 0.01 ms (standard deviation) with the precise timer\n\n- with **Windows**, the regular timer fails quickly as frame rate is increased, due to fluctuations in the ~ 10 ms range. However the precise timer performs even better than in Unix, with fluctuations of less than 0.01 ms (standard deviation).\n\n\n## Behavior when interval is exceeded\n\nAs explained above, it the contents of the loop take longer to execute than the requested time interval, the Timer simply moves on to the next loop but does not try to compensate for the extra time by making the next loop shorter:\n\n![](https://raw.githubusercontent.com/ovinc/oclock/master/media/img/timer_interval_exceeded.png)\n\n\n\n# Development\n\nInstall the package by cloning the GitHub repo (https://github.com/ovinc/oclock.git) and install in editable mode from the root of the repo:\n```\npip install -e .\n```\n\n## Testing\n\nPackage requirements to run the tests:\n- pytest\n- numpy\n\nGeneral testing is done with *pytest* (from the root of the repository):\n```bash\npytest\n```\n(**Note**: close the interactive countdown window at the end of the pytest run to finish the test.)\n\nAdditional testing of interactive command line for real-time timer control can be done by running the example file from the root of the repository.\n```bash\npython -m example\n```\n\nSee also *Accuracy Test* paragraph above to run performance tests for constant-duration loops with the `Timer` class.\n\n## Contributing\n\nIssues and Pull requests must be submitted on GitHub (https://github.com/ovinc/oclock) with commits (preferably squashed into a single commit) in branch *authors*.\n\nVersion number is automatically extracted from git tag using *setuptools_scm*. Git tags are added by the repo's maintainer.\n\n# Requirements\n\nPython 3.x\n\n(Tests only made from python 3.5 to python 3.9 included)\n\n\n# Author\n\nOlivier Vincent\n\n(ovinc.py@gmail.com)\n\n# Contributors\n\nThe `oclock.Event` class was originally written by Chris D.\n\n(https://stackoverflow.com/questions/48984512/making-a-timer-timeout-inaccuracy-of-threading-event-wait-python-3-6)\n\n\n# License\n\nGNU GPLv3, see *LICENSE* file\n",
"bugtrack_url": null,
"license": "GNU GPLv3",
"summary": "Tools for timed, no-drift loops of constant duration, and other misc. timing tools (GUI countdown, context managers etc.)",
"version": "1.4.0",
"project_urls": {
"Homepage": "https://github.com/ovinc/oclock"
},
"split_keywords": [
"timing",
"loops",
"constant duration",
"cancellable",
"modifiable",
"countdown",
"context manager",
"gui"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "f1ed0de362ca8db4342ca1cbf95b3746dc00fcb2cbf5a649f03c5eff6ec94102",
"md5": "9b94bc9a40ca8a80c315c534ef20a978",
"sha256": "3d23f70f0f34e06943833ad30aba70d596e128fd3ed799665b87c451e1179f7b"
},
"downloads": -1,
"filename": "oclock-1.4.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "9b94bc9a40ca8a80c315c534ef20a978",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.5",
"size": 32420,
"upload_time": "2023-12-01T16:28:42",
"upload_time_iso_8601": "2023-12-01T16:28:42.670908Z",
"url": "https://files.pythonhosted.org/packages/f1/ed/0de362ca8db4342ca1cbf95b3746dc00fcb2cbf5a649f03c5eff6ec94102/oclock-1.4.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "5cf3ca065b32b4a5bdee80045ab7d8163b3b89e271598dad207c92ada889fc13",
"md5": "8e159c16243518314d85d99f467e833d",
"sha256": "696fdbaa4247c33c90b67846aa952a443377cbab653953fce35b29945bc6d2f3"
},
"downloads": -1,
"filename": "oclock-1.4.0.tar.gz",
"has_sig": false,
"md5_digest": "8e159c16243518314d85d99f467e833d",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.5",
"size": 941072,
"upload_time": "2023-12-01T16:28:45",
"upload_time_iso_8601": "2023-12-01T16:28:45.665718Z",
"url": "https://files.pythonhosted.org/packages/5c/f3/ca065b32b4a5bdee80045ab7d8163b3b89e271598dad207c92ada889fc13/oclock-1.4.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-12-01 16:28:45",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "ovinc",
"github_project": "oclock",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "oclock"
}