Improve python bindings: str_methods.py improved.

Patch by Christoph Holtermann:

str_methods.py
- added a flagging system to change the output of the __str__ methods
- added unicode and str-methods to invoice and entry

function_class.py
- added modified add_method and add_classmethods to implement the previous

gnucash_business.py
- add methods_return_instance_lists for method Invoice.GetEntries

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@20695 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Christian Stimming
2011-05-27 06:39:27 +00:00
parent e1e89fa741
commit e782f4368c
3 changed files with 270 additions and 57 deletions

View File

@@ -1,26 +1,90 @@
#!/usr/bin/env python
## @file @brief Add __str__ and __unicode__ methods to financial objects so that @code print object @endcode leads to human readable results
""" @package str_methods.py -- Add __str__ and __unicode__ methods to financial objects
Import this module and str(Object) and unicode(Object) where Object is Transaction, Split,Invoice
or Entry leads to human readable results. That is handy when using @code print object @endcode
I chose to put these functions/methods in a seperate file to develop them like this and maybe if
they prove to be useful they can be put in gnucash_core.py.
I am searching to find the best way to serialize these complex objects. Ideally this serialization
would be configurable.
If someone has suggestions how to beautify, purify or improve this code in any way please feel
free to do so.
This is written as a first approach to a shell-environment using ipython to interactively manipulate
GnuCashs Data."""
# str_methods.py -- Add __str__ and __unicode__ methods to financial objects
#
## @file
# @brief Add __str__ and __unicode__ methods to financial objects so that @code print object @endcode leads to human readable results
# @author Christoph Holtermann, c.holtermann@gmx.de
# @ingroup python_bindings_examples
# @date May 2011
#
# Import this module and str(Object) and unicode(Object) where Object is Transaction or Split leads
# to human readable results. That is handy when using @code print object @endcode
# ToDo :
#
# I chose to put these functions/methods in a seperate file to develop them like this and maybe if
# they prove to be useful they can be put in gnucash_core.py
#
# This is written as a first approach to a shell-environment using ipython to interactively manipulate
# GnuCashs Data.
# * Testing for SWIGtypes
# * dealing the cutting format in a bit more elegant way
# * having setflag as a classmethod makes it probably impossible to have flags on instance level. Would changing that be useful ?
# * It seems useful to have an object for each modification. That is because there is some Initialisation to be done.
#
import gnucash
import gnucash, function_class
# Default values for encoding of strings in GnuCashs Database
DEFAULT_ENCODING = "UTF-8"
DEFAULT_ERROR = "ignore"
def setflag(self, name, value):
if not(name in self.OPTIONFLAGS_BY_NAME):
self.register_optionflag(name)
if value == True:
self.optionflags |= self.OPTIONFLAGS_BY_NAME[name]
else:
self.optionflags &= ~self.OPTIONFLAGS_BY_NAME[name]
def getflag(self, name):
if not(name in self.OPTIONFLAGS_BY_NAME):
raise KeyError(str(name)+" is not a registered key.")
return ((self.optionflags & self.OPTIONFLAGS_BY_NAME[name]) != 0)
def register_optionflag(self,name):
"""Taken from doctest.py"""
# Create a new flag unless `name` is already known.
return self.OPTIONFLAGS_BY_NAME.setdefault(name, 1 << len(self.OPTIONFLAGS_BY_NAME))
def ya_add_method(_class, function, method_name=None, clsmethod=False, noinstance=False):
"""Calls add_method from function_methods.py but makes it
possible to use functions in this module. Also keeps the
docstring"""
if method_name == None:
method_name = function.__name__
setattr(gnucash.gnucash_core_c,function.__name__,function)
if clsmethod:
mf=_class.ya_add_classmethod(function.__name__,method_name)
elif noinstance:
mf=_class.add_method(function.__name__,method_name)
else:
mf=_class.ya_add_method(function.__name__,method_name)
if function.__doc__ != None:
setattr(mf, "__doc__", function.__doc__)
def infect(_class, function, method_name):
if not getattr(_class, "OPTIONFLAGS_BY_NAME", None):
_class.OPTIONFLAGS_BY_NAME={}
_class.optionflags=0
ya_add_method(_class,register_optionflag,clsmethod=True)
ya_add_method(_class,setflag,clsmethod=True)
ya_add_method(_class,getflag,clsmethod=True)
ya_add_method(_class, function, method_name)
class ClassWithCutting__format__():
"""This class provides a __format__ method which cuts values to a certain width.
If width is too big '...' will be put at the end of the resulting string."""
def __init__(self,value):
self.value = value
@@ -99,7 +163,7 @@ class ClassWithCutting__format__():
return value
def all_as_ClassWithCutting__format__(*args):
def all_as_classwithcutting__format__(*args):
"""Converts every argument to instance of ClassWithCutting__format__"""
import types
@@ -111,27 +175,45 @@ def all_as_ClassWithCutting__format__(*args):
return l
def all_as_ClassWithCutting__format__keys(**keys):
def all_as_classwithcutting__format__keys(encoding=None, error=None, **keys):
"""Converts every argument to instance of ClassWithCutting__format__"""
import types
d={}
if encoding==None:
encoding=DEFAULT_ENCODING
if error==None:
error=DEFAULT_ERROR
for a in keys:
if type(keys[a]) in [types.StringType, types.UnicodeType]:
keys[a]=keys[a].decode("UTF-8")
keys[a]=keys[a].decode(encoding,error)
d[a]=ClassWithCutting__format__(keys[a])
return d
def __split__unicode__(self):
"""__unicode__ method for Split"""
# Split
def __split__unicode__(self, encoding=None, error=None):
"""__unicode__(self, encoding=None, error=None) -> object
Serialize the Split object and return as a new Unicode object.
Keyword arguments:
encoding -- defaults to str_methods.default_encoding
error -- defaults to str_methods.default_error
See help(unicode) for more details or http://docs.python.org/howto/unicode.html.
"""
from gnucash import Split
import time
self=Split(instance=self)
#self=Split(instance=self)
lot=gnucash.GncLot(instance=self.GetLot())
lot=self.GetLot()
if lot:
if type(lot).__name__ == 'SwigPyObject':
lot=gnucash.GncLot(instance=lot)
lot_str=lot.get_title()
else:
lot_str='---'
@@ -140,40 +222,42 @@ def __split__unicode__(self):
# This dict and the return statement can be changed according to individual needs
fmt_dict={
"account_name":'Account:',
"account_value":self.GetAccount().name,
"value_name":'Value:',
"value_value":self.GetValue(),
"memo_name":'Memo:',
"memo_value":self.GetMemo(),
"transaction_name1":'Transaction:',
"transaction_value1":time.ctime(transaction.GetDate()),
"transaction_name2":u'-',
"transaction_value2":transaction.GetDescription(),
"lot_name":'Lot: ',
"lot_value":lot_str}
return (u"{account_name:8}{account_value:20} "+
u"{value_name:7}{value_value:>10} "+
u"{memo_name:7}{memo_value:30} "+
u"{transaction_name1:12}{transaction_value1:15} "+
u"{transaction_name2:1}{transaction_value2:30} "+
u"{lot_name:5}{lot_value:10}").\
format(**all_as_ClassWithCutting__format__keys(**fmt_dict))
"account":self.GetAccount().name,
"value":self.GetValue(),
"memo":self.GetMemo(),
"lot":lot_str}
fmt_str= (u"Account: {account:20} "+
u"Value: {value:>10} "+
u"Memo: {memo:30} ")
if self.optionflags & self.OPTIONFLAGS_BY_NAME["PRINT_TRANSACTION"]:
fmt_t_dict={
"transaction_time":time.ctime(transaction.GetDate()),
"transaction2":transaction.GetDescription()}
fmt_t_str=(
u"Transaction: {transaction_time:30} "+
u"- {transaction2:30} "+
u"Lot: {lot:10}")
fmt_dict.update(fmt_t_dict)
fmt_str += fmt_t_str
return fmt_str.format(**all_as_classwithcutting__format__keys(encoding,error,**fmt_dict))
def __split__str__(self):
"""__str__ method for split class"""
"""Returns a bytestring representation of self.__unicode__"""
from gnucash import Split
self=Split(instance=self)
#self=Split(instance=self)
return unicode(self).encode('utf-8')
gnucash.gnucash_core_c.__split__str__=__split__str__
gnucash.Split.add_method("__split__str__","__str__")
gnucash.gnucash_core_c.__split__unicode__=__split__unicode__
gnucash.Split.add_method("__split__unicode__","__unicode__")
# This could be something like an __init__. Maybe we could call it virus because it infects the Split object which
# thereafter mutates to have better capabilities.
infect(gnucash.Split,__split__str__,"__str__")
infect(gnucash.Split,__split__unicode__,"__unicode__")
gnucash.Split.register_optionflag("PRINT_TRANSACTION")
gnucash.Split.setflag("PRINT_TRANSACTION",True)
def __transaction__unicode__(self):
"""__unicode__ method for Transaction class"""
@@ -181,19 +265,25 @@ def __transaction__unicode__(self):
import time
self=Transaction(instance=self)
fmt_tuple=('Date:',str(time.ctime(self.GetDate())),
'Description:',str(self.GetDescription()),
'Notes:',str(self.GetNotes()))
fmt_tuple=('Date:',time.ctime(self.GetDate()),
'Description:',self.GetDescription(),
'Notes:',self.GetNotes())
transaction_str = u"{0:6}{1:25} {2:14}{3:40} {4:7}{5:40}".format(
*all_as_ClassWithCutting__format__(*fmt_tuple))
*all_as_classwithcutting__format__(*fmt_tuple))
transaction_str += "\n"
splits_str=""
for n,split in enumerate(self.GetSplitList()):
if not type(split)==gnucash.Split:
if not (type(split)==gnucash.Split):
split=gnucash.Split(instance=split)
splits_str += u"[{0:>2}] ".format(str(n))
transaction_flag = split.getflag("PRINT_TRANSACTION")
split.setflag("PRINT_TRANSACTION",False)
splits_str += u"[{0:>2}] ".format(unicode(n))
splits_str += unicode(split)
splits_str += "\n"
split.setflag("PRINT_TRANSACTION",transaction_flag)
return transaction_str + splits_str
@@ -210,3 +300,94 @@ gnucash.Transaction.add_method("__transaction__str__","__str__")
gnucash.gnucash_core_c.__transaction__unicode__=__transaction__unicode__
gnucash.Transaction.add_method("__transaction__unicode__","__unicode__")
def __invoice__unicode__(self):
"""__unicode__ method for Invoice"""
from gnucash.gnucash_business import Invoice
self=Invoice(instance=self)
# This dict and the return statement can be changed according to individual needs
fmt_dict={
"id_name":"ID:",
"id_value":self.GetID(),
"notes_name":"Notes:",
"notes_value":self.GetNotes(),
"active_name":"Active:",
"active_value":str(self.GetActive()),
"owner_name":"Owner Name:",
"owner_value":self.GetOwner().GetName(),
"total_name":"Total:",
"total_value":str(self.GetTotal()),
"currency_mnemonic":self.GetCurrency().get_mnemonic()}
ret_invoice= (u"{id_name:4}{id_value:10} {notes_name:7}{notes_value:20} {active_name:8}{active_value:7} {owner_name:12}{owner_value:20}"+
u"{total_name:8}{total_value:10}{currency_mnemonic:3}").\
format(**all_as_classwithcutting__format__keys(**fmt_dict))
ret_entries=u""
entry_list = self.GetEntries()
for entry in entry_list: # Type of entry has to be checked
if not(type(entry)==Entry):
entry=Entry(instance=entry)
ret_entries += " "+unicode(entry)+"\n"
return ret_invoice+"\n"+ret_entries
def __invoice__str__(self):
"""__str__ method for invoice class"""
from gnucash.gnucash_business import Invoice
self=Invoice(instance=self)
return unicode(self).encode('utf-8')
from gnucash.gnucash_business import Invoice
gnucash.gnucash_core_c.__invoice__str__=__invoice__str__
gnucash.gnucash_business.Invoice.add_method("__invoice__str__","__str__")
gnucash.gnucash_core_c.__invoice__unicode__=__invoice__unicode__
gnucash.gnucash_business.Invoice.add_method("__invoice__unicode__","__unicode__")
def __entry__unicode__(self):
"""__unicode__ method for Entry"""
from gnucash.gnucash_business import Entry
self=Entry(instance=self)
# This dict and the return statement can be changed according to individual needs
fmt_dict={
"date_name":"Date:",
"date_value":unicode(self.GetDate()),
"description_name":"Description:",
"description_value":self.GetDescription(),
"notes_name":"Notes:",
"notes_value":self.GetNotes(),
"quant_name":"Quantity:",
"quant_value":unicode(gnucash.GncNumeric(instance=self.GetQuantity())),
"invprice_name":"InvPrice:",
"invprice_value":unicode(gnucash.GncNumeric(instance=self.GetInvPrice()))}
return (u"{date_name:6}{date_value:15} {description_name:13}{description_value:20} {notes_name:7}{notes_value:20}"+
u"{quant_name:12}{quant_value:7} {invprice_name:10}{invprice_value:7}").\
format(**all_as_classwithcutting__format__keys(**fmt_dict))
def __entry__str__(self):
"""__str__ method for Entry class"""
from gnucash.gnucash_business import Entry
self=Entry(instance=self)
return unicode(self).encode('utf-8')
from gnucash.gnucash_business import Entry
gnucash.gnucash_core_c.__entry__str__=__entry__str__
gnucash.gnucash_business.Entry.add_method("__entry__str__","__str__")
gnucash.gnucash_core_c.__entry__unicode__=__entry__unicode__
gnucash.gnucash_business.Entry.add_method("__entry__unicode__","__unicode__")

