Edgewall Software
Modify

Opened 18 years ago

Closed 18 years ago

#3045 closed enhancement (fixed)

[RFC] Ordered ExtensionPoint implementation

Reported by: Alec Thomas Owned by: Alec Thomas
Priority: low Milestone: 0.10
Component: general Version: devel
Severity: minor Keywords: rfc architecture
Cc: Branch:
Release Notes:
API Changes:
Internal Changes:

Description (last modified by Alec Thomas)

In the same vein as SingletonExtensionPoint, this uses a config entry to determine the order in which the extensions are returned:

class ExtensionPoint(property):
    """ An extension point, possibly ordered by a configuration option. Entries
    missing from the option can be excluded. """
    def __init__(self, interface, cfg_section=None, cfg_property=None,
                 exclude_missing=False):
        property.__init__(self, self.extensions)
        self.interface = interface
        self.__doc__ = 'List of components that implement `%s`' % \
                       self.interface.__name__
        self.cfg_section = cfg_section or 'interfaces'
        self.cfg_property = cfg_property or interface.__name__.lower()
        self.exclude_missing = exclude_missing

    def extensions(self, component):
        """Return a list of components that declare to implement the
        extension point interface."""
        order = filter(None, component.config.getlist(self.cfg_section,
                                                      self.cfg_property))
        extensions = ComponentMeta._registry.get(self.interface, [])
        extensions = filter(None, [component.compmgr[cls] for cls in extensions
                                   if not self.exclude_missing
                                   or not order
                                   or cls.__name__ in order])
        if order:
            def compare(x, y):
                x, y = x.__class__.__name__, y.__class__.__name__
                if x not in order:
                    return int(y in order)
                if y not in order:
                    return -int(x in order)
                return cmp(order.index(x), order.index(y))
            extensions.sort(compare)
        return extensions

    def __repr__(self):
        """Return a textual representation of the extension point."""
        return '<ExtensionPoint %s>' % self.interface.__name__

This could replace the existing ExtensionPoint class and add ordering automatically to all existing interfaces. Ordering for any interface can be specified in the [interfaces] section. eg.

[interfaces]
iconfigurable = TicketSystem, WikiSystem
ipermissionrequestor = PermissionSystem

…and yet another case for factoring getbool out into trac.util… ;)

Attachments (0)

Change History (5)

comment:1 by Christian Boos, 18 years ago

Keywords: rfc architecture added
Severity: trivialminor

Well, the plugin architecture is normally independant from the fact that components have a .config, as it's the Environment class, acting as the ComponentManager which adds this attribute to the components that it activates (in Environment.component_activated()).

There's an exception made to this rule for the SingletonExtensionPoint, and I always thought that because of this, the SingletonExtensionPoint should have been placed in env.py rather than in core.py.

I don't think this exception could be made for the base ExtensionPoint without making at the same time the dependency to the .config explicit… and I'm not sure that this is wanted.

So I'd suggest that your ExtensionPoint above should be renamed OrderedExtensionPoint, and placed, together with SingletonExtensionPoint, in env.py.

Then, some extension points could be turned into the ordered type, when it matters.

comment:2 by Alec Thomas, 18 years ago

Sounds good to me. OrderedExtensionPoint was actually what I had at first. I just got lured in by the possibility of adding ordering to everything! MWuaha.

comment:3 by Alec Thomas, 18 years ago

Description: modified (diff)

Here's an updated version using the new config option magic:

class OrderedExtensionOption(ListOption):
    def __init__(self, section, name, interface, default=None,
                 include_missing=True, doc=''):
        ListOption.__init__(self, section, name, default, doc=doc)
        self.xtnpt = ExtensionPoint(interface)
        self.include_missing = include_missing

    def __get__(self, instance, owner):
        if instance is None:
            return self
        order = ListOption.__get__(self, instance, owner)
        if not order and self.default is not None:
            items = filter(None, [item.strip() for item in
                                  self.default.split(self.sep, ',')])
        components = []
        for impl in self.xtnpt.extensions(instance):
            if self.include_missing or impl.__class__.__name__ in order:
                components.append(impl)

        if not components:
            if self.default is not None:
                return self.default(instance.env)
            raise AttributeError('Cannot find any implementations of the "%s" '
                                 'interface from "%s".  Please update the '
                                 'option %s.%s in trac.ini.'
                                 % (self.xtnpt.interface.__name__,
                                    ', '.join(order), self.section, self.name))
        if order:
            def compare(x, y):
                x, y = x.__class__.__name__, y.__class__.__name__
                if x not in order:
                    return int(y in order)
                if y not in order:
                    return -int(x in order)
                return cmp(order.index(x), order.index(y))
            components.sort(compare)

        return components

comment:4 by Christian Boos, 18 years ago

Milestone: 0.10
Owner: changed from Jonas Borgström to Alec Thomas

comment:5 by Christian Boos, 18 years ago

Resolution: fixed
Status: newclosed

Was implemented in r3358.

Modify Ticket

Change Properties
Set your email in Preferences
Action
as closed The owner will remain Alec Thomas.
The resolution will be deleted. Next status will be 'reopened'.
to The owner will be changed from Alec Thomas to the specified user.

Add Comment


E-mail address and name can be saved in the Preferences .
 
Note: See TracTickets for help on using tickets.