Source code for mlfarm.core

import importlib

[docs]class ClassBuilder: '''The instance of this class is callable with or without arguments and the call will create an instance of the python class identifier passed to the constructor. Args: _class (str): A python class identifier, e.g. mlfarm.core.ClassBuilder args (dict | list | object | None): Arguments for the constructor of _class. If None, the default constructor in called. Returns: ClassBuilder: A callable object that returns the instance of the class with the arguments provided. As an example we will be using the following class: >>> class A: ... def __init__(self, a): ... self.a = a ... >>> a = A(1) >>> a.a 1 >>> cb = ClassBuilder("__main__.A", {"a": 2}) >>> cb().a 2 >>> cb() == cb() False ''' def __init__(self, _class, args): split = _class.split('.') mn = '.'.join(split[:-1]) self._class = getattr(importlib.import_module(mn), split[-1]) self.args = args def __call__(self, **args): if (not self.args) and args: self.args = args elif self.args and args: if isinstance(self.args, dict) and isinstance(args, dict): self.args.update(args) if not self.args: obj = self._class() elif isinstance(self.args, list): obj = self._class(*self.args) elif isinstance(self.args, dict): obj = self._class(**self.args) else: obj = self._class(self.args) return obj
[docs]class BaseVisitor: '''This is a fake implementation of the visitor pattern. It has the purpose of offering a way to transform nested dicts and lists. The BaseVisitor class is the Identity transform so it returs a clone of the object. >>> bv = BaseVisitor() >>> a = [1, {'a': 2}, 3] >>> b = bv.visit(a) >>> b [1, {'a': 2}, 3] >>> b[0] = 2 >>> a [1, {'a': 2}, 3] '''
[docs] def visit(self, obj): '''This method is to be called on the object to be transformed. Args: obj (str | object | list | dict): Object to be transformed. Returns: str | object | list | dict: The output of the transformation. ''' if isinstance(obj, dict): return self.visit_dict(obj) elif isinstance(obj, list): return self.visit_list(obj) elif isinstance(obj, str): return self.visit_str(obj) else: return self.visit_obj(obj)
[docs] def visit_obj(self, obj): '''This is here to be overwriten in a child class. We know the argument is not list, dict or str. Args: obj ( object ): Object to be transformed. Returns: str | object | list | dict: The same object. >>> class ObjVisitor(BaseVisitor): ... def visit_obj(self, obj): ... if obj % 2 == 0: ... return obj // 2 ... else: ... return (obj + 1) // 2 ... >>> ov = ObjVisitor() >>> a = list(range(10)) >>> ov.visit(a) [0, 1, 1, 2, 2, 3, 3, 4, 4, 5] ''' return obj
[docs] def visit_str(self, obj): '''This is here to be overwriten in a child class. The argument is a string. Args: obj (str): Object to be transformed. Returns: str | object | list | dict: The same object. >>> class StrVisitor(BaseVisitor): ... def visit_str(self, obj): ... if obj == 'nok': ... return 'error' ... return obj ... >>> sv = StrVisitor() >>> a = [{'a': 'ok'}, {'b': 1}, {'c': 'nok'}] >>> sv.visit(a) [{'a': 'ok'}, {'b': 1}, {'c': 'error'}] ''' return obj
[docs] def visit_list(self, obj): '''This is here to be overwriten in a child class. The argument is a list. Args: obj (list): Object to be transformed. Returns: str | object | list | dict: The same object. >>> class ListVisitor(BaseVisitor): ... def visit_list(self, obj): ... try: ... return sum(obj) ... except: ... return super().visit_list(obj) ... >>> lv = ListVisitor() >>> a = {'a': [1, 2, 3], 'b': 'ok', 'c': [1, 2, 'nok']} >>> lv.visit(a) {'a': 6, 'b': 'ok', 'c': [1, 2, 'nok']} ''' return [self.visit(o) for o in obj]
[docs] def visit_dict(self, obj): '''This is here to be overwriten in a child class. The argument is a dict. Args: obj (dict): Object to be transformed. Returns: str | object | list | dict: The same object. >>> class DictVisitor(BaseVisitor): ... def visit_dict(self, obj): ... try: ... return { **obj, 'ab': obj['a'] + obj['b'] } ... except: ... return super().visit_dict(obj) ... >>> dv = DictVisitor() >>> a = [{'a': 'ok'}, {'b': 1}, {'a': 'o', 'b': 'k'}] >>> dv.visit(a) [{'a': 'ok'}, {'b': 1}, {'a': 'o', 'b': 'k', 'ab': 'ok'}] ''' return dict([(k, self.visit(v)) for k, v in obj.items()])
[docs]class ContextAwareVisitor(BaseVisitor): '''Extends BaseVisitor to keep the path to the current node. It is designed to be extended and/or used for debugging. ''' _path = []
[docs] def get_path(self): '''To be used in an child class. Returns: str: Full path to current node. ''' return ''.join(self._path)
[docs] def visit_list(self, obj): '''Appends "[i]" for each i index in the list before calling the child visit. ''' i = 0 res = [] for o in obj: self._path.append(f'[{i}]') res.append(self.visit(o)) self._path.pop() i+=1 return res
[docs] def visit_dict(self, obj): '''Appends ".k" for each k key in the dict before calling the child visit. ''' res = {} for k,v in obj.items(): self._path.append(k if len(self._path) == 0 else f'.{k}') res[k] = self.visit(v) self._path.pop() return res
[docs]class CompositeVisitor(BaseVisitor): '''The visit method of this class will chain the object trough all the visitor parameters. Args: *args: Arguments as a list, each should be an implementation of the BaseVisitor class. >>> class StrVisitor(BaseVisitor): ... def visit_str(self, obj): ... if obj == 'nok': ... return 0 ... return obj ... >>> class ListVisitor(BaseVisitor): ... def visit_list(self, obj): ... try: ... return sum(obj) ... except: ... return super().visit_list(obj) ... >>> vl = [ ... StrVisitor(), ... ListVisitor() ... ] ... >>> cv = CompositeVisitor(*vl) >>> a = {'a': [1, 2, 3], 'b': 'ok', 'c': [1, 2, 'nok']} >>> bo = vl[0].visit(a) >>> bo {'a': [1, 2, 3], 'b': 'ok', 'c': [1, 2, 0]} >>> bo = vl[1].visit(bo) >>> o = cv.visit(a) >>> o == bo True >>> o {'a': 6, 'b': 'ok', 'c': 3} ''' def __init__(self, *args): self.args = args
[docs] def visit(self, obj): for visitor in self.args: obj = visitor.visit(obj) return obj
[docs]class ClassVisitor(BaseVisitor): '''Extends BaseVisitor and transforms dicts into objects is a specific key is present. '''
[docs] def visit_dict(self, obj): '''If the argument has the "class" key, an instance of that class will be returned. Args: obj (dict): May have the "class" key, if not the parent visit_dict is called. Returns: dict | object | ClassBuilder: If the "builder" key is also present the backend instance of ClassBuilder is returned. >>> class A: ... def __init__(self, a): ... self.a = a ... >>> cv = ClassVisitor() >>> data = { ... "class": "__main__.A", ... "builder": True, ... "args": { ... "a": 1 ... } ... } ... >>> a = cv.visit(data) >>> a().a 1 >>> a(a=2).a 2 ''' if not obj.get('class', False): return super().visit_dict(obj) builder = ClassBuilder( obj['class'], self.visit(obj.get('args', None)) ) if obj.get('builder', False): return builder return builder()
if __name__ == "__main__": # class A is used in the tests, for some reason __main__.A is not working in the doctest context. class A: def __init__(self, a): self.a = a import doctest doctest.testmod()