update configuration doc & more tests

This commit is contained in:
Dima Gerasimov 2020-05-13 20:23:29 +01:00
parent cda6bd51ce
commit 522bfff679
3 changed files with 47 additions and 16 deletions

View file

@ -224,7 +224,7 @@ My conclusion was using a *combined approach*:
Inheritance is a standard mechanism, which doesn't require any extra frameworks and plays well with other Python concepts. As a specific example:
,#+begin_src python
#+begin_src python
from my.config import bluemaestro as user_config
@dataclass
@ -256,24 +256,27 @@ I claim this solves pretty much everything:
- *(6)*: the dataclass header is easily readable, and it's possible to generate the docs automatically
Downsides:
- inheriting from ~user_config~ means early import of =my.config=
- inheriting from ~user_config~ means an early import of =my.config=
Generally it's better to keep everything as lazy as possible and defer loading to the first time the config is used.
This might be annoying at times, e.g. if you have a top-level import of you module, but no config.
But considering that in 99% of cases config is going to be on the disk
and it's possible to do something dynamic like =del sys.modules['my.bluemastro']= to reload the config, I think it's a minor issue.
# TODO demonstrate in a test?
and it's [[https://github.com/karlicoss/HPI/blob/1e6e0bd381d20437343473878c7f63b1f9d6362b/tests/demo.py#L22-L25][possible]] to do something dynamic like =del sys.modules['my.bluemastro']= to reload the config, I think it's a minor issue.
- =make_config= allows for some mypy false negatives in the user config
E.g. if you forgot =export_path= attribute, mypy would miss it. But you'd have a runtime failure, and the downstream code using config is still correctly type checked.
Perhaps it will be better when [[https://github.com/python/mypy/issues/5374][this]] is fixed.
Perhaps it will be better when [[https://github.com/python/mypy/issues/5374][this mypy issue]] is fixed.
- the =make_config= bit is a little scary and manual
However, it's extracted in a generic helper, and [[https://github.com/karlicoss/HPI/blob/d6f071e3b12ba1cd5a86ad80e3821bec004e6a6d/my/twitter/archive.py#L17][ends up pretty simple]]
# In addition, it's not even necessary if you don't have optional attributes, you can simply use the class variables (i.e. ~bluemaestro.export_path~)
# upd. ugh, you can't, it doesn't handle default attributes overriding correctly (see tests/demo.py)
# eh. basically all I need is class level dataclass??
- inheriting from ~user_config~ requires it to be a =class= rather than an =object=
A practical downside is you can't use something like ~SimpleNamespace~.

View file

@ -16,13 +16,8 @@ class demo(user_config):
username: str
timezone: tzinfo = pytz.utc
def config() -> demo:
from .core.cfg import make_config
config = make_config(demo)
return config
from .core.cfg import make_config
config = make_config(demo)
from pathlib import Path
from typing import Sequence, Iterable
@ -40,17 +35,17 @@ class Item:
def inputs() -> Sequence[Path]:
return get_files(config().data_path)
return get_files(config.data_path)
import json
def items() -> Iterable[Item]:
for f in inputs():
dt = datetime.fromtimestamp(f.stat().st_mtime, tz=config().timezone)
dt = datetime.fromtimestamp(f.stat().st_mtime, tz=config.timezone)
j = json.loads(f.read_text())
for raw in j:
yield Item(
username=config().username,
username=config.username,
raw=raw,
dt=dt,
)

View file

@ -54,7 +54,40 @@ def test_dynamic_config_simplenamespace(tmp_path: Path) -> None:
my.config.demo = user_config # type: ignore[misc, assignment]
from my.demo import config
assert config().username == 'user3'
assert config.username == 'user3'
# make sure our config handling pattern does it as expected
def test_attribute_handling(tmp_path: Path) -> None:
# doesn't work without it!
# because the config from test_dybamic_config_1 is cached in my.demo.demo
del sys.modules['my.demo']
import pytz
nytz = pytz.timezone('America/New_York')
import my.config
class user_config:
# check that override is taken into the account
timezone = nytz
irrelevant = 'hello'
username = 'UUU'
data_path = f'{tmp_path}/*.json'
my.config.demo = user_config # type: ignore[misc, assignment]
from my.demo import config
assert config.username == 'UUU'
# mypy doesn't know about it, but the attribute is there
assert getattr(config, 'irrelevant') == 'hello'
# check that overriden default attribute is actually getting overridden
assert config.timezone == nytz