Quick Start#

This is the fastest path to a real SMonitor integration.

Goal#

In this page you will:

  • configure SMonitor for your library,

  • emit a catalog-based warning,

  • decorate one public function for traceability.

At the end, you should see clear user-facing diagnostics and structured context.

1. Install#

conda install -c uibcdf smonitor

or from source:

python -m pip install --no-deps --editable .

2. Create _smonitor.py in your package root#

If your package is mylib, create mylib/_smonitor.py:

from mylib._private.smonitor.catalog import CODES, SIGNALS

PROFILE = "user"

SMONITOR = {
    "level": "WARNING",
    "trace_depth": 3,
    "capture_warnings": True,
    "capture_logging": True,
    "theme": "plain",
}

# Keep runtime templates/contracts in one source of truth.
CODES = CODES
SIGNALS = SIGNALS

3. Create the catalog#

Create mylib/_private/smonitor/catalog.py:

CATALOG = {
    "codes": {
        "MYLIB-W001": {
            "title": "Selection is broad",
            "user_message": "Selection '{selection}' may be too broad.",
            "user_hint": "Use a more specific selector, for example '{example}'.",
            "dev_message": "Broad selection received in parser path.",
            "dev_hint": "Review selector normalization for this call.",
        }
    },
    "warnings": {
        "BroadSelectionWarning": {
            "code": "MYLIB-W001",
            "source": "mylib.select_atoms",
            "category": "selection",
            "level": "WARNING",
        }
    },
    "signals": {},
}

CODES = CATALOG["codes"]
SIGNALS = CATALOG["signals"]

codes holds the wording, one entry per profile. warnings maps a warning class to the code it carries, plus the source and category every event of that kind should report. DiagnosticBundle looks a warning up here by its class name, so a catalog without a warnings group emits nothing through warn().

Create mylib/_private/smonitor/__init__.py:

from pathlib import Path

from .catalog import CATALOG, CODES, SIGNALS

PACKAGE_ROOT = Path(__file__).resolve().parents[2]

Create mylib/_private/smonitor/emitter.py:

from smonitor.integrations import DiagnosticBundle

from . import CATALOG, PACKAGE_ROOT

bundle = DiagnosticBundle(CATALOG, meta={"library": "mylib"}, package_root=PACKAGE_ROOT)
warn = bundle.warn
warn_once = bundle.warn_once

Create mylib/_private/smonitor/warnings.py, one class per catalog entry:

from smonitor.integrations import CatalogWarning

from . import CATALOG


class BroadSelectionWarning(CatalogWarning):
    catalog_key = "BroadSelectionWarning"

    def __init__(self, message=None, *, selection=None, example=None):
        super().__init__(
            message,
            catalog=CATALOG,
            extra={"selection": selection, "example": example},
        )

Two rules, and both matter:

The class takes the data, never the finished sentence. The template in codes owns the wording, and selection reaches report() and the event fingerprints as a typed field.

message comes first and the fields are keyword-only. Python rebuilds a warning as type(w)(*w.args)pickle, copy.deepcopy, warnings.warn(text, category) and pytest-xdist between a worker and the controller all do it — so a class naming a field first receives its own rendered sentence as that field and renders around it. Keyword-only keeps a misspelled field an error instead of one that is silently ignored.

warn() raises the warning through Python’s machinery as well as emitting the event, so pytest.warns and your users’ filterwarnings keep working on it.

4. Enable SMonitor on import#

In mylib/__init__.py:

from smonitor.integrations import ensure_configured
from ._private.smonitor import PACKAGE_ROOT

ensure_configured(PACKAGE_ROOT)

5. Emit via catalog and instrument one API function#

from smonitor import signal
from ._private.smonitor.emitter import warn
from ._private.smonitor.warnings import BroadSelectionWarning

@signal(tags=["selection"])
def select_atoms(selection: str):
    if selection == "all":
        warn(BroadSelectionWarning(selection=selection, example="atom_name == 'CA'"))

Expected result#

When selection == "all", users get a warning that:

  • explains what happened,

  • suggests a concrete fix,

  • stays consistent with your library profile.

Quick smoke command#

python -c "import mylib; from mylib.api import select_atoms; select_atoms('all')"

Expected:

  • import does not fail,

  • one warning event with MYLIB-W001,

  • message/hint resolved from catalog.

Common wiring mistakes#

  1. _smonitor.py in repo root instead of package root:

  • correct path is mylib/_smonitor.py.

  1. PACKAGE_ROOT pointing to wrong level:

  • use Path(__file__).resolve().parents[2] from mylib/_private/smonitor/__init__.py.

  1. Catalog not imported into _smonitor.py:

  • ensure CODES and SIGNALS are available at runtime.

  1. Circular imports during package init:

  • keep _private/smonitor modules lightweight and avoid importing heavy modules there.

You are done when#

  • importing mylib configures SMonitor without errors,

  • calling select_atoms("all") emits MYLIB-W001,

  • message and hint are resolved from catalog templates.

Next#

Continue with Mini Library Walkthrough for a complete example.