Raspberry Pi Safe Clean Up - the decorator way Yesterday, I've come up with the idea of using python context manager to ensure cleanup available here. This time I am using that context manager to make a decorator.



The context manager



# SafeGPIO.py
# updated, warning silenced

from RPi import GPIO
from exceptions import RuntimeWarning
import warnings

class SafeGPIO(object):

    def __enter__(self):
        return GPIO

    def __exit__(self, *args, **kwargs):
        with warnings.catch_warnings():
            warnings.simplefilter("error") #turn warning into exceptions
            try:
                GPIO.cleanup()
            except RuntimeWarning:
                pass # silence it




The decorator



#decorators.py

from . import SafeGPIO
from RPi import GPIO
from functools import wraps

def safe_gpio(func):
    """
    This decorator ensure GPIO.cleanup() is called when function call ends, 
    also it injects GPIO as first argument into your function
    """
    @wraps(func) # using wraps preservses doc string
    def wrapper(*args, **kwargs):
        with SafeGPIO() as GPIO:
            return func(GPIO, *args, **kwargs)
    return wrapper

def gpio(func):
    """
    This decorator injects GPIO as first argument into your function
    """
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(GPIO, *args, **kwargs)
    return wrapper


Use like this:

# decorator_test.py

from SafeGPIO.decorators import safe_gpio, gpio
from time import sleep
from random import choice, randint

GPIO_PINS = (3,5,7,8,10,11,12,13,15,16,18,19,21,22,23,24,26)
VALUES = (True, False)

@safe_gpio
def do_random_things_with_pins_for_ten_times(GPIO):
    GPIO.setmode(GPIO.BOARD)
    for pin in GPIO_PINS:
        GPIO.setup(pin, GPIO.OUT)
    for _ in xrange(10):
        pin = choice(GPIO_PINS)      # choose one of the GPIO pin
        value = choice(VALUES)       # output either true or false
        sleep_seconds = randint(1,3) # sleep from 1 to 3 seconds
        print "slected pin %d, output %r, sleep for %d seconds" %
                (pin, value, sleep_seconds)
        GPIO.output(pin, value)
        sleep(sleep_seconds)

@safe_gpio
def do_real_work(GPIO):
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(7, GPIO.OUT)
    print "doing real work for 5 seconds"
    GPIO.output(7, True)
    sleep(5)

@safe_gpio # guarantee to clean up on exit
def main(GPIO):
    do_random_things_with_pins_for_ten_times()
    do_real_work()

if __name__ == '__main__':
    main()
If you want to do gpio in one line, you can use functools.partial and either functools.wraps or functools.update_wrapper.

As you are using wraps already, you may as well use partial too.

def gpio(func):
    return update_wrapper(partial(func, GPIO), func)


Now it's a simple one-liner.
If you don't need @wraps, this can be further simplified to:

def gpio(func):
    return partial(func, GPIO)




Ways I would change the program:


Adding an option to SafeGPIO to have the option to handle errors.
Merging gpio and safe_gpio into one function would improve my user experience.
Less functions to remember, whilst keeping the option for it to be safe.
You don't use @gpio, so would you ever use it?

If your response is no, you should consider removing it.
As you could just import GPIO.
And it seems like a lot of overhead for importing GPIO.