Esempio n. 1
0
class AppStartup(Declarative):
    """A declarative class for defining a workbench app start-up contribution.

    AppStartup object can be contributed as extensions child to the 'startup'
    extension point of the 'ecpy.app' plugin. AppStartup object are used
    to customize the application start up.

    """
    #: The globally unique identifier for the start-up.
    id = d_(Unicode())

    #: The priority determine the order in which AppStartup are called. The
    #: **lowest** this number the sooner the object will be called. Two
    #: AppStartup with the same priority are called in the order in which they
    #: have been discovered.
    priority = d_(Int(20))

    @d_func
    def run(self, workbench, cmd_args):
        """Function called during app start-up.

        Parameters
        ----------
        workbench :
            Reference to the application workbench.

        cmd_args :
            Commandline arguments passed by the user.

        """
        pass
Esempio n. 2
0
class ExperimentActionBase(Declarative):

    # Name of event that triggers command
    event = d_(Unicode())

    dependencies = List()

    match = Callable()

    # Defines order of invocation. Less than 100 invokes before default. Higher
    # than 100 invokes after default. Note that if concurrent is True, then
    # order of execution is not guaranteed.
    weight = d_(Int(50))

    # Arguments to pass to command by keyword
    kwargs = d_(Dict())

    def _default_dependencies(self):
        return get_dependencies(self.event)

    def _default_match(self):
        code = compile(self.event, 'dynamic', 'eval')
        if len(self.dependencies) == 1:
            return partial(simple_match, self.dependencies[0])
        else:
            return partial(eval, code)

    def __str__(self):
        return f'{self.event} (weight={self.weight}; kwargs={self.kwargs})'
Esempio n. 3
0
class BaseOutput(PSIContribution):

    name = d_(Str()).tag(metadata=True)
    label = d_(Str()).tag(metadata=True)

    target_name = d_(Str()).tag(metadata=True)
    target = d_(Typed(Declarative).tag(metadata=True), writable=False)
    channel = Property().tag(metadata=True)
    engine = Property().tag(metadata=True)

    def _default_label(self):
        return self.name

    def _get_engine(self):
        if self.channel is None:
            return None
        else:
            return self.channel.engine

    def _get_channel(self):
        from .channel import Channel
        target = self.target
        while True:
            if target is None:
                return None
            elif isinstance(target, Channel):
                return target
            else:
                target = target.target

    def is_ready(self):
        raise NotImplementedError
Esempio n. 4
0
class BaseSettings(GroupBox):
    """Base widget for creating settings.

    """
    #: Id of this settings (different from the declaration one as multiple
    #: settings of the same type can exist for a single instrument).
    user_id = d_(Unicode())

    #: Reference to the declaration that created this object.
    declaration = d_(ForwardTyped(lambda: Settings))

    #: Whether or not to make the settings editable
    read_only = d_(Bool())

    @d_func
    def gather_infos(self):
        """Return the current values as a dictionary.

        The base funcion should always be called (using
        BaseSettings.gather_infos as super is not allowed in declarative
        functions) and all values should be strings.

        """
        return {'id': self.declaration.id, 'user_id': self.user_id}

    def _default_title(self):
        return self.user_id + ' (' + self.declaration.id + ')'

    def _post_setattr_user_id(self, old, new):
        if self.declaration:
            self.title = new + ' (' + self.declaration.id + ')'
Esempio n. 5
0
class Block(Declarative):

    name = d_(Unicode())
    label = d_(Unicode())
    compact_label = d_(Unicode())
    factory = d_(Callable())
    context_name_map = Typed(dict)

    blocks = Property()
    parameters = Property()

    hide = d_(List())

    def initialize(self):
        super().initialize()
        for p in self.parameters:
            if p.name in self.hide:
                p.visible = False

    def get_children(self, child_type):
        return [c for c in self.children if isinstance(c, child_type)]

    def _get_blocks(self):
        return self.get_children(Block)

    def _get_parameters(self):
        return self.get_children(Parameter)
Esempio n. 6
0
class Edges(EventInput):
    initial_state = d_(Int(0)).tag(metadata=True)
    debounce = d_(Int()).tag(metadata=True)

    def configure_callback(self):
        cb = super().configure_callback()
        return pipeline.edges(self.initial_state, self.debounce, self.fs,
                              cb).send
Esempio n. 7
0
class Coroutine(Input):
    coroutine = d_(Callable())
    args = d_(Tuple())
    force_active = set_default(True)

    def configure_callback(self):
        cb = super().configure_callback()
        return self.coroutine(*self.args, cb).send
Esempio n. 8
0
class Compare(Declarative):

    data = Typed(pd.DataFrame)
    x_column = d_(Unicode())
    y_column = d_(Unicode())
    as_difference = d_(Bool(True))
    jitter = d_(Bool(True))
    axes = Typed(pl.Axes)
    figure = Typed(pl.Figure)
    selected = Typed(list)

    def _default_figure(self):
        return pl.Figure()

    def _default_axes(self):
        return self.figure.add_subplot(111)

    def _observe_data(self, event):
        self._update_plot()

    def _observe_x_column(self, event):
        self._update_plot()

    def _observe_y_column(self, event):
        self._update_plot()

    def _observe_as_difference(self, event):
        self._update_plot()

    def _observe_jitter(self, event):
        self._update_plot()

    def _default_x_column(self):
        return self.data.columns[0]

    def _default_y_column(self):
        i = 1 if (len(self.data.columns) > 1) else 0
        return self.data.columns[i]

    def _update_plot(self):
        x = self.data[self.x_column].copy()
        y = self.data[self.y_column].copy()
        if self.as_difference:
            y -= x
        if self.jitter:
            x += np.random.uniform(-1, 1, len(x))
            y += np.random.uniform(-1, 1, len(x))

        self.axes.clear()
        self.axes.plot(x, y, 'ko', picker=4, mec='w', mew=1)
        if self.figure.canvas is not None:
            self.figure.canvas.draw()

    def pick_handler(self, event):
        rows = self.data.iloc[event.ind]
        files = list(rows.index.get_level_values('raw_file'))
        frequencies = list(rows.index.get_level_values('frequency'))
        self.selected = list(zip(files, frequencies))
Esempio n. 9
0
class ManufacturerAlias(Declarative):
    """Declares that a manufacturer may be known under different names.

    """
    #: Main name under which the vendor is expected to be known
    id = d_(Unicode())

    #: List of aliased names.
    aliases = d_(List())
Esempio n. 10
0
class BaseMonitorItem(DockItem):
    """Base class for the view associated with a monitor.

    """
    #: Reference to the monitor driving this view. This is susceptible to
    #: change during the lifetime of the widget.
    monitor = d_(Typed(BaseMonitor))

    #: Should this item be made floating by default.
    float_default = d_(Bool())
Esempio n. 11
0
class PlotContainer(BasePlotContainer):

    x_min = d_(Float(0))
    x_max = d_(Float(0))

    @observe('x_min', 'x_max')
    def format_container(self, event=None):
        # If we want to specify values relative to a psi context variable, we
        # cannot do it when initializing the plots.
        if (self.x_min != 0) or (self.x_max != 0):
            self.base_viewbox.setXRange(self.x_min, self.x_max, padding=0)
Esempio n. 12
0
class NIDAQHardwareAIChannel(NIDAQGeneralMixin, NIDAQTimingMixin,
                             HardwareAIChannel):

    #: Available terminal modes. Not all terminal modes may be supported by a
    #: particular device
    TERMINAL_MODES = 'pseudodifferential', 'differential', 'RSE', 'NRSE'
    terminal_mode = d_(Enum(*TERMINAL_MODES)).tag(metadata=True)

    #: Terminal coupling to use. Not all terminal couplings may be supported by
    #: a particular device. Can be `None`, `'AC'`, `'DC'` or `'ground'`.
    terminal_coupling = d_(Enum(None, 'AC', 'DC', 'ground')).tag(metadata=True)
Esempio n. 13
0
class ContextGroup(Declarative):
    '''
    Used to group together context items for management.
    '''
    # Group name
    name = d_(Unicode())

    # Label to use in the GUI
    label = d_(Unicode())

    # Are the parameters in this group visible?
    visible = d_(Bool(True))
