Changeset - fc2c3a6b4339
[Not reviewed]
0 13 0
Joar Wandborg - 10 years ago 2013-12-17 14:41:30
joar@wandborg.se
[license] Added notice to all python files
13 files changed with 52 insertions and 0 deletions:
0 comments (0 inline, 0 general)
accounting/__init__.py
Show inline comments
 
# Part of accounting-api project:
 
# https://gitorious.org/conservancy/accounting-api
 
# License: AGPLv3-or-later
 

	
accounting/client.py
Show inline comments
 
# Part of accounting-api project:
 
# https://gitorious.org/conservancy/accounting-api
 
# License: AGPLv3-or-later
 

	
 
import sys
 
import argparse
 
import json
 
import logging
 
import locale
 

	
 
from datetime import datetime
 
from decimal import Decimal
 

	
 
import requests
 

	
 
from accounting.models import Transaction, Posting, Amount
 
from accounting.transport import AccountingDecoder, AccountingEncoder
 

	
 
locale.setlocale(locale.LC_ALL, '')
 

	
 
_log = logging.getLogger(__name__)
 

	
 

	
 
class Client:
 
    def __init__(self, host=None, json_encoder=None,
 
                 json_decoder=None):
 
        self.host = host or 'http://localhost:5000'
 
        self.json_encoder = json_encoder or AccountingEncoder
 
        self.json_decoder = json_decoder or AccountingDecoder
 

	
 
    def get_balance(self):
 
        balance = self.get('/balance')
 
        return balance['balance_report']
 

	
 
    def get(self, path):
 
        response = requests.get(self.host + path)
 

	
 
        return self._decode_response(response)
 

	
 
    def _decode_response(self, response):
 
        response_data = response.json(cls=self.json_decoder)
 

	
 
        _log.debug('response_data: %s', response_data)
 

	
 
        return response_data
 

	
 
    def post(self, path, payload, **kw):
 
        kw.update({'headers': {'Content-Type': 'application/json'}})
 
        kw.update({'data': json.dumps(payload, cls=self.json_encoder)})
 

	
 
        return self._decode_response(requests.post(self.host + path, **kw))
 

	
accounting/config.py
Show inline comments
 
# Part of accounting-api project:
 
# https://gitorious.org/conservancy/accounting-api
 
# License: AGPLv3-or-later
 

	
 
import os
 

	
 
LEDGER_FILE = os.environ.get('LEDGER_FILE', None)
 
DEBUG = bool(int(os.environ.get('DEBUG', 0)))
 
PORT = int(os.environ.get('PORT', 5000))
 
HOST = os.environ.get('HOST', '127.0.0.1')
 
SQLALCHEMY_DATABASE_URI = os.environ.get(
 
    'DATABASE_URI',
 
    'sqlite:///../accounting-api.sqlite')
accounting/decorators.py
Show inline comments
 
# Part of accounting-api project:
 
# https://gitorious.org/conservancy/accounting-api
 
# License: AGPLv3-or-later
 

	
 
from functools import wraps
 

	
 
from flask import jsonify
 

	
 
from accounting.exceptions import AccountingException
 

	
 

	
 
def jsonify_exceptions(func):
 
    '''
 
    Wraps a Flask endpoint and catches any AccountingException-based
 
    exceptions which are returned to the client as JSON.
 
    '''
 
    @wraps(func)
 
    def wrapper(*args, **kw):
 
        try:
 
            return func(*args, **kw)
 
        except AccountingException as exc:
 
            return jsonify(error=exc)
 

	
 
    return wrapper
accounting/exceptions.py
Show inline comments
 
# Part of accounting-api project:
 
# https://gitorious.org/conservancy/accounting-api
 
# License: AGPLv3-or-later
 

	
 
class AccountingException(Exception):
 
    '''
 
    Used as a base for exceptions that are returned to the caller via the
 
    jsonify_exceptions decorator
 
    '''
 
    pass
accounting/gtkclient.py
Show inline comments
 
# Part of accounting-api project:
 
# https://gitorious.org/conservancy/accounting-api
 
# License: AGPLv3-or-later
 

	
 
import sys
 
import logging
 
import threading
 
import pkg_resources
 

	
 
from functools import wraps
 
from datetime import datetime
 

	
 
from gi.repository import Gtk
 
from gi.repository import GLib
 
from gi.repository import GObject
 

	
 
from accounting.client import Client
 

	
 
_log = logging.getLogger(__name__)
 

	
 

	
 
def indicate_activity(func_or_str):
 
    description = None
 

	
 
    def decorator(func):
 
        @wraps(func)
 
        def wrapper(self, *args, **kw):
 
            self.activity_description.set_text(description)
 
            self.activity_indicator.show()
 
            self.activity_indicator.start()
 

	
 
            return func(self, *args, **kw)
 

	
 
        return wrapper
 

	
 
    if callable(func_or_str):
 
        description = 'Working'
 
        return decorator(func_or_str)
 
    else:
 
        description = func_or_str
 
        return decorator
 

	
 

	
 
def indicate_activity_done(func):
 
    @wraps(func)
 
    def wrapper(self, *args, **kw):
 
        self.activity_description.set_text('')
 
        self.activity_indicator.stop()
 
        self.activity_indicator.hide()
 

	
 
        return func(self, *args, **kw)
 

	
accounting/models.py
Show inline comments
 
# Part of accounting-api project:
 
# https://gitorious.org/conservancy/accounting-api
 
# License: AGPLv3-or-later
 

	
 
import uuid
 
from decimal import Decimal
 

	
 

	
 
class Transaction:
 
    def __init__(self, id=None, date=None, payee=None, postings=None,
 
                 metadata=None, _generate_id=False):
 
        self.id = id
 
        self.date = date
 
        self.payee = payee
 
        self.postings = postings
 
        self.metadata = metadata if metadata is not None else {}
 

	
 
        if _generate_id:
 
            self.generate_id()
 

	
 
    def generate_id(self):
 
        self.id = str(uuid.uuid4())
 

	
 
    def __repr__(self):
 
        return ('<{self.__class__.__name__} {self.id} {date}' +
 
                ' {self.payee} {self.postings}').format(
 
                    self=self,
 
                    date=self.date.strftime('%Y-%m-%d'))
 

	
 

	
 
class Posting:
 
    def __init__(self, account=None, amount=None, metadata=None):
 
        self.account = account
 
        self.amount = amount
 
        self.metadata = metadata if metadata is not None else {}
 

	
 
    def __repr__(self):
 
        return ('<{self.__class__.__name__} "{self.account}"' +
 
                ' {self.amount}>').format(self=self)
 

	
 

	
 
class Amount:
 
    def __init__(self, amount=None, symbol=None):
 
        self.amount = Decimal(amount)
 
        self.symbol = symbol
 

	
 
    def __repr__(self):
 
        return ('<{self.__class__.__name__} {self.symbol}' +
 
                ' {self.amount}>').format(self=self)
 

	
 

	
 
class Account:
accounting/storage/__init__.py
Show inline comments
 
# Part of accounting-api project:
 
# https://gitorious.org/conservancy/accounting-api
 
# License: AGPLv3-or-later
 

	
 
from abc import ABCMeta, abstractmethod
 

	
 

	
 
class Storage():
 
    '''
 
    ABC for accounting storage
 
    '''
 
    __metaclass__ = ABCMeta
 

	
 
    def __init__(self, *args, **kw):
 
        pass
 

	
 
    @abstractmethod
 
    def get_transactions(self, *args, **kw):
 
        raise NotImplementedError
 

	
 
    @abstractmethod
 
    def get_transaction(self, *args, **kw):
 
        raise NotImplementedError
 

	
 
    @abstractmethod
 
    def get_account(self, *args, **kw):
 
        raise NotImplementedError
 

	
 
    @abstractmethod
 
    def get_accounts(self, *args, **kw):
 
        raise NotImplementedError
 

	
 
    @abstractmethod
 
    def add_transaction(self, transaction):
 
        raise NotImplementedError
 

	
 
    @abstractmethod
 
    def update_transaction(self, transaction):
 
        raise NotImplementedError
 

	
 
    @abstractmethod
 
    def reverse_transaction(self, transaction_id):
 
        raise NotImplementedError
accounting/storage/ledgercli.py
Show inline comments
 
# Part of accounting-api project:
 
