[storage] Added delete_transaction method

- Added storage.ledgercli implementation
- Added storage.ledgercli update_transaction
- Added storage.ledgercli get_transaction
- Pushing pre-built docs
This commit is contained in:
Joar Wandborg 2013-12-18 22:02:03 +01:00
parent c80955f199
commit 02fc05aebd
8 changed files with 191 additions and 14 deletions

View file

@ -4,8 +4,10 @@
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from accounting.exceptions import AccountingException
class Storage():
class Storage:
''' '''
ABC for accounting storage ABC for accounting storage
''' '''
@ -38,6 +40,14 @@ class Storage():
def update_transaction(self, transaction): def update_transaction(self, transaction):
raise NotImplementedError raise NotImplementedError
@abstractmethod
def delete_transaction(self, transaction_id):
raise NotImplementedError
@abstractmethod @abstractmethod
def reverse_transaction(self, transaction_id): def reverse_transaction(self, transaction_id):
raise NotImplementedError raise NotImplementedError
class TransactionNotFound(AccountingException):
pass

View file

@ -6,13 +6,15 @@ import sys
import subprocess import subprocess
import logging import logging
import time import time
import re
from datetime import datetime from datetime import datetime
from xml.etree import ElementTree from xml.etree import ElementTree
from contextlib import contextmanager from contextlib import contextmanager
from accounting.exceptions import AccountingException
from accounting.models import Account, Transaction, Posting, Amount from accounting.models import Account, Transaction, Posting, Amount
from accounting.storage import Storage from accounting.storage import Storage, TransactionNotFound
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
@ -205,6 +207,8 @@ class Ledger(Storage):
_log.debug('written to file: %s', output) _log.debug('written to file: %s', output)
return transaction.id
def bal(self): def bal(self):
output = self.send_command('xml') output = self.send_command('xml')
@ -252,6 +256,16 @@ class Ledger(Storage):
def get_transactions(self): def get_transactions(self):
return self.reg() return self.reg()
def get_transaction(self, transaction_id):
transactions = self.get_transactions()
for transaction in transactions:
if transaction.id == transaction_id:
return transaction
raise TransactionNotFound('No transaction with id %s found',
transaction_id)
def reg(self): def reg(self):
output = self.send_command('xml') output = self.send_command('xml')
@ -314,8 +328,111 @@ class Ledger(Storage):
return entries return entries
def delete_transaction(self, transaction_id):
'''
Delete a transaction from the ledger file.
This method opens the ledger file, loads all lines into memory and
looks for the transaction_id, then looks for the bounds of that
transaction in the ledger file, removes all lines within the bounds of
the transaction and removes them, then writes the lines back to the
ledger file.
Exceptions:
- RuntimeError: If all boundaries to the transaction are not found
- TransactionNotFound: If no such transaction_id can be found in
:data:`self.ledger_file`
'''
f = open(self.ledger_file, 'r')
lines = [i for i in f]
# A mapping of line meanings and their line numbers as found by the
# following logic
semantic_lines = dict(
id_location=None,
transaction_start=None,
next_transaction_or_eof=None
)
for i, line in enumerate(lines):
if transaction_id in line:
semantic_lines['id_location'] = i
break
if not semantic_lines['id_location']:
raise TransactionNotFound('No transaction with ID "%s" found')
transaction_start_pattern = re.compile(r'^\S')
cursor = semantic_lines['id_location'] - 1
# Find the first line of the transaction
while True:
if transaction_start_pattern.match(lines[cursor]):
semantic_lines['transaction_start'] = cursor
break
cursor -= 1
cursor = semantic_lines['id_location'] + 1
# Find the last line of the transaction
while True:
try:
if transaction_start_pattern.match(lines[cursor]):
semantic_lines['next_transaction_or_eof'] = cursor
break
except IndexError:
# Set next_line_without_starting_space_or_end_of_file to
# the cursor. The cursor will be an index not included in the
# list of lines
semantic_lines['next_transaction_or_eof'] = cursor
break
cursor += 1
if not all(map(lambda v: v is not None, semantic_lines.values())):
raise RuntimeError('Could not find all the values necessary for'
' safe deletion of a transaction.')
del_start = semantic_lines['transaction_start']
if len(lines) == semantic_lines['next_transaction_or_eof']:
_log.debug('There are no transactions below the transaction being'
' deleted. The line before the first line of the'
' transaction will be deleted.')
# Delete the preceding line to make the file
del_start -= 1
del lines[del_start:semantic_lines['next_transaction_or_eof']]
with open(self.ledger_file, 'w') as f:
for line in lines:
f.write(line)
def update_transaction(self, transaction): def update_transaction(self, transaction):
_log.debug('DUMMY: Updated transaction: %s', transaction) '''
Update a transaction in the ledger file.
Takes a :class:`~accounting.models.Transaction` object and removes
the old transaction using :data:`transaction.id` from the passed
:class:`~accounting.models.Transaction` instance and adds
:data:`transaction` to the database.
'''
if not transaction.id:
return AccountingException('The transaction %s has no'
' id attribute', transaction)
old_transaction = self.get_transaction(transaction.id)
self.delete_transaction(transaction.id)
self.add_transaction(transaction)
_log.debug('Updated transaction from: %s to: %s', old_transaction,
transaction)
def main(argv=None): def main(argv=None):

