Create a balance from the API

68 views
Skip to first unread message

Florian Lindner

unread,
May 2, 2019, 4:26:30 AM5/2/19
to Beancount
Hello,

I would like to create a balance assertion

YYYY-MM-DD balance Account  Amount

program,atically: https://aumayr.github.io/beancount-docs-static/api_reference/beancount.core.html#beancount.core.data.Balance

The parameter description in the API docs is only "Alias for field X". The source code reveals some more information:

# Attributes:
#   meta: See above.
#   date: See above.
#   account: A string, the account whose balance to check at the given date.
#   amount: An Amount, the number of units of the given currency you're
#     expecting 'account' to have at this date.
#   diff_amount: None if the balance check succeeds. This value is set to
#     an Amount instance if the balance fails, the amount of the difference.
#   tolerance: A Decimal object, the amount of tolerance to use in the
#     verification.
Balance = new_directive('Balance', [
    ('account', Account),
    ('amount', Amount),
    ('tolerance', Optional[Decimal]),
    ('diff_amount', Optional[Amount])])


Just omitting the optional arguments doesn't work and gives a TypeError.

Using this piece of code:

value = desc.split(" ")[-1]  # value = "1.522,43H"
sign = 1 if value[-1] == "H" else -1
units = sign * amount.Amount(D(value[:-1].replace(".", "").replace(",", ".")), "EUR")
print(units)
txn = data.Balance(meta,
                   datetime.datetime.strptime(row["Datum/Zeit"], "%d.%m.%Y %H:%M").date(),
                   account = self.file_account(),
                   amount = units,
                   tolerance = D(0), diff_amount = amount
                )

gives:

(Decimal('12415.79'), 'EUR')

Traceback (most recent call last):
  File "/usr/bin/bean-extract", line 4, in <module>
    from beancount.ingest.extract import main; main()
  File "/usr/lib/python3.7/site-packages/beancount/ingest/extract.py", line 257, in main
    return scripts_utils.trampoline_to_ingest(sys.modules[__name__])
  File "/usr/lib/python3.7/site-packages/beancount/ingest/scripts_utils.py", line 174, in trampoline_to_ingest
    return run_import_script_and_ingest(parser)
  File "/usr/lib/python3.7/site-packages/beancount/ingest/scripts_utils.py", line 224, in run_import_script_and_ingest
    return ingest(importers_list)
  File "/usr/lib/python3.7/site-packages/beancount/ingest/scripts_utils.py", line 116, in ingest
    detect_duplicates_func=detect_duplicates_func)
  File "/usr/lib/python3.7/site-packages/beancount/ingest/extract.py", line 252, in run
    detect_duplicates_func=detect_duplicates_func)
  File "/usr/lib/python3.7/site-packages/beancount/ingest/extract.py", line 218, in extract
    print_extracted_entries(new_entries, output)
  File "/usr/lib/python3.7/site-packages/beancount/ingest/extract.py", line 140, in print_extracted_entries
    entry_string = printer.format_entry(entry)
  File "/usr/lib/python3.7/site-packages/beancount/parser/printer.py", line 339, in format_entry
    return EntryPrinter(dcontext, render_weights)(entry)
  File "/usr/lib/python3.7/site-packages/beancount/parser/printer.py", line 117, in __call__
    method(obj, oss)
  File "/usr/lib/python3.7/site-packages/beancount/parser/printer.py", line 254, in Balance
    amount=entry.amount.to_string(self.dformat),
AttributeError: 'tuple' object has no attribute 'to_string'

when attached to the list of statements that is returned from the extract() function.

Also, I am obviosuly still having some problems crasping beancounts type system, esp. with optional parameters, like creating a transaction:

  txn = data.Transaction(meta, date, "*", payee, desc, set(), set(), [
                        data.Posting("Assets:Checking", units, None, None, None, None),
                        data.Posting("Expenses:Unknown", None, None, None, None, None)])

took me some time to find out, that I need to pass set() to some empty arguments, None to others.

My questions:

* How can I programmatically create a balance assertion?

* How do the parameters diff_amount and tolerance reflect in the produced statements? The balance statement does not seem to have these fields.

* What is the meaning of the diff_amount?

* And finally: Is there something I don't understand regarding beancount's API? So far, it feels a bit unpythonic.


Thanks a lot,
Florian

Patrick Ruckstuhl

unread,
May 2, 2019, 5:37:45 AM5/2/19
to bean...@googlegroups.com

Here's what I'm using

data.Balance(

            data.new_metadata(myFile, myLine),
            myDate,
            myAccount,
            amount.Amount(D(myAmount), myCurrency),
            None,
            None,
        )


* How do the parameters diff_amount and tolerance reflect in the produced statements? The balance statement does not seem to have these fields.

* What is the meaning of the diff_amount?

* And finally: Is there something I don't understand regarding beancount's API? So far, it feels a bit unpythonic.


Thanks a lot,
Florian

--
You received this message because you are subscribed to the Google Groups "Beancount" group.
To unsubscribe from this group and stop receiving emails from it, send an email to beancount+...@googlegroups.com.
To post to this group, send email to bean...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/beancount/f2123ceb-8a2a-40ca-ae56-f7b0527e413b%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Florian Lindner

unread,
May 2, 2019, 10:50:41 AM5/2/19
to Beancount
Ok, problem was that I multiplied the amount with 1 or -1 which, for some reason, returns a tuple. Now I use the __neg__ function directly.



>> Also, I am obviosuly still having some problems crasping beancounts type system, esp. with optional parameters, like creating a transaction:
>>
>>   txn = data.Transaction(meta, date, "*", payee, desc, set(), set(), [
>>                         data.Posting("Assets:Checking", units, None, None, None, None),
>>                         data.Posting("Expenses:Unknown", None, None, None, None, None)])
>>
>> took me some time to find out, that I need to pass set() to some empty arguments, None to others.
>>
>> My questions:
>>
>> * How can I programmatically create a balance assertion?
>
>
> Here's what I'm using
>
> data.Balance(
>
>             data.new_metadata(myFile, myLine),
>             myDate,
>             myAccount,
>             amount.Amount(D(myAmount), myCurrency),
>             None,
>             None,
>         )
Thanks!
Florian

Martin Blais

unread,
May 3, 2019, 10:48:41 PM5/3/19
to Beancount
On Thu, May 2, 2019 at 4:26 AM Florian Lindner <mailin...@xgm.de> wrote:
Hello,

I would like to create a balance assertion

YYYY-MM-DD balance Account  Amount

program,atically: https://aumayr.github.io/beancount-docs-static/api_reference/beancount.core.html#beancount.core.data.Balance

The parameter description in the API docs is only "Alias for field X". The source code reveals some more information:

# Attributes:
#   meta: See above.
#   date: See above.
#   account: A string, the account whose balance to check at the given date.
#   amount: An Amount, the number of units of the given currency you're
#     expecting 'account' to have at this date.
#   diff_amount: None if the balance check succeeds. This value is set to
#     an Amount instance if the balance fails, the amount of the difference.
#   tolerance: A Decimal object, the amount of tolerance to use in the
#     verification.
Balance = new_directive('Balance', [
    ('account', Account),
    ('amount', Amount),
    ('tolerance', Optional[Decimal]),
    ('diff_amount', Optional[Amount])])


Just omitting the optional arguments doesn't work and gives a TypeError.

Python Namedtyples don't come with defaults in their constructor.
I've been meaning to add this eventually - by inheriting and providing such a constructor.
It could make the entire codebase a little simpler and less error-prone, it's a bit annoying to always use the full set of positional arguments.
The tags and links empty sets could be handled there automatically as well.
Provide all the position arguments.
 
 

* How do the parameters diff_amount and tolerance reflect in the produced statements? The balance statement does not seem to have these fields.

They're only rendered in journals in bean-web.

 

* What is the meaning of the diff_amount?

If the balance fails, that's the difference between the computed and expected amount (enabled reporting errors without having to recompute the balance - not sure I like this design choice much).


* And finally: Is there something I don't understand regarding beancount's API? So far, it feels a bit unpythonic.

Python provides a class construct, but it's not without problems. 
Many experienced programmers don't use them (and many junior programmers insist on using them when they really don't need them).
Classes encourage mutable state and side-effects, and that makes code harder to maintain.
You don't need classes.
I avoid them almost always.
I prefer to use tuples and as a matter of convention avoid mutating them.
It's definitely not unPythonic to do that.

Stefano Zacchiroli