# https://gitorious.org/conservancy/accounting-api
 
# License: AGPLv3-or-later
 

	
 
import sys
 
import subprocess
 
import logging
 
import time
 

	
 
from datetime import datetime
 
from xml.etree import ElementTree
 
from contextlib import contextmanager
 

	
 
from accounting.models import Account, Transaction, Posting, Amount
 
from accounting.storage import Storage
 

	
 
_log = logging.getLogger(__name__)
 

	
 

	
 
class Ledger(Storage):
 
    def __init__(self, app=None, ledger_file=None, ledger_bin=None):
 
        if app:
 
            ledger_file = app.config['LEDGER_FILE']
 

	
 
        if ledger_file is None:
 
            raise ValueError('ledger_file cannot be None')
 

	
 
        self.ledger_bin = ledger_bin or 'ledger'
 
        self.ledger_file = ledger_file
 
        _log.info('ledger file: %s', ledger_file)
 

	
 
        self.locked = False
 
        self.ledger_process = None
 

	
 
    @contextmanager
 
    def locked_process(self):
 
        r'''
 
        Context manager that checks that the ledger process is not already
 
        locked, then "locks" the process and yields the process handle and
 
        unlocks the process when execution is returned.
 

	
 
        Since this decorated as a :func:`contextlib.contextmanager` the
 
        recommended use is with the ``with``-statement.
 

	
 
        .. code-block:: python
 

	
 
            with self.locked_process() as p:
 
                p.stdin.write(b'bal\n')
 

	
 
                output = self.read_until_prompt(p)
 

	
 
        '''
accounting/storage/sql/__init__.py
Show inline comments
 
# Part of accounting-api project:
 
# https://gitorious.org/conservancy/accounting-api
 
# License: AGPLv3-or-later
 

	
 
import logging
 
import json
 

	
 
from flask.ext.sqlalchemy import SQLAlchemy
 

	
 
from accounting.exceptions import AccountingException
 
from accounting.storage import Storage
 
from accounting.models import Transaction, Posting, Amount
 

	
 
_log = logging.getLogger(__name__)
 
db = SQLAlchemy()
 

	
 

	
 
class SQLStorage(Storage):
 
    def __init__(self, app=None):
 

	
 
        if not app:
 
            raise Exception('Missing app keyword argument')
 

	
 
        self.app = app
 
        db.init_app(app)
 

	
 
        from .models import Transaction as SQLTransaction, \
 
            Posting as SQLPosting, Amount as SQLAmount
 

	
 
        db.create_all()
 

	
 
        self.Transaction = SQLTransaction
 
        self.Posting = SQLPosting
 
        self.Amount = SQLAmount
 

	
 
    def get_transactions(self, *args, **kw):
 
        transactions = []
 

	
 
        for transaction in self.Transaction.query.all():
 
            dict_transaction = transaction.as_dict()
 
            dict_postings = dict_transaction.pop('postings')
 

	
 
            postings = []
 

	
 
            for dict_posting in dict_postings:
 
                dict_amount = dict_posting.pop('amount')
 
                posting = Posting(**dict_posting)
 
                posting.amount = Amount(**dict_amount)
 

	
 
                postings.append(posting)
 

	
 
            dict_transaction.update({'postings': postings})
accounting/storage/sql/models.py
Show inline comments
 
# Part of accounting-api project:
 
# https://gitorious.org/conservancy/accounting-api
 
# License: AGPLv3-or-later
 

	
 
import json
 

	
 
from . import db
 

	
 

	
 
class Transaction(db.Model):
 
    id = db.Column(db.Integer(), primary_key=True)
 
    uuid = db.Column(db.String, unique=True, nullable=False)
 
    date = db.Column(db.DateTime)
 
    payee = db.Column(db.String())
 
    meta = db.Column(db.String())
 

	
 
    def as_dict(self):
 
        return dict(
 
            id=self.uuid,
 
            date=self.date,
 
            payee=self.payee,
 
            postings=[p.as_dict() for p in self.postings],
 
            metadata=json.loads(self.meta)
 
        )
 

	
 

	
 
