Extending DASCore

Namespaces let external packages expose methods such as patch.my_plugin.method() without subclassing DASCore objects or adding optional dependencies to core. File formats use FiberIO, not namespaces.

Host Base class Entry-point group Registry
Patch PatchNameSpace dascore.patch_namespace patch.csv
Spool SpoolNameSpace dascore.spool_namespace spool.csv
Inventory InventoryNameSpace dascore.inventory_namespace inventory.csv
AnnotationSet AnnotationNameSpace dascore.annotation_namespace annotation.csv

The base classes live in dascore.utils.namespace. A method receives the host object, so it behaves like a normal host method.

Local namespaces

For notebooks, tests, or private code, define a named subclass and import it before access:

import dascore as dc
from dascore.constants import PatchType
from dascore.utils.namespace import PatchNameSpace


class MyPatchNamespace(PatchNameSpace):
    name = "my_ext"

    @dc.patch_function()
    def peak_to_peak(patch: PatchType) -> float:
        return patch.data.max() - patch.data.min()


patch = dc.get_example_patch()
value = patch.my_ext.peak_to_peak()

The name must be a public Python identifier. Defining a duplicate host/name pair warns and replaces the earlier class.

Plugin packages

Reusable extensions should register an entry point. Given the class above in dascore_extra.patch_namespace:

[project.entry-points."dascore.patch_namespace"]
my_ext = "dascore_extra.patch_namespace:MyPatchNamespace"

DASCore loads it on first access. Spool, Inventory, and AnnotationSet use the same pattern with the base class and group from the table.

After publishing the package, add it to the matching file under dascore/plugin_registry/ so missing-plugin errors can tell users what to install:

package_name,package_url,namespace
dascore-extra,https://github.com/yourorg/dascore-extra,my_ext

DASCore’s own patch.io, inventory.io, and annotation_set.io follow this model.

Array backends

Patch functions receive the original data backend. Use its array API namespace instead of NumPy when the function should support multiple backends:

import dascore as dc
from dascore.constants import PatchType
from dascore.utils.array_api import array_namespace


@dc.patch_function()
def double(patch: PatchType) -> PatchType:
    xp = array_namespace(patch.data)
    return patch.new(data=xp.multiply(patch.data, 2))

Plain NumPy-based functions may raise or return a NumPy-backed patch for other arrays. Operators and ufuncs stay in the array namespace when possible; otherwise they convert through NumPy and emit NumpyFallbackWarning.

NumPy functions such as np.mean(patch) and reductions over excluded dtypes always use this fallback; __array__-only objects report numpy as their backend.

Some operations use a PatchProcessor with separate metadata and array kernels. A backend package can register its own kernel without changing the operation:

import numpy as np

from dascore.proc.basic import Abs
from dascore.workflow import register_kernel


@register_kernel(Abs, "mybackend")
def _abs_mybackend(processor, data, meta, out_meta):
    return np.abs(data)

The data backend selects the kernel; unregistered backends use the processor’s standard kernel. Most patch functions are still plain functions and need no processor.

See Contributing, Testing, and Documentation for package conventions.