GnuCash  5.6-150-g038405b370+
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 
29 INSTANCE_ARGUMENT = "instance"
30 
31 class 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 
233 def 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 
250 def 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 
256 def 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 
262 def 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 
349 def 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 
358 def 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 
368 def 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 
380 def 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 
391 def 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 
def ya_add_method(cls, function_name, method_name)
Add the function, method_name to this class as a method named name.
def add_method(cls, function_name, method_name)
Add the function, method_name to this class as a method named name.
def add_constructor_and_methods_with_prefix(cls, prefix, constructor, exclude=[])
def ya_add_classmethod(cls, function_name, method_name)
Add the function, method_name to this class as a classmethod named name.
def decorate_method(cls, decorator, method_name, args, kargs)
decorate method method_name of class cls with decorator decorator
def add_methods_with_prefix(cls, prefix, exclude=[])