diff options
author | Pavel Zuna <pzuna@redhat.com> | 2009-05-14 18:29:00 +0200 |
---|---|---|
committer | Rob Crittenden <rcritten@redhat.com> | 2009-05-19 09:56:39 -0400 |
commit | 9437fc669ef2d4ca8ba191ea2378cbc71085c34f (patch) | |
tree | 703b3d1f4e9a7406ecf29b2feb07ca177a6fa535 /ipalib | |
parent | 0d6e4d2374d365df19b5a1932adbb59d631cb579 (diff) | |
download | freeipa-9437fc669ef2d4ca8ba191ea2378cbc71085c34f.tar.gz freeipa-9437fc669ef2d4ca8ba191ea2378cbc71085c34f.tar.xz freeipa-9437fc669ef2d4ca8ba191ea2378cbc71085c34f.zip |
Add Encoder base class and method decorators to encode arguments/decode return values. Also - unit tests.
Diffstat (limited to 'ipalib')
-rw-r--r-- | ipalib/encoder.py | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/ipalib/encoder.py b/ipalib/encoder.py new file mode 100644 index 000000000..044e1e0d3 --- /dev/null +++ b/ipalib/encoder.py @@ -0,0 +1,210 @@ +# Authors: +# Pavel Zuna <pzuna@redhat.com> +# +# Copyright (C) 2009 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 +""" +This module provides the Encoder base class, that adds encoding/decoding +capabilities to classes extending it. It also defines a set of decorators +designed to automagically encode method arguments and decode their return +values. +""" + +class Encoder(object): + """ + Base class implementing encoding python scalar types to strings + and vise-versa. + """ + encode_to = 'utf-8' + encode_none = False + encode_dict_keys = False + encode_dict_keys_postprocess = True + encode_dict_vals = True + encode_dict_vals_postprocess = True + encode_postprocessor = staticmethod(lambda x: x) + + decode_from = 'utf-8' + decode_none = False + decode_dict_keys = False + decode_dict_keys_postprocess = True + decode_dict_vals = True + decode_dict_vals_postprocess = True + decode_dict_vals_table = dict() + decode_dict_vals_table_keygen = staticmethod(lambda x: x) + decode_postprocessor = staticmethod(lambda x: x) + + def _decode_dict_val(self, key, val): + f = self.decode_dict_vals_table.get( + self.decode_dict_vals_table_keygen(key), self.decode + ) + return f(val) + + def encode(self, var): + """ + Encode any python built-in python type variable into `self.encode_to`. + + Compound types have their individual members encoded. + + Returns an encoded copy of 'var'. + """ + if isinstance(var, basestring): + return self.encode_postprocessor(var.encode(self.encode_to)) + elif isinstance(var, (bool, float, int, long)): + return self.encode_postprocessor(str(var).encode(self.encode_to)) + elif isinstance(var, list): + return [self.encode(m) for m in var] + elif isinstance(var, tuple): + return tuple(self.encode(m) for m in var) + elif isinstance(var, dict): + if self.encode_dict_keys: + dct = dict() + if not self.encode_dict_keys_postprocess: + tmp = self.encode_postprocessor + self.encode_postprocessor = lambda x: x + for (k, v) in var.iteritems(): + dct[self.encode(k)] = v + if not self.encode_dict_keys_postprocess: + self.encode_postprocessor = tmp + else: + dct = dict(var) + if self.encode_dict_vals: + if not self.encode_dict_vals_postprocess: + tmp = self.encode_postprocessor + self.encode_postprocessor = lambda x: x + for (k, v) in dct.iteritems(): + dct[k] = self.encode(v) + if not self.encode_dict_vals_postprocess: + self.encode_postprocessor = tmp + return dct + elif var is None: + if self.encode_none: + return self.encode_postprocessor( + str(var).encode(self.encode_to) + ) + return None + raise TypeError('python built-in type expected, got \'%s\'', type(var)) + + def decode(self, var): + """ + Decode strings in `self.decode_from` into python strings. + + Compound types have their individual members decoded. + + Dictionaries can have their values decoded into other types + by looking up keys in `self.decode_dict_vals_table`. + + Returns a decoded copy of 'var'. + """ + if isinstance(var, basestring): + return self.decode_postprocessor(var.decode(self.decode_from)) + elif isinstance(var, (bool, float, int, long)): + return self.decode_postprocessor(unicode(var)) + elif isinstance(var, list): + return [self.decode(m) for m in var] + elif isinstance(var, tuple): + return tuple(self.decode(m) for m in var) + elif isinstance(var, dict): + if self.decode_dict_keys: + dct = dict() + if not self.decode_dict_keys_postprocess: + tmp = self.decode_postprocessor + self.decode_postprocessor = lambda x: x + for (k, v) in var.iteritems(): + dct[self.decode(k)] = v + if not self.decode_dict_keys_postprocess: + self.decode_postprocessor = tmp + else: + dct = dict(var) + if self.decode_dict_vals: + if not self.decode_dict_vals_postprocess: + tmp = self.decode_postprocessor + self.decode_postprocessor = lambda x: x + for (k, v) in dct.iteritems(): + dct[k] = self._decode_dict_val(k, v) + if not self.decode_dict_vals_postprocess: + self.decode_postprocessor = tmp + return dct + elif var is None: + if self.decode_none: + return self.decode_postprocessor( + str(var).decode(self.decode_from) + ) + return None + raise TypeError('python built-in type expected, got \'%s\'', type(var)) + +## ENCODER METHOD DECORATORS + +def encode_args(*outer_args): + """ + Encode arguments of the decorated method specified by their sequence + number or name for keyword arguments. + + Example: + class some_class_that_needs_encoding_capabilities(Encoder): + ... + @encode_args(1, 3, 'name'): + def some_method( + self, encode_this, dont_encode_this, encode_this_too, **kwargs + ): + # if there's going to be a 'name' kwargs it will be encoded + ... + + This is an Encoder method decorator. + """ + def decorate(f): + def new_f(*args, **kwargs): + assert isinstance(args[0], Encoder), \ + 'first argument not Encoder instance' + new_args = list(args) + for a in outer_args: + if isinstance(a, int): + new_args[a] = args[0].encode(args[a]) + elif isinstance(a, basestring) and a in kwargs: + kwargs[a] = args[0].encode(kwargs[a]) + else: + raise TypeError( + 'encode_args takes a list of ints and basestrings' + ) + return f(*new_args, **kwargs) + new_f.func_name = f.func_name + return new_f + return decorate + + +def decode_retval(): + """ + Decode the return value of the decorated method. + + Example: + class some_class_that_needs_encoding_capabilities(Encoder): + ... + @decode_retval(): + def some_method(self): + ... + return this_will_be_decoded + ... + + This is an Encoder method decorator AND IT HAS TO BE CALLED (use "()")! + """ + def decorate(f): + def new_f(*args, **kwargs): + assert isinstance(args[0], Encoder), \ + 'first argument not Encoder instance' + return args[0].decode(f(*args, **kwargs)) + new_f.func_name = f.func_name + return new_f + return decorate + |