Source code for vibeqc.banner

"""Runtime banner and linked-library version reporter.

Printed at the top of any serious vibe-qc text output so a user can always
identify exactly which build of the program produced a given log.

Programmatic API
----------------

``banner()``
    Return the full multi-line banner as a string.

``print_banner()``
    Convenience: ``print(banner(), **kwargs)``.

``library_versions()``
    Dict of the versions of linked native dependencies (libint, libxc,
    spglib) plus the package version. Useful in tests and bug reports.

The ``format_scf_trace()`` helper in :mod:`vibeqc.scf_log` prepends the
banner automatically so a persisted SCF run log carries its provenance.
"""

from __future__ import annotations

import sys
from typing import Mapping, TextIO

# Package version — reads the installed metadata so it tracks pyproject.toml
# automatically at build/install time. Falls back to an explicit label if the
# package isn't found (e.g. running from a source tree without installation).
try:
    from importlib.metadata import PackageNotFoundError, version as _pkg_version
except ImportError:  # pragma: no cover — Python 3.11+ always ships this
    _pkg_version = None
    PackageNotFoundError = Exception  # type: ignore[misc, assignment]


__all__ = [
    "VIBEQC_VERSION",
    "banner",
    "print_banner",
    "library_versions",
]


def _compute_version() -> str:
    """Read the installed distribution metadata. The distribution is
    named ``vibe-qc`` on PyPI since the rename; older local installs
    may still be listed as the legacy ``vibeqc``, so we try both."""
    if _pkg_version is None:
        return "0.0.0+unknown"
    for dist_name in ("vibe-qc", "vibeqc"):
        try:
            return _pkg_version(dist_name)
        except PackageNotFoundError:
            continue
    return "0.0.0+unknown"


VIBEQC_VERSION: str = _compute_version()


[docs] def library_versions() -> Mapping[str, str]: """Return the version strings of vibe-qc itself and its native dependencies (libint, libxc, spglib). Keys are always present; values are ``"unknown"`` if a probe fails. The ``"vibe-qc"`` key carries the project version. The legacy ``"vibeqc"`` key is kept as an alias so code that predates the rename keeps working without change. """ versions: dict[str, str] = { "vibe-qc": VIBEQC_VERSION, "vibeqc": VIBEQC_VERSION, # alias, retained for back-compat } # Probe the compiled extension. Each of the three libraries exposes a # version accessor; every version is known at link time. try: from . import _vibeqc_core as _core except ImportError: versions.update({"libint": "unknown", "libxc": "unknown", "spglib": "unknown"}) return versions try: versions["libint"] = _core.libint_version() except Exception: versions["libint"] = "unknown" try: versions["libxc"] = getattr(_core, "libxc_version", lambda: "unknown")() except Exception: versions["libxc"] = "unknown" try: versions["spglib"] = _core.spglib_version() except Exception: versions["spglib"] = "unknown" return versions
_COPYRIGHT_LINE = "© Michael F. Peintinger · MPL 2.0"