Some days ago, I figured out that I'd like to get a notification every time a new question is asked here. So, I've made a really simple Python script, which uses pync
(only OSX compatible) to do that for me.
I'd like to get some feedback regarding anything that comes to your mind, including good practices. That's not a complicated piece of code, but I'd like some feedback on it anyway.
import stackexchange
import time
from pync import Notifier
def get_notifications():
"""This method uses the SE API and pops up a notification everytime a new question 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.append(question.title)
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)
else:
continue
time.sleep(2)
if __name__ == '__main__':
get_notifications()
4 Answers 4
Nitpicks
- Maximum line length should be 72 characters for docstrings.
- For your
old
variable, you may wan to use aset
instead of alist
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.
-
\$\begingroup\$ Thanks @MathiasEttinger. It looks awesome. While I'm not a fan of gradually
import
ing the modules this way, I might use this for now. \$\endgroup\$Grajdeanu Alex– Grajdeanu Alex2016年09月28日 11:34:25 +00:00Commented Sep 28, 2016 at 11:34
You don't delete previous items from old
and so it keeps increasing. This shouldn't have too much of an impact, but, can eat up your memory after a long while.
The else: continue
is also un-needed, as if it's else then it'll go to the beginning of the loop anyway.
Two more ideas:
- I would switch the
old.append(question.title)
and theNotifier.notify
-lines. It makes more sense to add the question to the list of already notified questions AFTER they have been (implicit successfully) notified. - the indices in string format are redundant if used in order, I would just use
Votes: {} | Answers: {} | Views: {}
.
for question in questions[:1]:
is just a fancy way for writing for question in questions[0]:
which is just question = questions[0]
.
In addition, I would make old
a set. This way the if question.title not in old:
check will stay O(1) and not grow with the number of seen questions.
Your format can be slightly simplified by accessing the attribute in the format string:
Notifier.notify('Votes: {0.score} | Answers: {1} | Views: {0.view_count}'.format(
question, len(question.answers)),
title='CodeReview: {.title}'.format(question),
subtitle='Tags: {}'.format(', '.join(question.tags)),
sound='default',
open=question.url)
Explore related questions
See similar questions with these tags.