1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# Author: Joel Grand-Guillaume
5
# Copyright 2011-2012 Camptocamp SA
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU Affero General Public License as
9
# published by the Free Software Foundation, either version 3 of the
10
# License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Affero General Public License for more details.
17
# You should have received a copy of the GNU Affero General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
##############################################################################
24
def UnicodeDictReader(utf8_data, **kwargs):
25
csv_reader = csv.DictReader(utf8_data, **kwargs)
26
for row in csv_reader:
27
yield dict([(key, unicode(value, 'utf-8')) for key, value in row.iteritems()])
29
class BankStatementImportParser(object):
31
Generic abstract class for defining parser for different files and
32
format to import in a bank statement. Inherit from it to create your
33
own. If your file is a .csv or .xls format, you should consider inheirt
34
from the FileParser instead.
37
def __init__(self, parser_name, *args, **kwargs):
38
# The name of the parser as it will be called
39
self.parser_name = parser_name
40
# The result as a list of row. One row per line of data in the file, but
41
# not the commission one !
42
self.result_row_list = None
43
# The file buffer on which to work on
44
self.filebuffer = None
45
# Concatenate here the global commission taken by the bank/office
47
self.commission_global_amount = None
50
def parser_for(cls, parser_name):
52
Override this method for every new parser, so that new_bank_statement_parser can
53
return the good class from his name.
57
def _decode_64b_stream(self):
59
Decode self.filebuffer in base 64 and override it
61
self.filebuffer = base64.b64decode(self.filebuffer)
64
def _format(self, decode_base_64=True, **kwargs):
66
Decode into base 64 if asked and Format the given filebuffer by calling
67
_custom_format method.
70
self._decode_64b_stream()
71
self._custom_format(kwargs)
74
def _custom_format(self, *args, **kwargs):
76
Implement a method in your parser to convert format, encoding and so on before
77
starting to work on datas. Work on self.filebuffer
79
return NotImplementedError
82
def _pre(self, *args, **kwargs):
84
Implement a method in your parser to make a pre-treatment on datas before parsing
85
them, like concatenate stuff, and so... Work on self.filebuffer
87
return NotImplementedError
89
def _parse(self, *args, **kwargs):
91
Implement a method in your parser to save the result of parsing self.filebuffer
92
in self.result_row_list instance property.
94
return NotImplementedError
96
def _validate(self, *args, **kwargs):
98
Implement a method in your parser to validate the self.result_row_list instance
99
property and raise an error if not valid.
101
return NotImplementedError
103
def _post(self, *args, **kwargs):
105
Implement a method in your parser to make some last changes on the result of parsing
106
the datas, like converting dates, computing commission, ...
107
Work on self.result_row_list and put the commission global amount if any
108
in the self.commission_global_amount one.
110
return NotImplementedError
114
def get_st_line_vals(self, line, *args, **kwargs):
116
Implement a method in your parser that must return a dict of vals that can be
117
passed to create method of statement line in order to record it. It is the responsibility
118
of every parser to give this dict of vals, so each one can implement his
119
own way of recording the lines.
120
:param: line: a dict of vals that represent a line of result_row_list
121
:return: dict of values to give to the create method of statement line,
122
it MUST contain at least:
130
return NotImplementedError
132
def get_st_line_commision(self, *args, **kwargs):
134
This is called by the importation method to create the commission line in
135
the bank statement. We will always create one line for the commission in the
136
bank statement, but it could be computated from a value of each line, or given
137
in a single line for the whole file.
138
return: float of the whole commission (self.commission_global_amount)
140
return self.commission_global_amount
142
def parse(self, filebuffer, *args, **kwargs):
144
This will be the method that will be called by wizard, button and so
145
to parse a filebuffer by calling successively all the private method
146
that need to be define for each parser.
148
[] of rows as {'key':value}
150
Note: The row_list must contain only value that are present in the account.
151
bank.statement.line object !!!
154
self.filebuffer = filebuffer
156
raise Exception(_('No buffer file given.'))
157
self._format(*args, **kwargs)
158
self._pre(*args, **kwargs)
159
self._parse(*args, **kwargs)
160
self._validate(*args, **kwargs)
161
self._post(*args, **kwargs)
162
return self.result_row_list
164
def itersubclasses(cls, _seen=None):
168
Generator over all subclasses of a given class, in depth first order.
170
>>> list(itersubclasses(int)) == [bool]
172
>>> class A(object): pass
175
>>> class D(B,C): pass
178
>>> for cls in itersubclasses(A):
179
... print(cls.__name__)
184
>>> # get ALL (new-style) classes currently defined
185
>>> [cls.__name__ for cls in itersubclasses(object)] #doctest: +ELLIPSIS
186
['type', ...'tuple', ...]
188
if not isinstance(cls, type):
189
raise TypeError('itersubclasses must be called with '
190
'new-style classes, not %.100r' % cls)
191
if _seen is None: _seen = set()
193
subs = cls.__subclasses__()
194
except TypeError: # fails only when cls is type
195
subs = cls.__subclasses__(cls)
200
for sub in itersubclasses(sub, _seen):
203
def new_bank_statement_parser(parser_name, *args, **kwargs):
205
Return an instance of the good parser class base on the providen name
206
:param char: parser_name
207
:return: class instance of parser_name providen.
209
for cls in itersubclasses(BankStatementImportParser):
210
if cls.parser_for(parser_name):
211
return cls(parser_name, *args, **kwargs)
b'\\ No newline at end of file'