미투데이에 PyPI의 업데이트 상황을 피드로 제공합니다. 다음 계정을 구독하거나 친신하시면 됩니다. (자동 수락입니다.)
유용한 패키지가 올라오면 미투 버튼을 눌러 다른 Python 프로그래머에게도 알려보세요.
GitHub Gist를 통해 소스를 공개했습니다. 별 제한 없이 쓰셔도 됩니다. 딱히 쓸 곳도 없긴 하지만…
<script src="http://gist.github.com/528781.js"></script>미투데이에 PyPI의 업데이트 상황을 피드로 제공합니다. 다음 계정을 구독하거나 친신하시면 됩니다. (자동 수락입니다.)
유용한 패키지가 올라오면 미투 버튼을 눌러 다른 Python 프로그래머에게도 알려보세요.
GitHub Gist를 통해 소스를 공개했습니다. 별 제한 없이 쓰셔도 됩니다. 딱히 쓸 곳도 없긴 하지만…
<script src="http://gist.github.com/528781.js"></script>application: me2pypi | |
version: 1 | |
runtime: python | |
api_version: 1 | |
handlers: | |
- url: /update | |
script: me2pypi.py | |
login: admin | |
- url: .* | |
script: me2pypi.py |
cron: | |
- description: update me2day | |
url: /update | |
schedule: every 1 mins |
#!/usr/bin/env python | |
""":mod:`me2pypi` --- me2DAY PyPI feed | |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
Provides PyPI_ feed for me2DAY_. | |
.. _PyPI: http://pypi.python.org/ | |
.. _me2DAY: http://me2day.net/ | |
""" | |
import collections | |
import random | |
import hashlib | |
import urllib | |
try: | |
import json | |
except ImportError: | |
from django.utils import simplejson as json | |
try: | |
from xml.etree import cElementTree as ElementTree | |
except ImportError: | |
from xml.etree import ElementTree | |
from google.appengine.api import urlfetch, memcache | |
from google.appengine.ext import webapp | |
class Package(tuple): | |
"""Python package. | |
:param name: a package name | |
:type name: :class:`basestring` | |
:param url: a PyPI URL of the package | |
:type url: :class:`basestring` | |
:param description: a short description | |
:type description: :class:`basestring` | |
.. data:: PYPI_RSS_URL | |
The PyPI RSS URL. | |
""" | |
PYPI_RSS_URL = "http://pypi.python.org/pypi?:action=rss" | |
__slots__ = () | |
@classmethod | |
def recent_packages(cls): | |
"""Fetches recent packages from PyPI. | |
:returns: a :class:`list` of :class:`Package` objects | |
""" | |
res = urlfetch.fetch(cls.PYPI_RSS_URL) | |
xml = ElementTree.fromstring(res.content) | |
packages = [] | |
for item in xml.getiterator("item"): | |
packages.append(cls(item.find("title").text, | |
item.find("link").text, | |
item.find("description").text)) | |
return packages[::-1] | |
def __new__(cls, name, url, description): | |
return tuple.__new__(cls, (name.strip(), url.strip(), description)) | |
@property | |
def name(self): | |
"""The package name.""" | |
return self[0] | |
@property | |
def url(self): | |
"""The PyPI URL of the package.""" | |
return self[1] | |
@property | |
def description(self): | |
"""A short description.""" | |
return self[2] | |
def __repr__(self): | |
return type(self).__name__ + tuple.__repr__(self) | |
class UpdateHandler(webapp.RequestHandler): | |
"""Updates recent PyPI list to the me2day.""" | |
ME2DAY_APP_KEY = "<me2day appkey>" | |
ME2DAY_USER = "<me2day user>" | |
ME2DAY_USER_KEY = "<me2day userkey>" | |
def make_ukey(self): | |
nonce = "%08x" % random.randrange(0x100000000) | |
return nonce + hashlib.md5(nonce + self.ME2DAY_USER_KEY).hexdigest() | |
def create_post(self, body, tags): | |
url = "http://me2day.net/api/create_post/%s.json" % self.ME2DAY_USER | |
form = {"uid": self.ME2DAY_USER, | |
"ukey": self.make_ukey(), | |
"akey": self.ME2DAY_APP_KEY, | |
"post[body]": body, | |
"post[tags]": tags} | |
form = urllib.urlencode(form) | |
res = urlfetch.fetch(url=url, payload=form, method="POST") | |
return json.loads(res.content) | |
def get_posts(self): | |
url = "http://me2day.net/api/get_posts/%s.json?count=100" | |
res = urlfetch.fetch(url % self.ME2DAY_USER) | |
return json.loads(res.content) | |
def format_message(self, package): | |
if package.description is None: | |
format = u'"%s":%s' % (package.name, package.url) | |
else: | |
text = u"%s \u2014 %s" % (package.name, package.description) | |
cut = len(text) - 150 | |
desc = package.description | |
if cut > 0: | |
desc = desc[:-cut] | |
format = u'"%s":%s \u2014 %s' % (package.name, package.url, desc) | |
return format.encode("utf-8") | |
def update(self, package): | |
return self.create_post(self.format_message(package), package.name) | |
def get(self): | |
posts = self.get_posts() | |
texts = [post["textBody"] for post in posts] | |
for package in Package.recent_packages(): | |
if any(text.strip().startswith(package.name) for text in texts): | |
continue | |
self.update(package) | |
break | |
class HomeHandler(webapp.RequestHandler): | |
"""Shows the manual.""" | |
@property | |
def readme(self): | |
"""Readme text.""" | |
file = open("readme.md") | |
body = file.read() | |
file.close() | |
return body | |
@property | |
def readme_html(self): | |
"""Readme HTML.""" | |
return self.markdown(self.readme) | |
def markdown(self, text): | |
"""Translates the Markdown ``text`` to HTML. It is memcached. | |
:param text: a text formatted in Markdown | |
:type text: :class:`basestring` | |
:returns: a translated HTML in :class:`str` | |
""" | |
if isinstance(text, unicode): | |
text = text.encode("utf-8") | |
html = memcache.get(text, namespace="md") | |
if html: | |
return html | |
form = {"content": text} | |
form = urllib.urlencode(form) | |
res = urlfetch.fetch("http://markdown-service.appspot.com/markdown", | |
payload=form, method="POST") | |
html = res.content | |
memcache.set(text, html, namespace="md") | |
return html | |
def get(self): | |
self.response.out.write(""" | |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" | |
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |
<html><head> | |
<meta http-equiv="Content-Type" | |
content="text/html; charset=utf-8" /> | |
<title>미투데이 | |
PyPI 피드</title> | |
<style type="text/css"> | |
body { background-color: #eee; padding: 0; margin: 0; } | |
.content { margin: 0 auto; width: 550px; padding: 1em; | |
background-color: white; font-family: sans-serif; } | |
h1 { margin-top: 0; } | |
a { text-decoration: none; border-bottom: 1px solid silver; | |
color: rgb(91, 53, 178); } | |
.gist-file { font-size: 8pt; } | |
</style> | |
</head><body><div class="content"> | |
""".strip().encode("utf-8")) | |
self.response.out.write(self.readme_html) | |
self.response.out.write("</div></body></html>") | |
application = webapp.WSGIApplication([ | |
("/update", UpdateHandler), | |
("/", HomeHandler) | |
], debug=True) | |
if __name__ == "__main__": | |
from google.appengine.ext.webapp import util | |
util.run_wsgi_app(application) |