Exaile 豆瓣电台插件 0.0.4

趁热打铁, Exaile豆瓣电台插件今天又有了一些更新:

  1. 支持今天豆瓣新发布的摇滚、民谣、NewAge频道
  2. 支持Exaile 0.3.2开始的界面透明
  3. 与官方保持一致,在非个人频道时禁用trash按钮

下载
http://github.com/sunng87/exaile-doubanfm-plugin/downloads

很多朋友纠结于安装,安装确实稍显麻烦,大家注意:

  1. 确认你的Exaile版本0.3.2。
  2. 下载,重命名下载的文件为doubanfm.tar.gz(Exaile要求压缩包内的文件名要以压缩包的名字开头,否则报错;而github上使用相同的文件名用作下载会有潜在的问题)
  3. 如果已经安装过这个插件,请删除 rm -r ~/.local/share/exaile/plugins/doubanfm
  4. 打开Exaile,打开Preferences,到Plugins页,安装
  5. 勾选doubanfm
  6. 如果plugins树下没有出现Douban Radio,关闭Preferences重开,找到Douban Radio,设置用户名密码
  7. 重启Exaile
  8. 打开文件菜单,Open Douban.fm,选择你需要的频道
  9. 双击音轨或点击播放开始播放
  10. 切换到豆瓣视图,菜单View->Doubanfm Mode,或快捷键 Ctrl+alt+d

exaile douban radio
Enjoy!

Python Web 开发框架 Bottle

Bottle是一个非常精致的WSGI框架,它提供了Python Web开发中需要的基本支持:URL路由,Request/Response对象封装,模板支持,与WSGI服务器集成支持。整个框架的全部代码约有2000行,它的核心部分没有其他任何依赖,只要有Python环境就可以运行。

Bottle适用于小型的Web开发,在应用程序规模比较小的情况下可以实现快速开发。但是由于自身功能所限,对于大型的Web程序,Bottle的功能略显不足,程序员需要手动管理模块、数据库、配置等等,与Pylons等框架相比Bottle的优势就难以体现出来了。

快速入门

通过一个简单的、典型的例子描述Bottle的使用:

from bottle import Bottle, run, mako_view, request

myapp = Bottle()

@myapp.get('/hello/:name/:count#\\d+#')
@mako_view('hello')
def hello(name, count):
    ip = request.environ.get('REMOTE_ADDR')
    return dict(n=name, c=int(count), ip=ip)

run(app=myapp)

我们创建一个Bottle对象,通过decorator配置一条路由记录。Bottle的url映射支持具名参数,“/hello/:name/:count#\\d+#” 格式的参数,可以匹配/hello/(.+?)/(\d+?)的URL。在方法体中,通过environ字典获得客户端IP,这个操作和其他WSGI框架是一致的。接着通过一个字典类型将Model数据传递给View。View模板通过decorator定义,采用mako模板引擎实现,模板名为hello,他表示在当前目录下一个名叫hello.tpl的文件。

剖析

Bottle主要可以分成4个模块:

  • Router和WSGI Application
  • Request和Response, Web helpers
  • Template Adapters
  • WSGI Server Adapters

Router和WSGI Application

一个Bottle对象是一个标准的WSGI App,这使他可以与很多WSGI Server集成。每个app下管理一个Router用于针对URL映射到handler方法。根据Python decorator的特性,路由规则是在程序启动时自动执行的。

Bottle的route方法用于注册handler到router中。除此以外,还有很多function currying帮助您简单地路由GET / POST 等特定HTTP方法。

Bottle最近以来支持了hook,可以注册一些方法在每个请求之前、或之后执行。

当接受一个HTTP请求时,WSGI Server会调用WSGI App的wsgi方法。它的流程是:

  1. 根据标准的WSGI接口规范,从WSGI environ中生成一个Request对象,初始化一个response对象。
  2. 在寻找方法之前,实际上Bottle会自动执行您注册的before_hook。(Bottle._add_hook_wrapper)
  3. 根据request.path的URL在router中寻找对应的handler方法(Bottle.match_url, Router.match)。这一步中除了找到合适的handler方法,还要负责提取url中的具名参数,将结果以tuple的形式返回。
  4. 以返回的方法和参数,执行handler,获得返回值。
  5. 执行所有的after_hooks。
  6. 根据返回的不同类型,写入Response的头部。在这一步,Bottle还会应用一些filter,在Bottle中,filter是用于处理handler返回类型的。例如,一个典型的filter即内置的dict2json,他将handler返回的dict类型自动换成json。(Bottle._cast)
  7. 根据返回的HTTP状态码,对handler返回对象进行处理。调用WSGI Server的start_response方法将返回对象写给客户端。

Request和Response, Web helpers

