Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.6.1]

Compatibility release for code written against 0.5.x. Packages that pinned `suthing>=0.5` without an upper bound broke on a fresh install of 0.6.0; with 0.6.1 they work unchanged. The shims below will be removed in 0.7, so pin `suthing>=0.6.1,<0.7` and move off them.

### Changed

- `strenum` is a dependency again. suthing does not use it, but code that imported `strenum` while only receiving it through suthing 0.5 failed with `ModuleNotFoundError` on 0.6.0.

### Deprecated

- `FileHandle.load(fpath=...)` is accepted again and emits `DeprecationWarning`; pass the path positionally.
- `Timer.mins` and `Timer.secs` are back and emit `DeprecationWarning`; use `Timer.elapsed` or `Timer.format()`.

## [0.6.0] - 2026-09-22

### Added

Expand Down Expand Up @@ -35,8 +48,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Removed

- **Breaking:** `Timer.mins` and `Timer.secs` (restored as deprecated in 0.6.1).
- **Breaking:** `secureit`, `timeit`, `Report`, `Return`, `SProfiler`, `profile` and the `suthing.decorate` module. Use `Profiler` / `profiled` for profiling and `Timer` for timing.
- Unused runtime dependencies `dataclass-wizard` and `strenum`. `mkdocs-gen-files` moves to the `docs` dependency group.
- Unused runtime dependencies `dataclass-wizard` and `strenum` (`strenum` restored in 0.6.1). `mkdocs-gen-files` moves to the `docs` dependency group.

## [0.5.1] - 2026-02-01

Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ classifiers = [
dependencies = [
"pandas>=2.3.3",
"python-dotenv>=1.0.0,<2",
"pyyaml>=6.0"
"pyyaml>=6.0",
"strenum>=0.4.15"
]
description = "Small Useful Things : python package for convenient primitives"
keywords = [
Expand All @@ -38,7 +39,7 @@ license = "MIT"
name = "suthing"
readme = "README.md"
requires-python = ">=3.11"
version = "0.6.0"
version = "0.6.1"

[[project.authors]]
email = "alexander@growgraph.dev"
Expand Down
24 changes: 22 additions & 2 deletions suthing/file_handle.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import json
import pathlib
import pickle
import warnings
from collections.abc import Iterator, Mapping
from enum import Enum
from importlib import resources
Expand Down Expand Up @@ -205,13 +206,20 @@ class FileHandle:

@classmethod
def load(
cls, path: PathLike, *, how: FileType | str | None = None, **kwargs: Any
cls,
path: PathLike | None = None,
*,
how: FileType | str | None = None,
fpath: PathLike | None = None,
**kwargs: Any,
) -> Any:
"""Read a file from disk.

Args:
path: File to read; ``~`` is expanded.
how: Format override; by default inferred from the extension.
fpath: Deprecated spelling of *path*, kept so code written for
suthing 0.5 keeps working; emits ``DeprecationWarning``.
**kwargs: Passed to ``pandas.read_csv`` for CSV/TSV. Any other
format rejects extra arguments.

Expand All @@ -220,8 +228,20 @@ def load(

Raises:
ValueError: If the format cannot be inferred.
TypeError: For keyword arguments the format does not take.
TypeError: For keyword arguments the format does not take, or
when neither or both of *path* and *fpath* are given.
"""
if fpath is not None:
warnings.warn(
"FileHandle.load(fpath=...) is deprecated; pass the path positionally",
DeprecationWarning,
stacklevel=2,
)
if path is not None:
raise TypeError("pass the path once: positionally or as fpath=")
path = fpath
if path is None:
raise TypeError("FileHandle.load() missing the file path")
fmt, _ = _resolve(path, how)
with open_compressed(path, "rb") as stream:
return _read(stream, fmt, **kwargs)
Expand Down
21 changes: 21 additions & 0 deletions suthing/timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import logging
import warnings
from collections.abc import Callable
from contextlib import ContextDecorator
from datetime import UTC, datetime
Expand Down Expand Up @@ -103,6 +104,26 @@ def elapsed_ms(self) -> int:
""":attr:`elapsed` in whole milliseconds."""
return int(self.elapsed * 1000)

@property
def mins(self) -> int:
"""Whole minutes of :attr:`elapsed`. Deprecated: use :attr:`elapsed`."""
warnings.warn(
"Timer.mins is deprecated; use Timer.elapsed or Timer.format()",
DeprecationWarning,
stacklevel=2,
)
return int(self.elapsed // SECONDS_PER_MINUTE)

@property
def secs(self) -> int:
"""Whole seconds past :attr:`mins`. Deprecated: use :attr:`elapsed`."""
warnings.warn(
"Timer.secs is deprecated; use Timer.elapsed or Timer.format()",
DeprecationWarning,
stacklevel=2,
)
return int(self.elapsed % SECONDS_PER_MINUTE)

@property
def running(self) -> bool:
"""Whether the timer has started and not yet exited."""
Expand Down
12 changes: 11 additions & 1 deletion test/test_file_handle.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,18 @@ def test_type_mismatch_raises_and_writes_nothing(tmp_path, item, name):

def test_unexpected_kwargs_rejected(tmp_path):
path = FileHandle.dump({"a": 1}, tmp_path / "x.yaml")
with pytest.raises(TypeError, match="fpath"):
with pytest.raises(TypeError, match="sep"):
FileHandle.load(path, sep=",")


def test_deprecated_fpath_still_loads(tmp_path):
path = FileHandle.dump({"a": 1}, tmp_path / "x.yaml")
with pytest.warns(DeprecationWarning, match="fpath"):
assert FileHandle.load(fpath=path) == {"a": 1}
with pytest.raises(TypeError, match="once"), pytest.warns(DeprecationWarning):
FileHandle.load(path, fpath=path)
with pytest.raises(TypeError, match="missing"):
FileHandle.load()


def test_jsonld_is_json_ld(tmp_path):
Expand Down
11 changes: 11 additions & 0 deletions test/test_timer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import time

import pytest

from suthing import Timer, format_duration, utc_now_iso


Expand Down Expand Up @@ -45,3 +47,12 @@ def test_utc_now_iso():
stamp = utc_now_iso()
assert stamp.endswith("+00:00") and "." not in stamp
assert "." in utc_now_iso("milliseconds")


def test_deprecated_mins_secs(monkeypatch):
clock = iter([10.0, 137.9])
monkeypatch.setattr("suthing.timer.perf_counter", lambda: next(clock))
with Timer() as t:
pass
with pytest.warns(DeprecationWarning):
assert (t.mins, t.secs) == (2, 7)
Loading
Loading