On 27.6.2016 14:55, David Kupka wrote:
On 28/04/16 14:45, Jan Cholasta wrote:
Hi,

I have pushed my thin client WIP branch to GitHub:
<https://github.com/jcholast/freeipa/tree/trac-4739>.

All commits up to "ipalib: use relative imports for cross-plugin
imports" should be good for review. The rest is subject to change
(WARNING: I will force push into this branch).

Honza


Hello!

Patch set:
schema: client-side cleanup
automember: fix automember to work with thin client
schema: do not crash in command_defaults if argument is None
schema: fix param default value handling

works* for me, ACK.

Thanks, pushed to master: f7cc15f0990ef2db57717a3c6a8e9db2c3dee951

Attaching the patches for reference.


*) xmlrpc test for automember_plugin test started failing with
automember patch. Fix for test attached.

LGTM

--
Jan Cholasta
From fd0553ca1216e68edb7ae88997a321339b08b612 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 22 Jun 2016 15:15:32 +0200
Subject: [PATCH 1/4] schema: fix param default value handling

Advertise param's default value even when `autofill` is False. When
`autofill` is False, set `alwaysask` to True in the schema, as it is
semantically equivallent and removes redundancy.

This fixes default value disappearing in CLI for some params.

https://fedorahosted.org/freeipa/ticket/4739
---
 ipaclient/remote_plugins/schema.py |  6 +++---
 ipaserver/plugins/schema.py        | 23 +++++++++++++----------
 2 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/ipaclient/remote_plugins/schema.py b/ipaclient/remote_plugins/schema.py
index c21da5f..b944fb0 100644
--- a/ipaclient/remote_plugins/schema.py
+++ b/ipaclient/remote_plugins/schema.py
@@ -213,7 +213,6 @@ def _create_param(meta):
 
     for key, value in meta.items():
         if key in ('alwaysask',
-                   'autofill',
                    'doc',
                    'label',
                    'multivalue',
@@ -229,11 +228,9 @@ def _create_param(meta):
             kwargs[key] = value
         elif key == 'default':
             default = value
-            kwargs['autofill'] = True
         elif key == 'default_from_param':
             kwargs['default_from'] = DefaultFrom(_nope,
                                                  *(str(k) for k in value))
-            kwargs['autofill'] = True
         elif key in ('exclude',
                      'include'):
             kwargs[key] = tuple(str(v) for v in value)
@@ -246,6 +243,9 @@ def _create_param(meta):
             default = tmp._convert_scalar(default[0])
         kwargs['default'] = default
 
+    if 'default' in kwargs or 'default_from' in kwargs:
+        kwargs['autofill'] = not kwargs.pop('alwaysask', False)
+
     param = cls(str(meta['name']), **kwargs)
 
     if sensitive:
diff --git a/ipaserver/plugins/schema.py b/ipaserver/plugins/schema.py
index a67d7b2..c3a0e60 100644
--- a/ipaserver/plugins/schema.py
+++ b/ipaserver/plugins/schema.py
@@ -542,23 +542,26 @@ class param(BaseParam):
                          'include'):
                 obj[key] = list(unicode(v) for v in value)
             if isinstance(metaobj, Command):
-                if key in ('alwaysask',
-                           'confirm'):
+                if key == 'alwaysask':
+                    obj.setdefault(key, value)
+                elif key == 'confirm':
                     obj[key] = value
                 elif key in ('cli_metavar',
                              'cli_name',
                              'option_group'):
                     obj[key] = unicode(value)
                 elif key == 'default':
-                    if param.autofill:
-                        if param.multivalue:
-                            obj[key] = [unicode(v) for v in value]
-                        else:
-                            obj[key] = [unicode(value)]
+                    if param.multivalue:
+                        obj[key] = [unicode(v) for v in value]
+                    else:
+                        obj[key] = [unicode(value)]
+                    if not param.autofill:
+                        obj['alwaysask'] = True
                 elif key == 'default_from':
-                    if param.autofill:
-                        obj['default_from_param'] = list(unicode(k)
-                                                         for k in value.keys)
+                    obj['default_from_param'] = list(unicode(k)
+                                                     for k in value.keys)
+                    if not param.autofill:
+                        obj['alwaysask'] = True
                 elif key in ('exponential',
                              'normalizer',
                              'only_absolute',
-- 
2.7.4

From 294bc271dafb021730174cfc4eacaaf187a53e68 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 23 Jun 2016 12:14:38 +0200
Subject: [PATCH 2/4] schema: do not crash in command_defaults if argument is
 None

https://fedorahosted.org/freeipa/ticket/4739
---
 ipaserver/plugins/schema.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/ipaserver/plugins/schema.py b/ipaserver/plugins/schema.py
index c3a0e60..96a8224 100644
--- a/ipaserver/plugins/schema.py
+++ b/ipaserver/plugins/schema.py
@@ -266,8 +266,8 @@ class command_defaults(PKQuery):
     def execute(self, name, **options):
         command = self.api.Command[name]
 
-        params = options.get('params', [])
-        kw = options.get('kw', {})
+        params = options.get('params') or []
+        kw = options.get('kw') or {}
 
         result = command.get_default(params, **kw)
 
-- 
2.7.4

From d46958d5d82615792ee480e23eeea573ea1aac90 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 22 Jun 2016 13:14:50 +0200
Subject: [PATCH 3/4] automember: fix automember to work with thin client

Properly mark `cn` as primary key of `automember` object.

This fixes automember crashing on output validation expecting primary key
value of None.

https://fedorahosted.org/freeipa/ticket/4739
---
 API.txt                         |  5 +++--
 VERSION                         |  4 ++--
 ipaserver/plugins/automember.py | 49 +++++++++++++++++++++++------------------
 3 files changed, 32 insertions(+), 26 deletions(-)

diff --git a/API.txt b/API.txt
index af4a23d..acd1d49 100644
--- a/API.txt
+++ b/API.txt
@@ -167,17 +167,18 @@ output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
 output: PrimaryKey('value')
 command: automember_del
 args: 1,2,3
-arg: Str('cn', cli_name='automember_rule')
+arg: Str('cn+', cli_name='automember_rule')
 option: StrEnum('type', values=[u'group', u'hostgroup'])
 option: Str('version?')
 output: Output('result', type=[<type 'dict'>])
 output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
 output: ListOfPrimaryKeys('value')
 command: automember_find
-args: 1,5,4
+args: 1,6,4
 arg: Str('criteria?')
 option: Flag('all', autofill=True, cli_name='all', default=False)
 option: Str('description?', autofill=False, cli_name='desc')
+option: Flag('pkey_only?', autofill=True, default=False)
 option: Flag('raw', autofill=True, cli_name='raw', default=False)
 option: StrEnum('type', values=[u'group', u'hostgroup'])
 option: Str('version?')
diff --git a/VERSION b/VERSION
index b257272..cb190ea 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=199
-# Last change: schema: Add known_fingerprints option to schema command
+IPA_API_VERSION_MINOR=200
+# Last change: automember: TODO
diff --git a/ipaserver/plugins/automember.py b/ipaserver/plugins/automember.py
index 26fef04..dfa8498 100644
--- a/ipaserver/plugins/automember.py
+++ b/ipaserver/plugins/automember.py
@@ -182,16 +182,6 @@ group_type = (
     ),
 )
 
-automember_rule = (
-    Str('cn',
-        cli_name='automember_rule',
-        label=_('Automember Rule'),
-        doc=_('Automember Rule'),
-        normalizer=lambda value: value.lower(),
-        flags={'no_create', 'no_update', 'no_search'},
-    ),
-)
-
 
 @register()
 class automember(LDAPObject):
@@ -249,6 +239,14 @@ class automember(LDAPObject):
     label = _('Auto Membership Rule')
 
     takes_params = (
+        Str('cn',
+            cli_name='automember_rule',
+            label=_('Automember Rule'),
+            doc=_('Automember Rule'),
+            primary_key=True,
+            normalizer=lambda value: value.lower(),
+            flags={'no_search'},
+        ),
         Str('description?',
             cli_name='desc',
             label=_('Description'),
@@ -260,7 +258,7 @@ class automember(LDAPObject):
             doc=_('Default group for entries to land'),
             flags=['no_create', 'no_update', 'no_search']
         ),
-    ) + automember_rule + regex_attrs
+    ) + regex_attrs
 
     def dn_exists(self, otype, oname):
         ldap = self.api.Backend.ldap2
@@ -312,7 +310,6 @@ class automember_add(LDAPCreate):
     Add an automember rule.
     """)
     takes_options = LDAPCreate.takes_options + group_type
-    takes_args = automember_rule
     msg_summary = _('Added automember rule "%(value)s"')
 
     def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
@@ -343,7 +340,6 @@ class automember_add_condition(LDAPUpdate):
     )
 
     takes_options = regex_attrs + regex_key + group_type
-    takes_args = automember_rule
     msg_summary = _('Added condition(s) to "%(value)s"')
 
     # Prepare the output to expect failed results
@@ -428,7 +424,6 @@ class automember_remove_condition(LDAPUpdate):
     Remove conditions from an automember rule.
     """)
     takes_options = regex_attrs + regex_key + group_type
-    takes_args = automember_rule
     msg_summary = _('Removed condition(s) from "%(value)s"')
 
     # Prepare the output to expect failed results
@@ -514,7 +509,6 @@ class automember_mod(LDAPUpdate):
     __doc__ = _("""
     Modify an automember rule.
     """)
-    takes_args = automember_rule
     takes_options = LDAPUpdate.takes_options + group_type
     msg_summary = _('Modified automember rule "%(value)s"')
 
@@ -529,15 +523,9 @@ class automember_del(LDAPDelete):
     __doc__ = _("""
     Delete an automember rule.
     """)
-    takes_args = automember_rule
     takes_options = group_type
     msg_summary = _('Deleted automember rule "%(value)s"')
 
-    def execute(self, *keys, **options):
-        result = super(automember_del, self).execute(*keys, **options)
-        result['value'] = pkey_to_value([keys[-1]], options)
-        return result
-
 
 @register()
 class automember_find(LDAPSearch):
@@ -562,7 +550,6 @@ class automember_show(LDAPRetrieve):
     __doc__ = _("""
     Display information about an automember rule.
     """)
-    takes_args = automember_rule
     takes_options = group_type
 
     def execute(self, *keys, **options):
@@ -572,11 +559,24 @@ class automember_show(LDAPRetrieve):
 
 
 @register()
+class automember_default_group(automember):
+    managed_permissions = {}
+
+    def get_params(self):
+        for param in super(automember_default_group, self).get_params():
+            if param.name == 'cn':
+                continue
+            yield param
+
+
+@register()
 class automember_default_group_set(LDAPUpdate):
     __doc__ = _("""
     Set default (fallback) group for all unmatched entries.
     """)
 
