import functools import importlib import inspect from abc import ABC from typing import Dict, List, Union import yaml from exceptions import ( DuplicatedNameError, DuplicatedTypeError, TypeNameResolveError, UnregisteredError, ) from scheduler.block import BaseBlock class Resolver: def __init__(self, config_path: str): with open(config_path, "r") as file: node_list = yaml.load(file, Loader=yaml.FullLoader) self.nodes = [] for n in node_list: if n.get("module"): module = importlib.import_module(n["module"]) cls = getattr(module, n["class"]) elif n["class"] == "int": cls = int elif n["class"] == "str": cls = str elif n["class"] == "float": cls = float elif n["class"] == "bool": cls = bool elif n["class"] == "list": cls = list elif n["class"] == "dict": cls = dict else: raise TypeNameResolveError(n["class"]) self.nodes.append( { "name": n["name"], "alias": n.get("alias", n["name"]), "category": n["category"], "dir": n.get("dir"), "class": cls, } ) self.consistent_assert() def consistent_assert(self): nameset = set() typeset = set() for n in self.nodes: name = n["name"] if name in nameset: raise DuplicatedNameError(name) if n["class"] in typeset: raise DuplicatedTypeError(n["class"]) nameset.add(name) typeset.add(n["class"]) if n["category"] == "builtin": continue types = set() if not self.is_abstract(self.lookup(name)): # add slots for check slots = self.slots(name) for s in slots.values(): types.add(s.annotation) if issubclass(n["class"], BaseBlock): # add inports for check inports = self.inports(name) for s in inports.values(): types.add(s.annotation) # add outport types.add(self.outport(name)) for t in types: if self.relookup(t) is None: raise UnregisteredError(name, t) def names(self) -> List[str]: return [n["name"] for n in self.nodes] @functools.lru_cache def lookup(self, name: str, key: str = "class") -> Union[str, type]: for n in self.nodes: if n["name"] == name: return n.get(key) return None @functools.lru_cache def relookup(self, cls: type) -> str: for n in self.nodes: if n["class"] == cls: return n["name"] return None @functools.lru_cache def is_abstract(self, cls: type) -> bool: for _, m in inspect.getmembers(cls): if getattr(m, "__isabstractmethod__", False): return True return False @functools.lru_cache def candidates(self, name: str) -> List[str]: cls = self.lookup(name) if cls is None: return [] names = [] for n in self.nodes: if not issubclass(n["class"], cls) or self.is_abstract(n["class"]): continue name = self.relookup(n["class"]) if name is not None: names.append(name) return names @functools.lru_cache def slots(self, name: str) -> Dict[str, inspect.Parameter]: cls = self.lookup(name) if cls is None: return None signature = inspect.signature(cls.__init__) parameters = dict(signature.parameters) parameters.pop("self") return parameters @functools.lru_cache def inports(self, name: str) -> Dict[str, inspect.Parameter]: cls = self.lookup(name) if cls is None: return None signature = inspect.signature(cls.__call__) parameters = dict(signature.parameters) parameters.pop("self") return parameters @functools.lru_cache def outport(self, name: str) -> type: cls = self.lookup(name) if cls is None: return None signature = inspect.signature(cls.__call__) return signature.return_annotation