with blocks guarantee clean-up even when errors happen. Write one that measures how long a block takes, which is handy for profiling slow LLM calls or data loads.
Implement a class Timer(clock=time.perf_counter):
with Timer() as t: records the start time on entry and the end on exit.t.elapsed holds the duration in seconds after the block. It's None before the block finishes.t.laps is a list. Calling t.lap() inside the block appends the time since the start (or since the previous lap).clock is injectable so tests can use a fake clock.