Development of python action

Hello! Does anyone have recommendations for steps on writing a python action? Is there a good way to set up a development environment? Is there a way to test the action outside of st2?

You can write your Python script like any other script, and then graft on StackStorm support. Similar to this action from our end-to-end tests (the only interesting parts of that for you will be the top of that file and the bottom of that file, the details of the test case aren’t relevant).

First, write your script normally:

import ...

args = {}
# parse arguments from the environment here
args[...] = os.environ.get('...')

# parse arguments from the command line here with argparse, click, etc.
args = ...

# your script logic goes here

Then wrap up the logic into its own function and call that function directly, like so:

import ...

def do_things():
    # your script logic goes here


# If this script is being run directly, parse the command line arguments
# and then pass them to the do_things function
if __name__ == '__main__':
    args = {}
    # parse arguments from the environment here
    args[...] = os.environ.get('...')

    # Parse script arguments here with argparse, click, etc.
    args = ...
    do_things(...)

Then you can graft StackStorm support onto it by adding a subclass of st2common.actions.Action, defining a run function on it, and having the run function call the do_things function itself:

import ...

def do_things():
    # your script logic goes here


try:
    # If this script is being run from a StackStorm python-script runner,
    # get the arguments from StackStorm and pass them to the do_things
    # function
    from st2common.runners.base_action import Action

    class DoThingsAction(Action):
        def run(self, *args, **kwargs):
            return do_things(*args, **kwargs)

except ImportError:
    pass


# If this script is being run directly, parse the command line arguments
# and then pass them to the do_things function
if __name__ == '__main__':
    args = {}
    # parse arguments from the environment here
    args[...] = os.environ.get('...')

    # Parse script arguments here with argparse, click, etc.
    args = ...
    do_things(...)

Hope this helps. Please feel free to ask further questions that may arise.

Oh, and you will also still need an action metadata file, just like every other StackStorm action, setting the runner_type to python-script and defining the action parameters like normal.

Here is the metadata file for the script I linked to in the previous post:

Thanks!! Is it more proper / useful to write the action as a subclass of BaseAction? Any tips on development with that paradigm?

Yes, you absolutely can do that, but it slows down development since then (unless you add some hacks to the code) you can only really run the code through StackStorm. So your REPL latency is higher.

I developed the script I linked to just running in a local virtualenv on my laptop, and then grafted the StackStorm integration in later, which let me debug the script first, and then once I knew the script was working as intended I knew that whatever bugs remained were in the StackStorm integration section. So it can be helpful to organize the code that way to make it easier to divide and conquer when troubleshooting.

Once your script is written out and pretty well debugged, and you have the StackStorm integration setup and working, then yes, you can certainly simply move the meat of your script into the run function:

import ...

from st2common.runners.base_action import Action


class MyAction(Action):
    def run(self, *args, **kwargs):
        # your script logic goes here
        return ...

Personally, I didn’t find it beneficial enough to do that, especially since I do still run that script outside of StackStorm. But I could certainly see it being beneficial if the Python script is small and simple enough.

I highly recommend adding tests for your action. There’s an entire section in our documentation on testing packs.

I did not add tests for the action I linked to because the action itself was a test for some of our other code. Otherwise I definitely would have done so.