schema: client-side code cleanup

Move client-side code scattered in global functions into neat classes.

https://fedorahosted.org/freeipa/ticket/4739

Reviewed-By: David Kupka <dkupka@redhat.com>
This commit is contained in:
Jan Cholasta
2016-06-22 13:27:25 +02:00
parent 61987b66ba
commit f7cc15f099

View File

@@ -13,8 +13,8 @@ from ipaclient.plugins.rpcclient import rpcclient
from ipalib import parameters, plugable from ipalib import parameters, plugable
from ipalib.frontend import Command, Method, Object from ipalib.frontend import Command, Method, Object
from ipalib.output import Output from ipalib.output import Output
from ipalib.parameters import Bool, DefaultFrom, Flag, Password, Str from ipalib.parameters import DefaultFrom, Flag, Password, Str
from ipalib.text import ConcatenatedLazyText, _ from ipalib.text import _
from ipapython.dn import DN from ipapython.dn import DN
from ipapython.dnsutil import DNSName from ipapython.dnsutil import DNSName
@@ -48,39 +48,6 @@ _PARAMS = {
class _SchemaCommand(Command): class _SchemaCommand(Command):
def __fix_default_from(self, param):
api = self.api
name = unicode(self.name)
param_name = unicode(param.name)
keys = param.default_from.keys
if keys:
def callback(*args):
kw = dict(zip(keys, args))
result = api.Command.command_defaults(
name,
params=[param_name],
kw=kw,
)['result']
return result.get(param_name)
else:
def callback():
result = api.Command.command_defaults(
name,
params=[param_name],
)['result']
return result.get(param_name)
callback.__name__ = '{0}_{1}_default'.format(self.name, param.name)
return param.clone(default_from=DefaultFrom(callback, *keys))
def get_args(self):
for arg in super(_SchemaCommand, self).get_args():
if arg.default_from is not None:
arg = self.__fix_default_from(arg)
yield arg
def get_options(self): def get_options(self):
skip = set() skip = set()
for option in super(_SchemaCommand, self).get_options(): for option in super(_SchemaCommand, self).get_options():
@@ -88,12 +55,6 @@ class _SchemaCommand(Command):
continue continue
if option.name in ('all', 'raw'): if option.name in ('all', 'raw'):
skip.add(option.name) skip.add(option.name)
if option.default_from is not None:
option = self.__fix_default_from(option)
if (isinstance(option, Bool) and
option.autofill and
option.default is False):
option = option.clone_retype(option.name, Flag)
yield option yield option
@@ -182,26 +143,54 @@ class _SchemaMethod(Method, _SchemaCommand):
yield output_param yield output_param
def _nope(): class _SchemaObject(Object):
pass pass
def _create_param_convert_scalar(cls): class _SchemaPlugin(object):
def _convert_scalar(self, value, index=None): bases = None
if isinstance(value, unicode): schema_key = None
return value
return super(cls, self)._convert_scalar(value)
return _convert_scalar def __init__(self, name):
self.name = name
self.__class = None
def _create_default_from(self, api, name, keys):
cmd_name = self.name
def _create_param(meta): def get_default(*args):
type_name = str(meta['type']) kw = dict(zip(keys, args))
sensitive = meta.get('sensitive', False) result = api.Command.command_defaults(
unicode(cmd_name),
params=[unicode(name)],
kw=kw,
)['result']
return result.get(name)
if keys:
def callback(*args):
return get_default(*args)
else:
def callback():
return get_default()
callback.__name__ = '{0}_{1}_default'.format(cmd_name, name)
return DefaultFrom(callback, *keys)
def _create_param(self, api, schema):
name = str(schema['name'])
type_name = str(schema['type'])
sensitive = schema.get('sensitive', False)
if type_name == 'str' and sensitive: if type_name == 'str' and sensitive:
cls = Password cls = Password
sensitive = False sensitive = False
elif (type_name == 'bool' and
'default' in schema and
schema['default'] == [u'False']):
cls = Flag
del schema['default']
else: else:
try: try:
cls = _PARAMS[type_name] cls = _PARAMS[type_name]
@@ -211,32 +200,32 @@ def _create_param(meta):
kwargs = {} kwargs = {}
default = None default = None
for key, value in meta.items(): for key, value in schema.items():
if key in ('alwaysask', if key in ('alwaysask',
'doc', 'doc',
'label', 'label',
'multivalue', 'multivalue',
'no_convert', 'no_convert',
'option_group', 'option_group',
'required', 'required'):
'sortorder'):
kwargs[key] = value kwargs[key] = value
elif key in ('cli_metavar', elif key in ('cli_metavar',
'cli_name'): 'cli_name'):
kwargs[key] = str(value) kwargs[key] = str(value)
elif key == 'confirm' and issubclass(cls, parameters.Password): elif key == 'confirm' and issubclass(cls, Password):
kwargs[key] = value kwargs[key] = value
elif key == 'default': elif key == 'default':
default = value default = value
elif key == 'default_from_param': elif key == 'default_from_param':
kwargs['default_from'] = DefaultFrom(_nope, keys = tuple(str(k) for k in value)
*(str(k) for k in value)) kwargs['default_from'] = (
self._create_default_from(api, name, keys))
elif key in ('exclude', elif key in ('exclude',
'include'): 'include'):
kwargs[key] = tuple(str(v) for v in value) kwargs[key] = tuple(str(v) for v in value)
if default is not None: if default is not None:
tmp = cls(str(meta['name']), **dict(kwargs, no_convert=False)) tmp = cls(name, **dict(kwargs, no_convert=False))
if tmp.multivalue: if tmp.multivalue:
default = tuple(tmp._convert_scalar(d) for d in default) default = tuple(tmp._convert_scalar(d) for d in default)
else: else:
@@ -246,15 +235,43 @@ def _create_param(meta):
if 'default' in kwargs or 'default_from' in kwargs: if 'default' in kwargs or 'default_from' in kwargs:
kwargs['autofill'] = not kwargs.pop('alwaysask', False) kwargs['autofill'] = not kwargs.pop('alwaysask', False)
param = cls(str(meta['name']), **kwargs) param = cls(name, **kwargs)
if sensitive: if sensitive:
object.__setattr__(param, 'password', True) object.__setattr__(param, 'password', True)
return param return param
def _create_class(self, api, schema):
class_dict = {}
def _create_output(schema): class_dict['name'] = self.name
if 'doc' in schema:
class_dict['doc'] = schema['doc']
if 'topic_topic' in schema:
class_dict['topic'] = str(schema['topic_topic'])
else:
class_dict['topic'] = None
class_dict['takes_params'] = tuple(self._create_param(api, s)
for s in schema.get('params', []))
return self.name, self.bases, class_dict
def __call__(self, api):
if self.__class is None:
schema = api._schema[self.schema_key][self.name]
name, bases, class_dict = self._create_class(api, schema)
self.__class = type(name, bases, class_dict)
return self.__class(api)
class _SchemaCommandPlugin(_SchemaPlugin):
bases = (_SchemaCommand,)
schema_key = 'commands'
def _create_output(self, api, schema):
if schema.get('multivalue', False): if schema.get('multivalue', False):
type_type = (tuple, list) type_type = (tuple, list)
if not schema.get('required', True): if not schema.get('required', True):
@@ -279,116 +296,43 @@ def _create_output(schema):
return Output(str(schema['name']), **kwargs) return Output(str(schema['name']), **kwargs)
def _create_class(self, api, schema):
name, bases, class_dict = (
super(_SchemaCommandPlugin, self)._create_class(api, schema))
if 'obj_class' in schema or 'attr_name' in schema:
bases = (_SchemaMethod,)
def _create_command(schema):
command = {}
command['name'] = str(schema['name'])
if 'doc' in schema:
command['doc'] = ConcatenatedLazyText(schema['doc'])
if 'topic_topic' in schema:
command['topic'] = str(schema['topic_topic'])
else:
command['topic'] = None
if 'obj_class' in schema: if 'obj_class' in schema:
command['obj_name'] = str(schema['obj_class']) class_dict['obj_name'] = str(schema['obj_class'])
if 'attr_name' in schema: if 'attr_name' in schema:
command['attr_name'] = str(schema['attr_name']) class_dict['attr_name'] = str(schema['attr_name'])
if 'exclude' in schema and u'cli' in schema['exclude']: if 'exclude' in schema and u'cli' in schema['exclude']:
command['NO_CLI'] = True class_dict['NO_CLI'] = True
command['takes_args'] = tuple(
_create_param(s) for s in schema['params'] args = set(str(s['name']) for s in schema['params']
if s.get('positional', s.get('required', True))) if s.get('positional', s.get('required', True)))
command['takes_options'] = tuple( class_dict['takes_args'] = tuple(
_create_param(s) for s in schema['params'] p for p in class_dict['takes_params'] if p.name in args)
if not s.get('positional', s.get('required', True))) class_dict['takes_options'] = tuple(
command['has_output'] = tuple( p for p in class_dict['takes_params'] if p.name not in args)
_create_output(m) for m in schema['output']) del class_dict['takes_params']
return command class_dict['has_output'] = tuple(
self._create_output(api, s) for s in schema['output'])
return name, bases, class_dict
def _create_class(schema): class _SchemaObjectPlugin(_SchemaPlugin):
cls = {} bases = (_SchemaObject,)
cls['name'] = str(schema['name']) schema_key = 'classes'
if 'doc' in schema:
cls['doc'] = ConcatenatedLazyText(schema['doc'])
if 'topic_topic' in schema:
cls['topic'] = str(schema['topic_topic'])
else:
cls['topic'] = None
cls['takes_params'] = tuple(_create_param(s) for s in schema['params'])
return cls
class _LazySchemaPlugin(object):
def __init__(self, base, schema):
self.__base = base
self.__schema = schema
self.__class = None
self.__module__ = None
@property
def name(self):
return str(self.__schema['name'])
@property
def bases(self):
if self.__base is Command:
if 'obj_class' in self.__schema:
return (_SchemaMethod,)
else:
return (_SchemaCommand,)
else:
return (self.__base,)
def __call__(self, api):
if self.__class is None:
if self.__base is Command:
metaobject = _create_command(self.__schema)
else:
metaobject = _create_class(self.__schema)
metaobject = type(self.name, self.bases, metaobject)
metaobject.__module__ = self.__module__
self.__class = metaobject
return self.__class(api)
def _create_commands(schema):
return [_LazySchemaPlugin(Command, s) for s in schema]
def _create_classes(schema):
return [_LazySchemaPlugin(Object, s) for s in schema]
def _create_topic(schema):
topic = {}
topic['name'] = str(schema['name'])
if 'doc' in schema:
topic['doc'] = ConcatenatedLazyText(schema['doc'])
if 'topic_topic' in schema:
topic['topic'] = str(schema['topic_topic'])
else:
topic['topic'] = None
return topic
def _create_topics(schema):
return [_create_topic(s) for s in schema]
def get_package(api): def get_package(api):
package_name = '{}${}'.format(__name__, id(api))
package_dir = '{}${}'.format(os.path.splitext(__file__)[0], id(api))
try: try:
return sys.modules[package_name] schema = api._schema
except KeyError: except AttributeError:
pass
client = rpcclient(api) client = rpcclient(api)
client.finalize() client.finalize()
@@ -398,44 +342,48 @@ def get_package(api):
finally: finally:
client.disconnect() client.disconnect()
commands = _create_commands(schema['commands']) for key in ('commands', 'classes', 'topics'):
classes = _create_classes(schema['classes']) schema[key] = {str(s.pop('name')): s for s in schema[key]}
topics = _create_topics(schema['topics'])
object.__setattr__(api, '_schema', schema)
fingerprint = str(schema['fingerprint'])
package_name = '{}${}'.format(__name__, fingerprint)
package_dir = '{}${}'.format(os.path.splitext(__file__)[0], fingerprint)
try:
return sys.modules[package_name]
except KeyError:
pass
package = types.ModuleType(package_name) package = types.ModuleType(package_name)
package.__file__ = os.path.join(package_dir, '__init__.py') package.__file__ = os.path.join(package_dir, '__init__.py')
package.modules = [] package.modules = ['plugins']
sys.modules[package_name] = package sys.modules[package_name] = package
module_name = '.'.join((package_name, 'commands')) module_name = '.'.join((package_name, 'plugins'))
module = types.ModuleType(module_name) module = types.ModuleType(module_name)
module.__file__ = os.path.join(package_dir, 'commands.py') module.__file__ = os.path.join(package_dir, 'plugins.py')
module.register = plugable.Registry() module.register = plugable.Registry()
package.modules.append('commands') for key, plugin_cls in (('commands', _SchemaCommandPlugin),
('classes', _SchemaObjectPlugin)):
for name in schema[key]:
plugin = plugin_cls(name)
plugin = module.register()(plugin)
setattr(module, name, plugin)
sys.modules[module_name] = module sys.modules[module_name] = module
for command in commands: for name, topic in six.iteritems(schema['topics']):
command.__module__ = module_name
command = module.register()(command)
setattr(module, command.name, command)
for cls in classes:
cls.__module__ = module_name
cls = module.register()(cls)
setattr(module, cls.name, command)
for topic in topics:
name = topic.pop('name')
module_name = '.'.join((package_name, name)) module_name = '.'.join((package_name, name))
try: try:
module = sys.modules[module_name] module = sys.modules[module_name]
except KeyError: except KeyError:
module = sys.modules[module_name] = types.ModuleType(module_name) module = sys.modules[module_name] = types.ModuleType(module_name)
module.__file__ = os.path.join(package_dir, '{}.py'.format(name)) module.__file__ = os.path.join(package_dir, '{}.py'.format(name))
module.__dict__.update(topic) module.__doc__ = topic.get('doc')
try: if 'topic_topic' in topic:
module.__doc__ = module.doc module.topic = str(topic['topic_topic'])
except AttributeError: else:
pass module.topic = None
return package return package