commit 39c24efc74aef956416c6292f99e5db1b0c2ed17 Author: Andrew Williams Date: Sat Apr 19 23:47:56 2014 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..184f3d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ \ No newline at end of file diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..5f44fba --- /dev/null +++ b/AUTHORS @@ -0,0 +1,6 @@ +Andrew Williams + +Inspiration and file format information from YNAB4 Alfred Workflow add-on, created by: + +James Seward +"ppiixx" \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..63d2b2e --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) Andrew Williams, 2014 +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..675eedc --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +pYNAB +===== + +```pYNAB``` is a read-only YNAB4 budget file reader for Python, with support for Python 2.7 and 3.2+ + +Why? +---- + +YNAB reports are OK, to give you a quick overview of your spending but outside that you'll need to export your information and put it into Excel or another application to manually work through. Python has amazing libraries like Pandas available to really chew through data, so the library was designed to scratch these itches. + + +Installation +------------ + +pYNAB is a standard Python package, installation can be done via ```setup.py``` + + +Usage +----- + +At the moment only very limited module documentation exists, in the near future we'll have some Sphinx documentation + +Testing +------- + +We have extremely basic testing at the moment, which can be executed via ```setup.py``` using ```python setup.py test``` \ No newline at end of file diff --git a/pynab/__init__.py b/pynab/__init__.py new file mode 100644 index 0000000..7226afc --- /dev/null +++ b/pynab/__init__.py @@ -0,0 +1,4 @@ +__author__ = 'Andrew Williams ' +__version__ = '0.1' + +from pynab.budget import Budget diff --git a/pynab/budget.py b/pynab/budget.py new file mode 100644 index 0000000..206f588 --- /dev/null +++ b/pynab/budget.py @@ -0,0 +1,103 @@ +import os +from json import load +import locale +from pynab.exceptions import InvalidBudget + + +class Category(object): + pass + + +class Account(dict): + + @property + def id(self): + return self['entityId'] + + @property + def name(self): + return self['name'] + + @property + def account_type(self): + return self['accountType'] + + @property + def note(self): + return self['note'] + + @property + def hidden(self): + return self['hidden'] + + @property + def on_budget(self): + return self['onBudget'] + + def __unicode__(self): + return u'{} ({})'.format(self['accountName'], self['accountType']) + + def __repr__(self): + return u'<{}>'.format(self.__unicode__()) + + +class Budget(object): + + def __init__(self, filename=None): + self._data = None + if filename: + self.load(filename) + + def load(self, filename): + + # Load the budget's metadata + if not os.path.exists(os.path.join(filename, 'Budget.ymeta')): + raise InvalidBudget('{} is a invalid YNAB budget') + with open(os.path.join(filename, 'Budget.ymeta'), 'r') as f: + meta = load(f) + + # Find the relative folder name + data_folder = os.path.join(filename, meta['relativeDataFolderName']) + + # Check the devices, and see which is tagged with full knowledge + devices_folder = os.path.join(data_folder, 'devices') + for device in os.listdir(devices_folder): + with open(os.path.join(devices_folder, device)) as f: + device_info = load(f) + if device_info['hasFullKnowledge']: + target_folder = device_info['deviceGUID'] + break + else: + raise InvalidBudget('No device has full budget data') + + # Load the full budget file + with open(os.path.join(data_folder, target_folder, 'Budget.yfull')) as f: + self._data = load(f) + + @property + def budget_type(self): + """ + Returnt the budget type + """ + if self._data: + return self._data['budgetMetaData']['budgetType'] + + @property + def currency_locale(self): + """ + Returns the budget's currency locale + """ + if self._data: + return self._data['budgetMetaData']['currencyLocale'] + + @property + def date_locale(self): + """ + Returns the budget's date locale + """ + if self._data: + return self._data['budgetMetaData']['dateLocale'] + + @property + def accounts(self): + return [Account(account) for account in self._data['accounts']] diff --git a/pynab/exceptions.py b/pynab/exceptions.py new file mode 100644 index 0000000..82becd7 --- /dev/null +++ b/pynab/exceptions.py @@ -0,0 +1,8 @@ + + +class InvalidBudget(Exception): + def __init__(self, msg): + self.msg = msg + + def __unicode__(self): + return self.msg \ No newline at end of file diff --git a/pynab/reader.py b/pynab/reader.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/pynab/reader.py @@ -0,0 +1 @@ + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..46e36a6 --- /dev/null +++ b/setup.py @@ -0,0 +1,43 @@ +from __future__ import print_function +from setuptools import setup +import io +import os + +import pynab + +here = os.path.abspath(os.path.dirname(__file__)) + +def read(*filenames, **kwargs): + encoding = kwargs.get('encoding', 'utf-8') + sep = kwargs.get('sep', '\n') + buf = [] + for filename in filenames: + with io.open(filename, encoding=encoding) as f: + buf.append(f.read()) + return sep.join(buf) + +long_description = read('README.md') + + +setup( + name='pynab', + version=pynab.__version__, + url='http://github.com/nikdoof/pynab/', + license='BSD', + author='Andrew Williams', + author_email='andy@tensixtyone.com', + description='YNAB4 budget parser for Python', + long_description=long_description, + packages=['pynab'], + platforms='any', + classifiers = [ + 'Programming Language :: Python', + 'rogramming Language :: Python :: 3', + 'Development Status :: 2 - Pre-Alpha', + 'Natural Language :: English', + 'Intended Audience :: Developers', + 'icense :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], +) \ No newline at end of file