#!/usr/bin/env python3 ## @file # @brief Add __str__ methods to financial objects so that @code print object @endcode leads to human readable results """ @package str_methods.py -- Add __str__ methods to financial objects Import this module and str(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 separate 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.""" # @author Christoph Holtermann, c.holtermann@gmx.de # @ingroup python_bindings_examples # @date May 2011 # # ToDo : # # * 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 from gnucash import 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 def __format__(self, fmt): def get_width(fmt_spec): """Parse fmt_spec to obtain width""" def remove_alignment(fmt_spec): if fmt_spec[1] in ["<","^",">"]: fmt_spec=fmt_spec[2:len(fmt_spec)] return fmt_spec def remove_sign(fmt_spec): if fmt_spec[0] in ["-","+"," "]: fmt_spec=fmt_spec[1:len(fmt_spec)] return fmt_spec def remove_cross(fmt_spec): if fmt_spec[0] in ["#"]: fmt_spec=fmt_spec[1:len(fmt_spec)] return fmt_spec def do_width(fmt_spec): n="" while len(fmt_spec)>0: if fmt_spec[0].isdigit(): n+=fmt_spec[0] fmt_spec=fmt_spec[1:len(fmt_spec)] else: break if n: return int(n) else: return None if len(fmt_spec)>=2: fmt_spec=remove_alignment(fmt_spec) if len(fmt_spec)>=1: fmt_spec=remove_sign(fmt_spec) if len(fmt_spec)>=1: fmt_spec=remove_cross(fmt_spec) width=do_width(fmt_spec) # Stop parsing here for we only need width return width def cut(s, width, replace_string="..."): """Cuts s to width and puts replace_string at it's end.""" #s=s.decode('UTF-8', "replace") if len(s)>width: if len(replace_string)>width: replace_string=replace_string[0:width] s=s[0:width-len(replace_string)] s=s+replace_string return s value=self.value # Replace Tabs and linebreaks #import types if isinstance(value, str): value = value.replace("\t","|") value = value.replace("\n","|") # Do regular formatting of object value = value.__format__(fmt) # Cut resulting value if longer than specified by width width = get_width(fmt) if width: value = cut(value, width, "...") return value def all_as_classwithcutting__format__(*args): """Converts every argument to instance of ClassWithCutting__format__""" #import types l=[] for a in args: #if type(a) in [types.StringType, types.UnicodeType]: # a=a.decode("UTF-8") l.append(ClassWithCutting__format__(a)) return l 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 isinstance(keys[a], str): # keys[a]=keys[a].decode(encoding,error) d[a]=ClassWithCutting__format__(keys[a]) return d # Split def __split__str__(self, encoding=None, error=None): """__str__(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) lot=self.GetLot() if lot: if type(lot).__name__ == 'SwigPyObject': lot=gnucash.GncLot(instance=lot) lot_str=lot.get_title() else: lot_str='---' transaction=self.GetParent() # This dict and the return statement can be changed according to individual needs fmt_dict={ "account":self.GetAccount().name, "value":self.GetValue(), "memo":self.GetMemo(), "lot":lot_str} fmt_str= ("Account: {account:20} "+ "Value: {value:>10} "+ "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=( "Transaction: {transaction_time:30} "+ "- {transaction2:30} "+ "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)) # 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__") gnucash.Split.register_optionflag("PRINT_TRANSACTION") gnucash.Split.setflag("PRINT_TRANSACTION",True) def __transaction__str__(self): """__str__ method for Transaction class""" from gnucash import Transaction import time self=Transaction(instance=self) fmt_tuple=('Date:',time.ctime(self.GetDate()), 'Description:',self.GetDescription(), 'Notes:',self.GetNotes()) transaction_str = "{0:6}{1:25} {2:14}{3:40} {4:7}{5:40}".format( *all_as_classwithcutting__format__(*fmt_tuple)) transaction_str += "\n" splits_str="" for n,split in enumerate(self.GetSplitList()): if not (type(split)==gnucash.Split): split=gnucash.Split(instance=split) transaction_flag = split.getflag("PRINT_TRANSACTION") split.setflag("PRINT_TRANSACTION",False) splits_str += "[{0:>2}] ".format(str(n)) splits_str += str(split) splits_str += "\n" split.setflag("PRINT_TRANSACTION",transaction_flag) return transaction_str + splits_str gnucash.gnucash_core_c.__transaction__str__=__transaction__str__ gnucash.Transaction.add_method("__transaction__str__","__str__") def __invoice__str__(self): """__str__ 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= ("{id_name:4}{id_value:10} {notes_name:7}{notes_value:20} {active_name:8}{active_value:7} {owner_name:12}{owner_value:20}"+ "{total_name:8}{total_value:10}{currency_mnemonic:3}").\ format(**all_as_classwithcutting__format__keys(**fmt_dict)) ret_entries="" 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 += " "+str(entry)+"\n" return ret_invoice+"\n"+ret_entries from gnucash.gnucash_business import Invoice gnucash.gnucash_core_c.__invoice__str__=__invoice__str__ gnucash.gnucash_business.Invoice.add_method("__invoice__str__","__str__") def __entry__str__(self): """__str__ 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":str(self.GetDate()), "description_name":"Description:", "description_value":self.GetDescription(), "notes_name":"Notes:", "notes_value":self.GetNotes(), "quant_name":"Quantity:", "quant_value":str(self.GetQuantity()), "invprice_name":"InvPrice:", "invprice_value":str(self.GetInvPrice())} return ("{date_name:6}{date_value:15} {description_name:13}{description_value:20} {notes_name:7}{notes_value:20}"+ "{quant_name:12}{quant_value:7} {invprice_name:10}{invprice_value:7}").\ format(**all_as_classwithcutting__format__keys(**fmt_dict)) from gnucash.gnucash_business import Entry gnucash.gnucash_core_c.__entry__str__=__entry__str__ gnucash.gnucash_business.Entry.add_method("__entry__str__","__str__")