update configuration doc & more tests
This commit is contained in:
parent
cda6bd51ce
commit
522bfff679
3 changed files with 47 additions and 16 deletions
|
@ -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~.
|
||||
|
|
11
my/demo.py
11
my/demo.py
|
@ -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 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,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue