[ledgercli] Store posting metadata

This commit is contained in:
Joar Wandborg 2013-12-29 21:22:51 +01:00
parent 8abbe3462f
commit 86a6bec585
2 changed files with 74 additions and 16 deletions

View file

@ -6,13 +6,10 @@ import os
import sys import sys
import subprocess import subprocess
import logging import logging
import time
import re import re
import pygit2
from datetime import datetime from datetime import datetime
from xml.etree import ElementTree from xml.etree import ElementTree
from contextlib import contextmanager
from accounting.exceptions import AccountingException, TransactionNotFound, \ from accounting.exceptions import AccountingException, TransactionNotFound, \
LedgerNotBalanced, TransactionIDCollision LedgerNotBalanced, TransactionIDCollision
@ -21,6 +18,14 @@ from accounting.storage import Storage
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
HAS_PYGIT = False
try:
import pygit2
HAS_PYGIT = True
except ImportError:
_log.warning('Failed to import pygit2')
class Ledger(Storage): class Ledger(Storage):
def __init__(self, app=None, ledger_file=None, ledger_bin=None): def __init__(self, app=None, ledger_file=None, ledger_bin=None):
@ -34,6 +39,12 @@ class Ledger(Storage):
self.ledger_file = ledger_file self.ledger_file = ledger_file
_log.info('ledger file: %s', ledger_file) _log.info('ledger file: %s', ledger_file)
self.init_pygit()
def init_pygit(self):
if not HAS_PYGIT:
return False
try: try:
self.repository = pygit2.Repository( self.repository = pygit2.Repository(
os.path.join(os.path.dirname(self.ledger_file), '.git')) os.path.join(os.path.dirname(self.ledger_file), '.git'))
@ -89,7 +100,7 @@ class Ledger(Storage):
return args return args
def send_command(self, command): def run_command(self, command):
''' '''
Creates a new ledger process with the specified :data:`command` and Creates a new ledger process with the specified :data:`command` and
returns the output. returns the output.
@ -147,27 +158,34 @@ class Ledger(Storage):
metadata_template = ' ;{0}: {1}\n' metadata_template = ' ;{0}: {1}\n'
# TODO: Generate metadata for postings
posting_template = (' {account} {p.amount.symbol}' posting_template = (' {account} {p.amount.symbol}'
' {p.amount.amount}\n') ' {p.amount.amount}\n')
output = b'' output = b''
out_postings = ''
for posting in transaction.postings:
out_postings += posting_template.format(
p=posting,
account=posting.account + ' ' * (
80 - (len(posting.account) + len(posting.amount.symbol) +
len(str(posting.amount.amount)) + 1 + 2))
)
if len(posting.metadata):
for k, v in posting.metadata.items():
out_postings += metadata_template.format(str(k), str(v))
# XXX: Even I hardly understands what this does, however I indent it it # XXX: Even I hardly understands what this does, however I indent it it
# stays unreadable. # stays unreadable.
output += transaction_template.format( output += transaction_template.format(
date=transaction.date.strftime('%Y-%m-%d'), date=transaction.date.strftime('%Y-%m-%d'),
t=transaction, t=transaction,
metadata=''.join([ metadata=''.join([
metadata_template.format(k, v) metadata_template.format(str(k), str(v))
for k, v in transaction.metadata.items()]), for k, v in transaction.metadata.items()]),
postings=''.join([posting_template.format( postings=out_postings
p=p,
account=p.account + ' ' * (
80 - (len(p.account) + len(p.amount.symbol) +
len(str(p.amount.amount)) + 1 + 2)
)) for p in transaction.postings
])
).encode('utf8') ).encode('utf8')
with open(self.ledger_file, 'ab') as f: with open(self.ledger_file, 'ab') as f:
@ -194,7 +212,7 @@ class Ledger(Storage):
return transaction.id return transaction.id
def bal(self): def bal(self):
output = self.send_command('xml') output = self.run_command('xml')
if output is None: if output is None:
raise RuntimeError('bal call returned no output') raise RuntimeError('bal call returned no output')
@ -248,7 +266,7 @@ class Ledger(Storage):
return transaction return transaction
def reg(self): def reg(self):
output = self.send_command('xml') output = self.run_command('xml')
if output is None: if output is None:
raise RuntimeError('reg call returned no output') raise RuntimeError('reg call returned no output')

View file

@ -293,8 +293,48 @@ class TransactionTestCase(unittest.TestCase):
self.assertEqual(response['error']['type'], 'TransactionNotFound') self.assertEqual(response['error']['type'], 'TransactionNotFound')
def test_post_transaction_with_metadata(self): pass def test_post_transaction_with_metadata(self):
transaction = copy.deepcopy(self.simple_transaction)
transaction.metadata.update({'foo': 'bar'})
response = self._post_json('/transaction', transaction)
transaction_id = response['transaction_ids'][0]
response = self._get_json('/transaction/' + transaction_id)
self.assertEqual(
response['transaction'].metadata,
transaction.metadata)
def test_post_transaction_with_posting_metadata(self):
transaction = copy.deepcopy(self.simple_transaction)
postings = [
Posting(account='Assets:Checking', metadata={'assets': 'checking'},
amount=Amount(amount='-100.10', symbol='$')),
Posting(account='Expenses:Foo', metadata={'expenses': 'foo'},
amount=Amount(amount='100.10', symbol='$'))
]
transaction.postings = postings
response = self._post_json('/transaction', transaction)
transaction_id = response['transaction_ids'][0]
response = self._get_json('/transaction/' + transaction_id)
for posting in response['transaction'].postings:
if posting.account == 'Expenses:Foo':
self.assertEqual(posting.metadata, {'expenses': 'foo'})
elif posting.account == 'Assets:Checking':
self.assertEqual(posting.metadata, {'assets': 'checking'})
else:
assert False, \
'Something about this transaction\'s postings is' \
' unexpected'
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()