Commits

Dan Connolly committed bacd6a0

finquick is its own project now: https://bitbucket.org/DanC/finquick

27:382a83011bd9 2012-04-21 finquick rename: rename part 4: thorough automated testing
...
21:61d54e75a9a2 2012-04-21 finquick rename part 1: directories
20:f063cde97e20 2012-04-17 integrate mock data to run per development.ini
...
11:0c63625b77bb 2012-04-16 elaborate README with motivation and dependencies
...
5:22f75ddb1982 2012-04-15 basic transaction search includes account names
...
1:21e496d8c16a 2012-04-14 JSON view of gnucash accounts works
0:6b553b57f7af 2012-04-14 toward web app access to gnucash db: pyramid alchemy scaffold

  • Participants
  • Parent commits 1459a71

Comments (0)

Files changed (16)

File finquick/CHANGES.txt

-0.0
----
-
--  Initial version

File finquick/MANIFEST.in

-include *.txt *.ini *.cfg *.rst
-recursive-include finquick *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml

File finquick/README.rst

-finquick -- web app access to gnucash financial data
-===================================================
-
-Support for SQL storage in GnuCash__ 2.4.10 allows reuse of the data
-using Web technologies, without extensive study of the GnuCash source
-code (which is upwards of 10MB, compressed).
-
-__ http://gnucash.org/
-
-Pyramid__ is "a small, fast, down-to-earth Python web application
-development framework." Building a RESTful JSON API to the GnuCash
-data is straightforward using SQLAlchemy__ SQL toolkit, which is
-conveniently packaged in the `alchemy` scaffold__.
-
-__ http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/
-__ http://docs.sqlalchemy.org/
-__ http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/narr/project.html#scaffolds-included-with-pyramid
-
-Building a browser interface to the JSON data is straightforward
-with AngularJS__, a JavaScript framework with two-way databinding,
-
-__ http://docs.angularjs.org/
-
-
-Getting Started
----------------
-
-- cd <directory containing this file>
-
-- `$venv/bin/python setup.py develop` to install dependencies
-  in your development environment.
-
-- `$venv/bin/populate_finquick development.ini` to initialize
-  the database. *I think this is not quite right.*
-
-- `$venv/bin/pserve development.ini` to start the server;
-  it prints a web address at start-up; point your browser there.
-
-**Note Well** I recommend using `--no-site-packages` when you
-set up a virual environment for finquick development, because
-zope.sqlalchemy seems to interact poorly with something otherwise,
-leading to the dreaded `ImportError: No module named sqlalchemy`.
-

File finquick/development.ini

-[app:main]
-use = egg:finquick
-
-pyramid.reload_templates = true
-pyramid.debug_authorization = false
-pyramid.debug_notfound = false
-pyramid.debug_routematch = false
-pyramid.default_locale_name = en
-pyramid.includes =
-    pyramid_tm
-
-sqlalchemy.url = sqlite:///%(here)s/finquick.db
-bootstrap_db = true
-
-[server:main]
-use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, finquick, sqlalchemy
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[logger_finquick]
-level = DEBUG
-handlers =
-qualname = finquick
-
-[logger_sqlalchemy]
-level = INFO
-handlers =
-qualname = sqlalchemy.engine
-# "level = INFO" logs SQL queries.
-# "level = DEBUG" logs SQL queries and results.
-# "level = WARN" logs neither.  (Recommended for production systems.)
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration

File finquick/finquick/__init__.py

