Exemple #1
0
class Quirk(abc.ABC):
    """Base class for sourdough quirks (mixin-approximations).
    
    Args:
        library (ClassVar[sourdough.Library]): related Library instance that 
            will store concrete subclasses and allow runtime construction and 
            instancing of those stored subclasses.
    
    Namespaces: library, bases
    
    """
    library: ClassVar[sourdough.Library] = sourdough.Library(
        contents={'base': Base})
    """ Initialization Methods """
    def __init_subclass__(cls, **kwargs):
        """Adds 'cls' to 'library' if it is a concrete class."""
        super().__init_subclass__(**kwargs)
        # Creates a snakecase key of the class name.
        key = sourdough.tools.snakify(cls.__name__)
        # Adds base classes to 'bases' using 'key'.
        if not hasattr(Base.bases, 'quirk'):
            setattr(Base.bases, 'quirks', cls)
            setattr(cls, 'bases', Base.bases)
            setattr(cls.bases, key, cls)
        # Adds concrete subclasses to 'library' using 'key'.
        if not abc.ABC in cls.__bases__:
            cls.library[key] = cls
Exemple #2
0
class Stage(sourdough.quirks.Base, sourdough.quirks.Needy, abc.ABC):
    """Creates a sourdough object.

    Args:
        needs (ClassVar[Union[Sequence[str], str]]): attributes needed from 
            another instance for some method within a subclass. Defaults to an
            empty list.
        library (ClassVar[Library]): related Library instance that will store
            subclasses and allow runtime construction and instancing of those
            stored subclasses.              
            
    """
    needs: ClassVar[Union[Sequence[str], str]] = []
    library: ClassVar[sourdough.Library] = sourdough.Library()
    """ Class Methods """
    @classmethod
    def create(cls, **kwargs) -> object:
        """[summary]

        Raises:
            ValueError: [description]

        Returns:
            object: [description]
            
        """
        needs = list(more_itertools.always_iterable(cls.needs))
        method = getattr(cls, f'from_{needs[0]}')
        for need in needs:
            if need not in kwargs:
                raise ValueError(
                    f'The create method must include a {need} argument')
        return method(**kwargs)
Exemple #3
0
 def __init_subclass__(cls, **kwargs):
     """Adds 'cls' to 'library' if it is a concrete class."""
     super().__init_subclass__(**kwargs)
     # Creates a snakecase key of the class name.
     key = sourdough.tools.snakify(cls.__name__)
     if Base in cls.__bases__:
         # Adds this class to 'bases' using 'key'.
         setattr(cls.bases, key, cls)
         # Creates a library on this class base for storing subclasses.
         cls.library = sourdough.Library()
     # Adds concrete subclasses to 'library' using 'key'.
     if not abc.ABC in cls.__bases__:
         cls.library[key] = cls
Exemple #4
0
class Settings(sourdough.quirks.Base, sourdough.Configuration):
    """Loads and stores configuration settings for a Project.

    Args:
        contents (Union[str, pathlib.Path, Mapping[str, Mapping[str, Any]]]): a 
            dict, a str file path to a file with settings, or a pathlib Path to
            a file with settings. Defaults to en empty dict.
        infer_types (bool): whether values in 'contents' are converted to other 
            datatypes (True) or left alone (False). If 'contents' was imported 
            from an .ini file, a False value will leave all values as strings. 
            Defaults to True.
        defaults (Mapping[str, Mapping[str]]): any default options that should
            be used when a user does not provide the corresponding options in 
            their configuration settings. Defaults to a dict with 'general'
            and 'files' section listed in the class annotations.
        skip (Sequence[str]): names of suffixes to skip when constructing nodes
            for a sourdough project.
        library (ClassVar[Library]): related Library instance that will store
            subclasses and allow runtime construction and instancing of those
            stored subclasses.    
            
    """
    contents: Union[str, pathlib.Path,
                    Mapping[str, Mapping[str, Any]]] = (dataclasses.field(
                        default_factory=dict))
    infer_types: bool = True
    defaults: Mapping[str, Mapping[str, Any]] = dataclasses.field(
        default_factory=lambda: {
            'general': {
                'verbose': False,
                'parallelize': False,
                'conserve_memery': False
            },
            'files': {
                'source_format': 'csv',
                'interim_format': 'csv',
                'final_format': 'csv',
                'file_encoding': 'windows-1252'
            },
            'sourdough': {
                'default_design': 'pipeline',
                'default_workflow': 'graph'
            }
        })
    skip: Sequence[str] = dataclasses.field(
        default_factory=lambda:
        ['general', 'files', 'sourdough', 'parameters'])
    library: ClassVar[sourdough.Library] = sourdough.Library()
