kaparoo-python 0.6.0__py3-none-any.whl → 0.7.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.
- kaparoo/filesystem/__init__.py +2 -0
- kaparoo/filesystem/utils.py +53 -1
- kaparoo/utils/__init__.py +3 -3
- kaparoo/utils/timer.py +32 -32
- {kaparoo_python-0.6.0.dist-info → kaparoo_python-0.7.0.dist-info}/METADATA +2 -2
- {kaparoo_python-0.6.0.dist-info → kaparoo_python-0.7.0.dist-info}/RECORD +8 -8
- {kaparoo_python-0.6.0.dist-info → kaparoo_python-0.7.0.dist-info}/WHEEL +1 -1
- {kaparoo_python-0.6.0.dist-info → kaparoo_python-0.7.0.dist-info}/licenses/LICENSE +0 -0
kaparoo/filesystem/__init__.py
CHANGED
|
@@ -16,6 +16,7 @@ __all__ = (
|
|
|
16
16
|
"ensure_dir_exists",
|
|
17
17
|
"ensure_dirs_exist",
|
|
18
18
|
"ensure_file_exists",
|
|
19
|
+
"ensure_file_extension",
|
|
19
20
|
"ensure_files_exist",
|
|
20
21
|
"ensure_path_exists",
|
|
21
22
|
"ensure_paths_exist",
|
|
@@ -79,6 +80,7 @@ from kaparoo.filesystem.search import (
|
|
|
79
80
|
)
|
|
80
81
|
from kaparoo.filesystem.staged import StagedDirectory, StagedFile
|
|
81
82
|
from kaparoo.filesystem.utils import (
|
|
83
|
+
ensure_file_extension,
|
|
82
84
|
reserve_path,
|
|
83
85
|
reserve_paths,
|
|
84
86
|
stringify_path,
|
kaparoo/filesystem/utils.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
__all__ = (
|
|
4
|
+
"ensure_file_extension",
|
|
4
5
|
"reserve_path",
|
|
5
6
|
"reserve_paths",
|
|
6
7
|
"stringify_path",
|
|
@@ -15,7 +16,7 @@ from pathlib import Path
|
|
|
15
16
|
from typing import TYPE_CHECKING, overload
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
18
|
-
from collections.abc import Sequence
|
|
19
|
+
from collections.abc import Iterable, Sequence
|
|
19
20
|
from typing import Literal
|
|
20
21
|
|
|
21
22
|
from kaparoo.filesystem.types import StrPath, StrPaths
|
|
@@ -357,3 +358,54 @@ def reserve_paths(
|
|
|
357
358
|
reserve_path(p, exist_ok=exist_ok, make_parents=make_parents) for p in paths
|
|
358
359
|
]
|
|
359
360
|
return stringify_paths(paths) if stringify else paths
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def ensure_file_extension(
|
|
364
|
+
path: StrPath, ext: str | Iterable[str], *, add: bool = False
|
|
365
|
+
) -> Path:
|
|
366
|
+
"""Return `path` as a `Path`, requiring a case-insensitive `.<ext>` suffix.
|
|
367
|
+
|
|
368
|
+
A pure path check that never touches the filesystem. `ext` is a single
|
|
369
|
+
extension or an iterable of acceptable ones (e.g. `("jpg", "jpeg")`); the
|
|
370
|
+
leading dot on each is optional, so `"bin"` and `".bin"` behave the same.
|
|
371
|
+
Only the final suffix is considered: `archive.tar.gz` matches `ext="gz"`,
|
|
372
|
+
not `ext="tar.gz"`.
|
|
373
|
+
|
|
374
|
+
`add` mirrors `make` on `ensure_dir_exists`: when False (the default) a
|
|
375
|
+
path with no suffix raises like any other mismatch; when True, the missing
|
|
376
|
+
suffix is appended -- the *first* of `ext` when several are given, so pass
|
|
377
|
+
an ordered list/tuple if that matters. A *wrong* suffix always raises,
|
|
378
|
+
regardless of `add`.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
path: The path to check.
|
|
382
|
+
ext: The required extension, or an iterable of acceptable ones, each
|
|
383
|
+
with or without a leading dot.
|
|
384
|
+
add: Whether to append the (first) extension when `path` has no
|
|
385
|
+
suffix, instead of raising. Defaults to False.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
The path as a Path object, guaranteed to end in an accepted `.<ext>`.
|
|
389
|
+
|
|
390
|
+
Raises:
|
|
391
|
+
ValueError: If `ext` is empty, or `path`'s final suffix is none of the
|
|
392
|
+
accepted extensions -- except the no-suffix case resolved by
|
|
393
|
+
`add=True`.
|
|
394
|
+
"""
|
|
395
|
+
exts = [ext] if isinstance(ext, str) else list(ext)
|
|
396
|
+
exts = [e.removeprefix(".") for e in exts]
|
|
397
|
+
|
|
398
|
+
if not exts:
|
|
399
|
+
msg = "ext must name at least one extension"
|
|
400
|
+
raise ValueError(msg)
|
|
401
|
+
|
|
402
|
+
path = Path(path)
|
|
403
|
+
if add and not path.suffix:
|
|
404
|
+
return path.with_suffix(f".{exts[0]}")
|
|
405
|
+
|
|
406
|
+
if path.suffix.lower() not in {f".{e.lower()}" for e in exts}:
|
|
407
|
+
wanted = " / ".join(f".{e}" for e in exts)
|
|
408
|
+
msg = f"{path.name} must have a {wanted} extension (got {path.suffix!r})"
|
|
409
|
+
raise ValueError(msg)
|
|
410
|
+
|
|
411
|
+
return path
|
kaparoo/utils/__init__.py
CHANGED
|
@@ -6,8 +6,8 @@ __all__ = (
|
|
|
6
6
|
"Mean",
|
|
7
7
|
"Min",
|
|
8
8
|
"Reduction",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
9
|
+
"SpanRecord",
|
|
10
|
+
"SpanTimer",
|
|
11
11
|
"Std",
|
|
12
12
|
"Sum",
|
|
13
13
|
"Timer",
|
|
@@ -42,4 +42,4 @@ from kaparoo.utils.optional import (
|
|
|
42
42
|
unwrap_or_factories,
|
|
43
43
|
unwrap_or_factory,
|
|
44
44
|
)
|
|
45
|
-
from kaparoo.utils.timer import
|
|
45
|
+
from kaparoo.utils.timer import SpanRecord, SpanTimer, Timer
|
kaparoo/utils/timer.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
__all__ = ("
|
|
3
|
+
__all__ = ("SpanRecord", "SpanTimer", "Timer")
|
|
4
4
|
|
|
5
5
|
import time
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
@@ -22,20 +22,20 @@ _SCALES: dict[str, float] = {"s": 1e-9, "ms": 1e-6, "us": 1e-3, "ns": 1.0}
|
|
|
22
22
|
_LABEL_POLICIES: frozenset[str] = frozenset({"merge", "separate", "reject"})
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
class
|
|
26
|
-
"""A single timing record produced by `
|
|
25
|
+
class SpanRecord(TypedDict):
|
|
26
|
+
"""A single timing record produced by `SpanTimer`.
|
|
27
27
|
|
|
28
|
-
A
|
|
28
|
+
A span is produced either by `lap` (the span since the previous
|
|
29
29
|
lap) or by `measure` (the span of a wrapped block).
|
|
30
30
|
|
|
31
31
|
Attributes:
|
|
32
|
-
label: The
|
|
32
|
+
label: The span's name. May carry a " (N)" suffix when produced
|
|
33
33
|
under `on_same_label="separate"`.
|
|
34
|
-
duration: Length of this
|
|
34
|
+
duration: Length of this span, in the timer's `unit` and rounded
|
|
35
35
|
by `ndigits` if given. For `lap`, the time since the previous
|
|
36
36
|
lap (or the timer start); for `measure`, the wrapped block's
|
|
37
37
|
duration.
|
|
38
|
-
total_time: Time elapsed from the timer start to this
|
|
38
|
+
total_time: Time elapsed from the timer start to this span's end,
|
|
39
39
|
in the timer's `unit` and rounded by `ndigits` if given.
|
|
40
40
|
"""
|
|
41
41
|
|
|
@@ -45,14 +45,14 @@ class SegmentRecord(TypedDict):
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
class BaseTimer(ContextDecorator, ABC):
|
|
48
|
-
"""Abstract base for `Timer` and `
|
|
48
|
+
"""Abstract base for `Timer` and `SpanTimer`.
|
|
49
49
|
|
|
50
50
|
Provides the shared timing machinery: unit/precision formatting,
|
|
51
51
|
`pause`/`resume`/`suspend`, and a context-manager protocol that
|
|
52
52
|
auto-resumes a paused timer on exit. Subclasses implement `_finalize`
|
|
53
53
|
to record their final result, and may override the `_reset` hook to
|
|
54
54
|
clear per-`with`-block state. Not part of the public API -- prefer
|
|
55
|
-
`Timer` or `
|
|
55
|
+
`Timer` or `SpanTimer`.
|
|
56
56
|
|
|
57
57
|
An instance is reusable but **not reentrant**: a single instance must
|
|
58
58
|
not be nested within itself -- including as a decorator on a recursive
|
|
@@ -249,30 +249,30 @@ class Timer(BaseTimer):
|
|
|
249
249
|
self.elapsed = self._format_time(elapsed_ns)
|
|
250
250
|
|
|
251
251
|
|
|
252
|
-
class
|
|
253
|
-
"""A timer recording named time
|
|
252
|
+
class SpanTimer(BaseTimer):
|
|
253
|
+
"""A timer recording named time spans within one `with` block.
|
|
254
254
|
|
|
255
|
-
Usable as a context manager or as a decorator.
|
|
255
|
+
Usable as a context manager or as a decorator. Spans are recorded
|
|
256
256
|
in two complementary ways:
|
|
257
257
|
|
|
258
258
|
- `lap(label)` splits the timeline: each lap's `duration` is the
|
|
259
259
|
time since the previous lap (or the start), so every instant is
|
|
260
|
-
attributed to exactly one
|
|
260
|
+
attributed to exactly one span.
|
|
261
261
|
- `measure(label)` brackets a region (as a `with` block or a
|
|
262
262
|
decorator): only the wrapped span is recorded, and time spent
|
|
263
|
-
outside any `measure` block is attributed to no
|
|
263
|
+
outside any `measure` block is attributed to no span.
|
|
264
264
|
|
|
265
265
|
Both feed the same `records` / `summary`, honour `on_same_label`, and
|
|
266
266
|
exclude paused intervals.
|
|
267
267
|
|
|
268
268
|
Attributes:
|
|
269
269
|
on_same_label: The same-label handling policy (see `__init__`).
|
|
270
|
-
records: The recorded
|
|
270
|
+
records: The recorded spans, in call order.
|
|
271
271
|
elapsed: The total measured duration, from start to exit.
|
|
272
272
|
Defaults to 0.0 until the first exit.
|
|
273
273
|
|
|
274
274
|
Example:
|
|
275
|
-
with
|
|
275
|
+
with SpanTimer("ms", ndigits=1) as st:
|
|
276
276
|
step_a()
|
|
277
277
|
st.lap("A") # split: time since start
|
|
278
278
|
with st.measure("B"): # block: only this region
|
|
@@ -288,7 +288,7 @@ class SegmentTimer(BaseTimer):
|
|
|
288
288
|
ndigits: int | None = None,
|
|
289
289
|
on_same_label: LabelPolicy = "merge",
|
|
290
290
|
) -> None:
|
|
291
|
-
"""Initialize the
|
|
291
|
+
"""Initialize the span timer.
|
|
292
292
|
|
|
293
293
|
Args:
|
|
294
294
|
unit: The time unit for reported values. One of "s", "ms", "us",
|
|
@@ -313,7 +313,7 @@ class SegmentTimer(BaseTimer):
|
|
|
313
313
|
raise ValueError(msg)
|
|
314
314
|
|
|
315
315
|
self.on_same_label = on_same_label
|
|
316
|
-
self.records: list[
|
|
316
|
+
self.records: list[SpanRecord] = []
|
|
317
317
|
self.elapsed: float = 0.0
|
|
318
318
|
|
|
319
319
|
self._last_time: int = 0
|
|
@@ -323,8 +323,8 @@ class SegmentTimer(BaseTimer):
|
|
|
323
323
|
def summary(self) -> dict[str, float]:
|
|
324
324
|
"""Per-label sum of `duration` across `records`.
|
|
325
325
|
|
|
326
|
-
Only recorded
|
|
327
|
-
|
|
326
|
+
Only recorded spans count: time outside every `lap` / `measure`
|
|
327
|
+
span (e.g. after the last `lap`, or between `measure` blocks) is
|
|
328
328
|
not included. Each record's `duration` is already rounded by
|
|
329
329
|
`ndigits` (when set); this property sums those rounded values and
|
|
330
330
|
rounds the sum once more.
|
|
@@ -377,11 +377,11 @@ class SegmentTimer(BaseTimer):
|
|
|
377
377
|
else label
|
|
378
378
|
)
|
|
379
379
|
|
|
380
|
-
def _make_record(self, label: str) ->
|
|
380
|
+
def _make_record(self, label: str) -> SpanRecord:
|
|
381
381
|
"""Build a record stamped with the current time and advance `_last_time`."""
|
|
382
382
|
current_time = time.perf_counter_ns()
|
|
383
383
|
|
|
384
|
-
record:
|
|
384
|
+
record: SpanRecord = {
|
|
385
385
|
"label": label,
|
|
386
386
|
"duration": self._format_time(current_time - self._last_time),
|
|
387
387
|
"total_time": self._format_time(current_time - self._start_time),
|
|
@@ -415,12 +415,12 @@ class SegmentTimer(BaseTimer):
|
|
|
415
415
|
|
|
416
416
|
@contextmanager
|
|
417
417
|
def measure(self, label: str = "Block") -> Iterator[None]:
|
|
418
|
-
"""Record a
|
|
418
|
+
"""Record a span covering only the wrapped block (stopwatch style).
|
|
419
419
|
|
|
420
|
-
Unlike `lap`, which splits the timeline into contiguous
|
|
420
|
+
Unlike `lap`, which splits the timeline into contiguous spans,
|
|
421
421
|
`measure` times only the wrapped region; time spent outside any
|
|
422
|
-
`measure` block is attributed to no
|
|
423
|
-
block are excluded. A
|
|
422
|
+
`measure` block is attributed to no span. Pauses inside the
|
|
423
|
+
block are excluded. A span is recorded only on clean exit -- if
|
|
424
424
|
the block raises, nothing is recorded and the exception propagates.
|
|
425
425
|
Repeated labels follow `on_same_label`, exactly as `lap`. Do not
|
|
426
426
|
nest `measure` blocks: each resets the shared baseline, so an outer
|
|
@@ -428,23 +428,23 @@ class SegmentTimer(BaseTimer):
|
|
|
428
428
|
|
|
429
429
|
Because `contextmanager` results are also `ContextDecorator`s, the
|
|
430
430
|
returned object doubles as a decorator (every decorated call
|
|
431
|
-
records one
|
|
431
|
+
records one span, provided the timer is running when called):
|
|
432
432
|
|
|
433
|
-
st =
|
|
433
|
+
st = SpanTimer("ms")
|
|
434
434
|
|
|
435
435
|
@st.measure("load")
|
|
436
436
|
def load() -> None: ...
|
|
437
437
|
|
|
438
438
|
with st:
|
|
439
|
-
load() # records a "load"
|
|
439
|
+
load() # records a "load" span
|
|
440
440
|
with st.measure("parse"):
|
|
441
|
-
parse() # records a "parse"
|
|
441
|
+
parse() # records a "parse" span
|
|
442
442
|
|
|
443
443
|
Args:
|
|
444
|
-
label: The
|
|
444
|
+
label: The span's name. Defaults to "Block".
|
|
445
445
|
|
|
446
446
|
Yields:
|
|
447
|
-
None. The wrapped block runs while the
|
|
447
|
+
None. The wrapped block runs while the span is timed.
|
|
448
448
|
|
|
449
449
|
Raises:
|
|
450
450
|
RuntimeError: If the timer has not been started, or is paused on
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kaparoo-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Personally common and useful Python features
|
|
5
5
|
Keywords: filesystem,pathlib,paths,utilities
|
|
6
6
|
Author: Jaewoo Park
|
|
@@ -65,7 +65,7 @@ hook for custom filter kinds.
|
|
|
65
65
|
|
|
66
66
|
### [`kaparoo.utils`](https://github.com/kaparoo/kaparoo-python/tree/main/kaparoo/utils)
|
|
67
67
|
|
|
68
|
-
`Timer` / `
|
|
68
|
+
`Timer` / `SpanTimer` context-manager-and-decorator timers (with
|
|
69
69
|
`lap`-split and `measure`-block timings); `Aggregator` for nested,
|
|
70
70
|
pluggable metric aggregation (the batch → epoch → run pattern;
|
|
71
71
|
experimental); plus a small family of helpers for working with
|
|
@@ -5,7 +5,7 @@ kaparoo/data/sequences/base.py,sha256=m2JcIcT-SLrTzsjFCtgrQ9I5XVpB6PYBigyooEpg4V
|
|
|
5
5
|
kaparoo/data/sequences/composers.py,sha256=eaWW8VKyVq4zzJAvIh_LKYt6g-R-HqzzpoV4NKtFeqc,13373
|
|
6
6
|
kaparoo/data/sequences/templates.py,sha256=9a_vM-c9OaF-9qY0ybqRaGkVkywJzrQbNlEHfe39MHU,9572
|
|
7
7
|
kaparoo/data/sequences/utils.py,sha256=oe0qWwnAjsf-9CBUPSlkxkeuQS8kjn1sYhx2eDIwPKI,2808
|
|
8
|
-
kaparoo/filesystem/__init__.py,sha256=
|
|
8
|
+
kaparoo/filesystem/__init__.py,sha256=p1keuLIE0zJCh7lh6KXW9EyeEFMpnMrBzSOVjT3ic-M,1853
|
|
9
9
|
kaparoo/filesystem/directory.py,sha256=Pr15aMl0tz2-VU14rkaFzsV0Zo2oqaevDM0JLW-ZOwk,10421
|
|
10
10
|
kaparoo/filesystem/exceptions.py,sha256=dWzD7d30bUEAxKWMYtJWmIg8tK3meEFz84NhWmWXb6k,464
|
|
11
11
|
kaparoo/filesystem/existence.py,sha256=6AafbDmY_sXrePLs9Vg8gQgT7jS8ETAqxz7TysV8WJ4,12564
|
|
@@ -22,13 +22,13 @@ kaparoo/filesystem/search/filters/utils.py,sha256=j-AfVu298t5GuQDb1UCOtb0L7R2Hm9
|
|
|
22
22
|
kaparoo/filesystem/search/wrappers.py,sha256=CDLvw9IhVNZLtMSdGnldJLum9tt8UTSQoRphOxg0qD8,10782
|
|
23
23
|
kaparoo/filesystem/staged.py,sha256=VXjzszo-FAjQaO9aSPelorIVOyxIUS5SPDank5wQupM,18275
|
|
24
24
|
kaparoo/filesystem/types.py,sha256=Cm1MLEAujVRyB9uciJ-uUxC0hw6j-ycxVPKS9KFmJnw,202
|
|
25
|
-
kaparoo/filesystem/utils.py,sha256=
|
|
25
|
+
kaparoo/filesystem/utils.py,sha256=x9R42WLCyHdJHC1dwzSd7SxYh3HnPl2ZotfxjUnwdlE,12882
|
|
26
26
|
kaparoo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
-
kaparoo/utils/__init__.py,sha256=
|
|
27
|
+
kaparoo/utils/__init__.py,sha256=lkLwehMw3oOjMq8SPq-DQ6XCrBvsVfXJuLYchjJDwzA,773
|
|
28
28
|
kaparoo/utils/aggregate.py,sha256=8apzZiqLAxoSO51DDDMsOnkrsEaejudXDTc6h3uKRZc,13953
|
|
29
29
|
kaparoo/utils/optional.py,sha256=UgNhGDzl317PE_ESt9hW7yl9MYcdL0nV6Ly0mpqIz0U,4224
|
|
30
|
-
kaparoo/utils/timer.py,sha256=
|
|
31
|
-
kaparoo_python-0.
|
|
32
|
-
kaparoo_python-0.
|
|
33
|
-
kaparoo_python-0.
|
|
34
|
-
kaparoo_python-0.
|
|
30
|
+
kaparoo/utils/timer.py,sha256=IVkQxbSJqexEAPPiGW234M_e4-lW5lrXSBAnXIbQCVo,16906
|
|
31
|
+
kaparoo_python-0.7.0.dist-info/licenses/LICENSE,sha256=hb6LWYP2rtcoz4V2HpawmblDfHwjwsg9N3cz0c5JQJE,1067
|
|
32
|
+
kaparoo_python-0.7.0.dist-info/WHEEL,sha256=wXwAVsgVaOZ_pwDFqQm5Rd6PID-Fc74nkLc8X8gHiDo,81
|
|
33
|
+
kaparoo_python-0.7.0.dist-info/METADATA,sha256=k5XknG0Oi_MGDKSDrbpxJCohfiWit-NPm1ql2Wvtd5c,4324
|
|
34
|
+
kaparoo_python-0.7.0.dist-info/RECORD,,
|
|
File without changes
|