Repository navigation
Releases: seba2390/ExecutionTimer
Releases · seba2390/ExecutionTimer
Release list
v1.1.1
Documentation
- The threads section of the concurrency guide was wrong for free-threaded Python 3.14.
There, a new thread starts in a copy of the context that started it, so its sections
nest under the section that was open at that moment. A thread pool reuses its workers,
so each worker keeps the section it started under: work submitted later from another
section was reported under the first one. The guide said pool work is always reported at
the top level, and that free-threaded builds nest thread work correctly with no extra
work. Neither was true on every build. The guide now explains the difference and shows
how to get the same report on every build: run each call in
contextvars.copy_context().runto nest it under the current section, or in
contextvars.Context().runto keep it at the top level. Behaviour is unchanged. - Tests cover how threads and reused pool workers nest on each kind of build, and both
recipes. The guide's examples assert their results on every build.
Full changelog: v1.1.0...v1.1.1
v1.1.0
Changed
- The text report, from
get_execution_times_report()andlog_execution_times(), shows
each time with three decimals inµs,msors, whichever keeps the number below
1000:48.213 µs,315.612 ms,2.500 s. It used to show seconds with four
decimals, so any section under 50 µs read0.0000 s, and fast sections, including a
whole recursive call tree, looked like zeros. Code that parses the report text needs
updating.get_execution_timings()and the JSON export are unchanged and still return
seconds.
Full changelog: v1.0.3...v1.1.0
v1.0.3
Changed
- Entering and exiting a section is 20–25% faster, about 0.25 µs less per section on
CPython 3.14. Each active section's state is now a plain tuple rather than a named
tuple, whose constructor cost more than the rest of section entry. Behaviour is
unchanged.
Documentation
- A documentation site at https://seba2390.github.io/ExecutionTimer/, with a getting
started guide, a user guide and an API reference generated from the docstrings. It is
built with Sphinx and deployed to GitHub Pages whenevermainchanges. The package's
Documentationlink points to it from this release on. - The README is shorter and links to the site for details.
- Public functions and types have full docstrings, with parameters, return values and
exceptions. - The Python examples in the README and the documentation run as part of the test suite,
and CI builds the site on every pull request, failing on any warning. - Refresh the overhead benchmark, comparing 0.1.0, 1.0.2 and 1.0.3.
Full changelog: v1.0.2...v1.0.3
v1.0.2
[1.0.2] - 2026-09-30
Fixed
- Decorating a function that returns a coroutine, typically because another decorator
sits betweenTimerContextand anasync def, now times theawait. Previously the
section recorded only the call that created the coroutine, a few microseconds however
long the coroutine ran. Other awaitables, such as futures and tasks, are returned
unchanged.
Changed
- The test suite fails on any warning, and CI requires 100% statement and branch coverage
rather than 95%.
Documentation
- Note that flattening also merges sections whose own names end in an integer, such as
"row[1]"and"row[2]". - The release steps in the contributing guide describe the pull-request flow that the
protectedmainbranch requires. - Refresh the overhead benchmark against version 1.0.2.
v1.0.1
[1.0.1] - 2026-09-30
Fixed
- Exiting a section while an inner one is still active no longer leaves the active stack
corrupted when warnings are configured as errors (for example pytest's
filterwarnings = ["error"]). TheRuntimeWarningwas emitted before the exit was
recorded, so raising it skipped the cleanup that 1.0.0 introduced. The section is now
recorded and the stack unwound before the warning is emitted. - Sections whose parent was cleared while active now count as top-level. Previously,
clearing timings inside an outer section, such as a periodic clear in a long-running
loop, madeget_total_time(), the report and the JSONtotal_timeread0, every
percentage0.00%, and indented the sections beneath an unrelated one in the report.
Changed
- The build requires
hatchling>=1.27, the first release that supports the PEP 639
license-filesmetadata the project declares. - Dependabot groups its monthly updates into one pull request per ecosystem.
v1.0.0
[1.0.0] - 2026-09-30
First stable release. The public API is now covered by semantic versioning: breaking
changes will wait for 2.0. Three changes below are breaking; they clean up the API before
it is frozen.
Changed
- Breaking: the report from
get_execution_times_report()no longer starts with a
blank line, and its header readsTotal time:rather thanTotal calculation time:.
log_execution_times()still starts the report on its own line. - Breaking:
TimerContext.timeris now private (_timer). It exposed the internal
registry, which is not part of the public API. get_execution_times_report()no longer logs a warning when there are no timings; it
returns"".log_execution_times()warns instead, on the logger it was given, so
building an empty report no longer prints to stderr when logging is not configured.- The package is classified as
Development Status :: 5 - Production/Stable.
Removed
- Breaking: the
flattenparameter ofget_total_time(), which had no effect.
Documentation
- README links to the changelog, contributing guide, benchmarks and license are absolute,
so they work on PyPI. - Note that each
counter=value is kept as a separate section until the timings are
cleared.
Fixed
- Exiting a section while an inner one is still active, typically because a paused
generator holds a section open, no longer raisesRuntimeError. The raise replaced any
exception already propagating from the timed block and left the active stack corrupted
for the rest of the thread. The timer now emits aRuntimeWarning, records the exited
section, and discards the unfinished inner sections. Exiting a section that is no longer
active does nothing, so closing the paused generator later no longer raises either.
Security
- Workflows pin every action to a full commit SHA, with the release as a comment that
Dependabot keeps current, and CI installs dependencies withuv sync --lockedso a
stale lockfile fails the build.
v0.2.0
[0.2.0] - 2026-09-30
Changed
- Decorating a generator function or an async generator function now raises
TypeError.
Previously it silently timed only the creation of the generator object, which recorded
microseconds regardless of how long iteration took. - Python 3.11 is now supported; the minimum was 3.12 although nothing required it.
Fixed
log_execution_times()no longer logs an emptyINFOrecord, after its warning, when
there are no timings.
Documentation
- Explain that threads do not inherit the timing context, and how to nest thread work under
a section. - Explain how a section held open across a generator's
yieldabsorbs the caller's sections. - Note that
flattenhas no effect onget_total_time.
Added
- CI tests Python 3.11 and free-threaded Python 3.14t, and the package declares
free-threading support. - Workflows run with a read-only token by default and do not persist checkout credentials.
v0.1.1
[0.1.1] - 2026-09-20
Fixed
- Overlapping calls to the same section now accumulate each call's actual duration in
threads and asyncio tasks, including calls sharing one context or decorator. - Clearing active timings cannot add a discarded sample to a new entry at the same path.
- JSON sections and totals now use one consistent snapshot, and category totals retain
categories that disappear when counter variants are merged. - Flattened categories follow the most recently entered section, including revisited counters.
- Flattening preserves non-integer bracket suffixes such as
array[index]andempty[]. - An out-of-order context exit raises
RuntimeErrorwithout changing another section's
elapsed time or active stack.
Performance
- Cache each active section's path and parent, and reuse decorator contexts to reduce
recording allocations and avoid rebuilding paths on exit. - Sum total time directly without copying and flattening the registry.
- Calculate all JSON category totals in one pass over a shared snapshot.
- Skip report generation when logging at
INFOis disabled.
Added
- Deterministic regression tests for concurrency, clearing, category attribution, and
snapshot consistency, plus coverage for recursion, cancellation, and deep nesting. - A repeatable benchmark for recording and reporting overhead in
benchmarks/overhead.py. - Expanded the suite from 53 to 88 tests, achieving 100% statement and branch coverage.
v0.1.0
First public release of executiontimer — hierarchical execution timing with user-defined categories.
pip install executiontimerfrom execution_timer import TimerContext, get_execution_times_report
with TimerContext("solve"):
with TimerContext("step", category="gpu"):
...
print(get_execution_times_report())The install name is
executiontimer; the import name isexecution_timer.
Highlights
TimerContextworks as both a context manager and a decorator- Nested
withblocks automatically form a hierarchy in the report - Native
async defsupport — timing spans the wholeawait - Per-thread and per-asyncio-task context isolation via
contextvars - User-defined categories with per-category totals, and optional forbidden-nesting rules
- Loop counters via
counter=, merged back together withflatten=True - JSON export for dashboards, CI, or an LLM
- Zero dependencies, fully typed, ships
py.typed
Requires Python 3.12+. Tested on 3.12, 3.13 and 3.14 across Linux, macOS and Windows.
See CHANGELOG.md for the full list.