Esempio n. 14
0
class BaseTimeseriesPlot(SinglePlot):

    rect_center = d_(Float(0.5))
    rect_height = d_(Float(1))
    fill_color = d_(Typed(object))
    brush = Typed(object)
    _rising = Typed(list, ())
    _falling = Typed(list, ())

    def _default_brush(self):
        return pg.mkBrush(self.fill_color)

    def _default_plot(self):
        plot = pg.QtGui.QGraphicsPathItem()
        plot.setPen(self.pen)
        plot.setBrush(self.brush)
        return plot

    def update(self, event=None):
        lb, ub = self.parent.data_range.current_range
        current_time = self.parent.data_range.current_time

        starts = self._rising
        ends = self._falling
        if len(starts) == 0 and len(ends) == 1:
            starts = [0]
        elif len(starts) == 1 and len(ends) == 0:
            ends = [current_time]
        elif len(starts) > 0 and len(ends) > 0:
            if starts[0] > ends[0]:
                starts = np.r_[0, starts]
            if starts[-1] > ends[-1]:
                ends = np.r_[ends, current_time]

        try:
            epochs = np.c_[starts, ends]
        except ValueError as e:
            log.exception(e)
            log.warning('Unable to update %r, starts shape %r, ends shape %r',
                        self, starts, ends)
            return

        m = ((epochs >= lb) & (epochs < ub)) | np.isnan(epochs)
        epochs = epochs[m.any(axis=-1)]

        path = pg.QtGui.QPainterPath()
        y_start = self.rect_center - self.rect_height * 0.5
        for x_start, x_end in epochs:
            x_width = x_end - x_start
            r = pg.QtCore.QRectF(x_start, y_start, x_width, self.rect_height)
            path.addRect(r)

        deferred_call(self.plot.setPath, path)
Esempio n. 15
0
class Plot(Declarative):

    #: Id of the plot identifying the contributed plot.
    id = d_(Str())

    #: Description of the kind of plot being declared
    description = d_(Str())

    @d_func
    def get_cls(self) -> BasePlot:
        """Access the class implementing the logic."""
        pass
Esempio n. 16
0
class Editor(Declarative):
    """A declarative class for contributing a measurement editor.

    Editor object can be contributed as extensions child to the 'editors'
    extension point of the 'exopy.measurement' plugin.

    """
    #: Unique name used to identify the editor.
    #: The usual format is top_level_package_name.tool_name
    id = d_(Str())

    #: Editor description.
    description = d_(Str())

    #: Rank of this editor. Editors are displayed by rank and alphabetical
    #: order
    rank = d_(Int(100))

    @d_func
    def new(self, workbench, default=True):
        """Create a new instance of the editor.

        Parameters
        ----------
        workbench : Workbench
            Reference to the application workbench.

        default : bool
            Whether to use default parameters or not when creating the object.

        """
        raise NotImplementedError()

    @d_func
    def is_meant_for(self, workbench, selected_task):
        """Determine if the editor is fit to be used for the selected task.

        Parameters
        ----------
        workbench : Workbench
            Reference to the application workbench.

        selected_task : BaseTask
            Currently selected task.

        Returns
        -------
        answer : bool

        """
        raise NotImplementedError()
Esempio n. 17
0
class Preferences(Declarative):
    """Declarative class for defining a workbench preference contribution.

    Preferences object can be contributed as extensions child to the 'plugin'
    extension point of a preference plugin.

    """

    #: Id of the contribution. This MUST match the declaring plugin.
    # FIXME make this self-generated or something
    id = d_(Str())

    #: Short description of what is expected to be saved.
    description = d_(Str())

    #: Name of the method of the plugin contributing this extension to call
    #: when the preference plugin need to save the preferences.
    saving_method = d_(Str("preferences_from_members"))

    #: Name of the method of the plugin contributing this extension to call
    #: when the preference plugin need to load preferences.
    loading_method = d_(Str("update_members_from_preferences"))

    #: The list of plugin members whose values should be observed and whose
    #: update should cause and automatic update of the preferences.
    auto_save = d_(List())

    #: A callable taking the plugin_id and the preference declaration as arg
    #: and returning an autonomous enaml view (Container) used to edit
    #: the preferences.
    @d_func
    def edit_view(self, workbench: Workbench, id: str) -> Container:
        """Create a view to edit the preferences.

        Parameters
        ----------
        workbench :
            Reference to the application workbench.

        id : str
            Id of the plugin for which to generate the view.

        Returns
        -------
        view : enaml.widgets.api.Container
            View used to edit the preferences. It should have a model
            attribute. The model members must correspond to the tagged members
            the plugin, their values will be used to update the preferences.

        """
        pass
