# Recipe — One-off Python scripts with `mgf-common`

> **Audience:** consumers writing a one-off script (an admin tool, a
> data-fix, an interactive REPL session) that wants structured logs +
> typed errors WITHOUT the ceremony of a full CLI app.

## The shortest possible bootstrap

```python
from mgf.common import bootstrap

with bootstrap():
    # Everything is wired:
    #   - identity auto-derived from your package metadata
    #   - structured JSON logging at WARNING level
    #   - OTel if OTEL_EXPORTER_OTLP_ENDPOINT is set, else off
    #   - crash reporter installed
    #   - LIFO rollback if anything fails
    do_work()
```

Notice: no args. The library auto-derives `app_name` and `app_version`
from `sys.modules["__main__"].__spec__.parent` and the launching
package's distribution metadata. Works for `python -m my_pkg.script`
+ console-script entry points.

## When auto-derivation isn't enough

If you run an ad-hoc `python my_script.py` (no package), the
auto-derivation falls through to the placeholder identity and emits
`UnconfiguredWarning`. Pass identity explicitly:

```python
with bootstrap(app_name="my-fix-script", app_version="0.0.1"):
    do_work()
```

## For Jupyter notebooks

Notebooks have no `__main__` package, so auto-derivation falls
through. Use a session fixture in your first cell:

```python
# Cell 1 — once per kernel
import mgf.common

ctx = mgf.common.bootstrap(app_name="exploration", app_version="0.0.1")
```

```python
# Subsequent cells use the live ctx
import logging
logging.warning("hello from cell N")
```

Don't use `with bootstrap()` in notebooks — the context manager
shuts down at end-of-`with`, which doesn't match notebook lifecycle.
The `ctx = bootstrap(...)` form keeps wiring alive until you call
`ctx.shutdown()` explicitly (or restart the kernel).

## When NOT to use bootstrap

- **Library code** — bootstrap is for the application's `__main__`
  entry point. A library imported by a consumer should NOT call
  bootstrap (it claims process-wide state). Consumers wire it.
- **Pytest tests** — use the `mgf_context` fixture from
  `mgf.common.pytest_plugin` (auto-registered). See
  [`testing.md`](testing.md).
- **Subprocess workers spawned via `multiprocessing`** — the
  fork-style children inherit their parent's bootstrap state.
  spawn-style children need their own bootstrap. Document the
  policy when shipping such code.

## Pattern: structured logging with typed errors

```python
import logging

from mgf.common import bootstrap
from mgf.common.exceptions import AppConfigError, OperationError

LOG = logging.getLogger("my-script")

with bootstrap(app_name="my-script"):
    if not config_path.exists():
        raise AppConfigError(f"config file not found: {config_path}")

    LOG.info("processing %d records", len(records))

    for record in records:
        try:
            process(record)
        except SomeProviderError as exc:
            # Translate at the seam.
            raise OperationError(f"failed on {record.id}") from exc
```

When an unhandled exception escapes the `with`-block, the crash
reporter writes a full report to `<state>/my-script/crashes/`,
the excepthook logs it, and the process exits.

## Pattern: production secrets via vault

```python
from mgf.common import bootstrap
from mgf.common.vault import EnvVault, KeyringVault

vault = (
    KeyringVault(service="my-script")
    if os.environ.get("USE_KEYRING")
    else EnvVault()
)

with bootstrap(app_name="my-script"):
    api_token = vault.get("API_TOKEN")  # raises VaultError if absent
    do_work_with(api_token)
```

`VaultError` routes to `EXIT_VAULT_ERROR` if you wrap the script
body in `mgf.common.cli.run_and_translate` — see
[`cli.md`](cli.md) for that pattern.

## Pattern: deterministic test runs

For one-off scripts that need to be reproducible (scientific
computing, CI fixtures), use the testing preset:

```python
from mgf.common import bootstrap_for_test

with bootstrap_for_test(app_name="repro", app_version="0.0.1"):
    # WARNING-level logging, OTel off, deterministic preset.
    do_work()
```

This is the same fixture pytest uses; it produces identical output
across runs (no TTY-detection, no auto-OTel, no env var pickup).

## See also

- `docs/reference/lifecycle.md` — `bootstrap()` deep dive.
- `docs/recipes/cli.md` — the full CLI shape.
- `docs/recipes/testing.md` — pytest fixtures.
