Working to reverse the binary format
[crapvine.git] / attribute.py
blob41b2df0792b49050f05a0b89bcaa8df8fc1fe736
1 ## This file is part of Crapvine.
2 ##
3 ## Copyright (C) 2007 Andrew Sayman <lorien420@myrealbox.com>
4 ##
5 ## Crapvine is free software; you can redistribute it and/or modify
6 ## it under the terms of the GNU General Public License as published by
7 ## the Free Software Foundation; either version 3 of the License, or
8 ## (at your option) any later version.
9 ##
10 ## Crapvine is distributed in the hope that it will be useful,
11 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ## GNU General Public License for more details.
15 ## You should have received a copy of the GNU General Public License
16 ## along with this program. If not, see <http://www.gnu.org/licenses/>.
18 import types
19 import gobject
21 from datetime import datetime
22 from dateutil.parser import parse
24 import inspect, types, __builtin__
26 ############## preliminary: two utility functions #####################
28 def skip_redundant(iterable, skipset=None):
29 "Redundant items are repeated items or items in the original skipset."
30 if skipset is None: skipset = set()
31 for item in iterable:
32 if item not in skipset:
33 skipset.add(item)
34 yield item
36 def remove_redundant(metaclasses):
37 skipset = set([types.ClassType])
38 for meta in metaclasses: # determines the metaclasses to be skipped
39 skipset.update(inspect.getmro(meta)[1:])
40 return tuple(skip_redundant(metaclasses, skipset))
42 ##################################################################
43 ## now the core of the module: two mutually recursive functions ##
44 ##################################################################
46 memoized_metaclasses_map = {}
48 def get_noconflict_metaclass(bases, left_metas, right_metas):
49 """Not intended to be used outside of this module, unless you know
50 what you are doing."""
51 # make tuple of needed metaclasses in specified priority order
52 metas = left_metas + tuple(map(type, bases)) + right_metas
53 needed_metas = remove_redundant(metas)
55 # return existing confict-solving meta, if any
56 if needed_metas in memoized_metaclasses_map:
57 return memoized_metaclasses_map[needed_metas]
58 # nope: compute, memoize and return needed conflict-solving meta
59 elif not needed_metas: # wee, a trivial case, happy us
60 meta = type
61 elif len(needed_metas) == 1: # another trivial case
62 meta = needed_metas[0]
63 # check for recursion, can happen i.e. for Zope ExtensionClasses
64 elif needed_metas == bases:
65 raise TypeError("Incompatible root metatypes", needed_metas)
66 else: # gotta work ...
67 metaname = '_' + ''.join([m.__name__ for m in needed_metas])
68 meta = classmaker()(metaname, needed_metas, {})
69 memoized_metaclasses_map[needed_metas] = meta
70 return meta
72 def classmaker(left_metas=(), right_metas=()):
73 def make_class(name, bases, adict):
74 metaclass = get_noconflict_metaclass(bases, left_metas, right_metas)
75 return metaclass(name, bases, adict)
76 return make_class
79 class BaseAttr(object):
80 def __init__(self, default = None, linked_default = None):
81 self.__default = default
82 self.__linked_default = linked_default
83 @property
84 def default(self):
85 return self.__default
86 @property
87 def linked_default(self):
88 return self.__linked_default
90 class TextAttr(BaseAttr):
91 def __init__(self, name, default = '', linked_default = None):
92 BaseAttr.__init__(self, default, linked_default)
93 self.name = name
94 self.inst_attr = "__%s" % name
95 def __set__(self, instance, value):
96 setattr(instance, self.inst_attr, str(value))
97 def __get__(self, instance, owner):
98 if not instance:
99 return self
100 if hasattr(instance, self.inst_attr):
101 return getattr(instance, self.inst_attr)
102 else:
103 if self.linked_default:
104 return getattr(instance, self.linked_default)
105 else:
106 return self.default
107 def __delete__(self, instance):
108 raise AttributeError('Cannot delete attribute')
110 class NumberAsTextAttr(BaseAttr):
111 def __init__(self, name, default = '0', linked_default = None, enforce_as = 'grapevine_float', simplify = True):
112 BaseAttr.__init__(self, default, linked_default)
113 self.name = name
114 self.inst_attr = "__%s" % name
115 self.enforce_as = enforce_as
116 self.simplify = simplify
117 def __set__(self, instance, value):
118 if self.enforce_as == 'grapevine_float':
119 if not self.__is_valid_grapevine_float(value):
120 raise ValueError('Cannot set attribute to value %s, no valid numbers' % (value))
121 elif self.enforce_as == 'float':
122 try:
123 float(value)
124 except ValueError:
125 raise ValueError('Cannot set attribute to value %s, not a float value' % (value))
126 elif self.enforce_as == 'int':
127 try:
128 int(value)
129 except ValueError:
130 raise ValueError('Cannot set attribute to value %s, not an int value' %(value))
131 if self.simplify:
132 setattr(instance, self.inst_attr, self.__simplify_float_str(value))
133 else:
134 setattr(instance, self.inst_attr, value)
135 def __get__(self, instance, owner):
136 if not instance:
137 return self
138 if hasattr(instance, self.inst_attr):
139 return getattr(instance, self.inst_attr)
140 else:
141 if self.linked_default:
142 return getattr(instance, self.linked_default)
143 else:
144 return self.default
145 def __is_valid_grapevine_float(self, value):
146 """Grapevine can store an integer value, a float value, a range specified by
147 by a '-', and two option values. Any number style string needs to be checked
148 for all of these options.
150 returns True if value should be accepted by as a grapevine numeric"""
151 try:
152 float(value)
153 return True
154 except ValueError:
155 can_use = False
156 for separator_str in ['-', ' or ']:
157 for innerval in value.split(separator_str):
158 try:
159 float(innerval)
160 can_use = True
161 except ValueError:
162 pass
163 return can_use
164 def __simplify_float_str(self, value):
165 try:
166 if float(value) == round(float(value)):
167 return unicode(int(round(float(value))))
168 else:
169 return value
170 except ValueError:
171 return value
173 class BoolAttr(BaseAttr):
174 def __init__(self, name, default = False, linked_default = None):
175 BaseAttr.__init__(self, default, linked_default)
176 self.name = name
177 self.inst_attr = "__%s" % name
178 def __set__(self, instance, value):
179 final_set = False
180 if value == 'yes':
181 final_set = True
182 elif value == 'no':
183 final_set = False
184 elif value:
185 final_set = True
186 setattr(instance, self.inst_attr, value)
187 def __get__(self, instance, owner):
188 if not instance:
189 return self
190 if hasattr(instance, self.inst_attr):
191 return getattr(instance, self.inst_attr)
192 else:
193 if self.linked_default:
194 return getattr(instance, self.linked_default)
195 else:
196 return self.default
197 def __delete__(self, instance):
198 raise AttributeError('Cannot delete attribute')
200 class DateAttr(BaseAttr):
201 def __init__(self, name, default = None, linked_default = None):
202 BaseAttr.__init__(self, default, linked_default)
203 self.name = name
204 self.inst_attr = "__%s" % name
205 def __set__(self, instance, value):
206 if not isinstance(value, datetime):
207 setattr(instance, self.inst_attr, parse(value))
208 else:
209 setattr(instance, self.inst_attr, value)
210 def __get__(self, instance, owner):
211 if not instance:
212 return self
213 if hasattr(instance, self.inst_attr):
214 return getattr(instance, self.inst_attr)
215 else:
216 if self.linked_default:
217 return getattr(instance, self.linked_default)
218 else:
219 return self.default
220 def __delete__(self, instance):
221 raise AttributeError('Cannot delete attribute')
223 class AttributeBuilder(type):
224 def __init__(cls, name, bases, dict):
225 super(AttributeBuilder, cls).__init__(name, bases, dict)
226 attribute_class_map = [
227 ('required_attrs', TextAttr),
228 ('text_attrs', TextAttr),
229 ('number_as_text_attrs', NumberAsTextAttr),
230 ('bool_attrs', BoolAttr),
231 ('date_attrs', DateAttr),
232 ('text_children', TextAttr)
234 defaults = getattr(cls, 'defaults', {})
235 linked_defaults = getattr(cls, 'linked_defaults', {})
236 for pair in attribute_class_map:
237 current_desired_properties = getattr(cls, pair[0], [])
238 for prop in current_desired_properties:
239 local_kargs = {}
240 attr_name = prop if not isinstance(prop, tuple) else prop[0]
242 if defaults.has_key(attr_name):
243 local_kargs['default'] = defaults[attr_name]
244 if linked_defaults.has_key(attr_name):
245 local_kargs['linked_default'] = linked_defaults[attr_name]
247 extra_kargs = {} if not isinstance(prop, tuple) else prop[1]
248 local_kargs.update(extra_kargs)
250 new_attr = pair[1](attr_name, **local_kargs)
251 setattr(cls, attr_name, new_attr)