Esempio n. 18
0
class FFTContainer(BasePlotContainer):
    '''
    Contains one or more viewboxes that share the same frequency-based X-axis
    '''
    freq_lb = d_(Float(500))
    freq_ub = d_(Float(50000))
    octave_spacing = d_(Bool(True))

    def _default_x_transform(self):
        return np.log10

    @observe('container', 'freq_lb', 'freq_ub')
    def _update_x_limits(self, event):
        if not self.is_initialized:
            # This addresses a segfault that occurs when attempting to load
            # experiment manifests that use FFTContainer. If the Experiment
            # manifest attempts to set freq_lb or freq_ub, then it will attempt
            # to initialize everything else before the GUI is created, leading
            # to a segfault (creating an AxisItem leads to attempting to call
            # QGraphicsLabel.setHtml, which will segfault if there is no
            # instance of QtApplcation). By ensuring we don't continue if the
            # object is not initialized yet, we can properly load experiment
            # manifests (e.g., so that `psi` can properly list the available
            # paradigms).
            return
        self.base_viewbox.setXRange(np.log10(self.freq_lb),
                                    np.log10(self.freq_ub),
                                    padding=0)
        if self.octave_spacing:
            major_ticks = util.octave_space(self.freq_lb / 1e3,
                                            self.freq_ub / 1e3, 1.0)
            major_ticklabs = [str(t) for t in major_ticks]
            major_ticklocs = np.log10(major_ticks * 1e3)
            minor_ticks = util.octave_space(self.freq_lb / 1e3,
                                            self.freq_ub / 1e3, 0.125)
            minor_ticklabs = [str(t) for t in minor_ticks]
            minor_ticklocs = np.log10(minor_ticks * 1e3)
            ticks = [
                list(zip(major_ticklocs, major_ticklabs)),
                list(zip(minor_ticklocs, minor_ticklabs)),
            ]
            self.x_axis.setTicks(ticks)
        else:
            self.x_axis.setTicks()

    def _default_x_axis(self):
        x_axis = super()._default_x_axis()
        x_axis.setLabel('Frequency', units='Hz')
        x_axis.logTickStrings = format_log_ticks
        x_axis.setLogMode(True)
        return x_axis
Esempio n. 19
0
class ToneCalibrate(PSIContribution):

    outputs = d_(Dict())
    input_name = d_(Unicode())
    gain = d_(Float(-40))
    duration = d_(Float(100e3))
    iti = d_(Float(0))
    trim = d_(Float(10e-3))
    max_thd = d_(Value(None))
    min_snr = d_(Value(None))
    selector_name = d_(Unicode('default'))
    show_widget = d_(Bool(True))

    result = Value()
Esempio n. 20
0
class Accumulate(ContinuousInput):
    '''
    Chunk data based on number of calls
    '''
    n = d_(Int()).tag(metadata=True)
    axis = d_(Int(-1)).tag(metadata=True)
    newaxis = d_(Bool(False)).tag(metadata=True)

    status_cb = d_(Callable(lambda x: None))

    def configure_callback(self):
        cb = super().configure_callback()
        return pipeline.accumulate(self.n, self.axis, self.newaxis,
                                   self.status_cb, cb).send
Esempio n. 21
0
class Console(Container):
    """ Console widget """
    proxy = Typed(ProxyConsole)
    
    #: Font family, leave blank for default
    font_family = d_(Unicode())
    
    #: Font size, leave 0 for default
    font_size = d_(Int(0))
    
    #: Default console size in characters
    console_size = d_(Coerced(Size,(81,25)))
    
    #: Buffer size, leave 0 for default
    buffer_size = d_(Int(0))
    
    #: Display banner like version, etc..
    display_banner = d_(Bool(False))
    
    #: Code completion type
    #: Only can be set ONCE
    completion = d_(Enum('ncurses','plain', 'droplist'))
    
    #: Run the line or callabla
    execute = d_(Instance(object))
    
    #: Push variables to the console
    #: Note this is WRITE ONLY
    scope = d_(Dict(),readable=False)
    
    @observe('font_family','font_size','console_size','buffer_size',
             'scope','display_banner','execute','completion')
    def _update_proxy(self, change):
        super(Console, self)._update_proxy(change)
Esempio n. 22
0
class NIDAQTimingMixin(Declarative):

    #: Specifies sampling clock for the channel. Even if specifying a sample
    #: clock, you still need to explicitly set the fs attribute.
    sample_clock = d_(Str().tag(metadata=True))

    #: Specifies the start trigger for the channel. If None, sampling begins
    #: when task is started.
    start_trigger = d_(Str().tag(metadata=True))

    #: Reference clock for the channel. If you aren't sure, a good value is
    #: `PXI_Clk10` if using a PXI chassis. This ensures that the sample clocks
    #: across all NI cards in the PXI chassis are synchronized.
    reference_clock = d_(Str()).tag(metadata=True)
Esempio n. 23
0
class Channel(PSIContribution):

    #: Globally-unique name of channel used for identification
    name = d_(Unicode()).tag(metadata=True)

    #: Label of channel used in GUI
    label = d_(Unicode()).tag(metadata=True)

    #: Is channel active during experiment?
    active = Property()

    # SI unit (e.g., V)
    unit = d_(Unicode()).tag(metadata=True)

    # Number of samples to acquire before task ends. Typically will be set to
    # 0 to indicate continuous acquisition.
    samples = d_(Int(0)).tag(metadata=True)

    # Used to properly configure data storage.
    dtype = d_(Unicode()).tag(metadata=True)

    # Parent engine (automatically derived by Enaml hierarchy)
    engine = Property()

    # Calibration of channel
    calibration = d_(Typed(Calibration, factory=UnityCalibration))
    calibration.tag(metadata=True)

    # Can the user modify the channel calibration?
    calibration_user_editable = d_(Bool(False))

    # Is channel active during experiment?
    active = Property()

    filter_delay = d_(Float(0).tag(metadata=True))

    def _default_calibration(self):
        return UnityCalibration()

    def __init__(self, *args, **kwargs):
        # This is a hack due to the fact that name is defined as a Declarative
        # member and each Mixin will overwrite whether or not the name is
        # tagged.
        super().__init__(*args, **kwargs)
        self.members()['name'].tag(metadata=True)

    def _get_engine(self):
        return self.parent

    def _set_engine(self, engine):
        self.set_parent(engine)

    def configure(self):
        pass

    def _get_active(self):
        raise NotImplementedError

    def __str__(self):
        return self.label
Esempio n. 24
0
class BasePlot(PSIContribution):

    # Make this weak-referenceable so we can bind methods to Qt slots.
    __slots__ = '__weakref__'

    source_name = d_(Str())
    source = Typed(object)
    label = d_(Str())

    def update(self, event=None):
        pass

    def _reset_plots(self):
        pass
Esempio n. 25
0
class BaseEditor(DestroyablePage):
    """Base class for all editors.

    """
    #: Declaration defining this editor.
    declaration = ForwardTyped(lambda: Editor)

    #: Currently selected task in the tree.
    selected_task = d_(Typed(BaseTask))

    #: Should the tree be visible when this editor is selected.
    tree_visible = d_(Bool(True))

    #: Should the tree be enabled when this editor is selected.
    tree_enabled = d_(Bool(True))

    @d_func
    def react_to_selection(self, workbench):
        """Take any necessary actions when the editor is selected.

        This method is called by the framework at the appropriate time.

        Parameters
        ----------
        workbench : Workbench
            Reference to the application workbench.

        """
        pass

    @d_func
    def react_to_unselection(self, workbench):
        """Take any necessary actions when the editor is unselected.

        This method is called by the framework at the appropriate time.

        Parameters
        ----------
        workbench : Workbench
            Reference to the application workbench.

        """
        pass

    def _default_name(self):
        """Set the name of the widget otherwise the notebook does not work well

        """
        return self.declaration.id
Esempio n. 26
0
class ErrorHandler(Declarative):
    """Handler taking care of certain kind of errors."""

    #: Id of the error. When signaling errors it will referred to as the kind.
    id = d_(Str())

    #: Short description of what this handler can do. The keyword for the
    #: handle method should be specified.
    description = d_(Str())

    @d_func
    def handle(self, workbench: Workbench,
               infos: Union[Mapping, List[Mapping]]) -> Widget:
        """Handle the report by taking any appropriate measurement.

        The error should always be logged to be sure that a trace remains.

        Parameters
        ----------
        workbench :
            Reference to the application workbench.

        infos : dict or list
            Information about the error to handle. Should also accept a list
            of such description. The format of the infos should be described in
            the description member.

        Returns
        -------
        widget : enaml.widgets.api.Widget
            Enaml widget to display as appropriate in a dialog.

        """
        raise NotImplementedError()

    @d_func
    def report(self, workbench: Workbench) -> Widget:
        """Provide a report about all errors that occurred.

        Implementing this method is optional.

        Returns
        -------
        widget : enaml.widgets.api.Widget
            A widget describing the errors that will be included in a dialog
            by the plugin. If None is returned the report is simply ignored.

        """
        pass