class Posting(db.Model):
 
    id = db.Column(db.Integer(), primary_key=True)
 

	
 
    transaction_uuid = db.Column(db.String, db.ForeignKey('transaction.uuid'))
 
    transaction = db.relationship('Transaction', backref='postings')
 

	
 
    account = db.Column(db.String, nullable=False)
 

	
 
    amount_id = db.Column(db.Integer, db.ForeignKey('amount.id'))
 
    amount = db.relationship('Amount')
 

	
 
    meta = db.Column(db.String)
 

	
 
    def as_dict(self):
 
        return dict(
 
            account=self.account,
 
            amount=self.amount.as_dict(),
 
            metadata=json.loads(self.meta)
 
        )
 

	
 

	
 
class Amount(db.Model):
 
    id = db.Column(db.Integer, primary_key=True)
 
    symbol = db.Column(db.String)
 
    amount = db.Column(db.Numeric)
 

	
accounting/transport.py
Show inline comments
 
# Part of accounting-api project:
 
# https://gitorious.org/conservancy/accounting-api
 
# License: AGPLv3-or-later
 

	
 
from datetime import datetime
 

	
 
from flask import json
 

	
 
from accounting.models import Amount, Transaction, Posting, Account
 

	
 

	
 
class AccountingEncoder(json.JSONEncoder):
 
    def default(self, o):
 
        if isinstance(o, Account):
 
            return dict(
 
                __type__=o.__class__.__name__,
 
                name=o.name,
 
                amounts=o.amounts,
 
                accounts=o.accounts
 
            )
 
        elif isinstance(o, Transaction):
 
            return dict(
 
                __type__=o.__class__.__name__,
 
                id=o.id,
 
                date=o.date.strftime('%Y-%m-%d'),
 
                payee=o.payee,
 
                postings=o.postings,
 
                metadata=o.metadata
 
            )
 
        elif isinstance(o, Posting):
 
            return dict(
 
                __type__=o.__class__.__name__,
 
                account=o.account,
 
                amount=o.amount,
 
                metadata=o.metadata
 
            )
 
        elif isinstance(o, Amount):
 
            return dict(
 
                __type__=o.__class__.__name__,
 
                amount=str(o.amount),
 
                symbol=o.symbol
 
            )
 
        elif isinstance(o, Exception):
 
            return dict(
 
                __type__=o.__class__.__name__,
 
                args=o.args
 
            )
 

	
 
        return json.JSONEncoder.default(self, o)
 

	
 

	
 
class AccountingDecoder(json.JSONDecoder):
accounting/web.py
Show inline comments
 
# Part of accounting-api project:
 
# https://gitorious.org/conservancy/accounting-api
 
# License: AGPLv3-or-later
 

	
 
'''
 
This module contains the high-level webservice logic such as the Flask setup
 
and the Flask endpoints.
 
'''
 
import sys
 
import logging
 
import argparse
 

	
 
from flask import Flask, jsonify, request
 
from flask.ext.sqlalchemy import SQLAlchemy
 
from flask.ext.script import Manager
 
from flask.ext.migrate import Migrate, MigrateCommand
 

	
 
from accounting.storage import Storage
 
from accounting.storage.ledgercli import Ledger
 
from accounting.storage.sql import SQLStorage
 
from accounting.transport import AccountingEncoder, AccountingDecoder
 
from accounting.exceptions import AccountingException
 
from accounting.decorators import jsonify_exceptions
 

	
 

	
 
app = Flask('accounting')
 
app.config.from_pyfile('config.py')
 

	
 
storage = Storage()
 

	
 
if isinstance(storage, SQLStorage):
 
    # TODO: Move migration stuff into SQLStorage
 
    db = storage.db
 
    migrate = Migrate(app, db)
 

	
 
    manager = Manager(app)
 
    manager.add_command('db', MigrateCommand)
 

	
 

	
 
@app.before_request
 
def init_ledger():
 
    '''
 
    :py:meth:`flask.Flask.before_request`-decorated method that initializes an
 
    :py:class:`accounting.Ledger` object.
 
    '''
 
    global ledger
 
    #ledger = Ledger(ledger_file=app.config['LEDGER_FILE'])
 

	
 

	
 
# These will convert output from our internal classes to JSON and back
 
app.json_encoder = AccountingEncoder
 
app.json_decoder = AccountingDecoder
0 comments (0 inline, 0 general)