executiontimer 1.0.1__py3-none-any.whl → 1.0.2__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 +14 -3
- {executiontimer-1.0.1.dist-info → executiontimer-1.0.2.dist-info}/METADATA +8 -4
- executiontimer-1.0.2.dist-info/RECORD +7 -0
- executiontimer-1.0.1.dist-info/RECORD +0 -7
- {executiontimer-1.0.1.dist-info → executiontimer-1.0.2.dist-info}/WHEEL +0 -0
- {executiontimer-1.0.1.dist-info → executiontimer-1.0.2.dist-info}/licenses/LICENSE +0 -0
execution_timer/__init__.py
CHANGED
execution_timer/_timer.py
CHANGED
|
@@ -278,8 +278,9 @@ class TimerContext:
|
|
|
278
278
|
"""Decorate a function to time its execution under this context.
|
|
279
279
|
|
|
280
280
|
Coroutine functions are wrapped so the timing spans the entire ``await``, not just
|
|
281
|
-
creation of the coroutine object.
|
|
282
|
-
|
|
281
|
+
creation of the coroutine object. So is a coroutine returned by a plain function,
|
|
282
|
+
typically another decorator stacked on an ``async def``. Generator functions are
|
|
283
|
+
rejected: a wrapper would time only creation of the generator object, not its iteration.
|
|
283
284
|
"""
|
|
284
285
|
if inspect.isgeneratorfunction(func) or inspect.isasyncgenfunction(func):
|
|
285
286
|
msg = (
|
|
@@ -297,10 +298,20 @@ class TimerContext:
|
|
|
297
298
|
@functools.wraps(func)
|
|
298
299
|
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
299
300
|
with self:
|
|
300
|
-
|
|
301
|
+
result = func(*args, **kwargs)
|
|
302
|
+
if inspect.iscoroutine(result):
|
|
303
|
+
# A decorator between this one and an ``async def`` hides the coroutine function,
|
|
304
|
+
# so the call above only created the coroutine. Time awaiting it as well.
|
|
305
|
+
return cast("R", self._time_await(result))
|
|
306
|
+
return result
|
|
301
307
|
|
|
302
308
|
return wrapper
|
|
303
309
|
|
|
310
|
+
async def _time_await(self, coroutine: Coroutine[object, object, T]) -> T:
|
|
311
|
+
"""Await a coroutine that was created outside this context, timing the whole await."""
|
|
312
|
+
with self:
|
|
313
|
+
return await coroutine
|
|
314
|
+
|
|
304
315
|
def _wrap_async(self, func: Callable[P, Coroutine[object, object, T]]) -> Callable[P, Coroutine[object, object, T]]:
|
|
305
316
|
"""Wrap a coroutine function so the timing spans the whole await."""
|
|
306
317
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: executiontimer
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
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
|
|
@@ -134,6 +134,9 @@ Coroutine functions are supported natively — the timing spans the entire `awai
|
|
|
134
134
|
async def fetch(url: str) -> bytes: ...
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
+
This also holds when another decorator sits between `TimerContext` and the `async def`
|
|
138
|
+
and returns its coroutine: the section covers both the call and the `await`.
|
|
139
|
+
|
|
137
140
|
Generator functions (including `async` generators) cannot be decorated and raise a
|
|
138
141
|
`TypeError`: the decorator would time only the creation of the generator object, not its
|
|
139
142
|
iteration. Time the loop that consumes the generator with a `with` block instead.
|
|
@@ -153,7 +156,9 @@ get_execution_timings(flatten=False) # {("step[0]",): ..., ("step[1]",): ..., .
|
|
|
153
156
|
```
|
|
154
157
|
|
|
155
158
|
Flattening removes the final integer suffix (including negative counters). Other
|
|
156
|
-
bracketed names such as `array[index]` are preserved.
|
|
159
|
+
bracketed names such as `array[index]` are preserved. Flattening cannot tell a counter
|
|
160
|
+
from a name you wrote yourself, so sections named `"row[1]"` and `"row[2]"` are also merged
|
|
161
|
+
into `row`; use `flatten=False` to keep them apart. If merged entries have different
|
|
157
162
|
categories, the category from the most recently entered section is used.
|
|
158
163
|
|
|
159
164
|
Each counter value is stored as its own section until `clear_execution_timings()` is
|
|
@@ -286,7 +291,6 @@ an active section's current duration is added only when it exits.
|
|
|
286
291
|
Run the repeatable benchmark with `uv run python benchmarks/overhead.py`. It measures
|
|
287
292
|
fresh and reused contexts, sync and async decorators, nesting, and reporting. Compare
|
|
288
293
|
results using the same interpreter and machine; see [benchmarks/README.md](https://github.com/seba2390/ExecutionTimer/blob/main/benchmarks/README.md).
|
|
289
|
-
`log_execution_times()` skips building a report when its logger has `INFO` disabled.
|
|
290
294
|
|
|
291
295
|
## API
|
|
292
296
|
|
|
@@ -294,7 +298,7 @@ results using the same interpreter and machine; see [benchmarks/README.md](https
|
|
|
294
298
|
| --- | --- |
|
|
295
299
|
| `TimerContext(name, category=DEFAULT_CATEGORY, counter=None)` | Context manager **and** decorator for timing a section. |
|
|
296
300
|
| `get_execution_times_report(*, flatten=True)` | Formatted, indented report of all sections (`""` if none). |
|
|
297
|
-
| `log_execution_times(*, flatten=True, logger=None)` | Log that report at `INFO` level (a warning if empty). |
|
|
301
|
+
| `log_execution_times(*, flatten=True, logger=None)` | Log that report at `INFO` level (a warning if empty); a no-op if `INFO` is disabled. |
|
|
298
302
|
| `get_execution_timings(*, flatten=True)` | Timings as `dict[tuple[str, ...], TimingReport]`. |
|
|
299
303
|
| `get_execution_times_json(*, flatten=True, indent=2)` | All timings as a JSON string. |
|
|
300
304
|
| `save_execution_timings_json(path, *, flatten=True, indent=2)` | Write timings to a JSON file; returns the `Path`. |
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
execution_timer/__init__.py,sha256=WBDnG2RxhKZmuhb5U4wgVV4WNgvZehegtfBuSh7AA6I,959
|
|
2
|
+
execution_timer/_timer.py,sha256=ermOhB2xWM4acsGgkHuN3MyeA9uqpa9FgNuoAABdGjY,17439
|
|
3
|
+
execution_timer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
executiontimer-1.0.2.dist-info/METADATA,sha256=7hk9S89FJ0b9H-O2IAWak-c_ef55RS1Vq1pJPh4gCGY,13900
|
|
5
|
+
executiontimer-1.0.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
6
|
+
executiontimer-1.0.2.dist-info/licenses/LICENSE,sha256=rkJbDOYRrUX8k3S55pPiEquJXZGh07KvD01F1sBBppw,1077
|
|
7
|
+
executiontimer-1.0.2.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
execution_timer/__init__.py,sha256=SDCT34iz3KKhr1CyiCpvMrjjdf1umy55sScSp4bKUBo,959
|
|
2
|
-
execution_timer/_timer.py,sha256=4RP4SDki9TasaCfk56lmJX54EiYnQsvHOd5WnZuuoxI,16778
|
|
3
|
-
execution_timer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
executiontimer-1.0.1.dist-info/METADATA,sha256=o5McC2gZg2rh2G65qv0Hh6tdmGo6Mk_TfPfym_OmKhA,13612
|
|
5
|
-
executiontimer-1.0.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
6
|
-
executiontimer-1.0.1.dist-info/licenses/LICENSE,sha256=rkJbDOYRrUX8k3S55pPiEquJXZGh07KvD01F1sBBppw,1077
|
|
7
|
-
executiontimer-1.0.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|