Skip to content

Commit 6eefae3

Browse files
authored
Merge pull request #456 from seeM/layered-config
layered `Config` with `extra_files`; `Config` `repr`
2 parents d59b81e + 223aa82 commit 6eefae3

File tree

2 files changed

+121
-17
lines changed

2 files changed

+121
-17
lines changed

fastcore/foundation.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,16 +249,19 @@ def read_config_file(file, **kwargs):
249249
# %% ../nbs/02_foundation.ipynb 131
250250
class Config:
251251
"Reading and writing `ConfigParser` ini files"
252-
def __init__(self, cfg_path, cfg_name, create=None, save=True):
252+
def __init__(self, cfg_path, cfg_name, create=None, save=True, extra_files=None):
253253
cfg_path = Path(cfg_path).expanduser().absolute()
254254
self.config_path,self.config_file = cfg_path,cfg_path/cfg_name
255-
if self.config_file.exists(): self.d = read_config_file(self.config_file)
256-
elif create is not None:
257-
self.d = ConfigParser(create)['DEFAULT']
255+
self._cfg = ConfigParser()
256+
self.d = self._cfg['DEFAULT']
257+
found = [Path(o) for o in self._cfg.read(L(extra_files)+[self.config_file])]
258+
if self.config_file not in found and create is not None:
259+
self._cfg.read_dict({'DEFAULT':create})
258260
if save:
259261
cfg_path.mkdir(exist_ok=True, parents=True)
260-
self.save()
262+
save_config_file(self.config_file, create)
261263

264+
def __repr__(self): return repr(dict(self._cfg.items('DEFAULT', raw=True)))
262265
def __setitem__(self,k,v): self.d[k] = str(v)
263266
def __contains__(self,k): return k in self.d
264267
def save(self): save_config_file(self.config_file,self.d)

nbs/02_foundation.ipynb

