Skip to content

Commit

Permalink
Update settings handling (#4)
Browse files Browse the repository at this point in the history
* Add settings getter, DATE_FORMAT

* Extra line in sample journal.ledger

* Add DATE_FORMAT_MONTH and use settings_getter in grid, and related tests

* Add kwarg to specify default for get_setting, and use settings_getter with reconciler

* Rearranging deck chairs on new settings handling

* Use settings_getter with investments.py

* Update to investments.py settings usage and tests

* More stuff converted to using get_settings

* Add default for RECONCILER_CACHE_FILE and update readme and comments around this
  • Loading branch information
scarpent committed Oct 2, 2018
1 parent 4c3488d commit ac96b69
Show file tree
Hide file tree
Showing 21 changed files with 414 additions and 146 deletions.
45 changes: 17 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,12 +316,6 @@ to note when a scheduled item isn't an automated payment.

Interactively reconcile the account matching `ACCT` regex.

Relies on `settings.py` for the `RECONCILER_CACHE_FILE` setting. See
below for more on the settings file. If you don't create the settings
file you'll get the error:

ModuleNotFoundError: No module named 'ledgerbil.settings'

Example usage:

python main.py --file journal.ledger --reconcile 'bank: xyz'
Expand Down Expand Up @@ -437,7 +431,8 @@ want to give a positive amount for assets and negative for liability,
assuming the normal state of these kinds of accounts.

Statement ending info is saved to `RECONCILER_CACHE_FILE` (from
`settings.py`) and restored when the reconciler is restarted.
`settings.py`, or using the default `~/.ledgerbil_reconciler_cache`) and
restored when the reconciler is restarted.

### mark / unmark

Expand Down Expand Up @@ -515,31 +510,29 @@ ending date: 2016/10/29 ending balance: (not set) cleared: $70.00
After `finish`, the previous balance is saved in the cache (mentioned
above and more below!) and shown when you next reconcile this account,
which may be helpful for catching mistakes you make between visits to
the reconciler. There is also the `-R` option for ledgerbil which will
the reconciler.

### --reconciled-status, -R

There is also the `--reconciled-status` option for ledgerbil which will
go through all your cached entries and compare to ledger's `--cleared`
totals to see if things have gotten out of sync somehow.

Note that where the interactive reconciler only needs
`RECONCILER_CACHE_FILE`, `-R` needs more stuff configured in
`settings.py` so that ledgerbil can run ledger as an external program.
Note that where the interactive reconciler only uses
`RECONCILER_CACHE_FILE`, `--reconciled-status` needs more stuff
configured in `settings.py` so that ledgerbil can run ledger as an
external program.

## "ledgershell" (other commands)

The early ledgerbil features like schedule, sorting, and reconciler
didn't rely on the ledger command line program at all. They were
standalone commands that work with your data files. The reconciler is
now an exception, having acquired a feature for verifying your previous
balances with ledger's cleared totals. (Now that the door has been
opened, expect more integration in the future between ledgerbil and
ledger.)

The ledgershell programs use ledger to read and report on your data in
different ways. None of them modify your data (as of 4 August 2018,
anyway). (Oh. `portfolio` doesn't use ledger, but somehow still finds
itself in the ledgershell grouping.)
different ways. None of them modify your data as of October 2018.

(`portfolio` is listed with "other commands", but isn't a ledgershell
program, and currently has nothing to do with ledger or ledger data.)

There is no documentation other than the code and `--help`, which is
included below and often changing so don't count on this readme for the
included below and often changing, so don't count on this readme for the
latest.

### settings.py
Expand All @@ -549,15 +542,11 @@ Intended as a convenience, but which can also be a nuisance, the
ledger so you don't have to pass in everything every time. And, related
to the ambivalence about documentation mentioned above, There isn't any
documentation for this file yet. Just [`settings.py.example`][settings]
which you can copy to `settings.py` in the same directory and then
which you should copy to `settings.py` in the same directory and then
modify for your own needs.

[settings]: /ledgerbil/settings.py.example

To run the interactive reconciler, you only need
`RECONCILER_CACHE_FILE`. To use the reconciler's `-R` feature you'll
need more ledger stuff.

### grid

```
Expand Down
4 changes: 1 addition & 3 deletions ledgerbil/ledgerfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,7 @@ def sort(self):
else:
current_date = thing.thing_date

self.get_things().sort(
key=attrgetter('thing_date', 'thing_number')
)
self.get_things().sort(key=attrgetter('thing_date', 'thing_number'))

def print_file(self):
for thing in self.get_things():
Expand Down
14 changes: 6 additions & 8 deletions ledgerbil/ledgershell/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from .. import util
from ..colorable import Colorable
from ..settings import Settings
from ..settings_getter import get_setting
from ..util import get_date, get_float, parse_args
from .runner import get_ledger_output
from .util import get_account_balance
Expand All @@ -22,8 +22,6 @@
# todo: move this to ledgershell/util.py?
PAYEE_SUBTOTAL_REGEX = re.compile(r'^.*?\$\s*(\S+)\s*\$.*$')

settings = Settings()


def get_grid_report(args, ledger_args):
unit = 'month' if args.month else 'year'
Expand Down Expand Up @@ -217,11 +215,11 @@ def get_period_names(args, ledger_args, unit='year'):
period = ('--period', args.period) if args.period else tuple()

if unit == 'year':
date_format = '%Y'
date_format = get_setting('DATE_FORMAT_YEAR')
period_options = ('--yearly', '--date-format', date_format)
period_len = 4
else:
date_format = '%Y/%m'
date_format = get_setting('DATE_FORMAT_MONTH')
period_options = ('--monthly', '--date-format', date_format)
period_len = 7

Expand Down Expand Up @@ -361,10 +359,10 @@ def get_column_networth(period_name, ledger_args):
ending = period_name
else:
if len(period_name) == 4: # year
date_format = '%Y'
date_format = get_setting('DATE_FORMAT_YEAR')
networth_relativedelta = relativedelta(years=1)
else: # month
date_format = '%Y/%m'
date_format = get_setting('DATE_FORMAT_MONTH')
networth_relativedelta = relativedelta(months=1)

# Let's report net worth for the end of the current period,
Expand All @@ -374,7 +372,7 @@ def get_column_networth(period_name, ledger_args):
next_period_date = period_date + networth_relativedelta
ending = next_period_date.strftime(date_format)

accounts = tuple(parse_args(settings.NETWORTH_ACCOUNTS))
accounts = tuple(parse_args(get_setting('NETWORTH_ACCOUNTS')))
lines = get_ledger_output(
('balance',)
+ accounts
Expand Down
24 changes: 9 additions & 15 deletions ledgerbil/ledgershell/investments.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,13 @@
from textwrap import dedent

from ..colorable import Colorable
from ..settings import Settings
from ..settings_getter import get_setting
from ..util import parse_args
from .runner import get_ledger_command, get_ledger_output
from .util import AccountBalance, get_account_balance

settings = Settings()


def get_investment_command_options(
shares=False,
accounts=settings.INVESTMENT_DEFAULT_ACCOUNTS,
end_date=settings.INVESTMENT_DEFAULT_END_DATE):

def get_investment_command_options(accounts, end_date, shares=False):
options = ['--no-total']
if shares:
options += ['--exchange', '.'] # override --market
Expand All @@ -37,7 +31,7 @@ def warn_negative_dollars(amount, account):


def get_lines(args, shares=False):
options = get_investment_command_options(shares, args.accounts, args.end)
options = get_investment_command_options(args.accounts, args.end, shares)
output = get_ledger_output(options)

if args.command:
Expand Down Expand Up @@ -187,20 +181,20 @@ def get_args(args):
width=71
))
)
default_accounts = get_setting('INVESTMENT_DEFAULT_ACCOUNTS')
parser.add_argument(
'-a', '--accounts',
type=str,
default=settings.INVESTMENT_DEFAULT_ACCOUNTS,
help='balances for specified accounts (default: {})'.format(
settings.INVESTMENT_DEFAULT_ACCOUNTS
)
default=default_accounts,
help=f'balances for specified accounts (default: {default_accounts})'
)
default_end_date = get_setting('INVESTMENT_DEFAULT_END_DATE')
parser.add_argument(
'-e', '--end',
type=str,
metavar='DATE',
default=settings.INVESTMENT_DEFAULT_END_DATE,
help=f'end date (default: {settings.INVESTMENT_DEFAULT_END_DATE})'
default=default_end_date,
help=f'end date (default: {default_end_date})'
)
parser.add_argument(
'-c', '--command',
Expand Down
10 changes: 4 additions & 6 deletions ledgerbil/ledgershell/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
import subprocess
import sys

from ..settings import Settings

settings = Settings()
from ..settings_getter import get_setting


def get_ledger_command(args=None):
files = []
for f in settings.LEDGER_FILES:
files += ['-f', os.path.join(settings.LEDGER_DIR, f)]
return settings.LEDGER_COMMAND + tuple(files) + (args or tuple())
for f in get_setting('LEDGER_FILES'):
files += ['-f', os.path.join(get_setting('LEDGER_DIR'), f)]
return get_setting('LEDGER_COMMAND') + tuple(files) + (args or tuple())


def get_ledger_output(args=None):
Expand Down
84 changes: 79 additions & 5 deletions ledgerbil/ledgershell/tests/test_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,31 @@
import pytest

from .. import grid
from ... import settings, settings_getter
from ...colorable import Colorable
from ...tests.helpers import OutputFileTester


class MockSettings:
NETWORTH_ACCOUNTS = '(^assets ^liabilities)'
NETWORTH_ACCOUNTS = '(^fu ^bar)'
DATE_FORMAT_MONTH = settings_getter.defaults['DATE_FORMAT_MONTH']


def setup_function(module):
grid.settings = MockSettings()
class MockSettingsAltDateFormat:
NETWORTH_ACCOUNTS = '(^bar ^fu)'
DATE_FORMAT_MONTH = '%Y-%m'


class MockSettingsEmpty:
pass


def setup_function():
settings_getter.settings = MockSettings()


def teardown_function():
settings_getter.settings = settings.Settings()


@mock.patch(__name__ + '.grid.get_ledger_output')
Expand Down Expand Up @@ -58,6 +73,29 @@ def test_get_period_names_months(mock_ledger_output):
)


@mock.patch(__name__ + '.grid.get_ledger_output')
def test_get_period_names_months_different_format(mock_ledger_output):
settings_getter.settings = MockSettingsAltDateFormat()
output = dedent('''\
2017-11 - 2017-11 <Total> 0 0
2017-12 - 2017-12 <Total> 0 0
2018-01 - 2018-01 <Total> 0 0''')
mock_ledger_output.return_value = output
args, ledger_args = grid.get_args([
'--begin', 'banana', '--end', 'eggplant',
'--period', 'pear', 'lettuce'
])
expected = (('2017-11', '2017-12', '2018-01'), None)
actual = grid.get_period_names(args, tuple(ledger_args), 'month')
assert actual == expected
mock_ledger_output.assert_called_once_with(
('register', '--begin', 'banana', '--end', 'eggplant',
'--period', 'pear', '--monthly', '--date-format', '%Y-%m',
'--collapse', '--empty', 'lettuce')
)


@mock.patch(__name__ + '.grid.date')
@mock.patch(__name__ + '.grid.get_ledger_output')
def test_get_period_names_months_with_current(mock_ledger_output, mock_date):
Expand Down Expand Up @@ -345,13 +383,49 @@ def test_get_column_networth_year(mock_ledger_output):
expected = {'net worth': 3846.57}
assert grid.get_column_networth('2007', ('bogus', )) == expected
mock_ledger_output.assert_called_once_with(
('balance', '(^assets', '^liabilities)',
('balance', '(^fu', '^bar)',
'--depth', '1', '--end', '2008', 'bogus')
)


@mock.patch(__name__ + '.grid.get_ledger_output')
def test_get_column_networth_month(mock_ledger_output):
output = dedent('''\
$ 8,270.61 assets
$ -4,424.04 liabilities
--------------------
$ 3,846.57''')
mock_ledger_output.return_value = output
expected = {'net worth': 3846.57}
assert grid.get_column_networth('2007/10', tuple()) == expected
mock_ledger_output.assert_called_once_with(
('balance', '(^fu', '^bar)',
'--depth', '1', '--end', '2007/11')
)


@mock.patch(__name__ + '.grid.get_ledger_output')
def test_get_column_networth_month_different_date_format(mock_ledger_output):
settings_getter.settings = MockSettingsAltDateFormat()
output = dedent('''\
$ 8,270.61 assets
$ -4,424.04 liabilities
--------------------
$ 3,846.57''')
mock_ledger_output.return_value = output
expected = {'net worth': 3846.57}
assert grid.get_column_networth('2007-10', tuple()) == expected
mock_ledger_output.assert_called_once_with(
('balance', '(^bar', '^fu)',
'--depth', '1', '--end', '2007-11')
)


@mock.patch(__name__ + '.grid.get_ledger_output')
def test_get_column_networth_default_networth_accounts(mock_ledger_output):
"""get_column_networth should use a default for NETWORTH_ACCOUNTS
if the setting is not present"""
settings_getter.settings = MockSettingsEmpty()
output = dedent('''\
$ 8,270.61 assets
$ -4,424.04 liabilities
Expand All @@ -372,7 +446,7 @@ def test_get_column_networth_tomorrow_and_no_result(mock_ledger_output):
expected = {'net worth': 0.0}
assert grid.get_column_networth('tomorrow', tuple()) == expected
mock_ledger_output.assert_called_once_with(
('balance', '(^assets', '^liabilities)',
('balance', '(^fu', '^bar)',
'--depth', '1', '--end', 'tomorrow')
)

Expand Down
Loading

0 comments on commit ac96b69

Please sign in to comment.