Setting fcntl to None if not importable, adding tests module, updating README to...
[invirt/packages/python-jsonrpclib.git] / jsonrpclib / jsonclass.py
1 import types
2 import inspect
3 import re
4 import traceback
5
6 from jsonrpclib import config
7
8 iter_types = [
9     types.DictType,
10     types.ListType,
11     types.TupleType
12 ]
13
14 string_types = [
15     types.StringType,
16     types.UnicodeType
17 ]
18
19 numeric_types = [
20     types.IntType,
21     types.LongType,
22     types.FloatType
23 ]
24
25 value_types = [
26     types.BooleanType,
27     types.NoneType
28 ]
29
30 supported_types = iter_types+string_types+numeric_types+value_types
31 invalid_module_chars = r'[^a-zA-Z0-9\_\.]'
32
33 class TranslationError(Exception):
34     pass
35
36 def dump(obj, serialize_method=None, ignore_attribute=None, ignore=[]):
37     if not serialize_method:
38         serialize_method = config.serialize_method
39     if not ignore_attribute:
40         ignore_attribute = config.ignore_attribute
41     obj_type = type(obj)
42     # Parse / return default "types"...
43     if obj_type in numeric_types+string_types+value_types:
44         return obj
45     if obj_type in iter_types:
46         if obj_type in (types.ListType, types.TupleType):
47             new_obj = []
48             for item in obj:
49                 new_obj.append(dump(item, serialize_method,
50                                      ignore_attribute, ignore))
51             if obj_type is types.TupleType:
52                 new_obj = tuple(new_obj)
53             return new_obj
54         # It's a dict...
55         else:
56             new_obj = {}
57             for key, value in obj.iteritems():
58                 new_obj[key] = dump(value, serialize_method,
59                                      ignore_attribute, ignore)
60             return new_obj
61     # It's not a standard type, so it needs __jsonclass__
62     module_name = inspect.getmodule(obj).__name__
63     class_name = obj.__class__.__name__
64     json_class = class_name
65     if module_name not in ['', '__main__']:
66         json_class = '%s.%s' % (module_name, json_class)
67     return_obj = {"__jsonclass__":[json_class,]}
68     # If a serialization method is defined..
69     if serialize_method in dir(obj):
70         # Params can be a dict (keyword) or list (positional)
71         # Attrs MUST be a dict.
72         serialize = getattr(obj, serialize_method)
73         params, attrs = serialize()
74         return_obj['__jsonclass__'].append(params)
75         return_obj.update(attrs)
76         return return_obj
77     # Otherwise, try to figure it out
78     # Obviously, we can't assume to know anything about the
79     # parameters passed to __init__
80     return_obj['__jsonclass__'].append([])
81     attrs = {}
82     ignore_list = getattr(obj, ignore_attribute, [])+ignore
83     for attr_name, attr_value in obj.__dict__.iteritems():
84         if type(attr_value) in supported_types and \
85                 attr_name not in ignore_list and \
86                 attr_value not in ignore_list:
87             attrs[attr_name] = dump(attr_value, serialize_method,
88                                      ignore_attribute, ignore)
89     return_obj.update(attrs)
90     return return_obj
91
92 def load(obj):
93     if type(obj) in string_types+numeric_types+value_types:
94         return obj
95     if type(obj) is types.ListType:
96         return_list = []
97         for entry in obj:
98             return_list.append(load(entry))
99         return return_list
100     # Othewise, it's a dict type
101     if '__jsonclass__' not in obj.keys():
102         return_dict = {}
103         for key, value in obj.iteritems():
104             new_value = load(value)
105             return_dict[key] = new_value
106         return return_dict
107     # It's a dict, and it's a __jsonclass__
108     orig_module_name = obj['__jsonclass__'][0]
109     params = obj['__jsonclass__'][1]
110     if orig_module_name == '':
111         raise TranslationError('Module name empty.')
112     json_module_clean = re.sub(invalid_module_chars, '', orig_module_name)
113     if json_module_clean != orig_module_name:
114         raise TranslationError('Module name %s has invalid characters.' %
115                                orig_module_name)
116     json_module_parts = json_module_clean.split('.')
117     json_class = None
118     if len(json_module_parts) == 1:
119         # Local class name -- probably means it won't work
120         if json_module_parts[0] not in config.classes.keys():
121             raise TranslationError('Unknown class or module %s.' %
122                                    json_module_parts[0])
123         json_class = config.classes[json_module_parts[0]]
124     else:
125         json_class_name = json_module_parts.pop()
126         json_module_tree = '.'.join(json_module_parts)
127         try:
128             temp_module = __import__(json_module_tree)
129         except ImportError:
130             raise TranslationError('Could not import %s from module %s.' %
131                                    (json_class_name, json_module_tree))
132         json_class = getattr(temp_module, json_class_name)
133     # Creating the object...
134     new_obj = None
135     if type(params) is types.ListType:
136         new_obj = json_class(*params)
137     elif type(params) is types.DictType:
138         new_obj = json_class(**params)
139     else:
140         raise TranslationError('Constructor args must be a dict or list.')
141     for key, value in obj.iteritems():
142         if key == '__jsonclass__':
143             continue
144         setattr(new_obj, key, value)
145     return new_obj