Skip to content
Snippets Groups Projects
Commit 1953ae2a authored by Jon Crall's avatar Jon Crall
Browse files

inheritence for configs

parent ca241da2
No related branches found
No related tags found
1 merge request!23Start branch for dev/0.7.6
Pipeline #327016 failed
from dataclasses import dataclass
from scriptconfig import DataConfig
import ubelt as ub
@dataclass
class Data1:
arg1 = 1
arg2 = 2
arg3 = 3
class Data2(Data1):
arg4 = 4
arg5 = 5
arg6 = 6
class Data3(Data2):
arg2 = 22
arg3 = 33
arg5 = 55
class Config1(DataConfig):
arg1 = 1
arg2 = 2
arg3 = 3
class Config2(Config1):
arg4 = 4
arg5 = 5
arg6 = 6
class Config3(Config2):
arg2 = 22
arg3 = 33
arg5 = 55
def dc_dict(d):
return {k: getattr(d, k) for k in dir(d) if not k.startswith('_')}
def main():
from rich import print
d1 = Data1()
d2 = Data2()
d3 = Data3()
print('d1 = {}'.format(ub.urepr(dc_dict(d1), nl=1)))
print('d2 = {}'.format(ub.urepr(dc_dict(d2), nl=1)))
print('d3 = {}'.format(ub.urepr(dc_dict(d3), nl=1)))
c1 = Config1()
c2 = Config2()
c3 = Config3()
print('c1 = {}'.format(ub.urepr(c1, nl=1)))
print('c2 = {}'.format(ub.urepr(c2, nl=1)))
print('c3 = {}'.format(ub.urepr(c3, nl=1)))
if __name__ == '__main__':
"""
CommandLine:
python ~/code/scriptconfig/dev/devcheck/check_dataclass.py
"""
main()
......@@ -173,16 +173,36 @@ class MetaConfig(type):
@staticmethod
def __new__(mcls, name, bases, namespace, *args, **kwargs):
# print(f'MetaConfig.__new__ called: {mcls=} {name=} {bases=} {namespace=} {args=} {kwargs=}')
if 'default' in namespace and '__default__' not in namespace:
# Ensure the user updates to the newer "__default__" paradigm
namespace['__default__'] = namespace['default']
this_default = namespace['__default__'] = namespace['default']
ub.schedule_deprecation(
'scriptconfig', 'default', f'class attribute of {name}',
migration='Use __default__ instead',
deprecate='0.7.6', error='0.8.0', remove='0.9.0',
)
HANDLE_INHERITENCE = 1
if HANDLE_INHERITENCE:
# Handle inheritence, add in defaults from base classes
# Not sure this is exactly correct. Experimental.
this_default = namespace.get('__default__', {})
if this_default is None:
this_default = {}
this_default = ub.udict(this_default)
inheritence_default = {}
for base in bases:
if hasattr(base, '__default__'):
inheritence_default.update(base.__default__)
# unseen = base.__default__ - this_default
# this_default.update(unseen)
inheritence_default.update(this_default)
this_default = inheritence_default
namespace['__default__'] = namespace['default'] = this_default
if '__default__' in namespace and 'default' not in namespace:
# Backport to the older non-dunder __default__
namespace['default'] = namespace['__default__']
......@@ -200,6 +220,7 @@ class MetaConfig(type):
# Backport to the older non-dunder normalize
namespace['normalize'] = namespace['__post_init__']
# print('FINAL namespace = {}'.format(ub.urepr(namespace, nl=2)))
cls = super().__new__(mcls, name, bases, namespace, *args, **kwargs)
return cls
......@@ -275,6 +296,7 @@ class Config(ub.NiceRepr, DictLike, metaclass=MetaConfig):
>>> config2 = MyConfig(default=dict(option1='baz'))
"""
__scfg_class__ = 'Config'
__default__ = {}
def __init__(self, data=None, default=None, cmdline=False):
"""
......
......@@ -180,7 +180,7 @@ class MetaDataConfig(MetaConfig):
@staticmethod
def __new__(mcls, name, bases, namespace, *args, **kwargs):
# Defining a new class that inherits from DataConfig
# print(f'Meta.__new__ called: {mcls=} {name=} {bases=} {namespace=} {args=} {kwargs=}')
# print(f'MetaDataConfig.__new__ called: {mcls=} {name=} {bases=} {namespace=} {args=} {kwargs=}')
# Only do this for children of DataConfig, skip this for DataConfig
# itself. This is a hacky way to do that.
......@@ -192,19 +192,20 @@ class MetaDataConfig(MetaConfig):
for k, v in namespace.items():
if not k.startswith('_') and not callable(v) and not isinstance(v, classmethod):
attr_default[k] = v
default = attr_default.copy()
this_default = attr_default.copy()
cls_default = namespace.get('__default__', None)
if cls_default is None:
cls_default = {}
default.update(cls_default)
this_default.update(cls_default)
# Helps make the class pickleable. Pretty hacky though.
for k in attr_default:
namespace.pop(k)
namespace['__default__'] = default
namespace['__default__'] = this_default
# print(f'this_default={this_default}')
namespace['__did_dataconfig_init__'] = True
for k, v in default.items():
for k, v in this_default.items():
if isinstance(v, tuple) and len(v) == 1 and isinstance(v[0], Value):
warnings.warn(ub.paragraph(
f'''
......
def test_inheritence():
from scriptconfig import DataConfig
import ubelt as ub
class Config1(DataConfig):
arg1 = 1
arg2 = 2
arg3 = 3
class Config2(Config1):
arg4 = 4
arg5 = 5
arg6 = 6
class Config3(Config2):
arg2 = 22
arg3 = 33
arg5 = 55
c1 = Config1()
c2 = Config2()
c3 = Config3()
text1 = ('c1 = {}'.format(ub.urepr(c1, nl=1)))
text2 = ('c2 = {}'.format(ub.urepr(c2, nl=1)))
text3 = ('c3 = {}'.format(ub.urepr(c3, nl=1)))
print(text1)
print(text2)
print(text3)
assert text1 == ub.codeblock(
'''
c1 = Config1({
'arg1': 1,
'arg2': 2,
'arg3': 3,
})
''')
assert text2 == ub.codeblock(
'''
c2 = Config2({
'arg1': 1,
'arg2': 2,
'arg3': 3,
'arg4': 4,
'arg5': 5,
'arg6': 6,
})
''')
assert text3 == ub.codeblock(
'''
c3 = Config3({
'arg1': 1,
'arg2': 22,
'arg3': 33,
'arg4': 4,
'arg5': 55,
'arg6': 6,
})
''')
def test_scriptconfig_repr():
# TODO: maybe this extension lives in scriptconfig itself instead?
import scriptconfig as scfg
import ubelt as ub
class MyConfig(scfg.DataConfig):
arg1 = 1
arg2 = 2
c = MyConfig()
text = ub.urepr(c, nl=1)
assert text == ub.codeblock(
'''
MyConfig({
'arg1': 1,
'arg2': 2,
})
''')
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment