Skip to content

Instantly share code, notes, and snippets.

@dahlia
Created August 17, 2010 07:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dahlia/528781 to your computer and use it in GitHub Desktop.
Save dahlia/528781 to your computer and use it in GitHub Desktop.
미투데이 PyPI 피드

미투데이PyPI의 업데이트 상황을 피드로 제공합니다. 다음 계정을 구독하거나 친신하시면 됩니다. (자동 수락입니다.)

http://me2day.net/pypi

유용한 패키지가 올라오면 미투 버튼을 눌러 다른 Python 프로그래머에게도 알려보세요.

소스

http://gist.github.com/528781

GitHub Gist를 통해 소스를 공개했습니다. 별 제한 없이 쓰셔도 됩니다. 딱히 쓸 곳도 없긴 하지만…

<script src="http://gist.github.com/528781.js"></script>

작성자

홍민희
http://dahlia.kr/
http://me2day.net/dahlia

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>&#48120;&#53804;&#45936;&#51060;
PyPI &#54588;&#46300;</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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment