Source code for bdf

from __future__ import annotations

import os
import warnings
from pathlib import Path
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from ._errors import BDFValidationError
    from ._explore import explore
    from ._ingest import ingest
    from ._registry import get_entry, load_registry
    from ._templates import templates
    from ._validate import validate, validate_df
    from .io import read, save, scan
    from .plugins import detect
    from .registry_ld import build_registry, search, sparql
    from .repair import CleanReport, clean
    from .table_normalizers import normalize
    from .visualize import plot

__all__ = [
    # core I/O
    "read",
    "scan",
    "save",
    "normalize",
    "validate",
    "validate_df",
    "detect",
    # datasets helpers
    "datasets",
    "load_registry",
    "get_entry",
    # registry LD helpers
    "build_registry",
    "search",
    "sparql",
    # cleaning
    "clean",
    "CleanReport",
    # viz
    "plot",
    "explore",
    "ingest",
    "templates",
    # version
    "__version__",
    # errors
    "BDFValidationError",
]


# name: module
_LAZY_ATTRS: dict[str, str] = {
    "read": ".io",
    "scan": ".io",
    "save": ".io",
    "normalize": ".table_normalizers",
    "validate": "._validate",
    "validate_df": "._validate",
    "BDFValidationError": "._errors",
    "detect": ".plugins",
    "load_registry": "._registry",
    "get_entry": "._registry",
    "build_registry": ".registry_ld",
    "search": ".registry_ld",
    "sparql": ".registry_ld",
    "clean": ".repair",
    "CleanReport": ".repair",
    "plot": ".visualize",
    "explore": "._explore",
    "ingest": "._ingest",
    "templates": "._templates",
}


def __getattr__(name: str) -> Any:  # noqa: ANN401
    """Lazy imports so `import bdf` doesn't pull in heavy imports."""
    module = _LAZY_ATTRS.get(name)
    if module is None:
        msg = f"module {__name__!r} has no attribute {name!r}"
        raise AttributeError(msg)
    from importlib import import_module

    value = getattr(import_module(module, __name__), name)
    # Cache so later lookups skip __getattr__
    globals()[name] = value
    return value


def __dir__() -> list[str]:
    return sorted(__all__)


# Optional version
try:
    from importlib.metadata import version as _pkg_version  # type: ignore

    try:
        __version__ = _pkg_version("batterydf")
    except Exception:
        __version__ = _pkg_version("bdf")
except Exception:
    __version__ = "0.0.0-dev"


# Warning format settings

# Keep a handle to the original in case you want to restore it later
_default_formatwarning = warnings.formatwarning


def _bdf_short_formatwarning(message, category, filename, lineno, line=None):
    """
    Render warnings without absolute paths. If the warning originates inside
    the bdf package, just show 'bdf.<module>:<lineno>'; otherwise show a short
    filename. Message text remains unchanged.
    """
    try:
        p = Path(filename).resolve()
        # Heuristic: if file path contains '/bdf/' (or '\bdf\') treat it as our package
        fp = str(p).replace("\\", "/")
        if "/bdf/" in fp or fp.endswith("/bdf/__init__.py"):
            # Build a dotted module-ish label
            try:
                # relative to the package root
                pkg_root = Path(__file__).resolve().parent
                rel = p.relative_to(pkg_root)
                mod = "bdf." + ".".join(rel.with_suffix("").parts)
            except Exception:
                mod = "bdf"
            where = f"{mod}:{lineno}"
        else:
            # External warnings: keep only the basename to avoid leaking user paths
            where = f"{p.name}:{lineno}"
    except Exception:
        where = "<unknown>"

    return f"{category.__name__} [{where}]: {message}\n"


def _enable_short_warnings() -> bool:
    val = os.getenv("BDF_FORMAT_WARNINGS", "").strip().lower()
    return val in {"1", "true", "yes", "on"}


# Install the formatter (opt-in via env var).
if _enable_short_warnings():
    warnings.formatwarning = _bdf_short_formatwarning


# ----- dataset helpers (lazy to avoid cycles) -----
[docs] def datasets(registry_path: str | Path | None = None) -> list[str]: """Return dataset IDs from the registry.""" from ._registry import list_datasets as _list_datasets # lazy return _list_datasets(registry_path)