lcat
My personal /var/log
  • Home
  • Contact
  • HackMe!

Hledger Python Balance API

At first, I wanted to visualize my personal finance using [Panel](https://panel.holoviz.org/). Then, I realized the initial and maintenance effort is too much. I decided to switch to a collection of PHP scripts. But here is the early version of Python API that I used to interact with my hledger journals. I think this can be much improved. At the time of writing the script, I did not know about the `tidy` layout which will do the same thing as melting the `bare` format. At least this might be useful for me in the future.

```
import subprocess
import os
import io
import pandas as pd


class Ledger:
    MAIN_COMM = 'IDR'

    def __init__(self, files: list[str], *, verbose=False) -> None:
        self.files = files
        self.verbose = verbose

    @classmethod
    def from_env(cls, **kwargs):
        file = os.environ.get("LEDGER_FILE")
        if not file:
            raise ValueError("LEDGER_FILE environment variable is not set.")
        return cls([file], **kwargs)

    def balance(
        self,
        query: list[str],
        *,
        historical=True,
        begin: str = None,
        end: str = None,
        period: str = None,
        exchange: str = None,
        depth: int = None,
    ):
        args = ["bal", "-O", "csv", "--layout", "bare", "--no-total"]
        args += query

        read_csv_kwargs = {"skiprows": 1, "names": ["account", "commodity", "amount"]}

        if historical:
            args.append("-H")
        if begin:
            args += ["-b", begin]
        if end:
            args += ["-e", end]
        if period:
            args += ["-p", period]
            read_csv_kwargs.pop("skiprows")
            read_csv_kwargs.pop("names")
        if exchange:
            args += ["-X", exchange]
        if depth:
            args += ["--depth", str(depth)]

        df = pd.read_csv(self._hledger(args), **read_csv_kwargs)

        if period:
            df = df.melt(
                id_vars=["account", "commodity"], var_name="date", value_name="amount"
            )

        return df

    def _hledger(self, args):
        f_args = [("-f", f) for f in self.files]

        args = ("hledger", *[b for a in f_args for b in a], *args)

        p = subprocess.run(args, capture_output=True)
        if self.verbose:
            print(p)

        return io.BytesIO(p.stdout)


if __name__ == "__main__":
    ledger = Ledger.from_env(verbose=True)
    df = ledger.balance(["assets"])
    print(df)
```
Created: 2025-07-14 17:34:19, Updated: 2025-07-14 17:34:19, ID: 61870881-5fb1-423d-8d48-16720afee026