Parsing
Overview
Parsing is the main method by which Beancount types can be converted in their
respective Pydantic models. Every model provided by bdantic includes a
parse method which is responsible for parsing
its associated Beancount type into a new instance of the model.
import bdantic
from beancount.core import amount
from decimal import Decimal
amt = amount.Amount(number=Decimal(1.50), currency="USD"))
model = bdantic.parse(amt) # Produces a bdantic.models.Amount
In most cases, the model will match the native beancount type field-for-field.
In some cases the underlying Beancount data combines multiple types, like the
realization.RealAccount class which is both a dictionary of child accounts as
well as a container for individual account information. In cases like these the
model may differ slightly (i.e. the
RealAccount model uses a children field
for holding child accounts), however, models will always convert back to their
native Beancount types without issue.
When a Beancount type contains child elements which can be represented as a model the parse method will recursively convert child elements to models as well. For example, parsing a Transaction will also parse all child postings as well as all child amounts of those postings.
Not every Beancount type has an equivalent model. To see the currently suported types, refer to the type signature of the parse function.
Parsing Beancount Types
Parsing Files
The parse_loader function provides a convenient
interface for parsing the results of the beancount.loader functions. For
example, one can convert all entries loaded from a Beancount file to their
respective models like so:
import bdantic
from beancount import loader
bfile = bdantic.parse_loader(*loader.load_file("ledger.beancount"))
The BeancountFile model provides access to the parsed entries, errors, and options returned by the loader.
Parsing Query Results
The parse_query function provides an interface for parsing the results of running a Beancount query:
import bdantic
from beancount import loader
from beancount.query import query
entries, _, options = loader.load_file("ledger.beancount")
query = "SELECT date, narration, account, position"
result = query.run_query(entries, options, query)
parsed_result = bdantic.parse_query(result)
The QueryResult returned contains the column and row data with all Beancount types automatically parsed into models.
Parsing Realizations
The realization.realize Beancount function takes a list of entries and uses
them to calculate data about the accounts included in those entries. It returns
a realization.RealAccount which is a dict like object that contains the
entire account hierarchy. This object can be parsed:
import bdantic
from beancount.core import realization
entries, _, options = loader.load_file("ledger.beancount")
real = realization.realize(entries)
parsed_real = bdantic.parse(real)
An additional Account model is provided and
can be obtained by parsing it directly from a realization.RealAccount or
calling the to_account method
on a RealAccount. This model offers a
simplified view of a single account and is thus easier to render.
Internal
Internally, parsing is accomplished by abusing the fact that most objects have a
__dict__ property and all NamedTuple objects have a _asdict() method.
Since Beancount makes heavy use of NamedTuple objects this fact is used by the
recursive_parse function to recursively
create a dictionary representation of any complex Beancount type. Pydantic
models have a
parse_obj
method which takes in a dictionary and performs validation, producing a model if
all validation checks pass. The nested dictionary produced is fed into this
method to create the models.