#3045: [RFC] Ordered ExtensionPoint implementation
------------------------------+---------------------------------------------
Reporter: athomas | Owner: jonas
Type: enhancement | Status: new
Priority: low | Milestone:
Component: general | Version: devel
Severity: minor | Resolution:
Keywords: rfc architecture |
------------------------------+---------------------------------------------
Old description:
> In the same vein as `SingletonExtensionPoint`, this uses a config entry
> to determine the order in which the extensions are returned:
>
> {{{
> #!python
> 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
> }}}
New description:
In the same vein as `SingletonExtensionPoint`, this uses a config entry to
determine the order in which the extensions are returned:
{{{
#!python
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`... ;)
Comment (by athomas):
Here's an updated version using the new config option magic:
{{{
#!python
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
}}}
--
Ticket URL: <http://projects.edgewall.com/trac/ticket/3045>
The Trac Project <http://trac.edgewall.com/>
_______________________________________________
Trac-Tickets mailing list
[email protected]
http://lists.edgewall.com/mailman/listinfo/trac-tickets