I recently started working in Python (v. 3) and had to develop some scripts to perform various tasks that weren’t part of the main, long-running application.

tl;dr: Create a Class, instantiate and run it with an if-name

Coming from the Ruby world, and having worked a lot with similar sorts of tasks, I decided to follow my usual pattern of creating a class, which gets instantiated with the specific environment and runtime parameters, and then run using some performative verb method, such as run() or perform().

One of the tasks was to implement a “smoke test” of the application, to ensure all the endpoints were answering correctly. (This was at a time when some of the endpoints weren’t entirely finished and were returning 500 server errors; we wanted to make sure we weren’t regressing from that point when we deployed a new version to staging.)

Declaring a “Runner” Class

Declaring a class in Python is simple enough, especially if it’s not inheriting anything:

class MyTask:

    def __init__(self, param1, param2, ...):
        self.attribute1 = param1
        self.attribute2 = param2
        # and so on

    def run:
        # do the things

This gave me the basis for injecting all the various bits and pieces of the runtime environment for the task, using the “if name” idiom.

If-Name Idiom

Python has the idiom of checking whether the currently running script was called directly by comparing the content of the __name__ variable to the string "__main__". Putting this after the class, the body of the if part can then gather up the environment runtime information it needs, instantiate the task class, and receive the outcomes from the class.

if __name__ == '__main__':
    result = MyTask(ENVAR1, ENVAR2).run()
    print(result)

This script took the outcomes and created a nice-looking report of the results. If the task class was called by some other object, that consumer could do other operations if it wanted.

Re-thinking this now, I think it might be nicer to have the reporting part as a method on the object, enabling the use of decorators for doing other things with the results, they could easily do that and still have the formatted report. So let’s do that in this example.

DEFAULT1 = "hello"
DEFAULT2 = "world"
REPORT_FORMAT = """

Results of MyTask:
==================

Attribute 1: {attr1}
Attribute 2: {attr2}

------------------
Thanks for coming!

"""


class MyTask:
    def __init__(self,
                 param1=DEFAULT1,
                 param2=DEFAULT2,
                 report_format=REPORT_FORMAT):
        self.attr1 = param1
        self.attr2 = param2
        self.report_format = report_format

    def run(self):
        # Do some stuff

        results = [self.attr1, self.attr2]
        return results

    def __str__(self):
        return self.report_format.format(
            attr1=self.attr1,
            attr2=self.attr2
        )

    def report(self):
        print(str(self))


if __name__ == '__main__':
    runner = MyTask(param2="Monkey")
    print(runner.run())
    runner.report()

The output of running the script would be:

['hello', 'Monkey']


Results of MyTask:
==================

Attribute 1: hello
Attribute 2: Monkey

------------------
Thanks for coming!

Further Considerations

I think this applies to other patterns as well, such at gateway / proxy objects that might be long-lived in an application, specific service clients, background jobs to free up resources in the main application, ETL tasks that chain off each other, and many other one-off types of mini applications. I’ve not yet written command line utilities, but I think these would also benefit from this.