Bottle对HTTP的请求和响应封装了Request和Response对象。它采用threadlocal的方式,由Bottle app管理生命周期。您可以在声明handler方法时不必像Java Servlet那样将他们以参数传入,这样增加了方法设计的灵活性,也使得单元测试变得相对轻松。

Request对象是对WSGI environ属性的封装,可以从中取得的属性取决于WSGI Server对PEP333的实现。

关于Request API,可以参考文档

类似的,可以参考Response API了解如何对HTTP响应进行操作

和很多Web框架一样,你不必手动去设置HTTP重定向,helper方法会简化这些操作:abort, redirect, static_file。当然,你也可以自己创建一些helper。

Template Adapters

template方法用于渲染视图,您可以使用不同的模板实现:mako, jinja2, cheetah, simpletal以及Bottle自己的简单实现。

view作为一个decorator可以简化模板的选择。与route类似,作者也提供了一些function currying来支持mako_view这样简便的写法。

Server Adapters

Bottle的Server Adapters简直可以说是WSGI Server的博览会,从这里您可以了解目前比较流行的WSGI实现:

  • flup
  • wsgiref
  • cherrypy
  • paste
  • fapws3
  • tornado
  • Google Appengine
  • twisted
  • diesel
  • meinheld
  • gunicorn
  • eventlet
  • gevent
  • rocket

当然,这些不是全部,如果要使用不在其中的WSGI Server,您只需实现一个ServerAdapter的run方法即可,需要做的就是将Bottle app传给server并启动它。

内置的run方法用于启动服务,您还可以指定一个reloader参数使Bottle在后台检查源文件的修改情况,实现热加载。
除了run方式的启动,由于Bottle app本身就是一个符合标准的WSGI app,所以也可以通过一些服务器特有的方式启动服务,例如gunicorn:
gunicorn -w 2 -D -b 127.0.0.1:18080 module:app

实战

Middlewares

Middleware是WSGI的重要概念http://www.python.org/dev/peps/pep-0333/#middleware-components-that-play-both-sides 借助一些成熟的middleware可以添加一些Bottle目前不具备的功能:没错,Session。

Session Middleware最著名的选择叫做Beaker,要其用Beaker,只需要一个装饰器模式的App声明即可,您可以参考Beaker的文档.

Pylons以使用Middleware著称,而除了Routing这样核心的Middleware,包括Beaker和Authkit都可以应用在Bottle程序上。

App Mounting

Bottle的App提供一个很有用的mount方法帮助你将Web应用模块化。您可以将一个Bottle App挂载到一个父App上的某个路径,以父App启动后,父App可以为子App在一个路径下提供路由。

不过你会遇到这样的问题:

child = Bottle()

@child.get("/")
def hello():
    return "hello world"

parent = Bottle()
parent.mount(child, "/child")

很自然的,您希望打开浏览器访问http://localhost:8000/child可能看到hello world,然后却得到404.这个问题不难察觉,于是访问http://localhost:8000/child/,工作正常。

我曾给作者提过这个问题http://github.com/defnull/bottle/issues#issue/88,(补一句,Bottle的作者少有的Nice,对你提出的问题他通常都会认真解答),作者提了一个重定向的方式,不过还是没有决定把它直接加进mount方法里。

那么,我们需要一个新的mount来支持http://localhost:8000/child

def mount(parent, child, path):
    parent.mount(child, path)
    @parent.get(path)
    def redir():
        redirect(path+"/")

Google AppEngine

因为有WSGI标准,你可以用Bottle开发Google AppEngine程序。您只需要在handler文件中加入这样的代码即可:

from google.appengine.ext.webapp.util import run_wsgi_app
def main():
    run_wsgi_app(my_bottle_app)

if __name__ == '__main__':
    main()

依赖?Bottle本身是self-contained,不过你可能还需要一个强大一些的模板引擎来完成你的应用,比如Mako。这样,你需要把依赖和自己的程序一起上传到GAE。对于Mako来说,除了它自己,还依赖marksave,别忘了它。

以上就是我对Bottle的理解和使用心得,希望您在合适的场景下使用Bottle,只有这样,才能感受到这个框架所带来的乐趣。

迁移pylonsbook chapter 8到pylons1.0

Pylonsbook的第八章,是一个完整的CRUD程序例子。pylons升级到1.0之后,有一些代码不能正常工作了。

首先查看你使用的各种库的版本:
paster shell development.ini

import pylons
pylons.__version__
import sqlalchemy
sqlalchemy.__version__
import webhelpers
webhelpers.__version__

Pylons 1.0开始鼓励通过继承Base类的声明式ORM(pylonsbook介绍的是pylons 0.9x,sqlalchemy 0.5x,使用mapper的方式)
对pylons1.0,mode/__init__.py中声明model的代码如下:

from sqlalchemy import *
from sqlalchemy.orm import relationship

