diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e53000..f68c0f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,15 @@ All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog1.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added + +- Attributes that can be referenced in the `text` template string (suggested by [@mlisovyi](https://github.com/mlisovyi) in [#24]). + ### Changed - `Timer.timers` changed from regular to `dict` to a custom dictionary supporting basic statistics for named timers. diff --git a/README.md b/README.md index e485e6b..96d8b96 100644 --- a/README.md +++ b/README.md @@ -57,12 +57,38 @@ You can use `codetiming.Timer` in several different ways: You can turn off explicit reporting of the elapsed time by setting `logger=None`. +In the template text, you can also use explicit attributes to refer to the `name` of the timer, or log the elapsed time in `milliseconds`, `seconds` (the default), or `minutes`. For example: + +```python +t1 = Timer(name="NamedTimer", text="{name}: {minutes:.1f} minutes") +t2 = Timer(text="Elapsed time: {milliseconds:.0f} ms") +``` + +Note that the strings used by `text` are **not** f-strings. Instead they are used as templates that will be populated using `.format()` behind the scenes. If you want to combine the `text` template with an f-string, you need to use double braces for the template values: + +```python +t = Timer(text=f"{__file__}: {{:.4f}}") +``` + + +## Capturing the Elapsed Time + When using `Timer` as a class, you can capture the elapsed time when calling `.stop()`: ```python elapsed_time = t.stop() ``` +You can also find the last measured elapsed time in the `.last` attribute. The following code will have the same effect as the previous example: + +```python +t.stop() +elapsed_time = t.last +``` + + +## Named Timers + Named timers are made available in the class dictionary `Timer.timers`. The elapsed time will accumulate if the same name or same timer is used several times. Consider the following example: ```python @@ -87,6 +113,21 @@ WARNING:root:Time spent: 1.73 The example shows how you can redirect the timer output to the logging module. Note that the elapsed time spent in the two different uses of `t` has been accumulated in `Timer.timers`. +You can also get simple statistics about your named timers. Continuing from the example above: + +```python +>>> Timer.timers.max("example") +3.5836678670002584 + +>>> Timer.timers.mean("example") +2.6563487200000964 + +>>> Timer.timers.stdev("example") +1.311427314335879 +``` + +`timers` support `.count()`, `.total()`, `.min()`, `.max()`, `.mean()`, `.median()`, and `.stdev()`. + ## Acknowledgements diff --git a/codetiming/_timer.py b/codetiming/_timer.py index ac96763..39b483e 100644 --- a/codetiming/_timer.py +++ b/codetiming/_timer.py @@ -48,7 +48,13 @@ def stop(self) -> float: # Report elapsed time if self.logger: - self.logger(self.text.format(self.last)) + attributes = { + "name": self.name, + "milliseconds": self.last * 1000, + "seconds": self.last, + "minutes": self.last / 60, + } + self.logger(self.text.format(self.last, **attributes)) if self.name: self.timers.add(self.name, self.last) diff --git a/tests/test_codetiming.py b/tests/test_codetiming.py index fa3a559..aea8596 100644 --- a/tests/test_codetiming.py +++ b/tests/test_codetiming.py @@ -186,6 +186,45 @@ def test_timer_sets_last(): assert t.last >= 0.02 +def test_using_name_in_text_without_explicit_timer(capsys): + """Test that the name of the timer can be referenced in the text""" + name = "NamedTimer" + with Timer(name=name, text="{name}: {:.2f}"): + waste_time() + + stdout, stderr = capsys.readouterr() + assert re.match(f"{name}: " + r"0\.\d{2}", stdout) + + +def test_using_name_in_text_with_explicit_timer(capsys): + """Test that the name of the timer and the seconds attribute can be referenced in the text""" + name = "NamedTimer" + with Timer(name=name, text="{name}: {seconds:.2f}"): + waste_time() + + stdout, stderr = capsys.readouterr() + assert re.match(f"{name}: " + r"0\.\d{2}", stdout.strip()) + + +def test_using_minutes_attribute_in_text(capsys): + """Test that timer can report its duration in minutes""" + with Timer(text="{minutes:.1f} minutes"): + waste_time() + + stdout, stderr = capsys.readouterr() + assert stdout.strip() == "0.0 minutes" + + +def test_using_milliseconds_attribute_in_text(capsys): + """Test that timer can report its duration in milliseconds""" + with Timer(text="{milliseconds:.0f} {seconds:.3f}"): + waste_time() + + stdout, stderr = capsys.readouterr() + milliseconds, _, seconds = stdout.partition(" ") + assert int(milliseconds) == round(float(seconds) * 1000) + + def test_timers_cleared(): """Test that timers can be cleared""" with Timer(name="timer_to_be_cleared"):