Skip to content

Commit

Permalink
any:peach: for :snake::snake::snake:
Browse files Browse the repository at this point in the history
  • Loading branch information
OlofHarrysson committed Oct 19, 2019
0 parents commit f9d7e37
Show file tree
Hide file tree
Showing 8 changed files with 370 additions and 0 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Can I simply decorate config class? Or also want/need to inherit?

Do I want to assert so that class name isnt same as MasterConfig?

Do I want to inherit dataclasses? Whats the benefit? Do I automatically get freeze, hash, eq etc?


Need to be able to load configs. How does it work with objects?


How do we print objects?
How do we print nested configs?
If we have a function, which then calls another function. Can we show source code for both?


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.


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. No more passing around configs

Name suggestions:
Anyfig



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
2 changes: 2 additions & 0 deletions anyfig/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .masterconfig import MasterConfig, save_config, load_config
from .setup import setup_config, parse_args, choose_config, get_available_configs, overwrite, config_class, print_source
Empty file added anyfig/decorators.py
Empty file.
70 changes: 70 additions & 0 deletions anyfig/masterconfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from abc import ABC
import pprint
import inspect
from dataclasses import FrozenInstanceError


def load_config(path):
# TODO: Add checks so that object is config
print("Loading config")
with open(path, 'rb') as f:
return pickle.load(f)


def save_config(config_obj, path):
# TODO: Add checks so that object is config
print(f"Saving config @ {path}")
with open(path, 'wb') as f:
pickle.dump(config_obj, f, pickle.HIGHEST_PROTOCOL)

with open(path + '.txt', 'w') as f:
f.write(str(config_obj))


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

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

def get_parameters(self):
return self.__dict__

def __str__(self):
str_ = ""
params = vars(self)
params.pop('_frozen') # Dont print frozen
for key, val in params.items():
if hasattr(val, '__anyfig_print_source__'):
cls_str = val.__anyfig_print_source__()
s = f"'{key}':\n{cls_str}"
else:
s = pprint.pformat({key: val})

# Prettyprint adds some extra wings that I dont like
s = s.lstrip('{').rstrip('}').replace('\n ', '\n')
str_ += s + '\n'

return str_

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)

# 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"

def assert_name(name, method_name):
assert name != method_name, name_taken_msg

methods = inspect.getmembers(self, predicate=inspect.ismethod)
[assert_name(name, m[0]) for m in methods]
object.__setattr__(self, name, value)
68 changes: 68 additions & 0 deletions anyfig/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import anyfig


@print_source
class Noise():
def __init__(self, x, y):
self.x = x
self.y = y


@print_source
class Flip():
def __init__(self, x, y):
self.x = x
self.y = y
self.noise3 = Noise(1, 2)


@print_source
class Transformer():
def __init__(self, x):
# self.noise1 = Noise(x, x)
# self.flip = Flip(4, 0)
# self.noise2 = Noise(4, 0)
self.primitive = x


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


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


def main():
# config = setup_config()
config = setup_config(default_config='Train')
parameters = config.get_parameters()
print(parameters)
# params = str(config)
params = config.__dict__
print(params)
qwe

# config.frozen()
# config.frozen(freeze=False)
config.name = '123123'

print(config)
# print(config.name)


if __name__ == '__main__':
print("BEFORE MAIN")
main()
116 changes: 116 additions & 0 deletions anyfig/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from io import StringIO
import fire
import inspect
import argparse
import sys
from .masterconfig import MasterConfig


def setup_config(default_config=None): # TODO: Handle None
config_str = parse_args(default_config)
config = choose_config(config_str)
return config


def parse_args(default_config):
p = argparse.ArgumentParser()

p.add_argument('--config',
type=str,
default=default_config,
help='What config class to choose')

args, _ = p.parse_known_args()
return args.config


def choose_config(config_str):
# Create config object
available_configs = get_available_configs()
try:
config_class_ = available_configs[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 marked as '@config_class' -> {set(available_configs)}"
raise KeyError(err_msg) from e

# Overwrite parameters via optional input flags
config_obj = overwrite(config_obj)

# Freezes config
config_obj.frozen(freeze=True)
return config_obj


def get_available_configs():
available_configs = {}
for name, obj in inspect.getmembers(sys.modules[__name__]):
if inspect.isclass(obj) and issubclass(obj, MasterConfig):
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
and input flags. '''
class NullIO(StringIO):
def write(self, txt):
pass

def parse_unknown_flags(**kwargs):
return kwargs

sys.stdout = NullIO()
extra_arguments = fire.Fire(parse_unknown_flags)
sys.stdout = sys.__stdout__

for key, val in extra_arguments.items():
if key not in vars(config_obj):
err_str = f"The input parameter '{key}' isn't allowed. It's only possible to overwrite attributes that exist in the DefaultConfig class. Add your input parameter to the default class or catch it before this message"
raise NotImplementedError(err_str)
setattr(config_obj, key, val)

return config_obj


def config_class(func):
class_name = func.__name__
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
setattr(sys.modules[__name__], class_name, func)

return func


def print_source(func):
class_name = func.__name__
err_msg = (f"Can't decorate '{class_name}' of type {type(func)}. "
"Can only be used for classes")

assert inspect.isclass(func), err_msg

def __print_source__(self):
''' Get my source. Get my childrens sources '''

# Newline makes indention better
src = '\n' + inspect.getsource(self.__class__)

unique_classes = {v.__class__: v for k, v in vars(self).items()}
for key, val in unique_classes.items():
if hasattr(val, '__anyfig_print_source__'):
src += __print_source__(val)

# TODO: Source code can have different indention than \t
# Make it a config to anyfig?
# Adds one indention
src = src.replace('\n', '\n ')
return src

setattr(func, '__anyfig_print_source__', __print_source__)
return func
55 changes: 55 additions & 0 deletions run_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import anyfig


@anyfig.print_source
class Noise():
def __init__(self, x, y):
self.x = x
self.y = y


@anyfig.print_source
class Flip():
def __init__(self, x, y):
self.x = x
self.y = y
self.noise3 = Noise(1, 2)


@anyfig.print_source
class Transformer():
def __init__(self, x):
# self.noise1 = Noise(x, x)
# self.flip = Flip(4, 0)
# self.noise2 = Noise(4, 0)
self.primitive = x


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


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


def main():
config = anyfig.setup_config(default_config='Train')
print(config)


if __name__ == '__main__':
print("BEFORE MAIN")
main()
30 changes: 30 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from distutils.core import setup
setup(
name='anyfig',
packages=['anyfig'],
version='0.0.1',
license='MIT',
description='Config parameters in Python code. Anything is possible ;)',
author='Olof Harrysson',
author_email='harrysson.olof@gmail.com',
url=
'https://github.com/user/reponame', # Provide either the link to your github or to your website
download_url=
'https://github.com/user/reponame/archive/v_01.tar.gz', # I explain this later on
keywords=['SOME', 'MEANINGFULL',
'KEYWORDS'], # Keywords that define your package best
install_requires=[ # I get to this in a second
'validators',
'beautifulsoup4',
],
classifiers=[
'Development Status :: 3 - Alpha', # Chose either "3 - Alpha", "4 - Beta" or "5 - Production/Stable" as the current state of your package
'Intended Audience :: Developers', # Define that your audience are developers
'Topic :: Software Development :: Build Tools',
'License :: OSI Approved :: MIT License', # Again, pick a license
'Programming Language :: Python :: 3', #Specify which pyhton versions that you want to support
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],
)

0 comments on commit f9d7e37

Please sign in to comment.