Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
8 changes: 7 additions & 1 deletion codetiming/_timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
39 changes: 39 additions & 0 deletions tests/test_codetiming.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"):
Expand Down