expdpy.pedagogy._registry

Full source of expdpy.pedagogy._registrysrc/expdpy/pedagogy/_registry.py on GitHub. Each top-level definition is anchored; reach it from the source link on a reference page.

"""The concept-explainer registry: ``Explainer`` plus ``explain`` / ``list_topics``.

An *explainer* is data-independent teaching content about a method or concept ("what is a
fixed effect / when do I use it / what are the pitfalls"). It is structured (not a bare
string) so apps can render each section separately, while notebooks get a rich
``_repr_markdown_``.
"""

from __future__ import annotations

from dataclasses import dataclass, field

__all__ = ["Explainer", "explain", "list_topics", "register_topic"]

Explainer

@dataclass(frozen=True)
class Explainer:
    """Structured teaching content for one method or concept.

    Parameters
    ----------
    topic
        Canonical key (e.g. ``"fixed_effects"``).
    title
        Human-readable title (e.g. ``"Fixed effects"``).
    what
        One-paragraph "what it is".
    when_to_use
        One-paragraph "when to use it".
    caveats
        Bullet-point pitfalls / things to watch for.
    see_also
        Related topic keys.
    references
        Short citations (e.g. textbook chapters).
    """

    topic: str
    title: str
    what: str
    when_to_use: str
    caveats: tuple[str, ...] = ()
    see_also: tuple[str, ...] = ()
    references: tuple[str, ...] = field(default=())

    def to_markdown(self) -> str:
        """Render the explainer as a Markdown string (for apps, notebooks and docs)."""
        parts = [f"### {self.title}", "", f"**What it is.** {self.what}", ""]
        parts += [f"**When to use it.** {self.when_to_use}"]
        if self.caveats:
            parts += ["", "**Watch out for.**"]
            parts += [f"- {c}" for c in self.caveats]
        if self.see_also:
            parts += ["", f"*See also:* {', '.join(self.see_also)}"]
        if self.references:
            parts += ["", f"*References:* {'; '.join(self.references)}"]
        return "\n".join(parts)

    def _repr_markdown_(self) -> str:  # Jupyter rich display
        return self.to_markdown()

    def __str__(self) -> str:
        return self.to_markdown()


_TOPICS: dict[str, Explainer] = {}
_ALIASES: dict[str, str] = {}

register_topic

def register_topic(explainer: Explainer, *, aliases: tuple[str, ...] = ()) -> None:
    """Register ``explainer`` under its ``topic`` key (and any ``aliases``).

    Parameters
    ----------
    explainer
        The explainer to register.
    aliases
        Alternative keys that resolve to this topic (e.g. ``"fe"`` -> ``"fixed_effects"``).
    """
    _TOPICS[explainer.topic] = explainer
    for alias in aliases:
        _ALIASES[alias] = explainer.topic

explain

↩︎ docs

def explain(topic: str, *, lang: str = "en") -> Explainer:
    """Return the :class:`Explainer` for a method or concept.

    Parameters
    ----------
    topic
        A topic key or alias (see :func:`list_topics`).
    lang
        Language code. Only ``"en"`` ships today; the parameter is reserved so that adding
        translations later is non-breaking.

    Returns
    -------
    Explainer
        The matching explainer.

    Raises
    ------
    KeyError
        If ``topic`` is unknown; the message lists the available topics.

    Examples
    --------
    ```python
    import expdpy as ex

    ex.explain("fixed_effects").title
    ```
    """
    key = _ALIASES.get(topic, topic)
    if key not in _TOPICS:
        available = ", ".join(list_topics())
        raise KeyError(f"unknown topic {topic!r}; available topics: {available}")
    return _TOPICS[key]

list_topics

↩︎ docs

def list_topics() -> list[str]:
    """Return the sorted list of canonical topic keys (for app menus and docs)."""
    return sorted(_TOPICS)