diff --git a/docs/change_log.md b/docs/change_log.md index 1d7b1a59e..896ff256b 100644 --- a/docs/change_log.md +++ b/docs/change_log.md @@ -65,6 +65,7 @@ There is one significant exception to the above rules -- `MAJOR`=0 releases. Any **Enhancements** - added `dotdict` utility for easy dot-access with nested dictionary objects - all settings can be added via environment variables for cloud-based deployments +- add `@workflow` decorator for easily creating basic workflows **Refactors** - Fully reimplemented how all settings are loaded diff --git a/src/simmate/engine/__init__.py b/src/simmate/engine/__init__.py index 65da759d4..08c050c23 100644 --- a/src/simmate/engine/__init__.py +++ b/src/simmate/engine/__init__.py @@ -23,7 +23,7 @@ from simmate.database import connect -from .workflow import Workflow +from .workflow import Workflow, workflow from .s3_workflow import S3Workflow from .web_api_workflow import WebApiWorkflow from .execution import SimmateWorker as Worker diff --git a/src/simmate/engine/workflow.py b/src/simmate/engine/workflow.py index be3bb2b8b..ab05ddcff 100644 --- a/src/simmate/engine/workflow.py +++ b/src/simmate/engine/workflow.py @@ -5,6 +5,7 @@ import logging import re import uuid +from functools import wraps from pathlib import Path import cloudpickle @@ -1239,3 +1240,54 @@ def _deserialize_parameters( ) return parameters_cleaned + + +def workflow( + original_function: callable = None, + app_name: str = "Basic", + type_name: str = "Toolkit", + use_database: bool = False, +): + """ + A decorator that converts a function into a workflow class. + + ## Example use: + + ``` python + @workflow + def example(name, **kwargs): + print(f"Hello {name}!") + return 12345 + ``` + """ + + def decorator(original_function): + @wraps(original_function) + def wrapper(*args, **kwargs): + # Preset name example: 'example_fxn' --> 'ExampleFxn' + # code copied from https://stackoverflow.com/questions/4303492/ + preset_name = "".join( + x.capitalize() or "_" for x in original_function.__name__.split("_") + ) + + # dynamically create the new Workflow subclass. + NewClass = type( + f"{app_name}__{type_name}__{preset_name}", + tuple([Workflow]), + { + "run_config": staticmethod(original_function), + "use_database": use_database, + }, + ) + + return NewClass + + # BUG-FIX: normally we don't call the wrapper here, but I do this + # to make sure the function immediately returns our new class. + return wrapper() + + # Check if the decorator is used with or without arguments + if original_function is None: + return decorator + else: + return decorator(original_function)