class PageTag(Base):
    __tablename__ = 'pagetag'
    id = Column('id', Integer, Sequence('pagetag_seq_id', optional=True), primary_key=True)
    pageid = Column('pageid', Integer, ForeignKey('page.id'))
    tagid = Column('tagid', Integer, ForeignKey('tag.id'))

class Page(Base):
    """docstring for Page"""
    __tablename__ = 'page'
    id = Column('id', Integer, Sequence('page_seq_id', optional=True), primary_key=True)
    content = Column('content', Text, nullable=False)
    posted = Column('posted', DateTime, default=now())
    title = Column('title', Unicode(255), default=u'Untitled Page')
    heading = Column('heading', Unicode(255))

    tags = relationship(PageTag, backref='page')
    comments = relationship("Comment", backref='page')

class Tag(Base):
    """docstring for Tag"""
    __tablename__ = 'tag'
    id = Column('id', Integer, Sequence('tag_seq_id', optional=True), primary_key=True)
    name = Column('name', Unicode(20), nullable=False, unique=True)

class Comment(Base):
    __tablename__ = 'comment'
    id = Column('id', Integer, Sequence('comment_seq_id', optional=True), primary_key=True)
    content = Column('content', Text, default=u'')
    name = Column('name', Unicode(255))
    email = Column('email', Unicode(255), nullable=False)
    created = Column('created', TIMESTAMP, default=now())
    pageid = Column(Integer, ForeignKey('page.id'))

init_model 方法的内容也不相同,但是对Session的配置都是在Session.configure里进行。 如果设置autocommit=True,即使设置了autoflush=True仍然需要Session.flush()一下才能把变更写入数据库。否则,则需要显示调用Session.commit()

在写controller的部分,pylonsbook大量用了

page_q = model.meta.Session.query(model.Page)
c.page = page_q.get(int(id))

page_q = model.meta.Session.query(model.Page)
c.page = page_q.filter_by(id=id).first()

在新版本里只需要
c.page=Session.query(model.Page).get(id)
即可。

在模板部分,pylons现在默认不依赖formbuild,所以文中提到的helper方法要额外安装formbuild库。不过这些也可以用过webhelper来实现,在lib/helpers.py里:
from webhelpers.html.tags import *

在field.html中

<p>Heading: ${h.text(
    "heading"
)}
</p>

<p>
Title: ${h.text(
    "title"
)}
</p>

<p>
Content: ${h.textarea(
    "content", cols=40, rows=10
)}
</p>

模板中一个非常常用的方法url_for在pylons1.0里被改为pylons.url,需要修改/lib/helpers.py
from pylons import url

书中所有的重定向都是直接设置response相关属性实现的,实际可以用redirect,结合url:
redirect(url(controller='page', action='view', id=page.id))

分页部分,如果你使用的是sqlalchemy 0.6的话,随pylons 1.0发布的Webhelpers 0.6还不支持 sqlalchemy 0.6的query object,需要手动升级到1.2. (easy_install -U webhelpers)

最后关于flash状态,webhelpers里有一个专门的实现,不过还是不太好用:
http://pylonshq.com/docs/en/1.0/thirdparty/webhelpers/pylonslib/

The post is brought to you by lekhonee v0.7

ls my python packages

在豆瓣上看到一个无聊的秀gems的活动,我也无聊一下,秀一下eggs。