Exemple #5
0
class Base(abc.ABC):
    """Abstract base class for automatic registration of subclasses. 
    
    Even though not technically a Quirk subclass (because Quirk inherits from
    Base), it should be used in the same manner. Base is used throughout the
    project subpackage as a Quirk to automatically store base subclasses.
    
    Any non-abstract subclass will automatically store itself in the class 
    attribute 'library' using the snakecase name of the class as the key.
    
    Any direct subclass will automatically store itself in the class attribute 
    'bases' using the snakecase name of the class as the key.
    
    Args:
        bases (ClassVar[sourdough.Library]): related Library instance that will 
            store direct subclasses (those with Base in their '__bases__'
            attribute) and allow runtime construction and instancing of those
            stored subclasses.
    
    Attributes:
        library (ClassVar[sourdough.Library]): related Library instance that 
            will store concrete subclasses and allow runtime construction and 
            instancing of those stored subclasses. 'library' is automatically
            created when a direct Base subclass (Base is in its '__bases__') is 
            instanced.
            
    Namespaces: library, bases, and __init_subclass__.
    
    """
    bases: ClassVar[sourdough.Library] = sourdough.Library()
    """ Initialization Methods """
    def __init_subclass__(cls, **kwargs):
        """Adds 'cls' to 'library' if it is a concrete class."""
        super().__init_subclass__(**kwargs)
        # Creates a snakecase key of the class name.
        key = sourdough.tools.snakify(cls.__name__)
        if Base in cls.__bases__:
            # Adds this class to 'bases' using 'key'.
            setattr(cls.bases, key, cls)
            # Creates a library on this class base for storing subclasses.
            cls.library = sourdough.Library()
        # Adds concrete subclasses to 'library' using 'key'.
        if not abc.ABC in cls.__bases__:
            cls.library[key] = cls
Exemple #6
0
class Validator(sourdough.quirks.Quirk):
    """Mixin for calling validation methods

    Args:
        validations (List[str]): a list of attributes that need validating.
            Each item in 'validations' should have a corresponding method named 
            f'_validate_{name}' or match a key in 'converters'. Defaults to an 
            empty list. 
        converters (sourdough.Library):
               
    """
    validations: ClassVar[Sequence[str]] = []
    converters: ClassVar[sourdough.Library] = sourdough.Library()
    """ Public Methods """
    def initialize_converter(
            self, name: str, converter: Union[str, Converter,
                                              Type[Converter]]) -> Converter:
        """[summary]

        Args:
            converter (Union[Converter, Type[Converter]]): [description]

        Returns:
            Converter: [description]
        """
        if isinstance(converter, str):
            try:
                converter = self.converters[name]
            except KeyError:
                raise KeyError(
                    f'No local or stored type validator exists for {name}')
        if not isinstance(converter, Converter):
            converter = converter()
            self.converters[name] = converter
        return converter

    def validate(self, validations: Sequence[str] = None) -> None:
        """Validates or converts stored attributes.
        
        Args:
            validations (List[str]): a list of attributes that need validating.
                Each item in 'validations' should have a corresponding method 
                named f'_validate_{name}' or match a key in 'converters'. If not 
                passed, the 'validations' attribute will be used instead. 
                Defaults to None. 
        
        """
        if validations is None:
            validations = self.validations
        # Calls validation methods based on names listed in 'validations'.
        for name in validations:
            if hasattr(self, f'_validate_{name}'):
                kwargs = {name: getattr(self, name)}
                validated = getattr(self, f'_validate_{name}')(**kwargs)
            else:
                converter = self.initialize_converter(name=name,
                                                      converter=name)
                try:
                    validated = converter.validate(item=getattr(self, name),
                                                   instance=self)
                except AttributeError:
                    validated = getattr(self, name)
            setattr(self, name, validated)
        return self
Exemple #7
0
class Component(sourdough.quirks.Base, sourdough.quirks.Element, abc.ABC):
    """Base class for parts of a sourdough Workflow.

    Args:
        name (str): designates the name of a class instance that is used for 
            internal referencing throughout sourdough. For example, if a 
            sourdough instance needs settings from a Configuration instance, 
            'name' should match the appropriate section name in a Configuration 
            instance. Defaults to None.
        contents (Any): stored item(s) for use by a Component subclass instance.
        library (ClassVar[Library]): related Library instance that will store
            subclasses and allow runtime construction and instancing of those
            stored subclasses.    
                
    """
    name: str = None
    contents: Any = None
    iterations: Union[int, str] = 1
    parameters: Mapping[Any, Any] = dataclasses.field(default_factory=dict)
    parallel: ClassVar[bool] = False
    library: ClassVar[sourdough.Library] = sourdough.Library()
    """ Public Class Methods """
    @classmethod
    def create(cls, source: str, **kwargs) -> Component:
        """[summary]

        Args:
            source (str): [description]

        Raises:
            NotImplementedError: [description]

        Returns:
            Component: [description]
            
        """
        if (isinstance(source, str)
                or (isinstance(source, Sequence) and all(source, str))):
            return cls.from_name(name=source, **kwargs)
        elif isinstance(source, cls.bases.Stage):
            return

    @classmethod
    def from_outline(cls, name: str, outline: sourdough.project.Outline,
                     **kwargs) -> Component:
        """[summary]

        Args:
            name (str): [description]
            section (str): [description]
            outline (sourdough.project.Outline): [description]

        Returns:
            Component: [description]
        """
        if name in outline.initialization:
            parameters = outline.initialization[name]
            parameters.update(kwargs)
        else:
            parameters = kwargs
        component = cls.library.borrow(names=[name, outline.designs[name]])
        instance = component(name=name, **parameters)
        return instance

    """ Public Methods """

    def execute(self, data: Any, **kwargs) -> Any:
        """[summary]

        Args:
            data (Any): [description]

        Returns:
            Any: [description]
            
        """
        if self.iterations in ['infinite']:
            while True:
                data = self.implement(data=data, **kwargs)
        else:
            for iteration in range(self.iterations):
                data = self.implement(data=data, **kwargs)
        return data

    def implement(self, data: Any, **kwargs) -> Any:
        """[summary]

        Args:
            data (Any): [description]

        Returns:
            Any: [description]
            
        """
        if self.parameters:
            parameters = self.parameters
            parameters.update(kwargs)
        else:
            parameters = kwargs
        if self.contents not in [None, 'None', 'none']:
            data = self.contents.implement(data=data, **parameters)
        return data

    """ Private Methods """

    def _add_attributes(self, attributes: Dict[Any, Any]) -> None:
        """[summary]

        Args:
            attributes (Dict[Any, Any]): [description]

        Returns:
            [type]: [description]
            
        """
        for key, value in attributes.items():
            setattr(self, key, value)
        return self
Exemple #8
0
class Manager(sourdough.quirks.Base, sourdough.quirks.Element):
    """

    Args:
        name (str): designates the name of a class instance that is used for 
            internal referencing throughout sourdough. For example, if a 
            sourdough instance needs settings from a Configuration instance, 
            'name' should match the appropriate section name in a Configuration 
            instance. Defaults to None. 
        library (ClassVar[Library]): related Library instance that will store
            subclasses and allow runtime construction and instancing of those
            stored subclasses.
                
    """
    name: str = None
    contents: Any = None
    workflow: sourdough.Structure = None
    needs: ClassVar[Union[Sequence[str], str]] = ['outline', 'name']
    library: ClassVar[sourdough.Library] = sourdough.Library()
    """ Public Class Methods """
    @classmethod
    def create(cls, **kwargs) -> object:
        """[summary]

        Raises:
            ValueError: [description]

        Returns:
            object: [description]
            
        """
        needs = list(more_itertools.always_iterable(cls.needs))
        method = getattr(cls, f'from_{needs[0]}')
        for need in needs:
            if need not in kwargs:
                raise ValueError(
                    f'The create method must include a {need} argument')
        return method(**kwargs)

    @classmethod
    def from_outline(cls, outline: Stage, name: str, **kwargs) -> Manager:
        """[summary]

        Args:
            outline (Stage): [description]
            name (str): [description]

        Returns:
            Manager: [description]
            
        """
        names = [name]
        if name in outline.designs:
            names.append(outline.design[name])
        manager = cls.library.borrow(names=names)
        structure = cls.bases.stage.library.borrow(names='workflow')
        workflow = structure.create(outline=outline, name=name)
        return manager(name=name, workflow=workflow, **kwargs)

    """ Public Methods """

    def execute(self, data: Any) -> Any:
        """[summary]

        Args:
            data (Any): [description]

        Returns:
            Any: [description]
        """
        for node in self.workflow:
            data = node.execute(data=data)
        return data
