GnuCash c935c2f+
Loading...
Searching...
No Matches
function_class.py
Go to the documentation of this file.
1# function_class.py -- Library for making python classes from a set
2# of functions.
3#
4# Copyright (C) 2008 ParIT Worker Co-operative <paritinfo@parit.ca>
5# This program is free software; you can redistribute it and/or
6# modify it under the terms of the GNU General Public License as
7# published by the Free Software Foundation; either version 2 of
8# the License, or (at your option) any later version.
9#
10# This program 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.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, contact:
17# Free Software Foundation Voice: +1-617-542-5942
18# 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
19# Boston, MA 02110-1301, USA gnu@gnu.org
20#
21# @author Mark Jenkins, ParIT Worker Co-operative <mark@parit.ca>
22
23
28
29INSTANCE_ARGUMENT = "instance"
30
31class ClassFromFunctions(object):
32 """Inherit this class to give yourself a python class that wraps a set of
33 functions that together constitute the methods of the class.
34
35 The method functions must all have as a first argument an object
36 holding the instance data. There must also be a function that
37 returns a new instance of the class, the constructor.
38
39 Your subclass must define
40 _module - The module where the method functions, including the
41 constructor can be found
42 _new_instance - The name of a function that serves as a constructor,
43 returning the instance data.
44
45 To access the instance data, use the read-only property instance.
46
47 To add some functions from _module as methods, call classmethods like
48 add_method and add_methods_with_prefix.
49 """
50 def __new__(cls, *args, **kargs):
51 # why reimplement __new__? Because later on we're going to
52 # use new to avoid creating new instances when existing instances
53 # already exist with the same __instance value, or equivalent __instance
54 # values, where this is desirable...
55 return super(ClassFromFunctions, cls).__new__(cls)
56
57 def __init__(self, *args, **kargs):
58 """Construct a new instance, using either the function
59 self._module[self._new_instance] or using existing instance
60 data. (specified with the keyword argument, instance)
61
62 if instance argument is None it will be ignored and the
63 constructor will be called to get a new instance
64
65 Pass the arguments that should be passed on to
66 self._module[self._new_instance]. Any arguments of that
67 are instances of ClassFromFunctions will be switched with the instance
68 data. (by calling the .instance property)
69 """
70 if INSTANCE_ARGUMENT in kargs and kargs[INSTANCE_ARGUMENT] is not None:
71 inst = kargs[INSTANCE_ARGUMENT]
72 # Unwrap if someone passes a wrapper object as instance data,
73 # e.g. GncNumeric(instance=some_GncNumeric). This can happen
74 # when a method's return type is changed from a raw SWIG proxy
75 # to a wrapper class and callers still re-wrap the result.
76 if isinstance(inst, ClassFromFunctions):
77 inst = inst.instance
78 self.__instance = inst
79 else:
80 self.__instance = getattr(self._module, self._new_instance)(
81 *process_list_convert_to_instance(args),
82 **process_dict_convert_to_instance(kargs))
83
84 def get_instance(self):
85 """Get the instance data.
86
87 You can also call the instance property
88 """
89 return self.__instance
90
91 instance = property(get_instance)
92
93 # CLASS METHODS
94
95 @classmethod
96 def add_method(cls, function_name, method_name):
97 """! Add the function, method_name to this class as a method named name
98
99 arguments:
100 @param cls Class: class to add methods to
101 @param function_name string: name of the function to add
102 @param method_name string: name of the method that function will be called
103
104 function will be wrapped by method_function"""
105
106 def method_function(self, *meth_func_args, **meth_func_kargs):
107 """! wrapper method for function
108
109 arguments:
110 @param self: FunctionClass instance. Will be turned to its instance property.
111 @param *meth_func_args: arguments to be passed to function. All FunctionClass
112 objects will be turned to their respective instances.
113 @param **meth_func_kargs: keyword arguments to be passed to function. All
114 FunctionClass objects will be turned to their respective instances."""
115 return getattr(self._module, function_name)(
116 self.instance,
117 *process_list_convert_to_instance(meth_func_args),
118 **process_dict_convert_to_instance(meth_func_kargs)
119 )
120
121 setattr(cls, method_name, method_function)
122 setattr(method_function, "__name__", method_name)
123 return method_function
124
125 @classmethod
126 def ya_add_classmethod(cls, function_name, method_name):
127 """! Add the function, method_name to this class as a classmethod named name
128
129 Taken from function_class and modified from add_method() to add classmethod
130 instead of method and not to turn self argument to self.instance.
131
132 arguments:
133 @param cls Class: class to add methods to
134 @param function_name string: name of the function to add
135 @param method_name string: name of the classmethod that function will be called
136
137 function will be wrapped by method_function"""
138
139 def method_function(self, *meth_func_args, **meth_func_kargs):
140 """! wrapper method for function
141
142 arguments:
143 @param self: FunctionClass instance.
144 @param *meth_func_args: arguments to be passed to function. All FunctionClass
145 objects will be turned to their respective instances.
146 @param **meth_func_kargs: keyword arguments to be passed to function. All
147 FunctionClass objects will be turned to their respective instances."""
148 return getattr(self._module, function_name)(
149 self,
150 *process_list_convert_to_instance(meth_func_args),
151 **process_dict_convert_to_instance(meth_func_kargs)
152 )
153
154 setattr(cls, method_name, classmethod(method_function))
155 setattr(method_function, "__name__", method_name)
156 return method_function
157
158 @classmethod
159 def ya_add_method(cls, function_name, method_name):
160 """! Add the function, method_name to this class as a method named name
161
162 Taken from function_class. Modified to not turn self to self.instance
163 as add_method() does.
164
165 arguments:
166 @param cls Class: class to add methods to
167 @param function_name string: name of the function to add
168 @param method_name string: name of the method that function will be called
169
170 function will be wrapped by method_function"""
171
172 def method_function(self, *meth_func_args, **meth_func_kargs):
173 """! wrapper method for function
174
175 arguments:
176 @param self: FunctionClass instance.
177 @param *meth_func_args: arguments to be passed to function. All FunctionClass
178 objects will be turned to their respective instances.
179 @param **meth_func_kargs: keyword arguments to be passed to function. All
180 FunctionClass objects will be turned to their respective instances."""
181 return getattr(self._module, function_name)(
182 self,
183 *process_list_convert_to_instance(meth_func_args),
184 **process_dict_convert_to_instance(meth_func_kargs)
185 )
186
187 setattr(cls, method_name, method_function)
188 setattr(method_function, "__name__", method_name)
189 return method_function
190
191 @classmethod
192 def add_methods_with_prefix(cls, prefix, exclude=[]):
193 """Add a group of functions with the same prefix, exclude methods
194 in array exclude.
195 """
196 for function_name, function_value, after_prefix in \
197 extract_attributes_with_prefix(cls._module, prefix):
198
199 if not (function_name in exclude):
200 cls.add_method(function_name, after_prefix)
201
202 @classmethod
203 def add_constructor_and_methods_with_prefix(cls, prefix, constructor, exclude=[]):
204 """Add a group of functions with the same prefix, and set the
205 _new_instance attribute to prefix + constructor. Don't add methods
206 in array exclude.
207 """
208 cls.add_methods_with_prefix(prefix, exclude=exclude)
209 cls._new_instance = prefix + constructor
210
211 @classmethod
212 def decorate_functions(cls, decorator, *args):
213 for function_name in args:
214 setattr( cls, function_name,
215 decorator( getattr(cls, function_name) ) )
216
217 @classmethod
218 def decorate_method(cls, decorator, method_name, *args, **kargs):
219 """! decorate method method_name of class cls with decorator decorator
220
221 in difference to decorate_functions() this allows to provide additional
222 arguments for the decorator function.
223
224 arguments:
225 @param cls: class
226 @param decorator: function to decorate method
227 @param method_name: name of method to decorate (string)
228 @param *args: positional arguments for decorator
229 @param **kargs: keyword arguments for decorator"""
230 setattr(cls, method_name,
231 decorator(getattr(cls, method_name), *args, **kargs))
232
233def method_function_returns_instance(method_function, cls):
234 """A function decorator that is used to decorate method functions that
235 return instance data, to return instances instead.
236
237 You can't use this decorator with @, because this function has a second
238 argument.
239 """
240 assert( 'instance' == INSTANCE_ARGUMENT )
241 def new_function(*args, **kargs):
242 kargs_cls = { INSTANCE_ARGUMENT : method_function(*args, **kargs) }
243 if kargs_cls['instance'] == None:
244 return None
245 else:
246 return cls( **kargs_cls )
247
248 return new_function
249
250def method_function_returns_instance_list(method_function, cls):
251 def new_function(*args, **kargs):
252 return [ cls( **{INSTANCE_ARGUMENT: item} )
253 for item in method_function(*args, **kargs) ]
254 return new_function
255
256def methods_return_instance_lists(cls, function_dict):
257 for func_name, instance_name in iter(function_dict.items()):
258 setattr(cls, func_name,
259 method_function_returns_instance_list(
260 getattr(cls, func_name), instance_name))
261
262def default_arguments_decorator(function, *args, **kargs):
263 """! Decorates a function to give it default, positional and keyword arguments
264
265 mimics python behavior when setting defaults in function/method arguments.
266 arguments can be set for positional or keyword arguments.
267
268 kargs_pos contains positions of the keyword arguments.
269 @exception A TypeError will be raised if an argument is set as a positional and keyword argument
270 at the same time.
271 @note It might be possible to get keyword argument positional information using
272 introspection to avoid having to specify them manually
273
274 a keyword argument default will be overwritten by a positional argument at the
275 actual function call
276
277 this function modifies the docstring of the wrapped function to reflect
278 the defaults.
279
280 You can't use this decorator with @, because this function has more
281 than one argument.
282
283 arguments:
284 @param *args: optional positional defaults
285 @param kargs_pos: dict with keyword arguments as key and their position in the argument list as value
286 @param **kargs: optional keyword defaults
287
288 @return new_function wrapping original function
289 """
290
291 def new_function(*function_args, **function_kargs):
292 kargs_pos = {}
293 if "kargs_pos" in kargs:
294 kargs_pos = kargs.pop("kargs_pos")
295 new_argset = list(function_args)
296 new_argset.extend(args[len(function_args) :])
297 new_kargset = {**kargs, **function_kargs}
298 for karg_pos in kargs_pos:
299 if karg_pos in new_kargset:
300 pos_karg = kargs_pos[karg_pos]
301 if pos_karg < len(new_argset):
302 new_kargset.pop(karg_pos)
303
304 return function(*new_argset, **new_kargset)
305
306 kargs_pos = {} if "kargs_pos" not in kargs else kargs["kargs_pos"]
307 for karg_pos in kargs_pos:
308 if karg_pos in kargs:
309 pos_karg = kargs_pos[karg_pos]
310 if pos_karg < len(args):
311 raise TypeError(
312 "default_arguments_decorator() got multiple values for argument '%s'"
313 % karg_pos
314 )
315
316 if new_function.__doc__ is None:
317 new_function.__doc__ = ""
318 if len(args):
319 firstarg = True
320 new_function.__doc__ += "positional argument defaults:\n"
321 for arg in args:
322 if not firstarg:
323 new_function.__doc__ += ", "
324 else:
325 new_function.__doc__ += " "
326 firstarg = False
327 new_function.__doc__ += str(arg)
328 new_function.__doc__ += "\n"
329 if len(kargs):
330 new_function.__doc__ += "keyword argument defaults:\n"
331 for karg in kargs:
332 if karg != "kargs_pos":
333 new_function.__doc__ += (
334 " " + str(karg) + " = " + str(kargs[karg]) + "\n"
335 )
336 if kargs_pos:
337 new_function.__doc__ += "keyword argument positions:\n"
338 for karg in kargs_pos:
339 new_function.__doc__ += (
340 " " + str(karg) + " is at pos " + str(kargs_pos[karg]) + "\n"
341 )
342 if len(args) or len(kargs):
343 new_function.__doc__ += (
344 "(defaults have been set by default_arguments_decorator method)"
345 )
346 return new_function
347
348
349def return_instance_if_value_has_it(value):
350 """Return value.instance if value is an instance of ClassFromFunctions,
351 else return value
352 """
353 if isinstance(value, ClassFromFunctions):
354 return value.instance
355 else:
356 return value
357
358def process_list_convert_to_instance( value_list ):
359 """Return a list built from value_list, where if a value is in an instance
360 of ClassFromFunctions, we put value.instance in the list instead.
361
362 Things that are not instances of ClassFromFunctions are returned to
363 the new list unchanged.
364 """
365 return [ return_instance_if_value_has_it(value)
366 for value in value_list ]
367
368def process_dict_convert_to_instance(value_dict):
369 """Return a dict built from value_dict, where if a value is in an instance
370 of ClassFromFunctions, we put value.instance in the dict instead.
371
372 Things that are not instances of ClassFromFunctions are returned to
373 the new dict unchanged.
374 """
375 return {
376 key: return_instance_if_value_has_it(value) for key, value in value_dict.items()
377 }
378
379
380def extract_attributes_with_prefix(obj, prefix):
381 """Generator that iterates through the attributes of an object and
382 for any attribute that matches a prefix, this yields
383 the attribute name, the attribute value, and the text that appears
384 after the prefix in the name
385 """
386 for attr_name, attr_value in iter(obj.__dict__.items()):
387 if attr_name.startswith(prefix):
388 after_prefix = attr_name[ len(prefix): ]
389 yield attr_name, attr_value, after_prefix
390
391def methods_return_instance(cls, function_dict):
392 """Iterates through a dictionary of function name strings and instance names
393 and sets the function to return the associated instance
394 """
395 for func_name, instance_name in iter(function_dict.items()):
396 setattr(cls, func_name,
397 method_function_returns_instance( getattr(cls, func_name), instance_name))
398
add_methods_with_prefix(cls, prefix, exclude=[])
add_constructor_and_methods_with_prefix(cls, prefix, constructor, exclude=[])
ya_add_classmethod(cls, function_name, method_name)
Add the function, method_name to this class as a classmethod named name.
decorate_method(cls, decorator, method_name, *args, **kargs)
decorate method method_name of class cls with decorator decorator
add_method(cls, function_name, method_name)
Add the function, method_name to this class as a method named name.
ya_add_method(cls, function_name, method_name)
Add the function, method_name to this class as a method named name.