From 2842e85d88f4c6cedfdf41d3cce2ee7449369871 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 23 Sep 2008 23:51:03 +0000 Subject: 317: Renamed public.py to frontend.py; renamed test_public.py to test_frontend.py --- ipalib/frontend.py | 571 +++++++++++++++++++++++++++ ipalib/public.py | 571 --------------------------- ipalib/tests/test_frontend.py | 884 ++++++++++++++++++++++++++++++++++++++++++ ipalib/tests/test_public.py | 884 ------------------------------------------ 4 files changed, 1455 insertions(+), 1455 deletions(-) create mode 100644 ipalib/frontend.py delete mode 100644 ipalib/public.py create mode 100644 ipalib/tests/test_frontend.py delete mode 100644 ipalib/tests/test_public.py diff --git a/ipalib/frontend.py b/ipalib/frontend.py new file mode 100644 index 00000000..678bd2de --- /dev/null +++ b/ipalib/frontend.py @@ -0,0 +1,571 @@ +# 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 + +""" +Base classes for the public plugable.API instance, which the XML-RPC, CLI, +and UI all use. +""" + +import re +import inspect +import plugable +from plugable import lock, check_name +import errors +from errors import check_type, check_isinstance, raise_TypeError +import ipa_types + + +RULE_FLAG = 'validation_rule' + +def rule(obj): + assert not hasattr(obj, RULE_FLAG) + setattr(obj, RULE_FLAG, True) + return obj + +def is_rule(obj): + return callable(obj) and getattr(obj, RULE_FLAG, False) is True + + +class DefaultFrom(plugable.ReadOnly): + """ + Derives a default for one value using other supplied values. + + Here is an example that constructs a user's initials from his first + and last name: + + >>> df = DefaultFrom(lambda f, l: f[0] + l[0], 'first', 'last') + >>> df(first='John', last='Doe') # Both keys + 'JD' + >>> df() is None # Returns None if any key is missing + True + >>> df(first='John', middle='Q') is None # Still returns None + True + """ + def __init__(self, callback, *keys): + """ + :param callback: The callable to call when all ``keys`` are present. + :param keys: The keys used to map from keyword to position arguments. + """ + assert callable(callback), 'not a callable: %r' % callback + assert len(keys) > 0, 'must have at least one key' + for key in keys: + assert type(key) is str, 'not an str: %r' % key + self.callback = callback + self.keys = keys + lock(self) + + def __call__(self, **kw): + """ + If all keys are present, calls the callback; otherwise returns None. + + :param kw: The keyword arguments. + """ + vals = tuple(kw.get(k, None) for k in self.keys) + if None in vals: + return None + try: + return self.callback(*vals) + except Exception: + return None + + +class Param(plugable.ReadOnly): + def __init__(self, name, type_, + doc='', + required=False, + multivalue=False, + default=None, + default_from=None, + rules=tuple(), + normalize=None): + self.name = check_name(name) + self.doc = check_type(doc, str, 'doc') + self.type = check_isinstance(type_, ipa_types.Type, 'type_') + self.required = check_type(required, bool, 'required') + self.multivalue = check_type(multivalue, bool, 'multivalue') + self.default = default + self.default_from = check_type(default_from, + DefaultFrom, 'default_from', allow_none=True) + self.__normalize = normalize + self.rules = (type_.validate,) + rules + lock(self) + + def __convert_scalar(self, value, index=None): + if value is None: + raise TypeError('value cannot be None') + converted = self.type(value) + if converted is None: + raise errors.ConversionError( + self.name, value, self.type, index=index + ) + return converted + + def convert(self, value): + if self.multivalue: + if type(value) in (tuple, list): + return tuple( + self.__convert_scalar(v, i) for (i, v) in enumerate(value) + ) + return (self.__convert_scalar(value, 0),) # tuple + return self.__convert_scalar(value) + + def __normalize_scalar(self, value): + if not isinstance(value, basestring): + raise_TypeError(value, basestring, 'value') + try: + return self.__normalize(value) + except Exception: + return value + + def normalize(self, value): + if self.__normalize is None: + return value + if self.multivalue: + if type(value) in (tuple, list): + return tuple(self.__normalize_scalar(v) for v in value) + return (self.__normalize_scalar(value),) # tuple + return self.__normalize_scalar(value) + + def __validate_scalar(self, value, index=None): + if type(value) is not self.type.type: + raise_TypeError(value, self.type.type, 'value') + for rule in self.rules: + error = rule(value) + if error is not None: + raise errors.RuleError( + self.name, value, error, rule, index=index + ) + + def validate(self, value): + if self.multivalue: + if type(value) is not tuple: + raise_TypeError(value, tuple, 'value') + for (i, v) in enumerate(value): + self.__validate_scalar(v, i) + else: + self.__validate_scalar(value) + + def get_default(self, **kw): + if self.default_from is not None: + default = self.default_from(**kw) + if default is not None: + try: + return self.convert(self.normalize(default)) + except errors.ValidationError: + return None + return self.default + + def get_values(self): + if self.type.name in ('Enum', 'CallbackEnum'): + return self.type.values + return tuple() + + def __call__(self, value, **kw): + if value in ('', tuple(), []): + value = None + if value is None: + value = self.get_default(**kw) + if value is None: + if self.required: + raise errors.RequirementError(self.name) + return None + else: + value = self.convert(self.normalize(value)) + self.validate(value) + return value + + def __repr__(self): + return '%s(%r, %s())' % ( + self.__class__.__name__, + self.name, + self.type.name, + ) + + +def create_param(spec): + """ + Create a `Param` instance from a param spec string. + + If ``spec`` is a `Param` instance, ``spec`` is returned unchanged. + + If ``spec`` is an str instance, then ``spec`` is parsed and an + appropriate `Param` instance is created and returned. + + The spec string determines the param name, whether the param is required, + and whether the param is multivalue according the following syntax: + + name => required=True, multivalue=False + name? => required=False, multivalue=False + name+ => required=True, multivalue=True + name* => required=False, multivalue=True + + :param spec: A spec string or a `Param` instance. + """ + if type(spec) is Param: + return spec + if type(spec) is not str: + raise TypeError( + 'create_param() takes %r or %r; got %r' % (str, Param, spec) + ) + if spec.endswith('?'): + kw = dict(required=False, multivalue=False) + name = spec[:-1] + elif spec.endswith('*'): + kw = dict(required=False, multivalue=True) + name = spec[:-1] + elif spec.endswith('+'): + kw = dict(required=True, multivalue=True) + name = spec[:-1] + else: + kw = dict(required=True, multivalue=False) + name = spec + return Param(name, ipa_types.Unicode(), **kw) + + +class Command(plugable.Plugin): + __public__ = frozenset(( + 'get_default', + 'convert', + 'normalize', + 'validate', + 'execute', + '__call__', + 'smart_option_order', + 'args', + 'options', + 'params', + 'args_to_kw', + 'kw_to_args', + )) + takes_options = tuple() + takes_args = tuple() + args = None + options = None + params = None + can_forward = True + + def finalize(self): + self.args = plugable.NameSpace(self.__create_args(), sort=False) + if len(self.args) == 0 or not self.args[-1].multivalue: + self.max_args = len(self.args) + else: + self.max_args = None + self.options = plugable.NameSpace(self.__create_options(), sort=False) + self.params = plugable.NameSpace( + tuple(self.args()) + tuple(self.options()), sort=False + ) + super(Command, self).finalize() + + def get_args(self): + return self.takes_args + + def get_options(self): + return self.takes_options + + def __create_args(self): + optional = False + multivalue = False + for arg in self.get_args(): + arg = create_param(arg) + if optional and arg.required: + raise ValueError( + '%s: required argument after optional' % arg.name + ) + if multivalue: + raise ValueError( + '%s: only final argument can be multivalue' % arg.name + ) + if not arg.required: + optional = True + if arg.multivalue: + multivalue = True + yield arg + + def __create_options(self): + for option in self.get_options(): + yield create_param(option) + + def __convert_iter(self, kw): + for (key, value) in kw.iteritems(): + if key in self.params: + yield (key, self.params[key].convert(value)) + else: + yield (key, value) + + def convert(self, **kw): + return dict(self.__convert_iter(kw)) + + def __normalize_iter(self, kw): + for (key, value) in kw.iteritems(): + if key in self.params: + yield (key, self.params[key].normalize(value)) + else: + yield (key, value) + + def normalize(self, **kw): + return dict(self.__normalize_iter(kw)) + + def __get_default_iter(self, kw): + for param in self.params(): + if param.name not in kw: + value = param.get_default(**kw) + if value is not None: + yield(param.name, value) + + def get_default(self, **kw): + return dict(self.__get_default_iter(kw)) + + def validate(self, **kw): + for param in self.params(): + value = kw.get(param.name, None) + if value is not None: + param.validate(value) + elif param.required: + raise errors.RequirementError(param.name) + + def execute(self, *args, **kw): + print '%s.execute():' % self.name + print ' args =', args + print ' kw =', kw + + def __call__(self, *args, **kw): + if len(args) > 0: + arg_kw = self.args_to_kw(*args) + assert set(arg_kw).intersection(kw) == set() + kw.update(arg_kw) + kw = self.normalize(**kw) + kw = self.convert(**kw) + kw.update(self.get_default(**kw)) + self.validate(**kw) + args = tuple(kw.pop(name) for name in self.args) + self.execute(*args, **kw) + + def args_to_kw(self, *values): + if self.max_args is not None and len(values) > self.max_args: + if self.max_args == 0: + raise errors.ArgumentError(self, 'takes no arguments') + if self.max_args == 1: + raise errors.ArgumentError(self, 'takes at most 1 argument') + raise errors.ArgumentError(self, + 'takes at most %d arguments' % len(self.args) + ) + return dict(self.__args_to_kw_iter(values)) + + def __args_to_kw_iter(self, values): + multivalue = False + for (i, arg) in enumerate(self.args()): + assert not multivalue + if len(values) > i: + if arg.multivalue: + multivalue = True + yield (arg.name, values[i:]) + else: + yield (arg.name, values[i]) + else: + break + + def kw_to_args(self, **kw): + return tuple(kw.get(name, None) for name in self.args) + + +class Object(plugable.Plugin): + __public__ = frozenset(( + 'Method', + 'Property', + 'params' + )) + __Method = None + __Property = None + takes_params = tuple() + + def __init__(self): + self.params = plugable.NameSpace( + (create_param(p) for p in self.takes_params), sort=False + ) + + def __create_params(self): + for param in self.takes_params: + yield create_param(param) + + def __get_Method(self): + return self.__Method + Method = property(__get_Method) + + def __get_Property(self): + return self.__Property + Property = property(__get_Property) + + def set_api(self, api): + super(Object, self).set_api(api) + self.__Method = self.__create_namespace('Method') + self.__Property = self.__create_namespace('Property') + + def __create_namespace(self, name): + return plugable.NameSpace(self.__filter_members(name)) + + def __filter_members(self, name): + namespace = getattr(self.api, name) + assert type(namespace) is plugable.NameSpace + for proxy in namespace(): # Equivalent to dict.itervalues() + if proxy.obj_name == self.name: + yield proxy.__clone__('attr_name') + + +class Attribute(plugable.Plugin): + __public__ = frozenset(( + 'obj', + 'obj_name', + )) + __obj = None + + def __init__(self): + m = re.match( + '^([a-z][a-z0-9]+)_([a-z][a-z0-9]+)$', + self.__class__.__name__ + ) + assert m + self.__obj_name = m.group(1) + self.__attr_name = m.group(2) + + def __get_obj_name(self): + return self.__obj_name + obj_name = property(__get_obj_name) + + def __get_attr_name(self): + return self.__attr_name + attr_name = property(__get_attr_name) + + def __get_obj(self): + """ + Returns the obj instance this attribute is associated with, or None + if no association has been set. + """ + return self.__obj + obj = property(__get_obj) + + def set_api(self, api): + self.__obj = api.Object[self.obj_name] + super(Attribute, self).set_api(api) + + +class Method(Attribute, Command): + __public__ = Attribute.__public__.union(Command.__public__) + + def __init__(self): + Attribute.__init__(self) + Command.__init__(self) + + def get_options(self): + for option in self.takes_options: + yield option + if self.obj is not None and self.obj.Property is not None: + def get_key(p): + if p.param.required: + if p.param.default_from is None: + return 0 + return 1 + return 2 + for prop in sorted(self.obj.Property(), key=get_key): + yield prop.param + + +class Property(Attribute): + __public__ = frozenset(( + 'rules', + 'param', + 'type', + )).union(Attribute.__public__) + + type = ipa_types.Unicode() + required = False + multivalue = False + default = None + default_from = None + normalize = None + + def __init__(self): + super(Property, self).__init__() + self.rules = tuple(sorted( + self.__rules_iter(), + key=lambda f: getattr(f, '__name__'), + )) + self.param = Param(self.attr_name, self.type, + doc=self.doc, + required=self.required, + multivalue=self.multivalue, + default=self.default, + default_from=self.default_from, + rules=self.rules, + normalize=self.normalize, + ) + + def __rules_iter(self): + """ + Iterates through the attributes in this instance to retrieve the + methods implementing validation rules. + """ + for name in dir(self.__class__): + if name.startswith('_'): + continue + base_attr = getattr(self.__class__, name) + if is_rule(base_attr): + attr = getattr(self, name) + if is_rule(attr): + yield attr + + +class Application(Command): + """ + Base class for commands register by an external application. + + Special commands that only apply to a particular application built atop + `ipalib` should subclass from ``Application``. + + Because ``Application`` subclasses from `Command`, plugins that subclass + from ``Application`` with be available in both the ``api.Command`` and + ``api.Application`` namespaces. + """ + + __public__ = frozenset(( + 'application', + 'set_application' + )).union(Command.__public__) + __application = None + + def __get_application(self): + """ + Returns external ``application`` object. + """ + return self.__application + application = property(__get_application) + + def set_application(self, application): + """ + Sets the external application object to ``application``. + """ + if self.__application is not None: + raise AttributeError( + '%s.application can only be set once' % self.name + ) + if application is None: + raise TypeError( + '%s.application cannot be None' % self.name + ) + object.__setattr__(self, '_Application__application', application) + assert self.application is application diff --git a/ipalib/public.py b/ipalib/public.py deleted file mode 100644 index 678bd2de..00000000 --- a/ipalib/public.py +++ /dev/null @@ -1,571 +0,0 @@ -# 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 - -""" -Base classes for the public plugable.API instance, which the XML-RPC, CLI, -and UI all use. -""" - -import re -import inspect -import plugable -from plugable import lock, check_name -import errors -from errors import check_type, check_isinstance, raise_TypeError -import ipa_types - - -RULE_FLAG = 'validation_rule' - -def rule(obj): - assert not hasattr(obj, RULE_FLAG) - setattr(obj, RULE_FLAG, True) - return obj - -def is_rule(obj): - return callable(obj) and getattr(obj, RULE_FLAG, False) is True - - -class DefaultFrom(plugable.ReadOnly): - """ - Derives a default for one value using other supplied values. - - Here is an example that constructs a user's initials from his first - and last name: - - >>> df = DefaultFrom(lambda f, l: f[0] + l[0], 'first', 'last') - >>> df(first='John', last='Doe') # Both keys - 'JD' - >>> df() is None # Returns None if any key is missing - True - >>> df(first='John', middle='Q') is None # Still returns None - True - """ - def __init__(self, callback, *keys): - """ - :param callback: The callable to call when all ``keys`` are present. - :param keys: The keys used to map from keyword to position arguments. - """ - assert callable(callback), 'not a callable: %r' % callback - assert len(keys) > 0, 'must have at least one key' - for key in keys: - assert type(key) is str, 'not an str: %r' % key - self.callback = callback - self.keys = keys - lock(self) - - def __call__(self, **kw): - """ - If all keys are present, calls the callback; otherwise returns None. - - :param kw: The keyword arguments. - """ - vals = tuple(kw.get(k, None) for k in self.keys) - if None in vals: - return None - try: - return self.callback(*vals) - except Exception: - return None - - -class Param(plugable.ReadOnly): - def __init__(self, name, type_, - doc='', - required=False, - multivalue=False, - default=None, - default_from=None, - rules=tuple(), - normalize=None): - self.name = check_name(name) - self.doc = check_type(doc, str, 'doc') - self.type = check_isinstance(type_, ipa_types.Type, 'type_') - self.required = check_type(required, bool, 'required') - self.multivalue = check_type(multivalue, bool, 'multivalue') - self.default = default - self.default_from = check_type(default_from, - DefaultFrom, 'default_from', allow_none=True) - self.__normalize = normalize - self.rules = (type_.validate,) + rules - lock(self) - - def __convert_scalar(self, value, index=None): - if value is None: - raise TypeError('value cannot be None') - converted = self.type(value) - if converted is None: - raise errors.ConversionError( - self.name, value, self.type, index=index - ) - return converted - - def convert(self, value): - if self.multivalue: - if type(value) in (tuple, list): - return tuple( - self.__convert_scalar(v, i) for (i, v) in enumerate(value) - ) - return (self.__convert_scalar(value, 0),) # tuple - return self.__convert_scalar(value) - - def __normalize_scalar(self, value): - if not isinstance(value, basestring): - raise_TypeError(value, basestring, 'value') - try: - return self.__normalize(value) - except Exception: - return value - - def normalize(self, value): - if self.__normalize is None: - return value - if self.multivalue: - if type(value) in (tuple, list): - return tuple(self.__normalize_scalar(v) for v in value) - return (self.__normalize_scalar(value),) # tuple - return self.__normalize_scalar(value) - - def __validate_scalar(self, value, index=None): - if type(value) is not self.type.type: - raise_TypeError(value, self.type.type, 'value') - for rule in self.rules: - error = rule(value) - if error is not None: - raise errors.RuleError( - self.name, value, error, rule, index=index - ) - - def validate(self, value): - if self.multivalue: - if type(value) is not tuple: - raise_TypeError(value, tuple, 'value') - for (i, v) in enumerate(value): - self.__validate_scalar(v, i) - else: - self.__validate_scalar(value) - - def get_default(self, **kw): - if self.default_from is not None: - default = self.default_from(**kw) - if default is not None: - try: - return self.convert(self.normalize(default)) - except errors.ValidationError: - return None - return self.default - - def get_values(self): - if self.type.name in ('Enum', 'CallbackEnum'): - return self.type.values - return tuple() - - def __call__(self, value, **kw): - if value in ('', tuple(), []): - value = None - if value is None: - value = self.get_default(**kw) - if value is None: - if self.required: - raise errors.RequirementError(self.name) - return None - else: - value = self.convert(self.normalize(value)) - self.validate(value) - return value - - def __repr__(self): - return '%s(%r, %s())' % ( - self.__class__.__name__, - self.name, - self.type.name, - ) - - -def create_param(spec): - """ - Create a `Param` instance from a param spec string. - - If ``spec`` is a `Param` instance, ``spec`` is returned unchanged. - - If ``spec`` is an str instance, then ``spec`` is parsed and an - appropriate `Param` instance is created and returned. - - The spec string determines the param name, whether the param is required, - and whether the param is multivalue according the following syntax: - - name => required=True, multivalue=False - name? => required=False, multivalue=False - name+ => required=True, multivalue=True - name* => required=False, multivalue=True - - :param spec: A spec string or a `Param` instance. - """ - if type(spec) is Param: - return spec - if type(spec) is not str: - raise TypeError( - 'create_param() takes %r or %r; got %r' % (str, Param, spec) - ) - if spec.endswith('?'): - kw = dict(required=False, multivalue=False) - name = spec[:-1] - elif spec.endswith('*'): - kw = dict(required=False, multivalue=True) - name = spec[:-1] - elif spec.endswith('+'): - kw = dict(required=True, multivalue=True) - name = spec[:-1] - else: - kw = dict(required=True, multivalue=False) - name = spec - return Param(name, ipa_types.Unicode(), **kw) - - -class Command(plugable.Plugin): - __public__ = frozenset(( - 'get_default', - 'convert', - 'normalize', - 'validate', - 'execute', - '__call__', - 'smart_option_order', - 'args', - 'options', - 'params', - 'args_to_kw', - 'kw_to_args', - )) - takes_options = tuple() - takes_args = tuple() - args = None - options = None - params = None - can_forward = True - - def finalize(self): - self.args = plugable.NameSpace(self.__create_args(), sort=False) - if len(self.args) == 0 or not self.args[-1].multivalue: - self.max_args = len(self.args) - else: - self.max_args = None - self.options = plugable.NameSpace(self.__create_options(), sort=False) - self.params = plugable.NameSpace( - tuple(self.args()) + tuple(self.options()), sort=False - ) - super(Command, self).finalize() - - def get_args(self): - return self.takes_args - - def get_options(self): - return self.takes_options - - def __create_args(self): - optional = False - multivalue = False - for arg in self.get_args(): - arg = create_param(arg) - if optional and arg.required: - raise ValueError( - '%s: required argument after optional' % arg.name - ) - if multivalue: - raise ValueError( - '%s: only final argument can be multivalue' % arg.name - ) - if not arg.required: - optional = True - if arg.multivalue: - multivalue = True - yield arg - - def __create_options(self): - for option in self.get_options(): - yield create_param(option) - - def __convert_iter(self, kw): - for (key, value) in kw.iteritems(): - if key in self.params: - yield (key, self.params[key].convert(value)) - else: - yield (key, value) - - def convert(self, **kw): - return dict(self.__convert_iter(kw)) - - def __normalize_iter(self, kw): - for (key, value) in kw.iteritems(): - if key in self.params: - yield (key, self.params[key].normalize(value)) - else: - yield (key, value) - - def normalize(self, **kw): - return dict(self.__normalize_iter(kw)) - - def __get_default_iter(self, kw): - for param in self.params(): - if param.name not in kw: - value = param.get_default(**kw) - if value is not None: - yield(param.name, value) - - def get_default(self, **kw): - return dict(self.__get_default_iter(kw)) - - def validate(self, **kw): - for param in self.params(): - value = kw.get(param.name, None) - if value is not None: - param.validate(value) - elif param.required: - raise errors.RequirementError(param.name) - - def execute(self, *args, **kw): - print '%s.execute():' % self.name - print ' args =', args - print ' kw =', kw - - def __call__(self, *args, **kw): - if len(args) > 0: - arg_kw = self.args_to_kw(*args) - assert set(arg_kw).intersection(kw) == set() - kw.update(arg_kw) - kw = self.normalize(**kw) - kw = self.convert(**kw) - kw.update(self.get_default(**kw)) - self.validate(**kw) - args = tuple(kw.pop(name) for name in self.args) - self.execute(*args, **kw) - - def args_to_kw(self, *values): - if self.max_args is not None and len(values) > self.max_args: - if self.max_args == 0: - raise errors.ArgumentError(self, 'takes no arguments') - if self.max_args == 1: - raise errors.ArgumentError(self, 'takes at most 1 argument') - raise errors.ArgumentError(self, - 'takes at most %d arguments' % len(self.args) - ) - return dict(self.__args_to_kw_iter(values)) - - def __args_to_kw_iter(self, values): - multivalue = False - for (i, arg) in enumerate(self.args()): - assert not multivalue - if len(values) > i: - if arg.multivalue: - multivalue = True - yield (arg.name, values[i:]) - else: - yield (arg.name, values[i]) - else: - break - - def kw_to_args(self, **kw): - return tuple(kw.get(name, None) for name in self.args) - - -class Object(plugable.Plugin): - __public__ = frozenset(( - 'Method', - 'Property', - 'params' - )) - __Method = None - __Property = None - takes_params = tuple() - - def __init__(self): - self.params = plugable.NameSpace( - (create_param(p) for p in self.takes_params), sort=False - ) - - def __create_params(self): - for param in self.takes_params: - yield create_param(param) - - def __get_Method(self): - return self.__Method - Method = property(__get_Method) - - def __get_Property(self): - return self.__Property - Property = property(__get_Property) - - def set_api(self, api): - super(Object, self).set_api(api) - self.__Method = self.__create_namespace('Method') - self.__Property = self.__create_namespace('Property') - - def __create_namespace(self, name): - return plugable.NameSpace(self.__filter_members(name)) - - def __filter_members(self, name): - namespace = getattr(self.api, name) - assert type(namespace) is plugable.NameSpace - for proxy in namespace(): # Equivalent to dict.itervalues() - if proxy.obj_name == self.name: - yield proxy.__clone__('attr_name') - - -class Attribute(plugable.Plugin): - __public__ = frozenset(( - 'obj', - 'obj_name', - )) - __obj = None - - def __init__(self): - m = re.match( - '^([a-z][a-z0-9]+)_([a-z][a-z0-9]+)$', - self.__class__.__name__ - ) - assert m - self.__obj_name = m.group(1) - self.__attr_name = m.group(2) - - def __get_obj_name(self): - return self.__obj_name - obj_name = property(__get_obj_name) - - def __get_attr_name(self): - return self.__attr_name - attr_name = property(__get_attr_name) - - def __get_obj(self): - """ - Returns the obj instance this attribute is associated with, or None - if no association has been set. - """ - return self.__obj - obj = property(__get_obj) - - def set_api(self, api): - self.__obj = api.Object[self.obj_name] - super(Attribute, self).set_api(api) - - -class Method(Attribute, Command): - __public__ = Attribute.__public__.union(Command.__public__) - - def __init__(self): - Attribute.__init__(self) - Command.__init__(self) - - def get_options(self): - for option in self.takes_options: - yield option - if self.obj is not None and self.obj.Property is not None: - def get_key(p): - if p.param.required: - if p.param.default_from is None: - return 0 - return 1 - return 2 - for prop in sorted(self.obj.Property(), key=get_key): - yield prop.param - - -class Property(Attribute): - __public__ = frozenset(( - 'rules', - 'param', - 'type', - )).union(Attribute.__public__) - - type = ipa_types.Unicode() - required = False - multivalue = False - default = None - default_from = None - normalize = None - - def __init__(self): - super(Property, self).__init__() - self.rules = tuple(sorted( - self.__rules_iter(), - key=lambda f: getattr(f, '__name__'), - )) - self.param = Param(self.attr_name, self.type, - doc=self.doc, - required=self.required, - multivalue=self.multivalue, - default=self.default, - default_from=self.default_from, - rules=self.rules, - normalize=self.normalize, - ) - - def __rules_iter(self): - """ - Iterates through the attributes in this instance to retrieve the - methods implementing validation rules. - """ - for name in dir(self.__class__): - if name.startswith('_'): - continue - base_attr = getattr(self.__class__, name) - if is_rule(base_attr): - attr = getattr(self, name) - if is_rule(attr): - yield attr - - -class Application(Command): - """ - Base class for commands register by an external application. - - Special commands that only apply to a particular application built atop - `ipalib` should subclass from ``Application``. - - Because ``Application`` subclasses from `Command`, plugins that subclass - from ``Application`` with be available in both the ``api.Command`` and - ``api.Application`` namespaces. - """ - - __public__ = frozenset(( - 'application', - 'set_application' - )).union(Command.__public__) - __application = None - - def __get_application(self): - """ - Returns external ``application`` object. - """ - return self.__application - application = property(__get_application) - - def set_application(self, application): - """ - Sets the external application object to ``application``. - """ - if self.__application is not None: - raise AttributeError( - '%s.application can only be set once' % self.name - ) - if application is None: - raise TypeError( - '%s.application cannot be None' % self.name - ) - object.__setattr__(self, '_Application__application', application) - assert self.application is application diff --git a/ipalib/tests/test_frontend.py b/ipalib/tests/test_frontend.py new file mode 100644 index 00000000..fa78773f --- /dev/null +++ b/ipalib/tests/test_frontend.py @@ -0,0 +1,884 @@ +# 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 + +""" +Unit tests for `ipalib.public` module. +""" + +from tstutil import raises, getitem, no_set, no_del, read_only, ClassChecker +from tstutil import check_TypeError +from ipalib import public, plugable, errors, ipa_types + + +def test_RULE_FLAG(): + assert public.RULE_FLAG == 'validation_rule' + + +def test_rule(): + """ + Tests the `public.rule` function. + """ + flag = public.RULE_FLAG + rule = public.rule + def my_func(): + pass + assert not hasattr(my_func, flag) + rule(my_func) + assert getattr(my_func, flag) is True + @rule + def my_func2(): + pass + assert getattr(my_func2, flag) is True + + +def test_is_rule(): + """ + Tests the `public.is_rule` function. + """ + is_rule = public.is_rule + flag = public.RULE_FLAG + + class no_call(object): + def __init__(self, value): + if value is not None: + assert value in (True, False) + setattr(self, flag, value) + + class call(no_call): + def __call__(self): + pass + + assert is_rule(call(True)) + assert not is_rule(no_call(True)) + assert not is_rule(call(False)) + assert not is_rule(call(None)) + + +class test_DefaultFrom(ClassChecker): + """ + Tests the `public.DefaultFrom` class. + """ + _cls = public.DefaultFrom + + def test_class(self): + assert self.cls.__bases__ == (plugable.ReadOnly,) + + def test_init(self): + """ + Tests the `public.DefaultFrom.__init__` method. + """ + def callback(*args): + return args + keys = ('givenname', 'sn') + o = self.cls(callback, *keys) + assert read_only(o, 'callback') is callback + assert read_only(o, 'keys') == keys + + def test_call(self): + """ + Tests the `public.DefaultFrom.__call__` method. + """ + def callback(givenname, sn): + return givenname[0] + sn[0] + keys = ('givenname', 'sn') + o = self.cls(callback, *keys) + kw = dict( + givenname='John', + sn='Public', + hello='world', + ) + assert o(**kw) == 'JP' + assert o() is None + for key in ('givenname', 'sn'): + kw_copy = dict(kw) + del kw_copy[key] + assert o(**kw_copy) is None + + +class test_Option(ClassChecker): + """ + Tests the `public.Param` class. + """ + _cls = public.Param + + def test_class(self): + assert self.cls.__bases__ == (plugable.ReadOnly,) + + def test_init(self): + """ + Tests the `public.Param.__init__` method. + """ + name = 'sn' + type_ = ipa_types.Unicode() + o = self.cls(name, type_) + assert o.__islocked__() is True + assert read_only(o, 'name') is name + assert read_only(o, 'type') is type_ + assert read_only(o, 'doc') == '' + assert read_only(o, 'required') is False + assert read_only(o, 'multivalue') is False + assert read_only(o, 'default') is None + assert read_only(o, 'default_from') is None + assert read_only(o, 'rules') == (type_.validate,) + + def test_convert(self): + """ + Tests the `public.Param.convert` method. + """ + name = 'some_number' + type_ = ipa_types.Int() + okay = (7, 7L, 7.0, ' 7 ') + fail = ('7.0', '7L', 'whatever', object) + + # Scenario 1: multivalue=False + o = self.cls(name, type_) + e = raises(TypeError, o.convert, None) + assert str(e) == 'value cannot be None' + for value in okay: + new = o.convert(value) + assert new == 7 + assert type(new) is int + for value in fail: + e = raises(errors.ConversionError, o.convert, value) + assert e.name is name + assert e.value is value + assert e.error is type_.conversion_error + assert e.index is None + + # Scenario 2: multivalue=True + o = self.cls(name, type_, multivalue=True) + for none in [None, (7, None)]: + e = raises(TypeError, o.convert, none) + assert str(e) == 'value cannot be None' + for value in okay: + assert o.convert((value,)) == (7,) + assert o.convert([value]) == (7,) + assert o.convert(okay) == tuple(int(v) for v in okay) + cnt = 5 + for value in fail: + for i in xrange(cnt): + others = list(7 for x in xrange(cnt)) + others[i] = value + for v in [tuple(others), list(others)]: + e = raises(errors.ConversionError, o.convert, v) + assert e.name is name + assert e.value is value + assert e.error is type_.conversion_error + assert e.index == i + + def test_normalize(self): + """ + Tests the `public.Param.normalize` method. + """ + name = 'sn' + t = ipa_types.Unicode() + callback = lambda value: value.lower() + values = (None, u'Hello', (u'Hello',), 'hello', ['hello']) + + # Scenario 1: multivalue=False, normalize=None + o = self.cls(name, t) + for v in values: + # When normalize=None, value is returned, no type checking: + assert o.normalize(v) is v + + # Scenario 2: multivalue=False, normalize=callback + o = self.cls(name, t, normalize=callback) + for v in (u'Hello', u'hello', 'Hello'): # Okay + assert o.normalize(v) == 'hello' + for v in [None, 42, (u'Hello',)]: # Not basestring + check_TypeError(v, basestring, 'value', o.normalize, v) + + # Scenario 3: multivalue=True, normalize=None + o = self.cls(name, t, multivalue=True) + for v in values: + # When normalize=None, value is returned, no type checking: + assert o.normalize(v) is v + + # Scenario 4: multivalue=True, normalize=callback + o = self.cls(name, t, multivalue=True, normalize=callback) + for value in [(u'Hello',), (u'hello',), 'Hello', ['Hello']]: # Okay + assert o.normalize(value) == (u'hello',) + fail = 42 # Not basestring + for v in [fail, [fail], (u'Hello', fail)]: # Non unicode member + check_TypeError(fail, basestring, 'value', o.normalize, v) + + def test_validate(self): + """ + Tests the `public.Param.validate` method. + """ + name = 'sn' + type_ = ipa_types.Unicode() + def case_rule(value): + if not value.islower(): + return 'Must be lower case' + my_rules = (case_rule,) + okay = u'whatever' + fail_case = u'Whatever' + fail_type = 'whatever' + + # Scenario 1: multivalue=False + o = self.cls(name, type_, rules=my_rules) + assert o.rules == (type_.validate, case_rule) + o.validate(okay) + e = raises(errors.RuleError, o.validate, fail_case) + assert e.name is name + assert e.value is fail_case + assert e.error == 'Must be lower case' + assert e.rule is case_rule + assert e.index is None + check_TypeError(fail_type, unicode, 'value', o.validate, fail_type) + + ## Scenario 2: multivalue=True + o = self.cls(name, type_, multivalue=True, rules=my_rules) + o.validate((okay,)) + cnt = 5 + for i in xrange(cnt): + others = list(okay for x in xrange(cnt)) + others[i] = fail_case + value = tuple(others) + e = raises(errors.RuleError, o.validate, value) + assert e.name is name + assert e.value is fail_case + assert e.error == 'Must be lower case' + assert e.rule is case_rule + assert e.index == i + for not_tuple in (okay, [okay]): + check_TypeError(not_tuple, tuple, 'value', o.validate, not_tuple) + for has_str in [(fail_type,), (okay, fail_type)]: + check_TypeError(fail_type, unicode, 'value', o.validate, has_str) + + def test_get_default(self): + """ + Tests the `public.Param.get_default` method. + """ + name = 'greeting' + type_ = ipa_types.Unicode() + default = u'Hello, world!' + default_from = public.DefaultFrom( + lambda first, last: u'Hello, %s %s!' % (first, last), + 'first', 'last' + ) + + # Scenario 1: multivalue=False + o = self.cls(name, type_, + default=default, + default_from=default_from, + ) + assert o.default is default + assert o.default_from is default_from + assert o.get_default() == default + assert o.get_default(first='John', last='Doe') == 'Hello, John Doe!' + + # Scenario 2: multivalue=True + default = (default,) + o = self.cls(name, type_, + default=default, + default_from=default_from, + multivalue=True, + ) + assert o.default is default + assert o.default_from is default_from + assert o.get_default() == default + assert o.get_default(first='John', last='Doe') == ('Hello, John Doe!',) + + def test_get_value(self): + """ + Tests the `public.Param.get_values` method. + """ + name = 'status' + values = (u'Active', u'Inactive') + o = self.cls(name, ipa_types.Unicode()) + assert o.get_values() == tuple() + o = self.cls(name, ipa_types.Enum(*values)) + assert o.get_values() == values + + +def test_create_param(): + """ + Test the `public.create_param` function. + """ + f = public.create_param + for name in ['arg', 'arg?', 'arg*', 'arg+']: + o = f(name) + assert type(o) is public.Param + assert type(o.type) is ipa_types.Unicode + assert o.name == 'arg' + assert f(o) is o + o = f('arg') + assert o.required is True + assert o.multivalue is False + o = f('arg?') + assert o.required is False + assert o.multivalue is False + o = f('arg*') + assert o.required is False + assert o.multivalue is True + o = f('arg+') + assert o.required is True + assert o.multivalue is True + + +class test_Command(ClassChecker): + """ + Tests the `public.Command` class. + """ + _cls = public.Command + + def get_subcls(self): + class Rule(object): + def __init__(self, name): + self.name = name + + def __call__(self, value): + if value != self.name: + return 'must equal %s' % self.name + + default_from = public.DefaultFrom( + lambda arg: arg, + 'default_from' + ) + normalize = lambda value: value.lower() + type_ = ipa_types.Unicode() + + class example(self.cls): + takes_options = ( + public.Param('option0', type_, + normalize=normalize, + default_from=default_from, + rules=(Rule('option0'),) + ), + public.Param('option1', type_, + normalize=normalize, + default_from=default_from, + rules=(Rule('option1'),), + required=True, + ), + ) + return example + + def get_instance(self, args=tuple(), options=tuple()): + """ + Helper method used to test args and options. + """ + class example(self.cls): + takes_args = args + takes_options = options + o = example() + o.finalize() + return o + + def test_class(self): + assert self.cls.__bases__ == (plugable.Plugin,) + assert self.cls.takes_options == tuple() + assert self.cls.takes_args == tuple() + + def test_get_args(self): + """ + Tests the `public.Command.get_args` method. + """ + assert list(self.cls().get_args()) == [] + args = ('login', 'stuff') + o = self.get_instance(args=args) + assert o.get_args() is args + + def test_get_options(self): + """ + Tests the `public.Command.get_options` method. + """ + assert list(self.cls().get_options()) == [] + options = ('verbose', 'debug') + o = self.get_instance(options=options) + assert o.get_options() is options + + def test_args(self): + """ + Tests the ``Command.args`` instance attribute. + """ + assert 'args' in self.cls.__public__ # Public + assert self.cls().args is None + o = self.cls() + o.finalize() + assert type(o.args) is plugable.NameSpace + assert len(o.args) == 0 + args = ('destination', 'source?') + ns = self.get_instance(args=args).args + assert type(ns) is plugable.NameSpace + assert len(ns) == len(args) + assert list(ns) == ['destination', 'source'] + assert type(ns.destination) is public.Param + assert type(ns.source) is public.Param + assert ns.destination.required is True + assert ns.destination.multivalue is False + assert ns.source.required is False + assert ns.source.multivalue is False + + # Test TypeError: + e = raises(TypeError, self.get_instance, args=(u'whatever',)) + assert str(e) == \ + 'create_param() takes %r or %r; got %r' % (str, public.Param, u'whatever') + + # Test ValueError, required after optional: + e = raises(ValueError, self.get_instance, args=('arg1?', 'arg2')) + assert str(e) == 'arg2: required argument after optional' + + # Test ValueError, scalar after multivalue: + e = raises(ValueError, self.get_instance, args=('arg1+', 'arg2')) + assert str(e) == 'arg2: only final argument can be multivalue' + + def test_max_args(self): + """ + Test the ``Command.max_args`` instance attribute. + """ + o = self.get_instance() + assert o.max_args == 0 + o = self.get_instance(args=('one?',)) + assert o.max_args == 1 + o = self.get_instance(args=('one', 'two?')) + assert o.max_args == 2 + o = self.get_instance(args=('one', 'multi+',)) + assert o.max_args is None + o = self.get_instance(args=('one', 'multi*',)) + assert o.max_args is None + + def test_options(self): + """ + Tests the ``Command.options`` instance attribute. + """ + assert 'options' in self.cls.__public__ # Public + assert self.cls().options is None + o = self.cls() + o.finalize() + assert type(o.options) is plugable.NameSpace + assert len(o.options) == 0 + options = ('target', 'files*') + ns = self.get_instance(options=options).options + assert type(ns) is plugable.NameSpace + assert len(ns) == len(options) + assert list(ns) == ['target', 'files'] + assert type(ns.target) is public.Param + assert type(ns.files) is public.Param + assert ns.target.required is True + assert ns.target.multivalue is False + assert ns.files.required is False + assert ns.files.multivalue is True + + def test_convert(self): + """ + Tests the `public.Command.convert` method. + """ + assert 'convert' in self.cls.__public__ # Public + kw = dict( + option0='option0', + option1='option1', + whatever=False, + also=object, + ) + expected = dict(kw) + expected.update(dict(option0=u'option0', option1=u'option1')) + o = self.subcls() + o.finalize() + for (key, value) in o.convert(**kw).iteritems(): + v = expected[key] + assert value == v + assert type(value) is type(v) + + def test_normalize(self): + """ + Tests the `public.Command.normalize` method. + """ + assert 'normalize' in self.cls.__public__ # Public + kw = dict( + option0=u'OPTION0', + option1=u'OPTION1', + option2=u'option2', + ) + norm = dict((k, v.lower()) for (k, v) in kw.items()) + sub = self.subcls() + sub.finalize() + assert sub.normalize(**kw) == norm + + def test_get_default(self): + """ + Tests the `public.Command.get_default` method. + """ + assert 'get_default' in self.cls.__public__ # Public + no_fill = dict( + option0='value0', + option1='value1', + whatever='hello world', + ) + fill = dict( + default_from='the default', + ) + default = dict( + option0='the default', + option1='the default', + ) + sub = self.subcls() + sub.finalize() + assert sub.get_default(**no_fill) == {} + assert sub.get_default(**fill) == default + + def test_validate(self): + """ + Tests the `public.Command.validate` method. + """ + assert 'validate' in self.cls.__public__ # Public + + sub = self.subcls() + sub.finalize() + + # Check with valid args + okay = dict( + option0=u'option0', + option1=u'option1', + another_option='some value', + ) + sub.validate(**okay) + + # Check with an invalid arg + fail = dict(okay) + fail['option0'] = u'whatever' + e = raises(errors.RuleError, sub.validate, **fail) + assert e.name == 'option0' + assert e.value == u'whatever' + assert e.error == 'must equal option0' + assert e.rule.__class__.__name__ == 'Rule' + assert e.index is None + + # Check with a missing required arg + fail = dict(okay) + fail.pop('option1') + e = raises(errors.RequirementError, sub.validate, **fail) + assert e.name == 'option1' + assert e.value is None + assert e.index is None + + def test_execute(self): + """ + Tests the `public.Command.execute` method. + """ + assert 'execute' in self.cls.__public__ # Public + + def test_args_to_kw(self): + """ + Test the `public.Command.args_to_kw` method. + """ + assert 'args_to_kw' in self.cls.__public__ # Public + o = self.get_instance(args=('one', 'two?')) + assert o.args_to_kw(1) == dict(one=1) + assert o.args_to_kw(1, 2) == dict(one=1, two=2) + + o = self.get_instance(args=('one', 'two*')) + assert o.args_to_kw(1) == dict(one=1) + assert o.args_to_kw(1, 2) == dict(one=1, two=(2,)) + assert o.args_to_kw(1, 2, 3) == dict(one=1, two=(2, 3)) + + o = self.get_instance(args=('one', 'two+')) + assert o.args_to_kw(1) == dict(one=1) + assert o.args_to_kw(1, 2) == dict(one=1, two=(2,)) + assert o.args_to_kw(1, 2, 3) == dict(one=1, two=(2, 3)) + + o = self.get_instance() + e = raises(errors.ArgumentError, o.args_to_kw, 1) + assert str(e) == 'example takes no arguments' + + o = self.get_instance(args=('one?',)) + e = raises(errors.ArgumentError, o.args_to_kw, 1, 2) + assert str(e) == 'example takes at most 1 argument' + + o = self.get_instance(args=('one', 'two?')) + e = raises(errors.ArgumentError, o.args_to_kw, 1, 2, 3) + assert str(e) == 'example takes at most 2 arguments' + + def test_kw_to_args(self): + """ + Tests the `public.Command.kw_to_args` method. + """ + assert 'kw_to_args' in self.cls.__public__ # Public + o = self.get_instance(args=('one', 'two?')) + assert o.kw_to_args() == (None, None) + assert o.kw_to_args(whatever='hello') == (None, None) + assert o.kw_to_args(one='the one') == ('the one', None) + assert o.kw_to_args(two='the two') == (None, 'the two') + assert o.kw_to_args(whatever='hello', two='Two', one='One') == \ + ('One', 'Two') + + +class test_Object(ClassChecker): + """ + Tests the `public.Object` class. + """ + _cls = public.Object + + def test_class(self): + assert self.cls.__bases__ == (plugable.Plugin,) + assert type(self.cls.Method) is property + assert type(self.cls.Property) is property + + def test_init(self): + """ + Tests the `public.Object.__init__` method. + """ + o = self.cls() + assert read_only(o, 'Method') is None + assert read_only(o, 'Property') is None + + def test_set_api(self): + """ + Tests the `public.Object.set_api` method. + """ + # Setup for test: + class DummyAttribute(object): + def __init__(self, obj_name, attr_name, name=None): + self.obj_name = obj_name + self.attr_name = attr_name + if name is None: + self.name = '%s_%s' % (obj_name, attr_name) + else: + self.name = name + def __clone__(self, attr_name): + return self.__class__( + self.obj_name, + self.attr_name, + getattr(self, attr_name) + ) + + def get_attributes(cnt, format): + for name in ['other', 'user', 'another']: + for i in xrange(cnt): + yield DummyAttribute(name, format % i) + + cnt = 10 + formats = dict( + Method='method_%d', + Property='property_%d', + ) + + class api(object): + Method = plugable.NameSpace( + get_attributes(cnt, formats['Method']) + ) + Property = plugable.NameSpace( + get_attributes(cnt, formats['Property']) + ) + assert len(api.Method) == cnt * 3 + assert len(api.Property) == cnt * 3 + + class user(self.cls): + pass + + # Actually perform test: + o = user() + o.set_api(api) + assert read_only(o, 'api') is api + for name in ['Method', 'Property']: + namespace = getattr(o, name) + assert isinstance(namespace, plugable.NameSpace) + assert len(namespace) == cnt + f = formats[name] + for i in xrange(cnt): + attr_name = f % i + attr = namespace[attr_name] + assert isinstance(attr, DummyAttribute) + assert attr is getattr(namespace, attr_name) + assert attr.obj_name == 'user' + assert attr.attr_name == attr_name + assert attr.name == attr_name + + def test_params(self): + """ + Test the ``public.Object.params`` instance attribute. + """ + ns = self.cls().params + assert type(ns) is plugable.NameSpace + assert len(ns) == 0 + class example(self.cls): + takes_params = ('banana', 'apple') + ns = example().params + assert type(ns) is plugable.NameSpace + assert len(ns) == 2, repr(ns) + assert list(ns) == ['banana', 'apple'] + for p in ns(): + assert type(p) is public.Param + assert p.required is True + assert p.multivalue is False + + +class test_Attribute(ClassChecker): + """ + Tests the `public.Attribute` class. + """ + _cls = public.Attribute + + def test_class(self): + assert self.cls.__bases__ == (plugable.Plugin,) + assert type(self.cls.obj) is property + assert type(self.cls.obj_name) is property + assert type(self.cls.attr_name) is property + + def test_init(self): + """ + Tests the `public.Attribute.__init__` method. + """ + class user_add(self.cls): + pass + o = user_add() + assert read_only(o, 'obj') is None + assert read_only(o, 'obj_name') == 'user' + assert read_only(o, 'attr_name') == 'add' + + def test_set_api(self): + """ + Tests the `public.Attribute.set_api` method. + """ + user_obj = 'The user public.Object instance' + class api(object): + Object = dict(user=user_obj) + class user_add(self.cls): + pass + o = user_add() + assert read_only(o, 'api') is None + assert read_only(o, 'obj') is None + o.set_api(api) + assert read_only(o, 'api') is api + assert read_only(o, 'obj') is user_obj + + +class test_Method(ClassChecker): + """ + Tests the `public.Method` class. + """ + _cls = public.Method + + def test_class(self): + assert self.cls.__bases__ == (public.Attribute, public.Command) + assert self.cls.implements(public.Command) + + def get_subcls(self): + class example_prop0(public.Property): + 'Prop zero' + class example_prop1(public.Property): + 'Prop one' + class example_obj(object): + __prop = None + def __get_prop(self): + if self.__prop is None: + self.__prop = plugable.NameSpace([ + plugable.PluginProxy( + public.Property, example_prop0(), 'attr_name' + ), + plugable.PluginProxy( + public.Property, example_prop1(), 'attr_name' + ), + ]) + return self.__prop + Property = property(__get_prop) + type_ = ipa_types.Unicode() + class noun_verb(self.cls): + takes_options= ( + public.Param('option0', type_), + public.Param('option1', type_), + ) + obj = example_obj() + return noun_verb + + def test_get_options(self): + """ + Tests the `public.Method.get_options` method. + """ + sub = self.subcls() + names = ('option0', 'option1', 'prop0', 'prop1') + options = tuple(sub.get_options()) + assert len(options) == 4 + for (i, option) in enumerate(options): + assert option.name == names[i] + assert isinstance(option, public.Param) + + +class test_Property(ClassChecker): + """ + Tests the `public.Property` class. + """ + _cls = public.Property + + def get_subcls(self): + class user_givenname(self.cls): + 'User first name' + + @public.rule + def rule0_lowercase(self, value): + if not value.islower(): + return 'Must be lowercase' + return user_givenname + + def test_class(self): + assert self.cls.__bases__ == (public.Attribute,) + assert isinstance(self.cls.type, ipa_types.Unicode) + assert self.cls.required is False + assert self.cls.multivalue is False + assert self.cls.default is None + assert self.cls.default_from is None + assert self.cls.normalize is None + + def test_init(self): + """ + Tests the `public.Property.__init__` method. + """ + o = self.subcls() + assert len(o.rules) == 1 + assert o.rules[0].__name__ == 'rule0_lowercase' + param = o.param + assert isinstance(param, public.Param) + assert param.name == 'givenname' + assert param.doc == 'User first name' + + +class test_Application(ClassChecker): + """ + Tests the `public.Application` class. + """ + _cls = public.Application + + def test_class(self): + assert self.cls.__bases__ == (public.Command,) + assert type(self.cls.application) is property + + def test_application(self): + """ + Tests the `public.Application.application` property. + """ + assert 'application' in self.cls.__public__ # Public + assert 'set_application' in self.cls.__public__ # Public + app = 'The external application' + class example(self.cls): + 'A subclass' + for o in (self.cls(), example()): + assert read_only(o, 'application') is None + e = raises(TypeError, o.set_application, None) + assert str(e) == ( + '%s.application cannot be None' % o.__class__.__name__ + ) + o.set_application(app) + assert read_only(o, 'application') is app + e = raises(AttributeError, o.set_application, app) + assert str(e) == ( + '%s.application can only be set once' % o.__class__.__name__ + ) + assert read_only(o, 'application') is app diff --git a/ipalib/tests/test_public.py b/ipalib/tests/test_public.py deleted file mode 100644 index fa78773f..00000000 --- a/ipalib/tests/test_public.py +++ /dev/null @@ -1,884 +0,0 @@ -# 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 - -""" -Unit tests for `ipalib.public` module. -""" - -from tstutil import raises, getitem, no_set, no_del, read_only, ClassChecker -from tstutil import check_TypeError -from ipalib import public, plugable, errors, ipa_types - - -def test_RULE_FLAG(): - assert public.RULE_FLAG == 'validation_rule' - - -def test_rule(): - """ - Tests the `public.rule` function. - """ - flag = public.RULE_FLAG - rule = public.rule - def my_func(): - pass - assert not hasattr(my_func, flag) - rule(my_func) - assert getattr(my_func, flag) is True - @rule - def my_func2(): - pass - assert getattr(my_func2, flag) is True - - -def test_is_rule(): - """ - Tests the `public.is_rule` function. - """ - is_rule = public.is_rule - flag = public.RULE_FLAG - - class no_call(object): - def __init__(self, value): - if value is not None: - assert value in (True, False) - setattr(self, flag, value) - - class call(no_call): - def __call__(self): - pass - - assert is_rule(call(True)) - assert not is_rule(no_call(True)) - assert not is_rule(call(False)) - assert not is_rule(call(None)) - - -class test_DefaultFrom(ClassChecker): - """ - Tests the `public.DefaultFrom` class. - """ - _cls = public.DefaultFrom - - def test_class(self): - assert self.cls.__bases__ == (plugable.ReadOnly,) - - def test_init(self): - """ - Tests the `public.DefaultFrom.__init__` method. - """ - def callback(*args): - return args - keys = ('givenname', 'sn') - o = self.cls(callback, *keys) - assert read_only(o, 'callback') is callback - assert read_only(o, 'keys') == keys - - def test_call(self): - """ - Tests the `public.DefaultFrom.__call__` method. - """ - def callback(givenname, sn): - return givenname[0] + sn[0] - keys = ('givenname', 'sn') - o = self.cls(callback, *keys) - kw = dict( - givenname='John', - sn='Public', - hello='world', - ) - assert o(**kw) == 'JP' - assert o() is None - for key in ('givenname', 'sn'): - kw_copy = dict(kw) - del kw_copy[key] - assert o(**kw_copy) is None - - -class test_Option(ClassChecker): - """ - Tests the `public.Param` class. - """ - _cls = public.Param - - def test_class(self): - assert self.cls.__bases__ == (plugable.ReadOnly,) - - def test_init(self): - """ - Tests the `public.Param.__init__` method. - """ - name = 'sn' - type_ = ipa_types.Unicode() - o = self.cls(name, type_) - assert o.__islocked__() is True - assert read_only(o, 'name') is name - assert read_only(o, 'type') is type_ - assert read_only(o, 'doc') == '' - assert read_only(o, 'required') is False - assert read_only(o, 'multivalue') is False - assert read_only(o, 'default') is None - assert read_only(o, 'default_from') is None - assert read_only(o, 'rules') == (type_.validate,) - - def test_convert(self): - """ - Tests the `public.Param.convert` method. - """ - name = 'some_number' - type_ = ipa_types.Int() - okay = (7, 7L, 7.0, ' 7 ') - fail = ('7.0', '7L', 'whatever', object) - - # Scenario 1: multivalue=False - o = self.cls(name, type_) - e = raises(TypeError, o.convert, None) - assert str(e) == 'value cannot be None' - for value in okay: - new = o.convert(value) - assert new == 7 - assert type(new) is int - for value in fail: - e = raises(errors.ConversionError, o.convert, value) - assert e.name is name - assert e.value is value - assert e.error is type_.conversion_error - assert e.index is None - - # Scenario 2: multivalue=True - o = self.cls(name, type_, multivalue=True) - for none in [None, (7, None)]: - e = raises(TypeError, o.convert, none) - assert str(e) == 'value cannot be None' - for value in okay: - assert o.convert((value,)) == (7,) - assert o.convert([value]) == (7,) - assert o.convert(okay) == tuple(int(v) for v in okay) - cnt = 5 - for value in fail: - for i in xrange(cnt): - others = list(7 for x in xrange(cnt)) - others[i] = value - for v in [tuple(others), list(others)]: - e = raises(errors.ConversionError, o.convert, v) - assert e.name is name - assert e.value is value - assert e.error is type_.conversion_error - assert e.index == i - - def test_normalize(self): - """ - Tests the `public.Param.normalize` method. - """ - name = 'sn' - t = ipa_types.Unicode() - callback = lambda value: value.lower() - values = (None, u'Hello', (u'Hello',), 'hello', ['hello']) - - # Scenario 1: multivalue=False, normalize=None - o = self.cls(name, t) - for v in values: - # When normalize=None, value is returned, no type checking: - assert o.normalize(v) is v - - # Scenario 2: multivalue=False, normalize=callback - o = self.cls(name, t, normalize=callback) - for v in (u'Hello', u'hello', 'Hello'): # Okay - assert o.normalize(v) == 'hello' - for v in [None, 42, (u'Hello',)]: # Not basestring - check_TypeError(v, basestring, 'value', o.normalize, v) - - # Scenario 3: multivalue=True, normalize=None - o = self.cls(name, t, multivalue=True) - for v in values: - # When normalize=None, value is returned, no type checking: - assert o.normalize(v) is v - - # Scenario 4: multivalue=True, normalize=callback - o = self.cls(name, t, multivalue=True, normalize=callback) - for value in [(u'Hello',), (u'hello',), 'Hello', ['Hello']]: # Okay - assert o.normalize(value) == (u'hello',) - fail = 42 # Not basestring - for v in [fail, [fail], (u'Hello', fail)]: # Non unicode member - check_TypeError(fail, basestring, 'value', o.normalize, v) - - def test_validate(self): - """ - Tests the `public.Param.validate` method. - """ - name = 'sn' - type_ = ipa_types.Unicode() - def case_rule(value): - if not value.islower(): - return 'Must be lower case' - my_rules = (case_rule,) - okay = u'whatever' - fail_case = u'Whatever' - fail_type = 'whatever' - - # Scenario 1: multivalue=False - o = self.cls(name, type_, rules=my_rules) - assert o.rules == (type_.validate, case_rule) - o.validate(okay) - e = raises(errors.RuleError, o.validate, fail_case) - assert e.name is name - assert e.value is fail_case - assert e.error == 'Must be lower case' - assert e.rule is case_rule - assert e.index is None - check_TypeError(fail_type, unicode, 'value', o.validate, fail_type) - - ## Scenario 2: multivalue=True - o = self.cls(name, type_, multivalue=True, rules=my_rules) - o.validate((okay,)) - cnt = 5 - for i in xrange(cnt): - others = list(okay for x in xrange(cnt)) - others[i] = fail_case - value = tuple(others) - e = raises(errors.RuleError, o.validate, value) - assert e.name is name - assert e.value is fail_case - assert e.error == 'Must be lower case' - assert e.rule is case_rule - assert e.index == i - for not_tuple in (okay, [okay]): - check_TypeError(not_tuple, tuple, 'value', o.validate, not_tuple) - for has_str in [(fail_type,), (okay, fail_type)]: - check_TypeError(fail_type, unicode, 'value', o.validate, has_str) - - def test_get_default(self): - """ - Tests the `public.Param.get_default` method. - """ - name = 'greeting' - type_ = ipa_types.Unicode() - default = u'Hello, world!' - default_from = public.DefaultFrom( - lambda first, last: u'Hello, %s %s!' % (first, last), - 'first', 'last' - ) - - # Scenario 1: multivalue=False - o = self.cls(name, type_, - default=default, - default_from=default_from, - ) - assert o.default is default - assert o.default_from is default_from - assert o.get_default() == default - assert o.get_default(first='John', last='Doe') == 'Hello, John Doe!' - - # Scenario 2: multivalue=True - default = (default,) - o = self.cls(name, type_, - default=default, - default_from=default_from, - multivalue=True, - ) - assert o.default is default - assert o.default_from is default_from - assert o.get_default() == default - assert o.get_default(first='John', last='Doe') == ('Hello, John Doe!',) - - def test_get_value(self): - """ - Tests the `public.Param.get_values` method. - """ - name = 'status' - values = (u'Active', u'Inactive') - o = self.cls(name, ipa_types.Unicode()) - assert o.get_values() == tuple() - o = self.cls(name, ipa_types.Enum(*values)) - assert o.get_values() == values - - -def test_create_param(): - """ - Test the `public.create_param` function. - """ - f = public.create_param - for name in ['arg', 'arg?', 'arg*', 'arg+']: - o = f(name) - assert type(o) is public.Param - assert type(o.type) is ipa_types.Unicode - assert o.name == 'arg' - assert f(o) is o - o = f('arg') - assert o.required is True - assert o.multivalue is False - o = f('arg?') - assert o.required is False - assert o.multivalue is False - o = f('arg*') - assert o.required is False - assert o.multivalue is True - o = f('arg+') - assert o.required is True - assert o.multivalue is True - - -class test_Command(ClassChecker): - """ - Tests the `public.Command` class. - """ - _cls = public.Command - - def get_subcls(self): - class Rule(object): - def __init__(self, name): - self.name = name - - def __call__(self, value): - if value != self.name: - return 'must equal %s' % self.name - - default_from = public.DefaultFrom( - lambda arg: arg, - 'default_from' - ) - normalize = lambda value: value.lower() - type_ = ipa_types.Unicode() - - class example(self.cls): - takes_options = ( - public.Param('option0', type_, - normalize=normalize, - default_from=default_from, - rules=(Rule('option0'),) - ), - public.Param('option1', type_, - normalize=normalize, - default_from=default_from, - rules=(Rule('option1'),), - required=True, - ), - ) - return example - - def get_instance(self, args=tuple(), options=tuple()): - """ - Helper method used to test args and options. - """ - class example(self.cls): - takes_args = args - takes_options = options - o = example() - o.finalize() - return o - - def test_class(self): - assert self.cls.__bases__ == (plugable.Plugin,) - assert self.cls.takes_options == tuple() - assert self.cls.takes_args == tuple() - - def test_get_args(self): - """ - Tests the `public.Command.get_args` method. - """ - assert list(self.cls().get_args()) == [] - args = ('login', 'stuff') - o = self.get_instance(args=args) - assert o.get_args() is args - - def test_get_options(self): - """ - Tests the `public.Command.get_options` method. - """ - assert list(self.cls().get_options()) == [] - options = ('verbose', 'debug') - o = self.get_instance(options=options) - assert o.get_options() is options - - def test_args(self): - """ - Tests the ``Command.args`` instance attribute. - """ - assert 'args' in self.cls.__public__ # Public - assert self.cls().args is None - o = self.cls() - o.finalize() - assert type(o.args) is plugable.NameSpace - assert len(o.args) == 0 - args = ('destination', 'source?') - ns = self.get_instance(args=args).args - assert type(ns) is plugable.NameSpace - assert len(ns) == len(args) - assert list(ns) == ['destination', 'source'] - assert type(ns.destination) is public.Param - assert type(ns.source) is public.Param - assert ns.destination.required is True - assert ns.destination.multivalue is False - assert ns.source.required is False - assert ns.source.multivalue is False - - # Test TypeError: - e = raises(TypeError, self.get_instance, args=(u'whatever',)) - assert str(e) == \ - 'create_param() takes %r or %r; got %r' % (str, public.Param, u'whatever') - - # Test ValueError, required after optional: - e = raises(ValueError, self.get_instance, args=('arg1?', 'arg2')) - assert str(e) == 'arg2: required argument after optional' - - # Test ValueError, scalar after multivalue: - e = raises(ValueError, self.get_instance, args=('arg1+', 'arg2')) - assert str(e) == 'arg2: only final argument can be multivalue' - - def test_max_args(self): - """ - Test the ``Command.max_args`` instance attribute. - """ - o = self.get_instance() - assert o.max_args == 0 - o = self.get_instance(args=('one?',)) - assert o.max_args == 1 - o = self.get_instance(args=('one', 'two?')) - assert o.max_args == 2 - o = self.get_instance(args=('one', 'multi+',)) - assert o.max_args is None - o = self.get_instance(args=('one', 'multi*',)) - assert o.max_args is None - - def test_options(self): - """ - Tests the ``Command.options`` instance attribute. - """ - assert 'options' in self.cls.__public__ # Public - assert self.cls().options is None - o = self.cls() - o.finalize() - assert type(o.options) is plugable.NameSpace - assert len(o.options) == 0 - options = ('target', 'files*') - ns = self.get_instance(options=options).options - assert type(ns) is plugable.NameSpace - assert len(ns) == len(options) - assert list(ns) == ['target', 'files'] - assert type(ns.target) is public.Param - assert type(ns.files) is public.Param - assert ns.target.required is True - assert ns.target.multivalue is False - assert ns.files.required is False - assert ns.files.multivalue is True - - def test_convert(self): - """ - Tests the `public.Command.convert` method. - """ - assert 'convert' in self.cls.__public__ # Public - kw = dict( - option0='option0', - option1='option1', - whatever=False, - also=object, - ) - expected = dict(kw) - expected.update(dict(option0=u'option0', option1=u'option1')) - o = self.subcls() - o.finalize() - for (key, value) in o.convert(**kw).iteritems(): - v = expected[key] - assert value == v - assert type(value) is type(v) - - def test_normalize(self): - """ - Tests the `public.Command.normalize` method. - """ - assert 'normalize' in self.cls.__public__ # Public - kw = dict( - option0=u'OPTION0', - option1=u'OPTION1', - option2=u'option2', - ) - norm = dict((k, v.lower()) for (k, v) in kw.items()) - sub = self.subcls() - sub.finalize() - assert sub.normalize(**kw) == norm - - def test_get_default(self): - """ - Tests the `public.Command.get_default` method. - """ - assert 'get_default' in self.cls.__public__ # Public - no_fill = dict( - option0='value0', - option1='value1', - whatever='hello world', - ) - fill = dict( - default_from='the default', - ) - default = dict( - option0='the default', - option1='the default', - ) - sub = self.subcls() - sub.finalize() - assert sub.get_default(**no_fill) == {} - assert sub.get_default(**fill) == default - - def test_validate(self): - """ - Tests the `public.Command.validate` method. - """ - assert 'validate' in self.cls.__public__ # Public - - sub = self.subcls() - sub.finalize() - - # Check with valid args - okay = dict( - option0=u'option0', - option1=u'option1', - another_option='some value', - ) - sub.validate(**okay) - - # Check with an invalid arg - fail = dict(okay) - fail['option0'] = u'whatever' - e = raises(errors.RuleError, sub.validate, **fail) - assert e.name == 'option0' - assert e.value == u'whatever' - assert e.error == 'must equal option0' - assert e.rule.__class__.__name__ == 'Rule' - assert e.index is None - - # Check with a missing required arg - fail = dict(okay) - fail.pop('option1') - e = raises(errors.RequirementError, sub.validate, **fail) - assert e.name == 'option1' - assert e.value is None - assert e.index is None - - def test_execute(self): - """ - Tests the `public.Command.execute` method. - """ - assert 'execute' in self.cls.__public__ # Public - - def test_args_to_kw(self): - """ - Test the `public.Command.args_to_kw` method. - """ - assert 'args_to_kw' in self.cls.__public__ # Public - o = self.get_instance(args=('one', 'two?')) - assert o.args_to_kw(1) == dict(one=1) - assert o.args_to_kw(1, 2) == dict(one=1, two=2) - - o = self.get_instance(args=('one', 'two*')) - assert o.args_to_kw(1) == dict(one=1) - assert o.args_to_kw(1, 2) == dict(one=1, two=(2,)) - assert o.args_to_kw(1, 2, 3) == dict(one=1, two=(2, 3)) - - o = self.get_instance(args=('one', 'two+')) - assert o.args_to_kw(1) == dict(one=1) - assert o.args_to_kw(1, 2) == dict(one=1, two=(2,)) - assert o.args_to_kw(1, 2, 3) == dict(one=1, two=(2, 3)) - - o = self.get_instance() - e = raises(errors.ArgumentError, o.args_to_kw, 1) - assert str(e) == 'example takes no arguments' - - o = self.get_instance(args=('one?',)) - e = raises(errors.ArgumentError, o.args_to_kw, 1, 2) - assert str(e) == 'example takes at most 1 argument' - - o = self.get_instance(args=('one', 'two?')) - e = raises(errors.ArgumentError, o.args_to_kw, 1, 2, 3) - assert str(e) == 'example takes at most 2 arguments' - - def test_kw_to_args(self): - """ - Tests the `public.Command.kw_to_args` method. - """ - assert 'kw_to_args' in self.cls.__public__ # Public - o = self.get_instance(args=('one', 'two?')) - assert o.kw_to_args() == (None, None) - assert o.kw_to_args(whatever='hello') == (None, None) - assert o.kw_to_args(one='the one') == ('the one', None) - assert o.kw_to_args(two='the two') == (None, 'the two') - assert o.kw_to_args(whatever='hello', two='Two', one='One') == \ - ('One', 'Two') - - -class test_Object(ClassChecker): - """ - Tests the `public.Object` class. - """ - _cls = public.Object - - def test_class(self): - assert self.cls.__bases__ == (plugable.Plugin,) - assert type(self.cls.Method) is property - assert type(self.cls.Property) is property - - def test_init(self): - """ - Tests the `public.Object.__init__` method. - """ - o = self.cls() - assert read_only(o, 'Method') is None - assert read_only(o, 'Property') is None - - def test_set_api(self): - """ - Tests the `public.Object.set_api` method. - """ - # Setup for test: - class DummyAttribute(object): - def __init__(self, obj_name, attr_name, name=None): - self.obj_name = obj_name - self.attr_name = attr_name - if name is None: - self.name = '%s_%s' % (obj_name, attr_name) - else: - self.name = name - def __clone__(self, attr_name): - return self.__class__( - self.obj_name, - self.attr_name, - getattr(self, attr_name) - ) - - def get_attributes(cnt, format): - for name in ['other', 'user', 'another']: - for i in xrange(cnt): - yield DummyAttribute(name, format % i) - - cnt = 10 - formats = dict( - Method='method_%d', - Property='property_%d', - ) - - class api(object): - Method = plugable.NameSpace( - get_attributes(cnt, formats['Method']) - ) - Property = plugable.NameSpace( - get_attributes(cnt, formats['Property']) - ) - assert len(api.Method) == cnt * 3 - assert len(api.Property) == cnt * 3 - - class user(self.cls): - pass - - # Actually perform test: - o = user() - o.set_api(api) - assert read_only(o, 'api') is api - for name in ['Method', 'Property']: - namespace = getattr(o, name) - assert isinstance(namespace, plugable.NameSpace) - assert len(namespace) == cnt - f = formats[name] - for i in xrange(cnt): - attr_name = f % i - attr = namespace[attr_name] - assert isinstance(attr, DummyAttribute) - assert attr is getattr(namespace, attr_name) - assert attr.obj_name == 'user' - assert attr.attr_name == attr_name - assert attr.name == attr_name - - def test_params(self): - """ - Test the ``public.Object.params`` instance attribute. - """ - ns = self.cls().params - assert type(ns) is plugable.NameSpace - assert len(ns) == 0 - class example(self.cls): - takes_params = ('banana', 'apple') - ns = example().params - assert type(ns) is plugable.NameSpace - assert len(ns) == 2, repr(ns) - assert list(ns) == ['banana', 'apple'] - for p in ns(): - assert type(p) is public.Param - assert p.required is True - assert p.multivalue is False - - -class test_Attribute(ClassChecker): - """ - Tests the `public.Attribute` class. - """ - _cls = public.Attribute - - def test_class(self): - assert self.cls.__bases__ == (plugable.Plugin,) - assert type(self.cls.obj) is property - assert type(self.cls.obj_name) is property - assert type(self.cls.attr_name) is property - - def test_init(self): - """ - Tests the `public.Attribute.__init__` method. - """ - class user_add(self.cls): - pass - o = user_add() - assert read_only(o, 'obj') is None - assert read_only(o, 'obj_name') == 'user' - assert read_only(o, 'attr_name') == 'add' - - def test_set_api(self): - """ - Tests the `public.Attribute.set_api` method. - """ - user_obj = 'The user public.Object instance' - class api(object): - Object = dict(user=user_obj) - class user_add(self.cls): - pass - o = user_add() - assert read_only(o, 'api') is None - assert read_only(o, 'obj') is None - o.set_api(api) - assert read_only(o, 'api') is api - assert read_only(o, 'obj') is user_obj - - -class test_Method(ClassChecker): - """ - Tests the `public.Method` class. - """ - _cls = public.Method - - def test_class(self): - assert self.cls.__bases__ == (public.Attribute, public.Command) - assert self.cls.implements(public.Command) - - def get_subcls(self): - class example_prop0(public.Property): - 'Prop zero' - class example_prop1(public.Property): - 'Prop one' - class example_obj(object): - __prop = None - def __get_prop(self): - if self.__prop is None: - self.__prop = plugable.NameSpace([ - plugable.PluginProxy( - public.Property, example_prop0(), 'attr_name' - ), - plugable.PluginProxy( - public.Property, example_prop1(), 'attr_name' - ), - ]) - return self.__prop - Property = property(__get_prop) - type_ = ipa_types.Unicode() - class noun_verb(self.cls): - takes_options= ( - public.Param('option0', type_), - public.Param('option1', type_), - ) - obj = example_obj() - return noun_verb - - def test_get_options(self): - """ - Tests the `public.Method.get_options` method. - """ - sub = self.subcls() - names = ('option0', 'option1', 'prop0', 'prop1') - options = tuple(sub.get_options()) - assert len(options) == 4 - for (i, option) in enumerate(options): - assert option.name == names[i] - assert isinstance(option, public.Param) - - -class test_Property(ClassChecker): - """ - Tests the `public.Property` class. - """ - _cls = public.Property - - def get_subcls(self): - class user_givenname(self.cls): - 'User first name' - - @public.rule - def rule0_lowercase(self, value): - if not value.islower(): - return 'Must be lowercase' - return user_givenname - - def test_class(self): - assert self.cls.__bases__ == (public.Attribute,) - assert isinstance(self.cls.type, ipa_types.Unicode) - assert self.cls.required is False - assert self.cls.multivalue is False - assert self.cls.default is None - assert self.cls.default_from is None - assert self.cls.normalize is None - - def test_init(self): - """ - Tests the `public.Property.__init__` method. - """ - o = self.subcls() - assert len(o.rules) == 1 - assert o.rules[0].__name__ == 'rule0_lowercase' - param = o.param - assert isinstance(param, public.Param) - assert param.name == 'givenname' - assert param.doc == 'User first name' - - -class test_Application(ClassChecker): - """ - Tests the `public.Application` class. - """ - _cls = public.Application - - def test_class(self): - assert self.cls.__bases__ == (public.Command,) - assert type(self.cls.application) is property - - def test_application(self): - """ - Tests the `public.Application.application` property. - """ - assert 'application' in self.cls.__public__ # Public - assert 'set_application' in self.cls.__public__ # Public - app = 'The external application' - class example(self.cls): - 'A subclass' - for o in (self.cls(), example()): - assert read_only(o, 'application') is None - e = raises(TypeError, o.set_application, None) - assert str(e) == ( - '%s.application cannot be None' % o.__class__.__name__ - ) - o.set_application(app) - assert read_only(o, 'application') is app - e = raises(AttributeError, o.set_application, app) - assert str(e) == ( - '%s.application can only be set once' % o.__class__.__name__ - ) - assert read_only(o, 'application') is app -- cgit