View file

@ -11,7 +11,6 @@ import logging
import argparse import argparse
from flask import Flask, jsonify, request from flask import Flask, jsonify, request
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.script import Manager from flask.ext.script import Manager
from flask.ext.migrate import Migrate, MigrateCommand from flask.ext.migrate import Migrate, MigrateCommand
@ -65,6 +64,7 @@ def transaction_get():
''' '''
return jsonify(transactions=storage.get_transactions()) return jsonify(transactions=storage.get_transactions())
@app.route('/transaction/<string:transaction_id>', methods=['POST']) @app.route('/transaction/<string:transaction_id>', methods=['POST'])
@jsonify_exceptions @jsonify_exceptions
def transaction_update(transaction_id=None): def transaction_update(transaction_id=None):
@ -74,8 +74,8 @@ def transaction_update(transaction_id=None):
transaction = request.json['transaction'] transaction = request.json['transaction']
if transaction.id is not None and not transaction.id == transaction_id: if transaction.id is not None and not transaction.id == transaction_id:
raise AccountingException('The transaction data has an ID attribute and' raise AccountingException('The transaction data has an ID attribute'
' it is not the same ID as in the path') ' and it is not the same ID as in the path')
elif transaction.id is None: elif transaction.id is None:
transaction.id = transaction_id transaction.id = transaction_id
@ -84,6 +84,17 @@ def transaction_update(transaction_id=None):
return jsonify(status='OK') return jsonify(status='OK')
@app.route('/transaction/<string:transaction_id>', methods=['DELETE'])
@jsonify_exceptions
def transaction_delete(transaction_id=None):
if transaction_id is None:
raise AccountingException('Transaction ID cannot be None')
storage.delete_transaction(transaction_id)
return jsonify(status='OK')
@app.route('/transaction', methods=['POST']) @app.route('/transaction', methods=['POST'])
@jsonify_exceptions @jsonify_exceptions
def transaction_post(): def transaction_post():
@ -139,10 +150,12 @@ def transaction_post():
if not transactions: if not transactions:
raise AccountingException('No transaction data provided') raise AccountingException('No transaction data provided')
for transaction in transactions: transaction_ids = []
storage.add_transaction(transaction)
return jsonify(status='OK') for transaction in transactions:
transaction_ids.append(storage.add_transaction(transaction))
return jsonify(status='OK', transaction_ids=transaction_ids)
def main(argv=None): def main(argv=None):

View file

