From 3e1324e2237569ce800d835344c342f8ad165f8b Mon Sep 17 00:00:00 2001 From: "Philipp A." Date: Tue, 8 Sep 2026 11:32:22 +0200 Subject: [PATCH] style: improve stack traces --- pyproject.toml | 1 + src/scverse_misc/_extensions.py | 29 +++++++++++++------------- src/scverse_misc/datasets/_fetcher.py | 9 +++++--- src/scverse_misc/datasets/_registry.py | 9 +++++--- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e79e199..8a07364 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,6 +103,7 @@ lint.select = [ "C4", # flake8-comprehensions "D", # pydocstyle "E", # Error detected by Pycodestyle + "EM", # Error messages "F", # Errors detected by Pyflakes "I", # isort "RUF100", # Report unused noqa directives diff --git a/src/scverse_misc/_extensions.py b/src/scverse_misc/_extensions.py index 53ab7bd..5816e63 100644 --- a/src/scverse_misc/_extensions.py +++ b/src/scverse_misc/_extensions.py @@ -91,14 +91,14 @@ def _check_namespace_signature(ns_class: type, cls: type, canonical_instance_nam # Ensure there are at least two parameters (self and mdata) if len(params) < 2: - raise TypeError(f"Namespace initializer must accept a {cls.__name__} instance as the second parameter.") + msg = f"Namespace initializer must accept a {cls.__name__} instance as the second parameter." + raise TypeError(msg) # Get the second parameter (expected to be `canonical_instance_name`) [_, param, *_] = params.values() if param.annotation is inspect.Parameter.empty: - raise AttributeError( - f"Namespace initializer's second parameter must be annotated as the {cls.__name__!r} class, got empty annotation." - ) + msg = f"Namespace initializer's second parameter must be annotated as the {cls.__name__!r} class, got empty annotation." + raise AttributeError(msg) name_ok = param.name == canonical_instance_name @@ -107,9 +107,8 @@ def _check_namespace_signature(ns_class: type, cls: type, canonical_instance_nam type_hints = get_type_hints(ns_class.__init__) # type: ignore[misc] # https://github.com/python/mypy/issues/21236 resolved_type = type_hints.get(param.name, param.annotation) except NameError as e: - raise NameError( - f"Namespace initializer's second parameter must be named {canonical_instance_name!r}, got '{param.name}'." - ) from e + msg = f"Namespace initializer's second parameter must be named {canonical_instance_name!r}, got {param.name!r}." + raise NameError(msg) from e type_ok = resolved_type is cls @@ -117,20 +116,19 @@ def _check_namespace_signature(ns_class: type, cls: type, canonical_instance_nam case (True, True): return # Signature is correct. case (False, True): - raise TypeError( - f"Namespace initializer's second parameter must be named {canonical_instance_name!r}, got {param.name!r}." - ) + msg = f"Namespace initializer's second parameter must be named {canonical_instance_name!r}, got {param.name!r}." + raise TypeError(msg) case (True, False): type_repr = getattr(resolved_type, "__name__", str(resolved_type)) - raise TypeError( - f"Namespace initializer's second parameter must be annotated as the {cls.__name__!r} class, got {type_repr!r}." - ) + msg = f"Namespace initializer's second parameter must be annotated as the {cls.__name__!r} class, got {type_repr!r}." + raise TypeError(msg) case _: type_repr = getattr(resolved_type, "__name__", str(resolved_type)) - raise TypeError( + msg = ( f"Namespace initializer's second parameter must be named {canonical_instance_name!r}, got {param.name!r}. " f"And must be annotated as {cls.__name__!r}, got {type_repr!r}." ) + raise TypeError(msg) def _create_namespace[NameSpT: ExtensionNamespace]( @@ -141,7 +139,8 @@ def _create_namespace[NameSpT: ExtensionNamespace]( def namespace(ns_class: type[NameSpT]) -> type[NameSpT]: _check_namespace_signature(ns_class, cls, canonical_instance_name) # Perform the runtime signature check if name in reserved_namespaces: - raise AttributeError(f"cannot override reserved attribute {name!r}") + msg = f"cannot override reserved attribute {name!r}" + raise AttributeError(msg) elif hasattr(cls, name): warnings.warn( f"Overriding existing custom namespace {name!r} (on {cls.__name__!r})", UserWarning, stacklevel=2 diff --git a/src/scverse_misc/datasets/_fetcher.py b/src/scverse_misc/datasets/_fetcher.py index b4c65c4..e9956e7 100644 --- a/src/scverse_misc/datasets/_fetcher.py +++ b/src/scverse_misc/datasets/_fetcher.py @@ -121,10 +121,12 @@ def download(file: FileEntry, /, dest: Path | None = None, processor: Processor return download(replace(file, url=fallback, fallback_urls=None), dest=dest, processor=processor) except (OSError, ValueError) as e: exceptions.append(e) - raise ExceptionGroup(f"Could not download {file.name}", exceptions) from None + msg = f"Could not download {file.name}" + raise ExceptionGroup(msg, exceptions) from None if entry.type not in _LOADERS: - raise KeyError(f"No loader registered for type {entry.type!r}. Available: {available_loaders()}") + msg = f"No loader registered for type {entry.type!r}. Available: {available_loaders()}" + raise KeyError(msg) return cast("Loader[T]", _LOADERS[entry.type])(entry, target, download, **kwargs) @@ -151,5 +153,6 @@ def _load_spatialdata(entry: DatasetEntry, target: Path, download: DownloadCB, / download(entry.file(suffix=".zip"), dest=dest, processor=pooch.Unzip(extract_dir=".")) zarrs = sorted(dest.glob("*.zarr")) if len(zarrs) != 1: - raise RuntimeError(f"Expected exactly one .zarr extracted under {dest}, found {len(zarrs)}: {zarrs}.") + msg = f"Expected exactly one .zarr extracted under {dest}, found {len(zarrs)}: {zarrs}." + raise RuntimeError(msg) return sd.read_zarr(zarrs[0], **cast("dict[str, Any]", kwargs)) diff --git a/src/scverse_misc/datasets/_registry.py b/src/scverse_misc/datasets/_registry.py index ef11f39..9e35164 100644 --- a/src/scverse_misc/datasets/_registry.py +++ b/src/scverse_misc/datasets/_registry.py @@ -40,7 +40,8 @@ def resolve_url(self, base_url: str | None = None) -> str: return self.url if base_url and self.s3_key: return f"{base_url.rstrip('/')}/{self.s3_key}" - raise ValueError(f"FileEntry {self.name!r} has neither `url` nor `s3_key` (with a registry `base_url`).") + msg = f"FileEntry {self.name!r} has neither `url` nor `s3_key` (with a registry `base_url`)." + raise ValueError(msg) @dataclass(frozen=True, slots=True) @@ -68,9 +69,11 @@ def file(self, *, name: str | None = None, suffix: str | None = None) -> FileEnt matches = [f for f in self.files if f.name.endswith(suffix)] crit = f"suffix={suffix!r}" else: - raise ValueError("Pass exactly one of `name` or `suffix`.") + msg = "Pass exactly one of `name` or `suffix`." + raise ValueError(msg) if len(matches) != 1: - raise ValueError(f"Expected exactly one file with {crit} in {self.name!r}, found {len(matches)}.") + msg = f"Expected exactly one file with {crit} in {self.name!r}, found {len(matches)}." + raise ValueError(msg) return matches[0]