diff -Nru ofxstatement-plugins-20191113/debian/changelog ofxstatement-plugins-20191114/debian/changelog --- ofxstatement-plugins-20191113/debian/changelog 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/debian/changelog 2019-11-14 09:40:41.000000000 +0000 @@ -1,3 +1,14 @@ +ofxstatement-plugins (20191114) unstable; urgency=medium + + * Call pytest on plugin test. + * wrap-and-sort on debian/ + * Update submodules: + - ofxstatement-russian + - ofxstatement-bubbas + * Disable ofxstatement-paypal (it conflicts with ofxstatement-polish). + + -- Alexander GQ Gerasiov Thu, 14 Nov 2019 12:40:41 +0300 + ofxstatement-plugins (20191113) unstable; urgency=medium * ofxstatement-1822direkt renamed to ofxstatement-germany diff -Nru ofxstatement-plugins-20191113/debian/control ofxstatement-plugins-20191114/debian/control --- ofxstatement-plugins-20191113/debian/control 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/debian/control 2019-11-13 19:25:43.000000000 +0000 @@ -2,13 +2,19 @@ Section: misc Priority: optional Maintainer: Alexander GQ Gerasiov -Build-Depends: debhelper (>= 9), quilt, dh-python, python3-all, python3-setuptools, - ofxstatement, - python3-appdirs, - python3-mock, - python3-click, - python3-openpyxl, - python3-xlrd, +Build-Depends: debhelper (>= 9), + dh-python, + ofxstatement, + python3-all, + python3-appdirs, + python3-click, + python3-mock, + python3-openpyxl, + python3-pytest, + python3-pytest-runner, + python3-setuptools, + python3-xlrd, + quilt Standards-Version: 4.1.1 Homepage: https://github.com/gerasiov/ofxstatement-plugins Vcs-Git: https://github.com/gerasiov/ofxstatement-plugins @@ -16,7 +22,7 @@ Package: ofxstatement-plugins Architecture: all -Depends: ${python3:Depends}, ${misc:Depends} +Depends: ${misc:Depends}, ${python3:Depends} Description: set of plugins for ofxstatement Most internet banking systems are capable of exporting account transactions to some sort of computer readable formats, but few supports standard data formats, diff -Nru ofxstatement-plugins-20191113/debian/copyright ofxstatement-plugins-20191114/debian/copyright --- ofxstatement-plugins-20191113/debian/copyright 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/debian/copyright 2019-11-14 09:40:21.000000000 +0000 @@ -47,7 +47,7 @@ 2013, Mirko Dziadzka 2013-2015, bubbas License: AGPL-3 - + Files: ofxstatement-czech/* Copyright: 2013, Andrey Lebedev @@ -76,7 +76,7 @@ 2016, Zoltan Nagy License: AGPL-3 -Files: ofxstatement-paypal/* +Files: _ofxstatement-paypal/* Copyright: 2016, Alexander Krasnukhin License: Apache-2.0 diff -Nru ofxstatement-plugins-20191113/debian/patches/01-fix-autotests.patch ofxstatement-plugins-20191114/debian/patches/01-fix-autotests.patch --- ofxstatement-plugins-20191113/debian/patches/01-fix-autotests.patch 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/debian/patches/01-fix-autotests.patch 2019-11-14 09:40:21.000000000 +0000 @@ -12,16 +12,6 @@ include_package_data=True, zip_safe=True ) ---- a/ofxstatement-bubbas/src/ofxstatement/plugins/lbbamazon.py -+++ b/ofxstatement-bubbas/src/ofxstatement/plugins/lbbamazon.py -@@ -7,6 +7,7 @@ - class LbbAmazonCsvStatementParser(CsvStatementParser): - mappings = {"date": 1, "memo": 3, "amount": 6} - date_format = "%d.%m.%Y" -+ ignore_amazon_points = False - - def split_records(self): - return csv.reader(self.fin, delimiter=';') --- a/ofxstatement-intesasp/setup.py +++ b/ofxstatement-intesasp/setup.py @@ -36,7 +36,7 @@ @@ -59,8 +49,8 @@ include_package_data=True, zip_safe=True ) ---- a/ofxstatement-paypal/setup.py -+++ b/ofxstatement-paypal/setup.py +--- a/_ofxstatement-paypal/setup.py ++++ b/_ofxstatement-paypal/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- diff -Nru ofxstatement-plugins-20191113/debian/patches/03-fix-pytest.patch ofxstatement-plugins-20191114/debian/patches/03-fix-pytest.patch --- ofxstatement-plugins-20191113/debian/patches/03-fix-pytest.patch 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/debian/patches/03-fix-pytest.patch 2019-11-13 19:20:37.000000000 +0000 @@ -0,0 +1,40 @@ +--- a/ofxstatement-czech/src/ofxstatement/plugins/tests/test_gpc.py ++++ b/ofxstatement-czech/src/ofxstatement/plugins/tests/test_gpc.py +@@ -10,7 +10,7 @@ + pass + + +-def test_generator(test_file_name): ++def generate_test(test_file_name): + def test_parser(self): + with open(test_file_name, 'rb') as test_file: + parser = GPCParser(test_file) +@@ -43,7 +43,7 @@ + for t in tests: + test_base = os.path.splitext(os.path.split(t)[1])[0] + test_core = 'test_%s' % test_base +- tests = test_generator(t) ++ tests = generate_test(t) + for test in zip(['parser', 'parseLine'], tests): + test_name = "{}_{}".format(test_core, test[0]) + setattr(TestParser, test_name, test[1]) +--- a/ofxstatement-czech/src/ofxstatement/plugins/tests/test_maxibps.py ++++ b/ofxstatement-czech/src/ofxstatement/plugins/tests/test_maxibps.py +@@ -8,7 +8,7 @@ + pass + + +-def test_generator(test_file_name): ++def generate_test(test_file_name): + def test_parser(self): + with open(test_file_name, "U", encoding="utf-8-sig") as test_file: + parser = PSTextFormatParser(test_file) +@@ -35,7 +35,7 @@ + for t in tests: + test_base = os.path.splitext(os.path.split(t)[1])[0] + test_core = 'test_%s' % test_base +- tests = test_generator(t) ++ tests = generate_test(t) + for test in zip(['parser', 'parseLine'], tests): + test_name = "{}_{}".format(test_core, test[0]) + setattr(TestParser, test_name, test[1]) diff -Nru ofxstatement-plugins-20191113/debian/patches/series ofxstatement-plugins-20191114/debian/patches/series --- ofxstatement-plugins-20191113/debian/patches/series 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/debian/patches/series 2019-11-13 19:19:27.000000000 +0000 @@ -1,2 +1,3 @@ 01-fix-autotests.patch 02-be-argenta-do-not-install-script.patch +03-fix-pytest.patch diff -Nru ofxstatement-plugins-20191113/debian/rules ofxstatement-plugins-20191114/debian/rules --- ofxstatement-plugins-20191113/debian/rules 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/debian/rules 2019-11-14 09:23:50.000000000 +0000 @@ -30,6 +30,11 @@ plugin_test_% : % cd $< && python3 setup.py test + # run pytest with doctest and ignore exit code 5 (no tests found) + cd $< && PYTEST_ADDOPTS=--doctest-modules python3 setup.py pytest; \ + PYTEST_RESULT=$$?; \ + test $$PYTEST_RESULT -eq 5 && exit 0; \ + exit $$PYTEST_RESULT override_dh_auto_clean: $(addprefix plugin_clean_,$(PLUGINS)) Binary files /tmp/tmpgcm9tP/Em2DCkvQur/ofxstatement-plugins-20191113/ofxstatement-be-argenta/ofxstatement/plugins/__pycache__/__init__.cpython-37.pyc and /tmp/tmpgcm9tP/jpN5QLcHad/ofxstatement-plugins-20191114/ofxstatement-be-argenta/ofxstatement/plugins/__pycache__/__init__.cpython-37.pyc differ Binary files /tmp/tmpgcm9tP/Em2DCkvQur/ofxstatement-plugins-20191113/ofxstatement-be-argenta/ofxstatement/__pycache__/__init__.cpython-37.pyc and /tmp/tmpgcm9tP/jpN5QLcHad/ofxstatement-plugins-20191114/ofxstatement-be-argenta/ofxstatement/__pycache__/__init__.cpython-37.pyc differ Binary files /tmp/tmpgcm9tP/Em2DCkvQur/ofxstatement-plugins-20191113/ofxstatement-betterment/ofxstatement/plugins/__pycache__/__init__.cpython-37.pyc and /tmp/tmpgcm9tP/jpN5QLcHad/ofxstatement-plugins-20191114/ofxstatement-betterment/ofxstatement/plugins/__pycache__/__init__.cpython-37.pyc differ Binary files /tmp/tmpgcm9tP/Em2DCkvQur/ofxstatement-plugins-20191113/ofxstatement-betterment/ofxstatement/__pycache__/__init__.cpython-37.pyc and /tmp/tmpgcm9tP/jpN5QLcHad/ofxstatement-plugins-20191114/ofxstatement-betterment/ofxstatement/__pycache__/__init__.cpython-37.pyc differ diff -Nru ofxstatement-plugins-20191113/ofxstatement-bubbas/src/ofxstatement/plugins/lbbamazon.py ofxstatement-plugins-20191114/ofxstatement-bubbas/src/ofxstatement/plugins/lbbamazon.py --- ofxstatement-plugins-20191113/ofxstatement-bubbas/src/ofxstatement/plugins/lbbamazon.py 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-bubbas/src/ofxstatement/plugins/lbbamazon.py 2019-11-14 09:40:31.000000000 +0000 @@ -7,6 +7,7 @@ class LbbAmazonCsvStatementParser(CsvStatementParser): mappings = {"date": 1, "memo": 3, "amount": 6} date_format = "%d.%m.%Y" + ignore_amazon_points = False def split_records(self): return csv.reader(self.fin, delimiter=';') diff -Nru ofxstatement-plugins-20191113/_ofxstatement-paypal/convert.py ofxstatement-plugins-20191114/_ofxstatement-paypal/convert.py --- ofxstatement-plugins-20191113/_ofxstatement-paypal/convert.py 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/_ofxstatement-paypal/convert.py 2019-11-14 09:40:31.000000000 +0000 @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os.path +import click +import logging + +from ofxstatement.ofx import OfxWriter +from ofxstatement.plugins import paypal + + +@click.command() +@click.argument('path') +@click.option('--debug', is_flag=True, default=False) +def convert(path, debug): + """Parse and print transactions from PayPal's *.csv files.""" + + logging.basicConfig(level=logging.INFO, format='[%(levelname)s] %(message)s') + + root, ext = os.path.splitext(path) + output_file = root + '.ofx' + + parser = paypal.PayPalPlugin(ui=None, settings=None).get_parser(path) + statement = parser.parse() + + if debug: + for line in statement.lines: + print(line) + return + + with open(output_file, 'w') as out: + writer = OfxWriter(statement) + out.write(writer.toxml()) + + +if __name__ == '__main__': + convert() diff -Nru ofxstatement-plugins-20191113/_ofxstatement-paypal/LICENSE ofxstatement-plugins-20191114/_ofxstatement-paypal/LICENSE --- ofxstatement-plugins-20191113/_ofxstatement-paypal/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/_ofxstatement-paypal/LICENSE 2019-11-14 09:40:31.000000000 +0000 @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff -Nru ofxstatement-plugins-20191113/_ofxstatement-paypal/ofxstatement/__init__.py ofxstatement-plugins-20191114/_ofxstatement-paypal/ofxstatement/__init__.py --- ofxstatement-plugins-20191113/_ofxstatement-paypal/ofxstatement/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/_ofxstatement-paypal/ofxstatement/__init__.py 2019-11-14 09:40:31.000000000 +0000 @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file diff -Nru ofxstatement-plugins-20191113/_ofxstatement-paypal/ofxstatement/plugins/__init__.py ofxstatement-plugins-20191114/_ofxstatement-paypal/ofxstatement/plugins/__init__.py --- ofxstatement-plugins-20191113/_ofxstatement-paypal/ofxstatement/plugins/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/_ofxstatement-paypal/ofxstatement/plugins/__init__.py 2019-11-14 09:40:31.000000000 +0000 @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file diff -Nru ofxstatement-plugins-20191113/_ofxstatement-paypal/ofxstatement/plugins/paypal.py ofxstatement-plugins-20191114/_ofxstatement-paypal/ofxstatement/plugins/paypal.py --- ofxstatement-plugins-20191113/_ofxstatement-paypal/ofxstatement/plugins/paypal.py 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/_ofxstatement-paypal/ofxstatement/plugins/paypal.py 2019-11-14 09:40:31.000000000 +0000 @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- + +import locale +import itertools +import csv + +from datetime import datetime + +from contextlib import contextmanager +from ofxstatement.parser import StatementParser +from ofxstatement.plugin import Plugin +from ofxstatement.statement import Statement, StatementLine, generate_transaction_id + + +def take(iterable, n): + """Return first n items of the iterable as a list.""" + return list(itertools.islice(iterable, n)) + + +def drop(iterable, n): + """Drop first n items of the iterable and return result as a list.""" + return list(itertools.islice(iterable, n, None)) + + +def head(iterable): + """Return first element of the iterable.""" + return take(iterable, 1)[0] + + +@contextmanager +def scoped_setlocale(category, loc=None): + """Scoped version of locale.setlocale()""" + orig = locale.getlocale(category) + try: + yield locale.setlocale(category, loc) + finally: + locale.setlocale(category, orig) + + +def atof(string, loc=None): + """Locale aware atof function for our parser.""" + with scoped_setlocale(locale.LC_NUMERIC, loc): + return locale.atof(string) + + +class PayPalStatementParser(StatementParser): + bank_id = 'PayPal' + date_format = '%Y/%m/%d' + valid_header = [ + u"Date", + u"Time", + u"Time Zone", + u"Name", + u"Type", + u"Status", + u"Currency", + u"Gross", + u"Fee", + u"Net", + u"From Email Address", + u"To Email Address", + u"Transaction ID", + u"Counterparty Status", + u"Address Status", + u"Item Title", + u"Item ID", + u"Shipping and Handling Amount", + u"Insurance Amount", + u"Sales Tax", + u"Option 1 Name", + u"Option 1 Value", + u"Option 2 Name", + u"Option 2 Value", + u"Auction Site", + u"Buyer ID", + u"Item URL", + u"Closing Date", + u"Escrow Id", + u"Invoice Id", + u"Reference Txn ID", + u"Invoice Number", + u"Custom Number", + u"Receipt ID", + u"Balance", + u"Address Line 1", + u"Address Line 2/District/Neighborhood", + u"Town/City", + u"State/Province/Region/County/Territory/Prefecture/Republic", + u"Zip/Postal Code", + u"Country", + u"Contact Phone Number", + u"", + ] + + def __init__(self, fin, account_id, currency, encoding=None, locale=None, analyze=False): + self.account_id = account_id + self.currency = currency + self.locale = locale + self.encoding = encoding + self.analyze = analyze + + with open(fin, 'r', encoding=self.encoding) as f: + self.lines = f.readlines() + + self.validate() + self.statement = Statement( + bank_id=self.bank_id, + account_id=self.account_id, + currency=self.currency + ) + + @property + def reader(self): + return csv.reader(self.lines, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL) + + @property + def header(self): + return [c.strip() for c in head(self.reader)] + + @property + def rows(self): + rs = drop(self.reader, 1) + currency_idx = self.valid_header.index("Currency") + return [r for r in rs if r[currency_idx] == self.currency] + + def validate(self): + """ + Validate to ensure csv has the same header we expect. + """ + + expected = self.valid_header + actual = self.header + if expected != actual: + msg = "\n".join([ + "Header template doesn't match:", + "expected: %s" % expected, + "actual : %s" % actual + ]) + raise ValueError(msg) + + def split_records(self): + for row in self.rows: + yield row + + def parse_record(self, row): + + id_idx = self.valid_header.index("Transaction ID") + date_idx = self.valid_header.index("Date") + memo_idx = self.valid_header.index("Name") + refnum_idx = self.valid_header.index("Reference Txn ID") + amount_idx = self.valid_header.index("Gross") + payee_idx = self.valid_header.index("To Email Address") + title_idx = self.valid_header.index("Item Title") + + stmt_line = StatementLine() + stmt_line.id = row[id_idx] + stmt_line.date = datetime.strptime(row[date_idx], self.date_format) + stmt_line.memo = row[memo_idx] + + if self.analyze: + memo_parts = [ + row[memo_idx] + ] + + payee = row[payee_idx] + if payee and (payee.lower() == 'steamgameseu@steampowered.com'): + memo_parts.append(row[title_idx]) + + stmt_line.memo = ' / '.join(filter(bool, memo_parts)) + + stmt_line.refnum = row[refnum_idx] + stmt_line.amount = atof(row[amount_idx].replace(" ", ""), self.locale) + + return stmt_line + + +def parse_bool(value): + if value in ('True', 'true', '1'): + return True + if value in ('False', 'false', '0'): + return False + raise ValueError("Can't parse boolean value: %s" % value) + + +class PayPalPlugin(Plugin): + def get_parser(self, fin): + kwargs = { + 'encoding': 'iso8859-1', + } + if self.settings: + if 'account_id' in self.settings: + kwargs['account_id'] = self.settings.get('account_id') + if 'currency' in self.settings: + kwargs['currency'] = self.settings.get('currency') + if 'locale' in self.settings: + kwargs['locale'] = self.settings.get('locale') + if 'encoding' in self.settings: + kwargs['encoding'] = self.settings.get('encoding') + if 'analyze' in self.settings: + kwargs['analyze'] = parse_bool(self.settings.get('analyze')) + return PayPalStatementParser(fin, **kwargs) diff -Nru ofxstatement-plugins-20191113/_ofxstatement-paypal/README.rst ofxstatement-plugins-20191114/_ofxstatement-paypal/README.rst --- ofxstatement-plugins-20191113/_ofxstatement-paypal/README.rst 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/_ofxstatement-paypal/README.rst 2019-11-14 09:40:31.000000000 +0000 @@ -0,0 +1,67 @@ +.. image:: https://travis-ci.org/themalkolm/ofxstatement-paypal.svg?branch=master + :target: https://travis-ci.org/themalkolm/ofxstatement-paypal + +ofxstatement-paypal +=================== + +This is a collection of parsers for proprietary statement formats, produced by +`PayPal`_. It parses ``*.csv`` file exported from the site. + +It is a plugin for `ofxstatement`_. + +.. _PayPal: https://www.paypal.com +.. _ofxstatement: https://github.com/kedder/ofxstatement + +Configuration +============= + +PayPal exports data for all currencies in one file. This means you must define different configurations for different +currencies. See below for examples. + +It worth mentioning that there is ``analyze`` option that enables simple analyzing that modifies memo in attempt +to make it more relevant e.g. it picks ``Item Title`` for any steam purchases: + +``WWW.Steampowered.com`` -> ``WWW.Steampowered.com / Hero Siege`` + +It is completely optional and up to you. + +Locale +====== + +You can configure exact locale and encodings to use during parsing. Here is example how to configure both of them +with the default configuration you always have. + +.. code-block:: + + [default] + plugin = paypal + encoding = iso8859-1 + + [paypal] + plugin = paypal + encoding = iso8859-1 + locale = sv_SE + ... + +Example +======= + +.. code-block:: + + [paypal:sek] + plugin = paypal + account_id = john.doe@gmail.com/SEK + currency = SEK + analyze = 1 + + [paypal:eur] + plugin = paypal + account_id = john.doe@gmail.com/EUR + currency = EUR + analyze = 1 + + [paypal:usd] + plugin = paypal + account_id = john.doe@gmail.com/USD + currency = USD + analyze = 1 diff -Nru ofxstatement-plugins-20191113/_ofxstatement-paypal/requirements.txt ofxstatement-plugins-20191114/_ofxstatement-paypal/requirements.txt --- ofxstatement-plugins-20191113/_ofxstatement-paypal/requirements.txt 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/_ofxstatement-paypal/requirements.txt 2019-11-14 09:40:31.000000000 +0000 @@ -0,0 +1,2 @@ +ofxstatement>=0.5.0 +click>=6.6 \ No newline at end of file diff -Nru ofxstatement-plugins-20191113/_ofxstatement-paypal/setup.py ofxstatement-plugins-20191114/_ofxstatement-paypal/setup.py --- ofxstatement-plugins-20191113/_ofxstatement-paypal/setup.py 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/_ofxstatement-paypal/setup.py 2019-11-14 09:40:31.000000000 +0000 @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from distutils.core import setup + +version = '1.0.0' +with open('README.rst') as f: + long_description = f.read() + +setup(name='ofxstatement-paypal', + version=version, + author='Alexander Krasnukhin', + author_email='the.malkolm@gmail.com', + url='https://github.com/themalkolm/ofxstatement-paypal', + description=('ofxstatement plugins for paypal'), + long_description=long_description, + license='Apache License 2.0', + keywords=['ofx', 'ofxstatement', 'paypal'], + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Programming Language :: Python :: 3', + 'Natural Language :: English', + 'Topic :: Office/Business :: Financial :: Accounting', + 'Topic :: Utilities', + 'Environment :: Console', + 'Operating System :: OS Independent' + ], + packages=['ofxstatement', 'ofxstatement.plugins'], + namespace_packages=['ofxstatement', 'ofxstatement.plugins'], + entry_points={ + 'ofxstatement': ['paypal = ofxstatement.plugins.paypal:PayPalPlugin'] + }, + install_requires=['ofxstatement'], + test_suite='ofxstatement.plugins.tests', + include_package_data=True, + zip_safe=True + ) diff -Nru ofxstatement-plugins-20191113/_ofxstatement-paypal/.travis.yml ofxstatement-plugins-20191114/_ofxstatement-paypal/.travis.yml --- ofxstatement-plugins-20191113/_ofxstatement-paypal/.travis.yml 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/_ofxstatement-paypal/.travis.yml 2019-11-14 09:40:31.000000000 +0000 @@ -0,0 +1,8 @@ +language: python +python: 3.5 +script: + - python setup.py install +notifications: + email: + on_success: change + on_failure: change diff -Nru ofxstatement-plugins-20191113/ofxstatement-paypal/convert.py ofxstatement-plugins-20191114/ofxstatement-paypal/convert.py --- ofxstatement-plugins-20191113/ofxstatement-paypal/convert.py 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-paypal/convert.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import os.path -import click -import logging - -from ofxstatement.ofx import OfxWriter -from ofxstatement.plugins import paypal - - -@click.command() -@click.argument('path') -@click.option('--debug', is_flag=True, default=False) -def convert(path, debug): - """Parse and print transactions from PayPal's *.csv files.""" - - logging.basicConfig(level=logging.INFO, format='[%(levelname)s] %(message)s') - - root, ext = os.path.splitext(path) - output_file = root + '.ofx' - - parser = paypal.PayPalPlugin(ui=None, settings=None).get_parser(path) - statement = parser.parse() - - if debug: - for line in statement.lines: - print(line) - return - - with open(output_file, 'w') as out: - writer = OfxWriter(statement) - out.write(writer.toxml()) - - -if __name__ == '__main__': - convert() diff -Nru ofxstatement-plugins-20191113/ofxstatement-paypal/LICENSE ofxstatement-plugins-20191114/ofxstatement-paypal/LICENSE --- ofxstatement-plugins-20191113/ofxstatement-paypal/LICENSE 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-paypal/LICENSE 1970-01-01 00:00:00.000000000 +0000 @@ -1,202 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff -Nru ofxstatement-plugins-20191113/ofxstatement-paypal/ofxstatement/__init__.py ofxstatement-plugins-20191114/ofxstatement-paypal/ofxstatement/__init__.py --- ofxstatement-plugins-20191113/ofxstatement-paypal/ofxstatement/__init__.py 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-paypal/ofxstatement/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file diff -Nru ofxstatement-plugins-20191113/ofxstatement-paypal/ofxstatement/plugins/__init__.py ofxstatement-plugins-20191114/ofxstatement-paypal/ofxstatement/plugins/__init__.py --- ofxstatement-plugins-20191113/ofxstatement-paypal/ofxstatement/plugins/__init__.py 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-paypal/ofxstatement/plugins/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file diff -Nru ofxstatement-plugins-20191113/ofxstatement-paypal/ofxstatement/plugins/paypal.py ofxstatement-plugins-20191114/ofxstatement-paypal/ofxstatement/plugins/paypal.py --- ofxstatement-plugins-20191113/ofxstatement-paypal/ofxstatement/plugins/paypal.py 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-paypal/ofxstatement/plugins/paypal.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,201 +0,0 @@ -# -*- coding: utf-8 -*- - -import locale -import itertools -import csv - -from datetime import datetime - -from contextlib import contextmanager -from ofxstatement.parser import StatementParser -from ofxstatement.plugin import Plugin -from ofxstatement.statement import Statement, StatementLine, generate_transaction_id - - -def take(iterable, n): - """Return first n items of the iterable as a list.""" - return list(itertools.islice(iterable, n)) - - -def drop(iterable, n): - """Drop first n items of the iterable and return result as a list.""" - return list(itertools.islice(iterable, n, None)) - - -def head(iterable): - """Return first element of the iterable.""" - return take(iterable, 1)[0] - - -@contextmanager -def scoped_setlocale(category, loc=None): - """Scoped version of locale.setlocale()""" - orig = locale.getlocale(category) - try: - yield locale.setlocale(category, loc) - finally: - locale.setlocale(category, orig) - - -def atof(string, loc=None): - """Locale aware atof function for our parser.""" - with scoped_setlocale(locale.LC_NUMERIC, loc): - return locale.atof(string) - - -class PayPalStatementParser(StatementParser): - bank_id = 'PayPal' - date_format = '%Y/%m/%d' - valid_header = [ - u"Date", - u"Time", - u"Time Zone", - u"Name", - u"Type", - u"Status", - u"Currency", - u"Gross", - u"Fee", - u"Net", - u"From Email Address", - u"To Email Address", - u"Transaction ID", - u"Counterparty Status", - u"Address Status", - u"Item Title", - u"Item ID", - u"Shipping and Handling Amount", - u"Insurance Amount", - u"Sales Tax", - u"Option 1 Name", - u"Option 1 Value", - u"Option 2 Name", - u"Option 2 Value", - u"Auction Site", - u"Buyer ID", - u"Item URL", - u"Closing Date", - u"Escrow Id", - u"Invoice Id", - u"Reference Txn ID", - u"Invoice Number", - u"Custom Number", - u"Receipt ID", - u"Balance", - u"Address Line 1", - u"Address Line 2/District/Neighborhood", - u"Town/City", - u"State/Province/Region/County/Territory/Prefecture/Republic", - u"Zip/Postal Code", - u"Country", - u"Contact Phone Number", - u"", - ] - - def __init__(self, fin, account_id, currency, encoding=None, locale=None, analyze=False): - self.account_id = account_id - self.currency = currency - self.locale = locale - self.encoding = encoding - self.analyze = analyze - - with open(fin, 'r', encoding=self.encoding) as f: - self.lines = f.readlines() - - self.validate() - self.statement = Statement( - bank_id=self.bank_id, - account_id=self.account_id, - currency=self.currency - ) - - @property - def reader(self): - return csv.reader(self.lines, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL) - - @property - def header(self): - return [c.strip() for c in head(self.reader)] - - @property - def rows(self): - rs = drop(self.reader, 1) - currency_idx = self.valid_header.index("Currency") - return [r for r in rs if r[currency_idx] == self.currency] - - def validate(self): - """ - Validate to ensure csv has the same header we expect. - """ - - expected = self.valid_header - actual = self.header - if expected != actual: - msg = "\n".join([ - "Header template doesn't match:", - "expected: %s" % expected, - "actual : %s" % actual - ]) - raise ValueError(msg) - - def split_records(self): - for row in self.rows: - yield row - - def parse_record(self, row): - - id_idx = self.valid_header.index("Transaction ID") - date_idx = self.valid_header.index("Date") - memo_idx = self.valid_header.index("Name") - refnum_idx = self.valid_header.index("Reference Txn ID") - amount_idx = self.valid_header.index("Gross") - payee_idx = self.valid_header.index("To Email Address") - title_idx = self.valid_header.index("Item Title") - - stmt_line = StatementLine() - stmt_line.id = row[id_idx] - stmt_line.date = datetime.strptime(row[date_idx], self.date_format) - stmt_line.memo = row[memo_idx] - - if self.analyze: - memo_parts = [ - row[memo_idx] - ] - - payee = row[payee_idx] - if payee and (payee.lower() == 'steamgameseu@steampowered.com'): - memo_parts.append(row[title_idx]) - - stmt_line.memo = ' / '.join(filter(bool, memo_parts)) - - stmt_line.refnum = row[refnum_idx] - stmt_line.amount = atof(row[amount_idx].replace(" ", ""), self.locale) - - return stmt_line - - -def parse_bool(value): - if value in ('True', 'true', '1'): - return True - if value in ('False', 'false', '0'): - return False - raise ValueError("Can't parse boolean value: %s" % value) - - -class PayPalPlugin(Plugin): - def get_parser(self, fin): - kwargs = { - 'encoding': 'iso8859-1', - } - if self.settings: - if 'account_id' in self.settings: - kwargs['account_id'] = self.settings.get('account_id') - if 'currency' in self.settings: - kwargs['currency'] = self.settings.get('currency') - if 'locale' in self.settings: - kwargs['locale'] = self.settings.get('locale') - if 'encoding' in self.settings: - kwargs['encoding'] = self.settings.get('encoding') - if 'analyze' in self.settings: - kwargs['analyze'] = parse_bool(self.settings.get('analyze')) - return PayPalStatementParser(fin, **kwargs) diff -Nru ofxstatement-plugins-20191113/ofxstatement-paypal/README.rst ofxstatement-plugins-20191114/ofxstatement-paypal/README.rst --- ofxstatement-plugins-20191113/ofxstatement-paypal/README.rst 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-paypal/README.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,67 +0,0 @@ -.. image:: https://travis-ci.org/themalkolm/ofxstatement-paypal.svg?branch=master - :target: https://travis-ci.org/themalkolm/ofxstatement-paypal - -ofxstatement-paypal -=================== - -This is a collection of parsers for proprietary statement formats, produced by -`PayPal`_. It parses ``*.csv`` file exported from the site. - -It is a plugin for `ofxstatement`_. - -.. _PayPal: https://www.paypal.com -.. _ofxstatement: https://github.com/kedder/ofxstatement - -Configuration -============= - -PayPal exports data for all currencies in one file. This means you must define different configurations for different -currencies. See below for examples. - -It worth mentioning that there is ``analyze`` option that enables simple analyzing that modifies memo in attempt -to make it more relevant e.g. it picks ``Item Title`` for any steam purchases: - -``WWW.Steampowered.com`` -> ``WWW.Steampowered.com / Hero Siege`` - -It is completely optional and up to you. - -Locale -====== - -You can configure exact locale and encodings to use during parsing. Here is example how to configure both of them -with the default configuration you always have. - -.. code-block:: - - [default] - plugin = paypal - encoding = iso8859-1 - - [paypal] - plugin = paypal - encoding = iso8859-1 - locale = sv_SE - ... - -Example -======= - -.. code-block:: - - [paypal:sek] - plugin = paypal - account_id = john.doe@gmail.com/SEK - currency = SEK - analyze = 1 - - [paypal:eur] - plugin = paypal - account_id = john.doe@gmail.com/EUR - currency = EUR - analyze = 1 - - [paypal:usd] - plugin = paypal - account_id = john.doe@gmail.com/USD - currency = USD - analyze = 1 diff -Nru ofxstatement-plugins-20191113/ofxstatement-paypal/requirements.txt ofxstatement-plugins-20191114/ofxstatement-paypal/requirements.txt --- ofxstatement-plugins-20191113/ofxstatement-paypal/requirements.txt 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-paypal/requirements.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -ofxstatement>=0.5.0 -click>=6.6 \ No newline at end of file diff -Nru ofxstatement-plugins-20191113/ofxstatement-paypal/setup.py ofxstatement-plugins-20191114/ofxstatement-paypal/setup.py --- ofxstatement-plugins-20191113/ofxstatement-paypal/setup.py 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-paypal/setup.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from distutils.core import setup - -version = '1.0.0' -with open('README.rst') as f: - long_description = f.read() - -setup(name='ofxstatement-paypal', - version=version, - author='Alexander Krasnukhin', - author_email='the.malkolm@gmail.com', - url='https://github.com/themalkolm/ofxstatement-paypal', - description=('ofxstatement plugins for paypal'), - long_description=long_description, - license='Apache License 2.0', - keywords=['ofx', 'ofxstatement', 'paypal'], - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Programming Language :: Python :: 3', - 'Natural Language :: English', - 'Topic :: Office/Business :: Financial :: Accounting', - 'Topic :: Utilities', - 'Environment :: Console', - 'Operating System :: OS Independent' - ], - packages=['ofxstatement', 'ofxstatement.plugins'], - namespace_packages=['ofxstatement', 'ofxstatement.plugins'], - entry_points={ - 'ofxstatement': ['paypal = ofxstatement.plugins.paypal:PayPalPlugin'] - }, - install_requires=['ofxstatement'], - test_suite='ofxstatement.plugins.tests', - include_package_data=True, - zip_safe=True - ) diff -Nru ofxstatement-plugins-20191113/ofxstatement-paypal/.travis.yml ofxstatement-plugins-20191114/ofxstatement-paypal/.travis.yml --- ofxstatement-plugins-20191113/ofxstatement-paypal/.travis.yml 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-paypal/.travis.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -language: python -python: 3.5 -script: - - python setup.py install -notifications: - email: - on_success: change - on_failure: change diff -Nru ofxstatement-plugins-20191113/ofxstatement-russian/README.rst ofxstatement-plugins-20191114/ofxstatement-russian/README.rst --- ofxstatement-plugins-20191113/ofxstatement-russian/README.rst 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-russian/README.rst 2019-11-14 09:40:32.000000000 +0000 @@ -9,11 +9,15 @@ `ofxstatement-russian`_ provides some Russian banks plugins for ofxstatement. + +.. _ofxstatement: https://github.com/kedder/ofxstatement +.. _ofxstatement-russian: https://github.com/gerasiov/ofxstatement-russian + Supported banks: * Avangard Bank (http://avangard.ru) plugin 'avangard' * Tinkoff Bank (http://tinkoff.ru) plugin 'tinkoff' -* SberBank (http://sbrf.ru) plugin 'sberbank' +* SberBank (http://sbrf.ru) plugins 'sberbank_csv', 'sberbank_txt' * AlfaBank (https://www.alfabank.ru) plugin 'alfabank' * VTB (https://www.vtb.ru) plugin 'vtb' @@ -28,14 +32,15 @@ CSV statement for credit, debit and saving account are supported. -SberBank +SberBankCSV -------- -TXT statement (available via "request statement by e-mail" function) for debit card is supported. +CSV statement (available via "request statement by e-mail as Excel sheet" function). -.. _ofxstatement: https://github.com/kedder/ofxstatement -.. _ofxstatement-russian: https://github.com/gerasiov/ofxstatement-russian +SberBankTxt +-------- +Legacy TXT statement (available via "request statement by e-mail" function) for debit card is supported. AlfaBank ------- @@ -79,7 +84,7 @@ Currency (if not set, will be extracted from the first record) -sberbank +sberbank_txt -------- bank diff -Nru ofxstatement-plugins-20191113/ofxstatement-russian/setup.py ofxstatement-plugins-20191114/ofxstatement-russian/setup.py --- ofxstatement-plugins-20191113/ofxstatement-russian/setup.py 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-russian/setup.py 2019-11-14 09:40:32.000000000 +0000 @@ -32,13 +32,14 @@ namespace_packages=["ofxstatement", "ofxstatement.plugins"], entry_points={ 'ofxstatement': - [ - 'avangard = ofxstatement.plugins.avangard:AvangardPlugin', - 'tinkoff = ofxstatement.plugins.tinkoff:TinkoffPlugin', - 'sberbank = ofxstatement.plugins.sberbank:SberBankPlugin', - 'alfabank = ofxstatement.plugins.alfabank:AlfabankPlugin', - 'vtb = ofxstatement.plugins.vtb:VtbPlugin', - ] + [ + 'avangard = ofxstatement.plugins.avangard:AvangardPlugin', + 'tinkoff = ofxstatement.plugins.tinkoff:TinkoffPlugin', + 'sberbank_csv = ofxstatement.plugins.sberbank_csv:SberBankCSVPlugin', + 'sberbank_txt = ofxstatement.plugins.sberbank_txt:SberBankTxtPlugin', + 'alfabank = ofxstatement.plugins.alfabank:AlfabankPlugin', + 'vtb = ofxstatement.plugins.vtb:VtbPlugin', + ] }, install_requires=['ofxstatement'], test_suite="ofxstatement.plugins.tests", diff -Nru ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/sberbank_csv.py ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/sberbank_csv.py --- ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/sberbank_csv.py 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/sberbank_csv.py 2019-11-14 09:40:32.000000000 +0000 @@ -0,0 +1,74 @@ +# SberBank (http://sberbank.ru) CSV plugin for ofxstatement +# +# Copyright 2013 Andrey Lebedev +# Copyright 2016 Alexander Gerasiov +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from ofxstatement.parser import StatementParser +from ofxstatement.plugin import Plugin +from ofxstatement import statement +from datetime import datetime +import csv + +# file format options +SB_DELIMITER = ';' +SD_TIME_FORMAT = '%d.%m.%Y' +SD_ENCODING = 'utf-8' +SB_FIELDNAMES = ['card_type', 'card_num', 'date_user', 'date', 'auth_code', 'op_type', 'op_city', + 'op_country', 'description', 'currency', 'currency_amount', 'amount'] + + +class SberBankCSVStatementParser(StatementParser): + statement = None + + def __init__(self, fin): + self.statement = statement.Statement() + self.fin = fin + # Skip 1st row with column's headers + self.fin.readline() + self.cur_record = 1 + + def split_records(self): + return csv.DictReader(self.fin, delimiter=SB_DELIMITER, fieldnames=SB_FIELDNAMES) + + def parse_record(self, line): + transaction = statement.StatementLine() + + if not self.statement.account_id: + self.statement.account_id = '{} {}'.format(line['card_type'], line['card_num']) + + transaction.date = datetime.strptime(line['date'], SD_TIME_FORMAT) + transaction.date_user = datetime.strptime(line['date_user'], SD_TIME_FORMAT) + + transaction.amount = float(line['amount'].replace(',', '.')) + + transaction.trntype = 'DEBIT' if transaction.amount > 0 else 'CREDIT' + + transaction.memo = ', '.join(line[f] for f in + ('description', 'op_city', 'op_country', 'op_type') if line[f]) + + return transaction + + +class SberBankCSVPlugin(Plugin): + """SberBank CSV (http://sberbank.ru) + """ + + def get_parser(self, fin): + f = open(fin, 'r', encoding=SD_ENCODING) + parser = SberBankCSVStatementParser(f) + parser.statement.currency = self.settings.get('currency') + parser.statement.account_id = self.settings.get('account') + parser.statement.bank_id = self.settings.get('bank', 'SberBank') + return parser diff -Nru ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/sberbank.py ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/sberbank.py --- ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/sberbank.py 2019-11-13 15:16:47.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/sberbank.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,199 +0,0 @@ -# SberBank (http://sbrf.ru) plugin for ofxstatement -# -# Copyright 2013 Andrey Lebedev -# Copyright 2016 Alexander Gerasiov -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 3 as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from ofxstatement.parser import StatementParser -from ofxstatement.plugin import Plugin -from ofxstatement import statement -from datetime import datetime -import re - -# file format options -sb_encoding = 'cp1251' - - -class ParserState: - name = "" - matchers = None - - def __init__(self, name, parser): - self.name = name - self.matchers = list() - parser.append(self) - - def addMatcher(self, reString, nextState=None, function=None): - self.matchers.append([re.compile(reString), nextState, function]) - - def run(self, line): - for (matcher, nextState, function) in self.matchers: - match = matcher.match(line) - if match: - if function: - function(match) - return nextState - return None - - def __str__(self): - string = "State '%s' matchers:\n" % self.name - for matcher in self.matchers: - string += "re '%s' => state '%s'\n" % (matcher[0], matcher[1]) - return string - - -class SberBankStatementParser(StatementParser): - statement = None - - transaction = None - account_id = "" - account_fl_len = 0 - - machine = {} - currentState = None - internal = None - - def append(self, state): - self.machine[state.name] = state - - def extractCurrency(self, match): - if not self.statement.currency: - self.statement.currency = match.group(1) - - def extractBeginBalance(self, match): - if not self.statement.start_balance: - self.statement.start_balance = float(match.group(1)) - - def extractEndBalance(self, match): - if not self.statement.end_balance: - self.statement.end_balance = float(match.group(1)) - if not self.statement.account_id: - self.statement.account_id = " ".join(self.account_id.split()) - if self.transaction: - self.transaction.memo = " ".join(self.transaction.memo.split()) - self.statement.lines.append(self.transaction) - self.transaction = None - - def parseDate(self, string): - rusMonths = { - u'ЯНВ': 1, - u'ФЕВ': 2, - u'МАР': 3, - u'АПР': 4, - u'МАЙ': 5, - u'ИЮН': 6, - u'ИЮЛ': 7, - u'АВГ': 8, - u'СЕН': 9, - u'ОКТ': 10, - u'НОЯ': 11, - u'ДЕК': 12, - } - return datetime(2000 + int(string[5:]), rusMonths[string[2:5]], int(string[:2])) - - def extractTransaction(self, match): - if self.transaction: - self.transaction.memo = " ".join(self.transaction.memo.split()) - self.statement.lines.append(self.transaction) - - self.transaction = statement.StatementLine() - - self.account_fl_len = len(match.group(1)) - - self.transaction.date = self.parseDate(match.group(3)) - self.transaction.memo = match.group(4) - self.transaction.amount = float(match.group(5)) * (1 if match.group(6) else -1) - self.transaction.trntype = 'DEBIT' if match.group(6) else 'CREDIT' - if match.group(1).strip(): - self.account_id += match.group(1) - - def extractTransactionAppend(self, match): - first = match.group(1)[:self.account_fl_len] - second = match.group(1)[self.account_fl_len:] - self.account_id += first - self.transaction.memo += second - - def __init__(self, fin): - self.statement = statement.Statement() - self.internal = {} - self.fin = fin - - self.currentState = 'init' - - state = ParserState('init', self) - state.addMatcher(u"^.*ВАЛЮТА СЧЕТА.*$", 'currency') - - state = ParserState('currency', self) - state.addMatcher("^\s*(\w{3})\s*$", - 'begin_balance', - self.extractCurrency) - - state = ParserState('begin_balance', self) - state.addMatcher(u"^ОСТАТОК НА НАЧАЛО ПЕРИОДА:\s*(\d+\.\d{2})(\+)?\s*$", - 'table_header', - self.extractBeginBalance) - - state = ParserState('table_header', self) - state.addMatcher("^[-+]{80,}$", 'table_header2') - - state = ParserState('table_header2', self) - state.addMatcher("^[-+]{80,}$", 'transaction') - - state = ParserState('transaction', self) - state.addMatcher("^[-+]{80,}$", 'end_balance') - state.addMatcher( - u"^(.*)\s*(\d{2}[А-Я]{3})\s+(\d{2}[А-Я]{3}\d{2})\s+\d{6}\s+(.*)\s\w{3}\s+\d*\.\d{2}\s+(\d*\.\d{2})(CR)?\s*$", - None, - self.extractTransaction) - state.addMatcher( - u"^(.*)\s*(\d{2}[А-Я]{3})\s+(\d{2}[А-Я]{3}\d{2})\s+\d{6}\s+(КОМИССИЯ)\s+(\d*\.\d{2})(CR)?\s*$", - None, - self.extractTransaction) - state.addMatcher( - u"^(.*)\s*(\d{2}[А-Я]{3})\s+(\d{2}[А-Я]{3}\d{2})\s+\d{6}\s+(.*)\s(\d*\.\d{2})(CR)?\s*$", - None, - self.extractTransaction) - state.addMatcher(u".*ИТОГО ПО.*") - state.addMatcher(u"^(.+)\s*$", - None, - self.extractTransactionAppend) - - state = ParserState('end_balance', self) - state.addMatcher(u"^ОСТАТОК НА КОНЕЦ ПЕРИОДА:\s*(\d+\.\d{2})\+?\s*$", - 'table_header', - self.extractEndBalance) - - def run(self, line): - nextState = self.machine[self.currentState].run(line) - if nextState: - self.currentState = nextState - - def parse(self): - for line in self.fin: - self.run(line) - - return self.statement - - -class SberBankPlugin(Plugin): - """SberBank TXT (http://sbrf.ru) - """ - - def get_parser(self, fin): - f = open(fin, 'r', encoding=sb_encoding) - parser = SberBankStatementParser(f) - parser.statement.currency = self.settings.get('currency', None) - parser.statement.account_id = self.settings.get('account', None) - parser.statement.bank_id = self.settings.get('bank', 'SberBank') - return parser diff -Nru ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/sberbank_txt.py ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/sberbank_txt.py --- ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/sberbank_txt.py 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/sberbank_txt.py 2019-11-14 09:40:32.000000000 +0000 @@ -0,0 +1,199 @@ +# SberBankTxt (http://sbrf.ru) plugin for ofxstatement +# +# Copyright 2013 Andrey Lebedev +# Copyright 2016 Alexander Gerasiov +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from ofxstatement.parser import StatementParser +from ofxstatement.plugin import Plugin +from ofxstatement import statement +from datetime import datetime +import re + +# file format options +sb_encoding = 'cp1251' + + +class ParserState: + name = "" + matchers = None + + def __init__(self, name, parser): + self.name = name + self.matchers = list() + parser.append(self) + + def addMatcher(self, reString, nextState=None, function=None): + self.matchers.append([re.compile(reString), nextState, function]) + + def run(self, line): + for (matcher, nextState, function) in self.matchers: + match = matcher.match(line) + if match: + if function: + function(match) + return nextState + return None + + def __str__(self): + string = "State '%s' matchers:\n" % self.name + for matcher in self.matchers: + string += "re '%s' => state '%s'\n" % (matcher[0], matcher[1]) + return string + + +class SberBankTxtStatementParser(StatementParser): + statement = None + + transaction = None + account_id = "" + account_fl_len = 0 + + machine = {} + currentState = None + internal = None + + def append(self, state): + self.machine[state.name] = state + + def extractCurrency(self, match): + if not self.statement.currency: + self.statement.currency = match.group(1) + + def extractBeginBalance(self, match): + if not self.statement.start_balance: + self.statement.start_balance = float(match.group(1)) + + def extractEndBalance(self, match): + if not self.statement.end_balance: + self.statement.end_balance = float(match.group(1)) + if not self.statement.account_id: + self.statement.account_id = " ".join(self.account_id.split()) + if self.transaction: + self.transaction.memo = " ".join(self.transaction.memo.split()) + self.statement.lines.append(self.transaction) + self.transaction = None + + def parseDate(self, string): + rusMonths = { + u'ЯНВ': 1, + u'ФЕВ': 2, + u'МАР': 3, + u'АПР': 4, + u'МАЙ': 5, + u'ИЮН': 6, + u'ИЮЛ': 7, + u'АВГ': 8, + u'СЕН': 9, + u'ОКТ': 10, + u'НОЯ': 11, + u'ДЕК': 12, + } + return datetime(2000 + int(string[5:]), rusMonths[string[2:5]], int(string[:2])) + + def extractTransaction(self, match): + if self.transaction: + self.transaction.memo = " ".join(self.transaction.memo.split()) + self.statement.lines.append(self.transaction) + + self.transaction = statement.StatementLine() + + self.account_fl_len = len(match.group(1)) + + self.transaction.date = self.parseDate(match.group(3)) + self.transaction.memo = match.group(4) + self.transaction.amount = float(match.group(5)) * (1 if match.group(6) else -1) + self.transaction.trntype = 'DEBIT' if match.group(6) else 'CREDIT' + if match.group(1).strip(): + self.account_id += match.group(1) + + def extractTransactionAppend(self, match): + first = match.group(1)[:self.account_fl_len] + second = match.group(1)[self.account_fl_len:] + self.account_id += first + self.transaction.memo += second + + def __init__(self, fin): + self.statement = statement.Statement() + self.internal = {} + self.fin = fin + + self.currentState = 'init' + + state = ParserState('init', self) + state.addMatcher(r"^.*ВАЛЮТА СЧЕТА.*$", 'currency') + + state = ParserState('currency', self) + state.addMatcher(r"^\s*(\w{3})\s*$", + 'begin_balance', + self.extractCurrency) + + state = ParserState('begin_balance', self) + state.addMatcher(r"^ОСТАТОК НА НАЧАЛО ПЕРИОДА:\s*(\d+\.\d{2})(\+)?\s*$", + 'table_header', + self.extractBeginBalance) + + state = ParserState('table_header', self) + state.addMatcher(r"^[-+]{80,}$", 'table_header2') + + state = ParserState('table_header2', self) + state.addMatcher(r"^[-+]{80,}$", 'transaction') + + state = ParserState('transaction', self) + state.addMatcher(r"^[-+]{80,}$", 'end_balance') + state.addMatcher( + r"^(.*)\s*(\d{2}[А-Я]{3})\s+(\d{2}[А-Я]{3}\d{2})\s+\d{6}\s+(.*)\s\w{3}\s+\d*\.\d{2}\s+(\d*\.\d{2})(CR)?\s*$", + None, + self.extractTransaction) + state.addMatcher( + r"^(.*)\s*(\d{2}[А-Я]{3})\s+(\d{2}[А-Я]{3}\d{2})\s+\d{6}\s+(КОМИССИЯ)\s+(\d*\.\d{2})(CR)?\s*$", + None, + self.extractTransaction) + state.addMatcher( + r"^(.*)\s*(\d{2}[А-Я]{3})\s+(\d{2}[А-Я]{3}\d{2})\s+\d{6}\s+(.*)\s(\d*\.\d{2})(CR)?\s*$", + None, + self.extractTransaction) + state.addMatcher(r".*ИТОГО ПО.*") + state.addMatcher(r"^(.+)\s*$", + None, + self.extractTransactionAppend) + + state = ParserState('end_balance', self) + state.addMatcher(r"^ОСТАТОК НА КОНЕЦ ПЕРИОДА:\s*(\d+\.\d{2})\+?\s*$", + 'table_header', + self.extractEndBalance) + + def run(self, line): + nextState = self.machine[self.currentState].run(line) + if nextState: + self.currentState = nextState + + def parse(self): + for line in self.fin: + self.run(line) + + return self.statement + + +class SberBankTxtPlugin(Plugin): + """SberBank TXT (http://sbrf.ru) + """ + + def get_parser(self, fin): + f = open(fin, 'r', encoding=sb_encoding) + parser = SberBankTxtStatementParser(f) + parser.statement.currency = self.settings.get('currency', None) + parser.statement.account_id = self.settings.get('account', None) + parser.statement.bank_id = self.settings.get('bank', 'SberBank') + return parser diff -Nru ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/tests/samples/sberbank.csv ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/tests/samples/sberbank.csv --- ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/tests/samples/sberbank.csv 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/tests/samples/sberbank.csv 2019-11-14 09:40:32.000000000 +0000 @@ -0,0 +1,12 @@ +Тип карты;Номер карты;Дата совершения операции;Дата обработки операции;Код авторизации;Тип операции;Город совершения операции;Страна совершения операции;Описание;Валюта операции;Сумма в валюте операции;Сумма в валюте счета; +Основная;*6833;31.10.2019;31.10.2019;204840;4829;MOSCOW;RUS;SBOL перевод 4276****1234 И. ИВАН ИВАНОВИЧ;;;-4320,4; +Основная;*6833;21.10.2019;21.10.2019;279185;6011;MOSKVA;RUS;ATM 762019 ;;;-3100; +Основная;*6833;11.10.2019;11.10.2019;258668;;MOSCOW;RUS;SBOL перевод 5469****1236 З. СЕМЕН СЕМЕНОВИЧ;;;1000; +Основная;*6833;16.06.2019;17.06.2019;228007;;Moscow;RUS;SBERBANK ONL@IN VKLAD-KARTA ;;;9,01; +Основная;*6833;11.06.2019;11.06.2019;294002;;MOSCOW;RUS;SBOL перевод 5469****1236 З. СЕМЕН СЕМЕНОВИЧ;;;1100; +Основная;*6833;17.05.2019;17.05.2019;269538;4829;MOSCOW;RUS;SBOL перевод 4276****1240 Б. АЛЕКСЕЙ ПЕТРОВИЧ;;;-1250; +Основная;*6833;22.04.2019;22.04.2019;;07;Moscow;RUS;Прочие выплаты;;;15000; +Основная;*6833;10.04.2019;10.04.2019;225412;4829;MOSCOW;RUS;SBOL перевод 5469****1242 Р. ФЕДОР ВИКТОРОВИЧ;;;-,01; +Основная;*6833;08.04.2019;09.04.2019;299610;6012;MOSCOW ;RUS;Rocketbank.ru Card2Card ;;;-900; +Основная;*6833;01.04.2019;01.04.2019;250011;4829;MOSCOW;RUS;SBOL перевод 6762****1243 А. АЛЕКСАНДР АЛЕКСАНДРОВИЧ;;;-300; +Основная;*6833;01.04.2019;01.04.2019;265912;6012;Visa Direct ;RUS;TINKOFF BANK CARD2CARD перевод 5213****1244 ;;;100,23; diff -Nru ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/tests/samples/sberbank_maestro.txt ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/tests/samples/sberbank_maestro.txt --- ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/tests/samples/sberbank_maestro.txt 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/tests/samples/sberbank_maestro.txt 2019-11-14 09:40:32.000000000 +0000 @@ -0,0 +1,38 @@ + + + + : 0.00 + RUR + + + . + 1319 - 1319 1319 001 001 + + : 0.00 +--------------------+-----+-----+-------+--------------------------+---------------+-------------- + , | | | | , | | + |.|-| -| , | | + | || | | | +--------------------+-----+-----+-------+--------------------------+---------------+-------------- + - MAESTRO 26 2619 231670 SBOL RUR 10000.00 10000.00CR +XXXXXXXXX XXXX40696 MOSCOW RUS + 26 2619 269948 SBOL RUR 10000.00 10000.00 + MOSCOW RUS + 01 0119 242351 SBOL RUR 10000.00 10000.00CR + MOSCOW RUS + 03 0319 203374 ATM 038051 RUR 10000.00 10000.00 + MOSKVA RU + ***************** 0.00 +--------------------+-----+-----+-------+--------------------------+---------------+-------------- +: 0.00 + : 0.00 + +R - ( ) + . + 30 + . + + : Principal , 997970 7970 +: 7970/0495 : + (495)500-00-05; (495)544-45-45 + diff -Nru ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/tests/samples/sberbank_visa.txt ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/tests/samples/sberbank_visa.txt --- ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/tests/samples/sberbank_visa.txt 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/tests/samples/sberbank_visa.txt 2019-11-14 09:40:32.000000000 +0000 @@ -0,0 +1,125 @@ + + + + : 0.00 + RUR + + + . + 1618 - 1619 1619 001 002 + + : 318.30+ +--------------------+-----+-----+-------+--------------------------+---------------+-------------- + , | | | | , | | + |.|-| -| , | | + | || | | | +--------------------+-----+-----+-------+--------------------------+---------------+-------------- +VISA GOLD 17 1818 220342 SBOL RUR 100.00 100.00CR +XXXX XXXX XXX4 6122 MOSCOW RU + 17 1818 282725 SBOL RUR 412.00 412.00 + MOSCOW RU + 02 0218 000000 RUR 10000.00 10000.00CR + 03 0318 277436 SBOL RUR 5000.00 5000.00 + MOSCOW RU + 27 2718 262373 SBOL RUR 15000.00 15000.00CR + MOSCOW RU + 07 0718 211698 ATM 038470 RUR 4900.00 4900.00 + MOSKVA RU + 27 2718 298990 SBOL RUR 2200.00 2200.00CR + MOSCOW RU + 03 0418 289647 PEREKRESTOK KRYLATSKOY RUR 1699.00 1699.00 + E NOGINSK RU + 08 0818 225952 SBOL RUR 5000.00 5000.00 + MOSCOW RU + 21 2218 240352 SBOL RUR 3000.00 3000.00 + MOSCOW RU + 21 2218 526331 SBOL RUR 3000.00 3000.00 + MOSCOW RU + 11 1218 249635 SBOL RUR 3070.00 3070.00 + MOSCOW RU + 20 2018 241773 SBOL RUR 750.00 750.00 + MOSCOW RU + 22 2218 274410 SBOL RUR 2000.00 2000.00 + MOSCOW RU + 21 2218 292466 TINKOFF BANK CARD2CARD RUR 3500.00 3500.00CR + Visa Direct RU + 29 2918 295346 SBOL RUR 2000.00 2000.00CR + MOSCOW RU + 02 0318 283180 SBOL RUR 1135.00 1135.00 + MOSCOW RU + 28 2818 245096 SBOL RUR 300.00 300.00CR + MOSCOW RU + 28 2818 244212 SBOL RUR 1000.00 1000.00 + MOSCOW RU + 28 2818 244212 SBOL 10.00 + MOSCOW RU + 29 2918 254948 SBOL RUR 970.00 970.00CR + MOSCOW RU +--------------------+-----+-----+-------+--------------------------+---------------+-------------- + ***************** 3094.00CR + : 20877.29+ + +R - ( ) + . + 30 + . + + : Principal , 997982 7982 +: 7982/1603 : + (495)500-00-05; (495)544-45-45 + + + + + : 0.00 + RUR + + + . + 1618 - 1619 1619 002 002 + + : 318.30+ +--------------------+-----+-----+-------+--------------------------+---------------+-------------- + , | | | | , | | + |.|-| -| , | | + | || | | | +--------------------+-----+-----+-------+--------------------------+---------------+-------------- +VISA GOLD 26 2619 228535 SBOL RUR 10000.00 10000.00 +XXXX XXXX XXX4 6122 MOSCOW RU + 26 2619 266743 TINKOFF BANK CARD2CARD RUR 10000.00 10000.00CR + Visa Direct RU + 01 0119 250011 SBOL RUR 10000.00 10000.00 + MOSCOW RU + 01 0119 265912 TINKOFF BANK CARD2CARD RUR 10000.00 10000.00CR + Visa Direct RU + 08 0919 299610 Rocketbank.ru Card2Car RUR 900.00 900.00 + d MOSCOW RU + 10 1019 225412 SBOL RUR 0.01 0.01 + MOSCOW RU + 22 2219 000000 RUR 15000.00 15000.00CR + 22 2219 260819 SBOL RUR 15000.00 15000.00 + MOSCOW RU + 03 0319 253096 SBOL RUR 7700.00 7700.00CR + MOSCOW RU + 07 0719 252784 SBOL RUR 2300.00 2300.00CR + MOSCOW RU + 17 1719 269538 SBOL RUR 1250.00 1250.00 + MOSCOW RU + 06 0619 228706 SBOL RUR 385.00 385.00 + MOSCOW RU + 11 1119 294002 SBOL RUR 10000.00 10000.00CR + MOSCOW RU + ***************** 20558.99CR +--------------------+-----+-----+-------+--------------------------+---------------+-------------- + 20558.99CR + : 20877.29+ + +R - ( ) + . + 30 + . + + : Principal , 997982 7982 +: 7982/1603 : + (495)500-00-05; (495)544-45-45 + diff -Nru ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/tests/test_sberbank_csv.py ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/tests/test_sberbank_csv.py --- ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/tests/test_sberbank_csv.py 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/tests/test_sberbank_csv.py 2019-11-14 09:40:32.000000000 +0000 @@ -0,0 +1,49 @@ +import os +import datetime + +from ofxstatement.ui import UI +from ofxstatement.plugins.sberbank_csv import SberBankCSVPlugin + + +SAMPLES_DIR = os.path.join(os.path.dirname(__file__), 'samples') + + +def test_sberbank(): + plugin = SberBankCSVPlugin(UI(), {'currency': 'RUR'}) + s = plugin.get_parser(os.path.join(SAMPLES_DIR, 'sberbank.csv')).parse() + + assert s is not None + + assert s.account_id == u'Основная *6833' + assert s.currency == 'RUR' + assert s.bank_id == 'SberBank' + + assert len(s.lines) == 11 + + assert all(l.amount for l in s.lines) + + assert s.lines[0].__dict__ == { + 'amount': -4320.4, + 'check_no': None, + 'date': datetime.datetime(2019, 10, 31, 0, 0), + 'date_user': datetime.datetime(2019, 10, 31, 0, 0), + 'id': None, + 'memo': 'SBOL перевод 4276****1234 И. ИВАН ИВАНОВИЧ, MOSCOW, RUS, 4829', + 'payee': None, + 'refnum': None, + 'trntype': 'CREDIT' + } + + assert s.lines[3].__dict__ == { + 'amount': 9.01, + 'check_no': None, + 'date': datetime.datetime(2019, 6, 17, 0, 0), + 'date_user': datetime.datetime(2019, 6, 16, 0, 0), + 'id': None, + 'memo': 'SBERBANK ONL@IN VKLAD-KARTA , Moscow, RUS', + 'payee': None, + 'refnum': None, + 'trntype': 'DEBIT' + } + + assert sum(l.amount for l in s.lines) == 7338.83 diff -Nru ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/tests/test_sberbank_txt.py ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/tests/test_sberbank_txt.py --- ofxstatement-plugins-20191113/ofxstatement-russian/src/ofxstatement/plugins/tests/test_sberbank_txt.py 1970-01-01 00:00:00.000000000 +0000 +++ ofxstatement-plugins-20191114/ofxstatement-russian/src/ofxstatement/plugins/tests/test_sberbank_txt.py 2019-11-14 09:40:32.000000000 +0000 @@ -0,0 +1,65 @@ +import os +import datetime + +from ofxstatement.ui import UI +from ofxstatement.plugins.sberbank_txt import SberBankTxtPlugin + + +SAMPLES_DIR = os.path.join(os.path.dirname(__file__), 'samples') + + +def test_parse_maestro(): + plugin = SberBankTxtPlugin(UI(), {}) + s = plugin.get_parser(os.path.join(SAMPLES_DIR, 'sberbank_maestro.txt')).parse() + + assert s is not None + + assert s.account_id == 'СБЕРБАНК - MAESTRO XXXXXXXXX XXXX40696 ОСНОВНАЯ' + assert s.currency == 'RUR' + assert s.bank_id == 'SberBank' + assert s.end_balance == 0 + assert s.start_balance == 0 + + assert len(s.lines) == 4 + + assert all(l.amount for l in s.lines) + + line0 = s.lines[0] + + assert line0.amount == 10000.0 + assert line0.memo == 'SBOL MOSCOW RUS' + assert line0.date == datetime.datetime(2019, 3, 26, 0, 0) + assert line0.trntype == 'DEBIT' + + +def test_parse_visa(): + plugin = SberBankTxtPlugin(UI(), {}) + s = plugin.get_parser(os.path.join(SAMPLES_DIR, 'sberbank_visa.txt')).parse() + + assert s is not None + + assert s.account_id == 'VISA GOLD XXXX XXXX XXX4 6122 ОСНОВНАЯ' + assert s.currency == 'RUR' + + assert s.bank_id == 'SberBank' + assert s.end_balance == 20877.29 + assert s.start_balance == 318.3 + + assert len(s.lines) == 34 + + assert all(l.amount for l in s.lines) + + assert s.lines[2].memo == 'ПОПОЛНЕНИЕ СЧЕТА' + assert s.lines[2].trntype == 'DEBIT' + + assert s.lines[7].memo == 'PEREKRESTOK KRYLATSKOY E NOGINSK RU' + assert s.lines[7].trntype == 'CREDIT' + assert s.lines[7].amount == -1699.0 + assert s.lines[7].date == datetime.datetime(2018, 10, 4, 0, 0) + + assert s.lines[14].memo == 'TINKOFF BANK CARD2CARD Visa Direct RU' + + assert s.lines[25].memo == 'Rocketbank.ru Card2Car d MOSCOW RU' + assert s.lines[25].trntype == 'CREDIT' + + assert abs(sum(l.amount for l in s.lines) + s.start_balance - s.end_balance) < 0.001 Binary files /tmp/tmpgcm9tP/Em2DCkvQur/ofxstatement-plugins-20191113/ofxstatement-simple/ofxstatement/plugins/__pycache__/__init__.cpython-37.pyc and /tmp/tmpgcm9tP/jpN5QLcHad/ofxstatement-plugins-20191114/ofxstatement-simple/ofxstatement/plugins/__pycache__/__init__.cpython-37.pyc differ Binary files /tmp/tmpgcm9tP/Em2DCkvQur/ofxstatement-plugins-20191113/ofxstatement-simple/ofxstatement/__pycache__/__init__.cpython-37.pyc and /tmp/tmpgcm9tP/jpN5QLcHad/ofxstatement-plugins-20191114/ofxstatement-simple/ofxstatement/__pycache__/__init__.cpython-37.pyc differ