GnuCash  4.11-137-g155922540d+
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  self.__instance = kargs[INSTANCE_ARGUMENT]
72  else:
73  self.__instance = getattr(self._module, self._new_instance)(
74  *process_list_convert_to_instance(args),
75  **process_dict_convert_to_instance(kargs))
76 
77  def get_instance(self):
78  """Get the instance data.
79 
80  You can also call the instance property
81  """
82  return self.__instance
83 
84  instance = property(get_instance)
85 
86  # CLASS METHODS
87 
88  @classmethod
89  def add_method(cls, function_name, method_name):
90  """! Add the function, method_name to this class as a method named name
91 
92  arguments:
93  @param cls Class: class to add methods to
94  @param function_name string: name of the function to add
95  @param method_name string: name of the method that function will be called
96 
97  function will be wrapped by method_function"""
98 
99  def method_function(self, *meth_func_args, **meth_func_kargs):
100  """! wrapper method for function
101 
102  arguments:
103  @param self: FunctionClass instance. Will be turned to its instance property.
104  @param *meth_func_args: arguments to be passed to function. All FunctionClass
105  objects will be turned to their respective instances.
106  @param **meth_func_kargs: keyword arguments to be passed to function. All
107  FunctionClass objects will be turned to their respective instances."""
108  return getattr(self._module, function_name)(
109  self.instance,
110  *process_list_convert_to_instance(meth_func_args),
111  **process_dict_convert_to_instance(meth_func_kargs)
112  )
113 
114  setattr(cls, method_name, method_function)
115  setattr(method_function, "__name__", method_name)
116  return method_function
117 
118  @classmethod
119  def ya_add_classmethod(cls, function_name, method_name):
120  """! Add the function, method_name to this class as a classmethod named name
121 
122  Taken from function_class and modified from add_method() to add classmethod
123  instead of method and not to turn self argument to self.instance.
124 
125  arguments:
126  @param cls Class: class to add methods to
127  @param function_name string: name of the function to add
128  @param method_name string: name of the classmethod that function will be called
129 
130  function will be wrapped by method_function"""
131 
132  def method_function(self, *meth_func_args, **meth_func_kargs):
133  """! wrapper method for function
134 
135  arguments:
136  @param self: FunctionClass instance.
137  @param *meth_func_args: arguments to be passed to function. All FunctionClass
138  objects will be turned to their respective instances.
139  @param **meth_func_kargs: keyword arguments to be passed to function. All
140  FunctionClass objects will be turned to their respective instances."""
141  return getattr(self._module, function_name)(
142  self,
143  *process_list_convert_to_instance(meth_func_args),
144  **process_dict_convert_to_instance(meth_func_kargs)
145  )
146 
147  setattr(cls, method_name, classmethod(method_function))
148  setattr(method_function, "__name__", method_name)
149  return method_function
150 
151  @classmethod
152  def ya_add_method(cls, function_name, method_name):
153  """! Add the function, method_name to this class as a method named name
154 
155  Taken from function_class. Modified to not turn self to self.instance
156  as add_method() does.
157 
158  arguments:
159  @param cls Class: class to add methods to
160  @param function_name string: name of the function to add
161  @param method_name string: name of the method that function will be called
162 
163  function will be wrapped by method_function"""
164 
165  def method_function(self, *meth_func_args, **meth_func_kargs):
166  """! wrapper method for function
167 
168  arguments:
169  @param self: FunctionClass instance.
170  @param *meth_func_args: arguments to be passed to function. All FunctionClass
171  objects will be turned to their respective instances.
172  @param **meth_func_kargs: keyword arguments to be passed to function. All
173  FunctionClass objects will be turned to their respective instances."""
174  return getattr(self._module, function_name)(
175  self,
176  *process_list_convert_to_instance(meth_func_args),
177  **process_dict_convert_to_instance(meth_func_kargs)
178  )
179 
180  setattr(cls, method_name, method_function)
181  setattr(method_function, "__name__", method_name)
182  return method_function
183 
184  @classmethod
185  def add_methods_with_prefix(cls, prefix, exclude=[]):
186  """Add a group of functions with the same prefix, exclude methods
187  in array exclude.
188  """
189  for function_name, function_value, after_prefix in \
190  extract_attributes_with_prefix(cls._module, prefix):
191 
192  if not (function_name in exclude):
193  cls.add_method(function_name, after_prefix)
194 
195  @classmethod
196  def add_constructor_and_methods_with_prefix(cls, prefix, constructor, exclude=[]):
197  """Add a group of functions with the same prefix, and set the
198  _new_instance attribute to prefix + constructor. Don't add methods
199  in array exclude.
200  """
201  cls.add_methods_with_prefix(prefix, exclude=exclude)
202  cls._new_instance = prefix + constructor
203 
204  @classmethod
205  def decorate_functions(cls, decorator, *args):
206  for function_name in args:
207  setattr( cls, function_name,
208  decorator( getattr(cls, function_name) ) )
209 
210  @classmethod
211  def decorate_method(cls, decorator, method_name, *args, **kargs):
212  """! decorate method method_name of class cls with decorator decorator
213 
214  in difference to decorate_functions() this allows to provide additional
215  arguments for the decorator function.
216 
217  arguments:
218  @param cls: class
219  @param decorator: function to decorate method
220  @param method_name: name of method to decorate (string)
221  @param *args: positional arguments for decorator
222  @param **kargs: keyword arguments for decorator"""
223  setattr(cls, method_name,
224  decorator(getattr(cls, method_name), *args, **kargs))
225 
226 def method_function_returns_instance(method_function, cls):
227  """A function decorator that is used to decorate method functions that
228  return instance data, to return instances instead.
229 
230  You can't use this decorator with @, because this function has a second
231  argument.
232  """
233  assert( 'instance' == INSTANCE_ARGUMENT )
234  def new_function(*args, **kargs):
235  kargs_cls = { INSTANCE_ARGUMENT : method_function(*args, **kargs) }
236  if kargs_cls['instance'] == None:
237  return None
238  else:
239  return cls( **kargs_cls )
240 
241  return new_function
242 
243 def method_function_returns_instance_list(method_function, cls):
244  def new_function(*args, **kargs):
245  return [ cls( **{INSTANCE_ARGUMENT: item} )
246  for item in method_function(*args, **kargs) ]
247  return new_function
248 
249 def methods_return_instance_lists(cls, function_dict):
250  for func_name, instance_name in iter(function_dict.items()):
251  setattr(cls, func_name,
252  method_function_returns_instance_list(
253  getattr(cls, func_name), instance_name))
254 
255 def default_arguments_decorator(function, *args, **kargs):
256  """! Decorates a function to give it default, positional and keyword arguments
257 
258  mimics python behavior when setting defaults in function/method arguments.
259  arguments can be set for positional or keyword arguments.
260 
261  kargs_pos contains positions of the keyword arguments.
262  @exception A TypeError will be raised if an argument is set as a positional and keyword argument
263  at the same time.
264  @note It might be possible to get keyword argument positional information using
265  introspection to avoid having to specify them manually
266 
267  a keyword argument default will be overwritten by a positional argument at the
268  actual function call
269 
270  this function modifies the docstring of the wrapped function to reflect
271  the defaults.
272 
273  You can't use this decorator with @, because this function has more
274  than one argument.
275 
276  arguments:
277  @param *args: optional positional defaults
278  @param kargs_pos: dict with keyword arguments as key and their position in the argument list as value
279  @param **kargs: optional keyword defaults
280 
281  @return new_function wrapping original function
282  """
283 
284  def new_function(*function_args, **function_kargs):
285  kargs_pos = {}
286  if "kargs_pos" in kargs:
287  kargs_pos = kargs.pop("kargs_pos")
288  new_argset = list(function_args)
289  new_argset.extend(args[len(function_args) :])
290  new_kargset = {**kargs, **function_kargs}
291  for karg_pos in kargs_pos:
292  if karg_pos in new_kargset:
293  pos_karg = kargs_pos[karg_pos]
294  if pos_karg < len(new_argset):
295  new_kargset.pop(karg_pos)
296 
297  return function(*new_argset, **new_kargset)
298 
299  kargs_pos = {} if "kargs_pos" not in kargs else kargs["kargs_pos"]
300  for karg_pos in kargs_pos:
301  if karg_pos in kargs:
302  pos_karg = kargs_pos[karg_pos]
303  if pos_karg < len(args):
304  raise TypeError(
305  "default_arguments_decorator() got multiple values for argument '%s'"
306  % karg_pos
307  )
308 
309  if new_function.__doc__ is None:
310  new_function.__doc__ = ""
311  if len(args):
312  firstarg = True
313  new_function.__doc__ += "positional argument defaults:\n"
314  for arg in args:
315  if not firstarg:
316  new_function.__doc__ += ", "
317  else:
318  new_function.__doc__ += " "
319  firstarg = False
320  new_function.__doc__ += str(arg)
321  new_function.__doc__ += "\n"
322  if len(kargs):
323  new_function.__doc__ += "keyword argument defaults:\n"
324  for karg in kargs:
325  if karg != "kargs_pos":
326  new_function.__doc__ += (
327  " " + str(karg) + " = " + str(kargs[karg]) + "\n"
328  )
329  if kargs_pos:
330  new_function.__doc__ += "keyword argument positions:\n"
331  for karg in kargs_pos:
332  new_function.__doc__ += (
333  " " + str(karg) + " is at pos " + str(kargs_pos[karg]) + "\n"
334  )
335  if len(args) or len(kargs):
336  new_function.__doc__ += (
337  "(defaults have been set by default_arguments_decorator method)"
338  )
339  return new_function
340 
341 
342 def return_instance_if_value_has_it(value):
343  """Return value.instance if value is an instance of ClassFromFunctions,
344  else return value
345  """
346  if isinstance(value, ClassFromFunctions):
347  return value.instance
348  else:
349  return value
350 
351 def process_list_convert_to_instance( value_list ):
352  """Return a list built from value_list, where if a value is in an instance
353  of ClassFromFunctions, we put value.instance in the list instead.
354 
355  Things that are not instances of ClassFromFunctions are returned to
356  the new list unchanged.
357  """
358  return [ return_instance_if_value_has_it(value)
359  for value in value_list ]
360 
361 def process_dict_convert_to_instance(value_dict):
362  """Return a dict built from value_dict, where if a value is in an instance
363  of ClassFromFunctions, we put value.instance in the dict instead.
364 
365  Things that are not instances of ClassFromFunctions are returned to
366  the new dict unchanged.
367  """
368  return {
369  key: return_instance_if_value_has_it(value) for key, value in value_dict.items()
370  }
371 
372 
373 def extract_attributes_with_prefix(obj, prefix):
374  """Generator that iterates through the attributes of an object and
375  for any attribute that matches a prefix, this yields
376  the attribute name, the attribute value, and the text that appears
377  after the prefix in the name
378  """
379  for attr_name, attr_value in iter(obj.__dict__.items()):
380  if attr_name.startswith(prefix):
381  after_prefix = attr_name[ len(prefix): ]
382  yield attr_name, attr_value, after_prefix
383 
384 def methods_return_instance(cls, function_dict):
385  """Iterates through a dictionary of function name strings and instance names
386  and sets the function to return the associated instance
387  """
388  for func_name, instance_name in iter(function_dict.items()):
389  setattr(cls, func_name,
390  method_function_returns_instance( getattr(cls, func_name), instance_name))
391 
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=[])