Coverage for dataexcept/__main__.py: 95%
40 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-09-03 20:46 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-09-03 20:46 +0000
1"""Command line interface for the DataExcept package."""
3from __future__ import annotations
5import argparse
6import pkgutil
7import sys
8from importlib import import_module
9from types import ModuleType
10from typing import Iterable
12from . import __path__ as _PKG_PATH
13from . import __version__
16def _iter_exception_modules() -> Iterable[ModuleType]:
17 """Yield every submodule that explicitly defines ``__all__``."""
18 allowed_suffixes = ("exceptions", "_exceptions")
19 # dataexcept.base holds DataExceptError, the root of the hierarchy, and
20 # does not match the suffix rule.
21 always_include = {"dataexcept.base"}
23 for module_info in pkgutil.walk_packages(
24 _PKG_PATH, prefix="dataexcept.", onerror=lambda name: None
25 ):
26 if (
27 not module_info.name.endswith(allowed_suffixes)
28 and module_info.name not in always_include
29 ):
30 continue
31 try:
32 module = import_module(module_info.name)
33 except ImportError as exc: # pragma: no cover - defensive guard
34 print(
35 f"dataexcept: failed to import {module_info.name}: {exc}",
36 file=sys.stderr,
37 )
38 continue
39 if getattr(module, "__all__", None): 39 ↛ 23line 39 didn't jump to line 23 because the condition on line 39 was always true
40 yield module
43def _iter_exception_names() -> Iterable[str]:
44 seen: set[str] = set()
45 for module in _iter_exception_modules():
46 names = getattr(module, "__all__", None)
47 if not names: 47 ↛ 48line 47 didn't jump to line 48 because the condition on line 47 was never true
48 continue
49 for name in names:
50 if name.endswith("Error") and name not in seen:
51 seen.add(name)
52 yield name
55def _list_exceptions() -> None:
56 for name in sorted(_iter_exception_names()):
57 print(name)
60def main(argv: list[str] | None = None) -> None:
61 """Entry point for the ``dataexcept`` command."""
62 parser = argparse.ArgumentParser(
63 # Without this, `python -m dataexcept --version` reports "__main__.py".
64 prog="dataexcept",
65 description="Utilities for DataExcept",
66 )
67 parser.add_argument(
68 "--version",
69 action="version",
70 version=f"%(prog)s {__version__}",
71 )
72 subparsers = parser.add_subparsers(dest="command")
73 subparsers.add_parser("list", help="List available exception classes")
75 args = parser.parse_args(argv)
77 if args.command == "list":
78 _list_exceptions()
79 else: # pragma: no cover - help message
80 parser.print_help()
83if __name__ == "__main__": # pragma: no cover - manual invocation
84 main()