Webinar

November 3rd, 2021 12:00 PM EDT
Automation for Field Services & DistributionNovember 3rd, 2021 12:00 PM EDT
Learn how creating a digital workforce can improve your supply chain processes!

Best practices in creating Python robots

TL;DR

Do:

  • Always keep readability in mind.
  • Keep functions short and sweet.
  • Use classes and modules as namespaces to separate concerns.

Don't:

  • Create heavily nested loops.
  • Write functions that do many things.

What is pythonic code?

When googling around and reading about Python, you very quickly see the term pythonic. It means code that follows the established conventions of the language. Pythonic Python code is clean, uses Python idioms, and follows Python style guidelines.

With Robot Framework, you can write task suites using almost natural language (e.g., English). Python's syntax is very close to written English too, and that's why it's so easy to start writing code in it.

Embracing best practices is equally important when writing pure Python robots, as it is when writing Python keyword libraries for your Robot Framework robot.

See also the article on naming best practices.

Why?

You might ask: What does all of this matter? Isn't it enough that the code works?

Short answer: Not really.

Nitpicking about idioms and code style might seem like unnecessary work, but really, it's not! When following established conventions, your code will be:

  • Readable (by other Python programmers).
  • Maintainable (by you or by your colleagues).
  • Testable.
  • Robust.

Python style and idioms

Python has well-established ways of writing code. Obligatory reading for every Python programmer is the Python Style Guide (PEP8).

When writing PEP8-compliant code, you know your code will look familiar and readable for other Python programmers too.

Useful tools to help with Python style are (among others):

You can find these as editor plugins for your favorite code editor too.

Robot Framework specifics

Something to note when using Robot Framework: when creating a Python keyword library, the module name must correspond to the class name (in CamelCase). This goes against PEP8 but is a Robot Framework convention.

Here's an example module DoNothingLibrary.py:

class DoNothingLibrary:

  def do_nothing_keyword(self):
      pass

Idioms

When coming from another language, e.g., Java, it might be tempting to start writing Python code that looks like Java. Instead, it's good practice to start learning pythonic idioms from the beginning, like list comprehensions:

So for example, instead of writing:

values = [1, 2, 3, 4, 5, 6]
multiplied_by_two = []

for i in range(len(values)):
    multiplied_by_two.append(values[i] * 2)

You would write:

values = [1, 2, 3, 4, 5, 6]
multiplied_by_two = [value * 2 for value in values]

Read more about Python code style.

Code structuring

The basic unit of code in Python is a function. Functions can be grouped together in a module. You create your application by calling these functions.

There are also many reasons to put your functions inside a class (inside a module).

For example, in Robot Framework, the convention is to put the Python keywords inside a class, which is a natural way to tightly group functionality.

Example:

Module AccountingSystem.py:

class AccountingSystem:

    def login(self):
        self.some_actions()

Module EmailComposer.py:

class EmailConnector:

    def get_new_messages(self):
        self.some_actions()

Module tasks.py:

from AccountingSystem import AccountingSystem
from EmailConnector import EmailConnector

accounting = AccountingSystem()
email = EmailConnector()


def main():
    email.get_new_messages()
    accounting.login()

Unit testing

When creating Python robots or Python libraries, it's a very good practice to write unit tests for your code.

Writing a unit test is not difficult per se. Writing good unit tests requires some thought and, most essentially, a testable codebase. Writing unit tests helps you write better code.

Try to avoid any shared state between objects and global variables. Ideal code (at least from the perspective of testing) is purely functional code. A pure function is deterministic and has no side effects.

An example of a not very testable function:

number = 5

def multiply():
    multiplier = function_representing_some_outside_system()
    return number * multiplier

def function_representing_some_outside_system():
    return random.randint(1, 10)

The function multiply does not return the same value every time (because of the use of randint). It also uses a variable value outside its scope. Of course, a shared state is sometimes needed, but then you should use classes and still be very careful with handling the state.

To make this testable, you could refactor the code like this:

def multiply(number, multiplier):
    return number * multiplier

def function_representing_some_outside_system():
    return random.randint(1, 10)

def main():
    number = parse_value_from_command_line_argument()
    multiplier = function_representing_some_outside_system()
    result = multiply(number, multiplier)

To test this code, you would then write:

def test_multiply():
    test_number = 2
    test_multiplier = 5
    result = multiply(test_number, test_multiplier)

    assert result == 10

Now our code is pulled out into a deterministic function with no side effects. Testing this trivial function is, well, trivial. Testing function_representing_some_outside_system is not unit testing at all, as it represents an external system (e.g., a system under RPA).

The testing framework that we recommend is Pytest.

Exception handling

If in your code there is a possibility for failure, you should prepare for it. Use try-except blocks.

The most simple case in the context of RPA follows this pattern:

def main():
    try:
        automation_steps()
    except Exception as err:
        action_on_fail(err)
    finally:
        teardown()

At its simplest, action_on_fail might be "do nothing".

Logging

When using Python with Robot Framework, you can use Robot Framework's logger:

from robot.api import logger

class SomeLibrary:

    def some_keyword(self):
        logger.info("And now for something completely different.")

These will be printed in the Robot Framework log file.

When writing pure Python robots, you will have to take care of logging yourself.

Here is an example of how to configure your logger:

import logging
import sys

stdout = logging.StreamHandler(sys.stdout)

logging.basicConfig(
    level=logging.INFO,
    format="[{%(filename)s:%(lineno)d} %(levelname)s - %(message)s",
    handlers=[stdout],
)

LOGGER = logging.getLogger(__name__)

Summary

Following best practices makes your Python-based robots more readable, maintainable, testable, and robust. Once you have learned the best practices, you can apply them in all your future projects, even outside the automation domain. A worthy investment indeed!

January 20, 2021