unread,
May 4, 2019, 3:00:14 AM5/4/19
to bean...@googlegroups.com
On Fri, May 03, 2019 at 10:48:27PM -0400, Martin Blais wrote:
> On Thu, May 2, 2019 at 4:26 AM Florian Lindner <mailin...@xgm.de> wrote:
> > Just omitting the optional arguments doesn't work and gives a TypeError.
> >
>
> Python Namedtyples don't come with defaults in their constructor.
> I've been meaning to add this eventually - by inheriting and providing such
> a constructor.
> It could make the entire codebase a little simpler and less error-prone,
> it's a bit annoying to always use the full set of positional arguments.
> The tags and links empty sets could be handled there automatically as well.

Python data classes might come in handy here:
https://docs.python.org/3/library/dataclasses.html

--
Stefano Zacchiroli . za...@upsilon.cc . upsilon.cc/zack . . o . . . o . o
Computer Science Professor . CTO Software Heritage . . . . . o . . . o o
Former Debian Project Leader & OSI Board Director . . . o o o . . . o .
« the first rule of tautology club is the first rule of tautology club »

Zhuoyun Wei

unread,
May 5, 2019, 12:04:04 AM5/5/19
to Stefano Zacchiroli, Beancount
On Sat, May 4, 2019, at 15:00, Stefano Zacchiroli wrote:

> Python data classes might come in handy here:
> https://docs.python.org/3/library/dataclasses.html

I like dataclasses. It has the same usage interface and repr as namedtuples, but with the flexibility of classes. There will be no more awkward `txn = txn._replace(narration='foo')`, just `txn.narration = `foo`.

The only problem is that it's in Python 3.7. AFAIK some distros are stuck with Python 3.6...

--
Zhuoyun Wei

Martin Blais

unread,
May 5, 2019, 8:02:41 AM5/5/19
to Beancount, Stefano Zacchiroli
On Sun, May 5, 2019 at 12:04 AM Zhuoyun Wei <wzy...@wzyboy.org> wrote:
On Sat, May 4, 2019, at 15:00, Stefano Zacchiroli wrote:

> Python data classes might come in handy here:
> https://docs.python.org/3/library/dataclasses.html

I like dataclasses. It has the same usage interface and repr as namedtuples, but with the flexibility of classes. There will be no more awkward `txn = txn._replace(narration='foo')`, just `txn.narration = `foo`.

The flexibility you like from dataclasses is specifically that which I'd want to avoid. The "danger" is to start mutating instances as a side-effect of calling functions. While it's possible to carefully use the mutable interface of dataclasses only on brand new instances, the awkward _replace() interface of namedtuple makes that even clearer. In an ideal world, all mutation should occur upon construction. In other words, unlike most Python developers, I am
- utterly uncaring for classes (1)
- terrified of side-effects and mutation
As such, I'd make all dataclasses frozen...

Furthermore, I believe in a kind of "bill of rights" for programmers: Programmers should feel free to use any subset of a language as they see fit. A great new tool would be one that programmatically allows one to enforce such constraints over sections of a codebase (e.g. "under this directory, no classes are allowed"). I wish for something like that in Python and C++ and Go. Using a subset of a language doesn't make it unidiomatic. In particular, avoiding the use of mutation in Python code doesn't make it any less Pythonic. You have to consider this in the context of the evolving nature of programming languages, and the many regrets of their creators. Python has way too many features.



The only problem is that it's in Python 3.7. AFAIK some distros are stuck with Python 3.6... 

--
Zhuoyun Wei

--
You received this message because you are subscribed to the Google Groups "Beancount" group.
To unsubscribe from this group and stop receiving emails from it, send an email to beancount+...@googlegroups.com.
To post to this group, send email to bean...@googlegroups.com.

Stefano Zacchiroli

unread,
May 5, 2019, 8:24:01 AM5/5/19
to bean...@googlegroups.com
On Sun, May 05, 2019 at 08:02:25AM -0400, Martin Blais wrote:
> As such, I'd make all dataclasses frozen...

Sure, frozen dataclasses are great, and they would be great and an
improvement on the front of default arguments wrt to the current named
tuples approach.

I'm myself a functional programmer, so I don't need any convincing on
the usefulness of immutability :-)
Reply all
Reply to author
Forward
0 new messages