Esempio n. 27
0
class PSIContribution(Declarative):

    #: Name of contribution. Should be unique among all contributions for that
    #: subclass as it is typically used by the plugins to find a particular
    #: contribution.
    name = d_(Str())

    #: Label of contribution. Not required, but a good idea as it affects how
    #: the contribution may be represented in the user interface.
    label = d_(Str())

    #: Flag indicating whether item has already been registered.
    registered = Bool(False)

    def _default_name(self):
        # Provide a default name if none is specified TODO: make this mandatory
        # (i.e., no default?)
        return self.parent.name + '.' + self.__class__.__name__

    def _default_label(self):
        return self.name.replace('_', ' ')

    @classmethod
    def valid_name(self, label):
        '''
        Can be used to convert a label provided to a valid name
        '''
        # TODO: Get rid of this?
        return re.sub('\W|^(?=\d)', '_', label)

    def find_manifest_class(self):
        return find_manifest_class(self)

    def load_manifest(self, workbench):
        if self.registered:
            return
        try:
            manifest_class = self.find_manifest_class()
            manifest = manifest_class(contribution=self)
            workbench.register(manifest)
            self.registered = True
            m = 'Loaded manifest for contribution %s (%s) with ID %r'
            log.debug(m, self.name, manifest_class.__name__, manifest.id)
        except ManifestNotFoundError:
            m = 'No manifest defined for contribution %s'
            log.warn(m, self.name)
        except ValueError as e:
            m = f'Manifest "{manifest.id}" for plugin "{self.name}" already registered.'
            raise ImportError(m) from e
Esempio n. 28
0
class Trigger(DigitalOutput):

    #: Total number of triggers.
    total_fired = d_(Int(0), writable=False)

    #: Specifies the default duration for the trigger. This can be overridden
    #: by specifying a duration when calling `fire`.
    duration = d_(Float(0.1))

    def fire(self, duration=None):
        if duration is None:
            duration = self.duration
        if self.engine.configured:
            self.engine.fire_sw_do(self.channel.name, duration=duration)
            self.total_fired += 1
Esempio n. 29
0
class Starter(Declarative):
    """Object responsible initializind/finalizing a driver of a certain type.

    """
    #: Unique id identifying this starter.
    #: The usual format is top_level_package_name.starter_name
    id = d_(Unicode())

    #: Description of the starter action.
    description = d_(Unicode())

    #: Starter instance to use for managing associate instruments.
    #: Note that the class must be defined in a python file not enaml file
    #:  to be pickeable.
    starter = d_(Typed(BaseStarter))
Esempio n. 30
0
class ExperimentActionBase(Declarative):

    # Name of event that triggers command
    event = d_(Str())

    dependencies = List()

    match = Callable()

    #: Defines order of invocation. Less than 100 invokes before default. Higher
    #: than 100 invokes after default. Note that if concurrent is True, then
    #: order of execution is not guaranteed.
    weight = d_(Int(50))

    #: Arguments to pass to command by keyword
    kwargs = d_(Dict())

    #: Should action be delayed? If nonzero, this may cause some timing issues.
    #: Use with caution.
    delay = d_(Float(0))

    def _get_params(self, **kwargs):
        kwargs.update(self.kwargs)
        params = {}
        for k, v in kwargs.items():
            if getattr(v, 'is_lookup', False):
                v = v()
            params[k] = v
        return params

    def _default_dependencies(self):
        return get_dependencies(self.event)

    def _default_match(self):
        code = compile(self.event, 'dynamic', 'eval')
        if len(self.dependencies) == 1:
            return partial(simple_match, self.dependencies[0])
        else:
            return partial(eval_match, code)

    def __str__(self):
        return f'{self.event} (weight={self.weight}; kwargs={self.kwargs})'

    def invoke(self, core, **kwargs):
        if self.delay != 0:
            timed_call(self.delay * 1e3, self._invoke, core, **kwargs)
        else:
            return self._invoke(core, **kwargs)