-'''__init__ -- finquick package init: build Pyramid WSGI application
-
-'''
-
-# dependencies from pypi; ../README.rst and ../setup.py
-import injector
-from injector import provides, singleton
-from pyramid.config import Configurator
-import sqlalchemy
-
-import models
-import views
-
-
-def main(global_config, **settings):
-    """Build finquick Pyramid WSGI application.
-
-    @see: `paste.app_factory`__
-    __ http://pythonpaste.org/deploy/#paste-app-factory
-
-    @param global_config: DEFAULT settings;
-                          see file:`development.ini`, `production.ini`
-    @param settings: settings for this application
-    @return: a WSGI application.
-    """
-    finquick, config = RunTime.make(settings, [views.FinquickAPI, Configurator])
-    finquick.add_rest_api()
-    return config.make_wsgi_app()
-
-
-class RunTime(injector.Module):
-    '''Use runtime config settings to bootstrap dependency injection.
-    '''
-
-    def __init__(self, settings):
-        '''
-        @param settings: as per `paste.app_factory`
-        '''
-        self._settings = settings
-
-    @singleton
-    @provides(Configurator)
-    def app_settings(self):
-        config = Configurator(settings=self._settings)
-        # hmm... hardcoded 'static' literal...
-        config.add_static_view('static', 'static', cache_max_age=3600)
-        return config
-
-    @singleton
-    @provides(sqlalchemy.engine.Engine)
-    def db(self, section='sqlalchemy.'):
-        e = sqlalchemy.engine_from_config(self._settings, section)
-        if 'bootstrap_db' in self._settings:
-            models.Mock().bootstrap(e)
-        return e
-
-    @classmethod
-    def make(cls, settings, what):
-        '''Given app settings, instantiate classes with dependency injection.
-
-        @param settings: as per `paste.app_factory`
-        @param what: list of classes to instantiate;
-                     use None as list item to get the whole Injector depgraph.
-        '''
-        mods = [cls(settings), models.DBConfig()]
-        depgraph = injector.Injector(mods)
-        return [depgraph.get(it) if it else depgraph
-                for it in what]

File finquick/finquick/dotdict.py

-class dotdict(dict):
-    '''
-    ack: Darugar Oct 2008
-    http://parand.com/say/index.php/2008/10/24/python-dot-notation-dictionary-access/
-    '''
-    def __getattr__(self, attr):
-        return self.get(attr, None)
-    __setattr__= dict.__setitem__
-    __delattr__= dict.__delitem__

File finquick/finquick/models.py

-'''models -- finquick database access
-
-After we mock up a database session with test data...
-
->>> (session, ) = Mock.make([KSession])
-
-... we can query for accounts as per the usual sqlalchemy orm API.
-
-There is always exactly one root account in a GnuCash book.
-.. todo:: cite GnuCash spec that says so.
-
->>> root = session.query(Account).filter(Account.account_type == 'ROOT').one()
-
-Our mock data has one top-level bank:
-
->>> banks = session.query(Account).filter(Account.account_type == 'BANK').all()
->>> banks
-... #doctest: +NORMALIZE_WHITESPACE
-[Account(guid=u'a35af99599ef5adbb8e1904b86ae1f26',
- name=u'Bank X', account_type=u'BANK',
- description=u'', hidden=False, placeholder=False,
- parent_guid=u'934e3c4f6aa55a8faedf160686214cc4')]
-
->>> banks[0].parent is root
-True
-
-'''
-
-import uuid
-import datetime
-
-import injector
-from injector import inject, provides, singleton
-from sqlalchemy import (
-    Column, ForeignKey,
-    Integer, String, Boolean,
-    Date, Text,
-    and_, or_
-    )
-from sqlalchemy import orm, sql, engine, create_engine
-from sqlalchemy.ext.declarative import declarative_base
-from zope.sqlalchemy import ZopeTransactionExtension
-
-from dotdict import dotdict
-
-KSession = injector.Key('Session')
-Base = declarative_base()
-
-
-class DBConfig(injector.Module):
-    '''Provide a transactional session, given an Engine.
-    '''
-    @singleton
-    @provides(KSession)
-    @inject(engine=engine.Engine)
-    def session(self, engine):
-        sm = orm.sessionmaker(extension=ZopeTransactionExtension())
-        s = orm.scoped_session(sm)
-        s.configure(bind=engine)
-        return s
-
-    @classmethod
-    def mods(cls):
-        '''Instantiate this module and its dependencies.
-        '''
-        return [cls()]
-
-
-class GuidMixin(object):
-    '''Provide guid primary key as used by many tables in GnuCash.
-    '''
-    T = String(32)
-    guid = Column(T, primary_key=True)
-
-    @classmethod
-    def fmt(cls, u):
-        '''Compute GnuCash string form from python uuid.
-
-        >>> u = uuid.uuid5(uuid.NAMESPACE_DNS, 'example.org')
-        >>> s = GuidMixin.fmt(u)
-        >>> '-' in s
-        False
-        >>> len(s)
-        32
-        >>> len(s) == GuidMixin.T.length
-        True
-        '''
-        return str(u).replace('-', '')
-
-    def __repr__(self):
-        '''Represent orm objects as useful, deterministic strings.
-
-        >>> class T(Base, GuidMixin):
-        ...     __tablename__ = 'person'
-        ...     name = Column(String)
-        >>> T(guid=_n2g('Bob'), name='Bob')
-        T(guid='8b415c81c3255b6b975a40e0b5cdb699', name='Bob')
-        '''
-        cols = self.__class__.__table__.columns
-        vals = [(c.name, getattr(self, c.name))
-                for c in cols]
-        return '%s(%s)' % (self.__class__.__name__,
-                           ', '.join(['%s=%s' % (n, repr(v))
-                                     for n, v in vals]))
-
-
-def _n2g(name):
-    ns = uuid.NAMESPACE_OID  # a bit of a kludge
-    return GuidMixin.fmt(uuid.uuid5(ns, name))
-
-
-class Account(Base, GuidMixin):
-    __tablename__ = 'accounts'
-    name = Column(String)
-    account_type = Column(String)  # should be Enumeration...
-    description = Column(Text)
-    hidden = Column(Boolean)
-    placeholder = Column(Boolean)
-    parent_guid = Column(String, ForeignKey('accounts.guid'))
-
-    # Self-join relationships default to one-to-many...  "To establish
-    # the relationship as many-to-one, an extra directive is added
-    # known as remote_side..."
-    #
-    # Since guid isn't in local, we exploit: "... remote_side may
-    # also be passed as a callable function which is evaluated at
-    # mapper initialization time."
-    parent = orm.relationship('Account', remote_side=lambda: Account.guid)
-
-
-class Transaction(Base, GuidMixin):
-    __tablename__ = 'transactions'
-    currency_guid = Column(String)
-    num = Column(String)
-    post_date = Column(Date)
-    enter_date = Column(Date)
-    description = Column(String)
-    splits = orm.relationship('Split')
-
-    @classmethod
-    def search_query(cls, session, txt):
-        '''Find transactions by text search in description/memo.
-
-        Given our mock session, with mock data...
-
-        >>> (session, ) = Mock.make([KSession])
-
-        A search query returns the following columns:
-
-        >>> q = Transaction.search_query(session, 'Electric')
-        >>> [d['name'] for d in q.column_descriptions]
-        ... # doctest: +NORMALIZE_WHITESPACE
-        ['post_date', 'description', 'split_guid', 'tx_guid',
-         'account_guid', 'account_name', 'account_type', 'memo',
-         'value_num', 'value_denom']
-
-        Our test data has one transaction with `Electric` in the description:
-
-        >>> rows = q.all()
-        >>> [(r.account_name, r.value_num / r.value_denom) for r in rows]
-        [(u'Bank X', -250), (u'Utilities', 250)]
-        >>> len(set([row.tx_guid for row in rows]))
-        1
-
-        We can find the same transaction by matching a split memo:
-        >>> split_matches = Transaction.search_query(session, 'killowatt')
-        >>> split_matches[0].tx_guid == rows[0].tx_guid
-        True
-
-        .. todo:: search by date, account, and amount as well.
-        '''
-
-        pattern = '%' + txt + '%'
-
-        detail = session.query(Transaction.post_date.label('post_date'),
-                               Transaction.description.label('description'),
-                               Split.guid.label('split_guid'),
-                               Split.tx_guid.label('tx_guid'),
-                               Split.account_guid.label('account_guid'),
-                               Account.name.label('account_name'),
-                               Account.account_type.label('account_type'),
-                               Split.memo.label('memo'),
-                               Split.value_num.label('value_num'),
-                               Split.value_denom.label('value_denom')).filter(
-            and_(Split.tx_guid == Transaction.guid,
-                 Split.account_guid == Account.guid))
-
-        return detail.filter(
-            or_(Transaction.description.like(pattern),
-                sql.exists([Split.guid],
-                           and_(Split.tx_guid == Transaction.guid,
-                                Split.memo.like(pattern))
-                           ).correlate(Transaction.__table__))
-            ).order_by(Transaction.post_date.desc(), Transaction.guid,
-                       Account.account_type, Split.guid)
-
-
-class Split(Base, GuidMixin):
-    __tablename__ = 'splits'
-    tx_guid = Column(String, ForeignKey('transactions.guid'))
-    transaction = orm.relationship('Transaction')
-    account_guid = Column(String, ForeignKey('accounts.guid'))
-    account = orm.relationship('Account')
-    memo = Column(String)
-    #action = Column(String)
-    #reconcile_state = Column(String)
-    #reconcile_date = Column(Date)
-    value_num = Column(Integer)
-    value_denom = Column(Integer)
-    # TODO: derive value, a decimal
-    #quantity_num = Column(Integer)
-    #quantity_denom = Column(Integer)
-    #lot_guid = Column(String)
-
-
-def _fix_date(col, x):
-    if x and col['type'].__class__ == Date:
-        return x.isoformat()
-    else:
-        return x
-
-
-def jrec(rec, col_descs):
-    return dict([(c['name'], _fix_date(c, getattr(rec, c['name'])))
-                 for c in col_descs])
-
-
-class Mock(injector.Module):
-    # TODO: use GnuCash to make a small data set and use CSV files.
-    accounts = (dotdict(name='Root Account', account_type='ROOT', parent=None),
-                dotdict(name='Bank X', account_type='BANK',
-                        parent='Root Account'),
-                dotdict(name='Utilities', account_type='EXPENSE',
-                        parent='Root Account'))
-
-    transactions = [dotdict(post_date=datetime.datetime(2001, 01, 01, 1, 2, 3),
-                            description='Electric company',
-                            guid=_n2g('Electric company'))]
-
-    splits = [dotdict(tx_guid=_n2g('Electric company'),
-                      account_guid=_n2g('Bank X'),
-                      memo='',
-                      guid=_n2g(''),
-                      value_num=-25000,
-                      value_denom=100),
-              dotdict(tx_guid=_n2g('Electric company'),
-                      account_guid=_n2g('Utilities'),
-                      memo='lots o killowatt hours',
-                      guid=_n2g('lots o killowatt hours'),
-                      value_num=25000,
-                      value_denom=100)]
-
-    @classmethod
-    def make(cls, what):
-        mods = [cls()] + DBConfig.mods()
-        depgraph = injector.Injector(mods)
-        return [depgraph.get(it) if it else depgraph
-                for it in what]
-
-    @singleton
-    @provides(engine.Engine)
-    def engine_with_mock_data(self, url='sqlite:///'):
-        engine = create_engine(url)
-        self.bootstrap(engine)
-        return engine
-
-    def bootstrap(self, engine):
-        Base.metadata.create_all(engine)
-        engine.execute(Account.__table__.insert(),
-                       self.mock_accounts())
-        engine.execute(Transaction.__table__.insert(), self.transactions)
-        engine.execute(Split.__table__.insert(), self.splits)
-
-    def mock_accounts(self):
-        return [dict(name=acct.name,
-                     guid=_n2g(acct.name),
-                     account_type=acct.account_type,
-                     parent_guid=_n2g(acct.parent) if acct.parent else None,
-                     description='',
-                     placeholder=False,
-                     hidden=False)
-                for acct in self.accounts]

File finquick/finquick/scripts/__init__.py

-# package

File finquick/finquick/scripts/initializedb.py

-import os
-import sys
-import transaction
-
-from sqlalchemy import engine_from_config
-
-from pyramid.paster import (
-    get_appsettings,
-    setup_logging,
-    )
-
-from ..models import (
-    Base,
-    )
-
-def usage(argv):
-    cmd = os.path.basename(argv[0])
-    print('usage: %s <config_uri>\n'
-          '(example: "%s development.ini")' % (cmd, cmd)) 
-    sys.exit(1)
-
-def main(argv=sys.argv):
-    raise NotImplemented  # We're using gnucash's database
-    if len(argv) != 2:
-        usage(argv)
-    config_uri = argv[1]
-    setup_logging(config_uri)
-    settings = get_appsettings(config_uri)
-    engine = engine_from_config(settings, 'sqlalchemy.')
-    DBSession.configure(bind=engine)
-    Base.metadata.create_all(engine)
-    with transaction.manager:
-        model = MyModel(name='one', value=1)
-        DBSession.add(model)

File finquick/finquick/static/favicon.ico

Removed
Old image

File finquick/finquick/static/fin.js

-// cribbed from http://docs.angularjs.org/#!/tutorial/step_05
-
-angular.service('Account', function($resource) {
-    return $resource('../account/:guid', {}, {
-	query: {method: 'GET', params: {guid: '-'}, isArray: true}
-    });
-});
-
-
-// todo: consider whether global controllers are fine.
-function AccountsCtrl(Account) {
-    var self = this;
-
-    self.accounts = Account.query({}, function(accounts) {
-	var a;
-	var parents = {};
-	var account_index = {}; // by guid
-	var acct;
-
-	roots = angular.Array.filter(accounts,
-				     function(a) {
-					 return a.account_type == 'ROOT'
-				     });
-	self.root = roots[0];  // TODO: what if there is none?
-
-	for (a = 0; a < accounts.length; a++) {
-	    acct = accounts[a];
-	    account_index[acct.guid] = acct;
-	}
-	self.account_index = account_index;
-
-	self.children = function(pacct) {
-	    return angular.Array.filter(
-		accounts,
-		function(ch) {
-		    return ch.parent_guid == pacct.guid;
-		});
-	};
-    });
-}
-AccountsCtrl.$inject = ['Account'];
-
-
-angular.service('Transaction', function($resource) {
-    return $resource('../transaction/:guid', {}, {
-	// override the query method to make sure the trailing / is kept
-	query: {method: 'GET', params: {guid: '-'},
-		isArray: true, verifyCache: true}
-    });
-});
-
-
-function TransactionsCtrl(Transaction, $log) {
-    var self = this;
-
-    self.matches = [{post_date: '2012-01-01',
-		     description: 'fun fun',
-		     splits: [{
-			 memo: 'memo',
-			 account_type: 'BANK',
-			 account_name: 'Friendly Bank',
-			 value_num: 200,
-			 value_denom: 100}]}];
-    self.search = function(qtxt) {
-	self.matches = Transaction.query({q: qtxt});
-    }
-}
-TransactionsCtrl.$inject = ['Transaction', '$log'];
-
-// todo: testing

