Plugin API Policy

SpectroChemPy plugins expose public APIs in two complementary places. Choose the location from the kind of operation, not from the plugin name.

Package/plugin namespaces

Use scp.<plugin>.* for functions that create objects, read files, or run global plugin-level workflows. These functions do not operate on an existing NDDataset instance as their parent object.

Examples:

scp.nmr.read_topspin(...)
scp.iris.IRIS(...)
scp.iris.batch_iris(...)

The legacy alias scp.read_topspin(...) is kept as a compatibility layer for existing code. When the NMR plugin is installed, it delegates through the plugin reader registry to scp.nmr.read_topspin(...). When the plugin is not installed, the core stub raises a MissingPluginError with the install hint pip install spectrochempy[nmr].

Dataset accessors

Use dataset.<plugin>.* only for operations that genuinely use the parent NDDataset as input. The accessor callable receives the dataset as its first argument.

Examples:

dataset.iris.kernel_matrix(...)

Avoid dataset accessors for I/O, object creation, or high-level scientific workflows. In particular, do not add APIs such as:

dataset.read_topspin(...)
dataset.nmr.read_topspin(...)
dataset.nmr.phase(...)
dataset.nmr.apodize(...)

For NMR, the high-level scientific API is Experiment:

dataset = scp.nmr.read(path)
experiment = scp.nmr.Experiment(dataset)
spectrum = experiment.process(
    apodization="em", lb=10.0, phase="manual", phc0=45.0
)

Low-level NMR operations (apodization, phasing, FFT) already exist as NDDataset methods (dataset.em(...), dataset.pk(...), dataset.fft()). The NMR-specific orchestration and scientific interpretation belong exclusively to Experiment, avoiding two concurrent high-level APIs.

Current implementation note

The current plugin registry stores namespaced dataset accessors as string keys such as "iris.kernel_matrix". At runtime, DatasetPluginAccessor exposes this as dataset.iris.kernel_matrix(...). This is an incremental mechanism, not a final accessor-class design.

A future cleanup may move mature domains toward real accessor classes, but the public policy should remain stable:

  • I/O, object creation, and global workflows belong at scp.<plugin>.*.

  • Operations on an existing dataset belong at dataset.<plugin>.*.

  • Legacy aliases are thin compatibility layers, not new primary APIs.

Namespace conventions

Official plugin namespaces should be short, stable, and domain-oriented. They represent the scientific or technical domain exposed to users, for example scp.iris or scp.nmr. Experimental plugin namespaces such as scp.cantera follow the same naming convention but are not yet stable. Avoid creating a second namespace for the same domain unless there is a clear migration plan.

Core I/O namespaces such as jcamp, csv, omnic, opus, matlab, spc, soc, wire, quadera, and labspec are reserved. Plugins must not claim these names, to prevent shadowing and keep scp.<domain> unambiguous.

Reserved public root symbols

A plugin’s short I/O namespace must not collide with a public scp symbol. Registration refuses the namespace with a controlled warning whenever its name matches an existing public scp symbol — a public function or class, an NDDataset method exposed at root, a compatibility alias, a public submodule, a core I/O namespace, or a plot-profile function. The rejected name is recorded in spectrochempy.core.io_namespaces._REJECTED_IO_NAMESPACES for introspection.

The reader behind a refused namespace is unaffected: it remains available through its explicit read_<format> function (for example scp.read_simpson / scp.<plugin>.read_simpson). Never rely on a short namespace that matches a public root symbol, and never build a hybrid object that is both callable and carries .read.

For the other plugin-provided root surfaces (root_exports, reader exports, analysis and simulation extensions), the guarantee is resolution priority: whenever a name is a public scp symbol, root attribute access always resolves to the core symbol, regardless of plugin installation or discovery order. These surfaces are not rejected at registration time.

I/O namespace names colliding with a public scp symbol are rejected at
registration; all other plugin root surfaces are resolved core-first, so
the core symbol always wins.

Documentation and examples should prefer namespace APIs, such as scp.iris.IRIS(), over root-level compatibility aliases such as scp.IRIS. Compatibility aliases may remain in tests when they intentionally protect old user code.