Skip to content

Custom Decorators

If you've made it this far, you're likely considering writing a custom decorator to apply to a workflow. This may be an custom cache_manager or maybe an entirely new integration all together. Congratulations and we hope you'll enjoy the experience. Let's dig in.

Why Use Decorators?

Python decorators wrap normal python functions or methods and modify their behavior. For example, almost all built-in components in lolpop are wrapped in lolpop.utils.common_utils.log_execution. This is a wrapper function that logs the start and end of a method to a lolpop Logger class, as well as the runtime.

Decorators are powerful, as they can modify the existing functions behavior. For example, we leverage cache_managers to skip running methods all together if the method input hasn't changed as its output has already been saved to the cache. You might also opt to integrate with an external orchestration tool via decorators in lolpop.

Be careful when using decorators, they can have unintended consequences and you should only travel down this path if you're sure of what you are doing.

How to Write a Custom Decorator

Writing custom decorators in lolpop is relatively easy and straightforward. We'll assume you already know how to write a normal python decorator. What you'll then need to do is simply write a custom lolpop componentwhere one of your class methods should be a decorator.

In your custom component, you'll also want to include the default configuration decorator_method and the value should be the name of the class method that is the decorator you wish to use.

How to Use a Custom Decorator

To use a custom decorator, you'll add a new section to your workflow yaml: decorators. This section you'll then fill out as usual, with the abstract integration name and the class you wish to implement, as shown below:

pipeline: 
  process: OfflineProcess 
  train: OfflineTrain
  predict: OfflinePredict
component: 
  metadata_tracker: MLFlowMetadataTracker
  notifier: StdOutNotifier
  resource_version_control: dvcVersionControl
  metrics_tracker: MLFlowMetricsTracker
decorator: 
  cache_manager: LocalCacheManager
... 

cache_manager: 
    config: 
        decorator_method: cache_decorator
        integration_types: ["component"]
...

Note, you can include configuration for your decorator/component as normal via <integration_name>.config in the yaml file. This will be loaded into the decorator and will be available during the runtime of the decorator_method.

And that's it! lolpop internally will process all decorators and will apply them to all relevant methods. By default, lolpop applies your decorator to all component methods. You can modify this default behavior via the integration_types and integration_classes configuration in your decorator config. integration_types takes a list of integration types to apply to: "component" and "pipeline". If you wish to apply a decorator to a runner class, you should do that manually in your own runner class. integration_classes is an explicit list of class names to apply the decorator to. You can otherwise control how the decorator is applied via including that logic in the decorator itself.