@ -285,6 +285,11 @@ and the Flask endpoints.</p>
<tt class="descclassname">accounting.web.</tt><tt class="descname">main</tt><big>(</big><em>argv=None</em><big>)</big><a class="reference internal" href="../_modules/accounting/web.html#main"><span class="viewcode-link">[source]</span></a><a class="headerlink" href="#accounting.web.main" title="Permalink to this definition"></a></dt> <tt class="descclassname">accounting.web.</tt><tt class="descname">main</tt><big>(</big><em>argv=None</em><big>)</big><a class="reference internal" href="../_modules/accounting/web.html#main"><span class="viewcode-link">[source]</span></a><a class="headerlink" href="#accounting.web.main" title="Permalink to this definition"></a></dt>
<dd></dd></dl> <dd></dd></dl>
<dl class="function">
<dt id="accounting.web.transaction_delete">
<tt class="descclassname">accounting.web.</tt><tt class="descname">transaction_delete</tt><big>(</big><em>transaction_id=None</em><big>)</big><a class="headerlink" href="#accounting.web.transaction_delete" title="Permalink to this definition"></a></dt>
<dd></dd></dl>
<dl class="function"> <dl class="function">
<dt id="accounting.web.transaction_get"> <dt id="accounting.web.transaction_get">
<tt class="descclassname">accounting.web.</tt><tt class="descname">transaction_get</tt><big>(</big><big>)</big><a class="reference internal" href="../_modules/accounting/web.html#transaction_get"><span class="viewcode-link">[source]</span></a><a class="headerlink" href="#accounting.web.transaction_get" title="Permalink to this definition"></a></dt> <tt class="descclassname">accounting.web.</tt><tt class="descname">transaction_get</tt><big>(</big><big>)</big><a class="reference internal" href="../_modules/accounting/web.html#transaction_get"><span class="viewcode-link">[source]</span></a><a class="headerlink" href="#accounting.web.transaction_get" title="Permalink to this definition"></a></dt>

View file

@ -102,7 +102,19 @@ based on <tt class="xref py py-attr docutils literal"><span class="pre">self.led
<dl class="method"> <dl class="method">
<dt id="accounting.storage.ledgercli.Ledger.delete_transaction"> <dt id="accounting.storage.ledgercli.Ledger.delete_transaction">
<tt class="descname">delete_transaction</tt><big>(</big><em>transaction_id</em><big>)</big><a class="headerlink" href="#accounting.storage.ledgercli.Ledger.delete_transaction" title="Permalink to this definition"></a></dt> <tt class="descname">delete_transaction</tt><big>(</big><em>transaction_id</em><big>)</big><a class="headerlink" href="#accounting.storage.ledgercli.Ledger.delete_transaction" title="Permalink to this definition"></a></dt>
<dd></dd></dl> <dd><p>Delete a transaction from the ledger file.</p>
<p>This method opens the ledger file, loads all lines into memory and
looks for the transaction_id, then looks for the bounds of that
transaction in the ledger file, removes all lines within the bounds of
the transaction and removes them, then writes the lines back to the
ledger file.</p>
<p>Exceptions:</p>
<ul class="simple">
<li>RuntimeError: If all boundaries to the transaction are not found</li>
<li>TransactionNotFound: If no such transaction_id can be found in
<tt class="xref py py-data docutils literal"><span class="pre">self.ledger_file</span></tt></li>
</ul>
</dd></dl>
<dl class="method"> <dl class="method">
<dt id="accounting.storage.ledgercli.Ledger.get_process"> <dt id="accounting.storage.ledgercli.Ledger.get_process">
@ -168,7 +180,12 @@ without the prompt.</p>
<dl class="method"> <dl class="method">
<dt id="accounting.storage.ledgercli.Ledger.update_transaction"> <dt id="accounting.storage.ledgercli.Ledger.update_transaction">
<tt class="descname">update_transaction</tt><big>(</big><em>transaction</em><big>)</big><a class="reference internal" href="../_modules/accounting/storage/ledgercli.html#Ledger.update_transaction"><span class="viewcode-link">[source]</span></a><a class="headerlink" href="#accounting.storage.ledgercli.Ledger.update_transaction" title="Permalink to this definition"></a></dt> <tt class="descname">update_transaction</tt><big>(</big><em>transaction</em><big>)</big><a class="reference internal" href="../_modules/accounting/storage/ledgercli.html#Ledger.update_transaction"><span class="viewcode-link">[source]</span></a><a class="headerlink" href="#accounting.storage.ledgercli.Ledger.update_transaction" title="Permalink to this definition"></a></dt>
<dd></dd></dl> <dd><p>Update a transaction in the ledger file.</p>
<p>Takes a <a class="reference internal" href="accounting.html#accounting.models.Transaction" title="accounting.models.Transaction"><tt class="xref py py-class docutils literal"><span class="pre">Transaction</span></tt></a> object and removes
the old transaction using <tt class="xref py py-data docutils literal"><span class="pre">transaction.id</span></tt> from the passed
<a class="reference internal" href="accounting.html#accounting.models.Transaction" title="accounting.models.Transaction"><tt class="xref py py-class docutils literal"><span class="pre">Transaction</span></tt></a> instance and adds
<tt class="xref py py-data docutils literal"><span class="pre">transaction</span></tt> to the database.</p>
</dd></dl>
</dd></dl> </dd></dl>
@ -190,6 +207,11 @@ without the prompt.</p>
<tt class="descname">add_transaction</tt><big>(</big><em>transaction</em><big>)</big><a class="headerlink" href="#accounting.storage.Storage.add_transaction" title="Permalink to this definition"></a></dt> <tt class="descname">add_transaction</tt><big>(</big><em>transaction</em><big>)</big><a class="headerlink" href="#accounting.storage.Storage.add_transaction" title="Permalink to this definition"></a></dt>
<dd></dd></dl> <dd></dd></dl>
<dl class="method">
<dt id="accounting.storage.Storage.delete_transaction">
<tt class="descname">delete_transaction</tt><big>(</big><em>transaction_id</em><big>)</big><a class="headerlink" href="#accounting.storage.Storage.delete_transaction" title="Permalink to this definition"></a></dt>
<dd></dd></dl>
<dl class="method"> <dl class="method">
<dt id="accounting.storage.Storage.get_account"> <dt id="accounting.storage.Storage.get_account">
<tt class="descname">get_account</tt><big>(</big><em>*args</em>, <em>**kw</em><big>)</big><a class="reference internal" href="../_modules/accounting/storage.html#Storage.get_account"><span class="viewcode-link">[source]</span></a><a class="headerlink" href="#accounting.storage.Storage.get_account" title="Permalink to this definition"></a></dt> <tt class="descname">get_account</tt><big>(</big><em>*args</em>, <em>**kw</em><big>)</big><a class="reference internal" href="../_modules/accounting/storage.html#Storage.get_account"><span class="viewcode-link">[source]</span></a><a class="headerlink" href="#accounting.storage.Storage.get_account" title="Permalink to this definition"></a></dt>

