mirror of
https://github.com/nikdoof/pynab.git
synced 2025-12-13 09:42:17 +00:00
Initial commit
This commit is contained in:
56
.gitignore
vendored
Normal file
56
.gitignore
vendored
Normal file
@@ -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/
|
||||||
6
AUTHORS
Normal file
6
AUTHORS
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Andrew Williams <andy@tensixtyone.com>
|
||||||
|
|
||||||
|
Inspiration and file format information from YNAB4 Alfred Workflow add-on, created by:
|
||||||
|
|
||||||
|
James Seward
|
||||||
|
"ppiixx"
|
||||||
12
LICENSE
Normal file
12
LICENSE
Normal file
@@ -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.
|
||||||
26
README.md
Normal file
26
README.md
Normal file
@@ -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```
|
||||||
4
pynab/__init__.py
Normal file
4
pynab/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
__author__ = 'Andrew Williams <andy@tensixtyone.com>'
|
||||||
|
__version__ = '0.1'
|
||||||
|
|
||||||
|
from pynab.budget import Budget
|
||||||
103
pynab/budget.py
Normal file
103
pynab/budget.py
Normal file
@@ -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']]
|
||||||
8
pynab/exceptions.py
Normal file
8
pynab/exceptions.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class InvalidBudget(Exception):
|
||||||
|
def __init__(self, msg):
|
||||||
|
self.msg = msg
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.msg
|
||||||
1
pynab/reader.py
Normal file
1
pynab/reader.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
43
setup.py
Normal file
43
setup.py
Normal file
@@ -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',
|
||||||
|
],
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user