File finquick/finquick/static/index.html

-<!doctype html>
-<html xmlns:ng="http://angularjs.org">
-  <head>
-    <title>My GnuCash Data</title>
-    <meta charset="utf-8" />
-    <style type="text/css">
-      .money { text-align: right }
-      .transactions th { border-bottom: 2px solid blue }
-      .transactions .search td { border-bottom: 1px solid blue }
-      .transactions .transaction tr:last-child td {
-        border-bottom: 1px solid grey }
-      }
-      .BANK { font-style: bold; color: red }
-    </style>
-  </head>
-  <body>
-    <h1>Finance Fun</h1>
-
-    <div ng:controller="AccountsCtrl">
-      <h2>Accounts</h2>
-
-      <table>
-	<tr><th>Account Name</th><th>Type</th></tr>
-	<tr ng:repeat="account in
-		       children(root).$filter({'hidden': false}).$orderBy('name')">
-	  <td>{{account.name}}</td><td>{{account.account_type}}</td>
-	</tr>
-      </table>
-    </div>
-
-    <div ng:controller="TransactionsCtrl">
-      <h2>Transactions</h2>
-      <table class="transactions">
-	<thead>
-	<tr>
-	  <th>Date</th><th>Description / Memo</th>
-	  <th>Account</th><th>Amount</th>
-	  <th> </th>
-	</tr>
-	</thead>
-	<tbody class="search">
-	  <form ng:submit="search(q)">
-	    <tr>
-	      <td> </td>
-	      <td><input name="q"/></td>
-	      <td> </td>
-	      <td> </td>
-	      <td><input type='submit' value='Search' /></td>
-	    </tr>
-	  </form>
-	</tbody>
-
-	<tbody ng:repeat="tx in matches" class="transaction">
-	  <tr class="transaction">
-	    <td>{{tx.post_date.substring(0, 10) | date:'M/d/yyyy'}}</td>
-	    <td>{{tx.description}}</td>
-	  </tr>
-	  <tr ng:repeat="d in tx.splits">
-	    <td>&#160;</td>
-	    <td>{{d.memo}}</td>
-	    <td ng:class="d.account_type">{{d.account_name}}</td>
-	    <td class="money">{{d.value_num / d.value_denom | currency }}</td>
-	  </tr>
-	</tbody>
-
-      </table>
-    </div>
-
-    <script src="http://code.angularjs.org/angular-0.9.12.js" ng:autobind></script>
-    <script src="fin.js" type="text/javascript"></script>
-  </body>
-</html>
-<!--
-cribbed from
-http://docs.angularjs.org/#!/misc/downloading
--->

File finquick/finquick/views.py

