-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
any:peach: for :snake::snake::snake:
- Loading branch information
0 parents
commit f9d7e37
Showing
8 changed files
with
370 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
], | ||
) |