Skip to content

Commit

Permalink
Merge pull request #1 from OlofHarrysson/noinherit
Browse files Browse the repository at this point in the history
Removed the need for inheriting from MasterConfig
  • Loading branch information
OlofHarrysson authored Nov 11, 2019
2 parents 8287369 + de53866 commit d6c2d1b
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 54 deletions.
9 changes: 3 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ import anyfig
import random

@anyfig.config_class
class FooConfig(anyfig.MasterConfig):
class FooConfig():
def __init__(self):
super().__init__()
# Config-parameters goes as attributes
self.experiment_note = 'Changed some stuff'
self.seed = random.randint(0, 80085)
Expand All @@ -47,9 +46,8 @@ import anyfig
import random

@anyfig.config_class
class FooConfig(anyfig.MasterConfig):
class FooConfig():
def __init__(self):
super().__init__()
self.experiment_note = 'Changed some stuff'
self.seed = random.randint(0, 80085)

Expand All @@ -75,9 +73,8 @@ import anyfig
import random

@anyfig.config_class
class FooConfig(anyfig.MasterConfig):
class FooConfig():
def __init__(self):
super().__init__()
self.experiment_note = 'Changed some stuff'
self.seed = random.randint(0, 80085)

Expand Down
2 changes: 1 addition & 1 deletion anyfig/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from anyfig.masterconfig import MasterConfig, save_config, load_config
from anyfig.anyfig_setup import setup_config, parse_args, choose_config, get_available_configs, overwrite, config_class, print_source
from anyfig.anyfig_setup import setup_config, parse_args, choose_config, overwrite, config_class, print_source
53 changes: 29 additions & 24 deletions anyfig/anyfig_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import sys
from .masterconfig import MasterConfig, is_anyfig_class

registered_config_classes = {}


def setup_config(default_config=None):
config_str = parse_args(default_config)
Expand All @@ -26,22 +28,27 @@ def parse_args(default_config):

def choose_config(config_str):
# Create config object
available_configs = get_available_configs()
err_msg = (
"Specify which config to use by either starting your python script with "
"the input argument --config=YourConfigClass or set "
"'default_config=YourConfigClass' in anyfigs setup_config method")
"'default_config=YourConfigClass' in anyfigs 'setup_config' method")
if config_str == None:
raise RuntimeError(err_msg)

err_msg = ("There aren't any registered config classes. Decorate a class "
"with '@anyfig.config_class' and make sure that the class is "
"imported to the file where the function 'anyfig.setup_config' "
"is called from")
assert len(registered_config_classes), err_msg

try:
config_class_ = available_configs[config_str]
config_class_ = registered_config_classes[config_str]
config_obj = config_class_()
except KeyError as e:
err_msg = (
f"Config class '{config_str}' wasn't found. Feel free to create "
"it as a new config class or use one of the existing ones "
f"{list(available_configs)}")
f"{list(registered_config_classes)}")
raise KeyError(err_msg) from e

# Overwrite parameters via optional input flags
Expand All @@ -52,15 +59,6 @@ def choose_config(config_str):
return config_obj


def get_available_configs():
available_configs = {}
for name, obj in inspect.getmembers(sys.modules[__name__]):
if is_anyfig_class(obj):
available_configs[name] = obj
available_configs.pop('MasterConfig')
return available_configs


def overwrite(config_obj):
''' Overwrites parameters with input flags. Function is needed for the
convenience of specifying parameters via a combination of the config classes
Expand Down Expand Up @@ -93,25 +91,32 @@ def config_class(func):
class_name = func.__name__
module_name = sys.modules[__name__]

# Makes sure that nothing fishy is going on...
err_msg = (f"Can't decorate '{class_name}' of type {type(func)}. "
"Can only be used for classes")
assert inspect.isclass(func), err_msg

err_msg = (f"Can't decorate '{class_name}' since it's not a sublass of "
"'chilliconfig.MasterConfig'")
assert issubclass(func, MasterConfig), err_msg

err_msg = "The class name 'MasterConfig' is reserved"
assert class_name != 'MasterConfig', err_msg

err_msg = (
f"The config class {class_name} has already been registered. "
f"The config class '{class_name}' has already been registered. "
"Duplicated names aren't allowed. Either change the name or avoid "
"importing the duplicated classes at the same time")
assert not hasattr(module_name, class_name), err_msg
"importing the duplicated classes at the same time. "
f"The registered classes are '{registered_config_classes}'")
assert class_name not in registered_config_classes, err_msg

# Config class functions
members = inspect.getmembers(func, inspect.isfunction)
members = {name: function for name, function in members}

# Transfers functions from MasterConfig to config class
for name, member in inspect.getmembers(MasterConfig, inspect.isfunction):
if name not in members: # Only transfer not implemented functions
setattr(func, name, member)

setattr(module_name, class_name, func)
# Manually add attributes to config class
setattr(func, "_frozen", False)
setattr(func, "config_class", func.__name__)

registered_config_classes[class_name] = func
return func


Expand Down
23 changes: 11 additions & 12 deletions anyfig/masterconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from dataclasses import FrozenInstanceError
import pickle
from pathlib import Path
import copy


def is_anyfig_class(obj):
Expand Down Expand Up @@ -31,15 +32,14 @@ def save_config(obj, path):


class MasterConfig(ABC):
def __init__(self):
self._frozen: bool = False
self.config_class = type(self).__name__

def frozen(self, freeze=True):
self._frozen = freeze
return self

def get_parameters(self):
return self.__dict__
params = copy.deepcopy(self.__dict__)
params.pop('_frozen')
return params

def __str__(self):
str_ = ""
Expand All @@ -61,13 +61,12 @@ def __str__(self):

def __setattr__(self, name, value):
# Raise error if frozen unless we're trying to unfreeze the config
if hasattr(self, '_frozen'):
if name == '_frozen':
pass
elif self._frozen:
err_msg = (f"Cannot set attribute '{name}'. Config object is frozen. "
"Unfreeze the config for a mutable config object")
raise FrozenInstanceError(err_msg)
if name == '_frozen':
pass
elif self._frozen:
err_msg = (f"Cannot set attribute '{name}'. Config object is frozen. "
"Unfreeze the config for a mutable config object")
raise FrozenInstanceError(err_msg)

# Check for reserved names
name_taken_msg = f"The attribute '{name}' can't be assigned to config '{type(self).__name__}' since it already has a method by that name"
Expand Down
9 changes: 3 additions & 6 deletions example/1_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,27 @@ def __init__(self, x):


@anyfig.config_class
class MainConfig(anyfig.MasterConfig):
class MainConfig():
def __init__(self):
print("MAIN CONFIG SUPER")
super().__init__()
# self.start_time = time.time()
self.img_size = 100
self.classes = ['car', 'dog']
self.freeze_config = False


@anyfig.config_class
class Train(anyfig.MasterConfig):
class Train():
def __init__(self):
super().__init__()
self.name = 'oldname'
self.transforms111 = Transformer(100)
# self.freeze_config = False
# self.frozen = 123


@anyfig.config_class
class Config(anyfig.MasterConfig):
class Config():
def __init__(self):
super().__init__()
self.name = 'oldname1111'
self.transforms111 = Transformer(100)
# self.freeze_config = False
Expand Down
18 changes: 16 additions & 2 deletions example/2_example.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import anyfig
import random
# import otherfile


class RandomClass():
def __init__(self):
self.seed = 0
self.hej = 'wowowo'


@anyfig.config_class
class FooConfig(anyfig.MasterConfig):
class FooConfig2(RandomClass):
def __init__(self):
print("INIT FOOCONFIG")
super().__init__()
# self.frozen = 123
self.experiment_note = 'Changed some stuff'
self.seed = random.randint(0, 80085)

def myfunc(self):
pass

# def __str__(self):
# return 'strannnng'


config = anyfig.setup_config(default_config='FooConfig')
print(config)
print(config.seed) # Prints -1
7 changes: 7 additions & 0 deletions example/otherfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import anyfig


@anyfig.config_class
class FooConfig2():
def __init__(self):
self.experiment_note = 'Changed some stuff'
3 changes: 0 additions & 3 deletions notes.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
Would like to be able to use dataclasses structure. Its possible now but the type hinting is needed, and its not enforced so just seems bad to use.

Can do I want to remove the inheritence from anyfig.MasterConfig? It looks neater but might be a bit harder for people to grasp what is happening. I don't want it to inherit form masterConfig and then when people inherit other shit it breaks.

make it so __repr__ is ambigious


Should have a function to register the config object in the anyfig module which can later simply be imported anywhere. Singleton idea. No more passing around configs



Do we want to let users be able to create several configs? The fire/argparse doesn't work then. Unless we hook into that and divides it... But not for v1

Mark classes / properties as final? For properties, it would be cool to just
Expand Down

0 comments on commit d6c2d1b

Please sign in to comment.