diff options
author | Jason Gerard DeRose <jderose@redhat.com> | 2008-08-06 20:38:07 +0000 |
---|---|---|
committer | Jason Gerard DeRose <jderose@redhat.com> | 2008-08-06 20:38:07 +0000 |
commit | 0c7769473ca01facdcb1768868bfd053e726fddf (patch) | |
tree | 1419ab73cffb6914ff9bc8daca30a643444e33fe | |
parent | 57534ca5a0f5443c80ffba4c1640650a5989c7b8 (diff) | |
download | freeipa.git-0c7769473ca01facdcb1768868bfd053e726fddf.tar.gz freeipa.git-0c7769473ca01facdcb1768868bfd053e726fddf.tar.xz freeipa.git-0c7769473ca01facdcb1768868bfd053e726fddf.zip |
64: Almost finish with Proxy2, where base class is passed to __init__ and methods use @export decorator; added corresponding unit tests
-rw-r--r-- | ipalib/plugable.py | 73 | ||||
-rw-r--r-- | ipalib/tests/test_plugable.py | 71 |
2 files changed, 144 insertions, 0 deletions
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 diff --git a/ipalib/tests/test_plugable.py b/ipalib/tests/test_plugable.py index ab9a8665..40e98ed3 100644 --- a/ipalib/tests/test_plugable.py +++ b/ipalib/tests/test_plugable.py @@ -168,6 +168,77 @@ def test_Proxy(): assert c.name == 'do_a_thing' +def test_Proxy2(): + cls = plugable.Proxy2 + export = plugable.export + assert issubclass(cls, plugable.ReadOnly) + + # Setup: + class base(object): + @export + def public_0(self): + return 'public_0' + + @export + def public_1(self): + return 'public_1' + + @export + def _get_some_prop(self): + return 'ya got it' + + def __call__(self, caller): + return 'ya called it, %s.' % caller + + def private_0(self): + return 'private_0' + + def private_1(self): + return 'private_1' + + class plugin(base): + pass + + # Test that TypeError is raised when base is not a class: + raises(TypeError, cls, base(), None) + + # Test that ValueError is raised when target is not instance of base: + raises(ValueError, cls, base, object()) + + # Test with correct arguments: + i = plugin() + p = cls(base, i) + assert read_only(p, 'base') is base + assert list(p) == ['_get_some_prop', 'public_0', 'public_1'] + + # Test normal methods: + for n in xrange(2): + pub = 'public_%d' % n + priv = 'private_%d' % n + assert getattr(i, pub)() == pub + assert getattr(p, pub)() == pub + assert getattr(i, priv)() == priv + assert not hasattr(p, priv) + + # Test __call__: + value = 'ya called it, dude.' + assert i('dude') == value + assert p('dude') == value + + # Test implied property: + fget = '_get_some_prop' + name = 'some_prop' + value = 'ya got it' + assert getattr(i, fget)() == value + assert getattr(p, fget)() == value + assert getattr(p, name) == value + + + + + + + def test_Registrar(): class Base1(object): pass |