Add new Param method for marshalling values from complex data types to primitive data types suitable for transmission over RPC.

This change makes it possible to use complex data types (like python-netaddr IPAddress) in parameters.

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

This will help implementing IP address parameter types properly in https://fedorahosted.org/freeipa/ticket/1487 .

--
Jan Cholasta
>From 861748ce6c47d6e7852e19c5f862393a1b287c26 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 26 Oct 2011 11:26:30 -0400
Subject: [PATCH] Add new Param method for marshalling values from complex
 data types to primitive data types suitable for
 transmission over RPC.

This change makes it possible to use complex data types in parameters.

ticket 2033
---
 ipalib/frontend.py                   |   17 +++++++++++++----
 ipalib/parameters.py                 |   19 +++++++++++++++++++
 ipalib/plugins/dns.py                |    4 +++-
 tests/test_ipalib/test_frontend.py   |   13 +++++++++++++
 tests/test_ipalib/test_parameters.py |   33 +++++++++++++++++++++++++++++++++
 5 files changed, 81 insertions(+), 5 deletions(-)

diff --git a/ipalib/frontend.py b/ipalib/frontend.py
index 61e7f49..a864c2d 100644
--- a/ipalib/frontend.py
+++ b/ipalib/frontend.py
@@ -565,6 +565,8 @@ class Command(HasParam):
         return newdict
 
     def __attributes_2_entry(self, kw):
+        kw = self.marshal(**kw)
+
         for name in self.params:
             if self.params[name].attribute and name in kw:
                 value = kw[name]
@@ -648,10 +650,14 @@ class Command(HasParam):
             (k, self.params[k].convert(v)) for (k, v) in kw.iteritems()
         )
 
-    def __convert_iter(self, kw):
-        for param in self.params():
-            if kw.get(param.name, None) is None:
-                continue
+    def marshal(self, **kw):
+        """
+        Return a dictionary of marshalled values.
+        """
+        for (name, value) in kw.iteritems():
+            if name in self.params:
+                kw[name] = self.params[name].marshal(value)
+        return kw
 
     def get_default(self, **kw):
         """
@@ -757,6 +763,9 @@ class Command(HasParam):
         """
         Forward call over XML-RPC to this same command on server.
         """
+        params = self.args_options_2_params(*args, **kw)
+        params = self.marshal(**params)
+        (args, kw) = self.params_2_args_options(**params)
         return self.Backend.xmlclient.forward(self.name, *args, **kw)
 
     def finalize(self):
diff --git a/ipalib/parameters.py b/ipalib/parameters.py
index f9e171b..85f9591 100644
--- a/ipalib/parameters.py
+++ b/ipalib/parameters.py
@@ -717,6 +717,25 @@ class Param(ReadOnly):
             error=ugettext(self.type_error),
         )
 
+    def marshal(self, value):
+        """
+        Marshal ``value`` to a primitive data type, suitable for transmission.
+        """
+        if value is None:
+            return None
+        if self.multivalue:
+            return tuple(self._marshal_scalar(v, i) for (i, v) in enumerate(value))
+        else:
+            return self._marshal_scalar(value)
+
+    def _marshal_scalar(self, value, index=None):
+        """
+        Marshal a single scalar value.
+        """
+        if not isinstance(value, (int, long, bool, float, str, unicode)):
+            raise TypeError('value: must be primitive type, not %s' % type(value).__name__)
+        return value
+
     def validate(self, value, context=None):
         """
         Check validity of ``value``.
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index 97eb6a6..545e33b 100644
--- a/ipalib/plugins/dns.py
+++ b/ipalib/plugins/dns.py
@@ -518,7 +518,7 @@ class dnszone_find(LDAPSearch):
             if 'idnsname' not in options:
                 options['idnsname'] = self.obj.params['idnsname'].get_default(**options)
             del options['name_from_ip']
-        return super(dnszone_find, self).args_options_2_entry(self, *args, **options)
+        return super(dnszone_find, self).args_options_2_entry(*args, **options)
 
     takes_options = LDAPSearch.takes_options + (
         Flag('forward_only',
@@ -713,6 +713,7 @@ class dnsrecord_cmd_w_record_options(Command):
             yield self.get_record_option(t)
 
     def record_options_2_entry(self, **options):
+        options = self.marshal(**options)
         entries = dict((t, options.get(t, [])) for t in _record_attributes)
         entries.update(dict((k, []) for (k,v) in entries.iteritems() if v == None ))
         return entries
@@ -900,6 +901,7 @@ class dnsrecord_mod(dnsrecord_mod_record):
                 old_entry_attrs[a] = v
 
     def record_options_2_entry(self, **options):
+        options = self.marshal(**options)
         entries = dict((t, options.get(t, [])) for t in _record_attributes)
         return has_cli_options(entries, self.no_option_msg, True)
 
diff --git a/tests/test_ipalib/test_frontend.py b/tests/test_ipalib/test_frontend.py
index 0f6aecb..f0d0076 100644
--- a/tests/test_ipalib/test_frontend.py
+++ b/tests/test_ipalib/test_frontend.py
@@ -412,6 +412,19 @@ class test_Command(ClassChecker):
         sub.finalize()
         assert sub.normalize(**kw) == norm
 
+    def test_marshal(self):
+        """
+        Test the `ipalib.frontend.Command.marshal` method.
+        """
+        kw = dict(
+            option0=u'option0',
+            option1=u'option1',
+            nooption=1234,
+        )
+        sub = self.subcls()
+        sub.finalize()
+        assert sub.marshal(**kw) == kw
+
     def test_get_default(self):
         """
         Test the `ipalib.frontend.Command.get_default` method.
diff --git a/tests/test_ipalib/test_parameters.py b/tests/test_ipalib/test_parameters.py
index e63bbb7..cdbe236 100644
--- a/tests/test_ipalib/test_parameters.py
+++ b/tests/test_ipalib/test_parameters.py
@@ -431,6 +431,39 @@ class test_Param(ClassChecker):
         # Test with incorrect type
         e = raises(errors.ConversionError, o._convert_scalar, 'hello', index=17)
 
+    def test_marshal(self):
+        """
+        Test the `ipalib.parameters.Param.marshal` method.
+        """
+        okay = ('Hello', u'Hello', 0, 4.2, True, False, unicode_str)
+        class Subclass(self.cls):
+            def _marshal_scalar(self, value, index=None):
+                return value
+
+        # Test when multivalue=False:
+        o = Subclass('my_param')
+        assert o.marshal(None) is None
+        for value in okay:
+            assert o.marshal(value) is value
+
+        # Test when multivalue=True:
+        o = Subclass('my_param', multivalue=True)
+        assert o.marshal(None) is None
+        assert o.marshal(okay) == okay
+
+    def test_marshal_scalar(self):
+        """
+        Test the `ipalib.parameters.Param._marshal_scalar` method.
+        """
+        o = self.cls('my_param')
+
+        # Test with correct type:
+        assert o._marshal_scalar(0) is 0
+
+        # Test with incorrect type:
+        e = raises(TypeError, o._marshal_scalar, object())
+        assert str(e) == 'value: must be primitive type, not %s' % object.__name__
+
     def test_validate(self):
         """
         Test the `ipalib.parameters.Param.validate` method.
-- 
1.7.6.4

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to