Exemple #9
0
class Workflow(base.Stage, sourdough.Graph):
    """Stores lightweight workflow and corresponding components.
    
    Args:
        contents (Dict[str, List[str]]): an adjacency list where the keys are 
            the names of nodes and the values are names of nodes which the key 
            is connected to. Defaults to an empty dict.
        default (Any): default value to use when a key is missing and a new
            one is automatically corrected. Defaults to an empty list.
        components (sourdough.Library): stores Component instances that 
            correspond to nodes in 'contents'. Defaults to an empty Library.
        needs (ClassVar[Union[Sequence[str], str]]): attributes needed from 
            another instance for some method within a subclass. Defaults to 
            a list with 'outline' and 'name'
                   
    """
    contents: Dict[str, List[str]] = dataclasses.field(default_factory=dict)
    default: Any = dataclasses.field(default_factory=list)
    components: sourdough.Library = sourdough.Library()
    needs: ClassVar[Union[Sequence[str], str]] = ['outline', 'name']
    """ Public Class Methods """
    @classmethod
    def from_outline(cls, outline: Outline, name: str) -> Workflow:
        """Creates a Workflow from an Outline.

        Args:
            outline (Outline): [description]
            name (str): [description]

        Returns:
            Workflow: [description]
            
        """
        workflow = cls()
        workflow = cls._add_component(name=name,
                                      outline=outline,
                                      workflow=workflow)
        for component in outline.components[name]:
            workflow = cls._add_component(name=component,
                                          outline=outline,
                                          workflow=workflow)
        return workflow

    """ Public Methods """

    def combine(self, workflow: Workflow) -> None:
        """Adds 'other' Workflow to this Workflow.
        
        Combining creates an edge between every endpoint of this instance's
        Workflow and the every root of 'workflow'.
        
        Args:
            workflow (Workflow): a second Workflow to combine with this one.
            
        Raises:
            ValueError: if 'workflow' has nodes that are also in 'flow'.
            
        """
        if any(k in workflow.components.keys()
               for k in self.components.keys()):
            raise ValueError('Cannot combine Workflows with the same nodes')
        else:
            self.components.update(workflow.components)
        super().combine(structure=workflow)
        return self

    def execute(self,
                data: Any,
                copy_components: bool = True,
                **kwargs) -> Any:
        """Iterates over 'contents', using 'components'.
        
        Args:
            
        Returns:
            
        """
        for path in iter(self):
            data = self.execute_path(data=data,
                                     path=path,
                                     copy_components=copy_components,
                                     **kwargs)
        return data

    def execute_path(self,
                     data: Any,
                     path: Sequence[str],
                     copy_components: bool = True,
                     **kwargs) -> Any:
        """Iterates over 'contents', using 'components'.
        
        Args:
            
        Returns:
            
        """
        for node in more_itertools.always_iterable(path):
            if copy_components:
                component = copy.deepcopy(self.components[node])
            else:
                component = self.components[node]
            data = component.execute(data=data, **kwargs)
        return data

    """ Private Class Methods """

    @classmethod
    def _add_component(cls, name: str, outline: Outline,
                       workflow: Workflow) -> Workflow:
        """[summary]

        Args:
            name (str): [description]
            details (Details): [description]
            workflow (Workflow): [description]

        Returns:
            Workflow: [description]
            
        """
        workflow.append(node=name)
        design = outline.designs[name]
        component = cls.bases.component.library.borrow(names=[name, design])
        instance = component.from_outline(name=name, outline=outline)
        workflow.components[name] = instance
        return workflow