Lines changed: 113 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2004,16 +2004,19 @@
20042004
"#|export\n",
20052005
"class Config:\n",
20062006
" \"Reading and writing `ConfigParser` ini files\"\n",
2007-
" def __init__(self, cfg_path, cfg_name, create=None, save=True):\n",
2007+
" def __init__(self, cfg_path, cfg_name, create=None, save=True, extra_files=None):\n",
20082008
" cfg_path = Path(cfg_path).expanduser().absolute()\n",
20092009
" self.config_path,self.config_file = cfg_path,cfg_path/cfg_name\n",
2010-
" if self.config_file.exists(): self.d = read_config_file(self.config_file)\n",
2011-
" elif create is not None:\n",
2012-
" self.d = ConfigParser(create)['DEFAULT']\n",
2010+
" self._cfg = ConfigParser()\n",
2011+
" self.d = self._cfg['DEFAULT']\n",
2012+
" found = [Path(o) for o in self._cfg.read(L(extra_files)+[self.config_file])]\n",
2013+
" if self.config_file not in found and create is not None:\n",
2014+
" self._cfg.read_dict({'DEFAULT':create})\n",
20132015
" if save:\n",
20142016
" cfg_path.mkdir(exist_ok=True, parents=True)\n",
2015-
" self.save()\n",
2017+
" save_config_file(self.config_file, create)\n",
20162018
"\n",
2019+
" def __repr__(self): return repr(dict(self._cfg.items('DEFAULT', raw=True)))\n",
20172020
" def __setitem__(self,k,v): self.d[k] = str(v)\n",
20182021
" def __contains__(self,k): return k in self.d\n",
20192022
" def save(self): save_config_file(self.config_file,self.d)\n",
@@ -2033,26 +2036,70 @@
20332036
"`Config` is a convenient wrapper around `ConfigParser` ini files with a single section (`DEFAULT`)."
20342037
]
20352038
},
2039+
{
2040+
"cell_type": "markdown",
2041+
"metadata": {},
2042+
"source": [
2043+
"Instantiate a `Config` from an ini file at `cfg_path/cfg_name`:"
2044+
]
2045+
},
20362046
{
20372047
"cell_type": "code",
20382048
"execution_count": null,
20392049
"metadata": {},
2040-
"outputs": [],
2050+
"outputs": [
2051+
{
2052+
"data": {
2053+
"text/plain": [
2054+
"{'user': 'fastai', 'lib_name': 'fastcore', 'some_path': 'test'}"
2055+
]
2056+
},
2057+
"execution_count": null,
2058+
"metadata": {},
2059+
"output_type": "execute_result"
2060+
}
2061+
],
2062+
"source": [
2063+
"save_config_file('../tmp.ini', _d)\n",
2064+
"try: cfg = Config('..', 'tmp.ini')\n",
2065+
"finally: os.unlink('../tmp.ini')\n",
2066+
"cfg"
2067+
]
2068+
},
2069+
{
2070+
"cell_type": "markdown",
2071+
"metadata": {},
2072+
"source": [
2073+
"You can create a new file if one doesn't exist by providing a `create` dict:"
2074+
]
2075+
},
2076+
{
2077+
"cell_type": "code",
2078+
"execution_count": null,
2079+
"metadata": {},
2080+
"outputs": [
2081+
{
2082+
"data": {
2083+
"text/plain": [
2084+
"{'user': 'fastai', 'lib_name': 'fastcore', 'some_path': 'test'}"
2085+
]
2086+
},
2087+
"execution_count": null,
2088+
"metadata": {},
2089+
"output_type": "execute_result"
2090+
}
2091+
],
20412092
"source": [
20422093
"try: cfg = Config('..', 'tmp.ini', create=_d)\n",
20432094
"finally: os.unlink('../tmp.ini')\n",
2044-
"\n",
2045-
"test_eq(cfg.user,'fastai')\n",
2046-
"test_eq(cfg['some_path'], 'test')\n",
2047-
"test_eq(cfg.path('some_path'), Path('../test').absolute())\n",
2048-
"test_eq(cfg.get('foo','bar'),'bar')"
2095+
"cfg"
20492096
]
20502097
},
20512098
{
20522099
"cell_type": "markdown",
20532100
"metadata": {},
20542101
"source": [
2055-
"You can also create an in-memory `Config` by passing `save=False`:"
2102+
"If you additionally pass `save=False`, the `Config` will contain the items from `create` without writing a new file:"
20562103
]
20572104
},
20582105
{
@@ -2066,6 +2113,60 @@
20662113
"assert not Path('../tmp.ini').exists()"
20672114
]
20682115
},
2116+
{
2117+
"cell_type": "markdown",
2118+
"metadata": {},
2119+
"source": [
2120+
"Keys can be accessed as attributes, items, or with `get` and an optional default:"
2121+
]
2122+
},
2123+
{
2124+
"cell_type": "code",
2125+
"execution_count": null,
2126+
"metadata": {},
2127+
"outputs": [],
2128+
"source": [
2129+
"test_eq(cfg.user,'fastai')\n",
2130+
"test_eq(cfg['some_path'], 'test')\n",
2131+
"test_eq(cfg.get('foo','bar'),'bar')"
2132+
]
2133+
},
2134+
{
2135+
"cell_type": "markdown",
2136+
"metadata": {},
2137+
"source": [
2138+
"Use `path` to access keys as `pathlib.Path`s relative to `cfg_path`:"
2139+
]
2140+
},
2141+
{
2142+
"cell_type": "code",
2143+
"execution_count": null,
2144+
"metadata": {},
2145+
"outputs": [],
2146+
"source": [
2147+
"test_eq(cfg.path('some_path'), Path('../test').absolute())"
2148+
]
2149+
},
2150+
{
2151+
"cell_type": "markdown",
2152+
"metadata": {},
2153+
"source": [
2154+
"Extra files can be read _before_ `cfg_path/cfg_name` using `extra_files`, in the order they appear:"
2155+
]
2156+
},
2157+
{
2158+
"cell_type": "code",
2159+
"execution_count": null,
2160+
"metadata": {},
2161+
"outputs": [],
2162+
"source": [
2163+
"with tempfile.TemporaryDirectory() as d:\n",
2164+
" a = Config(d, 'a.ini', {'a':0,'b':0})\n",
2165+
" b = Config(d, 'b.ini', {'a':1,'c':0})\n",
2166+
" c = Config(d, 'c.ini', {'a':2,'d':0}, extra_files=[a.config_file,b.config_file])\n",
2167+
" test_eq(c.d, {'a':'2','b':'0','c':'0','d':'0'})"
2168+
]
2169+
},
20692170
{
20702171
"cell_type": "markdown",
20712172
"metadata": {},

0 commit comments

Comments
 (0)