diff --git a/queenbee_dsl/__init__.py b/queenbee_dsl/__init__.py index e69de29..63c968d 100644 --- a/queenbee_dsl/__init__.py +++ b/queenbee_dsl/__init__.py @@ -0,0 +1 @@ +"""Queenbee Python DSL.""" diff --git a/queenbee_dsl/common.py b/queenbee_dsl/common.py index fde9b2f..c6f0cee 100644 --- a/queenbee_dsl/common.py +++ b/queenbee_dsl/common.py @@ -1,4 +1,80 @@ +from dataclasses import dataclass +from typing import NamedTuple +import importlib +from collections import namedtuple + + def camel_to_snake(name: str) -> str: """Change name from CamelCase to snake-case.""" return name[0].lower() + \ ''.join(['-' + x.lower() if x.isupper() else x for x in name][1:]) + + +@dataclass +class _BaseClass: + """Base class for Queenbee DSL Function and DAG. + + Do not use this class directly. + """ + _cached_queenbee = None + _cached_outputs = None + _cached_package = None + _cached_inputs = None + + @property + def queenbee(self): + raise NotImplementedError + + @property + def _outputs(self) -> NamedTuple: + raise NotImplementedError + + @property + def _inputs(self) -> NamedTuple: + """Return inputs as a simple object with dot notation. + + Use this property to access the inputs when creating a DAG. + + The name starts with a _ not to conflict with a possible member of the class + with the name inputs. + """ + if self._cached_inputs: + return self._cached_inputs + cls_name = camel_to_snake(self.__class__.__name__) + mapper = { + inp.name.replace('-', '_'): { + 'name': inp.name.replace('-', '_'), + 'parent': cls_name, + 'value': inp + } for inp in self.queenbee.inputs + } + + inputs = namedtuple('Inputs', list(mapper.keys())) + self._cached_inputs = inputs(*list(mapper.values())) + + return self._cached_inputs + + @property + def _package(self) -> dict: + """Queenbee package information. + + This information will only be available if the function is part of a Python + package. + """ + if self._cached_package: + return self._cached_package + + module = importlib.import_module(self._python_package) + assert hasattr(module, '__queenbee__'), \ + 'Failed to find __queenbee__ info in __init__.py' + self._cached_package = getattr(module, '__queenbee__') + return self._cached_package + + @property + def _python_package(self) -> str: + """Python package information for this function. + + This information will only be available if the function is part of a Python + package. + """ + return self.__module__.split('.')[0] diff --git a/queenbee_dsl/dag/_base.py b/queenbee_dsl/dag/_base.py index e5f42ed..271f7db 100644 --- a/queenbee_dsl/dag/_base.py +++ b/queenbee_dsl/dag/_base.py @@ -2,16 +2,27 @@ from typing import NamedTuple import inspect from collections import namedtuple -import importlib from queenbee.recipe.dag import DAG as QBDAG -from ..common import camel_to_snake +from ..common import _BaseClass @dataclass -class DAG: - """Baseclass for DSL DAG classes.""" +class DAG(_BaseClass): + """Baseclass for DSL DAG classes. + + Every Queenbee DAG must subclass from this class. + + Attributes: + queenbee + _dependencies + _inputs + _outputs + _package + _python_package + + """ __decorator__ = 'dag' _cached_queenbee = None _cached_inputs = None @@ -20,7 +31,7 @@ class DAG: @property def queenbee(self) -> QBDAG: - """Convert this class to a Queenbee class.""" + """Convert this class to a Queenbee DAG.""" # cache the DAG since it always stays the same for each instance if self._cached_queenbee: return self._cached_queenbee @@ -65,32 +76,6 @@ def queenbee(self) -> QBDAG: ) return self._cached_queenbee - @property - def _inputs(self) -> NamedTuple: - """Return function inputs as a simple object with dot notation. - - Use this property to access the inputs when creating a DAG. - - The name starts with a _ not to conflict with a possible member of the class - with the name inputs. - """ - if self._cached_inputs: - return self._cached_inputs - - cls_name = camel_to_snake(self.__class__.__name__) - mapper = { - inp.name.replace('-', '_'): { - 'name': inp.name.replace('-', '_'), - 'parent': cls_name, - 'value': inp - } for inp in self.queenbee.inputs - } - - inputs = namedtuple('Inputs', list(mapper.keys())) - self._cached_inputs = inputs(*list(mapper.values())) - - return self._cached_inputs - @property def _outputs(self) -> NamedTuple: """Return dag outputs as a simple object with dot notation. @@ -109,31 +94,6 @@ def _outputs(self) -> NamedTuple: return self._cached_outputs - @property - def _package(self) -> dict: - """Queenbee package information. - - This information will only be available if the dag is part of a Python - package. - """ - if self._cached_package: - return self._cached_package - - module = importlib.import_module(self._python_package) - assert hasattr(module, '__queenbee__'), \ - f'Failed to find __queenbee__ info in {module.__name__}\'s __init__.py' - self._cached_package = getattr(module, '__queenbee__') - return self._cached_package - - @property - def _python_package(self) -> str: - """Python package information for this dag. - - This information will only be available if the dag is part of a Python - package. - """ - return self.__module__.split('.')[0] - @property def _dependencies(self): """DAG dependencies. diff --git a/queenbee_dsl/dag/_inputs.py b/queenbee_dsl/dag/_inputs.py index 0f25588..8006f43 100644 --- a/queenbee_dsl/dag/_inputs.py +++ b/queenbee_dsl/dag/_inputs.py @@ -55,6 +55,15 @@ def reference_type(self): class StringInput(_InputBase): + """ A DAG string input. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + spec: A JSONSchema specification to validate input values. + + """ annotations: Dict = None description: str = None default: str = None @@ -62,23 +71,67 @@ class StringInput(_InputBase): class IntegerInput(StringInput): + """ A DAG integer input. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + spec: A JSONSchema specification to validate input values. + + """ default: int = None class NumberInput(StringInput): + """ A DAG number input. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + spec: A JSONSchema specification to validate input values. + + """ default: float = None class BooleanInput(StringInput): + """ A DAG boolean input. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + spec: A JSONSchema specification to validate input values. + + """ default: bool = None class DictInput(StringInput): + """ A DAG dictionary input. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + spec: A JSONSchema specification to validate input values. + + """ default: Dict = None class FolderInput(StringInput): + """ A DAG folder input. + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + spec: A JSONSchema specification to validate input values. + + """ @property def is_artifact(self): return True @@ -89,6 +142,16 @@ def reference_type(self): class FileInput(FolderInput): + """ A DAG file input. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + extensions: An optional list of valid extensions for input file. + spec: A JSONSchema specification to validate input values. + + """ extensions: List[str] = None @property @@ -97,6 +160,16 @@ def reference_type(self): class PathInput(FileInput): + """ A DAG path input. A path can be a file or a folder. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + extensions: An optional list of valid extensions for input file. + spec: A JSONSchema specification to validate input values. + + """ @property def reference_type(self): @@ -105,7 +178,7 @@ def reference_type(self): @dataclass class Inputs: - """DAG inputs.""" + """DAG inputs enumeration.""" str = StringInput int = IntegerInput float = NumberInput diff --git a/queenbee_dsl/dag/_outputs.py b/queenbee_dsl/dag/_outputs.py index 0e43c5d..9e6dc8b 100644 --- a/queenbee_dsl/dag/_outputs.py +++ b/queenbee_dsl/dag/_outputs.py @@ -66,6 +66,16 @@ def reference_type(self): class StringOutput(_OutputBase): + """ A DAG string output. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + source: Source for this output. A source is usually from one of the template + outputs but it can also be declared as a relative path. + + """ + source: Any # this field will be translated to from_ annotations: Dict = None description: str = None @@ -81,10 +91,28 @@ def change_self_to_inputs(cls, v): class IntegerOutput(StringOutput): + """ A DAG integer output. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + source: Source for this output. A source is usually from one of the template + outputs but it can also be declared as a relative path. + + """ ... class NumberOutput(StringOutput): + """ A DAG number output. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + source: Source for this output. A source is usually from one of the template + outputs but it can also be declared as a relative path. + + """ ... @@ -93,10 +121,28 @@ class BooleanOutput(StringOutput): class DictOutput(StringOutput): + """ A DAG dictionary output. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + source: Source for this output. A source is usually from one of the template + outputs but it can also be declared as a relative path. + + """ ... class FolderOutput(StringOutput): + """ A DAG folder output. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + source: Source for this output. A source is usually from one of the template + outputs but it can also be declared as a relative path. + + """ @property def is_artifact(self): @@ -108,6 +154,15 @@ def reference_type(self): class FileOutput(FolderOutput): + """ A DAG file output. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + source: Source for this output. A source is usually from one of the template + outputs but it can also be declared as a relative path. + + """ @property def reference_type(self): @@ -115,12 +170,21 @@ def reference_type(self): class PathOutput(FolderOutput): + """ A DAG path output. A path can be a file or a folder. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + source: Source for this output. A source is usually from one of the template + outputs but it can also be declared as a relative path. + + """ ... @dataclass class Outputs: - """DAG outputs.""" + """DAG outputs enumeration.""" str = StringOutput int = IntegerOutput float = NumberOutput diff --git a/queenbee_dsl/dag/_task.py b/queenbee_dsl/dag/_task.py index ab8709d..07d6154 100644 --- a/queenbee_dsl/dag/_task.py +++ b/queenbee_dsl/dag/_task.py @@ -10,16 +10,14 @@ from queenbee.base.parser import parse_double_quotes_vars from ._inputs import _InputBase as DAGInput - - -def camel_to_snake(name: str) -> str: - """Change name from CamelCase to snake-case.""" - return name[0].lower() + \ - ''.join(['-' + x.lower() if x.isupper() else x for x in name][1:]) +from ..common import camel_to_snake def _validate_task_args(func) -> None: - """Validate task arguments.""" + """Validate task arguments. + + This function ensures that the value for all the task arguments are provided. + """ func_args = inspect.getfullargspec(func) arg_names = func_args.args[1:] # first arg is self defaults = func_args.defaults # default values @@ -46,17 +44,18 @@ def _add_sub_path(arg: Dict, sub_paths: Dict) -> Dict: return arg -def _get_from(value, inputs_info): - """Return a Queenbee from value. +def _get_from(value, inputs_info) -> Dict: + """Return a Queenbee From value dictionary. The output will be an InputReference, TaskReference or a ValueReference or a ValueListReference. Args: - name: Reference object name. value: Reference object. inputs_info: DAG inputs info to get the information for DAGInput types. + Returns: + Dict - a Queenbee From value dictionary. """ if isinstance(value, DAGInput): variable = inputs_info[id(value)] @@ -75,12 +74,11 @@ def _get_from(value, inputs_info): def _get_task_arguments(func, inputs_info, sub_paths) -> List[TaskArguments]: - """Get task arguments as Queenbee task arguments.""" + """Get task input arguments in Python method as Queenbee task arguments.""" task_args = [] template = func.__task_template__ func_args = inspect.getfullargspec(func) - # print(func_args) - # print('-----------\n') + names = func_args.args[1:] # first arg is self if not names: # no arguments @@ -142,7 +140,7 @@ def _get_task_loop(value, inputs_info) -> DAGTaskLoop: def _get_task_returns(func) -> NamedTuple: - """Set task returns based on template outputs and returns.""" + """Get task returns based on template outputs and returns.""" template = func.__task_template__ pattern = r'[\'\"]from[\'\"]\s*:\s*.*\._outputs\.(\S*)\s*[,}]' parent = func.__name__.replace('_', '-') @@ -166,9 +164,29 @@ def _get_task_returns(func) -> NamedTuple: return outputs(*list(mapper.values())) -def task(template, needs=None, loop=None, sub_folder=None, sub_paths: Dict = None, - annotations=None): - """A decorator for task methods in a DAG.""" +def task(template, needs=None, loop=None, sub_folder: str = None, sub_paths: Dict = None, + annotations: Dict = None): + """Task decorator for methods in a DAG. + + @task(template=Template, ...) + + Args: + template: A queenbee-dsl Function or DAG. + needs: A list of methods that this should be executed before this task. + This task will only be executed after these tasks are executed + successfully. + loop: An iterator to loop over. The task will be executed for each item + in this iterator. + sub_folder: An optional sub_folder for execution of this task. If sub_folder + is provided all the outputs from this task will be copied under the + sub_folder. Sub_folder is usually used with loops and named based on + the loop item to keep the results of each iteration in a separate folder. + sub_paths: A dictionary to provide sub_path for input arguments. Sub_path is + useful when a task only needs a file or a sub folder from the provided + argument. + annotations: An optional dictionary to annotate a task. + + """ sub_paths = sub_paths or {} sub_paths = {key.replace('-', '_'): value for key, value in sub_paths.items()} diff --git a/queenbee_dsl/function/__init__.py b/queenbee_dsl/function/__init__.py index 69ab6b2..6d6a0f9 100644 --- a/queenbee_dsl/function/__init__.py +++ b/queenbee_dsl/function/__init__.py @@ -1,22 +1,4 @@ """All function related decorators and objects including inputs and outputs.""" from ._inputs import Inputs # expose for easy import from ._outputs import Outputs # expose for easy import -from ._base import Function -from queenbee.base.parser import parse_double_quotes_vars - - -def command(func): - """Class method decorator for commands.""" - - def _clean_command(command): - """A helper function to reformat python command to Queenbee command.""" - refs = parse_double_quotes_vars(command) - for ref in refs: - command = command.replace( - ref, ref.replace('self.', 'inputs.').replace('_', '-') - ) - return command - - func.__decorator__ = 'command' - func.parse_command = _clean_command - return func +from ._base import Function, command diff --git a/queenbee_dsl/function/_base.py b/queenbee_dsl/function/_base.py index cfa01c5..ef2d0ee 100644 --- a/queenbee_dsl/function/_base.py +++ b/queenbee_dsl/function/_base.py @@ -2,25 +2,33 @@ from typing import NamedTuple import inspect from collections import namedtuple -import importlib from queenbee.plugin.function import Function as QBFunction +from queenbee.base.parser import parse_double_quotes_vars -from ..common import camel_to_snake +from ..common import camel_to_snake, _BaseClass @dataclass -class Function: - """Baseclass for DSL Function classes.""" +class Function(_BaseClass): + """Baseclass for DSL Function classes. + + Every Queenbee DAG must subclass from this class. + + Attributes: + queenbee + _dependencies + _inputs + _outputs + _package + _python_package + + """ __decorator__ = 'function' - _cached_queenbee = None - _cached_outputs = None - _cached_package = None - _cached_inputs = None @property def queenbee(self) -> QBFunction: - """Convert this class to a Queenbee class.""" + """Convert this class to a Queenbee Function.""" # cache the Function since it always stays the same for each instance if self._cached_queenbee: return self._cached_queenbee @@ -55,31 +63,6 @@ def queenbee(self) -> QBFunction: return self._cached_queenbee - @property - def _inputs(self) -> NamedTuple: - """Return function inputs as a simple object with dot notation. - - Use this property to access the inputs when creating a DAG. - - The name starts with a _ not to conflict with a possible member of the class - with the name inputs. - """ - if self._cached_inputs: - return self._cached_inputs - cls_name = camel_to_snake(self.__class__.__name__) - mapper = { - inp.name.replace('-', '_'): { - 'name': inp.name.replace('-', '_'), - 'parent': cls_name, - 'value': inp - } for inp in self.queenbee.inputs - } - - inputs = namedtuple('Inputs', list(mapper.keys())) - self._cached_inputs = inputs(*list(mapper.values())) - - return self._cached_inputs - @property def _outputs(self) -> NamedTuple: """Return function outputs as a simple object with dot notation. @@ -103,27 +86,24 @@ def _outputs(self) -> NamedTuple: return self._cached_outputs - @property - def _package(self) -> dict: - """Queenbee package information. - This information will only be available if the function is part of a Python - package. - """ - if self._cached_package: - return self._cached_package +def command(func): + """Command decorator for a task. - module = importlib.import_module(self._python_package) - assert hasattr(module, '__queenbee__'), \ - 'Failed to find __queenbee__ info in __init__.py' - self._cached_package = getattr(module, '__queenbee__') - return self._cached_package + A method that is decorated by a command must return a string. Use ``{{}}`` to + template the command with command arguments (e.g. {{self.name}}). - @property - def _python_package(self) -> str: - """Python package information for this function. + """ - This information will only be available if the function is part of a Python - package. - """ - return self.__module__.split('.')[0] + def _clean_command(command: str) -> str: + """A helper function to reformat python command to Queenbee function commands.""" + refs = parse_double_quotes_vars(command) + for ref in refs: + command = command.replace( + ref, ref.replace('self.', 'inputs.').replace('_', '-') + ) + return command + + func.__decorator__ = 'command' + func.parse_command = _clean_command + return func diff --git a/queenbee_dsl/function/_inputs.py b/queenbee_dsl/function/_inputs.py index c01b86b..8c04ae1 100644 --- a/queenbee_dsl/function/_inputs.py +++ b/queenbee_dsl/function/_inputs.py @@ -50,6 +50,15 @@ def to_queenbee(self, name): class StringInput(_InputBase): + """ A Function string input. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + spec: A JSONSchema specification to validate input values. + + """ annotations: Dict = None description: str = None default: str = None @@ -57,36 +66,104 @@ class StringInput(_InputBase): class IntegerInput(StringInput): + """ A Function integer input. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + spec: A JSONSchema specification to validate input values. + + """ default: int = None class NumberInput(StringInput): + """ A Function number input. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + spec: A JSONSchema specification to validate input values. + + """ default: float = None class BooleanInput(StringInput): + """ A Function boolean input. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + spec: A JSONSchema specification to validate input values. + + """ default: bool = None class DictInput(StringInput): + """ A Function dictionary input. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + spec: A JSONSchema specification to validate input values. + + """ default: Dict = None class FolderInput(StringInput): + """ A Function folder input. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + path: Path to source folder. + spec: A JSONSchema specification to validate input values. + + """ path: str class FileInput(FolderInput): + """ A Function file input. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + extensions: An optional list of valid extensions for input file. + path: Path to source folder. + spec: A JSONSchema specification to validate input values. + + """ extensions: List[str] = None class PathInput(FileInput): + """ A Function file input. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + default: Default value. + extensions: An optional list of valid extensions for input file. + path: Path to source folder. + spec: A JSONSchema specification to validate input values. + + """ ... @dataclass class Inputs: - """Function inputs.""" + """Function inputs enumeration.""" str = StringInput int = IntegerInput float = NumberInput diff --git a/queenbee_dsl/function/_outputs.py b/queenbee_dsl/function/_outputs.py index 355af14..3a9c103 100644 --- a/queenbee_dsl/function/_outputs.py +++ b/queenbee_dsl/function/_outputs.py @@ -53,6 +53,14 @@ def reference_type(self): class StringOutput(_OutputBase): + """ A Function string output. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + path: Path to the source file for this output. + + """ path: str annotations: Dict = None description: str = None @@ -68,23 +76,62 @@ def change_self_to_inputs(cls, v): class IntegerOutput(StringOutput): + """ A Function integer output. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + path: Path to the source file for this output. + + """ ... class NumberOutput(StringOutput): + """ A Function number output. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + path: Path to the source file for this output. + + """ ... class BooleanOutput(StringOutput): + """ A Function boolean output. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + path: Path to the source file for this output. + + """ ... class DictOutput(StringOutput): + """ A Function dictionary output. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + path: Path to the source file for this output. + + """ ... class FolderOutput(StringOutput): + """ A Function folder output. + Args: + annotations: An optional annotation dictionary. + description: Input description. + path: Path to the source folder for this output. + + """ @property def is_artifact(self): return True @@ -95,12 +142,28 @@ def reference_type(self): class FileOutput(FolderOutput): + """ A Function file output. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + path: Path to the source file for this output. + + """ @property def reference_type(self): return 'TaskFileReference' class PathOutput(FolderOutput): + """ A Function path output. A path can be a file or a folder. + + Args: + annotations: An optional annotation dictionary. + description: Input description. + path: Path to the source file or folder for this output. + + """ @property def reference_type(self): return 'TaskPathReference' diff --git a/queenbee_dsl/package.py b/queenbee_dsl/package.py index f3c0c55..a3d337f 100644 --- a/queenbee_dsl/package.py +++ b/queenbee_dsl/package.py @@ -1,10 +1,11 @@ -from setuptools.command.develop import develop -from setuptools.command.install import install -from typing import Union, Dict import importlib import pkgutil import pathlib +from setuptools.command.develop import develop +from setuptools.command.install import install +from typing import Union + from queenbee.plugin.plugin import Plugin, PluginConfig, MetaData from queenbee.recipe.recipe import Recipe, BakedRecipe, Dependency, DependencyKind from queenbee.repository.package import PackageVersion @@ -15,7 +16,12 @@ def _init_repo() -> pathlib.Path: - """Initiate a local Queenbee repository.""" + """Initiate a local Queenbee repository. + + This function is used by package function to start a local Queenbee repository + if it doesn't exist. If the repository has already been created it will return + the path to the repository. + """ path = pathlib.Path.home()/'.queenbee'/'queenbee-dsl' path.mkdir(exist_ok=True) @@ -34,6 +40,13 @@ def _init_repo() -> pathlib.Path: class PackageQBInstall(install): + """A class to extend `pip install` run method. + + By adding this class to setup.py this package will be added to queenbee-dsl local + repository which makes it accessible to other queenbee packages as a dependency. + + See here for an example: https://github.com/pollination/honeybee-radiance-pollination/blob/0b5590f691427f256beb77b37bd43f545106eaf1/setup.py#L3-L14 + """ def run(self): install.run(self) @@ -42,6 +55,13 @@ def run(self): class PackageQBDevelop(develop): + """A class to extend `pip install -e` run method. + + By adding this class to setup.py this package will be added to queenbee-dsl local + repository which makes it accessible to other queenbee packages as a dependency. + + See here for an example: https://github.com/pollination/honeybee-radiance-pollination/blob/0b5590f691427f256beb77b37bd43f545106eaf1/setup.py#L3-L14 + """ def run(self): develop.run(self) @@ -49,16 +69,20 @@ def run(self): package(self.__queenbee_name__) -def _load_plugin(package_name: str, qb_info: Dict, module) -> Plugin: +def _load_plugin(module) -> Plugin: """Load Queenbee plugin from Python package. + Usually you should not be using this function directly. Use ``load`` function + instead. + args: - package_name: Plugin Python package name. The package must be installed - in the environment that this command being executed. + module: Python module object for a Queenbee Plugin module. returns: Plugin - A Queenbee plugin """ + qb_info = module.__queenbee__ + package_name = module.__name__ # get metadata config = PluginConfig.parse_obj(qb_info['config']) meta_data = dict(qb_info) @@ -82,8 +106,22 @@ def _load_plugin(package_name: str, qb_info: Dict, module) -> Plugin: return plugin -def _load_recipe(package_name: str, qb_info: Dict, baked: bool = False): +def _load_recipe(module, baked: bool = False)-> Union[BakedRecipe, Recipe]: # load entry-point DAG + """Load Queenbee plugin from Python package. + + Usually you should not be using this function directly. Use ``load`` function + instead. + + args: + module: Python module object for a Queenbee Recipe. + + returns: + Recipe - A Queenbee recipe. It will be a baked recipe if baked is set to True. + """ + qb_info = module.__queenbee__ + package_name = module.__name__ + main_dag = qb_info.get('entry_point', None)() assert main_dag, f'{package_name} __queenbee__ info is missing the enetry_point key.' @@ -126,7 +164,11 @@ def _load_recipe(package_name: str, qb_info: Dict, baked: bool = False): def load(package_name: str, baked: bool = False) -> Union[Plugin, BakedRecipe, Recipe]: - """Load Queenbee Plugin or Recipe from Python package.""" + """Load Queenbee Plugin or Recipe from Python package. + + package_name: Python package name (e.g. honeybee-radiance-pollination) + baked: A boolean value to indicate wether to return a Recipe or a BakedRecipe. + """ package_name = package_name.replace('-', '_') try: module = importlib.import_module(package_name) @@ -141,15 +183,19 @@ def load(package_name: str, baked: bool = False) -> Union[Plugin, BakedRecipe, R qb_info = getattr(module, '__queenbee__') if 'config' in qb_info: # it's a plugin - # get metadata - return _load_plugin(package_name, qb_info, module) + return _load_plugin(module) else: # it's a recipe - return _load_recipe(package_name, qb_info, baked) + return _load_recipe(module, baked) + +def package(package_name: str, readme: str = None) -> None: + """Package a plugin or a recipe and add it to Queenbee local repository. -def package(package_name, readme: str = None): - """Package a plugin or a recipe and add it to Queenbee local repository.""" + Args: + package_name: Python package name (e.g. honeybee-radiance-pollination) + readme: Readme contents as a string. + """ # init a Queenbee package repository_path = _init_repo() index_path = repository_path/'index.json'