+    obj_name = 'automember_default_group'
+
     takes_options = (
         Str('automemberdefaultgroup',
         cli_name='default_group',
@@ -605,6 +605,8 @@ class automember_default_group_remove(LDAPUpdate):
     Remove default (fallback) group for all unmatched entries.
     """)
 
+    obj_name = 'automember_default_group'
+
     takes_options = group_type
     msg_summary = _('Removed default (fallback) group for automember "%(value)s"')
 
@@ -638,6 +640,9 @@ class automember_default_group_show(LDAPRetrieve):
     __doc__ = _("""
     Display information about the default (fallback) automember groups.
     """)
+
+    obj_name = 'automember_default_group'
+
     takes_options = group_type
 
     def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
-- 
2.7.4

From 3d73b42715e8b69e6326a2c68512d80714c46ea4 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 22 Jun 2016 13:27:25 +0200
Subject: [PATCH 4/4] schema: client-side code cleanup

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

https://fedorahosted.org/freeipa/ticket/4739
---
 ipaclient/remote_plugins/schema.py | 432 ++++++++++++++++---------------------
 1 file changed, 190 insertions(+), 242 deletions(-)

diff --git a/ipaclient/remote_plugins/schema.py b/ipaclient/remote_plugins/schema.py
index b944fb0..9850038 100644
--- a/ipaclient/remote_plugins/schema.py
+++ b/ipaclient/remote_plugins/schema.py
@@ -13,8 +13,8 @@ from ipaclient.plugins.rpcclient import rpcclient
 from ipalib import parameters, plugable
 from ipalib.frontend import Command, Method, Object
 from ipalib.output import Output
-from ipalib.parameters import Bool, DefaultFrom, Flag, Password, Str
-from ipalib.text import ConcatenatedLazyText, _
+from ipalib.parameters import DefaultFrom, Flag, Password, Str
+from ipalib.text import _
 from ipapython.dn import DN
 from ipapython.dnsutil import DNSName
 
@@ -48,39 +48,6 @@ _PARAMS = {
 
 
 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):
         skip = set()
         for option in super(_SchemaCommand, self).get_options():
@@ -88,12 +55,6 @@ class _SchemaCommand(Command):
                 continue
             if option.name in ('all', 'raw'):
                 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
 
 
@@ -182,260 +143,247 @@ class _SchemaMethod(Method, _SchemaCommand):
                 yield output_param
 
 
-def _nope():
+class _SchemaObject(Object):
     pass
 
 
-def _create_param_convert_scalar(cls):
-    def _convert_scalar(self, value, index=None):
-        if isinstance(value, unicode):
-            return value
-        return super(cls, self)._convert_scalar(value)
+class _SchemaPlugin(object):
+    bases = None
+    schema_key = None
 
-    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):
-    type_name = str(meta['type'])
-    sensitive = meta.get('sensitive', False)
+        def get_default(*args):
+            kw = dict(zip(keys, args))
+            result = api.Command.command_defaults(
+                unicode(cmd_name),
+                params=[unicode(name)],
+                kw=kw,
+            )['result']
+            return result.get(name)
 
-    if type_name == 'str' and sensitive:
-        cls = Password
-        sensitive = False
-    else:
-        try:
-            cls = _PARAMS[type_name]
-        except KeyError:
-            cls = Str
-
-    kwargs = {}
-    default = None
-
-    for key, value in meta.items():
-        if key in ('alwaysask',
-                   'doc',
-                   'label',
-                   'multivalue',
-                   'no_convert',
-                   'option_group',
-                   'required',
-                   'sortorder'):
-            kwargs[key] = value
-        elif key in ('cli_metavar',
-                     'cli_name'):
-            kwargs[key] = str(value)
-        elif key == 'confirm' and issubclass(cls, parameters.Password):
-            kwargs[key] = value
-        elif key == 'default':
-            default = value
-        elif key == 'default_from_param':
-            kwargs['default_from'] = DefaultFrom(_nope,
-                                                 *(str(k) for k in value))
-        elif key in ('exclude',
-                     'include'):
-            kwargs[key] = tuple(str(v) for v in value)
-
-    if default is not None:
-        tmp = cls(str(meta['name']), **dict(kwargs, no_convert=False))
-        if tmp.multivalue:
-            default = tuple(tmp._convert_scalar(d) for d in default)
+        if keys:
+            def callback(*args):
+                return get_default(*args)
         else:
-            default = tmp._convert_scalar(default[0])
-        kwargs['default'] = default
+            def callback():
+                return get_default()
 
-    if 'default' in kwargs or 'default_from' in kwargs:
-        kwargs['autofill'] = not kwargs.pop('alwaysask', False)
+        callback.__name__ = '{0}_{1}_default'.format(cmd_name, name)
 
-    param = cls(str(meta['name']), **kwargs)
+        return DefaultFrom(callback, *keys)
 
-    if sensitive:
-        object.__setattr__(param, 'password', True)
+    def _create_param(self, api, schema):
+        name = str(schema['name'])
+        type_name = str(schema['type'])
+        sensitive = schema.get('sensitive', False)
 
-    return param
+        if type_name == 'str' and sensitive:
+            cls = Password
+            sensitive = False
+        elif (type_name == 'bool' and
+                'default' in schema and
+                schema['default'] == [u'False']):
+            cls = Flag
+            del schema['default']
+        else:
+            try:
+                cls = _PARAMS[type_name]
+            except KeyError:
+                cls = Str
+
+        kwargs = {}
+        default = None
+
+        for key, value in schema.items():
+            if key in ('alwaysask',
+                       'doc',
+                       'label',
+                       'multivalue',
+                       'no_convert',
+                       'option_group',
+                       'required'):
+                kwargs[key] = value
+            elif key in ('cli_metavar',
+                         'cli_name'):
+                kwargs[key] = str(value)
+            elif key == 'confirm' and issubclass(cls, Password):
+                kwargs[key] = value
+            elif key == 'default':
+                default = value
+            elif key == 'default_from_param':
+                keys = tuple(str(k) for k in value)
+                kwargs['default_from'] = (
+                    self._create_default_from(api, name, keys))
+            elif key in ('exclude',
+                         'include'):
+                kwargs[key] = tuple(str(v) for v in value)
+
+        if default is not None:
+            tmp = cls(name, **dict(kwargs, no_convert=False))
+            if tmp.multivalue:
+                default = tuple(tmp._convert_scalar(d) for d in default)
+            else:
+                default = tmp._convert_scalar(default[0])
+            kwargs['default'] = default
 
+        if 'default' in kwargs or 'default_from' in kwargs:
+            kwargs['autofill'] = not kwargs.pop('alwaysask', False)
 
-def _create_output(schema):
-    if schema.get('multivalue', False):
-        type_type = (tuple, list)
-        if not schema.get('required', True):
-            type_type = type_type + (type(None),)
-    else:
-        try:
-            type_type = _TYPES[schema['type']]
-        except KeyError:
-            type_type = None
-        else:
-            if not schema.get('required', True):
-                type_type = (type_type, type(None))
-
-    kwargs = {}
-    kwargs['type'] = type_type
-
-    if 'doc' in schema:
-        kwargs['doc'] = schema['doc']
-
-    if schema.get('no_display', False):
-        kwargs['flags'] = ('no_display',)
-
-    return Output(str(schema['name']), **kwargs)
-
-
-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:
-        command['obj_name'] = str(schema['obj_class'])
-    if 'attr_name' in schema:
-        command['attr_name'] = str(schema['attr_name'])
-    if 'exclude' in schema and u'cli' in schema['exclude']:
-        command['NO_CLI'] = True
-    command['takes_args'] = tuple(
-        _create_param(s) for s in schema['params']
-        if s.get('positional', s.get('required', True)))
-    command['takes_options'] = tuple(
-        _create_param(s) for s in schema['params']
-        if not s.get('positional', s.get('required', True)))
-    command['has_output'] = tuple(
-        _create_output(m) for m in schema['output'])
-
-    return command
-
-
-def _create_class(schema):
-    cls = {}
-    cls['name'] = str(schema['name'])
-    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
+        param = cls(name, **kwargs)
 
-    @property
-    def name(self):
-        return str(self.__schema['name'])
+        if sensitive:
+            object.__setattr__(param, 'password', True)
 
-    @property
-    def bases(self):
-        if self.__base is Command:
-            if 'obj_class' in self.__schema:
-                return (_SchemaMethod,)
-            else:
-                return (_SchemaCommand,)
+        return param
+
+    def _create_class(self, api, schema):
+        class_dict = {}
+
+        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:
-            return (self.__base,)
+            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:
-            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
+            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)
 
 
-def _create_commands(schema):
-    return [_LazySchemaPlugin(Command, s) for s in schema]
+class _SchemaCommandPlugin(_SchemaPlugin):
+    bases = (_SchemaCommand,)
+    schema_key = 'commands'
+
+    def _create_output(self, api, schema):
+        if schema.get('multivalue', False):
+            type_type = (tuple, list)
+            if not schema.get('required', True):
+                type_type = type_type + (type(None),)
+        else:
+            try:
+                type_type = _TYPES[schema['type']]
+            except KeyError:
+                type_type = None
+            else:
+                if not schema.get('required', True):
+                    type_type = (type_type, type(None))
+
+        kwargs = {}
+        kwargs['type'] = type_type
 
+        if 'doc' in schema:
+            kwargs['doc'] = schema['doc']
 
-def _create_classes(schema):
-    return [_LazySchemaPlugin(Object, s) for s in schema]
+        if schema.get('no_display', False):
+            kwargs['flags'] = ('no_display',)
 
+        return Output(str(schema['name']), **kwargs)
 
-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
+    def _create_class(self, api, schema):
+        name, bases, class_dict = (
+            super(_SchemaCommandPlugin, self)._create_class(api, schema))
 
-    return topic
+        if 'obj_class' in schema or 'attr_name' in schema:
+            bases = (_SchemaMethod,)
 
+        if 'obj_class' in schema:
+            class_dict['obj_name'] = str(schema['obj_class'])
+        if 'attr_name' in schema:
+            class_dict['attr_name'] = str(schema['attr_name'])
+        if 'exclude' in schema and u'cli' in schema['exclude']:
+            class_dict['NO_CLI'] = True
 
-def _create_topics(schema):
-    return [_create_topic(s) for s in schema]
+        args = set(str(s['name']) for s in schema['params']
+                   if s.get('positional', s.get('required', True)))
+        class_dict['takes_args'] = tuple(
+            p for p in class_dict['takes_params'] if p.name in args)
+        class_dict['takes_options'] = tuple(
+            p for p in class_dict['takes_params'] if p.name not in args)
+        del class_dict['takes_params']
+
+        class_dict['has_output'] = tuple(
+            self._create_output(api, s) for s in schema['output'])
+
+        return name, bases, class_dict
+
+
+class _SchemaObjectPlugin(_SchemaPlugin):
+    bases = (_SchemaObject,)
+    schema_key = 'classes'
 
 
 def get_package(api):
-    package_name = '{}${}'.format(__name__, id(api))
-    package_dir = '{}${}'.format(os.path.splitext(__file__)[0], id(api))
+    try:
+        schema = api._schema
+    except AttributeError:
+        client = rpcclient(api)
+        client.finalize()
+
+        client.connect(verbose=False)
+        try:
+            schema = client.forward(u'schema', version=u'2.170')['result']
+        finally:
+            client.disconnect()
+
+        for key in ('commands', 'classes', 'topics'):
+            schema[key] = {str(s.pop('name')): s for s in schema[key]}
+
+        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
 
-    client = rpcclient(api)
-    client.finalize()
-
-    client.connect(verbose=False)
-    try:
-        schema = client.forward(u'schema', version=u'2.170')['result']
-    finally:
-        client.disconnect()
-
-    commands = _create_commands(schema['commands'])
-    classes = _create_classes(schema['classes'])
-    topics = _create_topics(schema['topics'])
-
     package = types.ModuleType(package_name)
     package.__file__ = os.path.join(package_dir, '__init__.py')
-    package.modules = []
+    package.modules = ['plugins']
     sys.modules[package_name] = package
 
-    module_name = '.'.join((package_name, 'commands'))
+    module_name = '.'.join((package_name, 'plugins'))
     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()
-    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
 
-    for command in commands:
-        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')
+    for name, topic in six.iteritems(schema['topics']):
         module_name = '.'.join((package_name, name))
         try:
             module = sys.modules[module_name]
         except KeyError:
             module = sys.modules[module_name] = types.ModuleType(module_name)
             module.__file__ = os.path.join(package_dir, '{}.py'.format(name))
-        module.__dict__.update(topic)
-        try:
-            module.__doc__ = module.doc
-        except AttributeError:
-            pass
+        module.__doc__ = topic.get('doc')
+        if 'topic_topic' in topic:
+            module.topic = str(topic['topic_topic'])
+        else:
+            module.topic = None
 
     return package
-- 
2.7.4

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to