View file

@ -243,6 +243,12 @@
<dt><a href="api/accounting.storage.html#accounting.storage.ledgercli.Ledger.delete_transaction">delete_transaction() (accounting.storage.ledgercli.Ledger method)</a> <dt><a href="api/accounting.storage.html#accounting.storage.ledgercli.Ledger.delete_transaction">delete_transaction() (accounting.storage.ledgercli.Ledger method)</a>
</dt> </dt>
<dd><dl>
<dt><a href="api/accounting.storage.html#accounting.storage.Storage.delete_transaction">(accounting.storage.Storage method)</a>
</dt>
</dl></dd>
<dt><a href="api/accounting.html#accounting.transport.AccountingDecoder.dict_to_object">dict_to_object() (accounting.transport.AccountingDecoder method)</a> <dt><a href="api/accounting.html#accounting.transport.AccountingDecoder.dict_to_object">dict_to_object() (accounting.transport.AccountingDecoder method)</a>
</dt> </dt>
@ -546,16 +552,20 @@
</dl></dd> </dl></dd>
<dt><a href="api/accounting.html#accounting.web.transaction_get">transaction_get() (in module accounting.web)</a> <dt><a href="api/accounting.html#accounting.web.transaction_delete">transaction_delete() (in module accounting.web)</a>
</dt> </dt>
<dt><a href="api/accounting.html#accounting.web.transaction_post">transaction_post() (in module accounting.web)</a> <dt><a href="api/accounting.html#accounting.web.transaction_get">transaction_get() (in module accounting.web)</a>
</dt> </dt>
</dl></td> </dl></td>
<td style="width: 33%" valign="top"><dl> <td style="width: 33%" valign="top"><dl>
<dt><a href="api/accounting.html#accounting.web.transaction_post">transaction_post() (in module accounting.web)</a>
</dt>
<dt><a href="api/accounting.html#accounting.web.transaction_update">transaction_update() (in module accounting.web)</a> <dt><a href="api/accounting.html#accounting.web.transaction_update">transaction_update() (in module accounting.web)</a>
</dt> </dt>

Binary file not shown.

File diff suppressed because one or more lines are too long