ls /usr/local/lib/python2.6/dist-packages/ | grep .egg | sed ‘s/\(.*\)/<li>\1<\/li>/’

  • amqplib-0.6.1-py2.6.egg
  • argparse-1.1-py2.6.egg-info
  • AuthKit-0.4.5-py2.6.egg
  • avro-1.3.3-py2.6.egg
  • Beaker-1.3-py2.6.egg
  • Beaker-1.5.4-py2.6.egg
  • beanstalkc-0.2.0-py2.6.egg
  • bottle-0.8.1-py2.6.egg
  • bottle-0.8.3-py2.6.egg
  • bpython-0.9.6.2-py2.6.egg
  • bson-0.1.2-py2.6.egg
  • CherryPy-3.1.2-py2.6.egg
  • CouchDB-0.6-py2.6.egg
  • coverage-3.2-py2.6-linux-i686.egg
  • Cython-0.12.1-py2.6-linux-i686.egg
  • decorator-3.0.0-py2.6.egg
  • distribute-0.6.14-py2.6.egg
  • Django-1.0.2_final-py2.6.egg
  • Django-1.1-py2.6.egg
  • Django-1.2-py2.6.egg
  • django_sphinx-2.2.3-py2.6.egg
  • docutils-0.6-py2.6.egg
  • Editra-0.4.95-py2.6.egg
  • Editra-0.5.05-py2.6.egg
  • Editra-0.5.72-py2.6.egg
  • elementtree-1.2.7_20070827_preview-py2.6.egg
  • eventlet-0.9.9-py2.6.egg
  • Fabric-0.9.1-py2.6.egg
  • fapws3-0.5.dev-py2.6-linux-i686.egg
  • FormEncode-1.2.1-py2.6.egg
  • GeoAlchemy-0.1-py2.6.egg
  • Geohash-1.0rc1-py2.6.egg
  • gevent-0.13.0.egg-info
  • greenlet-0.3.1-py2.6-linux-i686.egg
  • gunicorn-0.10.0-py2.6.egg
  • gunicorn-0.11.0-py2.6.egg
  • gunicorn-0.8.1-py2.6.egg
  • httplib2-0.5.0-py2.6.egg
  • ipython-0.10-py2.6.egg
  • ipython-0.9.1-py2.6.egg
  • Jinja2-2.5-py2.6.egg
  • Mako-0.2.4-py2.6.egg
  • Mako-0.3.4-py2.6.egg
  • Markdoc-0.6.4-py2.6.egg-info
  • Markdown-2.0.3-py2.6.egg
  • Markdown-2.0.3-py2.6.egg-info
  • MarkupSafe-0.9.2-py2.6-linux-i686.egg
  • meinheld-0.3.1-py2.6-linux-i686.egg
  • Ming-0.2-py2.6.egg
  • mock-0.6.0-py2.6.egg
  • mocker-0.10.1-py2.6.egg
  • MySQL_python-1.2.3c1-py2.6-linux-i686.egg
  • networkx-1.0rc1-py2.6.egg
  • nose-0.10.4-py2.6.egg
  • numpy-1.4.1-py2.6-linux-i686.egg
  • Paste-1.7.2-py2.6.egg
  • PasteDeploy-1.3.3-py2.6.egg
  • PasteScript-1.7.3-py2.6.egg
  • pip-0.4-py2.6.egg
  • pip-0.8-py2.6.egg
  • progressbar-2.2-py2.6.egg
  • psycopg2-2.2.2-py2.6-linux-i686.egg
  • pycurl-7.19.0-py2.6-linux-i686.egg
  • pyev-0.5.3_3.8-py2.6-linux-i686.egg
  • Pygments-1.0-py2.6.egg
  • Pygments-1.3.1-py2.6.egg
  • pygooglechart-0.2.1-py2.6.egg
  • PyGreSQL-4.0-py2.6-linux-i686.egg
  • Pylons-0.9.7-py2.6.egg
  • Pylons-1.0-py2.6.egg
  • pymongo-1.6-py2.6-linux-i686.egg
  • pymongo-1.7-py2.6-linux-i686.egg
  • pymongo-1.8-py2.6-linux-i686.egg
  • pysal-1.0.0.egg-info
  • python_cjson-1.0.5-py2.6-linux-i686.egg
  • python_memcached-1.45-py2.6.egg
  • python_openid-2.2.5-py2.6.egg
  • PyXML-0.8.4-py2.6-linux-i686.egg
  • PyYAML-3.09-py2.6.egg-info
  • RDFobject-0.1.4-py2.6.egg
  • Rocket-1.1.1-py2.6.egg
  • Routes-1.10.3-py2.6.egg
  • Routes-1.12.3-py2.6.egg
  • Rtree-0.6.0-py2.6.egg
  • ruffus-2.2-py2.6.egg
  • scipy-0.8.0-py2.6-linux-i686.egg
  • setuptools-0.6c11-py2.6.egg-info
  • setuptools-0.6c9-py2.6.egg
  • setuptools-0.6c9-py2.6.egg.OLD.1283177768.67
  • Shapely-1.0.12-py2.6.egg
  • Shapely-1.2-py2.6.egg
  • simplejson-2.0.8-py2.6.egg
  • Spawning-0.9.3rc2-py2.6.egg
  • Sphinx-1.0b2-py2.6.egg
  • SQLAlchemy-0.6.3-py2.6.egg
  • Tempita-0.2-py2.6.egg
  • threadpool-1.2.7-py2.6.egg
  • tornado-0.2-py2.6.egg
  • twill-0.9-py2.6.egg
  • Twisted-10.1.0-py2.6-linux-i686.egg
  • vimpdb-0.3.8-py2.6.egg
  • virtualenv-1.3.3-py2.6.egg
  • WebError-0.10.1-py2.6.egg
  • WebHelpers-0.6.4-py2.6.egg
  • WebOb-0.9.6.1-py2.6.egg
  • web.py-0.32-py2.6.egg-info
  • web.py-0.34-py2.6.egg
  • WebTest-1.1-py2.6.egg
  • Werkzeug-0.6.2-py2.6.egg
  • whizzer-0.2.egg-info

话说用sed能不能让开头重复的行不输出呢?或者什么命令能实现?

The post is brought to you by lekhonee v0.7