[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:
parent
c80955f199
commit
02fc05aebd
8 changed files with 191 additions and 14 deletions
|
@ -4,8 +4,10 @@
|
|||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from accounting.exceptions import AccountingException
|
||||
|
||||
class Storage():
|
||||
|
||||
class Storage:
|
||||
'''
|
||||
ABC for accounting storage
|
||||
'''
|
||||
|
@ -38,6 +40,14 @@ class Storage():
|
|||
def update_transaction(self, transaction):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def delete_transaction(self, transaction_id):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def reverse_transaction(self, transaction_id):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class TransactionNotFound(AccountingException):
|
||||
pass
|
||||
|
|
|
@ -6,13 +6,15 @@ import sys
|
|||
import subprocess
|
||||
import logging
|
||||
import time
|
||||
import re
|
||||
|
||||
from datetime import datetime
|
||||
from xml.etree import ElementTree
|
||||
from contextlib import contextmanager
|
||||
|
||||
from accounting.exceptions import AccountingException
|
||||
from accounting.models import Account, Transaction, Posting, Amount
|
||||
from accounting.storage import Storage
|
||||
from accounting.storage import Storage, TransactionNotFound
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -205,6 +207,8 @@ class Ledger(Storage):
|
|||
|
||||
_log.debug('written to file: %s', output)
|
||||
|
||||
return transaction.id
|
||||
|
||||
def bal(self):
|
||||
output = self.send_command('xml')
|
||||
|
||||
|
@ -252,6 +256,16 @@ class Ledger(Storage):
|
|||
def get_transactions(self):
|
||||
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):
|
||||
output = self.send_command('xml')
|
||||
|
||||
|
@ -314,8 +328,111 @@ class Ledger(Storage):
|
|||
|
||||
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):
|
||||
_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):
|
||||
|
|
|
@ -11,7 +11,6 @@ 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
|
||||
|
||||
|
@ -65,6 +64,7 @@ def transaction_get():
|
|||
'''
|
||||
return jsonify(transactions=storage.get_transactions())
|
||||
|
||||
|
||||
@app.route('/transaction/<string:transaction_id>', methods=['POST'])
|
||||
@jsonify_exceptions
|
||||
def transaction_update(transaction_id=None):
|
||||
|
@ -74,8 +74,8 @@ def transaction_update(transaction_id=None):
|
|||
transaction = request.json['transaction']
|
||||
|
||||
if transaction.id is not None and not transaction.id == transaction_id:
|
||||
raise AccountingException('The transaction data has an ID attribute and'
|
||||
' it is not the same ID as in the path')
|
||||
raise AccountingException('The transaction data has an ID attribute'
|
||||
' and it is not the same ID as in the path')
|
||||
elif transaction.id is None:
|
||||
transaction.id = transaction_id
|
||||
|
||||
|
@ -84,6 +84,17 @@ def transaction_update(transaction_id=None):
|
|||
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'])
|
||||
@jsonify_exceptions
|
||||
def transaction_post():
|
||||
|
@ -139,10 +150,12 @@ def transaction_post():
|
|||
if not transactions:
|
||||
raise AccountingException('No transaction data provided')
|
||||
|
||||
for transaction in transactions:
|
||||
storage.add_transaction(transaction)
|
||||
transaction_ids = []
|
||||
|
||||
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):
|
||||
|
|
5
doc/build/html/api/accounting.html
vendored
5
doc/build/html/api/accounting.html
vendored
|
@ -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>
|
||||
<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">
|
||||
<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>
|
||||
|
|
26
doc/build/html/api/accounting.storage.html
vendored
26
doc/build/html/api/accounting.storage.html
vendored
|
@ -102,7 +102,19 @@ based on <tt class="xref py py-attr docutils literal"><span class="pre">self.led
|
|||
<dl class="method">
|
||||
<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>
|
||||
<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">
|
||||
<dt id="accounting.storage.ledgercli.Ledger.get_process">
|
||||
|
@ -168,7 +180,12 @@ without the prompt.</p>
|
|||
<dl class="method">
|
||||
<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>
|
||||
<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>
|
||||
|
||||
|
@ -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>
|
||||
<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">
|
||||
<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>
|
||||
|
|
14
doc/build/html/genindex.html
vendored
14
doc/build/html/genindex.html
vendored
|
@ -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>
|
||||
|
||||
<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>
|
||||
|
@ -546,16 +552,20 @@
|
|||
|
||||
</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><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>
|
||||
|
||||
</dl></td>
|
||||
<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>
|
||||
|
||||
|
|
BIN
doc/build/html/objects.inv
vendored
BIN
doc/build/html/objects.inv
vendored
Binary file not shown.
2
doc/build/html/searchindex.js
vendored
2
doc/build/html/searchindex.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue