Opened 20 years ago
Closed 19 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 )
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 , 20 years ago
| Keywords: | rfc architecture added |
|---|---|
| Severity: | trivial → minor |
comment:2 by , 20 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 , 20 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 , 19 years ago
| Milestone: | → 0.10 |
|---|---|
| Owner: | changed from to |



Well, the plugin architecture is normally independant from the fact that components have a
.config, as it's theEnvironmentclass, acting as theComponentManagerwhich adds this attribute to the components that it activates (inEnvironment.component_activated()).There's an exception made to this rule for the
SingletonExtensionPoint, and I always thought that because of this, theSingletonExtensionPointshould have been placed inenv.pyrather than incore.py.I don't think this exception could be made for the base
ExtensionPointwithout making at the same time the dependency to the.configexplicit… and I'm not sure that this is wanted.So I'd suggest that your
ExtensionPointabove should be renamedOrderedExtensionPoint, and placed, together withSingletonExtensionPoint, inenv.py.Then, some extension points could be turned into the ordered type, when it matters.