├── .gitignore ├── README.md ├── Amazon-CSV-Test.csv ├── amazonpayments.py ├── abstractprovider.py ├── qboconst.py ├── csvtoqbo.py ├── csvtoqbo-test.py └── qbo.py /.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | old/ 3 | tests/ 4 | *.pyc 5 | *.*~ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | csvtoqbo 2 | ======== 3 | 4 | Python tool to convert CSV files generated from multiple providers to QBO format for Quickbooks 5 | 6 | Usage: 7 | 8 | python csvtoqbo.py \ \ 9 | 10 | Example: 11 | 12 | python csvtoqbo.py -amazon Amazon-CSV-Test.csv 13 | 14 | Test Suite: 15 | 16 | python csvtoqbo-test.py 17 | -------------------------------------------------------------------------------- /Amazon-CSV-Test.csv: -------------------------------------------------------------------------------- 1 | "Date","Type","To/From","Name","Status","Amount","Fees","Transaction ID","Reference" 2 | "May 8, 2013","Payment","From","Nick","Completed","$50.00","$1.75","XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","9999ccff-c162-4741-819d-b3ae89b73d0a" 3 | "May 7, 2013","Payment","From","Arthur","Completed","$5.00","$0.30","XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","99947261" 4 | "May 7, 2013","Payment","From","Robert","Completed","$100.00","$3.20","XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","99997f7f-c341-4d98-9d89-87fdd78398a1" 5 | "May 7, 2013","Payment","From","Lora","Completed","$10.00","$0.59","XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","99947261" 6 | -------------------------------------------------------------------------------- /amazonpayments.py: -------------------------------------------------------------------------------- 1 | #concrete provider class for handling amazon payments csv 2 | 3 | from abstractprovider import AbstractProvider 4 | 5 | class amazonpayments(AbstractProvider): 6 | 7 | __providerID = '' 8 | __providerName = '' 9 | 10 | def __init__(self): 11 | self.__providerID = 'amazon' 12 | self.__providerName = 'Amazon Payments' 13 | 14 | def getID(self): 15 | return self.__providerID 16 | 17 | def getName(self): 18 | return self.__providerName 19 | 20 | @staticmethod 21 | def getStatus(self,row): 22 | return row.get('Status') 23 | 24 | @staticmethod 25 | def getDatePosted(self, row): 26 | return row.get('Date') 27 | 28 | @staticmethod 29 | def getTxnType(self,row): 30 | return row.get('Type') 31 | 32 | @staticmethod 33 | def getToFrom(self,row): 34 | return row.get('To/From') 35 | 36 | @staticmethod 37 | def getTxnAmount(self,row): 38 | return row.get('Amount') 39 | 40 | @staticmethod 41 | def getTxnName(self,row): 42 | return row.get('Name') 43 | 44 | -------------------------------------------------------------------------------- /abstractprovider.py: -------------------------------------------------------------------------------- 1 | #provider.py 2 | #Hides provider-specific details for the main csvtoqbo method. 3 | 4 | class AbstractProvider: 5 | #Abstract base class to be inherited by all concrete provider classes. 6 | __providerID = '' 7 | __providerName = '' 8 | 9 | def getID( self ): 10 | raise NotImplementedError( "Method getProviderID not implemented." ) 11 | 12 | def getName(self): 13 | raise NotImplementedError( "Method getName not implemented." ) 14 | 15 | @staticmethod 16 | def getStatus(self,row): 17 | raise NotImplementedError( "Method getStatus not implemented." ) 18 | 19 | @staticmethod 20 | def getDatePosted(self, row): 21 | raise NotImplementedError( "Method getDatePosted not implemented." ) 22 | 23 | @staticmethod 24 | def getTxnType(self,row): 25 | raise NotImplementedError( "Method getTxnType not implemented." ) 26 | 27 | @staticmethod 28 | def getToFrom(self,row): 29 | raise NotImplementedError( "Method getToFrom not implemented." ) 30 | 31 | @staticmethod 32 | def getTxnAmount(self,row): 33 | raise NotImplementedError( "Method getTxnAmount not implemented." ) 34 | 35 | @staticmethod 36 | def getTxnName(self,row): 37 | raise NotImplementedError( "Method getTxnName not implemented." ) 38 | 39 | -------------------------------------------------------------------------------- /qboconst.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # # 3 | # File: qboconst.py # 4 | # Developer: Justin Leto # 5 | # # 6 | # qboconst.py holds const values for QBO file format # 7 | # # 8 | # Usage: Called from qbo.py # 9 | # # 10 | ##################################################################### 11 | 12 | from datetime import date, time 13 | 14 | DATE_TODAY = "" + date.today().strftime('%Y%m%d%H%M%S') + ".000[-5]" 15 | 16 | HEADER = ("OFXHEADER:100\n" 17 | "DATA:OFXSGML\n" 18 | "VERSION:102\n" 19 | "SECURITY:NONE\n" 20 | "ENCODING:USASCII\n" 21 | "CHARSET:1252\n" 22 | "COMPRESSION:NONE\n" 23 | "OLDFILEUID:NONE\n" 24 | "NEWFILEUID:NONE\n\n" 25 | "\n" 26 | "\n" 27 | "\n" 28 | "\n" 29 | "0\n" 30 | "INFO\n" 31 | "OK\n" 32 | "\n" 33 | "" + DATE_TODAY + "\n" 34 | "ENG\n" 35 | "3000\n" 36 | "\n" 37 | "\n" 38 | "\n" 39 | "\n" 40 | "" + DATE_TODAY + "\n" 41 | "\n" 42 | "0\n" 43 | "INFO\n" 44 | "OK\n" 45 | "\n" 46 | "\n" 47 | "USD\n" 48 | "\n" 49 | "999999999\n" 50 | "999999999999\n" 51 | "CHECKING\n" 52 | "\n") 53 | 54 | FOOTER = ("\n" 55 | "0.00\n" 56 | "" + DATE_TODAY + "\n" 57 | "\n" 58 | "\n" 59 | "0.00\n" 60 | "" + DATE_TODAY + "\n" 61 | "\n" 62 | "\n" 63 | "\n" 64 | "\n" 65 | "\n") 66 | 67 | DATE_START = "" + DATE_TODAY + "\n" 68 | 69 | DATE_END = "" + DATE_TODAY + "\n" 70 | 71 | BANKTRANLIST_START = "\n" 72 | 73 | BANKTRANLIST_END = "\n" 74 | 75 | TRANSACTION_START = "" 76 | TRANSACTION_END = "" 77 | -------------------------------------------------------------------------------- /csvtoqbo.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # # 3 | # File: csvtoqbo.py # 4 | # Developer: Justin Leto # 5 | # # 6 | # main utility script file Python script to convert CSV files # 7 | # of transactions exported from various platforms to QBO for # 8 | # import into Quickbooks Online. # 9 | # # 10 | # Usage: python csvtoqbo.py # 11 | # # 12 | ##################################################################### 13 | 14 | import sys, traceback 15 | import os 16 | import logging 17 | import csv 18 | import qbo 19 | import amazonpayments 20 | 21 | # If only utility script is called 22 | if len(sys.argv) <= 1: 23 | sys.exit("Usage: python %s \n" 24 | "Where possible options include:\n" 25 | " -amazon Specify csv output is from Amazon Payments.\n" 26 | " --help Help for using this tool." % sys.argv[0] 27 | ) 28 | # If help is requested 29 | elif (sys.argv[1] == '--help'): 30 | sys.exit("Help for %s not yet implemented." % sys.argv[0]) 31 | 32 | # Test for valid options, instantiate appropiate provider object 33 | if sys.argv[1] == '-amazon': 34 | myProvider = None 35 | myProvider = amazonpayments.amazonpayments() 36 | 37 | # For each CSV file listed for conversion 38 | for arg in sys.argv: 39 | if sys.argv.index(arg) > 1: 40 | 41 | try: 42 | with open(arg[:len(arg)-3] + 'log'): 43 | os.remove(arg[:len(arg)-3] + 'log') 44 | except IOError: 45 | pass 46 | 47 | logging.basicConfig(filename=arg[:len(arg)-3] + 'log', level=logging.INFO) 48 | logging.info("Opening '%s' CSV File" % myProvider.getName()) 49 | 50 | try: 51 | 52 | with open(arg, 'r') as csvfile: 53 | 54 | # Open CSV for reading 55 | reader = csv.DictReader(csvfile, delimiter=',', quotechar='"') 56 | 57 | #instantiate the qbo object 58 | myQbo = None 59 | myQbo = qbo.qbo() 60 | txnCount = 0 61 | for row in reader: 62 | txnCount = txnCount+1 63 | sdata = str(row) 64 | 65 | #read in values from row of csv file 66 | status = myProvider.getStatus(myProvider,row) 67 | date_posted = myProvider.getDatePosted(myProvider,row) 68 | txn_type = myProvider.getTxnType(myProvider,row) 69 | to_from_flag = myProvider.getToFrom(myProvider,row) 70 | txn_amount = myProvider.getTxnAmount(myProvider,row) 71 | name = myProvider.getTxnName(myProvider,row) 72 | 73 | try: 74 | #Add transaction to the qbo document 75 | if myQbo.addTransaction(status, date_posted, txn_type, to_from_flag, txn_amount, name): 76 | print('Transaction [' + str(txnCount) + '] added successfully!') 77 | logging.info('Transaction [' + str(txnCount) + '] added successfully!') 78 | 79 | except: 80 | #Error adding transaction 81 | exc_type, exc_value, exc_traceback = sys.exc_info() 82 | lines = traceback.format_exception(exc_type, exc_value, exc_traceback) 83 | print(''.join('!! ' + line for line in lines)) 84 | logging.info("Transaction [" + str(txnCount) + "] excluded!") 85 | logging.info('>> Data: ' + str(sdata)) 86 | pass 87 | 88 | except: 89 | exc_type, exc_value, exc_traceback = sys.exc_info() 90 | lines = traceback.format_exception(exc_type, exc_value, exc_traceback) 91 | print(''.join('!! ' + line for line in lines)) 92 | logging.info("Trouble reading CSV file!") 93 | 94 | # After transactions have been read, write full QBO document to file 95 | try: 96 | filename = arg[:len(arg)-3] + 'qbo' 97 | if myQbo.Write('./'+ filename): 98 | print("QBO file written successfully!") 99 | #log successful write 100 | logging.info("QBO file %s written successfully!" % filename) 101 | 102 | except: 103 | #IO Error 104 | exc_type, exc_value, exc_traceback = sys.exc_info() 105 | lines = traceback.format_exception(exc_type, exc_value, exc_traceback) 106 | print(''.join('!! ' + line for line in lines)) 107 | logging.info(''.join('!! ' + line for line in lines)) 108 | -------------------------------------------------------------------------------- /csvtoqbo-test.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # # 3 | # File: csvtoqbo.py # 4 | # Developer: Justin Leto # 5 | # # 6 | # csvtoqbo-test.py is the file for unit testing the # 7 | # csvtoqbo.py utility. # 8 | # # 9 | # Usage: python csvtoqbo.py # 10 | # # 11 | ##################################################################### 12 | 13 | import os 14 | import unittest 15 | import qbo 16 | import qboconst 17 | import amazonpayments 18 | import cStringIO 19 | from datetime import date 20 | 21 | class csvtoqboTest(unittest.TestCase): 22 | 23 | # Initialize qbo object and ensure constant values pulled in from file 24 | def testInit(self): 25 | myQbo = qbo.qbo() 26 | self.assertEquals(myQbo.getHEADER(), qboconst.HEADER) 27 | self.assertEquals(myQbo.getFOOTER(), qboconst.FOOTER) 28 | self.assertEquals(myQbo.getDATE_START(), qboconst.DATE_START) 29 | self.assertEquals(myQbo.getDATE_END(), qboconst.DATE_END) 30 | self.assertEquals(myQbo.getBANKTRANLIST_START(), qboconst.BANKTRANLIST_START) 31 | self.assertEquals(myQbo.getBANKTRANLIST_END(), qboconst.BANKTRANLIST_END) 32 | self.assertEquals(myQbo.getTRANSACTION_START(), qboconst.TRANSACTION_START) 33 | self.assertEquals(myQbo.getTRANSACTION_END(), qboconst.TRANSACTION_END) 34 | 35 | # Add a valid transaction through the qbo.addTransaction() method 36 | def testAddTransaction(self): 37 | myQbo = None 38 | myQbo = qbo.qbo() 39 | 40 | status = 'Completed' 41 | date_posted = str(date.today().strftime('%b %d, %Y')) 42 | memo = 'AddTransactionTest' 43 | txn_type = 'Payment' 44 | to_from_flag = 'From' 45 | txn_amount = '1.00' 46 | name = 'TestBuy' 47 | 48 | self.assertEquals(myQbo.addTransaction(status, date_posted, txn_type, to_from_flag, txn_amount, name), True) 49 | self.assertEquals(myQbo.getCount(), 1) 50 | 51 | # Compare size of built document against file size known at development time 52 | def testBuild(self): 53 | myQbo = None 54 | myQbo = qbo.qbo() 55 | 56 | status = 'Completed' 57 | date_posted = str(date.today().strftime('%b %d, %Y')) 58 | memo = 'AddTransactionTest' 59 | txn_type = 'Payment' 60 | to_from_flag = 'From' 61 | txn_amount = '1.00' 62 | name = 'TestBuy' 63 | 64 | self.assertEquals(myQbo.addTransaction(status, date_posted, txn_type, to_from_flag, txn_amount, name), True) 65 | self.assertEquals(len(myQbo.getDocument()), 1137) 66 | 67 | # Writing document of known size to file 68 | def testWrite(self): 69 | myQbo = None 70 | myQbo = qbo.qbo() 71 | 72 | status = 'Completed' 73 | date_posted = str(date.today().strftime('%b %d, %Y')) 74 | memo = 'AddTransactionTest' 75 | txn_type = 'Payment' 76 | to_from_flag = 'From' 77 | txn_amount = '1.00' 78 | name = 'TestBuy' 79 | 80 | self.assertEquals(myQbo.addTransaction(status, date_posted, txn_type, to_from_flag, txn_amount, name), True) 81 | self.assertEquals(myQbo.Write('./csvtoqbo-test.qbo'), True) 82 | statinfo = os.stat('./csvtoqbo-test.qbo') 83 | self.assertEquals(statinfo.st_size, 1281) 84 | 85 | # Provider ID is set correctly on intialization 86 | def testProviderID(self): 87 | myProvider = amazonpayments.amazonpayments() 88 | self.assertEquals(myProvider.getID(), 'amazon') 89 | self.assertEquals(myProvider.getName(), 'Amazon Payments') 90 | 91 | # QBO class get functions for transaction method 92 | def testProviderGetters(self): 93 | myProvider = amazonpayments.amazonpayments() 94 | myDict = {'Status' : 'Completed', 95 | 'Date' : 'May 8, 2013', 96 | 'Type' : 'Payment', 97 | 'To/From' : 'From', 98 | 'Amount' : '1.00', 99 | 'Name' : 'TestBuy'} 100 | self.assertEquals(myProvider.getStatus(myProvider,myDict), 'Completed') 101 | self.assertEquals(myProvider.getDatePosted(myProvider,myDict), 'May 8, 2013') 102 | self.assertEquals(myProvider.getTxnType(myProvider, myDict), 'Payment') 103 | self.assertEquals(myProvider.getToFrom(myProvider, myDict), 'From') 104 | self.assertEquals(myProvider.getTxnAmount(myProvider, myDict), '1.00') 105 | self.assertEquals(myProvider.getTxnName(myProvider, myDict), 'TestBuy') 106 | 107 | # Test against command line with sample amazon test csv file 108 | def testCommandLineSampleCSVFile(self): 109 | 110 | name = 'Amazon-CSV-Test' 111 | csvname = name + '.csv' 112 | logname = name + '.log' 113 | try: 114 | with open(logname): 115 | os.remove(logname) 116 | except IOError: 117 | pass 118 | 119 | os.system('python csvtoqbo.py -amazon %s' % csvname) 120 | assert os.path.exists(logname) 121 | 122 | with open(logname) as f: 123 | content = f.readlines() 124 | if not "written successfully" in content[len(content)-1]: 125 | print(content) 126 | self.assertEquals(True, False) 127 | 128 | # main method for running unit tests 129 | if __name__ == '__main__': 130 | unittest.main() 131 | -------------------------------------------------------------------------------- /qbo.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # # 3 | # File: qbo.py # 4 | # Developer: Justin Leto # 5 | # # 6 | # qbo class provides an interface from main csv iterator method # 7 | # to handle qbo formatting, validations, and writing to file. # 8 | # # 9 | # Usage: python csvtoqbo.py # 10 | # # 11 | ##################################################################### 12 | 13 | import sys, traceback 14 | import os 15 | from datetime import datetime 16 | import logging 17 | import qboconst 18 | 19 | class qbo: 20 | 21 | # Holds a list of valid transactions via the addTransaction() method 22 | __transactions = list() 23 | 24 | # The full QBO document build from constants and transactions 25 | __document = None 26 | 27 | # Flag indicating whether the QBO document is valid 28 | __isValid = None 29 | 30 | # constructor 31 | def __init__(self): 32 | # Reads in constant values from file, set to private (const) variables 33 | self.__HEADER = qboconst.HEADER 34 | self.__FOOTER = qboconst.FOOTER 35 | self.__DATE_START = qboconst.DATE_START 36 | self.__DATE_END = qboconst.DATE_END 37 | self.__BANKTRANLIST_START = qboconst.BANKTRANLIST_START 38 | self.__BANKTRANLIST_END = qboconst.BANKTRANLIST_END 39 | self.__TRANSACTION_START = qboconst.TRANSACTION_START 40 | self.__TRANSACTION_END = qboconst.TRANSACTION_END 41 | 42 | # Set document to valid 43 | self.__isValid = True 44 | 45 | 46 | # PUBLIC GET METHODS for constant values - used in unit testing. 47 | # 48 | # 49 | def getHEADER(self): 50 | return self.__HEADER 51 | 52 | def getFOOTER(self): 53 | return self.__FOOTER 54 | 55 | def getDATE_START(self): 56 | return self.__DATE_START 57 | 58 | def getDATE_END(self): 59 | return self.__DATE_END 60 | 61 | def getBANKTRANLIST_START(self): 62 | return self.__BANKTRANLIST_START 63 | 64 | def getBANKTRANLIST_END(self): 65 | return self.__BANKTRANLIST_END 66 | 67 | def getTRANSACTION_START(self): 68 | return self.__TRANSACTION_START 69 | 70 | def getTRANSACTION_END(self): 71 | return self.__TRANSACTION_END 72 | 73 | # method to validate paramters used to submit transactions 74 | def validateTransaction(self, status, date_posted, txn_type, to_from_flag, txn_amount, name): 75 | 76 | if str.lower(status) != 'completed': 77 | #log status failure 78 | logging.info("Transaction status [" + status + "] invalid.") 79 | raise Exception("Transaction status [" + status + "] invalid.") 80 | 81 | #if type(datetime.strptime(str(date_posted), '%m/%d/%Y')) is not datetime: 82 | # logging.info("Transaction posted date [" + date_posted + "] invalid.") 83 | # raise Exception("Transaction posted date [" + date_posted + "] invalid.") 84 | 85 | if str.lower(txn_type) not in ('payment','refund','withdrawal', 'withdraw funds'): 86 | logging.info("Transaction type [" + str(txn_type) + "] not 'Payment', 'Refund', 'Withdraw Funds', or 'Withdrawal'.") 87 | raise Exception("Transaction type [" + str(txn_type) + "] not 'Payment', 'Refund', 'Withdraw Funds', or 'Withdrawal'.") 88 | 89 | if str.lower(to_from_flag) not in ('to', 'from'): 90 | logging.info("Transaction 'To/From' field [" + to_from_flag + "] invalid.") 91 | raise Exception("Transaction 'To/From' field [" + to_from_flag + "] invalid.") 92 | 93 | #logical test of txn_type and to_from_flag 94 | if ((str.lower(txn_type) == 'refund' and str.lower(to_from_flag) != 'to') or (str.lower(txn_type) == 'payment' and str.lower(to_from_flag) != 'from')): 95 | logging.info("Transaction type inconsistent with 'To/From' field.") 96 | raise Exception("Transaction type inconsistent with 'To/From' field.") 97 | 98 | if len(name) == 0 or not name: 99 | logging.info("Transaction name empty or null.") 100 | raise Exception("Transaction name empty or null.") 101 | 102 | return True 103 | 104 | # Add transaction takes in param values uses the required formatting QBO transactions 105 | # and pushes to list 106 | def addTransaction(self, status, date_posted, txn_type, to_from_flag, txn_amount, name): 107 | 108 | try: 109 | # Validating param values prior to committing transaction 110 | self.validateTransaction(status, date_posted, txn_type, to_from_flag, txn_amount, name) 111 | except: 112 | raise Exception 113 | 114 | # Construct QBO formatted transaction 115 | transaction = "" 116 | 117 | day = "" 118 | month = "" 119 | date_array = date_posted.split('/') 120 | day = date_array[0] 121 | month = date_array[1] 122 | if len(day) == 1: 123 | day = "0"+day 124 | if len(month) ==1: 125 | month = "0"+month 126 | 127 | rec_date = datetime.strptime(day+"/"+month+"/"+date_array[2], '%m/%d/%Y') 128 | rec_date = rec_date.strftime('%Y%m%d%H%M%S') + '.000[-5]' 129 | 130 | dtposted = '' + rec_date 131 | memo = '' + str(txn_type) 132 | 133 | if str.lower(txn_type) == 'payment': 134 | trtype = 'CREDIT' 135 | elif (str.lower(txn_type) == 'refund' and str.lower(to_from_flag) == 'to') or (str.lower(txn_type) in ('withdrawal','withdraw funds')): 136 | trtype = 'DEBIT' 137 | 138 | if str.lower(txn_type) in ('refund', 'withdrawal', 'withdraw funds'): 139 | tramt = '-' + str(txn_amount).replace('$','') 140 | else: 141 | tramt = '' + str(txn_amount).replace('$','') 142 | 143 | #tramt = '' + str(txn_amount).replace('$','') 144 | 145 | trname = '' + str(name) 146 | fitid = '' + rec_date + str(1000+len(self.__transactions))[1:] 147 | 148 | transaction = ("" + self.__TRANSACTION_START + "\n" 149 | "" + trtype + "\n" 150 | "" + dtposted + "\n" 151 | "" + tramt + "\n" 152 | "" + fitid + "\n" 153 | "" + trname + "\n" 154 | "" + memo + "\n" 155 | "" + self.__TRANSACTION_END + "\n") 156 | 157 | # Commit transaction to the document by adding to private member list object 158 | self.__transactions.append(transaction) 159 | 160 | logging.info("Transaction [" + str(self.getCount()) + "] Accepted.") 161 | return True 162 | 163 | # get the current number of valid committed transactions 164 | def getCount(self): 165 | return len(self.__transactions) 166 | 167 | # get the valid status of the document 168 | def isValid(self): 169 | # If number of valid transactions are 0 document is invalid 170 | if self.getCount() == 0: 171 | self.__isValid = False 172 | return self.__isValid 173 | 174 | # get the text of the document 175 | def getDocument(self): 176 | self.Build() 177 | return self.__document 178 | 179 | # Construct the document, add the transactions 180 | # save str into private member variable __document 181 | def Build(self): 182 | if not self.isValid(): 183 | logging.info("Error: QBO document is not valid.") 184 | raise Exception("Error: QBO document is not valid.") 185 | 186 | self.__document = ("" + self.__HEADER + "\n" 187 | "" + self.__BANKTRANLIST_START + "\n" 188 | "" + self.__DATE_START + "\n" 189 | "" + self.__DATE_END + "\n") 190 | 191 | for txn in self.__transactions: 192 | self.__document = self.__document + str(txn) 193 | 194 | self.__document = self.__document + ("" + self.__BANKTRANLIST_END + "\n" 195 | "" + self.__FOOTER + "") 196 | 197 | # Write QBO document to file 198 | def Write(self, filename): 199 | 200 | try: 201 | 202 | with open(filename, 'w') as f: 203 | # getDocument method will build document 204 | # test for validity and return string for write 205 | f.write(self.getDocument()) 206 | 207 | return True 208 | 209 | except: 210 | #log io error return False 211 | exc_type, exc_value, exc_traceback = sys.exc_info() 212 | lines = traceback.format_exception(exc_type, exc_value, exc_traceback) 213 | print(''.join('!! ' + line for line in lines)) 214 | logging.info('qbo.Write() method: '.join('!! ' + line for line in lines)) 215 | return False 216 | --------------------------------------------------------------------------------