executiontimer 0.1.1__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- execution_timer/__init__.py +1 -1
- execution_timer/_timer.py +18 -3
- {executiontimer-0.1.1.dist-info → executiontimer-0.2.0.dist-info}/METADATA +24 -5
- executiontimer-0.2.0.dist-info/RECORD +7 -0
- {executiontimer-0.1.1.dist-info → executiontimer-0.2.0.dist-info}/WHEEL +1 -1
- executiontimer-0.1.1.dist-info/RECORD +0 -7
- {executiontimer-0.1.1.dist-info → executiontimer-0.2.0.dist-info}/licenses/LICENSE +0 -0
execution_timer/__init__.py
CHANGED
execution_timer/_timer.py
CHANGED
|
@@ -247,8 +247,16 @@ class TimerContext:
|
|
|
247
247
|
"""Decorate a function to time its execution under this context.
|
|
248
248
|
|
|
249
249
|
Coroutine functions are wrapped so the timing spans the entire ``await``, not just
|
|
250
|
-
creation of the coroutine object.
|
|
250
|
+
creation of the coroutine object. Generator functions are rejected: a wrapper would
|
|
251
|
+
time only creation of the generator object, not its iteration.
|
|
251
252
|
"""
|
|
253
|
+
if inspect.isgeneratorfunction(func) or inspect.isasyncgenfunction(func):
|
|
254
|
+
msg = (
|
|
255
|
+
f"Cannot decorate generator function {func.__qualname__!r}: only creating the generator "
|
|
256
|
+
"would be timed. Time the loop that consumes it, or its body between yields, with a "
|
|
257
|
+
"'with TimerContext(...)' block instead."
|
|
258
|
+
)
|
|
259
|
+
raise TypeError(msg)
|
|
252
260
|
if inspect.iscoroutinefunction(func):
|
|
253
261
|
# ``iscoroutinefunction`` narrows nothing useful for the type checker, so bridge
|
|
254
262
|
# through an explicitly typed helper instead of leaking ``Any`` into the signature.
|
|
@@ -299,7 +307,10 @@ def log_execution_times(*, flatten: bool = True, logger: logging.Logger | None =
|
|
|
299
307
|
"""Log the execution-times report at INFO level; flatten counters if requested."""
|
|
300
308
|
target = logger if logger is not None else _LOGGER
|
|
301
309
|
if target.isEnabledFor(logging.INFO):
|
|
302
|
-
|
|
310
|
+
report = get_execution_times_report(flatten=flatten)
|
|
311
|
+
# An empty report has already been warned about; logging it would add a blank record.
|
|
312
|
+
if report:
|
|
313
|
+
target.info(report)
|
|
303
314
|
|
|
304
315
|
|
|
305
316
|
def get_execution_timings(*, flatten: bool = True) -> dict[tuple[str, ...], TimingReport]:
|
|
@@ -345,7 +356,11 @@ def save_execution_timings_json(path: str | Path, *, flatten: bool = True, inden
|
|
|
345
356
|
|
|
346
357
|
|
|
347
358
|
def get_total_time(*, flatten: bool = True) -> float:
|
|
348
|
-
"""Get total elapsed seconds across all top-level sections.
|
|
359
|
+
"""Get total elapsed seconds across all top-level sections.
|
|
360
|
+
|
|
361
|
+
``flatten`` has no effect, because merging counter variants cannot change the total. It is
|
|
362
|
+
accepted for symmetry with the other reporting functions.
|
|
363
|
+
"""
|
|
349
364
|
return _TIMER.compute_total_time(flatten=flatten)
|
|
350
365
|
|
|
351
366
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: executiontimer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Hierarchical execution timing with user-defined categories.
|
|
5
5
|
Project-URL: Homepage, https://github.com/seba2390/ExecutionTimer
|
|
6
6
|
Project-URL: Documentation, https://github.com/seba2390/ExecutionTimer#readme
|
|
@@ -16,15 +16,17 @@ Classifier: Intended Audience :: Developers
|
|
|
16
16
|
Classifier: Intended Audience :: Science/Research
|
|
17
17
|
Classifier: Operating System :: OS Independent
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
22
|
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
|
|
22
24
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
23
25
|
Classifier: Topic :: Software Development
|
|
24
26
|
Classifier: Topic :: Software Development :: Testing
|
|
25
27
|
Classifier: Topic :: System :: Benchmark
|
|
26
28
|
Classifier: Typing :: Typed
|
|
27
|
-
Requires-Python: >=3.
|
|
29
|
+
Requires-Python: >=3.11
|
|
28
30
|
Description-Content-Type: text/markdown
|
|
29
31
|
|
|
30
32
|
<p align="center">
|
|
@@ -55,7 +57,8 @@ Zero dependencies. Fully type annotated. Works with threads and `asyncio`.
|
|
|
55
57
|
- 🌳 **Automatic hierarchy** — nesting `with` blocks nests the report, no wiring required
|
|
56
58
|
- 🏷️ **User-defined categories** — tag sections with any string (`"gpu"`, `"io"`, `"db"`) and get per-category totals
|
|
57
59
|
- ⚡ **Native async** — decorating an `async def` times the whole `await`, not the coroutine object
|
|
58
|
-
- 🧵 **Thread and task safe** — context stacks are isolated per thread and per asyncio task
|
|
60
|
+
- 🧵 **Thread and task safe** — context stacks are isolated per thread and per asyncio task,
|
|
61
|
+
including on free-threaded Python builds
|
|
59
62
|
- 🔢 **Loop counters** — time each iteration separately, then merge them back together
|
|
60
63
|
- 📤 **JSON export** — structured output for dashboards, CI, or an LLM
|
|
61
64
|
- 🚫 **Nesting rules** — optionally forbid one category inside another to catch mistakes early
|
|
@@ -71,7 +74,7 @@ pip install executiontimer
|
|
|
71
74
|
uv add executiontimer
|
|
72
75
|
```
|
|
73
76
|
|
|
74
|
-
Requires Python 3.
|
|
77
|
+
Requires Python 3.11+.
|
|
75
78
|
|
|
76
79
|
> **Note** — the install name is `executiontimer`, the import name is `execution_timer`:
|
|
77
80
|
>
|
|
@@ -131,6 +134,10 @@ Coroutine functions are supported natively — the timing spans the entire `awai
|
|
|
131
134
|
async def fetch(url: str) -> bytes: ...
|
|
132
135
|
```
|
|
133
136
|
|
|
137
|
+
Generator functions (including `async` generators) cannot be decorated and raise a
|
|
138
|
+
`TypeError`: the decorator would time only the creation of the generator object, not its
|
|
139
|
+
iteration. Time the loop that consumes the generator with a `with` block instead.
|
|
140
|
+
|
|
134
141
|
### Counters
|
|
135
142
|
|
|
136
143
|
Pass `counter=i` to time loop iterations separately. The report merges them by default
|
|
@@ -233,6 +240,18 @@ New asyncio tasks inherit the timing context in which they are created. Their se
|
|
|
233
240
|
nest under that parent; changes to each task's active stack remain independent. Await
|
|
234
241
|
child tasks inside the parent section if you want the parent duration to include them.
|
|
235
242
|
|
|
243
|
+
New threads do *not* inherit the timing context, so sections recorded in a thread
|
|
244
|
+
appear at the top level of the report, and their time is added to the total alongside
|
|
245
|
+
the section that started the thread. To nest thread work under the current section, run
|
|
246
|
+
it with `contextvars.copy_context().run(...)` or `asyncio.to_thread(...)`. Either way,
|
|
247
|
+
concurrent threads accumulate overlapping time. (Free-threaded builds of Python 3.14 make
|
|
248
|
+
threads inherit the context by default.)
|
|
249
|
+
|
|
250
|
+
Generators run in their caller's context. A `with TimerContext(...)` block that stays open
|
|
251
|
+
across a `yield` therefore also contains whatever the caller times while the generator is
|
|
252
|
+
paused, and its duration includes that paused time. Close sections before yielding, or
|
|
253
|
+
time the loop that consumes the generator instead.
|
|
254
|
+
|
|
236
255
|
### Reusing contexts and clearing timings
|
|
237
256
|
|
|
238
257
|
A `TimerContext` can be reused, nested within itself, or shared by concurrent calls.
|
|
@@ -267,7 +286,7 @@ results using the same interpreter and machine; see [benchmarks/README.md](bench
|
|
|
267
286
|
| `get_execution_timings(*, flatten=True)` | Timings as `dict[tuple[str, ...], TimingReport]`. |
|
|
268
287
|
| `get_execution_times_json(*, flatten=True, indent=2)` | All timings as a JSON string. |
|
|
269
288
|
| `save_execution_timings_json(path, *, flatten=True, indent=2)` | Write timings to a JSON file; returns the `Path`. |
|
|
270
|
-
| `get_total_time(*, flatten=True)` | Total seconds across all top-level sections. |
|
|
289
|
+
| `get_total_time(*, flatten=True)` | Total seconds across all top-level sections (`flatten` has no effect on the sum). |
|
|
271
290
|
| `get_total_category_time(category)` | Total seconds in a category (top-most entries only). |
|
|
272
291
|
| `clear_execution_timings()` | Reset all recorded timings. |
|
|
273
292
|
| `register_forbidden_nesting(outer, inner)` | Forbid `inner` category directly inside `outer`. |
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
execution_timer/__init__.py,sha256=9A5FYIrOvXbohuQMyIis651hYdNmzvNLQxfR02pk92w,959
|
|
2
|
+
execution_timer/_timer.py,sha256=4PSyP501arREnqOKtXEMwjbsb5sFISVYFRc1EXOXGP0,15366
|
|
3
|
+
execution_timer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
executiontimer-0.2.0.dist-info/METADATA,sha256=B6G1utu8t7BPwVh8KFID_pPr-c9z2bW0M6hd8-eiPWg,12631
|
|
5
|
+
executiontimer-0.2.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
6
|
+
executiontimer-0.2.0.dist-info/licenses/LICENSE,sha256=rkJbDOYRrUX8k3S55pPiEquJXZGh07KvD01F1sBBppw,1077
|
|
7
|
+
executiontimer-0.2.0.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
execution_timer/__init__.py,sha256=WC6Cn6-mGTEHh1386OJ--hrAFaa9Z6eSBFze21brEfw,959
|
|
2
|
-
execution_timer/_timer.py,sha256=RbLD7HshcDBlcz0II7JiLZduuNr6Dao0D_d0gs9cuww,14526
|
|
3
|
-
execution_timer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
executiontimer-0.1.1.dist-info/METADATA,sha256=sAJT1E9gFJHlfd2oVwAwcrWYebEhq3xhgmjMIvv3NS4,11374
|
|
5
|
-
executiontimer-0.1.1.dist-info/WHEEL,sha256=THafob7ofN-NsuMN7Mg4qZyHaQI7KkD-QlcQatYhXPo,87
|
|
6
|
-
executiontimer-0.1.1.dist-info/licenses/LICENSE,sha256=rkJbDOYRrUX8k3S55pPiEquJXZGh07KvD01F1sBBppw,1077
|
|
7
|
-
executiontimer-0.1.1.dist-info/RECORD,,
|
|
File without changes
|