Simple CodeReview Questions Notifications for OSX
Nitpicks


Maximum line length should be 72 characters for docstrings.
For your old variable, you may wan to use a set instead of a list to check for existence (not in) as it will be \$O(1)\$ instead of \$O(n)\$.


Reusability

As it stand, I can't use this script because it is too much tied to your OSX only notifier. I'd rather yield questions from get_notifications (which will not be greatly named after that) and format them elsewhere, letting other users handle it with other notifications systems (or even email, for that matter).

Something like:

import stackexchange
import time


def pync_notify(question):
    from pync import Notifier
    Notifier.notify('Votes: {0} | Answers: {1} | Views: {2}'.format(
        question.score, len(question.answers), question.view_count),
        title='CodeReview: {0}'.format(question.title),
        subtitle='Tags: {0}'.format(', '.join(question.tags)),
        sound='default', open=question.url)


def gtk_notify(question):
    from gi.repository import Notify
    Notify.init('stackexchange')
    notification = Notify.Notification.new(
        'CodeReview: {}'.format(question.title),
        'Votes: {} | Answers: {} | Views: {}'.format(
            question.score, len(question.answers), question.view_count),
        'dialog-information')
    notification.show()


def get_questions():
    """Fetch recent questions from CodeReview

    This function uses the SE API and yield each
    new question that is asked on CR.
    """

    cr = stackexchange.Site(stackexchange.CodeReview)

    old = {}
    while True:
        questions = cr.recent_questions(filter='_b')
        for question in questions[:1]:
            if question.title not in old:
                old.add(question.title)
                yield question

        time.sleep(2)


if __name__ == '__main__':
    for question in get_questions():
         pync_notify(question)


Should be enough. Note that I import modules each time a new question appears, which is not ideal, but at least it let you easily deal with modules that are not present on the system. And the few extra time spent realizing the module is already loaded is negligible compared to your time.sleep(2) anyway. You may want to use classes with a __call__ method if you really want to import and initialize only once.

On the reusability side, it could also be good to be able to parametrize the generator with the site the user whish to fetch:

def get_questions(site=stackexchange.CodeReview):
    se = stackexchange.Site(site)
    …


Better usage of the stackexchange API

The stackexchange module let you directly query information using "site classes" (such as stackexchange.CodeReview) without necessarily relying on a stackexchange.Site object. So:

CR = stackexchange.CodeReview()
CR.questions()


will be a direct call to the SE API on questions.

From there, two things worth noting:


The sort method;
The fromdate parameter.


Sorting results

The advantage of using sort='creation' in the query parameters is that you only need to store the last asked question in your old variable. You can then iterate over the result of the query and stop when q.title == old.title without storing previous questions anymore.

Asking for questions asked after a given date

The fromdate parameter takes a timestamp and make the query returns only questions asked after that point in time. There is no real need for sorting anymore, all you need to remember is the date at when the last query was made.

Proposed improvements

import stackexchange
import datetime
import time


def pync_notify(question):
    from pync import Notifier
    Notifier.notify('Votes: {0} | Answers: {1} | Views: {2}'.format(
        question.score, len(question.answers), question.view_count),
        title='CodeReview: {0}'.format(question.title),
        subtitle='Tags: {0}'.format(', '.join(question.tags)),
        sound='default', open=question.url)


def gtk_notify(question):
    from gi.repository import Notify
    Notify.init('stackexchange')
    notification = Notify.Notification.new(
        'CodeReview: {}'.format(question.title),
        'Votes: {} | Answers: {} | Views: {}'.format(
            question.score, len(question.answers), question.view_count),
        'dialog-information')
    notification.show()


def now():
    """Return the current timestamp"""
    date = datetime.datetime.now()
    return int(date.timestamp())


def get_questions(site=stackexchange.CodeReview, query_delay=2):
    """Fetch recent questions from the Stack Exchange network

    This function uses the SE API and yield each
    new question that is asked on the given site.
    """

    se = site()
    last_query = now()

    while True:
        fromdate, last_query = last_query, now()
        questions = se.questions(fromdate=fromdate, sort='creation')
        yield from questions
        time.sleep(query_delay)


if __name__ == '__main__':
    for question in get_questions():
         pync_notify(question)




Last minute reading

On the discussion about throttles of the StackExchange API, one can read:


  While not strictly a throttle, the Stack Exchange API employs heavy caching and as such no application should make semantically identical requests more than once a minute. This is generally a waste of bandwidth as, more often than not, the exact same result will be returned.


This means that you probably want to increase your time.sleep delay from 2 to at least 60.