-'''views -- finquick REST API
-'''
-
-from itertools import groupby
-
-from injector import inject
-from pyramid.config import Configurator
-from pyramid.response import Response
-from sqlalchemy.exc import DBAPIError
-
-from dotdict import dotdict
-from models import (
-    Account, Transaction,
-    KSession
-    )
-
-
-class JSONDBView(object):
-    '''View to access DB and render to JSON.
-    '''
-    @inject(session=KSession)
-    def __init__(self, session):
-        self._session = session
-
-    def config(self, config, route_name):
-        '''Add this view to a Pyramid Configurator.
-        '''
-        config.add_view(self, route_name=route_name, renderer='json')
-
-    def __call__(self, request):
-        '''This class is abstract; subclass must implement __call__.
-        '''
-        raise NotImplemented
-
-
-class AccountsList(JSONDBView):
-    def __call__(self, request):
-        try:
-            accounts = self._session.query(Account)
-        except DBAPIError:
-            return DBFailHint
-        cols = Account.__table__.columns
-        return [dict([(c.name, getattr(acct, c.name)) for c in cols])
-                for acct in accounts]
-
-
-class TransactionsQuery(JSONDBView):
-    '''
-    .. todo:: query by date, account, amount as well as description/memo
-    '''
-    description_memo_query_param = 'q'
-
-    def __call__(self, request, limit=200):
-        q = request.params.get(self.description_memo_query_param, None)
-        if q is None:
-            return Response('missing q param', content_type='text/plain',
-                            status_int = 400)
-
-        dbq = Transaction.search_query(self._session, q)
-        try:
-            matches = dbq[:limit]
-        except DBAPIError:
-            return DBFailHint
-
-        return [split_denorm(tx_guid, list(split_details))
-                for (tx_guid, split_details)
-                in groupby(matches, lambda m: m.tx_guid)]
-
-
-def split_denorm(tx_guid, split_details):
-    '''De-normalize transaction split info.
-
-    >>> import datetime, pprint
-    >>> when = datetime.datetime(2001, 01, 01, 1, 2, 3)
-    >>> o = split_denorm('tx123',
-    ...         [dotdict(post_date=when, description='fun fun',
-    ...                  split_guid='s456', account_guid='a678',
-    ...                  memo='', tx_guid='tx123',
-    ...                  account_name='Bank X', account_type='BANK',
-    ...                  value_num=-35000, value_denom=100),
-    ...          dotdict(post_date=when, description='fun fun',
-    ...                  split_guid='s654', account_guid='a876',
-    ...                  memo='electric bill', tx_guid='tx123',
-    ...                  account_name='Utilities', account_type='EXPENSE',
-    ...                  value_num=35000, value_denom=100)])
-    >>> pprint.pprint(o)
-    {'description': 'fun fun',
-     'post_date': '2001-01-01T01:02:03',
-     'splits': [{'account_guid': 'a678',
-                 'account_name': 'Bank X',
-                 'account_type': 'BANK',
-                 'memo': '',
-                 'split_guid': 's456',
-                 'value_denom': 100,
-                 'value_num': -35000},
-                {'account_guid': 'a876',
-                 'account_name': 'Utilities',
-                 'account_type': 'EXPENSE',
-                 'memo': 'electric bill',
-                 'split_guid': 's654',
-                 'value_denom': 100,
-                 'value_num': 35000}],
-     'tx_guid': 'tx123'}
-    '''
-    return dict(tx_guid=tx_guid,
-                post_date=split_details[0].post_date.isoformat(),
-                description=split_details[0].description,
-                splits=[
-            dict(split_guid=d.split_guid,
-                 account_guid=d.account_guid,
-                 memo=d.memo,
-                 account_name=d.account_name,
-                 account_type=d.account_type,
-                 value_num=d.value_num,
-                 value_denom=d.value_denom)
-            for d in split_details])
-
-
-conn_err_msg = """\
-Pyramid is having a problem using your SQL database.  The problem
-might be caused by one of the following things:
-
-1.  You may need to run the "initialize_finquick_db" script
-    to initialize your database tables.  Check your virtual 
-    environment's "bin" directory for this script and try to run it.
-
-2.  Your database server may not be running.  Check that the
-    database server referred to by the "sqlalchemy.url" setting in
-    your "development.ini" file is running.
-
-After you fix the problem, please restart the Pyramid application to
-try it again.
-"""
-
-DBFailHint = Response(conn_err_msg,
-                      content_type='text/plain', status_int=500)
-
-
-class FinquickAPI(object):
-    '''Finquick REST/JSON API configuration.
-
-    .. todo:: Relax paths. As is, `/account` and `/account/` give 404;
-              some text matching {guid} is required, e.g. `/account/-` .
-    '''
-    account_route = dotdict(name='account', path='/account/{guid}')
-    transaction_route = dotdict(name='transaction', path='/transaction/{guid}')
-
-    @inject(config=Configurator,
-            av=AccountsList,
-            tqv=TransactionsQuery)
-    def __init__(self, config, av, tqv):
-        self._config = config
-        self._account_view = av
-        self._transaction_view = tqv
-
-    def add_rest_api(self):
-        for (rt, view) in (
-            (self.account_route, self._account_view),
-            (self.transaction_route, self._transaction_view)):
-            self._config.add_route(rt.name, rt.path)
-            view.config(self._config, rt.name)