View File

@@ -30,7 +30,7 @@ INSTANCE_ARGUMENT = "instance"
class ClassFromFunctions(object):
"""Inherit this class to give yourself a python class that wraps a set of
functions that together consitute the methods of the class.
functions that together constitute the methods of the class.
The method functions must all have as a first argument an object
holding the instance data. There must also be a function that
@@ -50,7 +50,7 @@ class ClassFromFunctions(object):
def __new__(cls, *args, **kargs):
# why reimpliment __new__? Because later on we're going to
# use new to avoid creating new instances when existing instances
# already exist with the same __instance value, or equivlent __instance
# already exist with the same __instance value, or equivalent __instance
# values, where this is desirable...
return super(ClassFromFunctions, cls).__new__(cls)
@@ -93,7 +93,37 @@ class ClassFromFunctions(object):
setattr(cls, method_name, method_function)
setattr(method_function, "__name__", method_name)
return method_function
@classmethod
def ya_add_classmethod(cls, function_name, method_name):
"""Add the function, method_name to this class as a classmethod named name
Taken from function_class and slightly modified.
"""
def method_function(self, *meth_func_args):
return getattr(self._module, function_name)(
self,
*process_list_convert_to_instance(meth_func_args) )
setattr(cls, method_name, classmethod(method_function))
setattr(method_function, "__name__", method_name)
return method_function
@classmethod
def ya_add_method(cls, function_name, method_name):
"""Add the function, method_name to this class as a method named name
Taken from function_class and slightly modified.
"""
def method_function(self, *meth_func_args):
return getattr(self._module, function_name)(
self,
*process_list_convert_to_instance(meth_func_args) )
setattr(cls, method_name, method_function)
setattr(method_function, "__name__", method_name)
return method_function
@classmethod
def add_methods_with_prefix(cls, prefix):
"""Add a group of functions with the same prefix

View File

@@ -31,7 +31,7 @@ import gnucash_core_c
from function_class import \
ClassFromFunctions, extract_attributes_with_prefix, \
default_arguments_decorator, method_function_returns_instance, \
methods_return_instance
methods_return_instance, methods_return_instance_lists
from gnucash_core import \
GnuCashCoreClass, GncNumeric, GncCommodity, Transaction, \
@@ -309,6 +309,8 @@ taxtableentry_dict = {
# Invoice
Invoice.add_constructor_and_methods_with_prefix('gncInvoice', 'Create')
methods_return_instance_lists(
Invoice, { 'GetEntries': Entry })
# Bill
Bill.add_methods_with_prefix('gncBill')