Making your OS X not sleep while running scripts

We have a lot of script at Wikia. About 6k lines of code of Python. Most of them are usually run in loops like this:

for app_directory in app_directories:
    script()

This can take a lot of time, especially that some of the scripts use Selenium and we have well over a hundred apps.

I often run those script loops on meetings, or during lunch break or if it's a big one, I leave it running when I finish my job and hope nothing will crash over night.

There was a common problem with this. Often I forgot to trigger caffeinate or any other setting that would prevent my Mac from going to sleep and I didn't want to turn off the sleeping completely because my battery would drain.

I thought that OS X provided the interface for Power Assertions. I thought I could use something like this in Python. Fortunately there's pyobj and I could run Objective-C code from python.

After taking some code from here I came up with my nosleep.py python module.

The only thing you have to do is to import it, and it will create a power assertion on import, and release it at the program exit:

import nosleep

The output then looks like this, it prints assertion logs on stderr:

% program-with-nosleep
Creating power assertion: status 0, id c_uint(1030L)
<usual program output>
Releasing power assertion: id c_uint(1030L)

The program doesn't let your OS X sleep, while it's running. Every program creates a separate assertion, OS X can sleep if all the assertions are released.

This is the whole module, tested only on OS X 10.10:

#coding=utf-8
import time
import sys
import ctypes
import CoreFoundation
import objc
import subprocess
import time
import atexit
import inspect

__author__ = 'alistra'

def setup_IO_framework():
    # load the IOKit library
    framework = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit')

    # declare parameters as described in IOPMLib.h
    framework.IOPMAssertionCreateWithName.argtypes = [
        ctypes.c_void_p,  # CFStringRef
        ctypes.c_uint32,  # IOPMAssertionLevel
        ctypes.c_void_p,  # CFStringRef
        ctypes.POINTER(ctypes.c_uint32)]  # IOPMAssertionID
    framework.IOPMAssertionRelease.argtypes = [
        ctypes.c_uint32]  # IOPMAssertionID
    return framework


def StringToCFString(string):
    # we'll need to convert our strings before use
    return objc.pyobjc_id(
        CoreFoundation.CFStringCreateWithCString(
            None, string,
            CoreFoundation.kCFStringEncodingASCII).nsstring())


def AssertionCreateWithName(framework, a_type,
                            a_level, a_reason):
    # this method will create an assertion using the IOKit library
    # several parameters
    a_id = ctypes.c_uint32(0)
    a_type = StringToCFString(a_type)
    a_reason = StringToCFString(a_reason)
    a_error = framework.IOPMAssertionCreateWithName(
        a_type, a_level, a_reason, ctypes.byref(a_id))

    # we get back a 0 or stderr, along with a unique c_uint
    # representing the assertion ID so we can release it later
    print >> sys.stderr, 'Creating power assertion: status %s, id %s' % (a_error, a_id)
    return a_error, a_id


def AssertionRelease(framework, assertion_id):
    # releasing the assertion is easy, and also returns a 0 on
    # success, or stderr otherwise
    print >> sys.stderr, 'Releasing power assertion: id %s' % a_id
    return framework.IOPMAssertionRelease(assertion_id)

framework = setup_IO_framework()
ret, a_id = AssertionCreateWithName(framework, 'NoDisplaySleepAssertion', 255, 'CA Script %s' % inspect.stack()[-1][1].split('/')[-1])

@atexit.register
def release_assertion():
    AssertionRelease(framework, a_id)