File finquick/production.ini

-[app:main]
-use = egg:finquick
-
-pyramid.reload_templates = false
-pyramid.debug_authorization = false
-pyramid.debug_notfound = false
-pyramid.debug_routematch = false
-pyramid.default_locale_name = en
-pyramid.includes =
-    pyramid_tm
-
-sqlalchemy.url = sqlite:///%(here)s/finquick.db
-
-[server:main]
-use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, finquick, sqlalchemy
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = WARN
-handlers = console
-
-[logger_finquick]
-level = WARN
-handlers =
-qualname = finquick
-
-[logger_sqlalchemy]
-level = WARN
-handlers =
-qualname = sqlalchemy.engine
-# "level = INFO" logs SQL queries.
-# "level = DEBUG" logs SQL queries and results.
-# "level = WARN" logs neither.  (Recommended for production systems.)
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration

File finquick/setup.cfg

-[nosetests]
-match=^test
-nocapture=1
-cover-package=finquick
-with-coverage=1
-cover-erase=1
-
-[compile_catalog]
-directory = finquick/locale
-domain = finquick
-statistics = true
-
-[extract_messages]
-add_comments = TRANSLATORS:
-output_file = finquick/locale/finquick.pot
-width = 80
-
-[init_catalog]
-domain = finquick
-input_file = finquick/locale/finquick.pot
-output_dir = finquick/locale
-
-[update_catalog]
-domain = finquick
-input_file = finquick/locale/finquick.pot
-output_dir = finquick/locale
-previous = true

File finquick/setup.py

-'''setup -- package dependencies for finquick
-
-per `The Hitchhiker's Guide to Packaging`__ and PasteDeploy__.
-
-__ http://guide.python-distribute.org/
-__ http://pythonpaste.org/deploy/
-'''
-import os
-
-from setuptools import setup, find_packages
-
-here = os.path.abspath(os.path.dirname(__file__))
-README = open(os.path.join(here, 'README.rst')).read()
-CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
-
-requires = [
-    'injector',
-    'pyramid',
-    'SQLAlchemy',
-    'transaction',
-    'pyramid_tm',
-    'zope.sqlalchemy',
-    'waitress',
-    'MySQL-python'
-    ]
-
-setup(name='finquick',
-      version='0.1',
-      description='finquick',
-      long_description=README + '\n\n' +  CHANGES,
-      classifiers=[
-        "Programming Language :: Python",
-        "Programming Language :: JavaScript",
-        "Framework :: Pylons",
-        "Topic :: Internet :: WWW/HTTP",
-        "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
-        "Topic :: Office/Business :: Financial :: Accounting",
-        "Environment :: Web Environment",
-        "Intended Audience :: End Users/Desktop",
-        "License :: OSI Approved",
-        "License :: OSI Approved :: Apache Software License",
-        ],
-      author='Dan Connolly',
-      author_email='dckc@madmode.com',
-      url='http://www.madmode.com/',
-      keywords='web wsgi bfg pylons pyramid gnucash',
-      packages=find_packages(),
-      include_package_data=True,
-      zip_safe=False,
-      test_suite='finquick',
-      install_requires=requires,
-      entry_points="""\
-      [paste.app_factory]
-      main = finquick:main
-      [console_scripts]
-      initialize_finquick_db = finquick.scripts.initializedb:main
-      """,
-      )
-