Source code for scitex_scholar

#!/usr/bin/env python3
"""SciTeX Scholar -- scientific paper search, enrichment, and management.

Quick Start:
    from scitex_scholar import Scholar, Paper, Papers

    scholar = Scholar()
    papers = scholar.search("deep learning")
    papers.save("results.bib")

Installation:
    pip install scitex-scholar

This module uses PEP 562 lazy `__getattr__` so `import scitex_scholar`
stays under 500ms cold-start. Submodules are imported on first attribute
access only.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

try:
    from importlib.metadata import PackageNotFoundError
    from importlib.metadata import version as _v

    try:
        __version__ = _v("scitex-scholar")
    except PackageNotFoundError:
        __version__ = "0.0.0+local"
    del _v, PackageNotFoundError
except ImportError:  # pragma: no cover — only on ancient Pythons
    __version__ = "0.0.0+local"
__author__ = "Yusuke Watanabe"


# Public API: declared for tab-completion and `list-python-apis`.
__all__ = [
    "__version__",
    "Scholar",
    "Paper",
    "Papers",
    "ScholarConfig",
    "ScholarAuthManager",
    "ScholarBrowserManager",
    "ScholarURLFinder",
    "CitationGraphBuilder",
    "plot_citation_graph",
    "to_bibtex",
    "to_ris",
    "to_endnote",
    "to_text_citation",
    "papers_to_format",
    "generate_cite_key",
    "make_citation_key",
    "from_connected_papers",
    "to_connected_papers",
    "apply_filters",
    "clean_abstract",
    "SCHOLAR_AVAILABLE",
]


# Always-True flag: downstream shims (e.g. `scitex.scholar` re-export) check
# this without forcing every submodule to import. Lazy attribute access
# below validates each name on demand.
SCHOLAR_AVAILABLE = True


# Map of public name -> (submodule, attribute). Module is imported only
# when the attribute is requested.
_LAZY: dict[str, tuple[str, str]] = {
    "Scholar": (".core.Scholar", "Scholar"),
    "Paper": (".core.Paper", "Paper"),
    "Papers": (".core.Papers", "Papers"),
    "ScholarConfig": (".config", "ScholarConfig"),
    "ScholarAuthManager": (".auth", "ScholarAuthManager"),
    "ScholarBrowserManager": (".browser", "ScholarBrowserManager"),
    "ScholarURLFinder": (".url_finder", "ScholarURLFinder"),
    "CitationGraphBuilder": (".citation_graph", "CitationGraphBuilder"),
    "plot_citation_graph": (".citation_graph", "plot_citation_graph"),
    "to_bibtex": (".formatting", "to_bibtex"),
    "to_ris": (".formatting", "to_ris"),
    "to_endnote": (".formatting", "to_endnote"),
    "to_text_citation": (".formatting", "to_text_citation"),
    "papers_to_format": (".formatting", "papers_to_format"),
    "generate_cite_key": (".formatting", "generate_cite_key"),
    "make_citation_key": (".formatting", "make_citation_key"),
    "from_connected_papers": (".migration", "from_connected_papers"),
    "to_connected_papers": (".migration", "to_connected_papers"),
    "apply_filters": (".filters", "apply_filters"),
}


def __getattr__(name: str) -> Any:
    if name in _LAZY:
        from importlib import import_module

        mod_name, attr = _LAZY[name]
        mod = import_module(mod_name, __name__)
        value = getattr(mod, attr)
        globals()[name] = value
        return value

    if name == "clean_abstract":
        from ._utils.text._TextNormalizer import TextNormalizer as _TN

        def clean_abstract(text: str) -> str:
            """Strip HTML/JATS XML tags from a CrossRef-style abstract."""
            return _TN.strip_html_tags(text)

        globals()["clean_abstract"] = clean_abstract
        return clean_abstract

    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


def __dir__() -> list[str]:
    return sorted(set(list(globals().keys()) + __all__))


if TYPE_CHECKING:
    # Re-export types for static checkers without paying import cost at runtime.
    from .auth import ScholarAuthManager  # noqa: F401
    from .browser import ScholarBrowserManager  # noqa: F401
    from .citation_graph import (  # noqa: F401
        CitationGraphBuilder,
        plot_citation_graph,
    )
    from .config import ScholarConfig  # noqa: F401
    from .core.Paper import Paper  # noqa: F401
    from .core.Papers import Papers  # noqa: F401
    from .core.Scholar import Scholar  # noqa: F401
    from .filters import apply_filters  # noqa: F401
    from .formatting import (  # noqa: F401
        generate_cite_key,
        make_citation_key,
        papers_to_format,
        to_bibtex,
        to_endnote,
        to_ris,
        to_text_citation,
    )
    from .migration import from_connected_papers, to_connected_papers  # noqa: F401
    from .url_finder import ScholarURLFinder  # noqa: F401


# EOF