#!/usr/bin/python
# Copyright 2014 Tyler Fenby
#
# This file is part of PySimple.
#
# PySimple is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PySimple 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 PySimple. If not, see <http://www.gnu.org/licenses/>.
import requests
from lxml import html
from pysimple import exceptions
API = 'https://bank.simple.com'
[docs]class Account(object):
"""A Simple account
Main entrypoint for the library
"""
def __init__(self, username, password):
self._username = username
self._password = password
self._session = requests.Session()
self._csrf = None
[docs] def login(self):
"""Log in to the bank via the API
Must be done before any other commands will work
:raises exceptions.LoginError: On any error in the request
"""
getresp = self._session.get(API + '/signin')
doc = html.fromstring(getresp.text)
self._csrf = doc.xpath('//meta[@name="_csrf"]')[0].attrib['content']
postresp = self._session.post(API + '/signin',
data={
'username': self._username,
'password': self._password,
'_csrf': self._csrf,
})
if postresp.url != 'https://bank.simple.com/activity':
raise exceptions.LoginError
@staticmethod
[docs] def dollars_to_mills(dollars):
"""Convert dollars to mills, which Simple uses
:param dollars: Amount to convert
:type dollars: int, float
:returns: Input amount, in mills
:rtype: int
"""
return int(dollars * 10000)
@staticmethod
[docs] def mills_to_dollars(mills):
"""Convert mills back to dollars
:param int mills: Amount to convert
:returns: Input amount, in mills
:rtype: float
"""
return mills / 10000.0
[docs] def all_goals(self):
"""Returns all (active and archived) goals, raw from the API
"""
resp = self._session.get(API + '/goals/data')
return resp.json()
[docs] def goals(self):
"""Return a list of active goals in a dict w/ their names as the key"""
all_goals = self.all_goals()
active = [x for x in all_goals if x['archived'] is False]
by_name = {x['name']: x for x in active}
return by_name
[docs] def transactions(self):
resp = self._session.get(API + '/transactions/data')
return resp.json()
[docs] def balances(self):
resp = self._session.get(API + '/account/balances')
return resp.json()
[docs] def transfer_between_goals(self, from_id, to_id, amount):
"""Transfer between two goals
:param from_id: ID of the source goal
:param to_id: ID of the dest goal
:param amount: Amount, in dollars, to transfer
:raises requests.exceptions.RequestException: On any error
"""
resp = self._session.post(API + '/goals/transfers',
data={
'from_goal_id': from_id,
'to_goal_id': to_id,
'amount': self.dollars_to_mills(amount),
'_csrf': self._csrf,
})
resp.raise_for_status()
[docs] def transfer_between_sts(self, to_id, amount):
"""Transfer between Safe-to-Spend and a goal
A positive `amount` will transfer from StS to the goal,
and a negative amount will do the opposite.
:param amount: Amount, in dollars, to transfer
:type amount: int, float
:raises requests.exceptions.RequestException: On any error
"""
resp = self._session.post(API + '/goals/%s/transactions' % to_id,
data={
'amount': self.dollars_to_mills(amount),
'_csrf': self._csrf,
})
resp.raise_for_status()