From bc1675dc3853748064dbf1485bf58bce0e344add Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 28 Jul 2008 04:34:25 +0000 Subject: 30: Added plugable module with more generic implementation of Registrar; added corresponding unit tests --- ipalib/plugable.py | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 ipalib/plugable.py (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py new file mode 100644 index 00000000..ba2241b9 --- /dev/null +++ b/ipalib/plugable.py @@ -0,0 +1,95 @@ +# Authors: +# Jason Gerard DeRose +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" +Utility classes for registering plugins, base classe for writing plugins. +""" + +import inspect +import exceptions + + + +class Registrar(object): + def __init__(self, *allowed): + """ + `*allowed` is a list of the base classes plugins can be subclassed + from. + """ + self.__allowed = frozenset(allowed) + self.__d = {} + self.__registered = set() + assert len(self.__allowed) == len(allowed) + for base in self.__allowed: + assert inspect.isclass(base) + assert base.__name__ not in self.__d + self.__d[base.__name__] = {} + + def __findbase(self, cls): + """ + If `cls` is a subclass of a base in self.__allowed, returns that + base; otherwise raises SubclassError. + """ + assert inspect.isclass(cls) + for base in self.__allowed: + if issubclass(cls, base): + return base + raise exceptions.SubclassError(cls, self.__allowed) + + def __call__(self, cls, override=False): + """ + Register the plugin `cls`. + """ + if not inspect.isclass(cls): + raise TypeError('plugin must be a class: %r' % cls) + + # Find the base class or raise SubclassError: + base = self.__findbase(cls) + sub_d = self.__d[base.__name__] + + # Raise DuplicateError if this exact class was already registered: + if cls in self.__registered: + raise exceptions.DuplicateError(cls) + + # Check override: + if cls.__name__ in sub_d: + # Must use override=True to override: + if not override: + raise exceptions.OverrideError(base, cls) + else: + # There was nothing already registered to override: + if override: + raise exceptions.MissingOverrideError(base, cls) + + # The plugin is okay, add to __registered and sub_d: + self.__registered.add(cls) + sub_d[cls.__name__] = cls + + def __getitem__(self, name): + """ + Returns a copy of the namespace dict of the base class named `name`. + """ + return dict(self.__d[name]) + + def __iter__(self): + """ + Iterates through the names of the allowed base classes. + """ + for key in self.__d: + yield key -- cgit From d7569a84b94ab304a1b7f353ea71c15061ebd5d4 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 31 Jul 2008 18:57:10 +0000 Subject: 31: Renamed exceptions.py to errors.py --- ipalib/plugable.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index ba2241b9..0de31d82 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -22,7 +22,7 @@ Utility classes for registering plugins, base classe for writing plugins. """ import inspect -import exceptions +import errors @@ -50,7 +50,7 @@ class Registrar(object): for base in self.__allowed: if issubclass(cls, base): return base - raise exceptions.SubclassError(cls, self.__allowed) + raise errors.SubclassError(cls, self.__allowed) def __call__(self, cls, override=False): """ @@ -65,17 +65,17 @@ class Registrar(object): # Raise DuplicateError if this exact class was already registered: if cls in self.__registered: - raise exceptions.DuplicateError(cls) + raise errors.DuplicateError(cls) # Check override: if cls.__name__ in sub_d: # Must use override=True to override: if not override: - raise exceptions.OverrideError(base, cls) + raise errors.OverrideError(base, cls) else: # There was nothing already registered to override: if override: - raise exceptions.MissingOverrideError(base, cls) + raise errors.MissingOverrideError(base, cls) # The plugin is okay, add to __registered and sub_d: self.__registered.add(cls) -- cgit From a131ebf72469d416d4c08e23a7f3ac70854b237b Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 31 Jul 2008 22:36:15 +0000 Subject: 32: Added Plugin and Proxy base classes in plugable module, along with to_cli() and from_cli() functions; added correspending unit tests --- ipalib/plugable.py | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 0de31d82..054b12db 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -21,10 +21,94 @@ Utility classes for registering plugins, base classe for writing plugins. """ + import inspect import errors +def to_cli(name): + assert isinstance(name, basestring) + return name.replace('__', '.').replace('_', '-') + +def from_cli(cli_name): + assert isinstance(cli_name, basestring) + return cli_name.replace('-', '_').replace('.', '__') + + +class Plugin(object): + """ + Base class for all plugins. + """ + + def __get_name(self): + """ + Returns the class name of this instance. + """ + return self.__class__.__name__ + name = property(__get_name) + + def __repr__(self): + """ + Returns a valid Python expression that could create this plugin + instance given the appropriate environment. + """ + return '%s.%s()' % ( + self.__class__.__module__, + self.__class__.__name__ + ) + + +class Proxy(object): + """ + Used to only export certain attributes into the dynamic API. + + Subclasses must list names of attributes to be proxied in the __slots__ + class attribute. + """ + + __slots__ = ( + '__obj', + 'name', + 'cli_name', + ) + + def __init__(self, obj, proxy_name=None): + """ + Proxy attributes on `obj`. + """ + if proxy_name is None: + proxy_name = obj.name + assert isinstance(proxy_name, str) + object.__setattr__(self, '_Proxy__obj', obj) + object.__setattr__(self, 'name', proxy_name) + object.__setattr__(self, 'cli_name', to_cli(proxy_name)) + for name in self.__slots__: + object.__setattr__(self, name, getattr(obj, name)) + + def __setattr__(self, name, value): + """ + Proxy instances are read-only. This raises an AttributeError + anytime an attempt is made to set an attribute. + """ + raise AttributeError('cannot set %s.%s' % + (self.__class__.__name__, name) + ) + + def __delattr__(self, name): + """ + Proxy instances are read-only. This raises an AttributeError + anytime an attempt is made to delete an attribute. + """ + raise AttributeError('cannot del %s.%s' % + (self.__class__.__name__, name) + ) + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, self.__obj) + + def __str__(self): + return self.cli_name + class Registrar(object): def __init__(self, *allowed): -- cgit From f53dec2600f95246a72fa3c847a485d2a94edfa7 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 1 Aug 2008 01:47:49 +0000 Subject: 33: Finished unit tests for plugable.Proxy --- ipalib/plugable.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 054b12db..de5f3f8f 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -27,10 +27,19 @@ import errors def to_cli(name): - assert isinstance(name, basestring) + """ + Takes a Python identifier and transforms it into form suitable for the + Command Line Interface. + """ + assert isinstance(name, str) return name.replace('__', '.').replace('_', '-') + def from_cli(cli_name): + """ + Takes a string from the Command Line Interface and transforms it into a + Python identifier. + """ assert isinstance(cli_name, basestring) return cli_name.replace('-', '_').replace('.', '__') @@ -69,7 +78,6 @@ class Proxy(object): __slots__ = ( '__obj', 'name', - 'cli_name', ) def __init__(self, obj, proxy_name=None): @@ -77,11 +85,10 @@ class Proxy(object): Proxy attributes on `obj`. """ if proxy_name is None: - proxy_name = obj.name + proxy_name = obj.__class__.__name__ assert isinstance(proxy_name, str) object.__setattr__(self, '_Proxy__obj', obj) object.__setattr__(self, 'name', proxy_name) - object.__setattr__(self, 'cli_name', to_cli(proxy_name)) for name in self.__slots__: object.__setattr__(self, name, getattr(obj, name)) @@ -107,7 +114,7 @@ class Proxy(object): return '%s(%r)' % (self.__class__.__name__, self.__obj) def __str__(self): - return self.cli_name + return to_cli(self.name) class Registrar(object): -- cgit From 31fc955355ac8d873b82d129021f599f820c2694 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 1 Aug 2008 03:12:17 +0000 Subject: 34: Added tests.unit_common with frequently used utility functions; split ro __setattr__, __delattr__ methods out of Proxy and into new ReadOnly base class; added corresponding unit tests --- ipalib/plugable.py | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index de5f3f8f..e74809cd 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -67,7 +67,32 @@ class Plugin(object): ) -class Proxy(object): +class ReadOnly(object): + """ + Base class for classes with read-only attributes. + """ + __slots__ = tuple() + + def __setattr__(self, name, value): + """ + This raises an AttributeError anytime an attempt is made to set an + attribute. + """ + raise AttributeError('read-only: cannot set %s.%s' % + (self.__class__.__name__, name) + ) + + def __delattr__(self, name): + """ + This raises an AttributeError anytime an attempt is made to delete an + attribute. + """ + raise AttributeError('read-only: cannot del %s.%s' % + (self.__class__.__name__, name) + ) + + +class Proxy(ReadOnly): """ Used to only export certain attributes into the dynamic API. @@ -92,24 +117,6 @@ class Proxy(object): for name in self.__slots__: object.__setattr__(self, name, getattr(obj, name)) - def __setattr__(self, name, value): - """ - Proxy instances are read-only. This raises an AttributeError - anytime an attempt is made to set an attribute. - """ - raise AttributeError('cannot set %s.%s' % - (self.__class__.__name__, name) - ) - - def __delattr__(self, name): - """ - Proxy instances are read-only. This raises an AttributeError - anytime an attempt is made to delete an attribute. - """ - raise AttributeError('cannot del %s.%s' % - (self.__class__.__name__, name) - ) - def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.__obj) -- cgit From 8881e4a543e9f1f1edda2d1cc935c020950214e6 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 1 Aug 2008 06:44:30 +0000 Subject: 38: dict interface of Registrar now works with both classes and strings as the key --- ipalib/plugable.py | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 5 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index e74809cd..9e94e96b 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -124,6 +124,83 @@ class Proxy(ReadOnly): return to_cli(self.name) +class NameSpace(ReadOnly): + """ + A read-only namespace of (key, value) pairs that can be accessed + both as instance attributes and as dictionary items. + """ + + def __init__(self, kw): + """ + The `kw` argument is a dict of the (key, value) pairs to be in this + NameSpace instance. The optional `order` keyword argument specifies + the order of the keys in this namespace; if omitted, the default is + to sort the keys in ascending order. + """ + assert isinstance(kw, dict) + self.__kw = dict(kw) + for (key, value) in self.__kw.items(): + assert not key.startswith('_') + setattr(self, key, value) + if order is None: + self.__keys = sorted(self.__kw) + else: + self.__keys = list(order) + assert set(self.__keys) == set(self.__kw) + self.__locked = True + + def __setattr__(self, name, value): + """ + Raises an exception if trying to set an attribute after the + NameSpace has been locked; otherwise calls object.__setattr__(). + """ + if self.__locked: + raise errors.SetError(name) + super(NameSpace, self).__setattr__(name, value) + + def __getitem__(self, key): + """ + Returns item from namespace named `key`. + """ + return self.__kw[key] + + def __hasitem__(self, key): + """ + Returns True if namespace has an item named `key`. + """ + return bool(key in self.__kw) + + def __iter__(self): + """ + Yields the names in this NameSpace in ascending order, or in the + the order specified in `order` kw arg. + + For example: + + >>> ns = NameSpace(dict(attr_b='world', attr_a='hello')) + >>> list(ns) + ['attr_a', 'attr_b'] + >>> [ns[k] for k in ns] + ['hello', 'world'] + """ + for key in self.__keys: + yield key + + def __call__(self): + """ + Iterates through the values in this NameSpace in the same order as + the keys. + """ + for key in self.__keys: + yield self.__kw[key] + + def __len__(self): + """ + Returns number of items in this NameSpace. + """ + return len(self.__keys) + + class Registrar(object): def __init__(self, *allowed): """ @@ -179,15 +256,30 @@ class Registrar(object): self.__registered.add(cls) sub_d[cls.__name__] = cls - def __getitem__(self, name): + def __getitem__(self, item): """ Returns a copy of the namespace dict of the base class named `name`. """ - return dict(self.__d[name]) + if inspect.isclass(item): + if item not in self.__allowed: + raise KeyError(repr(item)) + key = item.__name__ + else: + key = item + return dict(self.__d[key]) + + def __contains__(self, item): + """ + Returns True if a base class named `name` is in this Registrar. + """ + if inspect.isclass(item): + return item in self.__allowed + return item in self.__d def __iter__(self): """ - Iterates through the names of the allowed base classes. + Iterates through a (base, registered_plugins) tuple for each allowed + base. """ - for key in self.__d: - yield key + for base in self.__allowed: + yield (base, self.__d[base.__name__].values()) -- cgit From f3762a76c0824296e90385eac27455aaf06af32d Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 1 Aug 2008 20:42:35 +0000 Subject: 40: Rewrote dictionary interface for plugable.NameSpace to better suite new architecture --- ipalib/plugable.py | 89 +++++++++++++++++++++--------------------------------- 1 file changed, 35 insertions(+), 54 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 9e94e96b..4923c621 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -130,75 +130,56 @@ class NameSpace(ReadOnly): both as instance attributes and as dictionary items. """ - def __init__(self, kw): - """ - The `kw` argument is a dict of the (key, value) pairs to be in this - NameSpace instance. The optional `order` keyword argument specifies - the order of the keys in this namespace; if omitted, the default is - to sort the keys in ascending order. - """ - assert isinstance(kw, dict) - self.__kw = dict(kw) - for (key, value) in self.__kw.items(): - assert not key.startswith('_') - setattr(self, key, value) - if order is None: - self.__keys = sorted(self.__kw) - else: - self.__keys = list(order) - assert set(self.__keys) == set(self.__kw) - self.__locked = True - - def __setattr__(self, name, value): + def __init__(self, items): """ - Raises an exception if trying to set an attribute after the - NameSpace has been locked; otherwise calls object.__setattr__(). """ - if self.__locked: - raise errors.SetError(name) - super(NameSpace, self).__setattr__(name, value) + object.__setattr__(self, '_NameSpace__items', tuple(items)) - def __getitem__(self, key): - """ - Returns item from namespace named `key`. - """ - return self.__kw[key] + # dict mapping Python name to item: + object.__setattr__(self, '_NameSpace__pname', {}) - def __hasitem__(self, key): - """ - Returns True if namespace has an item named `key`. - """ - return bool(key in self.__kw) + # dict mapping human-readibly name to item: + object.__setattr__(self, '_NameSpace__hname', {}) + + for item in self.__items: + for (key, d) in [ + (item.name, self.__pname), + (str(item), self.__hname), + ]: + assert key not in d + d[key] = item def __iter__(self): """ - Yields the names in this NameSpace in ascending order, or in the - the order specified in `order` kw arg. - - For example: + Iterates through the items in this NameSpace in the same order they + were passed in the contructor. + """ + for item in self.__items: + yield item - >>> ns = NameSpace(dict(attr_b='world', attr_a='hello')) - >>> list(ns) - ['attr_a', 'attr_b'] - >>> [ns[k] for k in ns] - ['hello', 'world'] + def __len__(self): + """ + Returns number of items in this NameSpace. """ - for key in self.__keys: - yield key + return len(self.__items) - def __call__(self): + def __contains__(self, key): """ - Iterates through the values in this NameSpace in the same order as - the keys. + Returns True if an item with pname or hname `key` is in this + NameSpace. """ - for key in self.__keys: - yield self.__kw[key] + return (key in self.__pname) or (key in self.__hname) - def __len__(self): + def __getitem__(self, key): """ - Returns number of items in this NameSpace. + Returns item with pname or hname `key`; otherwise raises KeyError. """ - return len(self.__keys) + if key in self.__pname: + return self.__pname[key] + if key in self.__hname: + return self.__hname[key] + raise KeyError('NameSpace has no item for key %r' % key) + class Registrar(object): -- cgit From a0f480a414d2aa3a5f79e77026ff9183c1dd3a48 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 1 Aug 2008 20:58:48 +0000 Subject: 41: New plugable.NameSpace now has attributes set for each member; updated unit tests --- ipalib/plugable.py | 1 + 1 file changed, 1 insertion(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 4923c621..9025c1db 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -142,6 +142,7 @@ class NameSpace(ReadOnly): object.__setattr__(self, '_NameSpace__hname', {}) for item in self.__items: + object.__setattr__(self, item.name, item) for (key, d) in [ (item.name, self.__pname), (str(item), self.__hname), -- cgit From 74f5719078adfcfdf8b98bf97f0828dd150c840d Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 1 Aug 2008 21:25:46 +0000 Subject: 42: plugable.Plugin.__init__() now takes the plugable.API instance as its single argument --- ipalib/plugable.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 9025c1db..f298e97e 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -49,6 +49,16 @@ class Plugin(object): Base class for all plugins. """ + def __init__(self, api): + self.__api = api + + def __get_api(self): + """ + Returns the plugable.API object this plugin has been instatiated in. + """ + return self.__api + api = property(__get_api) + def __get_name(self): """ Returns the class name of this instance. @@ -132,6 +142,8 @@ class NameSpace(ReadOnly): def __init__(self, items): """ + `items` should be an iterable providing the members of this + NameSpace. """ object.__setattr__(self, '_NameSpace__items', tuple(items)) @@ -182,7 +194,6 @@ class NameSpace(ReadOnly): raise KeyError('NameSpace has no item for key %r' % key) - class Registrar(object): def __init__(self, *allowed): """ @@ -265,3 +276,9 @@ class Registrar(object): """ for base in self.__allowed: yield (base, self.__d[base.__name__].values()) + + +class API(ReadOnly): + def __init__(self, registrar): + for (base, plugins) in registrar: + pass -- cgit From c3bf5ad8579e6f09aba558a68de947b2be398619 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 5 Aug 2008 03:21:52 +0000 Subject: 43: Fleshed out new plugable.API class; added corresponding unit tests --- ipalib/plugable.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index f298e97e..32cbe033 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -281,4 +281,13 @@ class Registrar(object): class API(ReadOnly): def __init__(self, registrar): for (base, plugins) in registrar: - pass + ns = NameSpace(self.__plugin_iter(base, plugins)) + assert not hasattr(self, base.__name__) + object.__setattr__(self, base.__name__, ns) + + def __plugin_iter(self, base, plugins): + assert issubclass(base.proxy, Proxy) + for cls in plugins: + plugin = cls(self) + assert plugin.api is self + yield base.proxy(plugin) -- cgit From 42c53b2a5345560e2583e3d7686b29cde812d52b Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 5 Aug 2008 04:24:19 +0000 Subject: 44: Added Plugin.finalize() method called by API after all plugin instances are created; updated corresponding unit tests --- ipalib/plugable.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 32cbe033..cafb8c50 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -49,16 +49,27 @@ class Plugin(object): Base class for all plugins. """ - def __init__(self, api): - self.__api = api + __api = None def __get_api(self): """ - Returns the plugable.API object this plugin has been instatiated in. + Returns the plugable.API instance passed to Plugin.finalize(), or + or returns None if finalize() has not yet been called. """ return self.__api api = property(__get_api) + def finalize(self, api): + """ + After all the plugins are instantiated, the plugable.API calls this + method, passing itself as the only argument. This is where plugins + should check that other plugins they depend upon have actually be + loaded. + """ + assert self.__api is None, 'finalize() can only be called once' + assert api is not None, 'finalize() argument cannot be None' + self.__api = api + def __get_name(self): """ Returns the class name of this instance. @@ -280,14 +291,18 @@ class Registrar(object): class API(ReadOnly): def __init__(self, registrar): + object.__setattr__(self, '_API__plugins', []) for (base, plugins) in registrar: ns = NameSpace(self.__plugin_iter(base, plugins)) assert not hasattr(self, base.__name__) object.__setattr__(self, base.__name__, ns) + for plugin in self.__plugins: + plugin.finalize(self) + assert plugin.api is self def __plugin_iter(self, base, plugins): assert issubclass(base.proxy, Proxy) for cls in plugins: - plugin = cls(self) - assert plugin.api is self + plugin = cls() + self.__plugins.append(plugin) yield base.proxy(plugin) -- cgit From 2b3c2238f6ecb5fc496acc50fc81f5b658d23c4b Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 5 Aug 2008 04:40:44 +0000 Subject: 45: Fixed docstring typo in plugable.__doc__ --- ipalib/plugable.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index cafb8c50..80349090 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -18,10 +18,9 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ -Utility classes for registering plugins, base classe for writing plugins. +Utility classes for registering plugins, base classes for writing plugins. """ - import inspect import errors -- cgit From d134b483066ae9d3a7e76d6e491f0f91eba6a954 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 5 Aug 2008 05:12:09 +0000 Subject: 46: plugable.API now takes allowed base class in __init__ and creates Registrar at API.register, thereby coupling the two; updated correspending unit tests --- ipalib/plugable.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 80349090..70743f5a 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -289,9 +289,15 @@ class Registrar(object): class API(ReadOnly): - def __init__(self, registrar): + def __init__(self, *allowed): + object.__setattr__(self, 'register', Registrar(*allowed)) object.__setattr__(self, '_API__plugins', []) - for (base, plugins) in registrar: + + def __call__(self): + """ + Finalize the registration, instantiate the plugins. + """ + for (base, plugins) in self.register: ns = NameSpace(self.__plugin_iter(base, plugins)) assert not hasattr(self, base.__name__) object.__setattr__(self, base.__name__, ns) -- cgit From 56fa454fdd229524999127a5b89cc7c9077b9bd6 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 5 Aug 2008 06:33:09 +0000 Subject: 47: Added plugable.check_identifier() function; added corresponding unit tests --- ipalib/plugable.py | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 70743f5a..8f2cbc27 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -21,6 +21,7 @@ Utility classes for registering plugins, base classes for writing plugins. """ +import re import inspect import errors @@ -43,6 +44,16 @@ def from_cli(cli_name): return cli_name.replace('-', '_').replace('.', '__') +def check_identifier(name): + """ + Raises errors.NameSpaceError if `name` is not a valid Python identifier + suitable for use in a NameSpace. + """ + regex = r'^[a-z][_a-z0-9]*[a-z0-9]$' + if re.match(regex, name) is None: + raise errors.NameSpaceError(name, regex) + + class Plugin(object): """ Base class for all plugins. -- cgit From 159207514fadfacb6e1df9713abd2c61c24d7b77 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 5 Aug 2008 22:21:57 +0000 Subject: 52: Got cli working against new framework --- ipalib/plugable.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 8f2cbc27..2c5fd18c 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -92,7 +92,7 @@ class Plugin(object): Returns a valid Python expression that could create this plugin instance given the appropriate environment. """ - return '%s.%s()' % ( + return '%s.%s' % ( self.__class__.__module__, self.__class__.__name__ ) @@ -296,7 +296,8 @@ class Registrar(object): base. """ for base in self.__allowed: - yield (base, self.__d[base.__name__].values()) + sub_d = self.__d[base.__name__] + yield (base, tuple(sub_d[k] for k in sorted(sub_d))) class API(ReadOnly): -- cgit From f31f7813febf0665a072d474166ea883bc7365dc Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 5 Aug 2008 23:34:59 +0000 Subject: 53: Changed plugable.Registar so the same plugin can be added to in the ns for more than one base (for cmd and mthd) --- ipalib/plugable.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 2c5fd18c..a8996cf2 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -236,10 +236,13 @@ class Registrar(object): base; otherwise raises SubclassError. """ assert inspect.isclass(cls) + found = False for base in self.__allowed: if issubclass(cls, base): - return base - raise errors.SubclassError(cls, self.__allowed) + found = True + yield base + if not found: + raise errors.SubclassError(cls, self.__allowed) def __call__(self, cls, override=False): """ @@ -248,27 +251,29 @@ class Registrar(object): if not inspect.isclass(cls): raise TypeError('plugin must be a class: %r' % cls) - # Find the base class or raise SubclassError: - base = self.__findbase(cls) - sub_d = self.__d[base.__name__] - # Raise DuplicateError if this exact class was already registered: if cls in self.__registered: raise errors.DuplicateError(cls) - # Check override: - if cls.__name__ in sub_d: - # Must use override=True to override: - if not override: - raise errors.OverrideError(base, cls) - else: - # There was nothing already registered to override: - if override: - raise errors.MissingOverrideError(base, cls) + # Find the base class or raise SubclassError: + for base in self.__findbase(cls): + sub_d = self.__d[base.__name__] + + # Check override: + if cls.__name__ in sub_d: + # Must use override=True to override: + if not override: + raise errors.OverrideError(base, cls) + else: + # There was nothing already registered to override: + if override: + raise errors.MissingOverrideError(base, cls) + + # The plugin is okay, add to sub_d: + sub_d[cls.__name__] = cls - # The plugin is okay, add to __registered and sub_d: + # The plugin is okay, add to __registered: self.__registered.add(cls) - sub_d[cls.__name__] = cls def __getitem__(self, item): """ -- cgit From c6f69e1c66b86f8f375a3c561922a42fdc0b1afb Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 6 Aug 2008 02:00:18 +0000 Subject: 54: Added plugable.Proxy._clone() method; fleshed out public.obj; updated unit tests; port ipa script --- ipalib/plugable.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index a8996cf2..6e6c6973 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -145,8 +145,8 @@ class Proxy(ReadOnly): assert isinstance(proxy_name, str) object.__setattr__(self, '_Proxy__obj', obj) object.__setattr__(self, 'name', proxy_name) - for name in self.__slots__: - object.__setattr__(self, name, getattr(obj, name)) + #for name in self.__slots__: + # object.__setattr__(self, name, getattr(obj, name)) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.__obj) @@ -154,6 +154,18 @@ class Proxy(ReadOnly): def __str__(self): return to_cli(self.name) + def _clone(self, new_name): + return self.__class__(self.__obj, proxy_name=new_name) + + def __getattr__(self, name): + if name in self.__slots__: + return getattr(self.__obj, name) + raise AttributeError('attribute %r not in %s.__slots__' % ( + name, + self.__class__.__name__ + ) + ) + class NameSpace(ReadOnly): """ @@ -161,6 +173,8 @@ class NameSpace(ReadOnly): both as instance attributes and as dictionary items. """ + __max_len = None + def __init__(self, items): """ `items` should be an iterable providing the members of this @@ -214,6 +228,14 @@ class NameSpace(ReadOnly): return self.__hname[key] raise KeyError('NameSpace has no item for key %r' % key) + def __call__(self): + if self.__max_len is None: + ml = max(len(k) for k in self.__pname) + object.__setattr__(self, '_NameSpace__max_len', ml) + return self.__max_len + + + class Registrar(object): def __init__(self, *allowed): -- cgit From 277685439c91f496df9510e02418da01160df0ea Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 6 Aug 2008 03:27:00 +0000 Subject: 55: Cleaned up print_api() function in ipa script --- ipalib/plugable.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 6e6c6973..e017a8a4 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -175,12 +175,13 @@ class NameSpace(ReadOnly): __max_len = None - def __init__(self, items): + def __init__(self, items, base=None): """ `items` should be an iterable providing the members of this NameSpace. """ object.__setattr__(self, '_NameSpace__items', tuple(items)) + object.__setattr__(self, '_NameSpace__base', base) # dict mapping Python name to item: object.__setattr__(self, '_NameSpace__pname', {}) @@ -234,6 +235,14 @@ class NameSpace(ReadOnly): object.__setattr__(self, '_NameSpace__max_len', ml) return self.__max_len + def __repr__(self): + if self.__base is None: + base = repr(self.__base) + else: + base = '%s.%s' % (self.__base.__module__, self.__base.__name__) + return '%s(*proxies, base=%s)' % (self.__class__.__name__, base) + + @@ -329,6 +338,8 @@ class Registrar(object): class API(ReadOnly): def __init__(self, *allowed): + keys = tuple(b.__name__ for b in allowed) + object.__setattr__(self, '_API__keys', keys) object.__setattr__(self, 'register', Registrar(*allowed)) object.__setattr__(self, '_API__plugins', []) @@ -337,7 +348,7 @@ class API(ReadOnly): Finalize the registration, instantiate the plugins. """ for (base, plugins) in self.register: - ns = NameSpace(self.__plugin_iter(base, plugins)) + ns = NameSpace(self.__plugin_iter(base, plugins), base=base) assert not hasattr(self, base.__name__) object.__setattr__(self, base.__name__, ns) for plugin in self.__plugins: @@ -350,3 +361,7 @@ class API(ReadOnly): plugin = cls() self.__plugins.append(plugin) yield base.proxy(plugin) + + def __iter__(self): + for key in self.__keys: + yield key -- cgit From 8865f516dfbffaee0da679c47aa2709ec8f5d80f Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 6 Aug 2008 03:51:33 +0000 Subject: 56: Fixed Proxy.__call__ --- ipalib/plugable.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index e017a8a4..801ef7ab 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -132,6 +132,7 @@ class Proxy(ReadOnly): """ __slots__ = ( + '__call__', '__obj', 'name', ) @@ -145,6 +146,8 @@ class Proxy(ReadOnly): assert isinstance(proxy_name, str) object.__setattr__(self, '_Proxy__obj', obj) object.__setattr__(self, 'name', proxy_name) + if callable(obj): + object.__setattr__(self, '__call__', obj.__call__) #for name in self.__slots__: # object.__setattr__(self, name, getattr(obj, name)) -- cgit From e618d99bc7adb47b724aebf67ea85e59c520e10d Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 6 Aug 2008 03:58:15 +0000 Subject: 57: to_cli() function no longer replaces '__' with '.'; from_cli() function no longer replaces '.' with '__'; updated unit tests --- ipalib/plugable.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 801ef7ab..01adc613 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -32,7 +32,7 @@ def to_cli(name): Command Line Interface. """ assert isinstance(name, str) - return name.replace('__', '.').replace('_', '-') + return name.replace('_', '-') def from_cli(cli_name): @@ -41,7 +41,7 @@ def from_cli(cli_name): Python identifier. """ assert isinstance(cli_name, basestring) - return cli_name.replace('-', '_').replace('.', '__') + return cli_name.replace('-', '_') def check_identifier(name): @@ -143,7 +143,7 @@ class Proxy(ReadOnly): """ if proxy_name is None: proxy_name = obj.__class__.__name__ - assert isinstance(proxy_name, str) + check_identifier(proxy_name) object.__setattr__(self, '_Proxy__obj', obj) object.__setattr__(self, 'name', proxy_name) if callable(obj): -- cgit From 2081987186a533bd6c953c8d48dfcfd193802e44 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 6 Aug 2008 14:22:38 +0000 Subject: 58: A bit of docstring cleanup in plugable.py --- ipalib/plugable.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 01adc613..7602bce3 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -18,7 +18,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ -Utility classes for registering plugins, base classes for writing plugins. +Base classes for plug-in architecture and generative API. """ import re @@ -89,8 +89,7 @@ class Plugin(object): def __repr__(self): """ - Returns a valid Python expression that could create this plugin - instance given the appropriate environment. + Returns a fully qualified representation of the class. """ return '%s.%s' % ( self.__class__.__module__, @@ -125,7 +124,7 @@ class ReadOnly(object): class Proxy(ReadOnly): """ - Used to only export certain attributes into the dynamic API. + Used to only export certain attributes into the generative API. Subclasses must list names of attributes to be proxied in the __slots__ class attribute. -- cgit From 62d2cd65f22f748ec8db3d56d1baa9a533d4f11d Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 6 Aug 2008 14:27:33 +0000 Subject: 59: Removed NameSpace.__call__ method (returned max_len) --- ipalib/plugable.py | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 7602bce3..fe4a4531 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -175,8 +175,6 @@ class NameSpace(ReadOnly): both as instance attributes and as dictionary items. """ - __max_len = None - def __init__(self, items, base=None): """ `items` should be an iterable providing the members of this @@ -231,12 +229,6 @@ class NameSpace(ReadOnly): return self.__hname[key] raise KeyError('NameSpace has no item for key %r' % key) - def __call__(self): - if self.__max_len is None: - ml = max(len(k) for k in self.__pname) - object.__setattr__(self, '_NameSpace__max_len', ml) - return self.__max_len - def __repr__(self): if self.__base is None: base = repr(self.__base) @@ -245,9 +237,6 @@ class NameSpace(ReadOnly): return '%s(*proxies, base=%s)' % (self.__class__.__name__, base) - - - class Registrar(object): def __init__(self, *allowed): """ -- cgit From 4e825ba2d9d292af17acdecb2e7f739c3355a464 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 6 Aug 2008 14:59:54 +0000 Subject: 61: Proxy now does a setattr for all callable attributes in __slots__ (and uses __getattr__ for rest --- ipalib/plugable.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index fe4a4531..4a790a37 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -131,7 +131,6 @@ class Proxy(ReadOnly): """ __slots__ = ( - '__call__', '__obj', 'name', ) @@ -145,10 +144,10 @@ class Proxy(ReadOnly): check_identifier(proxy_name) object.__setattr__(self, '_Proxy__obj', obj) object.__setattr__(self, 'name', proxy_name) - if callable(obj): - object.__setattr__(self, '__call__', obj.__call__) - #for name in self.__slots__: - # object.__setattr__(self, name, getattr(obj, name)) + for name in self.__slots__: + attr = getattr(obj, name) + if callable(attr): + object.__setattr__(self, name, attr) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.__obj) -- cgit From 495f96a73f20f0d0331099251d2472f216d05cac Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 6 Aug 2008 15:06:39 +0000 Subject: 62: NameSpace no longer takes base=base kwarg --- ipalib/plugable.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 4a790a37..ee65c516 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -174,13 +174,12 @@ class NameSpace(ReadOnly): both as instance attributes and as dictionary items. """ - def __init__(self, items, base=None): + def __init__(self, items): """ `items` should be an iterable providing the members of this NameSpace. """ object.__setattr__(self, '_NameSpace__items', tuple(items)) - object.__setattr__(self, '_NameSpace__base', base) # dict mapping Python name to item: object.__setattr__(self, '_NameSpace__pname', {}) @@ -229,11 +228,7 @@ class NameSpace(ReadOnly): raise KeyError('NameSpace has no item for key %r' % key) def __repr__(self): - if self.__base is None: - base = repr(self.__base) - else: - base = '%s.%s' % (self.__base.__module__, self.__base.__name__) - return '%s(*proxies, base=%s)' % (self.__class__.__name__, base) + return '%s(<%d proxies>)' % (self.__class__.__name__, len(self)) class Registrar(object): @@ -338,7 +333,7 @@ class API(ReadOnly): Finalize the registration, instantiate the plugins. """ for (base, plugins) in self.register: - ns = NameSpace(self.__plugin_iter(base, plugins), base=base) + ns = NameSpace(self.__plugin_iter(base, plugins)) assert not hasattr(self, base.__name__) object.__setattr__(self, base.__name__, ns) for plugin in self.__plugins: -- cgit From 0c7769473ca01facdcb1768868bfd053e726fddf Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 6 Aug 2008 20:38:07 +0000 Subject: 64: Almost finish with Proxy2, where base class is passed to __init__ and methods use @export decorator; added corresponding unit tests --- ipalib/plugable.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index ee65c516..43dd50ca 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -25,6 +25,29 @@ import re import inspect import errors +EXPORT_FLAG = 'exported' + +def export(obj): + """ + Decorator function to set the 'exported' flag to True. + + For example: + + >>> @export + >>> def my_func(): + >>> pass + >>> assert my_func.exported is True + """ + assert not hasattr(obj, EXPORT_FLAG) + setattr(obj, EXPORT_FLAG, True) + return obj + +def is_exported(obj): + """ + Returns True if `obj` as an 'exported' attribute that is True. + """ + return getattr(obj, EXPORT_FLAG, False) is True + def to_cli(name): """ @@ -168,6 +191,56 @@ class Proxy(ReadOnly): ) +class Proxy2(ReadOnly): + def __init__(self, base, target): + if not inspect.isclass(base): + raise TypeError('arg1 must be a class, got %r' % base) + if not isinstance(target, base): + raise ValueError('arg2 must be instance of arg1, got %r' % target) + object.__setattr__(self, 'base', base) + object.__setattr__(self, '_Proxy2__target', target) + object.__setattr__(self, '_Proxy2__props', dict()) + + names = [] # The names of exported attributes + # This matches implied property fget methods like '_get_user' + r = re.compile(r'^_get_([a-z][_a-z0-9]*[a-z0-9])$') + for name in dir(base): + match = r.match(name) + if name != '__call__' and name.startswith('_') and not match: + continue # Skip '_SomeClass__private', etc. + base_attr = getattr(base, name) + if is_exported(base_attr): + target_attr = getattr(target, name) + assert not hasattr(self, name), 'Cannot override %r' % name + object.__setattr__(self, name, target_attr) + names.append(name) + if match: + assert callable(target_attr), '%s must be callable' % name + key = match.group(1) + assert not hasattr(self, key), ( + '%r cannot override %r' % (name, key) + ) + self.__props[key] = target_attr + object.__setattr__(self, '_Proxy2__names', tuple(names)) + + def __call__(self, *args, **kw): + return self.__target(*args, **kw) + + def __iter__(self): + for name in self.__names: + yield name + + def __getattr__(self, name): + if name in self.__props: + return self.__props[name]() + raise AttributeError(name) + + + + + + + class NameSpace(ReadOnly): """ A read-only namespace of (key, value) pairs that can be accessed -- cgit From f13f1226b4b798fd901ece6b9a37c06ca25c3c2e Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 6 Aug 2008 21:54:56 +0000 Subject: 65: Finished simplified Proxy2 class; updated unit tests --- ipalib/plugable.py | 80 ++++++++++++++++-------------------------------------- 1 file changed, 23 insertions(+), 57 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 43dd50ca..bf0f52b4 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -25,29 +25,6 @@ import re import inspect import errors -EXPORT_FLAG = 'exported' - -def export(obj): - """ - Decorator function to set the 'exported' flag to True. - - For example: - - >>> @export - >>> def my_func(): - >>> pass - >>> assert my_func.exported is True - """ - assert not hasattr(obj, EXPORT_FLAG) - setattr(obj, EXPORT_FLAG, True) - return obj - -def is_exported(obj): - """ - Returns True if `obj` as an 'exported' attribute that is True. - """ - return getattr(obj, EXPORT_FLAG, False) is True - def to_cli(name): """ @@ -192,53 +169,42 @@ class Proxy(ReadOnly): class Proxy2(ReadOnly): - def __init__(self, base, target): + __slots__ = ( + 'base', + 'name', + '__target', + ) + def __init__(self, base, target, name_attr='name'): if not inspect.isclass(base): raise TypeError('arg1 must be a class, got %r' % base) if not isinstance(target, base): raise ValueError('arg2 must be instance of arg1, got %r' % target) object.__setattr__(self, 'base', base) object.__setattr__(self, '_Proxy2__target', target) - object.__setattr__(self, '_Proxy2__props', dict()) - - names = [] # The names of exported attributes - # This matches implied property fget methods like '_get_user' - r = re.compile(r'^_get_([a-z][_a-z0-9]*[a-z0-9])$') - for name in dir(base): - match = r.match(name) - if name != '__call__' and name.startswith('_') and not match: - continue # Skip '_SomeClass__private', etc. - base_attr = getattr(base, name) - if is_exported(base_attr): - target_attr = getattr(target, name) - assert not hasattr(self, name), 'Cannot override %r' % name - object.__setattr__(self, name, target_attr) - names.append(name) - if match: - assert callable(target_attr), '%s must be callable' % name - key = match.group(1) - assert not hasattr(self, key), ( - '%r cannot override %r' % (name, key) - ) - self.__props[key] = target_attr - object.__setattr__(self, '_Proxy2__names', tuple(names)) - def __call__(self, *args, **kw): - return self.__target(*args, **kw) + # Check base.public + assert type(self.base.public) is frozenset + + # Check name + object.__setattr__(self, 'name', getattr(target, name_attr)) + check_identifier(self.name) def __iter__(self): - for name in self.__names: + for name in sorted(self.base.public): yield name - def __getattr__(self, name): - if name in self.__props: - return self.__props[name]() - raise AttributeError(name) - - - + def __getitem__(self, key): + if key in self.base.public: + return getattr(self.__target, key) + raise KeyError('no proxy attribute %r' % key) + def __getattr__(self, name): + if name in self.base.public: + return getattr(self.__target, name) + raise AttributeError('no proxy attribute %r' % name) + def __call__(self, *args, **kw): + return self['__call__'](*args, **kw) class NameSpace(ReadOnly): -- cgit From e63453a85816ee71617c89c4933ee85a605d58a4 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 6 Aug 2008 22:59:50 +0000 Subject: 66: Added NameSpace2 (bit simpler than NameSpace, better suited to Proxy2); added corresponding unit tests --- ipalib/plugable.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index bf0f52b4..1a186b61 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -207,6 +207,57 @@ class Proxy2(ReadOnly): return self['__call__'](*args, **kw) +class NameSpace2(ReadOnly): + """ + A read-only namespace of (key, value) pairs that can be accessed + both as instance attributes and as dictionary items. + """ + + def __init__(self, proxies): + """ + NameSpace2 + """ + object.__setattr__(self, '_NameSpace2__proxies', tuple(proxies)) + object.__setattr__(self, '_NameSpace2__d', dict()) + for proxy in self.__proxies: + assert isinstance(proxy, Proxy2) + assert proxy.name not in self.__d + self.__d[proxy.name] = proxy + assert not hasattr(self, proxy.name) + object.__setattr__(self, proxy.name, proxy) + + def __iter__(self): + """ + Iterates through the proxies in this NameSpace in the same order they + were passed in the contructor. + """ + for proxy in self.__proxies: + yield proxy + + def __len__(self): + """ + Returns number of proxies in this NameSpace. + """ + return len(self.__proxies) + + def __contains__(self, key): + """ + Returns True if a proxy named `key` is in this NameSpace. + """ + return key in self.__d + + def __getitem__(self, key): + """ + Returns proxy named `key`; otherwise raises KeyError. + """ + if key in self.__d: + return self.__d[key] + raise KeyError('NameSpace has no item for key %r' % key) + + def __repr__(self): + return '%s(<%d proxies>)' % (self.__class__.__name__, len(self)) + + class NameSpace(ReadOnly): """ A read-only namespace of (key, value) pairs that can be accessed -- cgit From 03bad04e7bdf6bf02eca13e0b3af3beb587fdc3d Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 6 Aug 2008 23:22:29 +0000 Subject: 67: Deleted NameSpace, Proxy; renamed NameSpace2, Proxy2 to NameSpace, Proxy --- ipalib/plugable.py | 123 ++++------------------------------------------------- 1 file changed, 9 insertions(+), 114 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 1a186b61..769e5617 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -123,52 +123,6 @@ class ReadOnly(object): class Proxy(ReadOnly): - """ - Used to only export certain attributes into the generative API. - - Subclasses must list names of attributes to be proxied in the __slots__ - class attribute. - """ - - __slots__ = ( - '__obj', - 'name', - ) - - def __init__(self, obj, proxy_name=None): - """ - Proxy attributes on `obj`. - """ - if proxy_name is None: - proxy_name = obj.__class__.__name__ - check_identifier(proxy_name) - object.__setattr__(self, '_Proxy__obj', obj) - object.__setattr__(self, 'name', proxy_name) - for name in self.__slots__: - attr = getattr(obj, name) - if callable(attr): - object.__setattr__(self, name, attr) - - def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, self.__obj) - - def __str__(self): - return to_cli(self.name) - - def _clone(self, new_name): - return self.__class__(self.__obj, proxy_name=new_name) - - def __getattr__(self, name): - if name in self.__slots__: - return getattr(self.__obj, name) - raise AttributeError('attribute %r not in %s.__slots__' % ( - name, - self.__class__.__name__ - ) - ) - - -class Proxy2(ReadOnly): __slots__ = ( 'base', 'name', @@ -180,7 +134,7 @@ class Proxy2(ReadOnly): if not isinstance(target, base): raise ValueError('arg2 must be instance of arg1, got %r' % target) object.__setattr__(self, 'base', base) - object.__setattr__(self, '_Proxy2__target', target) + object.__setattr__(self, '_Proxy__target', target) # Check base.public assert type(self.base.public) is frozenset @@ -206,8 +160,11 @@ class Proxy2(ReadOnly): def __call__(self, *args, **kw): return self['__call__'](*args, **kw) + def _clone(self, name_attr): + return self.__class__(self.base, self.__target, name_attr) + -class NameSpace2(ReadOnly): +class NameSpace(ReadOnly): """ A read-only namespace of (key, value) pairs that can be accessed both as instance attributes and as dictionary items. @@ -215,12 +172,12 @@ class NameSpace2(ReadOnly): def __init__(self, proxies): """ - NameSpace2 + NameSpace """ - object.__setattr__(self, '_NameSpace2__proxies', tuple(proxies)) - object.__setattr__(self, '_NameSpace2__d', dict()) + object.__setattr__(self, '_NameSpace__proxies', tuple(proxies)) + object.__setattr__(self, '_NameSpace__d', dict()) for proxy in self.__proxies: - assert isinstance(proxy, Proxy2) + assert isinstance(proxy, Proxy) assert proxy.name not in self.__d self.__d[proxy.name] = proxy assert not hasattr(self, proxy.name) @@ -258,68 +215,6 @@ class NameSpace2(ReadOnly): return '%s(<%d proxies>)' % (self.__class__.__name__, len(self)) -class NameSpace(ReadOnly): - """ - A read-only namespace of (key, value) pairs that can be accessed - both as instance attributes and as dictionary items. - """ - - def __init__(self, items): - """ - `items` should be an iterable providing the members of this - NameSpace. - """ - object.__setattr__(self, '_NameSpace__items', tuple(items)) - - # dict mapping Python name to item: - object.__setattr__(self, '_NameSpace__pname', {}) - - # dict mapping human-readibly name to item: - object.__setattr__(self, '_NameSpace__hname', {}) - - for item in self.__items: - object.__setattr__(self, item.name, item) - for (key, d) in [ - (item.name, self.__pname), - (str(item), self.__hname), - ]: - assert key not in d - d[key] = item - - def __iter__(self): - """ - Iterates through the items in this NameSpace in the same order they - were passed in the contructor. - """ - for item in self.__items: - yield item - - def __len__(self): - """ - Returns number of items in this NameSpace. - """ - return len(self.__items) - - def __contains__(self, key): - """ - Returns True if an item with pname or hname `key` is in this - NameSpace. - """ - return (key in self.__pname) or (key in self.__hname) - - def __getitem__(self, key): - """ - Returns item with pname or hname `key`; otherwise raises KeyError. - """ - if key in self.__pname: - return self.__pname[key] - if key in self.__hname: - return self.__hname[key] - raise KeyError('NameSpace has no item for key %r' % key) - - def __repr__(self): - return '%s(<%d proxies>)' % (self.__class__.__name__, len(self)) - class Registrar(object): def __init__(self, *allowed): -- cgit From 7335af8a9eb4b5ab6a0884f686a51a050464320b Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 7 Aug 2008 00:14:38 +0000 Subject: 68: Ported to changes in NameSpace, Proxy; updated unit tests --- ipalib/plugable.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 769e5617..91a9143d 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -127,6 +127,7 @@ class Proxy(ReadOnly): 'base', 'name', '__target', + '__name_attr', ) def __init__(self, base, target, name_attr='name'): if not inspect.isclass(base): @@ -135,6 +136,7 @@ class Proxy(ReadOnly): raise ValueError('arg2 must be instance of arg1, got %r' % target) object.__setattr__(self, 'base', base) object.__setattr__(self, '_Proxy__target', target) + object.__setattr__(self, '_Proxy__name_attr', name_attr) # Check base.public assert type(self.base.public) is frozenset @@ -163,6 +165,14 @@ class Proxy(ReadOnly): def _clone(self, name_attr): return self.__class__(self.base, self.__target, name_attr) + def __repr__(self): + return '%s(%s, %r, %r)' % ( + self.__class__.__name__, + self.base.__name__, + self.__target, + self.__name_attr, + ) + class NameSpace(ReadOnly): """ @@ -311,27 +321,27 @@ class API(ReadOnly): keys = tuple(b.__name__ for b in allowed) object.__setattr__(self, '_API__keys', keys) object.__setattr__(self, 'register', Registrar(*allowed)) - object.__setattr__(self, '_API__plugins', []) def __call__(self): """ Finalize the registration, instantiate the plugins. """ - for (base, plugins) in self.register: - ns = NameSpace(self.__plugin_iter(base, plugins)) + d = {} + def plugin_iter(base, classes): + for cls in classes: + if cls not in d: + d[cls] = cls() + plugin = d[cls] + yield Proxy(base, plugin) + + for (base, classes) in self.register: + ns = NameSpace(plugin_iter(base, classes)) assert not hasattr(self, base.__name__) object.__setattr__(self, base.__name__, ns) - for plugin in self.__plugins: + for plugin in d.values(): plugin.finalize(self) assert plugin.api is self - def __plugin_iter(self, base, plugins): - assert issubclass(base.proxy, Proxy) - for cls in plugins: - plugin = cls() - self.__plugins.append(plugin) - yield base.proxy(plugin) - def __iter__(self): for key in self.__keys: yield key -- cgit From 19dbd5714167cca0cd48cfd73052a6d896ebc5a1 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 7 Aug 2008 00:21:50 +0000 Subject: 69: Made Proxy.base a private attribute; updated unit tests --- ipalib/plugable.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 91a9143d..9cf313fa 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -124,38 +124,41 @@ class ReadOnly(object): class Proxy(ReadOnly): __slots__ = ( - 'base', - 'name', + '__base', '__target', '__name_attr', + '__public', + 'name', ) + def __init__(self, base, target, name_attr='name'): if not inspect.isclass(base): raise TypeError('arg1 must be a class, got %r' % base) if not isinstance(target, base): raise ValueError('arg2 must be instance of arg1, got %r' % target) - object.__setattr__(self, 'base', base) + object.__setattr__(self, '_Proxy__base', base) object.__setattr__(self, '_Proxy__target', target) object.__setattr__(self, '_Proxy__name_attr', name_attr) + object.__setattr__(self, '_Proxy__public', base.public) + object.__setattr__(self, 'name', getattr(target, name_attr)) - # Check base.public - assert type(self.base.public) is frozenset + # Check __public + assert type(self.__public) is frozenset # Check name - object.__setattr__(self, 'name', getattr(target, name_attr)) check_identifier(self.name) def __iter__(self): - for name in sorted(self.base.public): + for name in sorted(self.__public): yield name def __getitem__(self, key): - if key in self.base.public: + if key in self.__public: return getattr(self.__target, key) raise KeyError('no proxy attribute %r' % key) def __getattr__(self, name): - if name in self.base.public: + if name in self.__public: return getattr(self.__target, name) raise AttributeError('no proxy attribute %r' % name) @@ -163,12 +166,12 @@ class Proxy(ReadOnly): return self['__call__'](*args, **kw) def _clone(self, name_attr): - return self.__class__(self.base, self.__target, name_attr) + return self.__class__(self.__base, self.__target, name_attr) def __repr__(self): return '%s(%s, %r, %r)' % ( self.__class__.__name__, - self.base.__name__, + self.__base.__name__, self.__target, self.__name_attr, ) -- cgit From 778a019129b919b4856fc54e2f9d58209685f159 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 7 Aug 2008 00:35:51 +0000 Subject: 70: Plugin.__repr__ now again returns 'module_name.class_name()' form; updated unit test --- ipalib/plugable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 9cf313fa..a60105a4 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -91,7 +91,7 @@ class Plugin(object): """ Returns a fully qualified representation of the class. """ - return '%s.%s' % ( + return '%s.%s()' % ( self.__class__.__module__, self.__class__.__name__ ) -- cgit From f904cb0422194dc55cf74366145b2cf20299b657 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 7 Aug 2008 00:51:34 +0000 Subject: 71: Proxy now uses base.__public__ instead of base.public; updated unit tests --- ipalib/plugable.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index a60105a4..62d228ca 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -139,7 +139,7 @@ class Proxy(ReadOnly): object.__setattr__(self, '_Proxy__base', base) object.__setattr__(self, '_Proxy__target', target) object.__setattr__(self, '_Proxy__name_attr', name_attr) - object.__setattr__(self, '_Proxy__public', base.public) + object.__setattr__(self, '_Proxy__public', base.__public__) object.__setattr__(self, 'name', getattr(target, name_attr)) # Check __public @@ -228,7 +228,6 @@ class NameSpace(ReadOnly): return '%s(<%d proxies>)' % (self.__class__.__name__, len(self)) - class Registrar(object): def __init__(self, *allowed): """ -- cgit From 8e468248155947075689e6d01c3ab90fbd9f1643 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 8 Aug 2008 17:11:29 +0000 Subject: 81: Switch from tab to 4-space indentation --- ipalib/plugable.py | 592 ++++++++++++++++++++++++++--------------------------- 1 file changed, 296 insertions(+), 296 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 62d228ca..c3eb409b 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -27,323 +27,323 @@ import errors def to_cli(name): - """ - Takes a Python identifier and transforms it into form suitable for the - Command Line Interface. - """ - assert isinstance(name, str) - return name.replace('_', '-') + """ + Takes a Python identifier and transforms it into form suitable for the + Command Line Interface. + """ + assert isinstance(name, str) + return name.replace('_', '-') def from_cli(cli_name): - """ - Takes a string from the Command Line Interface and transforms it into a - Python identifier. - """ - assert isinstance(cli_name, basestring) - return cli_name.replace('-', '_') + """ + Takes a string from the Command Line Interface and transforms it into a + Python identifier. + """ + assert isinstance(cli_name, basestring) + return cli_name.replace('-', '_') def check_identifier(name): - """ - Raises errors.NameSpaceError if `name` is not a valid Python identifier - suitable for use in a NameSpace. - """ - regex = r'^[a-z][_a-z0-9]*[a-z0-9]$' - if re.match(regex, name) is None: - raise errors.NameSpaceError(name, regex) + """ + Raises errors.NameSpaceError if `name` is not a valid Python identifier + suitable for use in a NameSpace. + """ + regex = r'^[a-z][_a-z0-9]*[a-z0-9]$' + if re.match(regex, name) is None: + raise errors.NameSpaceError(name, regex) class Plugin(object): - """ - Base class for all plugins. - """ - - __api = None - - def __get_api(self): - """ - Returns the plugable.API instance passed to Plugin.finalize(), or - or returns None if finalize() has not yet been called. - """ - return self.__api - api = property(__get_api) - - def finalize(self, api): - """ - After all the plugins are instantiated, the plugable.API calls this - method, passing itself as the only argument. This is where plugins - should check that other plugins they depend upon have actually be - loaded. - """ - assert self.__api is None, 'finalize() can only be called once' - assert api is not None, 'finalize() argument cannot be None' - self.__api = api - - def __get_name(self): - """ - Returns the class name of this instance. - """ - return self.__class__.__name__ - name = property(__get_name) - - def __repr__(self): - """ - Returns a fully qualified representation of the class. - """ - return '%s.%s()' % ( - self.__class__.__module__, - self.__class__.__name__ - ) + """ + Base class for all plugins. + """ + + __api = None + + def __get_api(self): + """ + Returns the plugable.API instance passed to Plugin.finalize(), or + or returns None if finalize() has not yet been called. + """ + return self.__api + api = property(__get_api) + + def finalize(self, api): + """ + After all the plugins are instantiated, the plugable.API calls this + method, passing itself as the only argument. This is where plugins + should check that other plugins they depend upon have actually be + loaded. + """ + assert self.__api is None, 'finalize() can only be called once' + assert api is not None, 'finalize() argument cannot be None' + self.__api = api + + def __get_name(self): + """ + Returns the class name of this instance. + """ + return self.__class__.__name__ + name = property(__get_name) + + def __repr__(self): + """ + Returns a fully qualified representation of the class. + """ + return '%s.%s()' % ( + self.__class__.__module__, + self.__class__.__name__ + ) class ReadOnly(object): - """ - Base class for classes with read-only attributes. - """ - __slots__ = tuple() - - def __setattr__(self, name, value): - """ - This raises an AttributeError anytime an attempt is made to set an - attribute. - """ - raise AttributeError('read-only: cannot set %s.%s' % - (self.__class__.__name__, name) - ) - - def __delattr__(self, name): - """ - This raises an AttributeError anytime an attempt is made to delete an - attribute. - """ - raise AttributeError('read-only: cannot del %s.%s' % - (self.__class__.__name__, name) - ) + """ + Base class for classes with read-only attributes. + """ + __slots__ = tuple() + + def __setattr__(self, name, value): + """ + This raises an AttributeError anytime an attempt is made to set an + attribute. + """ + raise AttributeError('read-only: cannot set %s.%s' % + (self.__class__.__name__, name) + ) + + def __delattr__(self, name): + """ + This raises an AttributeError anytime an attempt is made to delete an + attribute. + """ + raise AttributeError('read-only: cannot del %s.%s' % + (self.__class__.__name__, name) + ) class Proxy(ReadOnly): - __slots__ = ( - '__base', - '__target', - '__name_attr', - '__public', - 'name', - ) - - def __init__(self, base, target, name_attr='name'): - if not inspect.isclass(base): - raise TypeError('arg1 must be a class, got %r' % base) - if not isinstance(target, base): - raise ValueError('arg2 must be instance of arg1, got %r' % target) - object.__setattr__(self, '_Proxy__base', base) - object.__setattr__(self, '_Proxy__target', target) - object.__setattr__(self, '_Proxy__name_attr', name_attr) - object.__setattr__(self, '_Proxy__public', base.__public__) - object.__setattr__(self, 'name', getattr(target, name_attr)) - - # Check __public - assert type(self.__public) is frozenset - - # Check name - check_identifier(self.name) - - def __iter__(self): - for name in sorted(self.__public): - yield name - - def __getitem__(self, key): - if key in self.__public: - return getattr(self.__target, key) - raise KeyError('no proxy attribute %r' % key) - - def __getattr__(self, name): - if name in self.__public: - return getattr(self.__target, name) - raise AttributeError('no proxy attribute %r' % name) - - def __call__(self, *args, **kw): - return self['__call__'](*args, **kw) - - def _clone(self, name_attr): - return self.__class__(self.__base, self.__target, name_attr) - - def __repr__(self): - return '%s(%s, %r, %r)' % ( - self.__class__.__name__, - self.__base.__name__, - self.__target, - self.__name_attr, - ) + __slots__ = ( + '__base', + '__target', + '__name_attr', + '__public', + 'name', + ) + + def __init__(self, base, target, name_attr='name'): + if not inspect.isclass(base): + raise TypeError('arg1 must be a class, got %r' % base) + if not isinstance(target, base): + raise ValueError('arg2 must be instance of arg1, got %r' % target) + object.__setattr__(self, '_Proxy__base', base) + object.__setattr__(self, '_Proxy__target', target) + object.__setattr__(self, '_Proxy__name_attr', name_attr) + object.__setattr__(self, '_Proxy__public', base.__public__) + object.__setattr__(self, 'name', getattr(target, name_attr)) + + # Check __public + assert type(self.__public) is frozenset + + # Check name + check_identifier(self.name) + + def __iter__(self): + for name in sorted(self.__public): + yield name + + def __getitem__(self, key): + if key in self.__public: + return getattr(self.__target, key) + raise KeyError('no proxy attribute %r' % key) + + def __getattr__(self, name): + if name in self.__public: + return getattr(self.__target, name) + raise AttributeError('no proxy attribute %r' % name) + + def __call__(self, *args, **kw): + return self['__call__'](*args, **kw) + + def _clone(self, name_attr): + return self.__class__(self.__base, self.__target, name_attr) + + def __repr__(self): + return '%s(%s, %r, %r)' % ( + self.__class__.__name__, + self.__base.__name__, + self.__target, + self.__name_attr, + ) class NameSpace(ReadOnly): - """ - A read-only namespace of (key, value) pairs that can be accessed - both as instance attributes and as dictionary items. - """ - - def __init__(self, proxies): - """ - NameSpace - """ - object.__setattr__(self, '_NameSpace__proxies', tuple(proxies)) - object.__setattr__(self, '_NameSpace__d', dict()) - for proxy in self.__proxies: - assert isinstance(proxy, Proxy) - assert proxy.name not in self.__d - self.__d[proxy.name] = proxy - assert not hasattr(self, proxy.name) - object.__setattr__(self, proxy.name, proxy) - - def __iter__(self): - """ - Iterates through the proxies in this NameSpace in the same order they - were passed in the contructor. - """ - for proxy in self.__proxies: - yield proxy - - def __len__(self): - """ - Returns number of proxies in this NameSpace. - """ - return len(self.__proxies) - - def __contains__(self, key): - """ - Returns True if a proxy named `key` is in this NameSpace. - """ - return key in self.__d - - def __getitem__(self, key): - """ - Returns proxy named `key`; otherwise raises KeyError. - """ - if key in self.__d: - return self.__d[key] - raise KeyError('NameSpace has no item for key %r' % key) - - def __repr__(self): - return '%s(<%d proxies>)' % (self.__class__.__name__, len(self)) + """ + A read-only namespace of (key, value) pairs that can be accessed + both as instance attributes and as dictionary items. + """ + + def __init__(self, proxies): + """ + NameSpace + """ + object.__setattr__(self, '_NameSpace__proxies', tuple(proxies)) + object.__setattr__(self, '_NameSpace__d', dict()) + for proxy in self.__proxies: + assert isinstance(proxy, Proxy) + assert proxy.name not in self.__d + self.__d[proxy.name] = proxy + assert not hasattr(self, proxy.name) + object.__setattr__(self, proxy.name, proxy) + + def __iter__(self): + """ + Iterates through the proxies in this NameSpace in the same order they + were passed in the contructor. + """ + for proxy in self.__proxies: + yield proxy + + def __len__(self): + """ + Returns number of proxies in this NameSpace. + """ + return len(self.__proxies) + + def __contains__(self, key): + """ + Returns True if a proxy named `key` is in this NameSpace. + """ + return key in self.__d + + def __getitem__(self, key): + """ + Returns proxy named `key`; otherwise raises KeyError. + """ + if key in self.__d: + return self.__d[key] + raise KeyError('NameSpace has no item for key %r' % key) + + def __repr__(self): + return '%s(<%d proxies>)' % (self.__class__.__name__, len(self)) class Registrar(object): - def __init__(self, *allowed): - """ - `*allowed` is a list of the base classes plugins can be subclassed - from. - """ - self.__allowed = frozenset(allowed) - self.__d = {} - self.__registered = set() - assert len(self.__allowed) == len(allowed) - for base in self.__allowed: - assert inspect.isclass(base) - assert base.__name__ not in self.__d - self.__d[base.__name__] = {} - - def __findbase(self, cls): - """ - If `cls` is a subclass of a base in self.__allowed, returns that - base; otherwise raises SubclassError. - """ - assert inspect.isclass(cls) - found = False - for base in self.__allowed: - if issubclass(cls, base): - found = True - yield base - if not found: - raise errors.SubclassError(cls, self.__allowed) - - def __call__(self, cls, override=False): - """ - Register the plugin `cls`. - """ - if not inspect.isclass(cls): - raise TypeError('plugin must be a class: %r' % cls) - - # Raise DuplicateError if this exact class was already registered: - if cls in self.__registered: - raise errors.DuplicateError(cls) - - # Find the base class or raise SubclassError: - for base in self.__findbase(cls): - sub_d = self.__d[base.__name__] - - # Check override: - if cls.__name__ in sub_d: - # Must use override=True to override: - if not override: - raise errors.OverrideError(base, cls) - else: - # There was nothing already registered to override: - if override: - raise errors.MissingOverrideError(base, cls) - - # The plugin is okay, add to sub_d: - sub_d[cls.__name__] = cls - - # The plugin is okay, add to __registered: - self.__registered.add(cls) - - def __getitem__(self, item): - """ - Returns a copy of the namespace dict of the base class named `name`. - """ - if inspect.isclass(item): - if item not in self.__allowed: - raise KeyError(repr(item)) - key = item.__name__ - else: - key = item - return dict(self.__d[key]) - - def __contains__(self, item): - """ - Returns True if a base class named `name` is in this Registrar. - """ - if inspect.isclass(item): - return item in self.__allowed - return item in self.__d - - def __iter__(self): - """ - Iterates through a (base, registered_plugins) tuple for each allowed - base. - """ - for base in self.__allowed: - sub_d = self.__d[base.__name__] - yield (base, tuple(sub_d[k] for k in sorted(sub_d))) + def __init__(self, *allowed): + """ + `*allowed` is a list of the base classes plugins can be subclassed + from. + """ + self.__allowed = frozenset(allowed) + self.__d = {} + self.__registered = set() + assert len(self.__allowed) == len(allowed) + for base in self.__allowed: + assert inspect.isclass(base) + assert base.__name__ not in self.__d + self.__d[base.__name__] = {} + + def __findbase(self, cls): + """ + If `cls` is a subclass of a base in self.__allowed, returns that + base; otherwise raises SubclassError. + """ + assert inspect.isclass(cls) + found = False + for base in self.__allowed: + if issubclass(cls, base): + found = True + yield base + if not found: + raise errors.SubclassError(cls, self.__allowed) + + def __call__(self, cls, override=False): + """ + Register the plugin `cls`. + """ + if not inspect.isclass(cls): + raise TypeError('plugin must be a class: %r' % cls) + + # Raise DuplicateError if this exact class was already registered: + if cls in self.__registered: + raise errors.DuplicateError(cls) + + # Find the base class or raise SubclassError: + for base in self.__findbase(cls): + sub_d = self.__d[base.__name__] + + # Check override: + if cls.__name__ in sub_d: + # Must use override=True to override: + if not override: + raise errors.OverrideError(base, cls) + else: + # There was nothing already registered to override: + if override: + raise errors.MissingOverrideError(base, cls) + + # The plugin is okay, add to sub_d: + sub_d[cls.__name__] = cls + + # The plugin is okay, add to __registered: + self.__registered.add(cls) + + def __getitem__(self, item): + """ + Returns a copy of the namespace dict of the base class named `name`. + """ + if inspect.isclass(item): + if item not in self.__allowed: + raise KeyError(repr(item)) + key = item.__name__ + else: + key = item + return dict(self.__d[key]) + + def __contains__(self, item): + """ + Returns True if a base class named `name` is in this Registrar. + """ + if inspect.isclass(item): + return item in self.__allowed + return item in self.__d + + def __iter__(self): + """ + Iterates through a (base, registered_plugins) tuple for each allowed + base. + """ + for base in self.__allowed: + sub_d = self.__d[base.__name__] + yield (base, tuple(sub_d[k] for k in sorted(sub_d))) class API(ReadOnly): - def __init__(self, *allowed): - keys = tuple(b.__name__ for b in allowed) - object.__setattr__(self, '_API__keys', keys) - object.__setattr__(self, 'register', Registrar(*allowed)) - - def __call__(self): - """ - Finalize the registration, instantiate the plugins. - """ - d = {} - def plugin_iter(base, classes): - for cls in classes: - if cls not in d: - d[cls] = cls() - plugin = d[cls] - yield Proxy(base, plugin) - - for (base, classes) in self.register: - ns = NameSpace(plugin_iter(base, classes)) - assert not hasattr(self, base.__name__) - object.__setattr__(self, base.__name__, ns) - for plugin in d.values(): - plugin.finalize(self) - assert plugin.api is self - - def __iter__(self): - for key in self.__keys: - yield key + def __init__(self, *allowed): + keys = tuple(b.__name__ for b in allowed) + object.__setattr__(self, '_API__keys', keys) + object.__setattr__(self, 'register', Registrar(*allowed)) + + def __call__(self): + """ + Finalize the registration, instantiate the plugins. + """ + d = {} + def plugin_iter(base, classes): + for cls in classes: + if cls not in d: + d[cls] = cls() + plugin = d[cls] + yield Proxy(base, plugin) + + for (base, classes) in self.register: + ns = NameSpace(plugin_iter(base, classes)) + assert not hasattr(self, base.__name__) + object.__setattr__(self, base.__name__, ns) + for plugin in d.values(): + plugin.finalize(self) + assert plugin.api is self + + def __iter__(self): + for key in self.__keys: + yield key -- cgit From d171dc90111cad91884c3a1b3afdb8b16b7c289e Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 8 Aug 2008 19:53:45 +0000 Subject: 82: Cleaned up unit tests for public.option; added some doodles in plugable.Base --- ipalib/plugable.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index c3eb409b..4032b574 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -54,6 +54,23 @@ def check_identifier(name): raise errors.NameSpaceError(name, regex) +class Abstract(object): + __public__ = frozenset() + + @classmethod + def implements(cls, arg): + assert type(cls.__public__) is frozenset + if isinstance(arg, str): + return arg in cls.__public__ + if type(getattr(arg, '__public__', None)) is frozenset: + return cls.__public__.issuperset(arg.__public__) + if type(arg) is frozenset: + return cls.__public__.issuperset(arg) + raise TypeError( + "must be str, frozenset, or have frozenset '__public__' attribute" + ) + + class Plugin(object): """ Base class for all plugins. -- cgit From 6dc60a18c7929e3a1f0fee6aeb06913cc8921ccc Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 8 Aug 2008 20:55:08 +0000 Subject: 84: Renamed Proxy.__public to Proxy.__public__ so it works with Abstract.implements() --- ipalib/plugable.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 4032b574..ad3d5872 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -144,8 +144,8 @@ class Proxy(ReadOnly): '__base', '__target', '__name_attr', - '__public', 'name', + '__public__', ) def __init__(self, base, target, name_attr='name'): @@ -156,26 +156,26 @@ class Proxy(ReadOnly): object.__setattr__(self, '_Proxy__base', base) object.__setattr__(self, '_Proxy__target', target) object.__setattr__(self, '_Proxy__name_attr', name_attr) - object.__setattr__(self, '_Proxy__public', base.__public__) + object.__setattr__(self, '__public__', base.__public__) object.__setattr__(self, 'name', getattr(target, name_attr)) - # Check __public - assert type(self.__public) is frozenset + # Check __public__ + assert type(self.__public__) is frozenset # Check name check_identifier(self.name) def __iter__(self): - for name in sorted(self.__public): + for name in sorted(self.__public__): yield name def __getitem__(self, key): - if key in self.__public: + if key in self.__public__: return getattr(self.__target, key) raise KeyError('no proxy attribute %r' % key) def __getattr__(self, name): - if name in self.__public: + if name in self.__public__: return getattr(self.__target, name) raise AttributeError('no proxy attribute %r' % name) -- cgit From 58a3b1d0915f32326e9387bc4978c53a16e5f217 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 8 Aug 2008 21:28:56 +0000 Subject: 85: Added ReadOnly._lock() method to make class easier to use; updated subclasses and unit tests --- ipalib/plugable.py | 57 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 25 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index ad3d5872..e328f0c3 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -118,25 +118,33 @@ class ReadOnly(object): """ Base class for classes with read-only attributes. """ - __slots__ = tuple() + __locked = False + + def _lock(self): + assert self.__locked is False + self.__locked = True def __setattr__(self, name, value): """ - This raises an AttributeError anytime an attempt is made to set an - attribute. + Raises an AttributeError if ReadOnly._lock() has already been called; + otherwise calls object.__setattr__() """ - raise AttributeError('read-only: cannot set %s.%s' % - (self.__class__.__name__, name) - ) + if self.__locked: + raise AttributeError('read-only: cannot set %s.%s' % + (self.__class__.__name__, name) + ) + return object.__setattr__(self, name, value) def __delattr__(self, name): """ - This raises an AttributeError anytime an attempt is made to delete an - attribute. + Raises an AttributeError if ReadOnly._lock() has already been called; + otherwise calls object.__delattr__() """ - raise AttributeError('read-only: cannot del %s.%s' % - (self.__class__.__name__, name) - ) + if self.__locked: + raise AttributeError('read-only: cannot del %s.%s' % + (self.__class__.__name__, name) + ) + return object.__delattr__(self, name) class Proxy(ReadOnly): @@ -153,17 +161,15 @@ class Proxy(ReadOnly): raise TypeError('arg1 must be a class, got %r' % base) if not isinstance(target, base): raise ValueError('arg2 must be instance of arg1, got %r' % target) - object.__setattr__(self, '_Proxy__base', base) - object.__setattr__(self, '_Proxy__target', target) - object.__setattr__(self, '_Proxy__name_attr', name_attr) - object.__setattr__(self, '__public__', base.__public__) - object.__setattr__(self, 'name', getattr(target, name_attr)) - - # Check __public__ + self.__base = base + self.__target = target + self.__name_attr = name_attr + self.name = getattr(target, name_attr) + self.__public__ = base.__public__ assert type(self.__public__) is frozenset - - # Check name check_identifier(self.name) + self._lock() + def __iter__(self): for name in sorted(self.__public__): @@ -204,14 +210,15 @@ class NameSpace(ReadOnly): """ NameSpace """ - object.__setattr__(self, '_NameSpace__proxies', tuple(proxies)) - object.__setattr__(self, '_NameSpace__d', dict()) + self.__proxies = tuple(proxies) + self.__d = dict() for proxy in self.__proxies: assert isinstance(proxy, Proxy) assert proxy.name not in self.__d self.__d[proxy.name] = proxy assert not hasattr(self, proxy.name) - object.__setattr__(self, proxy.name, proxy) + setattr(self, proxy.name, proxy) + self._lock() def __iter__(self): """ @@ -338,8 +345,8 @@ class Registrar(object): class API(ReadOnly): def __init__(self, *allowed): keys = tuple(b.__name__ for b in allowed) - object.__setattr__(self, '_API__keys', keys) - object.__setattr__(self, 'register', Registrar(*allowed)) + self.register = Registrar(*allowed) + self._lock() def __call__(self): """ -- cgit From fdfa827a36df87fd6b228fc1560576e268413104 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 8 Aug 2008 21:40:03 +0000 Subject: 86: Actually change *all* tab indentation to 4-space: 'sed s/\t/ /g' --- ipalib/plugable.py | 410 ++++++++++++++++++++++++++--------------------------- 1 file changed, 205 insertions(+), 205 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index e328f0c3..0a6a0caa 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -51,7 +51,7 @@ def check_identifier(name): """ regex = r'^[a-z][_a-z0-9]*[a-z0-9]$' if re.match(regex, name) is None: - raise errors.NameSpaceError(name, regex) + raise errors.NameSpaceError(name, regex) class Abstract(object): @@ -79,39 +79,39 @@ class Plugin(object): __api = None def __get_api(self): - """ - Returns the plugable.API instance passed to Plugin.finalize(), or - or returns None if finalize() has not yet been called. - """ - return self.__api + """ + Returns the plugable.API instance passed to Plugin.finalize(), or + or returns None if finalize() has not yet been called. + """ + return self.__api api = property(__get_api) def finalize(self, api): - """ - After all the plugins are instantiated, the plugable.API calls this - method, passing itself as the only argument. This is where plugins - should check that other plugins they depend upon have actually be - loaded. - """ - assert self.__api is None, 'finalize() can only be called once' - assert api is not None, 'finalize() argument cannot be None' - self.__api = api + """ + After all the plugins are instantiated, the plugable.API calls this + method, passing itself as the only argument. This is where plugins + should check that other plugins they depend upon have actually be + loaded. + """ + assert self.__api is None, 'finalize() can only be called once' + assert api is not None, 'finalize() argument cannot be None' + self.__api = api def __get_name(self): - """ - Returns the class name of this instance. - """ - return self.__class__.__name__ + """ + Returns the class name of this instance. + """ + return self.__class__.__name__ name = property(__get_name) def __repr__(self): - """ - Returns a fully qualified representation of the class. - """ - return '%s.%s()' % ( - self.__class__.__module__, - self.__class__.__name__ - ) + """ + Returns a fully qualified representation of the class. + """ + return '%s.%s()' % ( + self.__class__.__module__, + self.__class__.__name__ + ) class ReadOnly(object): @@ -125,79 +125,79 @@ class ReadOnly(object): self.__locked = True def __setattr__(self, name, value): - """ - Raises an AttributeError if ReadOnly._lock() has already been called; - otherwise calls object.__setattr__() - """ - if self.__locked: - raise AttributeError('read-only: cannot set %s.%s' % - (self.__class__.__name__, name) - ) - return object.__setattr__(self, name, value) + """ + Raises an AttributeError if ReadOnly._lock() has already been called; + otherwise calls object.__setattr__() + """ + if self.__locked: + raise AttributeError('read-only: cannot set %s.%s' % + (self.__class__.__name__, name) + ) + return object.__setattr__(self, name, value) def __delattr__(self, name): - """ - Raises an AttributeError if ReadOnly._lock() has already been called; - otherwise calls object.__delattr__() - """ - if self.__locked: - raise AttributeError('read-only: cannot del %s.%s' % - (self.__class__.__name__, name) - ) + """ + Raises an AttributeError if ReadOnly._lock() has already been called; + otherwise calls object.__delattr__() + """ + if self.__locked: + raise AttributeError('read-only: cannot del %s.%s' % + (self.__class__.__name__, name) + ) return object.__delattr__(self, name) class Proxy(ReadOnly): __slots__ = ( - '__base', - '__target', - '__name_attr', - 'name', - '__public__', + '__base', + '__target', + '__name_attr', + 'name', + '__public__', ) def __init__(self, base, target, name_attr='name'): - if not inspect.isclass(base): - raise TypeError('arg1 must be a class, got %r' % base) - if not isinstance(target, base): - raise ValueError('arg2 must be instance of arg1, got %r' % target) + if not inspect.isclass(base): + raise TypeError('arg1 must be a class, got %r' % base) + if not isinstance(target, base): + raise ValueError('arg2 must be instance of arg1, got %r' % target) self.__base = base - self.__target = target - self.__name_attr = name_attr - self.name = getattr(target, name_attr) - self.__public__ = base.__public__ - assert type(self.__public__) is frozenset - check_identifier(self.name) - self._lock() + self.__target = target + self.__name_attr = name_attr + self.name = getattr(target, name_attr) + self.__public__ = base.__public__ + assert type(self.__public__) is frozenset + check_identifier(self.name) + self._lock() def __iter__(self): - for name in sorted(self.__public__): - yield name + for name in sorted(self.__public__): + yield name def __getitem__(self, key): - if key in self.__public__: - return getattr(self.__target, key) - raise KeyError('no proxy attribute %r' % key) + if key in self.__public__: + return getattr(self.__target, key) + raise KeyError('no proxy attribute %r' % key) def __getattr__(self, name): - if name in self.__public__: - return getattr(self.__target, name) - raise AttributeError('no proxy attribute %r' % name) + if name in self.__public__: + return getattr(self.__target, name) + raise AttributeError('no proxy attribute %r' % name) def __call__(self, *args, **kw): - return self['__call__'](*args, **kw) + return self['__call__'](*args, **kw) def _clone(self, name_attr): - return self.__class__(self.__base, self.__target, name_attr) + return self.__class__(self.__base, self.__target, name_attr) def __repr__(self): - return '%s(%s, %r, %r)' % ( - self.__class__.__name__, - self.__base.__name__, - self.__target, - self.__name_attr, - ) + return '%s(%s, %r, %r)' % ( + self.__class__.__name__, + self.__base.__name__, + self.__target, + self.__name_attr, + ) class NameSpace(ReadOnly): @@ -207,167 +207,167 @@ class NameSpace(ReadOnly): """ def __init__(self, proxies): - """ - NameSpace - """ - self.__proxies = tuple(proxies) - self.__d = dict() - for proxy in self.__proxies: - assert isinstance(proxy, Proxy) - assert proxy.name not in self.__d - self.__d[proxy.name] = proxy - assert not hasattr(self, proxy.name) - setattr(self, proxy.name, proxy) - self._lock() + """ + NameSpace + """ + self.__proxies = tuple(proxies) + self.__d = dict() + for proxy in self.__proxies: + assert isinstance(proxy, Proxy) + assert proxy.name not in self.__d + self.__d[proxy.name] = proxy + assert not hasattr(self, proxy.name) + setattr(self, proxy.name, proxy) + self._lock() def __iter__(self): - """ - Iterates through the proxies in this NameSpace in the same order they - were passed in the contructor. - """ - for proxy in self.__proxies: - yield proxy + """ + Iterates through the proxies in this NameSpace in the same order they + were passed in the contructor. + """ + for proxy in self.__proxies: + yield proxy def __len__(self): - """ - Returns number of proxies in this NameSpace. - """ - return len(self.__proxies) + """ + Returns number of proxies in this NameSpace. + """ + return len(self.__proxies) def __contains__(self, key): - """ - Returns True if a proxy named `key` is in this NameSpace. - """ - return key in self.__d + """ + Returns True if a proxy named `key` is in this NameSpace. + """ + return key in self.__d def __getitem__(self, key): - """ - Returns proxy named `key`; otherwise raises KeyError. - """ - if key in self.__d: - return self.__d[key] - raise KeyError('NameSpace has no item for key %r' % key) + """ + Returns proxy named `key`; otherwise raises KeyError. + """ + if key in self.__d: + return self.__d[key] + raise KeyError('NameSpace has no item for key %r' % key) def __repr__(self): - return '%s(<%d proxies>)' % (self.__class__.__name__, len(self)) + return '%s(<%d proxies>)' % (self.__class__.__name__, len(self)) class Registrar(object): def __init__(self, *allowed): - """ - `*allowed` is a list of the base classes plugins can be subclassed - from. - """ - self.__allowed = frozenset(allowed) - self.__d = {} - self.__registered = set() - assert len(self.__allowed) == len(allowed) - for base in self.__allowed: - assert inspect.isclass(base) - assert base.__name__ not in self.__d - self.__d[base.__name__] = {} + """ + `*allowed` is a list of the base classes plugins can be subclassed + from. + """ + self.__allowed = frozenset(allowed) + self.__d = {} + self.__registered = set() + assert len(self.__allowed) == len(allowed) + for base in self.__allowed: + assert inspect.isclass(base) + assert base.__name__ not in self.__d + self.__d[base.__name__] = {} def __findbase(self, cls): - """ - If `cls` is a subclass of a base in self.__allowed, returns that - base; otherwise raises SubclassError. - """ - assert inspect.isclass(cls) - found = False - for base in self.__allowed: - if issubclass(cls, base): - found = True - yield base - if not found: - raise errors.SubclassError(cls, self.__allowed) + """ + If `cls` is a subclass of a base in self.__allowed, returns that + base; otherwise raises SubclassError. + """ + assert inspect.isclass(cls) + found = False + for base in self.__allowed: + if issubclass(cls, base): + found = True + yield base + if not found: + raise errors.SubclassError(cls, self.__allowed) def __call__(self, cls, override=False): - """ - Register the plugin `cls`. - """ - if not inspect.isclass(cls): - raise TypeError('plugin must be a class: %r' % cls) - - # Raise DuplicateError if this exact class was already registered: - if cls in self.__registered: - raise errors.DuplicateError(cls) - - # Find the base class or raise SubclassError: - for base in self.__findbase(cls): - sub_d = self.__d[base.__name__] - - # Check override: - if cls.__name__ in sub_d: - # Must use override=True to override: - if not override: - raise errors.OverrideError(base, cls) - else: - # There was nothing already registered to override: - if override: - raise errors.MissingOverrideError(base, cls) - - # The plugin is okay, add to sub_d: - sub_d[cls.__name__] = cls - - # The plugin is okay, add to __registered: - self.__registered.add(cls) + """ + Register the plugin `cls`. + """ + if not inspect.isclass(cls): + raise TypeError('plugin must be a class: %r' % cls) + + # Raise DuplicateError if this exact class was already registered: + if cls in self.__registered: + raise errors.DuplicateError(cls) + + # Find the base class or raise SubclassError: + for base in self.__findbase(cls): + sub_d = self.__d[base.__name__] + + # Check override: + if cls.__name__ in sub_d: + # Must use override=True to override: + if not override: + raise errors.OverrideError(base, cls) + else: + # There was nothing already registered to override: + if override: + raise errors.MissingOverrideError(base, cls) + + # The plugin is okay, add to sub_d: + sub_d[cls.__name__] = cls + + # The plugin is okay, add to __registered: + self.__registered.add(cls) def __getitem__(self, item): - """ - Returns a copy of the namespace dict of the base class named `name`. - """ - if inspect.isclass(item): - if item not in self.__allowed: - raise KeyError(repr(item)) - key = item.__name__ - else: - key = item - return dict(self.__d[key]) + """ + Returns a copy of the namespace dict of the base class named `name`. + """ + if inspect.isclass(item): + if item not in self.__allowed: + raise KeyError(repr(item)) + key = item.__name__ + else: + key = item + return dict(self.__d[key]) def __contains__(self, item): - """ - Returns True if a base class named `name` is in this Registrar. - """ - if inspect.isclass(item): - return item in self.__allowed - return item in self.__d + """ + Returns True if a base class named `name` is in this Registrar. + """ + if inspect.isclass(item): + return item in self.__allowed + return item in self.__d def __iter__(self): - """ - Iterates through a (base, registered_plugins) tuple for each allowed - base. - """ - for base in self.__allowed: - sub_d = self.__d[base.__name__] - yield (base, tuple(sub_d[k] for k in sorted(sub_d))) + """ + Iterates through a (base, registered_plugins) tuple for each allowed + base. + """ + for base in self.__allowed: + sub_d = self.__d[base.__name__] + yield (base, tuple(sub_d[k] for k in sorted(sub_d))) class API(ReadOnly): def __init__(self, *allowed): - keys = tuple(b.__name__ for b in allowed) + keys = tuple(b.__name__ for b in allowed) self.register = Registrar(*allowed) self._lock() def __call__(self): - """ - Finalize the registration, instantiate the plugins. - """ - d = {} - def plugin_iter(base, classes): - for cls in classes: - if cls not in d: - d[cls] = cls() - plugin = d[cls] - yield Proxy(base, plugin) - - for (base, classes) in self.register: - ns = NameSpace(plugin_iter(base, classes)) - assert not hasattr(self, base.__name__) - object.__setattr__(self, base.__name__, ns) - for plugin in d.values(): - plugin.finalize(self) - assert plugin.api is self + """ + Finalize the registration, instantiate the plugins. + """ + d = {} + def plugin_iter(base, classes): + for cls in classes: + if cls not in d: + d[cls] = cls() + plugin = d[cls] + yield Proxy(base, plugin) + + for (base, classes) in self.register: + ns = NameSpace(plugin_iter(base, classes)) + assert not hasattr(self, base.__name__) + object.__setattr__(self, base.__name__, ns) + for plugin in d.values(): + plugin.finalize(self) + assert plugin.api is self def __iter__(self): - for key in self.__keys: - yield key + for key in self.__keys: + yield key -- cgit From 3fe13d5945df224643374da477f68e04d4f443e5 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 8 Aug 2008 21:46:23 +0000 Subject: 87: Moved to_cli(), from_cli() functions from plugable.py into new cli.py file; moved corresponding unit tests into new test_cli.py file --- ipalib/plugable.py | 18 ------------------ 1 file changed, 18 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 0a6a0caa..ecbd5855 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -26,24 +26,6 @@ import inspect import errors -def to_cli(name): - """ - Takes a Python identifier and transforms it into form suitable for the - Command Line Interface. - """ - assert isinstance(name, str) - return name.replace('_', '-') - - -def from_cli(cli_name): - """ - Takes a string from the Command Line Interface and transforms it into a - Python identifier. - """ - assert isinstance(cli_name, basestring) - return cli_name.replace('-', '_') - - def check_identifier(name): """ Raises errors.NameSpaceError if `name` is not a valid Python identifier -- cgit From 1744723d11b2fbc93f43699f79df40d5d0b9305d Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 8 Aug 2008 21:49:09 +0000 Subject: 88: Renamed ReadOnly._lock() to ReadOnly.__lock__(); updated subclasses and unit tests --- ipalib/plugable.py | 69 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 33 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index ecbd5855..092e3bdd 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -36,6 +36,39 @@ def check_identifier(name): raise errors.NameSpaceError(name, regex) +class ReadOnly(object): + """ + Base class for classes with read-only attributes. + """ + __locked = False + + def __lock__(self): + assert self.__locked is False + self.__locked = True + + def __setattr__(self, name, value): + """ + Raises an AttributeError if ReadOnly.__lock__() has already been called; + otherwise calls object.__setattr__() + """ + if self.__locked: + raise AttributeError('read-only: cannot set %s.%s' % + (self.__class__.__name__, name) + ) + return object.__setattr__(self, name, value) + + def __delattr__(self, name): + """ + Raises an AttributeError if ReadOnly.__lock__() has already been called; + otherwise calls object.__delattr__() + """ + if self.__locked: + raise AttributeError('read-only: cannot del %s.%s' % + (self.__class__.__name__, name) + ) + return object.__delattr__(self, name) + + class Abstract(object): __public__ = frozenset() @@ -96,37 +129,7 @@ class Plugin(object): ) -class ReadOnly(object): - """ - Base class for classes with read-only attributes. - """ - __locked = False - - def _lock(self): - assert self.__locked is False - self.__locked = True - - def __setattr__(self, name, value): - """ - Raises an AttributeError if ReadOnly._lock() has already been called; - otherwise calls object.__setattr__() - """ - if self.__locked: - raise AttributeError('read-only: cannot set %s.%s' % - (self.__class__.__name__, name) - ) - return object.__setattr__(self, name, value) - def __delattr__(self, name): - """ - Raises an AttributeError if ReadOnly._lock() has already been called; - otherwise calls object.__delattr__() - """ - if self.__locked: - raise AttributeError('read-only: cannot del %s.%s' % - (self.__class__.__name__, name) - ) - return object.__delattr__(self, name) class Proxy(ReadOnly): @@ -150,7 +153,7 @@ class Proxy(ReadOnly): self.__public__ = base.__public__ assert type(self.__public__) is frozenset check_identifier(self.name) - self._lock() + self.__lock__() def __iter__(self): @@ -200,7 +203,7 @@ class NameSpace(ReadOnly): self.__d[proxy.name] = proxy assert not hasattr(self, proxy.name) setattr(self, proxy.name, proxy) - self._lock() + self.__lock__() def __iter__(self): """ @@ -328,7 +331,7 @@ class API(ReadOnly): def __init__(self, *allowed): keys = tuple(b.__name__ for b in allowed) self.register = Registrar(*allowed) - self._lock() + self.__lock__() def __call__(self): """ -- cgit From 5a1223e94367c4370a94f271ef7e087dbdb02615 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 8 Aug 2008 22:45:09 +0000 Subject: 90: Renamed plugable.Abstract to ProxyTarget, which now subclasses from ReadOnly; updated unit tests --- ipalib/plugable.py | 104 ++++++++++++++++++++++++++++------------------------- 1 file changed, 55 insertions(+), 49 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 092e3bdd..3f53fd4a 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -43,7 +43,11 @@ class ReadOnly(object): __locked = False def __lock__(self): - assert self.__locked is False + """ + Puts this instance into a read-only state, after which attempting to + set or delete an attribute will raise AttributeError. + """ + assert self.__locked is False, '__lock__() can only be called once' self.__locked = True def __setattr__(self, name, value): @@ -69,7 +73,7 @@ class ReadOnly(object): return object.__delattr__(self, name) -class Abstract(object): +class ProxyTarget(ReadOnly): __public__ = frozenset() @classmethod @@ -86,52 +90,6 @@ class Abstract(object): ) -class Plugin(object): - """ - Base class for all plugins. - """ - - __api = None - - def __get_api(self): - """ - Returns the plugable.API instance passed to Plugin.finalize(), or - or returns None if finalize() has not yet been called. - """ - return self.__api - api = property(__get_api) - - def finalize(self, api): - """ - After all the plugins are instantiated, the plugable.API calls this - method, passing itself as the only argument. This is where plugins - should check that other plugins they depend upon have actually be - loaded. - """ - assert self.__api is None, 'finalize() can only be called once' - assert api is not None, 'finalize() argument cannot be None' - self.__api = api - - def __get_name(self): - """ - Returns the class name of this instance. - """ - return self.__class__.__name__ - name = property(__get_name) - - def __repr__(self): - """ - Returns a fully qualified representation of the class. - """ - return '%s.%s()' % ( - self.__class__.__module__, - self.__class__.__name__ - ) - - - - - class Proxy(ReadOnly): __slots__ = ( '__base', @@ -155,7 +113,6 @@ class Proxy(ReadOnly): check_identifier(self.name) self.__lock__() - def __iter__(self): for name in sorted(self.__public__): yield name @@ -185,6 +142,55 @@ class Proxy(ReadOnly): ) +class Plugin(object): + """ + Base class for all plugins. + """ + + __api = None + + def __get_api(self): + """ + Returns the plugable.API instance passed to Plugin.finalize(), or + or returns None if finalize() has not yet been called. + """ + return self.__api + api = property(__get_api) + + def finalize(self, api): + """ + After all the plugins are instantiated, the plugable.API calls this + method, passing itself as the only argument. This is where plugins + should check that other plugins they depend upon have actually be + loaded. + """ + assert self.__api is None, 'finalize() can only be called once' + assert api is not None, 'finalize() argument cannot be None' + self.__api = api + + def __get_name(self): + """ + Returns the class name of this instance. + """ + return self.__class__.__name__ + name = property(__get_name) + + def __repr__(self): + """ + Returns a fully qualified representation of the class. + """ + return '%s.%s()' % ( + self.__class__.__module__, + self.__class__.__name__ + ) + + + + + + + + class NameSpace(ReadOnly): """ A read-only namespace of (key, value) pairs that can be accessed -- cgit From e3811f3f45adf977ade6468221368efb7f92294f Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 8 Aug 2008 23:07:22 +0000 Subject: 91: Fleshed out docstrings in plugable.Proxy --- ipalib/plugable.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 3f53fd4a..581f377b 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -91,6 +91,15 @@ class ProxyTarget(ReadOnly): class Proxy(ReadOnly): + """ + Allows access to only certain attributes on its target object (a + ProxyTarget). + + Think of a proxy as an argreement that "I will have at most these + attributes". This is different from (although similar to) an interface, + which can be thought of as an agreement that "I will have at least these + attributes". + """ __slots__ = ( '__base', '__target', @@ -100,6 +109,13 @@ class Proxy(ReadOnly): ) def __init__(self, base, target, name_attr='name'): + """ + `base` - the class defining the __public__ frozenset of attributes to + proxy + `target` - the target of the proxy (must be instance of `base`) + `name_attr` - the name of the str attribute on `target` to assign + to Proxy.name + """ if not inspect.isclass(base): raise TypeError('arg1 must be a class, got %r' % base) if not isinstance(target, base): @@ -114,15 +130,26 @@ class Proxy(ReadOnly): self.__lock__() def __iter__(self): + """ + Iterates though the attribute names this proxy is allowing access to. + """ for name in sorted(self.__public__): yield name def __getitem__(self, key): + """ + If this proxy allowes access to an attribute named `key`, return that + attrribute. + """ if key in self.__public__: return getattr(self.__target, key) raise KeyError('no proxy attribute %r' % key) def __getattr__(self, name): + """ + If this proxy allowes access to an attribute named `name`, return that + attrribute. + """ if name in self.__public__: return getattr(self.__target, name) raise AttributeError('no proxy attribute %r' % name) -- cgit From 45201e31c1b7be7cb770d2e864c307c95e743751 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 8 Aug 2008 23:26:17 +0000 Subject: 92: Added ProxyTarget.name property; added corresponding unit tests --- ipalib/plugable.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 581f377b..c5ceeffe 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -76,6 +76,13 @@ class ReadOnly(object): class ProxyTarget(ReadOnly): __public__ = frozenset() + def __get_name(self): + """ + Convenience property to return the class name. + """ + return self.__class__.__name__ + name = property(__get_name) + @classmethod def implements(cls, arg): assert type(cls.__public__) is frozenset -- cgit From cc5b0174949a4769876a892b210a9faa9683d81e Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 9 Aug 2008 01:06:42 +0000 Subject: 93: Added Proxy.implements() method; addeded corresponding unit tests --- ipalib/plugable.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index c5ceeffe..1e1f4a90 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -102,7 +102,7 @@ class Proxy(ReadOnly): Allows access to only certain attributes on its target object (a ProxyTarget). - Think of a proxy as an argreement that "I will have at most these + Think of a proxy as an agreement that "I will have at most these attributes". This is different from (although similar to) an interface, which can be thought of as an agreement that "I will have at least these attributes". @@ -136,9 +136,13 @@ class Proxy(ReadOnly): check_identifier(self.name) self.__lock__() + def implements(self, arg): + return self.__base.implements(arg) + def __iter__(self): """ - Iterates though the attribute names this proxy is allowing access to. + Iterates (in ascending order) though the attribute names this proxy is + allowing access to. """ for name in sorted(self.__public__): yield name @@ -176,6 +180,7 @@ class Proxy(ReadOnly): ) + class Plugin(object): """ Base class for all plugins. -- cgit From 3495c67d57868a02bafe6f1935d4846cd5615bf5 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 9 Aug 2008 01:46:12 +0000 Subject: 94: Renamed Proxy._clone() method to Proxy.__clone__(); updated unit tests --- ipalib/plugable.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 1e1f4a90..c5ec08ec 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -139,6 +139,14 @@ class Proxy(ReadOnly): def implements(self, arg): return self.__base.implements(arg) + def __clone__(self, name_attr): + """ + Returns a Proxy instance identical to this one except the proxy name + might be derived from a different attribute on the target. The same + base and target will be used. + """ + return self.__class__(self.__base, self.__target, name_attr) + def __iter__(self): """ Iterates (in ascending order) though the attribute names this proxy is @@ -168,8 +176,6 @@ class Proxy(ReadOnly): def __call__(self, *args, **kw): return self['__call__'](*args, **kw) - def _clone(self, name_attr): - return self.__class__(self.__base, self.__target, name_attr) def __repr__(self): return '%s(%s, %r, %r)' % ( -- cgit From 72f3132d2b98a44881ae7001d0001602a66bf8b5 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 9 Aug 2008 04:35:06 +0000 Subject: 95: Improved docstrings for ReadOnly class; added ReadOnly.__islocked__() method; added corresponding unit tests --- ipalib/plugable.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index c5ec08ec..4ce4a9ba 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -39,6 +39,39 @@ def check_identifier(name): class ReadOnly(object): """ Base class for classes with read-only attributes. + + Be forewarned that Python does not offer true read-only user defined + classes. In particular, do not rely upon the read-only-ness of this + class for security purposes. + + The point of this class is not to make it impossible to set or delete + attributes, but do make it impossible to accidentally do so. The plugins + are not thread-safe: in the server, they are loaded once and the same + instances will be used to process many requests. Therefore, it is + imperative that they not set any instance attributes after they have + been initialized. This base class enforces that policy. + + For example: + + >>> class givenName(ReadOnly): + >>> def __init__(self): + >>> self.whatever = 'some value' # Hasn't been locked yet + >>> self.__lock__() + >>> + >>> def finalize(self, api): + >>> # After the instance has been locked, attributes can still be + >>> # set, but only in a round-about, unconventional way: + >>> object.__setattr__(self, 'api', api) + >>> + >>> def normalize(self, value): + >>> # After the instance has been locked, trying to set an + >>> # attribute in the normal way will raise AttributeError. + >>> self.value = value # Not thread safe! + >>> return self.actually_normalize() + >>> + >>> def actually_normalize(self): + >>> # Again, this is not thread safe: + >>> return unicode(self.value).strip() """ __locked = False @@ -50,6 +83,12 @@ class ReadOnly(object): assert self.__locked is False, '__lock__() can only be called once' self.__locked = True + def __islocked__(self): + """ + Returns True if this instance is locked, False otherwise. + """ + return self.__locked + def __setattr__(self, name, value): """ Raises an AttributeError if ReadOnly.__lock__() has already been called; -- cgit From 409f688ef5ed453708df29913036593f7fa51e41 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 9 Aug 2008 04:37:37 +0000 Subject: 96: Fixed typo is ReadOnly docstring --- ipalib/plugable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 4ce4a9ba..d8578270 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -45,7 +45,7 @@ class ReadOnly(object): class for security purposes. The point of this class is not to make it impossible to set or delete - attributes, but do make it impossible to accidentally do so. The plugins + attributes, but to make it impossible to accidentally do so. The plugins are not thread-safe: in the server, they are loaded once and the same instances will be used to process many requests. Therefore, it is imperative that they not set any instance attributes after they have -- cgit From 9712eae51ce072962a7e969684ba9e8b4ec19dd9 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 9 Aug 2008 05:19:40 +0000 Subject: 97: Some whitespace and docstring cleanup; Plugin now subclasses from ProxyTarget --- ipalib/plugable.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index d8578270..2bef3de7 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -150,8 +150,8 @@ class Proxy(ReadOnly): '__base', '__target', '__name_attr', - 'name', '__public__', + 'name', ) def __init__(self, base, target, name_attr='name'): @@ -169,9 +169,9 @@ class Proxy(ReadOnly): self.__base = base self.__target = target self.__name_attr = name_attr - self.name = getattr(target, name_attr) self.__public__ = base.__public__ assert type(self.__public__) is frozenset + self.name = getattr(target, name_attr) check_identifier(self.name) self.__lock__() @@ -225,8 +225,7 @@ class Proxy(ReadOnly): ) - -class Plugin(object): +class Plugin(ProxyTarget): """ Base class for all plugins. """ @@ -252,16 +251,10 @@ class Plugin(object): assert api is not None, 'finalize() argument cannot be None' self.__api = api - def __get_name(self): - """ - Returns the class name of this instance. - """ - return self.__class__.__name__ - name = property(__get_name) - def __repr__(self): """ - Returns a fully qualified representation of the class. + Returns a fully qualified module_name.class_name() representation that + could be used to contruct this instance. """ return '%s.%s()' % ( self.__class__.__module__, @@ -269,12 +262,6 @@ class Plugin(object): ) - - - - - - class NameSpace(ReadOnly): """ A read-only namespace of (key, value) pairs that can be accessed -- cgit From 5315514f6c773de897c2e74a4ad31bbfeeae2bda Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 9 Aug 2008 18:58:46 +0000 Subject: 98: Completed docstrings in Proxy --- ipalib/plugable.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 2bef3de7..7a571995 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -176,6 +176,13 @@ class Proxy(ReadOnly): self.__lock__() def implements(self, arg): + """ + Returns True if this proxy implements `arg`. Calls the corresponding + classmethod on ProxyTarget. + + Unlike ProxyTarget.implements(), this is not a classmethod as a Proxy + only implements anything as an instance. + """ return self.__base.implements(arg) def __clone__(self, name_attr): @@ -196,8 +203,8 @@ class Proxy(ReadOnly): def __getitem__(self, key): """ - If this proxy allowes access to an attribute named `key`, return that - attrribute. + If this proxy allows access to an attribute named `key`, return that + attribute. """ if key in self.__public__: return getattr(self.__target, key) @@ -205,18 +212,25 @@ class Proxy(ReadOnly): def __getattr__(self, name): """ - If this proxy allowes access to an attribute named `name`, return that - attrribute. + If this proxy allows access to an attribute named `name`, return that + attribute. """ if name in self.__public__: return getattr(self.__target, name) raise AttributeError('no proxy attribute %r' % name) def __call__(self, *args, **kw): + """ + Attempts to call target.__call__(); raises KeyError if `__call__` is + not an attribute this proxy allows access to. + """ return self['__call__'](*args, **kw) - def __repr__(self): + """ + Returns a Python expression that could be used to construct this Proxy + instance given the appropriate environment. + """ return '%s(%s, %r, %r)' % ( self.__class__.__name__, self.__base.__name__, -- cgit From e756e12718a538d82de45fbba3a5e97f3a4d7d7f Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 9 Aug 2008 19:09:10 +0000 Subject: 99: Cleaned up unit tests for plugable.Plugin --- ipalib/plugable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 7a571995..b4a6fb10 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -268,7 +268,7 @@ class Plugin(ProxyTarget): def __repr__(self): """ Returns a fully qualified module_name.class_name() representation that - could be used to contruct this instance. + could be used to construct this Plugin instance. """ return '%s.%s()' % ( self.__class__.__module__, -- cgit From 0e532cd7b30023b10f97690540f4209106d7f832 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 9 Aug 2008 19:28:01 +0000 Subject: 100: Cleaned up NameSpace docstrings; cleanup up NameSpace unit tests --- ipalib/plugable.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index b4a6fb10..63de6deb 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -278,13 +278,17 @@ class Plugin(ProxyTarget): class NameSpace(ReadOnly): """ - A read-only namespace of (key, value) pairs that can be accessed - both as instance attributes and as dictionary items. + A read-only namespace of Proxy instances. Proxy.name is used to name the + attributes pointing to the Proxy instances, which can also be accesses + through a dictionary interface, for example: + + >>> assert namespace.my_proxy is namespace['my_proxy'] # True """ def __init__(self, proxies): """ - NameSpace + `proxies` - an iterable returning the Proxy instances to be contained + in this NameSpace. """ self.__proxies = tuple(proxies) self.__d = dict() @@ -299,7 +303,7 @@ class NameSpace(ReadOnly): def __iter__(self): """ Iterates through the proxies in this NameSpace in the same order they - were passed in the contructor. + were passed to the constructor. """ for proxy in self.__proxies: yield proxy -- cgit From 543aea31a4bf85d5843abd808d2200117ff35252 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 9 Aug 2008 19:33:13 +0000 Subject: 101: Registrar now subclasses from ReadOnly --- ipalib/plugable.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 63de6deb..029c8403 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -332,7 +332,7 @@ class NameSpace(ReadOnly): return '%s(<%d proxies>)' % (self.__class__.__name__, len(self)) -class Registrar(object): +class Registrar(ReadOnly): def __init__(self, *allowed): """ `*allowed` is a list of the base classes plugins can be subclassed @@ -346,6 +346,7 @@ class Registrar(object): assert inspect.isclass(base) assert base.__name__ not in self.__d self.__d[base.__name__] = {} + self.__lock__() def __findbase(self, cls): """ -- cgit From 0edb22c9ac70c5acfab51318810f693d59fab955 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 9 Aug 2008 19:39:58 +0000 Subject: 102: After the API instance calls plugin.finalize(), it also calls plugin.__lock__() --- ipalib/plugable.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 029c8403..1341a98f 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -447,6 +447,8 @@ class API(ReadOnly): object.__setattr__(self, base.__name__, ns) for plugin in d.values(): plugin.finalize(self) + plugin.__lock__() + assert plugin.__islocked__() is True assert plugin.api is self def __iter__(self): -- cgit From d7958f3fde94d20be126f4486b8d906eb38446f7 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 9 Aug 2008 19:48:47 +0000 Subject: 103: Fixed missing API.__keys assignment --- ipalib/plugable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 1341a98f..b607f0fa 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -425,7 +425,7 @@ class Registrar(ReadOnly): class API(ReadOnly): def __init__(self, *allowed): - keys = tuple(b.__name__ for b in allowed) + self.__keys = tuple(b.__name__ for b in allowed) self.register = Registrar(*allowed) self.__lock__() -- cgit From 86405236325204cb5750ce79f674a5ab01114fa7 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 12 Aug 2008 21:45:54 +0000 Subject: 119: Added ProxyTarget.implemented_by() classmethod; added corresponding unit tests --- ipalib/plugable.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index b607f0fa..a91063e7 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -135,6 +135,18 @@ class ProxyTarget(ReadOnly): "must be str, frozenset, or have frozenset '__public__' attribute" ) + @classmethod + def implemented_by(cls, arg): + if inspect.isclass(arg): + subclass = arg + else: + subclass = arg.__class__ + assert issubclass(subclass, cls), 'must be subclass of %r' % cls + for name in cls.__public__: + if not hasattr(subclass, name): + return False + return True + class Proxy(ReadOnly): """ -- cgit From f767543fe71db6fb840ad8f328158fe0c6d65ad4 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 12 Aug 2008 22:40:06 +0000 Subject: 120: Moved ProxyTarget below Proxy to emphasize relationship with Plugin; added docstrings for ProxyTarget.implements() and implemented_by() classmethods; fixed typo in Plugin.finalize() docstring --- ipalib/plugable.py | 118 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 37 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index a91063e7..230e8ee2 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -112,42 +112,6 @@ class ReadOnly(object): return object.__delattr__(self, name) -class ProxyTarget(ReadOnly): - __public__ = frozenset() - - def __get_name(self): - """ - Convenience property to return the class name. - """ - return self.__class__.__name__ - name = property(__get_name) - - @classmethod - def implements(cls, arg): - assert type(cls.__public__) is frozenset - if isinstance(arg, str): - return arg in cls.__public__ - if type(getattr(arg, '__public__', None)) is frozenset: - return cls.__public__.issuperset(arg.__public__) - if type(arg) is frozenset: - return cls.__public__.issuperset(arg) - raise TypeError( - "must be str, frozenset, or have frozenset '__public__' attribute" - ) - - @classmethod - def implemented_by(cls, arg): - if inspect.isclass(arg): - subclass = arg - else: - subclass = arg.__class__ - assert issubclass(subclass, cls), 'must be subclass of %r' % cls - for name in cls.__public__: - if not hasattr(subclass, name): - return False - return True - - class Proxy(ReadOnly): """ Allows access to only certain attributes on its target object (a @@ -251,6 +215,86 @@ class Proxy(ReadOnly): ) +class ProxyTarget(ReadOnly): + __public__ = frozenset() + + def __get_name(self): + """ + Convenience property to return the class name. + """ + return self.__class__.__name__ + name = property(__get_name) + + @classmethod + def implements(cls, arg): + """ + Returns True if this cls.__public__ frozenset contains `arg`; + returns False otherwise. + + There are three different ways this can be called: + + 1. With a argument, e.g.: + + >>> class base(ProxyTarget): + >>> __public__ = frozenset(['some_attr', 'another_attr']) + >>> base.implements('some_attr') + True + >>> base.implements('an_unknown_attribute') + False + + 2. With a argument, e.g.: + + >>> base.implements(frozenset(['some_attr'])) + True + >>> base.implements(frozenset(['some_attr', 'an_unknown_attribute'])) + False + + 3. With any object that has a `__public__` attribute that is + , e.g.: + + >>> class whatever(object): + >>> __public__ = frozenset(['another_attr']) + >>> base.implements(whatever) + True + + Unlike ProxyTarget.implemented_by(), this returns an abstract answer + because only the __public__ frozenset is checked... a ProxyTarget + need not itself have attributes for all names in __public__ + (subclasses might provide them). + """ + assert type(cls.__public__) is frozenset + if isinstance(arg, str): + return arg in cls.__public__ + if type(getattr(arg, '__public__', None)) is frozenset: + return cls.__public__.issuperset(arg.__public__) + if type(arg) is frozenset: + return cls.__public__.issuperset(arg) + raise TypeError( + "must be str, frozenset, or have frozenset '__public__' attribute" + ) + + @classmethod + def implemented_by(cls, arg): + """ + Returns True if (1) `arg` is an instance of or subclass of this class, + and (2) `arg` (or `arg.__class__` if instance) has an attribute for + each name in this class's __public__ frozenset; returns False + otherwise. + + Unlike ProxyTarget.implements(), this returns a concrete answer + because the attributes of the subclass are checked. + """ + if inspect.isclass(arg): + subclass = arg + else: + subclass = arg.__class__ + assert issubclass(subclass, cls), 'must be subclass of %r' % cls + for name in cls.__public__: + if not hasattr(subclass, name): + return False + return True + + class Plugin(ProxyTarget): """ Base class for all plugins. @@ -270,7 +314,7 @@ class Plugin(ProxyTarget): """ After all the plugins are instantiated, the plugable.API calls this method, passing itself as the only argument. This is where plugins - should check that other plugins they depend upon have actually be + should check that other plugins they depend upon have actually been loaded. """ assert self.__api is None, 'finalize() can only be called once' -- cgit From b72cfa5dcc488f3b497fa05a88985cc8f790cc00 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 12 Aug 2008 22:52:37 +0000 Subject: 121: Renamed API.__call__() method to API.finalize() --- ipalib/plugable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 230e8ee2..8241d8ea 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -485,7 +485,7 @@ class API(ReadOnly): self.register = Registrar(*allowed) self.__lock__() - def __call__(self): + def finalize(self): """ Finalize the registration, instantiate the plugins. """ -- cgit From 7db3aae1b26588b3650dae442b07dca0f33ab0c8 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 12 Aug 2008 23:40:36 +0000 Subject: 123: API.finalize() now raises AssetionError if called more than once; added corresponding unit tests --- ipalib/plugable.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 8241d8ea..71f03357 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -480,6 +480,8 @@ class Registrar(ReadOnly): class API(ReadOnly): + __finalized = False + def __init__(self, *allowed): self.__keys = tuple(b.__name__ for b in allowed) self.register = Registrar(*allowed) @@ -489,6 +491,7 @@ class API(ReadOnly): """ Finalize the registration, instantiate the plugins. """ + assert not self.__finalized, 'finalize() can only be called once' d = {} def plugin_iter(base, classes): for cls in classes: @@ -506,6 +509,7 @@ class API(ReadOnly): plugin.__lock__() assert plugin.__islocked__() is True assert plugin.api is self + object.__setattr__(self, '_API__finalized', True) def __iter__(self): for key in self.__keys: -- cgit From 70cbe10624e685f1dac6a898665048972665b97f Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 12 Aug 2008 23:45:36 +0000 Subject: 124: Fixed case in example in ReadOnly class docstring --- ipalib/plugable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 71f03357..89eb423b 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -53,7 +53,7 @@ class ReadOnly(object): For example: - >>> class givenName(ReadOnly): + >>> class givenname(ReadOnly): >>> def __init__(self): >>> self.whatever = 'some value' # Hasn't been locked yet >>> self.__lock__() -- cgit From 0fed74b56d1940f84e7b64c3661f21baabcb4616 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 13 Aug 2008 02:34:36 +0000 Subject: 138: Added ProxyTarget.doc property; CLI.print_commands() now uses cmd.doc instead of cmd.get_doc() --- ipalib/plugable.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 89eb423b..fbe5e638 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -128,6 +128,7 @@ class Proxy(ReadOnly): '__name_attr', '__public__', 'name', + 'doc', ) def __init__(self, base, target, name_attr='name'): @@ -146,10 +147,11 @@ class Proxy(ReadOnly): self.__target = target self.__name_attr = name_attr self.__public__ = base.__public__ - assert type(self.__public__) is frozenset self.name = getattr(target, name_attr) - check_identifier(self.name) + self.doc = target.doc self.__lock__() + assert type(self.__public__) is frozenset + check_identifier(self.name) def implements(self, arg): """ @@ -225,6 +227,13 @@ class ProxyTarget(ReadOnly): return self.__class__.__name__ name = property(__get_name) + def __get_doc(self): + """ + Convenience property to return the class docstring. + """ + return self.__class__.__doc__ + doc = property(__get_doc) + @classmethod def implements(cls, arg): """ -- cgit From ba8d32a110f3dc96b091df9a2520f60c99ac26ba Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 05:46:20 +0000 Subject: 150: NameSpace.__iter__() now iterates through the names, not the members; added NameSpace.__call__() method which iterates through the members; NameSpace no longer requires members to be Proxy instances; updated unit tests and affected code; cleaned up NameSpace docstrings and switch to epydoc param docstrings --- ipalib/plugable.py | 145 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 103 insertions(+), 42 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index fbe5e638..ec97722a 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -26,16 +26,6 @@ import inspect import errors -def check_identifier(name): - """ - Raises errors.NameSpaceError if `name` is not a valid Python identifier - suitable for use in a NameSpace. - """ - regex = r'^[a-z][_a-z0-9]*[a-z0-9]$' - if re.match(regex, name) is None: - raise errors.NameSpaceError(name, regex) - - class ReadOnly(object): """ Base class for classes with read-only attributes. @@ -151,7 +141,6 @@ class Proxy(ReadOnly): self.doc = target.doc self.__lock__() assert type(self.__public__) is frozenset - check_identifier(self.name) def implements(self, arg): """ @@ -341,60 +330,132 @@ class Plugin(ProxyTarget): ) -class NameSpace(ReadOnly): +def check_name(name): """ - A read-only namespace of Proxy instances. Proxy.name is used to name the - attributes pointing to the Proxy instances, which can also be accesses - through a dictionary interface, for example: + Raises errors.NameSpaceError if `name` is not a valid Python identifier + suitable for use in a NameSpace. + """ + assert type(name) is str, 'must be %r' % str + regex = r'^[a-z][_a-z0-9]*[a-z0-9]$' + if re.match(regex, name) is None: + raise errors.NameSpaceError(name, regex) + return name - >>> assert namespace.my_proxy is namespace['my_proxy'] # True + +class NameSpace(ReadOnly): + """ + A read-only namespace with handy container behaviours. + + Each member of a NameSpace instance must have a `name` attribute whose + value 1) is unique among the members and 2) passes the check_name() + function. Beyond that, no restrictions are placed on the members: they can + be classes or instances, and of any type. + + The members can be accessed as attributes on the NameSpace instance or + through a dictionary interface. For example, assuming `obj` is a member in + the NameSpace instance `namespace`: + + >>> obj is getattr(namespace, obj.name) # As attribute + True + >>> obj is namespace[obj.name] # As dictionary item + True + + Here is a more detailed example: + + >>> class member(object): + ... def __init__(self, i): + ... self.name = 'member_%d' % i + ... + >>> def get_members(cnt): + ... for i in xrange(cnt): + ... yield member(i) + ... + >>> namespace = NameSpace(get_members(2)) + >>> namespace.member_0 is namespace['member_0'] + True + >>> len(namespace) # Returns the number of members in namespace + 2 + >>> list(namespace) # As iterable, iterates through the member names + ['member_0', 'member_1'] + >>> list(namespace()) # Calling a NameSpace iterates through the members + [<__main__.member object at 0x836710>, <__main__.member object at 0x836750>] + >>> 'member_1' in namespace # NameSpace.__contains__() + True """ - def __init__(self, proxies): + def __init__(self, members): """ - `proxies` - an iterable returning the Proxy instances to be contained - in this NameSpace. + @type members: iterable + @param members: An iterable providing the members. """ - self.__proxies = tuple(proxies) self.__d = dict() - for proxy in self.__proxies: - assert isinstance(proxy, Proxy) - assert proxy.name not in self.__d - self.__d[proxy.name] = proxy - assert not hasattr(self, proxy.name) - setattr(self, proxy.name, proxy) + self.__names = tuple(self.__member_iter(members)) self.__lock__() + assert set(self.__d) == set(self.__names) - def __iter__(self): + def __member_iter(self, members): """ - Iterates through the proxies in this NameSpace in the same order they - were passed to the constructor. + Helper method used only from __init__(). """ - for proxy in self.__proxies: - yield proxy + for member in members: + name = check_name(member.name) + assert not ( + name in self.__d or hasattr(self, name) + ), 'already has member named %r' % name + self.__d[name] = member + setattr(self, name, member) + yield name def __len__(self): """ - Returns number of proxies in this NameSpace. + Returns the number of members in this NameSpace. """ - return len(self.__proxies) + return len(self.__d) - def __contains__(self, key): + def __contains__(self, name): """ - Returns True if a proxy named `key` is in this NameSpace. + Returns True if this NameSpace contains a member named `name`; returns + False otherwise. + + @type name: str + @param name: The name of a potential member """ - return key in self.__d + return name in self.__d - def __getitem__(self, key): + def __getitem__(self, name): + """ + If this NameSpace contains a member named `name`, returns that member; + otherwise raises KeyError. + + @type name: str + @param name: The name of member to retrieve + """ + if name in self.__d: + return self.__d[name] + raise KeyError('NameSpace has no member named %r' % name) + + def __iter__(self): """ - Returns proxy named `key`; otherwise raises KeyError. + Iterates through the member names in the same order as the members + were passed to the constructor. """ - if key in self.__d: - return self.__d[key] - raise KeyError('NameSpace has no item for key %r' % key) + for name in self.__names: + yield name + + def __call__(self): + """ + Iterates through the members in the same order they were passed to the + constructor. + """ + for name in self.__names: + yield self.__d[name] def __repr__(self): - return '%s(<%d proxies>)' % (self.__class__.__name__, len(self)) + """ + Returns pseudo-valid Python expression that could be used to construct + this NameSpace instance. + """ + return '%s(<%d members>)' % (self.__class__.__name__, len(self)) class Registrar(ReadOnly): -- cgit From a59d6698d2a13792210bcaeac1ee79e255fd8f1c Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 06:53:05 +0000 Subject: 153: Started cleaning up docstrings in Proxy and also experimented with restructuredtext formatting --- ipalib/plugable.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index ec97722a..8ab5e249 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -104,8 +104,7 @@ class ReadOnly(object): class Proxy(ReadOnly): """ - Allows access to only certain attributes on its target object (a - ProxyTarget). + Allows access to only certain attributes on its target object. Think of a proxy as an agreement that "I will have at most these attributes". This is different from (although similar to) an interface, @@ -123,16 +122,19 @@ class Proxy(ReadOnly): def __init__(self, base, target, name_attr='name'): """ - `base` - the class defining the __public__ frozenset of attributes to - proxy - `target` - the target of the proxy (must be instance of `base`) - `name_attr` - the name of the str attribute on `target` to assign - to Proxy.name + :param base: A subclass of `Plugin`. + :param target: An instance ``base`` or a subclass of ``base``. + :param name_attr: The name of the attribute on ``target`` from which + to derive ``self.name``. """ if not inspect.isclass(base): - raise TypeError('arg1 must be a class, got %r' % base) + raise TypeError( + '`base` must be a class, got %r' % base + ) if not isinstance(target, base): - raise ValueError('arg2 must be instance of arg1, got %r' % target) + raise ValueError( + '`target` must be an instance of `base`, got %r' % target + ) self.__base = base self.__target = target self.__name_attr = name_attr -- cgit From 00f4272662e56a98fe498cc8f5761cc15bcd3825 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 07:10:07 +0000 Subject: 154: Merged ProxyTarget functionality into Plugin to make things a bit clearer --- ipalib/plugable.py | 245 ++++++++++++++++++++++++++--------------------------- 1 file changed, 122 insertions(+), 123 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 8ab5e249..645b2d16 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -102,9 +102,130 @@ class ReadOnly(object): return object.__delattr__(self, name) +class Plugin(ReadOnly): + """ + Base class for all plugins. + """ + __public__ = frozenset() + __api = None + + def __get_name(self): + """ + Convenience property to return the class name. + """ + return self.__class__.__name__ + name = property(__get_name) + + def __get_doc(self): + """ + Convenience property to return the class docstring. + """ + return self.__class__.__doc__ + doc = property(__get_doc) + + def __get_api(self): + """ + Returns the `API` instance passed to `finalize`, or + or returns None if `finalize` has not yet been called. + """ + return self.__api + api = property(__get_api) + + @classmethod + def implements(cls, arg): + """ + Returns True if this cls.__public__ frozenset contains `arg`; + returns False otherwise. + + There are three different ways this can be called: + + 1. With a argument, e.g.: + + >>> class base(ProxyTarget): + >>> __public__ = frozenset(['some_attr', 'another_attr']) + >>> base.implements('some_attr') + True + >>> base.implements('an_unknown_attribute') + False + + 2. With a argument, e.g.: + + >>> base.implements(frozenset(['some_attr'])) + True + >>> base.implements(frozenset(['some_attr', 'an_unknown_attribute'])) + False + + 3. With any object that has a `__public__` attribute that is + , e.g.: + + >>> class whatever(object): + >>> __public__ = frozenset(['another_attr']) + >>> base.implements(whatever) + True + + Unlike ProxyTarget.implemented_by(), this returns an abstract answer + because only the __public__ frozenset is checked... a ProxyTarget + need not itself have attributes for all names in __public__ + (subclasses might provide them). + """ + assert type(cls.__public__) is frozenset + if isinstance(arg, str): + return arg in cls.__public__ + if type(getattr(arg, '__public__', None)) is frozenset: + return cls.__public__.issuperset(arg.__public__) + if type(arg) is frozenset: + return cls.__public__.issuperset(arg) + raise TypeError( + "must be str, frozenset, or have frozenset '__public__' attribute" + ) + + @classmethod + def implemented_by(cls, arg): + """ + Returns True if (1) `arg` is an instance of or subclass of this class, + and (2) `arg` (or `arg.__class__` if instance) has an attribute for + each name in this class's __public__ frozenset; returns False + otherwise. + + Unlike ProxyTarget.implements(), this returns a concrete answer + because the attributes of the subclass are checked. + """ + if inspect.isclass(arg): + subclass = arg + else: + subclass = arg.__class__ + assert issubclass(subclass, cls), 'must be subclass of %r' % cls + for name in cls.__public__: + if not hasattr(subclass, name): + return False + return True + + def finalize(self, api): + """ + After all the plugins are instantiated, `API` calls this method, + passing itself as the only argument. This is where plugins should + check that other plugins they depend upon have actually been loaded. + + :param api: An `API` instance. + """ + assert self.__api is None, 'finalize() can only be called once' + assert api is not None, 'finalize() argument cannot be None' + self.__api = api + + def __repr__(self): + """ + Returns a fully qualified module_name.class_name() representation that + could be used to construct this Plugin instance. + """ + return '%s.%s()' % ( + self.__class__.__module__, + self.__class__.__name__ + ) + + class Proxy(ReadOnly): """ - Allows access to only certain attributes on its target object. + Allows access to only certain attributes on a `Plugin`. Think of a proxy as an agreement that "I will have at most these attributes". This is different from (although similar to) an interface, @@ -208,128 +329,6 @@ class Proxy(ReadOnly): ) -class ProxyTarget(ReadOnly): - __public__ = frozenset() - - def __get_name(self): - """ - Convenience property to return the class name. - """ - return self.__class__.__name__ - name = property(__get_name) - - def __get_doc(self): - """ - Convenience property to return the class docstring. - """ - return self.__class__.__doc__ - doc = property(__get_doc) - - @classmethod - def implements(cls, arg): - """ - Returns True if this cls.__public__ frozenset contains `arg`; - returns False otherwise. - - There are three different ways this can be called: - - 1. With a argument, e.g.: - - >>> class base(ProxyTarget): - >>> __public__ = frozenset(['some_attr', 'another_attr']) - >>> base.implements('some_attr') - True - >>> base.implements('an_unknown_attribute') - False - - 2. With a argument, e.g.: - - >>> base.implements(frozenset(['some_attr'])) - True - >>> base.implements(frozenset(['some_attr', 'an_unknown_attribute'])) - False - - 3. With any object that has a `__public__` attribute that is - , e.g.: - - >>> class whatever(object): - >>> __public__ = frozenset(['another_attr']) - >>> base.implements(whatever) - True - - Unlike ProxyTarget.implemented_by(), this returns an abstract answer - because only the __public__ frozenset is checked... a ProxyTarget - need not itself have attributes for all names in __public__ - (subclasses might provide them). - """ - assert type(cls.__public__) is frozenset - if isinstance(arg, str): - return arg in cls.__public__ - if type(getattr(arg, '__public__', None)) is frozenset: - return cls.__public__.issuperset(arg.__public__) - if type(arg) is frozenset: - return cls.__public__.issuperset(arg) - raise TypeError( - "must be str, frozenset, or have frozenset '__public__' attribute" - ) - - @classmethod - def implemented_by(cls, arg): - """ - Returns True if (1) `arg` is an instance of or subclass of this class, - and (2) `arg` (or `arg.__class__` if instance) has an attribute for - each name in this class's __public__ frozenset; returns False - otherwise. - - Unlike ProxyTarget.implements(), this returns a concrete answer - because the attributes of the subclass are checked. - """ - if inspect.isclass(arg): - subclass = arg - else: - subclass = arg.__class__ - assert issubclass(subclass, cls), 'must be subclass of %r' % cls - for name in cls.__public__: - if not hasattr(subclass, name): - return False - return True - - -class Plugin(ProxyTarget): - """ - Base class for all plugins. - """ - - __api = None - - def __get_api(self): - """ - Returns the plugable.API instance passed to Plugin.finalize(), or - or returns None if finalize() has not yet been called. - """ - return self.__api - api = property(__get_api) - - def finalize(self, api): - """ - After all the plugins are instantiated, the plugable.API calls this - method, passing itself as the only argument. This is where plugins - should check that other plugins they depend upon have actually been - loaded. - """ - assert self.__api is None, 'finalize() can only be called once' - assert api is not None, 'finalize() argument cannot be None' - self.__api = api - - def __repr__(self): - """ - Returns a fully qualified module_name.class_name() representation that - could be used to construct this Plugin instance. - """ - return '%s.%s()' % ( - self.__class__.__module__, - self.__class__.__name__ - ) def check_name(name): -- cgit From 8c27f4c2ded1dfef8dcf406f4cfd89ba1e532e92 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 07:43:43 +0000 Subject: 155: More docstring cleanup in plugable.py --- ipalib/plugable.py | 55 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 26 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 645b2d16..5392c239 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -18,7 +18,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ -Base classes for plug-in architecture and generative API. +Base classes for plugin architecture. """ import re @@ -81,8 +81,8 @@ class ReadOnly(object): def __setattr__(self, name, value): """ - Raises an AttributeError if ReadOnly.__lock__() has already been called; - otherwise calls object.__setattr__() + Raises an AttributeError if `ReadOnly.__lock__()` has already been + called; otherwise calls object.__setattr__(). """ if self.__locked: raise AttributeError('read-only: cannot set %s.%s' % @@ -92,8 +92,8 @@ class ReadOnly(object): def __delattr__(self, name): """ - Raises an AttributeError if ReadOnly.__lock__() has already been called; - otherwise calls object.__delattr__() + Raises an AttributeError if `ReadOnly.__lock__()` has already been + called; otherwise calls object.__delattr__(). """ if self.__locked: raise AttributeError('read-only: cannot del %s.%s' % @@ -125,8 +125,8 @@ class Plugin(ReadOnly): def __get_api(self): """ - Returns the `API` instance passed to `finalize`, or - or returns None if `finalize` has not yet been called. + Returns the `API` instance passed to `finalize()`, or + or returns None if `finalize()` has not yet been called. """ return self.__api api = property(__get_api) @@ -329,12 +329,12 @@ class Proxy(ReadOnly): ) - - def check_name(name): """ - Raises errors.NameSpaceError if `name` is not a valid Python identifier - suitable for use in a NameSpace. + Raises `errors.NameSpaceError` if ``name`` is not a valid Python identifier + suitable for use in a `NameSpace`. + + :param name: Identifier to test. """ assert type(name) is str, 'must be %r' % str regex = r'^[a-z][_a-z0-9]*[a-z0-9]$' @@ -347,14 +347,18 @@ class NameSpace(ReadOnly): """ A read-only namespace with handy container behaviours. - Each member of a NameSpace instance must have a `name` attribute whose - value 1) is unique among the members and 2) passes the check_name() - function. Beyond that, no restrictions are placed on the members: they can - be classes or instances, and of any type. + Each member of a NameSpace instance must have a ``name`` attribute whose + value: + + 1. Is unique among the members + 2. Passes the `check_name()` function + + Beyond that, no restrictions are placed on the members: they can be + classes or instances, and of any type. The members can be accessed as attributes on the NameSpace instance or - through a dictionary interface. For example, assuming `obj` is a member in - the NameSpace instance `namespace`: + through a dictionary interface. For example, assuming ``obj`` is a member + in the NameSpace instance ``namespace``: >>> obj is getattr(namespace, obj.name) # As attribute True @@ -386,8 +390,7 @@ class NameSpace(ReadOnly): def __init__(self, members): """ - @type members: iterable - @param members: An iterable providing the members. + :param members: An iterable providing the members. """ self.__d = dict() self.__names = tuple(self.__member_iter(members)) @@ -396,7 +399,9 @@ class NameSpace(ReadOnly): def __member_iter(self, members): """ - Helper method used only from __init__(). + Helper method called only from `NameSpace.__init__()`. + + :param members: Same iterable passed to `NameSpace.__init__()`. """ for member in members: name = check_name(member.name) @@ -415,21 +420,19 @@ class NameSpace(ReadOnly): def __contains__(self, name): """ - Returns True if this NameSpace contains a member named `name`; returns + Returns True if this NameSpace contains a member named ``name``; returns False otherwise. - @type name: str - @param name: The name of a potential member + :param name: The name of a potential member """ return name in self.__d def __getitem__(self, name): """ - If this NameSpace contains a member named `name`, returns that member; + If this NameSpace contains a member named ``name``, returns that member; otherwise raises KeyError. - @type name: str - @param name: The name of member to retrieve + :param name: The name of member to retrieve """ if name in self.__d: return self.__d[name] -- cgit From f0dfb9f873ccafcc77b34e36f03723d73f9c5e0c Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 08:28:48 +0000 Subject: 156: Fixed all broken docstring cross references --- ipalib/plugable.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 5392c239..9fb3c079 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -182,13 +182,19 @@ class Plugin(ReadOnly): @classmethod def implemented_by(cls, arg): """ - Returns True if (1) `arg` is an instance of or subclass of this class, - and (2) `arg` (or `arg.__class__` if instance) has an attribute for - each name in this class's __public__ frozenset; returns False - otherwise. + Returns True if. - Unlike ProxyTarget.implements(), this returns a concrete answer - because the attributes of the subclass are checked. + 1. ``arg`` is an instance of or subclass of this class, and + + 2. ``arg`` (or ``arg.__class__`` if instance) has an attribute for + each name in this class's ``__public__`` frozenset + + Otherwise, returns False. + + Unlike `Plugin.implements`, this returns a concrete answer because + the attributes of the subclass are checked. + + :param arg: An instance of or subclass of this class. """ if inspect.isclass(arg): subclass = arg @@ -465,8 +471,8 @@ class NameSpace(ReadOnly): class Registrar(ReadOnly): def __init__(self, *allowed): """ - `*allowed` is a list of the base classes plugins can be subclassed - from. + :param *allowed: Base classes from which plugins accepted by this + Registrar must subclass. """ self.__allowed = frozenset(allowed) self.__d = {} @@ -480,8 +486,8 @@ class Registrar(ReadOnly): def __findbase(self, cls): """ - If `cls` is a subclass of a base in self.__allowed, returns that - base; otherwise raises SubclassError. + If ``cls`` is a subclass of a base in self.__allowed, returns that + base; otherwise raises `errors.SubclassError`. """ assert inspect.isclass(cls) found = False @@ -494,7 +500,7 @@ class Registrar(ReadOnly): def __call__(self, cls, override=False): """ - Register the plugin `cls`. + Register the plugin ``cls``. """ if not inspect.isclass(cls): raise TypeError('plugin must be a class: %r' % cls) @@ -525,7 +531,8 @@ class Registrar(ReadOnly): def __getitem__(self, item): """ - Returns a copy of the namespace dict of the base class named `name`. + Returns a copy of the namespace dict of the base class named + ``name``. """ if inspect.isclass(item): if item not in self.__allowed: @@ -537,7 +544,7 @@ class Registrar(ReadOnly): def __contains__(self, item): """ - Returns True if a base class named `name` is in this Registrar. + Returns True if a base class named ``name`` is in this Registrar. """ if inspect.isclass(item): return item in self.__allowed -- cgit From a3dc04ade4c8b640a881519144f009b70c6e4cfd Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 09:01:02 +0000 Subject: 157: More docstring cleanup; fixed remaining epydoc warnings --- ipalib/plugable.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 9fb3c079..ba4be6be 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -18,7 +18,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ -Base classes for plugin architecture. +Generic plugin framework. """ import re @@ -139,7 +139,7 @@ class Plugin(ReadOnly): There are three different ways this can be called: - 1. With a argument, e.g.: + With a argument, e.g.: >>> class base(ProxyTarget): >>> __public__ = frozenset(['some_attr', 'another_attr']) @@ -148,14 +148,14 @@ class Plugin(ReadOnly): >>> base.implements('an_unknown_attribute') False - 2. With a argument, e.g.: + With a argument, e.g.: >>> base.implements(frozenset(['some_attr'])) True >>> base.implements(frozenset(['some_attr', 'an_unknown_attribute'])) False - 3. With any object that has a `__public__` attribute that is + With any object that has a `__public__` attribute that is , e.g.: >>> class whatever(object): @@ -182,12 +182,12 @@ class Plugin(ReadOnly): @classmethod def implemented_by(cls, arg): """ - Returns True if. + Returns True if: 1. ``arg`` is an instance of or subclass of this class, and 2. ``arg`` (or ``arg.__class__`` if instance) has an attribute for - each name in this class's ``__public__`` frozenset + each name in this class's ``__public__`` frozenset Otherwise, returns False. @@ -471,7 +471,7 @@ class NameSpace(ReadOnly): class Registrar(ReadOnly): def __init__(self, *allowed): """ - :param *allowed: Base classes from which plugins accepted by this + :param allowed: Base classes from which plugins accepted by this Registrar must subclass. """ self.__allowed = frozenset(allowed) -- cgit From ca53615dddd487230c3e40231cb02467e19388d7 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 09:38:28 +0000 Subject: 158: Name local arg 'cls' to 'klass' in Registrar methods to avoid confusion with classmethods; some docstring improvement in Registrar --- ipalib/plugable.py | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index ba4be6be..1df3f836 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -364,7 +364,7 @@ class NameSpace(ReadOnly): The members can be accessed as attributes on the NameSpace instance or through a dictionary interface. For example, assuming ``obj`` is a member - in the NameSpace instance ``namespace``: + in the NameSpace instance ``namespace``, you could do this: >>> obj is getattr(namespace, obj.name) # As attribute True @@ -477,57 +477,63 @@ class Registrar(ReadOnly): self.__allowed = frozenset(allowed) self.__d = {} self.__registered = set() - assert len(self.__allowed) == len(allowed) for base in self.__allowed: assert inspect.isclass(base) assert base.__name__ not in self.__d self.__d[base.__name__] = {} self.__lock__() - def __findbase(self, cls): + def __findbases(self, klass): """ - If ``cls`` is a subclass of a base in self.__allowed, returns that - base; otherwise raises `errors.SubclassError`. + Iterates through allowed bases that ``klass`` is a subclass of. + + Raises `errors.SubclassError` if ``klass`` is not a subclass of any + allowed base. + + :param klass: The class to find bases for. """ - assert inspect.isclass(cls) + assert inspect.isclass(klass) found = False for base in self.__allowed: - if issubclass(cls, base): + if issubclass(klass, base): found = True yield base if not found: - raise errors.SubclassError(cls, self.__allowed) + raise errors.SubclassError(klass, self.__allowed) - def __call__(self, cls, override=False): + def __call__(self, klass, override=False): """ - Register the plugin ``cls``. + Register the plugin ``klass``. + + :param klass: A subclass of `Plugin` to attempt to register. + :param override: If true, override an already registered plugin. """ - if not inspect.isclass(cls): - raise TypeError('plugin must be a class: %r' % cls) + if not inspect.isclass(klass): + raise TypeError('plugin must be a class: %r' % klass) # Raise DuplicateError if this exact class was already registered: - if cls in self.__registered: - raise errors.DuplicateError(cls) + if klass in self.__registered: + raise errors.DuplicateError(klass) # Find the base class or raise SubclassError: - for base in self.__findbase(cls): + for base in self.__findbases(klass): sub_d = self.__d[base.__name__] # Check override: - if cls.__name__ in sub_d: + if klass.__name__ in sub_d: # Must use override=True to override: if not override: - raise errors.OverrideError(base, cls) + raise errors.OverrideError(base, klass) else: # There was nothing already registered to override: if override: - raise errors.MissingOverrideError(base, cls) + raise errors.MissingOverrideError(base, klass) # The plugin is okay, add to sub_d: - sub_d[cls.__name__] = cls + sub_d[klass.__name__] = klass # The plugin is okay, add to __registered: - self.__registered.add(cls) + self.__registered.add(klass) def __getitem__(self, item): """ -- cgit From b403fd822b76a7deffe8110fbeb7993ef3cac3a5 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 17:21:21 +0000 Subject: 159: Added plugable.DictProxy class; added corresponding unit tests; added setitem(), delitem() functions to tstutil --- ipalib/plugable.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 4 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 1df3f836..66cb18fe 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -426,8 +426,8 @@ class NameSpace(ReadOnly): def __contains__(self, name): """ - Returns True if this NameSpace contains a member named ``name``; returns - False otherwise. + Returns True if instance contains a member named ``name``, otherwise + False. :param name: The name of a potential member """ @@ -435,8 +435,10 @@ class NameSpace(ReadOnly): def __getitem__(self, name): """ - If this NameSpace contains a member named ``name``, returns that member; - otherwise raises KeyError. + Returns the member named ``name``. + + Raises KeyError if this NameSpace does not contain a member named + ``name``. :param name: The name of member to retrieve """ @@ -468,6 +470,66 @@ class NameSpace(ReadOnly): return '%s(<%d members>)' % (self.__class__.__name__, len(self)) +class DictProxy(ReadOnly): + """ + A read-only dict whose items can also be accessed as attributes. + + Although a DictProxy is read-only, the underlying dict can change (and is + assumed to). + + One of these is created for each allowed base class in a `Registrar` + instance. + """ + def __init__(self, d): + """ + :param d: The ``dict`` instance to proxy. + """ + self.__d = d + self.__lock__() + assert self.__islocked__() + + def __len__(self): + """ + Returns number of items in underlying ``dict``. + """ + return len(self.__d) + + def __iter__(self): + """ + Iterates through keys of underlying ``dict`` in ascending order. + """ + for name in sorted(self.__d): + yield name + + def __contains__(self, key): + """ + Returns True if underlying dict contains ``key``, False otherwise. + + :param key: The key to query upon. + """ + return key in self.__d + + def __getitem__(self, key): + """ + Returns value from underlying dict corresponding to ``key``. + + :param key: The key of the value to retrieve. + """ + if key in self.__d: + return self.__d[key] + raise KeyError('no item at key %r' % key) + + def __getattr__(self, name): + """ + Returns value from underlying dict corresponding to ``name``. + + :param name: The name of the attribute to retrieve. + """ + if name in self.__d: + return self.__d[name] + raise AttributeError('no attribute %r' % name) + + class Registrar(ReadOnly): def __init__(self, *allowed): """ -- cgit From 87cad5078a3c9ef7a978c85905309ee7d3ec194d Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 17:29:13 +0000 Subject: 160: DictProxy now checks type of d in __init__(); updated unit tests --- ipalib/plugable.py | 1 + 1 file changed, 1 insertion(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 66cb18fe..ffef8d64 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -484,6 +484,7 @@ class DictProxy(ReadOnly): """ :param d: The ``dict`` instance to proxy. """ + assert type(d) is dict, '`d` must be %r, got %r' % (dict, type(d)) self.__d = d self.__lock__() assert self.__islocked__() -- cgit From 7c64c8b95457c3aed1a3243ef1c22c303697a057 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 18:50:21 +0000 Subject: 161: Registrar now takes advantage of DictProxy; updated corresponding unit tests --- ipalib/plugable.py | 78 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 26 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index ffef8d64..b663a7ea 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -102,6 +102,16 @@ class ReadOnly(object): return object.__delattr__(self, name) +def lock(obj): + """ + Convenience function to lock a `ReadOnly` instance. + """ + assert isinstance(obj, ReadOnly) + obj.__lock__() + assert obj.__islocked__() + return obj + + class Plugin(ReadOnly): """ Base class for all plugins. @@ -477,8 +487,7 @@ class DictProxy(ReadOnly): Although a DictProxy is read-only, the underlying dict can change (and is assumed to). - One of these is created for each allowed base class in a `Registrar` - instance. + One of these is created for each allowed base in a `Registrar` instance. """ def __init__(self, d): """ @@ -486,8 +495,7 @@ class DictProxy(ReadOnly): """ assert type(d) is dict, '`d` must be %r, got %r' % (dict, type(d)) self.__d = d - self.__lock__() - assert self.__islocked__() + lock(self) def __len__(self): """ @@ -532,19 +540,44 @@ class DictProxy(ReadOnly): class Registrar(ReadOnly): + """ + Collects plugin classes as they are registered. + + The Registrar does not instantiate plugins... it only implements the + override logic and stores the plugins in a namespace per allowed base + class. + + The plugins are instantiated when `API.finalize()` is called. + """ def __init__(self, *allowed): """ :param allowed: Base classes from which plugins accepted by this Registrar must subclass. """ - self.__allowed = frozenset(allowed) + + class Val(ReadOnly): + """ + Internal class used so that only one mapping is needed. + """ + def __init__(self, base): + assert inspect.isclass(base) + self.base = base + self.name = base.__name__ + self.sub_d = dict() + self.dictproxy = DictProxy(self.sub_d) + lock(self) + + self.__allowed = allowed self.__d = {} self.__registered = set() for base in self.__allowed: - assert inspect.isclass(base) - assert base.__name__ not in self.__d - self.__d[base.__name__] = {} - self.__lock__() + val = Val(base) + assert not ( + val.name in self.__d or hasattr(self, val.name) + ) + self.__d[val.name] = val + setattr(self, val.name, val.dictproxy) + lock(self) def __findbases(self, klass): """ @@ -580,7 +613,7 @@ class Registrar(ReadOnly): # Find the base class or raise SubclassError: for base in self.__findbases(klass): - sub_d = self.__d[base.__name__] + sub_d = self.__d[base.__name__].sub_d # Check override: if klass.__name__ in sub_d: @@ -598,26 +631,19 @@ class Registrar(ReadOnly): # The plugin is okay, add to __registered: self.__registered.add(klass) - def __getitem__(self, item): + def __getitem__(self, key): """ - Returns a copy of the namespace dict of the base class named - ``name``. + Returns the DictProxy for plugins subclassed from the base named ``key``. """ - if inspect.isclass(item): - if item not in self.__allowed: - raise KeyError(repr(item)) - key = item.__name__ - else: - key = item - return dict(self.__d[key]) + if key not in self.__d: + raise KeyError('no base class named %r' % key) + return self.__d[key].dictproxy - def __contains__(self, item): + def __contains__(self, key): """ - Returns True if a base class named ``name`` is in this Registrar. + Returns True if a base class named ``key`` is in this Registrar. """ - if inspect.isclass(item): - return item in self.__allowed - return item in self.__d + return key in self.__d def __iter__(self): """ @@ -625,7 +651,7 @@ class Registrar(ReadOnly): base. """ for base in self.__allowed: - sub_d = self.__d[base.__name__] + sub_d = self.__d[base.__name__].sub_d yield (base, tuple(sub_d[k] for k in sorted(sub_d))) -- cgit From f423f2c9f0634d3b123eaaae8b13afd83cc0cf94 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 18:59:12 +0000 Subject: 162: Added link to container emulation documentation in plugable.py docstring --- ipalib/plugable.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index b663a7ea..30a4a5f0 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -19,6 +19,10 @@ """ Generic plugin framework. + +The classes in this module make heavy use of Python container emulation. If +you are unfamiliar with this Python feature, see +http://docs.python.org/ref/sequence-types.html """ import re -- cgit From 43c04f1cd356a46aab6720c64e8d15900b46bfdf Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 19:36:54 +0000 Subject: 163: Docstring improvement for ipalib/__init__.py and plugable.py --- ipalib/plugable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 30a4a5f0..60a8c548 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -18,7 +18,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ -Generic plugin framework. +Implementation of the plugin framework. The classes in this module make heavy use of Python container emulation. If you are unfamiliar with this Python feature, see -- cgit From d229a764749b37aded48ed6eec230df9105a62b0 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 20:32:35 +0000 Subject: 165: Added unit tests for plugable.lock() function; replaced occurances of 'self.__lock__()' with 'lock(self)' in plugable.py --- ipalib/plugable.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 60a8c548..5bfe5977 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -18,7 +18,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ -Implementation of the plugin framework. +Plugin framework. The classes in this module make heavy use of Python container emulation. If you are unfamiliar with this Python feature, see @@ -50,7 +50,7 @@ class ReadOnly(object): >>> class givenname(ReadOnly): >>> def __init__(self): >>> self.whatever = 'some value' # Hasn't been locked yet - >>> self.__lock__() + >>> lock(self) >>> >>> def finalize(self, api): >>> # After the instance has been locked, attributes can still be @@ -106,14 +106,20 @@ class ReadOnly(object): return object.__delattr__(self, name) -def lock(obj): +def lock(readonly): """ - Convenience function to lock a `ReadOnly` instance. + Locks a `ReadOnly` instance. + + This is mostly a convenience function to call `ReadOnly.__lock__()`. It + also verifies that the locking worked using `ReadOnly.__islocked__()` + + :param readonly: An instance of the `ReadOnly` class. """ - assert isinstance(obj, ReadOnly) - obj.__lock__() - assert obj.__islocked__() - return obj + if not isinstance(readonly, ReadOnly): + raise ValueError('not a ReadOnly instance: %r' % readonly) + readonly.__lock__() + assert readonly.__islocked__(), 'Ouch! The locking failed?' + return readonly class Plugin(ReadOnly): @@ -282,7 +288,7 @@ class Proxy(ReadOnly): self.__public__ = base.__public__ self.name = getattr(target, name_attr) self.doc = target.doc - self.__lock__() + lock(self) assert type(self.__public__) is frozenset def implements(self, arg): @@ -414,7 +420,7 @@ class NameSpace(ReadOnly): """ self.__d = dict() self.__names = tuple(self.__member_iter(members)) - self.__lock__() + lock(self) assert set(self.__d) == set(self.__names) def __member_iter(self, members): @@ -665,7 +671,7 @@ class API(ReadOnly): def __init__(self, *allowed): self.__keys = tuple(b.__name__ for b in allowed) self.register = Registrar(*allowed) - self.__lock__() + lock(self) def finalize(self): """ -- cgit From 5f38daf6dee93c7b80b79b6c915ce6916c79fcdc Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 21:40:37 +0000 Subject: 167: In API.finalize(), lock(plugin) is used instead of plugin.__lock__(); more docstring improvements in plugable.py --- ipalib/plugable.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 5bfe5977..e82167ab 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -357,8 +357,10 @@ class Proxy(ReadOnly): def check_name(name): """ - Raises `errors.NameSpaceError` if ``name`` is not a valid Python identifier - suitable for use in a `NameSpace`. + Verifies that ``name`` is suitable for a `NameSpace` member name. + + Raises `errors.NameSpaceError` if ``name`` is not a valid Python + identifier suitable for use as the name of `NameSpace` member. :param name: Identifier to test. """ @@ -666,6 +668,9 @@ class Registrar(ReadOnly): class API(ReadOnly): + """ + Dynamic API object through which `Plugin` instances are accessed. + """ __finalized = False def __init__(self, *allowed): @@ -692,8 +697,7 @@ class API(ReadOnly): object.__setattr__(self, base.__name__, ns) for plugin in d.values(): plugin.finalize(self) - plugin.__lock__() - assert plugin.__islocked__() is True + lock(plugin) assert plugin.api is self object.__setattr__(self, '_API__finalized', True) -- cgit From 07cd5372779fe88b1dd6c252e157b48a944c4669 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 22:13:42 +0000 Subject: 168: plugable.API now implements the all the usual container methods --- ipalib/plugable.py | 51 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index e82167ab..8d6a8047 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -674,7 +674,7 @@ class API(ReadOnly): __finalized = False def __init__(self, *allowed): - self.__keys = tuple(b.__name__ for b in allowed) + self.__d = dict() self.register = Registrar(*allowed) lock(self) @@ -683,24 +683,53 @@ class API(ReadOnly): Finalize the registration, instantiate the plugins. """ assert not self.__finalized, 'finalize() can only be called once' - d = {} + + instances = {} def plugin_iter(base, classes): - for cls in classes: - if cls not in d: - d[cls] = cls() - plugin = d[cls] + for klass in classes: + if klass not in instances: + instances[klass] = klass() + plugin = instances[klass] yield Proxy(base, plugin) for (base, classes) in self.register: - ns = NameSpace(plugin_iter(base, classes)) - assert not hasattr(self, base.__name__) - object.__setattr__(self, base.__name__, ns) - for plugin in d.values(): + namespace = NameSpace(plugin_iter(base, classes)) + name = base.__name__ + assert not ( + name in self.__d or hasattr(self, name) + ) + self.__d[name] = namespace + object.__setattr__(self, name, namespace) + + for plugin in instances.values(): plugin.finalize(self) lock(plugin) assert plugin.api is self object.__setattr__(self, '_API__finalized', True) + def __len__(self): + """ + Returns the number of namespaces in this API. + """ + return len(self.__d) + def __iter__(self): - for key in self.__keys: + """ + Iterates through the names of the namespaces in this API. + """ + for key in sorted(self.__d): yield key + + def __contains__(self, key): + """ + Returns True if this API contains a `NameSpace` named ``key``. + """ + return key in self.__d + + def __getitem__(self, key): + """ + Returns the `NameSpace` instance named ``key``. + """ + if key in self.__d: + return self.__d[key] + raise KeyError('API has no NameSpace %r' % key) -- cgit From 88a5b3ae2587ef71efecc1b59eb9ec94e09cacad Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 23:49:36 +0000 Subject: 169: Renamed DictProxy to MagicDict --- ipalib/plugable.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 8d6a8047..45c73354 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -492,11 +492,11 @@ class NameSpace(ReadOnly): return '%s(<%d members>)' % (self.__class__.__name__, len(self)) -class DictProxy(ReadOnly): +class MagicDict(ReadOnly): """ A read-only dict whose items can also be accessed as attributes. - Although a DictProxy is read-only, the underlying dict can change (and is + Although a MagicDict is read-only, the underlying dict can change (and is assumed to). One of these is created for each allowed base in a `Registrar` instance. @@ -576,7 +576,7 @@ class Registrar(ReadOnly): self.base = base self.name = base.__name__ self.sub_d = dict() - self.dictproxy = DictProxy(self.sub_d) + self.dictproxy = MagicDict(self.sub_d) lock(self) self.__allowed = allowed @@ -645,7 +645,7 @@ class Registrar(ReadOnly): def __getitem__(self, key): """ - Returns the DictProxy for plugins subclassed from the base named ``key``. + Returns the MagicDict for plugins subclassed from the base named ``key``. """ if key not in self.__d: raise KeyError('no base class named %r' % key) -- cgit From f6c2181eebf6e6bd794eaca8b78d3b35ad3be4e4 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 15 Aug 2008 01:04:19 +0000 Subject: 170: Added SetProxy and DictProxy classes to plugable so container emulation can be consolidated --- ipalib/plugable.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 45c73354..0d8286a4 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -122,6 +122,39 @@ def lock(readonly): return readonly +class SetProxy(ReadOnly): + def __init__(self, s): + allowed = (set, frozenset, dict) + if type(s) not in allowed: + raise TypeError('%r not in %r' % (type(s), allowed)) + self.__s = s + lock(self) + + def __len__(self): + return len(self.__s) + + def __iter__(self): + for key in sorted(self.__s): + yield key + + def __contains__(self, key): + return key in self.__s + + +class DictProxy(SetProxy): + def __init__(self, d): + if type(d) is not dict: + raise TypeError('%r is not %r' % (type(d), dict)) + self.__d = d + super(DictProxy, self).__init__(d) + + def __getitem__(self, key): + """ + Returns the value + """ + return self.__d[key] + + class Plugin(ReadOnly): """ Base class for all plugins. -- cgit From e43a5c642e1717c9309e8747e5433ab85abf2779 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 15 Aug 2008 01:24:51 +0000 Subject: 171: MagicDict now subclasses from DictProxy; updated unit tests --- ipalib/plugable.py | 88 +++++++++++++++++++----------------------------------- 1 file changed, 31 insertions(+), 57 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 0d8286a4..ba9b6973 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -123,6 +123,13 @@ def lock(readonly): class SetProxy(ReadOnly): + """ + A read-only proxy to an underlying set. + + Although the underlying set cannot be changed through the SetProxy, + the set can change and is expected to (unless the underlying set is a + frozen set). + """ def __init__(self, s): allowed = (set, frozenset, dict) if type(s) not in allowed: @@ -142,6 +149,9 @@ class SetProxy(ReadOnly): class DictProxy(SetProxy): + """ + A read-only proxy to an underlying dict. + """ def __init__(self, d): if type(d) is not dict: raise TypeError('%r is not %r' % (type(d), dict)) @@ -150,11 +160,31 @@ class DictProxy(SetProxy): def __getitem__(self, key): """ - Returns the value + Returns the value corresponding to ``key``. """ return self.__d[key] +class MagicDict(DictProxy): + """ + A read-only dict whose items can also be accessed as attributes. + + Although a MagicDict is read-only, the underlying dict can change (and is + assumed to). + + One of these is created for each allowed base in a `Registrar` instance. + """ + + def __getattr__(self, name): + """ + Returns the value corresponding to ``name``. + """ + try: + return self[name] + except KeyError: + raise AttributeError('no attribute %r' % name) + + class Plugin(ReadOnly): """ Base class for all plugins. @@ -525,63 +555,7 @@ class NameSpace(ReadOnly): return '%s(<%d members>)' % (self.__class__.__name__, len(self)) -class MagicDict(ReadOnly): - """ - A read-only dict whose items can also be accessed as attributes. - - Although a MagicDict is read-only, the underlying dict can change (and is - assumed to). - One of these is created for each allowed base in a `Registrar` instance. - """ - def __init__(self, d): - """ - :param d: The ``dict`` instance to proxy. - """ - assert type(d) is dict, '`d` must be %r, got %r' % (dict, type(d)) - self.__d = d - lock(self) - - def __len__(self): - """ - Returns number of items in underlying ``dict``. - """ - return len(self.__d) - - def __iter__(self): - """ - Iterates through keys of underlying ``dict`` in ascending order. - """ - for name in sorted(self.__d): - yield name - - def __contains__(self, key): - """ - Returns True if underlying dict contains ``key``, False otherwise. - - :param key: The key to query upon. - """ - return key in self.__d - - def __getitem__(self, key): - """ - Returns value from underlying dict corresponding to ``key``. - - :param key: The key of the value to retrieve. - """ - if key in self.__d: - return self.__d[key] - raise KeyError('no item at key %r' % key) - - def __getattr__(self, name): - """ - Returns value from underlying dict corresponding to ``name``. - - :param name: The name of the attribute to retrieve. - """ - if name in self.__d: - return self.__d[name] - raise AttributeError('no attribute %r' % name) class Registrar(ReadOnly): -- cgit From 1a92bdf29b3c65d7b9bd1c61d9eda0f98a70ecfa Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 15 Aug 2008 01:32:20 +0000 Subject: 172: API now subclasses from DictProxy --- ipalib/plugable.py | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index ba9b6973..4661aa1e 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -674,7 +674,7 @@ class Registrar(ReadOnly): yield (base, tuple(sub_d[k] for k in sorted(sub_d))) -class API(ReadOnly): +class API(DictProxy): """ Dynamic API object through which `Plugin` instances are accessed. """ @@ -683,7 +683,7 @@ class API(ReadOnly): def __init__(self, *allowed): self.__d = dict() self.register = Registrar(*allowed) - lock(self) + super(API, self).__init__(self.__d) def finalize(self): """ @@ -713,30 +713,3 @@ class API(ReadOnly): lock(plugin) assert plugin.api is self object.__setattr__(self, '_API__finalized', True) - - def __len__(self): - """ - Returns the number of namespaces in this API. - """ - return len(self.__d) - - def __iter__(self): - """ - Iterates through the names of the namespaces in this API. - """ - for key in sorted(self.__d): - yield key - - def __contains__(self, key): - """ - Returns True if this API contains a `NameSpace` named ``key``. - """ - return key in self.__d - - def __getitem__(self, key): - """ - Returns the `NameSpace` instance named ``key``. - """ - if key in self.__d: - return self.__d[key] - raise KeyError('API has no NameSpace %r' % key) -- cgit From 3e3b596f68957f46efa5af4b957c8add50fca8b6 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 15 Aug 2008 01:46:11 +0000 Subject: 173: NameSpace now subclasses from DictProxy --- ipalib/plugable.py | 62 ++++++++---------------------------------------------- 1 file changed, 9 insertions(+), 53 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 4661aa1e..7d42d4a0 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -434,7 +434,7 @@ def check_name(name): return name -class NameSpace(ReadOnly): +class NameSpace(DictProxy): """ A read-only namespace with handy container behaviours. @@ -483,10 +483,9 @@ class NameSpace(ReadOnly): """ :param members: An iterable providing the members. """ - self.__d = dict() - self.__names = tuple(self.__member_iter(members)) - lock(self) - assert set(self.__d) == set(self.__names) + super(NameSpace, self).__init__( + dict(self.__member_iter(members)) + ) def __member_iter(self, members): """ @@ -496,56 +495,16 @@ class NameSpace(ReadOnly): """ for member in members: name = check_name(member.name) - assert not ( - name in self.__d or hasattr(self, name) - ), 'already has member named %r' % name - self.__d[name] = member + assert not hasattr(self, name), 'already has attribute %r' % name setattr(self, name, member) - yield name - - def __len__(self): - """ - Returns the number of members in this NameSpace. - """ - return len(self.__d) - - def __contains__(self, name): - """ - Returns True if instance contains a member named ``name``, otherwise - False. - - :param name: The name of a potential member - """ - return name in self.__d - - def __getitem__(self, name): - """ - Returns the member named ``name``. - - Raises KeyError if this NameSpace does not contain a member named - ``name``. - - :param name: The name of member to retrieve - """ - if name in self.__d: - return self.__d[name] - raise KeyError('NameSpace has no member named %r' % name) - - def __iter__(self): - """ - Iterates through the member names in the same order as the members - were passed to the constructor. - """ - for name in self.__names: - yield name + yield (name, member) def __call__(self): """ - Iterates through the members in the same order they were passed to the - constructor. + Iterates through the members of this NameSpace. """ - for name in self.__names: - yield self.__d[name] + for key in self: + yield self[key] def __repr__(self): """ @@ -555,9 +514,6 @@ class NameSpace(ReadOnly): return '%s(<%d members>)' % (self.__class__.__name__, len(self)) - - - class Registrar(ReadOnly): """ Collects plugin classes as they are registered. -- cgit From ec0596b429d10b8659ea21a051fd98e047aece46 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 15 Aug 2008 03:24:37 +0000 Subject: 174: Fleshed out docstrings for SetProxy, DictProxy, and MagicDict --- ipalib/plugable.py | 59 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 10 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 7d42d4a0..e1731156 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -124,13 +124,17 @@ def lock(readonly): class SetProxy(ReadOnly): """ - A read-only proxy to an underlying set. + A read-only container with set/sequence behaviour. - Although the underlying set cannot be changed through the SetProxy, - the set can change and is expected to (unless the underlying set is a - frozen set). + This container acts as a proxy to an actual set-like object (a set, + frozenset, or dict) that is passed to the constructor. To the extent + possible in Python, this underlying set-like object cannot be modified + through the SetProxy... which just means you wont do it accidentally. """ def __init__(self, s): + """ + :param s: The target set-like object (a set, frozenset, or dict) + """ allowed = (set, frozenset, dict) if type(s) not in allowed: raise TypeError('%r not in %r' % (type(s), allowed)) @@ -138,21 +142,43 @@ class SetProxy(ReadOnly): lock(self) def __len__(self): + """ + Returns the number of items in this container. + """ return len(self.__s) def __iter__(self): + """ + Iterates (in ascending order) through the items (or keys) in this + container. + """ for key in sorted(self.__s): yield key def __contains__(self, key): + """ + Returns True if this container contains ``key``, False otherwise. + + :param key: The item (or key) to test for membership. + """ return key in self.__s class DictProxy(SetProxy): """ - A read-only proxy to an underlying dict. + A read-only container with mapping behaviour. + + This container acts as a proxy to an actual mapping object (a dict) that + is passed to the constructor. To the extent possible in Python, this + underlying mapping object cannot be modified through the DictProxy... + which just means you wont do it accidentally. + + Also see `SetProxy`. """ def __init__(self, d): + """ + :param d: The target mapping object (a dict) + """ if type(d) is not dict: raise TypeError('%r is not %r' % (type(d), dict)) self.__d = d @@ -161,28 +187,41 @@ class DictProxy(SetProxy): def __getitem__(self, key): """ Returns the value corresponding to ``key``. + + :param key: The key of the value you wish to retrieve. """ return self.__d[key] class MagicDict(DictProxy): """ - A read-only dict whose items can also be accessed as attributes. + A read-only mapping container whose values can also be accessed as + attributes. - Although a MagicDict is read-only, the underlying dict can change (and is - assumed to). + For example, assuming ``magic`` is a MagicDict instance that contains the + key ``name``, you could do this: - One of these is created for each allowed base in a `Registrar` instance. + >>> magic[name] is getattr(magic, name) + True + + This container acts as a proxy to an actual mapping object (a dict) that + is passed to the constructor. To the extent possible in Python, this + underlying mapping object cannot be modified through the MagicDict... + which just means you wont do it accidentally. + + Also see `DictProxy` and `SetProxy`. """ def __getattr__(self, name): """ Returns the value corresponding to ``name``. + + :param name: The name of the attribute you wish to retrieve. """ try: return self[name] except KeyError: - raise AttributeError('no attribute %r' % name) + raise AttributeError('no magic attribute %r' % name) class Plugin(ReadOnly): -- cgit From 233293fb4a60d57e60bce67035a88f57b2cbf751 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 15 Aug 2008 03:32:38 +0000 Subject: 175: Renamed Proxy to PluginProxy --- ipalib/plugable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index e1731156..a891bab5 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -351,7 +351,7 @@ class Plugin(ReadOnly): ) -class Proxy(ReadOnly): +class PluginProxy(ReadOnly): """ Allows access to only certain attributes on a `Plugin`. @@ -692,7 +692,7 @@ class API(DictProxy): if klass not in instances: instances[klass] = klass() plugin = instances[klass] - yield Proxy(base, plugin) + yield PluginProxy(base, plugin) for (base, classes) in self.register: namespace = NameSpace(plugin_iter(base, classes)) -- cgit From db8099febcb9c385eadfc4461dafa32df31bcbc0 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 15 Aug 2008 03:41:17 +0000 Subject: 176: PluginProxy now subclasses from SetProxy --- ipalib/plugable.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index a891bab5..c58114a9 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -351,7 +351,7 @@ class Plugin(ReadOnly): ) -class PluginProxy(ReadOnly): +class PluginProxy(SetProxy): """ Allows access to only certain attributes on a `Plugin`. @@ -390,8 +390,8 @@ class PluginProxy(ReadOnly): self.__public__ = base.__public__ self.name = getattr(target, name_attr) self.doc = target.doc - lock(self) assert type(self.__public__) is frozenset + super(PluginProxy, self).__init__(self.__public__) def implements(self, arg): """ @@ -411,31 +411,23 @@ class PluginProxy(ReadOnly): """ return self.__class__(self.__base, self.__target, name_attr) - def __iter__(self): - """ - Iterates (in ascending order) though the attribute names this proxy is - allowing access to. - """ - for name in sorted(self.__public__): - yield name - def __getitem__(self, key): """ - If this proxy allows access to an attribute named `key`, return that + If this proxy allows access to an attribute named ``key``, return that attribute. """ if key in self.__public__: return getattr(self.__target, key) - raise KeyError('no proxy attribute %r' % key) + raise KeyError('no public attribute %r' % key) def __getattr__(self, name): """ - If this proxy allows access to an attribute named `name`, return that - attribute. + If this proxy allows access to an attribute named ``name``, return + that attribute. """ if name in self.__public__: return getattr(self.__target, name) - raise AttributeError('no proxy attribute %r' % name) + raise AttributeError('no public attribute %r' % name) def __call__(self, *args, **kw): """ -- cgit From 5ed58fdb4213908b406fe625d0727ecc15dbd1cf Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 15 Aug 2008 03:45:07 +0000 Subject: 177: Docstring cleanup in NameSpace.__call__() --- ipalib/plugable.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index c58114a9..b8d2b390 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -532,7 +532,8 @@ class NameSpace(DictProxy): def __call__(self): """ - Iterates through the members of this NameSpace. + Iterates (in ascending order by name) through the members in this + NameSpace. """ for key in self: yield self[key] -- cgit From a24f2121d553644513dc90d423b9ac968de34bc2 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 15 Aug 2008 05:07:17 +0000 Subject: 178: Registrar now subclasses from DictProxy; made Registrar.__iter__ behave same as the other container emulation in plugable.py, and made the dictorary interface return the base and the attribute interface return the MagicDict; updated API class and unit tests --- ipalib/plugable.py | 85 +++++++++++++++++------------------------------------- 1 file changed, 26 insertions(+), 59 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index b8d2b390..57ab8bc7 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -546,7 +546,7 @@ class NameSpace(DictProxy): return '%s(<%d members>)' % (self.__class__.__name__, len(self)) -class Registrar(ReadOnly): +class Registrar(DictProxy): """ Collects plugin classes as they are registered. @@ -561,30 +561,19 @@ class Registrar(ReadOnly): :param allowed: Base classes from which plugins accepted by this Registrar must subclass. """ - - class Val(ReadOnly): - """ - Internal class used so that only one mapping is needed. - """ - def __init__(self, base): - assert inspect.isclass(base) - self.base = base - self.name = base.__name__ - self.sub_d = dict() - self.dictproxy = MagicDict(self.sub_d) - lock(self) - - self.__allowed = allowed - self.__d = {} + self.__allowed = dict((base, {}) for base in allowed) self.__registered = set() - for base in self.__allowed: - val = Val(base) - assert not ( - val.name in self.__d or hasattr(self, val.name) - ) - self.__d[val.name] = val - setattr(self, val.name, val.dictproxy) - lock(self) + super(Registrar, self).__init__( + dict(self.__base_iter()) + ) + + def __base_iter(self): + for (base, sub_d) in self.__allowed.iteritems(): + assert inspect.isclass(base) + name = base.__name__ + assert not hasattr(self, name) + setattr(self, name, MagicDict(sub_d)) + yield (name, base) def __findbases(self, klass): """ @@ -597,12 +586,12 @@ class Registrar(ReadOnly): """ assert inspect.isclass(klass) found = False - for base in self.__allowed: + for (base, sub_d) in self.__allowed.iteritems(): if issubclass(klass, base): found = True - yield base + yield (base, sub_d) if not found: - raise errors.SubclassError(klass, self.__allowed) + raise errors.SubclassError(klass, self.__allowed.keys()) def __call__(self, klass, override=False): """ @@ -619,17 +608,15 @@ class Registrar(ReadOnly): raise errors.DuplicateError(klass) # Find the base class or raise SubclassError: - for base in self.__findbases(klass): - sub_d = self.__d[base.__name__].sub_d - + for (base, sub_d) in self.__findbases(klass): # Check override: if klass.__name__ in sub_d: - # Must use override=True to override: if not override: + # Must use override=True to override: raise errors.OverrideError(base, klass) else: - # There was nothing already registered to override: if override: + # There was nothing already registered to override: raise errors.MissingOverrideError(base, klass) # The plugin is okay, add to sub_d: @@ -638,29 +625,6 @@ class Registrar(ReadOnly): # The plugin is okay, add to __registered: self.__registered.add(klass) - def __getitem__(self, key): - """ - Returns the MagicDict for plugins subclassed from the base named ``key``. - """ - if key not in self.__d: - raise KeyError('no base class named %r' % key) - return self.__d[key].dictproxy - - def __contains__(self, key): - """ - Returns True if a base class named ``key`` is in this Registrar. - """ - return key in self.__d - - def __iter__(self): - """ - Iterates through a (base, registered_plugins) tuple for each allowed - base. - """ - for base in self.__allowed: - sub_d = self.__d[base.__name__].sub_d - yield (base, tuple(sub_d[k] for k in sorted(sub_d))) - class API(DictProxy): """ @@ -687,16 +651,19 @@ class API(DictProxy): plugin = instances[klass] yield PluginProxy(base, plugin) - for (base, classes) in self.register: - namespace = NameSpace(plugin_iter(base, classes)) - name = base.__name__ + for name in self.register: + base = self.register[name] + magic = getattr(self.register, name) + namespace = NameSpace( + plugin_iter(base, (magic[k] for k in magic)) + ) assert not ( name in self.__d or hasattr(self, name) ) self.__d[name] = namespace object.__setattr__(self, name, namespace) - for plugin in instances.values(): + for plugin in instances.itervalues(): plugin.finalize(self) lock(plugin) assert plugin.api is self -- cgit From ab10f0843be45529925a226dc54a9fd0a30ad159 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 15 Aug 2008 05:19:02 +0000 Subject: 179: DictProxy now has __call__() method that iterates through the values; removed __call__() method from NameSpace as it subclasses from DictProxys; DictProxy unit tests now test __call__() --- ipalib/plugable.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 57ab8bc7..811a5527 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -192,6 +192,14 @@ class DictProxy(SetProxy): """ return self.__d[key] + def __call__(self): + """ + Iterates (in ascending order by key) through the values in this + container. + """ + for key in self: + yield self.__d[key] + class MagicDict(DictProxy): """ @@ -530,14 +538,6 @@ class NameSpace(DictProxy): setattr(self, name, member) yield (name, member) - def __call__(self): - """ - Iterates (in ascending order by name) through the members in this - NameSpace. - """ - for key in self: - yield self[key] - def __repr__(self): """ Returns pseudo-valid Python expression that could be used to construct -- cgit From 8b7fe7139dc47a421dd34376374a0ed06dc73f39 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 2 Sep 2008 17:29:01 +0000 Subject: 228: plugable.check_name() now uses errors.check_type() --- ipalib/plugable.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 811a5527..9880b0a0 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -28,6 +28,7 @@ http://docs.python.org/ref/sequence-types.html import re import inspect import errors +from errors import check_type, check_isinstance class ReadOnly(object): @@ -466,7 +467,7 @@ def check_name(name): :param name: Identifier to test. """ - assert type(name) is str, 'must be %r' % str + check_type(name, str, 'name') regex = r'^[a-z][_a-z0-9]*[a-z0-9]$' if re.match(regex, name) is None: raise errors.NameSpaceError(name, regex) -- cgit From e74713a076a72e75d6ca44d12df8500fb5cad8d2 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 8 Sep 2008 21:37:02 +0000 Subject: 267: Finished builtin CLI api command --- ipalib/plugable.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 9880b0a0..761d8a95 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -354,7 +354,7 @@ class Plugin(ReadOnly): Returns a fully qualified module_name.class_name() representation that could be used to construct this Plugin instance. """ - return '%s.%s()' % ( + return '%s.%s' % ( self.__class__.__module__, self.__class__.__name__ ) @@ -450,11 +450,10 @@ class PluginProxy(SetProxy): Returns a Python expression that could be used to construct this Proxy instance given the appropriate environment. """ - return '%s(%s, %r, %r)' % ( + return '%s(%s, %r)' % ( self.__class__.__name__, self.__base.__name__, self.__target, - self.__name_attr, ) -- cgit From 13f030d91e378064291d2065b547047bb3f175e8 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 8 Sep 2008 21:51:05 +0000 Subject: 271: Improved __repr__ methods for better output from the show-plugins command --- ipalib/plugable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 761d8a95..438815bb 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -354,7 +354,7 @@ class Plugin(ReadOnly): Returns a fully qualified module_name.class_name() representation that could be used to construct this Plugin instance. """ - return '%s.%s' % ( + return '%s.%s()' % ( self.__class__.__module__, self.__class__.__name__ ) -- cgit From 0453aa465f8371aa4baea5c06adad42481553e0a Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 9 Sep 2008 23:10:49 +0000 Subject: 274: NameSpace.__init__() now takes sort=True keyword arument to allow for non-sorted NameSpaces; updated and improved NameSpace unit tests --- ipalib/plugable.py | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 438815bb..66e5fada 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -518,32 +518,50 @@ class NameSpace(DictProxy): True """ - def __init__(self, members): + def __init__(self, members, sort=True): """ :param members: An iterable providing the members. + :param sort: Whether to sort the members by member name. """ + self.__members = tuple(members) + self.__sort = check_type(sort, bool, 'sort') + names = (m.name for m in self.__members) + if self.__sort: + self.__names = tuple(sorted(names)) + else: + self.__names = tuple(names) super(NameSpace, self).__init__( - dict(self.__member_iter(members)) + dict(self.__member_iter()) ) - def __member_iter(self, members): + def __member_iter(self): """ Helper method called only from `NameSpace.__init__()`. - - :param members: Same iterable passed to `NameSpace.__init__()`. """ - for member in members: + for member in self.__members: name = check_name(member.name) assert not hasattr(self, name), 'already has attribute %r' % name setattr(self, name, member) yield (name, member) + def __iter__(self): + """ + Iterates through member names. + + In this instance was created with ``sort=True``, + """ + for name in self.__names: + yield name + def __repr__(self): """ - Returns pseudo-valid Python expression that could be used to construct - this NameSpace instance. + Returns a pseudo-valid expression that could create this instance. """ - return '%s(<%d members>)' % (self.__class__.__name__, len(self)) + return '%s(<%d members>, sort=%r)' % ( + self.__class__.__name__, + len(self), + self.__sort, + ) class Registrar(DictProxy): -- cgit From 84a721d408c307add34440b7a68a9c7a858c52e3 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 18 Sep 2008 20:35:23 +0000 Subject: 294: NameSpace no longer subclasses from DictProxy; NameSpace.__getitem__() now works with int and slice objects --- ipalib/plugable.py | 65 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 16 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 66e5fada..88b60a2b 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -473,7 +473,7 @@ def check_name(name): return name -class NameSpace(DictProxy): +class NameSpace(ReadOnly): """ A read-only namespace with handy container behaviours. @@ -523,36 +523,69 @@ class NameSpace(DictProxy): :param members: An iterable providing the members. :param sort: Whether to sort the members by member name. """ - self.__members = tuple(members) self.__sort = check_type(sort, bool, 'sort') - names = (m.name for m in self.__members) if self.__sort: - self.__names = tuple(sorted(names)) + self.__members = tuple(sorted(members, key=lambda m: m.name)) else: - self.__names = tuple(names) - super(NameSpace, self).__init__( - dict(self.__member_iter()) - ) - - def __member_iter(self): - """ - Helper method called only from `NameSpace.__init__()`. - """ + self.__members = tuple(members) + self.__names = tuple(m.name for m in self.__members) + self.__map = dict() for member in self.__members: name = check_name(member.name) + assert name not in self.__map, 'already has key %r' % name + self.__map[name] = member assert not hasattr(self, name), 'already has attribute %r' % name setattr(self, name, member) - yield (name, member) + lock(self) + + def __len__(self): + """ + Returns the number of members. + """ + return len(self.__members) def __iter__(self): """ - Iterates through member names. + Iterates through the member names. - In this instance was created with ``sort=True``, + If this instance was created with ``sort=True``, the names will be in + alphabetical order; otherwise the names will be in the same order as + the members were passed to the constructor. """ for name in self.__names: yield name + def __call__(self): + """ + Iterates through the members. + + If this instance was created with ``sort=True``, the members will be + in alphabetical order by name; otherwise the members will be in the + same order as they were passed to the constructor. + """ + for member in self.__members: + yield member + + def __contains__(self, name): + """ + Returns True if namespace has a member named ``name``. + """ + return name in self.__map + + def __getitem__(self, spec): + """ + Returns a member by name or index, or returns a slice of members. + + :param spec: The name or index of a member, or a slice object. + """ + if type(spec) is str: + return self.__map[spec] + if type(spec) in (int, slice): + return self.__members[spec] + raise TypeError( + 'spec: must be %r, %r, or %r; got %r' % (str, int, slice, spec) + ) + def __repr__(self): """ Returns a pseudo-valid expression that could create this instance. -- cgit From 14eb96493b5b323eeee53b81a91f93508189b918 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 18 Sep 2008 21:23:05 +0000 Subject: 296: Added more to docstrings for NameSpace.__iter_() and NameSpace.__call__() --- ipalib/plugable.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 88b60a2b..dfefbe16 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -551,6 +551,8 @@ class NameSpace(ReadOnly): If this instance was created with ``sort=True``, the names will be in alphabetical order; otherwise the names will be in the same order as the members were passed to the constructor. + + This method is like an ordered version of dict.iterkeys(). """ for name in self.__names: yield name @@ -562,6 +564,8 @@ class NameSpace(ReadOnly): If this instance was created with ``sort=True``, the members will be in alphabetical order by name; otherwise the members will be in the same order as they were passed to the constructor. + + This method is like an ordered version of dict.itervalues(). """ for member in self.__members: yield member -- cgit From e524c826db12ffed029d627bd4afcfc03e7f899a Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 18 Sep 2008 21:45:25 +0000 Subject: 297: Added a better example in docstring for ReadOnly --- ipalib/plugable.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index dfefbe16..4e356783 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -48,26 +48,25 @@ class ReadOnly(object): For example: - >>> class givenname(ReadOnly): - >>> def __init__(self): - >>> self.whatever = 'some value' # Hasn't been locked yet - >>> lock(self) - >>> - >>> def finalize(self, api): - >>> # After the instance has been locked, attributes can still be - >>> # set, but only in a round-about, unconventional way: - >>> object.__setattr__(self, 'api', api) - >>> - >>> def normalize(self, value): - >>> # After the instance has been locked, trying to set an - >>> # attribute in the normal way will raise AttributeError. - >>> self.value = value # Not thread safe! - >>> return self.actually_normalize() - >>> - >>> def actually_normalize(self): - >>> # Again, this is not thread safe: - >>> return unicode(self.value).strip() + >>> ro = ReadOnly() # Initially unlocked, can setattr, delattr + >>> ro.name = 'John Doe' + >>> ro.message = 'Hello, world!' + >>> del ro.message + >>> ro.__lock__() # Now locked, cannot setattr, delattr + >>> ro.message = 'How are you?' + Traceback (most recent call last): + File "", line 1, in + File "/home/jderose/projects/freeipa2/ipalib/plugable.py", line 93, in __setattr__ + (self.__class__.__name__, name) + AttributeError: read-only: cannot set ReadOnly.message + >>> del ro.name + Traceback (most recent call last): + File "", line 1, in + File "/home/jderose/projects/freeipa2/ipalib/plugable.py", line 104, in __delattr__ + (self.__class__.__name__, name) + AttributeError: read-only: cannot del ReadOnly.name """ + __locked = False def __lock__(self): -- cgit From 81ebe078be56cef4c3d15e40d1b1fad01e67c0c0 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 18 Sep 2008 22:01:04 +0000 Subject: 298: Cleaned up docstrings in ReadOnly methods --- ipalib/plugable.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 4e356783..44653943 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -71,22 +71,25 @@ class ReadOnly(object): def __lock__(self): """ - Puts this instance into a read-only state, after which attempting to - set or delete an attribute will raise AttributeError. + Put this instance into a read-only state. + + After the instance has been locked, attempting to set or delete an + attribute will raise AttributeError. """ assert self.__locked is False, '__lock__() can only be called once' self.__locked = True def __islocked__(self): """ - Returns True if this instance is locked, False otherwise. + Return whether instance is locked. """ return self.__locked def __setattr__(self, name, value): """ - Raises an AttributeError if `ReadOnly.__lock__()` has already been - called; otherwise calls object.__setattr__(). + If unlocked, set attribute named ``name`` to ``value``. + + If this instance is locked, AttributeError will be raised. """ if self.__locked: raise AttributeError('read-only: cannot set %s.%s' % @@ -96,8 +99,9 @@ class ReadOnly(object): def __delattr__(self, name): """ - Raises an AttributeError if `ReadOnly.__lock__()` has already been - called; otherwise calls object.__delattr__(). + If unlocked, delete attribute named ``name``. + + If this instance is locked, AttributeError will be raised. """ if self.__locked: raise AttributeError('read-only: cannot del %s.%s' % -- cgit From ef0d7a71abe0d026b1b79b6dc32d17793a8d7806 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 18 Sep 2008 22:39:48 +0000 Subject: 299: Cleaned up unit tests for ReadOnly class --- ipalib/plugable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 44653943..9db4a5c6 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -81,7 +81,7 @@ class ReadOnly(object): def __islocked__(self): """ - Return whether instance is locked. + Return True if instance is locked, otherwise False. """ return self.__locked -- cgit From 5872221bd49dda962391ddfb88f22e86bf72afec Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 21 Sep 2008 21:30:19 +0000 Subject: 306: Added Plugin.set_api() method; added corresponding unit tests --- ipalib/plugable.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 9db4a5c6..19eae504 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -352,6 +352,14 @@ class Plugin(ReadOnly): assert api is not None, 'finalize() argument cannot be None' self.__api = api + def set_api(self, api): + """ + Set reference to `API` instance. + """ + assert self.__api is None, 'set_api() can only be called once' + assert api is not None, 'set_api() argument cannot be None' + self.__api = api + def __repr__(self): """ Returns a fully qualified module_name.class_name() representation that -- cgit From f73d976bdacae37557f0b2ccfa6da01ea58c685d Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 21 Sep 2008 21:50:56 +0000 Subject: 307: Split Plugin.finalize() into two steps 1) Plugin.set_api() and 2) Plugin.finalize(); updated unit tests --- ipalib/plugable.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 19eae504..725833cd 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -340,17 +340,10 @@ class Plugin(ReadOnly): return False return True - def finalize(self, api): + def finalize(self): """ - After all the plugins are instantiated, `API` calls this method, - passing itself as the only argument. This is where plugins should - check that other plugins they depend upon have actually been loaded. - - :param api: An `API` instance. """ - assert self.__api is None, 'finalize() can only be called once' - assert api is not None, 'finalize() argument cannot be None' - self.__api = api + lock(self) def set_api(self, api): """ @@ -730,7 +723,9 @@ class API(DictProxy): object.__setattr__(self, name, namespace) for plugin in instances.itervalues(): - plugin.finalize(self) - lock(plugin) + plugin.set_api(self) assert plugin.api is self + + for plugin in instances.itervalues(): + plugin.finalize() object.__setattr__(self, '_API__finalized', True) -- cgit From b206ef684388da64ee1deb37b064510705dd05bc Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 22 Sep 2008 01:28:57 +0000 Subject: 314: Completed some missing features in Command.__call__(); removed depreciated Command.print_call() method --- ipalib/plugable.py | 1 + 1 file changed, 1 insertion(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 725833cd..6e12d5c7 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -690,6 +690,7 @@ class API(DictProxy): Dynamic API object through which `Plugin` instances are accessed. """ __finalized = False + server_context = True def __init__(self, *allowed): self.__d = dict() -- cgit From f3aaf65f1c4bbee31dae9431423ab88a15eba990 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 24 Sep 2008 00:44:41 +0000 Subject: 320: plugable.API now respects the Plugin.__proxy__ flag; added test for plugins without proxy to unit tests for API --- ipalib/plugable.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 6e12d5c7..f883eb12 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -241,6 +241,7 @@ class Plugin(ReadOnly): Base class for all plugins. """ __public__ = frozenset() + __proxy__ = True __api = None def __get_name(self): @@ -709,7 +710,10 @@ class API(DictProxy): if klass not in instances: instances[klass] = klass() plugin = instances[klass] - yield PluginProxy(base, plugin) + if base.__proxy__: + yield PluginProxy(base, plugin) + else: + yield plugin for name in self.register: base = self.register[name] -- cgit From 19bbc48eb601bb942ed93776c05bf0c326970832 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 24 Sep 2008 02:52:19 +0000 Subject: 323: Added Command.run() method that dispatches to execute() or forward(); added corresponding unit tests --- ipalib/plugable.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index f883eb12..8bf90ea8 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -691,11 +691,15 @@ class API(DictProxy): Dynamic API object through which `Plugin` instances are accessed. """ __finalized = False - server_context = True - def __init__(self, *allowed): + def __init__(self, *allowed, **kw): self.__d = dict() self.register = Registrar(*allowed) + default = dict( + in_server_context=True, + ) + default.update(kw) + self.env = MagicDict(default) super(API, self).__init__(self.__d) def finalize(self): -- cgit From 3e70c3b56b29dcc9c0f6dd15eee7d4a24945944a Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 24 Sep 2008 04:44:52 +0000 Subject: 325: API.finalize() now creates instance attribtue 'plugins', which is a tuple of PluginInfo objects; renamed show_plugins cli command to namespaces; added new cli command plugins --- ipalib/plugable.py | 61 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 13 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 8bf90ea8..cd130a19 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -708,16 +708,47 @@ class API(DictProxy): """ assert not self.__finalized, 'finalize() can only be called once' - instances = {} - def plugin_iter(base, classes): - for klass in classes: - if klass not in instances: - instances[klass] = klass() - plugin = instances[klass] + class PluginInstance(object): + """ + Represents a plugin instance. + """ + + i = 0 + + def __init__(self, klass): + self.created = self.next() + self.klass = klass + self.instance = klass() + self.bases = [] + + @classmethod + def next(cls): + cls.i += 1 + return cls.i + + class PluginInfo(ReadOnly): + def __init__(self, p): + assert isinstance(p, PluginInstance) + self.created = p.created + self.name = p.klass.__name__ + self.module = str(p.klass.__module__) + self.plugin = '%s.%s' % (self.module, self.name) + self.bases = tuple(b.__name__ for b in p.bases) + lock(self) + + plugins = {} + def plugin_iter(base, subclasses): + for klass in subclasses: + assert issubclass(klass, base) + if klass not in plugins: + plugins[klass] = PluginInstance(klass) + p = plugins[klass] + assert base not in p.bases + p.bases.append(base) if base.__proxy__: - yield PluginProxy(base, plugin) + yield PluginProxy(base, p.instance) else: - yield plugin + yield p.instance for name in self.register: base = self.register[name] @@ -731,10 +762,14 @@ class API(DictProxy): self.__d[name] = namespace object.__setattr__(self, name, namespace) - for plugin in instances.itervalues(): - plugin.set_api(self) - assert plugin.api is self + for p in plugins.itervalues(): + p.instance.set_api(self) + assert p.instance.api is self - for plugin in instances.itervalues(): - plugin.finalize() + for p in plugins.itervalues(): + p.instance.finalize() object.__setattr__(self, '_API__finalized', True) + tuple(PluginInfo(p) for p in plugins.itervalues()) + object.__setattr__(self, 'plugins', + tuple(PluginInfo(p) for p in plugins.itervalues()) + ) -- cgit From f531f7da81864f135ff1a5f7d69e15fbe8a27210 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 24 Sep 2008 23:49:44 +0000 Subject: 354: Added NameSpace.__todict__() method that returns copy of NameSpace.__map; updated NameSpace unit test to also test __todict__() --- ipalib/plugable.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index cd130a19..e1d728d4 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -545,13 +545,13 @@ class NameSpace(ReadOnly): def __len__(self): """ - Returns the number of members. + Return the number of members. """ return len(self.__members) def __iter__(self): """ - Iterates through the member names. + Iterate through the member names. If this instance was created with ``sort=True``, the names will be in alphabetical order; otherwise the names will be in the same order as @@ -564,7 +564,7 @@ class NameSpace(ReadOnly): def __call__(self): """ - Iterates through the members. + Iterate through the members. If this instance was created with ``sort=True``, the members will be in alphabetical order by name; otherwise the members will be in the @@ -577,13 +577,13 @@ class NameSpace(ReadOnly): def __contains__(self, name): """ - Returns True if namespace has a member named ``name``. + Return True if namespace has a member named ``name``. """ return name in self.__map def __getitem__(self, spec): """ - Returns a member by name or index, or returns a slice of members. + Return a member by name or index, or returns a slice of members. :param spec: The name or index of a member, or a slice object. """ @@ -597,7 +597,7 @@ class NameSpace(ReadOnly): def __repr__(self): """ - Returns a pseudo-valid expression that could create this instance. + Return a pseudo-valid expression that could create this instance. """ return '%s(<%d members>, sort=%r)' % ( self.__class__.__name__, @@ -605,6 +605,12 @@ class NameSpace(ReadOnly): self.__sort, ) + def __todict__(self): + """ + Return a copy of the private dict mapping name to member. + """ + return dict(self.__map) + class Registrar(DictProxy): """ -- cgit From 023f612921b4d9cbd15e3148d09c02932a61d73e Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 25 Sep 2008 02:13:16 +0000 Subject: 361: Implemented crud.Add.get_options() method; added corresponding unit tests --- ipalib/plugable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index e1d728d4..cc61cbe9 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -432,7 +432,7 @@ class PluginProxy(SetProxy): """ if key in self.__public__: return getattr(self.__target, key) - raise KeyError('no public attribute %r' % key) + raise KeyError('no public attribute %s.%s' % (self.name, key)) def __getattr__(self, name): """ @@ -441,7 +441,7 @@ class PluginProxy(SetProxy): """ if name in self.__public__: return getattr(self.__target, name) - raise AttributeError('no public attribute %r' % name) + raise AttributeError('no public attribute %s.%s' % (self.name, name)) def __call__(self, *args, **kw): """ -- cgit From afdc72103847fc27efd00f8cc97a7320909ff6a0 Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Mon, 29 Sep 2008 17:41:30 +0200 Subject: Add support for environment variables, change tests accordingly --- ipalib/plugable.py | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index cc61cbe9..98a74dfa 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -692,20 +692,45 @@ class Registrar(DictProxy): self.__registered.add(klass) +class Environment(dict): + def __getitem__(self, key): + val = super(Environment, self).__getitem__(key) + if hasattr(val, 'get_value'): + return val.get_value() + else: + return val + + def __setitem__(self, key, value): + if key in self: + super_value = super(Environment, self).__getitem__(key) + + if key in self and hasattr(super_value, 'set_value'): + super_value.set_value(value) + else: + super(Environment, self).__setitem__(key, value) + + def __getattr__(self, name): + return self[name] + + def __setattr__(self, name, value): + self[name] = value + + def update(self, d): + assert isinstance(d, dict) + for key, value in d.iteritems(): + self[key] = value + + class API(DictProxy): """ Dynamic API object through which `Plugin` instances are accessed. """ __finalized = False - def __init__(self, *allowed, **kw): + def __init__(self, default_env, *allowed): self.__d = dict() self.register = Registrar(*allowed) - default = dict( - in_server_context=True, - ) - default.update(kw) - self.env = MagicDict(default) + self.env = Environment(default_env) super(API, self).__init__(self.__d) def finalize(self): -- cgit From 149429f3057e3ae934e660e3276c9e8d3c935d17 Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Thu, 2 Oct 2008 20:24:05 +0200 Subject: Environment is now subclassed from object, rather then dict. Added tests for Environment and config.py --- ipalib/plugable.py | 54 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 18 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 98a74dfa..ffe4a11f 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -692,32 +692,50 @@ class Registrar(DictProxy): self.__registered.add(klass) -class Environment(dict): +class Environment(object): + def __init__(self): + object.__setattr__(self, '_Environment__map', {}) + + def __setattr__(self, name, value): + self[name] = value + + def __getattr__(self, name): + return self[name] + + def __delattr__(self, name): + del self[name] + def __getitem__(self, key): - val = super(Environment, self).__getitem__(key) + val = self.__map[key] if hasattr(val, 'get_value'): return val.get_value() else: return val def __setitem__(self, key, value): - if key in self: - super_value = super(Environment, self).__getitem__(key) - - if key in self and hasattr(super_value, 'set_value'): - super_value.set_value(value) - else: - super(Environment, self).__setitem__(key, value) + if key in self or hasattr(self, key): + raise AttributeError('cannot overwrite %s.%s' % + (self.__class__.__name__, key) + ) + self.__map[key] = value + + def __delitem__(self, key): + raise AttributeError('read-only: cannot del %s.%s' % + (self.__class__.__name__, key) + ) - def __getattr__(self, name): - return self[name] + def __contains__(self, key): + return key in self.__map - def __setattr__(self, name, value): - self[name] = value + def __iter__(self): + for key in self.__map: + yield key - def update(self, d): - assert isinstance(d, dict) - for key, value in d.iteritems(): + def update(self, new_vals, ignore_errors = False): + assert type(new_vals) == dict + for key, value in new_vals.iteritems(): + if key in self and ignore_errors: + continue self[key] = value @@ -727,10 +745,10 @@ class API(DictProxy): """ __finalized = False - def __init__(self, default_env, *allowed): + def __init__(self, *allowed): self.__d = dict() self.register = Registrar(*allowed) - self.env = Environment(default_env) + self.env = Environment() super(API, self).__init__(self.__d) def finalize(self): -- cgit From 75bad44c27bff471c03ddc86283506f53f47520c Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Fri, 10 Oct 2008 05:23:00 -0400 Subject: Enable the verbose flag to pass thru xmlrpc --- ipalib/plugable.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index ffe4a11f..87f96876 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -738,6 +738,8 @@ class Environment(object): continue self[key] = value + def get(self, name, default=None): + return self.__map.get(name, default) class API(DictProxy): """ -- cgit From 20fa90cfb6e954040de47551762dfbb7680dba51 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 14 Oct 2008 00:39:23 -0600 Subject: Some small cleanup on Environment, filled in docstrings --- ipalib/plugable.py | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 87f96876..4a2658a7 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -693,19 +693,37 @@ class Registrar(DictProxy): class Environment(object): + """ + A mapping object used to store the environment variables. + """ + def __init__(self): object.__setattr__(self, '_Environment__map', {}) - def __setattr__(self, name, value): - self[name] = value - def __getattr__(self, name): + """ + Return the attribute named ``name``. + """ return self[name] + def __setattr__(self, name, value): + """ + Set the attribute named ``name`` to ``value``. + """ + self[name] = value + def __delattr__(self, name): - del self[name] + """ + Raise AttributeError (deletion is not allowed). + """ + raise AttributeError('cannot del %s.%s' % + (self.__class__.__name__, name) + ) def __getitem__(self, key): + """ + Return the value corresponding to ``key``. + """ val = self.__map[key] if hasattr(val, 'get_value'): return val.get_value() @@ -713,22 +731,26 @@ class Environment(object): return val def __setitem__(self, key, value): + """ + Set the item at ``key`` to ``value``. + """ if key in self or hasattr(self, key): raise AttributeError('cannot overwrite %s.%s' % (self.__class__.__name__, key) ) self.__map[key] = value - def __delitem__(self, key): - raise AttributeError('read-only: cannot del %s.%s' % - (self.__class__.__name__, key) - ) - def __contains__(self, key): + """ + Return True if instance contains ``key``; otherwise return False. + """ return key in self.__map def __iter__(self): - for key in self.__map: + """ + Iterate through keys in ascending order. + """ + for key in sorted(self.__map): yield key def update(self, new_vals, ignore_errors = False): -- cgit From 3a80297b04d6fbfd2367ec76c5651d20293adccc Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Fri, 17 Oct 2008 22:55:03 +0200 Subject: Reworking Environment, moved it to config.py --- ipalib/plugable.py | 72 +----------------------------------------------------- 1 file changed, 1 insertion(+), 71 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 4a2658a7..98aa4172 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -29,6 +29,7 @@ import re import inspect import errors from errors import check_type, check_isinstance +from config import Environment class ReadOnly(object): @@ -692,77 +693,6 @@ class Registrar(DictProxy): self.__registered.add(klass) -class Environment(object): - """ - A mapping object used to store the environment variables. - """ - - def __init__(self): - object.__setattr__(self, '_Environment__map', {}) - - def __getattr__(self, name): - """ - Return the attribute named ``name``. - """ - return self[name] - - def __setattr__(self, name, value): - """ - Set the attribute named ``name`` to ``value``. - """ - self[name] = value - - def __delattr__(self, name): - """ - Raise AttributeError (deletion is not allowed). - """ - raise AttributeError('cannot del %s.%s' % - (self.__class__.__name__, name) - ) - - def __getitem__(self, key): - """ - Return the value corresponding to ``key``. - """ - val = self.__map[key] - if hasattr(val, 'get_value'): - return val.get_value() - else: - return val - - def __setitem__(self, key, value): - """ - Set the item at ``key`` to ``value``. - """ - if key in self or hasattr(self, key): - raise AttributeError('cannot overwrite %s.%s' % - (self.__class__.__name__, key) - ) - self.__map[key] = value - - def __contains__(self, key): - """ - Return True if instance contains ``key``; otherwise return False. - """ - return key in self.__map - - def __iter__(self): - """ - Iterate through keys in ascending order. - """ - for key in sorted(self.__map): - yield key - - def update(self, new_vals, ignore_errors = False): - assert type(new_vals) == dict - for key, value in new_vals.iteritems(): - if key in self and ignore_errors: - continue - self[key] = value - - def get(self, name, default=None): - return self.__map.get(name, default) - class API(DictProxy): """ Dynamic API object through which `Plugin` instances are accessed. -- cgit From f1eb74e22cadf3a9f4ac991e0f8b922f6fb56d1e Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 17 Oct 2008 20:50:34 -0600 Subject: make-test now runs doctests also; fixed several broken doctests --- ipalib/plugable.py | 67 +++++++++++++++++++++++++----------------------------- 1 file changed, 31 insertions(+), 36 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 98aa4172..2a1bdb62 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -208,14 +208,15 @@ class DictProxy(SetProxy): class MagicDict(DictProxy): """ - A read-only mapping container whose values can also be accessed as - attributes. + A mapping container whose values can be accessed as attributes. - For example, assuming ``magic`` is a MagicDict instance that contains the - key ``name``, you could do this: + For example: - >>> magic[name] is getattr(magic, name) - True + >>> magic = MagicDict({'the_key': 'the value'}) + >>> magic['the_key'] + 'the value' + >>> magic.the_key + 'the value' This container acts as a proxy to an actual mapping object (a dict) that is passed to the constructor. To the extent possible in Python, this @@ -270,35 +271,27 @@ class Plugin(ReadOnly): @classmethod def implements(cls, arg): """ - Returns True if this cls.__public__ frozenset contains `arg`; - returns False otherwise. + Return True if class implements ``arg``. - There are three different ways this can be called: + There are three different ways this method can be called: With a argument, e.g.: - >>> class base(ProxyTarget): - >>> __public__ = frozenset(['some_attr', 'another_attr']) - >>> base.implements('some_attr') + >>> class base(Plugin): + ... __public__ = frozenset(['attr1', 'attr2']) + ... + >>> base.implements('attr1') + True + >>> base.implements('attr2') True - >>> base.implements('an_unknown_attribute') + >>> base.implements('attr3') False With a argument, e.g.: - >>> base.implements(frozenset(['some_attr'])) - True - >>> base.implements(frozenset(['some_attr', 'an_unknown_attribute'])) - False - With any object that has a `__public__` attribute that is , e.g.: - >>> class whatever(object): - >>> __public__ = frozenset(['another_attr']) - >>> base.implements(whatever) - True - Unlike ProxyTarget.implemented_by(), this returns an abstract answer because only the __public__ frozenset is checked... a ProxyTarget need not itself have attributes for all names in __public__ @@ -493,34 +486,36 @@ class NameSpace(ReadOnly): classes or instances, and of any type. The members can be accessed as attributes on the NameSpace instance or - through a dictionary interface. For example, assuming ``obj`` is a member - in the NameSpace instance ``namespace``, you could do this: + through a dictionary interface. For example: - >>> obj is getattr(namespace, obj.name) # As attribute + >>> class obj(object): + ... name = 'my_obj' + ... + >>> namespace = NameSpace([obj]) + >>> obj is getattr(namespace, 'my_obj') # As attribute True - >>> obj is namespace[obj.name] # As dictionary item + >>> obj is namespace['my_obj'] # As dictionary item True Here is a more detailed example: >>> class member(object): ... def __init__(self, i): + ... self.i = i ... self.name = 'member_%d' % i + ... def __repr__(self): + ... return 'member(%d)' % self.i ... - >>> def get_members(cnt): - ... for i in xrange(cnt): - ... yield member(i) - ... - >>> namespace = NameSpace(get_members(2)) + >>> namespace = NameSpace(member(i) for i in xrange(3)) >>> namespace.member_0 is namespace['member_0'] True >>> len(namespace) # Returns the number of members in namespace - 2 + 3 >>> list(namespace) # As iterable, iterates through the member names - ['member_0', 'member_1'] + ['member_0', 'member_1', 'member_2'] >>> list(namespace()) # Calling a NameSpace iterates through the members - [<__main__.member object at 0x836710>, <__main__.member object at 0x836750>] - >>> 'member_1' in namespace # NameSpace.__contains__() + [member(0), member(1), member(2)] + >>> 'member_1' in namespace # Does namespace contain 'member_1'? True """ -- cgit From 77a378bd61edf50b16b48c9ca73a50ecafd94e09 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 18 Oct 2008 01:02:31 -0600 Subject: Some PEP-257 and reStructuredText cleanup in plugable.py --- ipalib/plugable.py | 89 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 37 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 2a1bdb62..0ece9451 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -57,7 +57,7 @@ class ReadOnly(object): >>> ro.message = 'How are you?' Traceback (most recent call last): File "", line 1, in - File "/home/jderose/projects/freeipa2/ipalib/plugable.py", line 93, in __setattr__ + File ".../ipalib/plugable.py", line 93, in __setattr__ (self.__class__.__name__, name) AttributeError: read-only: cannot set ReadOnly.message >>> del ro.name @@ -113,7 +113,7 @@ class ReadOnly(object): def lock(readonly): """ - Locks a `ReadOnly` instance. + Lock a `ReadOnly` instance. This is mostly a convenience function to call `ReadOnly.__lock__()`. It also verifies that the locking worked using `ReadOnly.__islocked__()` @@ -148,23 +148,22 @@ class SetProxy(ReadOnly): def __len__(self): """ - Returns the number of items in this container. + Return the number of items in this container. """ return len(self.__s) def __iter__(self): """ - Iterates (in ascending order) through the items (or keys) in this - container. + Iterate (in ascending order) through keys. """ for key in sorted(self.__s): yield key def __contains__(self, key): """ - Returns True if this container contains ``key``, False otherwise. + Return True if this container contains ``key``. - :param key: The item (or key) to test for membership. + :param key: The key to test for membership. """ return key in self.__s @@ -191,7 +190,7 @@ class DictProxy(SetProxy): def __getitem__(self, key): """ - Returns the value corresponding to ``key``. + Return the value corresponding to ``key``. :param key: The key of the value you wish to retrieve. """ @@ -199,8 +198,7 @@ class DictProxy(SetProxy): def __call__(self): """ - Iterates (in ascending order by key) through the values in this - container. + Iterate (in ascending order by key) through values. """ for key in self: yield self.__d[key] @@ -228,7 +226,7 @@ class MagicDict(DictProxy): def __getattr__(self, name): """ - Returns the value corresponding to ``name``. + Return the value corresponding to ``name``. :param name: The name of the attribute you wish to retrieve. """ @@ -262,8 +260,9 @@ class Plugin(ReadOnly): def __get_api(self): """ - Returns the `API` instance passed to `finalize()`, or - or returns None if `finalize()` has not yet been called. + Return `API` instance passed to `finalize()`. + + If `finalize()` has not yet been called, None is returned. """ return self.__api api = property(__get_api) @@ -271,7 +270,7 @@ class Plugin(ReadOnly): @classmethod def implements(cls, arg): """ - Return True if class implements ``arg``. + Return True if this class implements ``arg``. There are three different ways this method can be called: @@ -311,12 +310,14 @@ class Plugin(ReadOnly): @classmethod def implemented_by(cls, arg): """ - Returns True if: + Return True if ``arg`` implements public interface of this class. + + This classmethod returns True if: - 1. ``arg`` is an instance of or subclass of this class, and + 1. ``arg`` is an instance of or subclass of this class, and - 2. ``arg`` (or ``arg.__class__`` if instance) has an attribute for - each name in this class's ``__public__`` frozenset + 2. ``arg`` (or ``arg.__class__`` if instance) has an attribute for + each name in this class's ``__public__`` frozenset. Otherwise, returns False. @@ -350,8 +351,10 @@ class Plugin(ReadOnly): def __repr__(self): """ - Returns a fully qualified module_name.class_name() representation that - could be used to construct this Plugin instance. + Return 'module_name.class_name()' representation. + + This representation could be used to instantiate this Plugin + instance given the appropriate environment. """ return '%s.%s()' % ( self.__class__.__module__, @@ -361,13 +364,14 @@ class Plugin(ReadOnly): class PluginProxy(SetProxy): """ - Allows access to only certain attributes on a `Plugin`. + Allow access to only certain attributes on a `Plugin`. Think of a proxy as an agreement that "I will have at most these attributes". This is different from (although similar to) an interface, which can be thought of as an agreement that "I will have at least these attributes". """ + __slots__ = ( '__base', '__target', @@ -403,26 +407,33 @@ class PluginProxy(SetProxy): def implements(self, arg): """ - Returns True if this proxy implements `arg`. Calls the corresponding - classmethod on ProxyTarget. + Return True if plugin being proxied implements ``arg``. + + This method simply calls the corresponding `Plugin.implements` + classmethod. - Unlike ProxyTarget.implements(), this is not a classmethod as a Proxy - only implements anything as an instance. + Unlike `Plugin.implements`, this is not a classmethod as a + `PluginProxy` can only implement anything as an instance. """ return self.__base.implements(arg) def __clone__(self, name_attr): """ - Returns a Proxy instance identical to this one except the proxy name - might be derived from a different attribute on the target. The same - base and target will be used. + Return a `PluginProxy` instance similar to this one. + + The new `PluginProxy` returned will be identical to this one except + the proxy name might be derived from a different attribute on the + target `Plugin`. The same base and target will be used. """ return self.__class__(self.__base, self.__target, name_attr) def __getitem__(self, key): """ - If this proxy allows access to an attribute named ``key``, return that - attribute. + Return attribute named ``key`` on target `Plugin`. + + If this proxy allows access to an attribute named ``key``, that + attribute will be returned. If access is not allowed, + KeyError will be raised. """ if key in self.__public__: return getattr(self.__target, key) @@ -430,8 +441,11 @@ class PluginProxy(SetProxy): def __getattr__(self, name): """ - If this proxy allows access to an attribute named ``name``, return - that attribute. + Return attribute named ``name`` on target `Plugin`. + + If this proxy allows access to an attribute named ``name``, that + attribute will be returned. If access is not allowed, + AttributeError will be raised. """ if name in self.__public__: return getattr(self.__target, name) @@ -439,15 +453,16 @@ class PluginProxy(SetProxy): def __call__(self, *args, **kw): """ - Attempts to call target.__call__(); raises KeyError if `__call__` is - not an attribute this proxy allows access to. + Call target `Plugin` and return its return value. + + If `__call__` is not an attribute this proxy allows access to, + KeyError is raised. """ return self['__call__'](*args, **kw) def __repr__(self): """ - Returns a Python expression that could be used to construct this Proxy - instance given the appropriate environment. + Return a Python expression that could create this instance. """ return '%s(%s, %r)' % ( self.__class__.__name__, @@ -458,7 +473,7 @@ class PluginProxy(SetProxy): def check_name(name): """ - Verifies that ``name`` is suitable for a `NameSpace` member name. + Verify that ``name`` is suitable for a `NameSpace` member name. Raises `errors.NameSpaceError` if ``name`` is not a valid Python identifier suitable for use as the name of `NameSpace` member. -- cgit From 5e0a0fa745433ef11d7c4ce2afbcbef401c96645 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 21 Oct 2008 08:47:08 -0600 Subject: In second example in NameSpace docstring, renamed 'member' class to 'Member' to make the example clearer --- ipalib/plugable.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 0ece9451..fd87586d 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -514,14 +514,14 @@ class NameSpace(ReadOnly): Here is a more detailed example: - >>> class member(object): + >>> class Member(object): ... def __init__(self, i): ... self.i = i ... self.name = 'member_%d' % i ... def __repr__(self): - ... return 'member(%d)' % self.i + ... return 'Member(%d)' % self.i ... - >>> namespace = NameSpace(member(i) for i in xrange(3)) + >>> namespace = NameSpace(Member(i) for i in xrange(3)) >>> namespace.member_0 is namespace['member_0'] True >>> len(namespace) # Returns the number of members in namespace @@ -529,7 +529,7 @@ class NameSpace(ReadOnly): >>> list(namespace) # As iterable, iterates through the member names ['member_0', 'member_1', 'member_2'] >>> list(namespace()) # Calling a NameSpace iterates through the members - [member(0), member(1), member(2)] + [Member(0), Member(1), Member(2)] >>> 'member_1' in namespace # Does namespace contain 'member_1'? True """ -- cgit From 6b8abb0d78a8d86d7ca52083a267fe226bf74656 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 26 Oct 2008 23:28:06 -0600 Subject: Implemented placeholder API.bootstrap() method; added API __doing(), __do_if_not_done(), isdone() methods borrowed from Env; API.finalize() now cascades call to API.bootstrap() --- ipalib/plugable.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index fd87586d..f0121433 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -707,19 +707,40 @@ class API(DictProxy): """ Dynamic API object through which `Plugin` instances are accessed. """ - __finalized = False def __init__(self, *allowed): self.__d = dict() + self.__done = set() self.register = Registrar(*allowed) self.env = Environment() super(API, self).__init__(self.__d) + def __doing(self, name): + if name in self.__done: + raise StandardError( + '%s.%s() already called' % (self.__class__.__name__, name) + ) + self.__done.add(name) + + def __do_if_not_done(self, name): + if name not in self.__done: + getattr(self, name)() + + def isdone(self, name): + return name in self.__done + + def bootstrap(self, **overrides): + """ + Initialize environment variables needed by built-in plugins. + """ + self.__doing('bootstrap') + def finalize(self): """ Finalize the registration, instantiate the plugins. """ - assert not self.__finalized, 'finalize() can only be called once' + self.__doing('finalize') + self.__do_if_not_done('bootstrap') class PluginInstance(object): """ -- cgit From 4fe03f5e17dfe9d4478a75dfada2282535c989fe Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 26 Oct 2008 23:53:44 -0600 Subject: Added API.load_plugins() place-holder, which cascades call to API.bootstrap() --- ipalib/plugable.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index f0121433..4c0b175f 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -735,9 +735,22 @@ class API(DictProxy): """ self.__doing('bootstrap') + def load_plugins(self, dry_run=False): + """ + Load plugins from all standard locations. + + `API.bootstrap` will automatically be called if it hasn't been + already. + """ + self.__doing('load_plugins') + self.__do_if_not_done('bootstrap') + def finalize(self): """ Finalize the registration, instantiate the plugins. + + `API.bootstrap` will automatically be called if it hasn't been + already. """ self.__doing('finalize') self.__do_if_not_done('bootstrap') -- cgit From 03accc5fb382777d9bbdb245f3211d5c06489f6e Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 27 Oct 2008 00:23:43 -0600 Subject: Copied plugin loading function from load_plugins.py to util.py; API.load_plugins() method now calls functions in util --- ipalib/plugable.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 4c0b175f..36721157 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -30,6 +30,7 @@ import inspect import errors from errors import check_type, check_isinstance from config import Environment +import util class ReadOnly(object): @@ -744,6 +745,10 @@ class API(DictProxy): """ self.__doing('load_plugins') self.__do_if_not_done('bootstrap') + if dry_run: + return + util.import_plugins_subpackage('ipalib') + util.import_plugins_subpackage('ipa_server') def finalize(self): """ -- cgit From d76202fea37e63fbc660ed2cf2059f455b8e2213 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 27 Oct 2008 01:35:40 -0600 Subject: API.env is now an Env instance rather than an Environment instance --- ipalib/plugable.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 36721157..59484989 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -29,7 +29,8 @@ import re import inspect import errors from errors import check_type, check_isinstance -from config import Environment +from config import Environment, Env +import constants import util @@ -713,7 +714,7 @@ class API(DictProxy): self.__d = dict() self.__done = set() self.register = Registrar(*allowed) - self.env = Environment() + self.env = Env super(API, self).__init__(self.__d) def __doing(self, name): -- cgit From e6254026fe73c423d357a2fa1489de35475da46c Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 27 Oct 2008 15:19:49 -0600 Subject: Implemented basic CLI.bootstrap(); added corresponding unit tests --- ipalib/plugable.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 59484989..f704077a 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -714,7 +714,7 @@ class API(DictProxy): self.__d = dict() self.__done = set() self.register = Registrar(*allowed) - self.env = Env + self.env = Env() super(API, self).__init__(self.__d) def __doing(self, name): @@ -736,6 +736,7 @@ class API(DictProxy): Initialize environment variables needed by built-in plugins. """ self.__doing('bootstrap') + self.env._bootstrap(**overrides) def load_plugins(self, dry_run=False): """ -- cgit From bb9691099b7b025fc491279314d8803f4fa3b571 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 27 Oct 2008 15:36:41 -0600 Subject: API.bootstrap() now calls Env._finalize_core(); updated unit tests --- ipalib/plugable.py | 1 + 1 file changed, 1 insertion(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index f704077a..6fe22429 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -737,6 +737,7 @@ class API(DictProxy): """ self.__doing('bootstrap') self.env._bootstrap(**overrides) + self.env._finalize_core(**dict(constants.DEFAULT_CONFIG)) def load_plugins(self, dry_run=False): """ -- cgit From 83d6c95e4636049a5bcedb533ad49f6e2cf79dfe Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 27 Oct 2008 23:39:43 -0600 Subject: API.load_plugins() no longer takes dry_run=False kwarg and instead checks in env.mode == 'unit_test' to decide whether to load the plugins; it also only loads ipa_server.plugins in env.in_server is True --- ipalib/plugable.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 6fe22429..8d689f7d 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -739,7 +739,7 @@ class API(DictProxy): self.env._bootstrap(**overrides) self.env._finalize_core(**dict(constants.DEFAULT_CONFIG)) - def load_plugins(self, dry_run=False): + def load_plugins(self): """ Load plugins from all standard locations. @@ -748,10 +748,11 @@ class API(DictProxy): """ self.__doing('load_plugins') self.__do_if_not_done('bootstrap') - if dry_run: + if self.env.mode == 'unit_test': return util.import_plugins_subpackage('ipalib') - util.import_plugins_subpackage('ipa_server') + if self.env.in_server: + util.import_plugins_subpackage('ipa_server') def finalize(self): """ -- cgit From 316bd855d5720f4babfb79d20c391de3f8958a60 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 28 Oct 2008 01:39:02 -0600 Subject: Added util.configure_logging() function; API.bootstrap() now calls util.configure_logging() --- ipalib/plugable.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 8d689f7d..dd74dc08 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -738,6 +738,13 @@ class API(DictProxy): self.__doing('bootstrap') self.env._bootstrap(**overrides) self.env._finalize_core(**dict(constants.DEFAULT_CONFIG)) + if self.env.mode == 'unit_test': + return + logger = util.configure_logging( + self.env.log, + self.env.verbose, + ) + object.__setattr__(self, 'log', 'logger') def load_plugins(self): """ -- cgit From fbcb55bd11d17dbff8ec3c7c99cf7b3bb91d3752 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 28 Oct 2008 02:10:56 -0600 Subject: lite-xmlrpc.py now uses api.bootstrap() property, logs to api.logger --- ipalib/plugable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index dd74dc08..b0ba32b7 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -744,7 +744,7 @@ class API(DictProxy): self.env.log, self.env.verbose, ) - object.__setattr__(self, 'log', 'logger') + object.__setattr__(self, 'logger', logger) def load_plugins(self): """ -- cgit From ddb5449c7faabbd4c1b71adfe84c386b943a163f Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 30 Oct 2008 01:11:33 -0600 Subject: Did some initial work for Context plugins --- ipalib/plugable.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index b0ba32b7..9ddcb30f 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -27,6 +27,7 @@ http://docs.python.org/ref/sequence-types.html import re import inspect +import threading import errors from errors import check_type, check_isinstance from config import Environment, Env @@ -705,6 +706,25 @@ class Registrar(DictProxy): self.__registered.add(klass) +class LazyContext(object): + """ + On-demand creation of thread-local context attributes. + """ + + def __init__(self, api): + self.__api = api + self.__context = threading.local() + + def __getattr__(self, name): + if name not in self.__context.__dict__: + if name not in self.__api.Context: + raise AttributeError('no Context plugin for %r' % name) + value = self.__api.Context[name].get_value() + self.__context.__dict__[name] = value + return self.__context.__dict__[name] + + + class API(DictProxy): """ Dynamic API object through which `Plugin` instances are accessed. @@ -715,6 +735,7 @@ class API(DictProxy): self.__done = set() self.register = Registrar(*allowed) self.env = Env() + self.context = LazyContext(self) super(API, self).__init__(self.__d) def __doing(self, name): -- cgit From 6879140db790a23a8782f7200400f2b58a69f6a0 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 30 Oct 2008 02:20:28 -0600 Subject: Added ipalib.plugins.f_misc with new 'context' Command; moved 'env' Command from cli to f_misc --- ipalib/plugable.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 9ddcb30f..2f86fa22 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -723,6 +723,9 @@ class LazyContext(object): self.__context.__dict__[name] = value return self.__context.__dict__[name] + def __getitem__(self, key): + return self.__getattr__(key) + class API(DictProxy): -- cgit From 3076cb4d2fa1be023a1c72d70cbdf5024047ff2a Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 30 Oct 2008 14:11:24 -0600 Subject: Plugin.set_api() now sets convience instance attributes from api for env, context, log, and all NameSpace --- ipalib/plugable.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 2f86fa22..d10ff797 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -351,6 +351,15 @@ class Plugin(ReadOnly): assert self.__api is None, 'set_api() can only be called once' assert api is not None, 'set_api() argument cannot be None' self.__api = api + if not isinstance(api, API): + return + for name in api: + assert not hasattr(self, name) + setattr(self, name, api[name]) + for name in ('env', 'context', 'log'): + if hasattr(api, name): + assert not hasattr(self, name) + setattr(self, name, getattr(api, name)) def __repr__(self): """ @@ -768,7 +777,7 @@ class API(DictProxy): self.env.log, self.env.verbose, ) - object.__setattr__(self, 'logger', logger) + object.__setattr__(self, 'log', logger) def load_plugins(self): """ -- cgit From 140458cfc694a1b77100c81a58600365627e7758 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 31 Oct 2008 12:29:59 -0600 Subject: API.finalize() now cascades call to API.load_plugins() --- ipalib/plugable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index d10ff797..de63e4ca 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -802,7 +802,7 @@ class API(DictProxy): already. """ self.__doing('finalize') - self.__do_if_not_done('bootstrap') + self.__do_if_not_done('load_plugins') class PluginInstance(object): """ -- cgit From cdfb7bfd5ebc1f5e44f4ee60cec14354040a0a72 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 31 Oct 2008 13:27:42 -0600 Subject: Logging is now configured in API.bootstrap(); removed depreciated util.configure_logging() function --- ipalib/plugable.py | 52 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 9 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index de63e4ca..9e612d68 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -28,10 +28,13 @@ http://docs.python.org/ref/sequence-types.html import re import inspect import threading +import logging +import os +from os import path import errors from errors import check_type, check_isinstance from config import Environment, Env -import constants +from constants import LOGGING_FILE_FORMAT, LOGGING_CONSOLE_FORMAT, DEFAULT_CONFIG import util @@ -766,18 +769,49 @@ class API(DictProxy): def bootstrap(self, **overrides): """ - Initialize environment variables needed by built-in plugins. + Initialize environment variables and logging. """ self.__doing('bootstrap') self.env._bootstrap(**overrides) - self.env._finalize_core(**dict(constants.DEFAULT_CONFIG)) + self.env._finalize_core(**dict(DEFAULT_CONFIG)) + log = logging.getLogger('ipa') + object.__setattr__(self, 'log', log) + if self.env.debug: + log.setLevel(logging.DEBUG) + else: + log.setLevel(logging.INFO) + + # Add stderr handler: + stderr = logging.StreamHandler() + stderr.setFormatter(logging.Formatter(LOGGING_CONSOLE_FORMAT)) + if self.env.debug: + level = logging.DEBUG + elif self.env.verbose: + level = logging.INFO + else: + level = logging.WARNING + stderr.setLevel(level) + log.addHandler(stderr) + + # Add file handler: if self.env.mode == 'unit_test': - return - logger = util.configure_logging( - self.env.log, - self.env.verbose, - ) - object.__setattr__(self, 'log', logger) + return # But not if in unit-test mode + log_dir = path.dirname(self.env.log) + if not path.isdir(log_dir): + try: + os.makedirs(log_dir) + except OSError: + log.warn('Could not create log_dir %r', log_dir) + return + handler = logging.FileHandler(self.env.log) + handler.setFormatter(logging.Formatter(LOGGING_FILE_FORMAT)) + if self.env.debug: + level = logging.DEBUG + else: + level = logging.INFO + handler.setLevel(level) + log.addHandler(handler) + def load_plugins(self): """ -- cgit From a23d41a57f43c3a0f298d3918ae1712181fa544e Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 31 Oct 2008 18:17:08 -0600 Subject: Reoganized global option functionality to it is easy for any script to use the environment-related global options; lite-xmlrpc.py now uses same global options --- ipalib/plugable.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 9e612d68..f552b61f 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -26,6 +26,7 @@ http://docs.python.org/ref/sequence-types.html """ import re +import sys import inspect import threading import logging @@ -38,6 +39,7 @@ from constants import LOGGING_FILE_FORMAT, LOGGING_CONSOLE_FORMAT, DEFAULT_CONFI import util + class ReadOnly(object): """ Base class for classes with read-only attributes. @@ -812,6 +814,31 @@ class API(DictProxy): handler.setLevel(level) log.addHandler(handler) + def bootstrap_from_options(self, options=None, context=None): + if options is None: + parser = util.add_global_options() + (options, args) = parser.parse_args( + list(s.decode('utf-8') for s in sys.argv[1:]) + ) + overrides = {} + if options.env is not None: + assert type(options.env) is list + for item in options.env: + try: + (key, value) = item.split('=', 1) + except ValueError: + # FIXME: this should raise an IPA exception with an + # error code. + # --Jason, 2008-10-31 + pass + overrides[str(key.strip())] = value.strip() + for key in ('conf', 'debug', 'verbose'): + value = getattr(options, key, None) + if value is not None: + overrides[key] = value + if context is not None: + overrides['context'] = context + self.bootstrap(**overrides) def load_plugins(self): """ -- cgit From 5269d1396c2e299d7fc66b55df7a84d482927549 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 31 Oct 2008 18:55:32 -0600 Subject: Logging formats are now env variables; added log_format_stderr_debug format used when env.debug is True --- ipalib/plugable.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index f552b61f..ccaf1f15 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -35,7 +35,7 @@ from os import path import errors from errors import check_type, check_isinstance from config import Environment, Env -from constants import LOGGING_FILE_FORMAT, LOGGING_CONSOLE_FORMAT, DEFAULT_CONFIG +from constants import DEFAULT_CONFIG import util @@ -785,14 +785,15 @@ class API(DictProxy): # Add stderr handler: stderr = logging.StreamHandler() - stderr.setFormatter(logging.Formatter(LOGGING_CONSOLE_FORMAT)) + format = self.env.log_format_stderr if self.env.debug: - level = logging.DEBUG + format = self.env.log_format_stderr_debug + stderr.setLevel(logging.DEBUG) elif self.env.verbose: - level = logging.INFO + stderr.setLevel(logging.INFO) else: - level = logging.WARNING - stderr.setLevel(level) + stderr.setLevel(logging.WARNING) + stderr.setFormatter(logging.Formatter(format)) log.addHandler(stderr) # Add file handler: @@ -806,12 +807,11 @@ class API(DictProxy): log.warn('Could not create log_dir %r', log_dir) return handler = logging.FileHandler(self.env.log) - handler.setFormatter(logging.Formatter(LOGGING_FILE_FORMAT)) + handler.setFormatter(logging.Formatter(self.env.log_format_file)) if self.env.debug: - level = logging.DEBUG + handler.setLevel(logging.DEBUG) else: - level = logging.INFO - handler.setLevel(level) + handler.setLevel(logging.INFO) log.addHandler(handler) def bootstrap_from_options(self, options=None, context=None): -- cgit From 5e5a83e4e84d2e9a5d6d987056199a8ed83978b8 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 31 Oct 2008 19:03:07 -0600 Subject: Renamed API.bootstrap_from_options() to bootstrap_with_global_options() --- ipalib/plugable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index ccaf1f15..64a9d835 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -814,7 +814,7 @@ class API(DictProxy): handler.setLevel(logging.INFO) log.addHandler(handler) - def bootstrap_from_options(self, options=None, context=None): + def bootstrap_with_global_options(self, options=None, context=None): if options is None: parser = util.add_global_options() (options, args) = parser.parse_args( -- cgit From 242a8183a7cc002f496421352e8346db4232648b Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 31 Oct 2008 20:25:33 -0600 Subject: Added custom log formatter util.LogFormatter that makes the human-readable time stamp in UTC --- ipalib/plugable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 64a9d835..08a978ed 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -793,7 +793,7 @@ class API(DictProxy): stderr.setLevel(logging.INFO) else: stderr.setLevel(logging.WARNING) - stderr.setFormatter(logging.Formatter(format)) + stderr.setFormatter(util.LogFormatter(format)) log.addHandler(stderr) # Add file handler: @@ -807,7 +807,7 @@ class API(DictProxy): log.warn('Could not create log_dir %r', log_dir) return handler = logging.FileHandler(self.env.log) - handler.setFormatter(logging.Formatter(self.env.log_format_file)) + handler.setFormatter(util.LogFormatter(self.env.log_format_file)) if self.env.debug: handler.setLevel(logging.DEBUG) else: -- cgit From 5bdf860647c5d5825791d50a94b34fbd9a7a71a9 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 6 Nov 2008 11:57:21 -0700 Subject: Added Plugin.call() method that calls an external executable via subprocess.call() --- ipalib/plugable.py | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 08a978ed..5e0611f9 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -32,6 +32,7 @@ import threading import logging import os from os import path +import subprocess import errors from errors import check_type, check_isinstance from config import Environment, Env @@ -366,6 +367,16 @@ class Plugin(ReadOnly): assert not hasattr(self, name) setattr(self, name, getattr(api, name)) + def call(self, *args): + """ + Call an external command via ``subprocess.call``. + + Returns the exit status of the call. + """ + if hasattr(self, 'log'): + self.log.debug('Calling %r', args) + return subprocess.call(args) + def __repr__(self): """ Return 'module_name.class_name()' representation. -- cgit From c26a3c8542472a2d3931c7dc82edfd684354af6b Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 7 Nov 2008 02:26:38 -0700 Subject: Finished fist draft of plugin tutorial in ipalib/__init__.py docstring --- ipalib/plugable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 5e0611f9..d65a83e2 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -808,7 +808,7 @@ class API(DictProxy): log.addHandler(stderr) # Add file handler: - if self.env.mode == 'unit_test': + if self.env.mode in ('dummy', 'unit_test'): return # But not if in unit-test mode log_dir = path.dirname(self.env.log) if not path.isdir(log_dir): @@ -860,7 +860,7 @@ class API(DictProxy): """ self.__doing('load_plugins') self.__do_if_not_done('bootstrap') - if self.env.mode == 'unit_test': + if self.env.mode in ('dummy', 'unit_test'): return util.import_plugins_subpackage('ipalib') if self.env.in_server: -- cgit From 2db738e8996528502293b8cc6861efedcba22c9a Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 24 Nov 2008 10:09:30 -0700 Subject: Some changes to make reading dubugging output easier --- ipalib/plugable.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index d65a83e2..7dafd440 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -253,6 +253,11 @@ class Plugin(ReadOnly): __proxy__ = True __api = None + def __init__(self): + log = logging.getLogger('ipa') + for name in ('debug', 'info', 'warning', 'error', 'critical'): + setattr(self, name, getattr(log, name)) + def __get_name(self): """ Convenience property to return the class name. -- cgit From 69041c3b1b2494d89097e490048c23292c8cbc52 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 17 Dec 2008 21:47:43 -0700 Subject: Removed Plugin.name property and replaced with instance attribute created in Plugin.__init__() --- ipalib/plugable.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 7dafd440..2bed992d 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -254,17 +254,14 @@ class Plugin(ReadOnly): __api = None def __init__(self): + cls = self.__class__ + self.name = cls.__name__ + self.module = cls.__module__ + self.fullname = '%s.%s' % (self.module, self.name) log = logging.getLogger('ipa') for name in ('debug', 'info', 'warning', 'error', 'critical'): setattr(self, name, getattr(log, name)) - def __get_name(self): - """ - Convenience property to return the class name. - """ - return self.__class__.__name__ - name = property(__get_name) - def __get_doc(self): """ Convenience property to return the class docstring. -- cgit From 171ed58367e58c59f9f67ef831f08ce80ba8508b Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 17 Dec 2008 21:57:58 -0700 Subject: Removed Plugin.doc property and replaced with instance attribute created in Plugin.__init__() --- ipalib/plugable.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 2bed992d..5363a51e 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -258,17 +258,11 @@ class Plugin(ReadOnly): self.name = cls.__name__ self.module = cls.__module__ self.fullname = '%s.%s' % (self.module, self.name) + self.doc = cls.__doc__ log = logging.getLogger('ipa') for name in ('debug', 'info', 'warning', 'error', 'critical'): setattr(self, name, getattr(log, name)) - def __get_doc(self): - """ - Convenience property to return the class docstring. - """ - return self.__class__.__doc__ - doc = property(__get_doc) - def __get_api(self): """ Return `API` instance passed to `finalize()`. -- cgit From 4f24f0fd8837383f4a2abc54946f6f84810807b8 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 17 Dec 2008 23:08:52 -0700 Subject: Plugin.doc instance attribute is now parsed out using inspect.getdoc(); added Plugin.summary instance attribute, created in Plugin.__init__() --- ipalib/plugable.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 5363a51e..e6b5c1ac 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -258,7 +258,11 @@ class Plugin(ReadOnly): self.name = cls.__name__ self.module = cls.__module__ self.fullname = '%s.%s' % (self.module, self.name) - self.doc = cls.__doc__ + self.doc = inspect.getdoc(cls) + if self.doc is None: + self.summary = '<%s>' % self.fullname + else: + self.summary = self.doc.split('\n\n', 1)[0] log = logging.getLogger('ipa') for name in ('debug', 'info', 'warning', 'error', 'critical'): setattr(self, name, getattr(log, name)) -- cgit From 4390523b7f854cefcb91843e1df3ca7575d43fea Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 21 Dec 2008 17:12:00 -0700 Subject: Improved Plugin.call() method and added its unit test --- ipalib/plugable.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index e6b5c1ac..f3b35d30 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -367,15 +367,22 @@ class Plugin(ReadOnly): assert not hasattr(self, name) setattr(self, name, getattr(api, name)) - def call(self, *args): + def call(self, executable, *args): """ - Call an external command via ``subprocess.call``. + Call ``executable`` with ``args`` using subprocess.call(). - Returns the exit status of the call. + If the call exits with a non-zero exit status, + `ipalib.errors.SubprocessError` is raised, from which you can retrieve + the exit code by checking the SubprocessError.returncode attribute. + + This method does *not* return what ``executable`` sent to stdout... for + that, use `Plugin.callread()`. """ - if hasattr(self, 'log'): - self.log.debug('Calling %r', args) - return subprocess.call(args) + argv = (executable,) + args + self.debug('Calling %r', argv) + returncode = subprocess.call(argv) + if returncode != 0: + raise errors.SubprocessError(returncode, argv) def __repr__(self): """ -- cgit From 9d091c98f1f1bf7bacf49e9eaaa18ba8bb1bfd70 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 21 Dec 2008 19:34:32 -0700 Subject: Plugin.__init__() now checks that subclass hasn't defined attributes that conflict with the logger methods; added corresponding unit test --- ipalib/plugable.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index f3b35d30..019386c3 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -263,8 +263,13 @@ class Plugin(ReadOnly): self.summary = '<%s>' % self.fullname else: self.summary = self.doc.split('\n\n', 1)[0] - log = logging.getLogger('ipa') - for name in ('debug', 'info', 'warning', 'error', 'critical'): + log = logging.getLogger(self.fullname) + for name in ('debug', 'info', 'warning', 'error', 'critical', 'exception'): + if hasattr(self, name): + raise StandardError( + '%s.%s attribute (%r) conflicts with Plugin logger' % ( + self.name, name, getattr(self, name)) + ) setattr(self, name, getattr(log, name)) def __get_api(self): -- cgit From f82c48f775d1a8440a19d2040dbc8da51cec04b1 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 21 Dec 2008 19:58:48 -0700 Subject: Added note in Plugin.set_api() about Plugin.log attribute being depreciated --- ipalib/plugable.py | 1 + 1 file changed, 1 insertion(+) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 019386c3..01b9b33c 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -367,6 +367,7 @@ class Plugin(ReadOnly): for name in api: assert not hasattr(self, name) setattr(self, name, api[name]) + # FIXME: the 'log' attribute is depreciated. See Plugin.__init__() for name in ('env', 'context', 'log'): if hasattr(api, name): assert not hasattr(self, name) -- cgit From 5b637f6a18a647a0ff084b2932faa1a4a887a5c2 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 22 Dec 2008 15:41:24 -0700 Subject: Removed depreciated code from config.py; removed corresponding unit tests --- ipalib/plugable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 01b9b33c..0120f972 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -35,7 +35,7 @@ from os import path import subprocess import errors from errors import check_type, check_isinstance -from config import Environment, Env +from config import Env from constants import DEFAULT_CONFIG import util -- cgit From b4dc333ee2a010f3629002932d06a8b8a10df1d3 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 2 Jan 2009 00:46:45 -0700 Subject: Removed depreciated code in ipalib.plugable that has been moving into ipalib.base --- ipalib/plugable.py | 251 +---------------------------------------------------- 1 file changed, 2 insertions(+), 249 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 0120f972..923b72ad 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -36,104 +36,9 @@ import subprocess import errors from errors import check_type, check_isinstance from config import Env -from constants import DEFAULT_CONFIG import util - - - -class ReadOnly(object): - """ - Base class for classes with read-only attributes. - - Be forewarned that Python does not offer true read-only user defined - classes. In particular, do not rely upon the read-only-ness of this - class for security purposes. - - The point of this class is not to make it impossible to set or delete - attributes, but to make it impossible to accidentally do so. The plugins - are not thread-safe: in the server, they are loaded once and the same - instances will be used to process many requests. Therefore, it is - imperative that they not set any instance attributes after they have - been initialized. This base class enforces that policy. - - For example: - - >>> ro = ReadOnly() # Initially unlocked, can setattr, delattr - >>> ro.name = 'John Doe' - >>> ro.message = 'Hello, world!' - >>> del ro.message - >>> ro.__lock__() # Now locked, cannot setattr, delattr - >>> ro.message = 'How are you?' - Traceback (most recent call last): - File "", line 1, in - File ".../ipalib/plugable.py", line 93, in __setattr__ - (self.__class__.__name__, name) - AttributeError: read-only: cannot set ReadOnly.message - >>> del ro.name - Traceback (most recent call last): - File "", line 1, in - File "/home/jderose/projects/freeipa2/ipalib/plugable.py", line 104, in __delattr__ - (self.__class__.__name__, name) - AttributeError: read-only: cannot del ReadOnly.name - """ - - __locked = False - - def __lock__(self): - """ - Put this instance into a read-only state. - - After the instance has been locked, attempting to set or delete an - attribute will raise AttributeError. - """ - assert self.__locked is False, '__lock__() can only be called once' - self.__locked = True - - def __islocked__(self): - """ - Return True if instance is locked, otherwise False. - """ - return self.__locked - - def __setattr__(self, name, value): - """ - If unlocked, set attribute named ``name`` to ``value``. - - If this instance is locked, AttributeError will be raised. - """ - if self.__locked: - raise AttributeError('read-only: cannot set %s.%s' % - (self.__class__.__name__, name) - ) - return object.__setattr__(self, name, value) - - def __delattr__(self, name): - """ - If unlocked, delete attribute named ``name``. - - If this instance is locked, AttributeError will be raised. - """ - if self.__locked: - raise AttributeError('read-only: cannot del %s.%s' % - (self.__class__.__name__, name) - ) - return object.__delattr__(self, name) - - -def lock(readonly): - """ - Lock a `ReadOnly` instance. - - This is mostly a convenience function to call `ReadOnly.__lock__()`. It - also verifies that the locking worked using `ReadOnly.__islocked__()` - - :param readonly: An instance of the `ReadOnly` class. - """ - if not isinstance(readonly, ReadOnly): - raise ValueError('not a ReadOnly instance: %r' % readonly) - readonly.__lock__() - assert readonly.__islocked__(), 'Ouch! The locking failed?' - return readonly +from base import ReadOnly, NameSpace, lock, islocked, check_name +from constants import DEFAULT_CONFIG class SetProxy(ReadOnly): @@ -512,158 +417,6 @@ class PluginProxy(SetProxy): ) -def check_name(name): - """ - Verify that ``name`` is suitable for a `NameSpace` member name. - - Raises `errors.NameSpaceError` if ``name`` is not a valid Python - identifier suitable for use as the name of `NameSpace` member. - - :param name: Identifier to test. - """ - check_type(name, str, 'name') - regex = r'^[a-z][_a-z0-9]*[a-z0-9]$' - if re.match(regex, name) is None: - raise errors.NameSpaceError(name, regex) - return name - - -class NameSpace(ReadOnly): - """ - A read-only namespace with handy container behaviours. - - Each member of a NameSpace instance must have a ``name`` attribute whose - value: - - 1. Is unique among the members - 2. Passes the `check_name()` function - - Beyond that, no restrictions are placed on the members: they can be - classes or instances, and of any type. - - The members can be accessed as attributes on the NameSpace instance or - through a dictionary interface. For example: - - >>> class obj(object): - ... name = 'my_obj' - ... - >>> namespace = NameSpace([obj]) - >>> obj is getattr(namespace, 'my_obj') # As attribute - True - >>> obj is namespace['my_obj'] # As dictionary item - True - - Here is a more detailed example: - - >>> class Member(object): - ... def __init__(self, i): - ... self.i = i - ... self.name = 'member_%d' % i - ... def __repr__(self): - ... return 'Member(%d)' % self.i - ... - >>> namespace = NameSpace(Member(i) for i in xrange(3)) - >>> namespace.member_0 is namespace['member_0'] - True - >>> len(namespace) # Returns the number of members in namespace - 3 - >>> list(namespace) # As iterable, iterates through the member names - ['member_0', 'member_1', 'member_2'] - >>> list(namespace()) # Calling a NameSpace iterates through the members - [Member(0), Member(1), Member(2)] - >>> 'member_1' in namespace # Does namespace contain 'member_1'? - True - """ - - def __init__(self, members, sort=True): - """ - :param members: An iterable providing the members. - :param sort: Whether to sort the members by member name. - """ - self.__sort = check_type(sort, bool, 'sort') - if self.__sort: - self.__members = tuple(sorted(members, key=lambda m: m.name)) - else: - self.__members = tuple(members) - self.__names = tuple(m.name for m in self.__members) - self.__map = dict() - for member in self.__members: - name = check_name(member.name) - assert name not in self.__map, 'already has key %r' % name - self.__map[name] = member - assert not hasattr(self, name), 'already has attribute %r' % name - setattr(self, name, member) - lock(self) - - def __len__(self): - """ - Return the number of members. - """ - return len(self.__members) - - def __iter__(self): - """ - Iterate through the member names. - - If this instance was created with ``sort=True``, the names will be in - alphabetical order; otherwise the names will be in the same order as - the members were passed to the constructor. - - This method is like an ordered version of dict.iterkeys(). - """ - for name in self.__names: - yield name - - def __call__(self): - """ - Iterate through the members. - - If this instance was created with ``sort=True``, the members will be - in alphabetical order by name; otherwise the members will be in the - same order as they were passed to the constructor. - - This method is like an ordered version of dict.itervalues(). - """ - for member in self.__members: - yield member - - def __contains__(self, name): - """ - Return True if namespace has a member named ``name``. - """ - return name in self.__map - - def __getitem__(self, spec): - """ - Return a member by name or index, or returns a slice of members. - - :param spec: The name or index of a member, or a slice object. - """ - if type(spec) is str: - return self.__map[spec] - if type(spec) in (int, slice): - return self.__members[spec] - raise TypeError( - 'spec: must be %r, %r, or %r; got %r' % (str, int, slice, spec) - ) - - def __repr__(self): - """ - Return a pseudo-valid expression that could create this instance. - """ - return '%s(<%d members>, sort=%r)' % ( - self.__class__.__name__, - len(self), - self.__sort, - ) - - def __todict__(self): - """ - Return a copy of the private dict mapping name to member. - """ - return dict(self.__map) - - class Registrar(DictProxy): """ Collects plugin classes as they are registered. -- cgit From 72340a594d558796d2ff447cd612311825033128 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 2 Jan 2009 01:16:17 -0700 Subject: Removed unneeded import of check_type, check_instance in plugable.py --- ipalib/plugable.py | 1 - 1 file changed, 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 923b72ad..4ef2135b 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -34,7 +34,6 @@ import os from os import path import subprocess import errors -from errors import check_type, check_isinstance from config import Env import util from base import ReadOnly, NameSpace, lock, islocked, check_name -- cgit From 6b6e6b1cab7a633faf16631a565ecb6988dadb48 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 3 Jan 2009 17:27:53 -0700 Subject: Ported plugin registration errors into errors2.py; plugable.Registrar now raises new errors2 exceptions --- ipalib/plugable.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 4ef2135b..094634d3 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -34,6 +34,7 @@ import os from os import path import subprocess import errors +import errors2 from config import Env import util from base import ReadOnly, NameSpace, lock, islocked, check_name @@ -461,7 +462,9 @@ class Registrar(DictProxy): found = True yield (base, sub_d) if not found: - raise errors.SubclassError(klass, self.__allowed.keys()) + raise errors2.PluginSubclassError( + plugin=klass, bases=self.__allowed.keys() + ) def __call__(self, klass, override=False): """ @@ -471,11 +474,11 @@ class Registrar(DictProxy): :param override: If true, override an already registered plugin. """ if not inspect.isclass(klass): - raise TypeError('plugin must be a class: %r' % klass) + raise TypeError('plugin must be a class; got %r' % klass) # Raise DuplicateError if this exact class was already registered: if klass in self.__registered: - raise errors.DuplicateError(klass) + raise errors2.PluginDuplicateError(plugin=klass) # Find the base class or raise SubclassError: for (base, sub_d) in self.__findbases(klass): @@ -483,11 +486,19 @@ class Registrar(DictProxy): if klass.__name__ in sub_d: if not override: # Must use override=True to override: - raise errors.OverrideError(base, klass) + raise errors2.PluginOverrideError( + base=base.__name__, + name=klass.__name__, + plugin=klass, + ) else: if override: # There was nothing already registered to override: - raise errors.MissingOverrideError(base, klass) + raise errors2.PluginMissingOverrideError( + base=base.__name__, + name=klass.__name__, + plugin=klass, + ) # The plugin is okay, add to sub_d: sub_d[klass.__name__] = klass -- cgit From bb6e9cfe9ff25f3a018b23785f71302911eab435 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 3 Jan 2009 18:02:58 -0700 Subject: Plugin.call() now uses errors2 version of SubprocessError --- ipalib/plugable.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 094634d3..f7baa2a1 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -291,9 +291,9 @@ class Plugin(ReadOnly): """ argv = (executable,) + args self.debug('Calling %r', argv) - returncode = subprocess.call(argv) - if returncode != 0: - raise errors.SubprocessError(returncode, argv) + code = subprocess.call(argv) + if code != 0: + raise errors2.SubprocessError(returncode=code, argv=argv) def __repr__(self): """ -- cgit From 912ab9e68b6030d4d115d3683893477abb4f6451 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 3 Jan 2009 18:08:39 -0700 Subject: Removed unneeded import of errors from plugable.py --- ipalib/plugable.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index f7baa2a1..d4ec8749 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -33,7 +33,6 @@ import logging import os from os import path import subprocess -import errors import errors2 from config import Env import util @@ -283,7 +282,7 @@ class Plugin(ReadOnly): Call ``executable`` with ``args`` using subprocess.call(). If the call exits with a non-zero exit status, - `ipalib.errors.SubprocessError` is raised, from which you can retrieve + `ipalib.errors2.SubprocessError` is raised, from which you can retrieve the exit code by checking the SubprocessError.returncode attribute. This method does *not* return what ``executable`` sent to stdout... for @@ -450,10 +449,10 @@ class Registrar(DictProxy): """ Iterates through allowed bases that ``klass`` is a subclass of. - Raises `errors.SubclassError` if ``klass`` is not a subclass of any - allowed base. + Raises `errors2.PluginSubclassError` if ``klass`` is not a subclass of + any allowed base. - :param klass: The class to find bases for. + :param klass: The plugin class to find bases for. """ assert inspect.isclass(klass) found = False -- cgit From 6fe78a4944f11d430b724103f7d8d49c92af9b63 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 4 Jan 2009 18:39:39 -0700 Subject: Renamed all references to 'ipa_server' to 'ipaserver' --- ipalib/plugable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/plugable.py') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index d4ec8749..b52db900 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -639,7 +639,7 @@ class API(DictProxy): return util.import_plugins_subpackage('ipalib') if self.env.in_server: - util.import_plugins_subpackage('ipa_server') + util.import_plugins_subpackage('ipaserver') def finalize(self): """ -- cgit