开始
入门Piston非常容易。用Piston写出来的API代码,无论是形式还是表现都与其他Django应用无异。 API代码使用URL映射一组handlers对资源进行定义。
(wrongway在这里强调一下:原文档部分文字含糊,一些概念混淆,比如handler,可能是一个Handler类,也可能是一个Hanlder实例对象,也可能是Handler类的read/update等方法,阅读时请注意)
在入门之前,建议您为API代码创建一个单独的目录,比如 ‘API’ 。
我们的应用结构如下::
urls.pysettings.pymyapp/__init__.pyviews.pymodels.pyapi/__init__.pyurls.pyhandlers.py
接下来,在最上层urls.py中定义一个’namespace’,以对应API,如下::
#!pythonurlpatterns = patterns('',# all my other url mappings(r'^api/', include('mysite.api.urls')),)
如上所设,包含API的urls.py将处理所有以’api/‘开头的网址。
接下来将展示如何创建资源,以及如何将URL与资源挂钩。
资源(Resources)
资源(Resource)指的是代码中映射各种数据的实体。可以是博文、评论以及其他任何数据。
(Wrongway吐槽:其实这一节是讲Handler的…)
下面,我们在handlers.py中创建一个简单的handler::
#!pythonfrom piston.handler import BaseHandlerfrom myapp.models import Blogpostclass BlogpostHandler(BaseHandler):allowed_methods = ('GET',)model = Blogpostdef read(self, request, post_slug):...
Piston通过handler将资源与models进行映射,这个过程中Piston做了很多繁重的幕后工作,为开发者节省了大量精力。
Resource必须是一个类,通常情况下,Resource要实现下列四个方法中的一个或多个:
:read: 由 GET 动作调用,获取对象而不做数据修改(该方法是幂等的)
 :create: 由 POST 动作调用,创建新对象(们)并返回该对象(们)(或是返回 :ref:rc.CREATED.)
 :update: 由 PUT 动作调用,更新某个已存在的对象并返回该对象(或是返回 :ref:rc.ALL_OK.)
 :delete: 由 DELETE 动作调用,删除某个已存的对象,只返回 :ref:rc.DELETED 。
除此之外,我们还可以定义其他我们所需的方法。只要在 fields 中填写方法名称,该方法就会被调用,
并在调用时自动传入 model 的实例做为参数。
该方法的返回值将用做该key(方法名称)的值,返回值不做限制,可以为任意类型。
NB: 上述自定义的 “resource methods” 应该被 @classmethod 修饰。因为使用这些自定义方法时Piston未必会对Handler进行实例化。 假设你已经定义了一个UserHandler类,并在该类中自定义了一个返回User对象的方法,在这种情况下,Piston是不会调用UserHandler实例的自定义对象方法, 只会调用UserHander类的自定义的类方法。
一个handler即可以表示单独的一个对象,也可以表示多个对象的集合,因此我们可以在read()方法中分别处理这两种情况::
#!pythonfrom piston.handler import BaseHandlerfrom myapp.models import Blogpostclass BlogpostHandler(BaseHandler):allowed_methods = ('GET',)model = Blogpostdef read(self, request, blogpost_id=None):"""Returns a single post if `blogpost_id` is given,otherwise a subset."""base = Blogpost.objectsif blogpost_id:return base.get(pk=blogpost_id)else:return base.all() # Or base.filter(...)
发射器(Emitters)
发射器(Emitters) 表示输出的数据类型,可以是 YAML, JSON, XML, Pickle 或 Django ,分别对应 emitters.py 中的 XMLEmitter, JSONEmitter, YAMLEmitter, PickleEmitter 和 DjangoEmitter 。
编写自己的emitters也很容易,要做的仅仅是创建一个继承 Emitter 的派生类,然后在其中创建 render 方法。
render方法接收一个 ‘request’ 参数,该参数是一个请求(request)对象的拷贝。 多了解一下request.GET是很有必要的。 (比如定义回调,JSON的输出)
要将数据进行序列化/渲染,需要调用 self.construct() ,该方法始终返回一个数据字典,我们就可以对字典做任何想做的操作,再将其返回(返回值须是unicode字符串)。
NB: 可以用 Emitter.register 函式注册Emitters,同样也可以用 Emitter.unregister 函式来移除注册(假使你想移除一个内置emitter)。
内置的emitter注册::
#!pythonclass JSONEmitter(Emitter):...Emitter.register('json', JSONEmitter, 'application/json; charset=utf-8')
自定义emitter时,可以引入Emitter模块并调用 ‘register’ 对其注册,从而使自定义的emitter生效。 也可以利用同名数据类型(即传入的第一个参数)来覆写内置或是已存在的emitters。
上述实践,使得Piston添加其他形式的Emitter扩展变得非常容易,比如protocal buffers或是CSV。
可以通过 ‘?format=’ GET参数(例如 ‘/api/blogposts/?format=yaml’)为Emitter设置格式。不过新版本的Piston中,我们可以在URL映射中配置 ‘emitter_format’ 关键字参数来设置Emitters(与’format’关键字并不冲突),如下::
#!pythonurlpatterns = patterns('',url(r'^blogposts(?P<emitter_format>.+)$', ...),)
这样,/blogposts.json 就会使用 JSON emitter。
此外,我们还可以在URL映射中直接设置关键字参数来指定emitter格式::
#!pythonurlpatterns = patterns('',url(r'^blogposts$', resource_here, { 'emitter_format': 'json' }),)
URL映射(Mapping URLs)
Piston的URL映射规则与Django无异。以BlogpostHandler为例:
在 urls.py 中::
#!pythonfrom django.conf.urls.defaults import *from piston.resource import Resourcefrom mysite.myapp.api.handlers import BlogpostHandlerblogpost_handler = Resource(BlogpostHandler)urlpatterns = patterns('',url(r'^blogpost/(?P<post_slug>[^/]+)/', blogpost_handler),url(r'^blogposts/', blogpost_handler),)
见上,任何对 /api/blogpost/some-slug-here/ 和 /api/blogposts/ 的访问都映射到BlogpostHandler, 分别对应同一个handler的两种不同数据集。 要注意:一个单独的handler可以处理单个对象,也可以处理多个对象的集合。
.. _anonymous_resources:
匿名资源(Anonymous Resources)
资源也可以是匿名的(“anonymous”)。匿名资源即是一种可以实例化的特殊资源, 它用于未经授权(未通过OAuth,Basic或是其他认证handler)的请求。
举个例子,本文前面的BlogpostHandler,允许匿名访问博文。 但我们并不想让匿名用户也能够创建/更新/删除博文,也不想让未经授权的用户看到所有字段。
类似的需求可以通过创建继承自AnonymousBaseHandler(而不是BaseHandler)的新handler来实现。 这样做可以省却很多繁重的工作量。
如下::
#!pythonfrom piston.handler import AnonymousBaseHandler, BaseHandlerclass AnonymousBlogpostHandler(AnonymousBaseHandler):model = Blogpostfields = ('title', 'content')class BlogpostHandler(BaseHandler):anonymous = AnonymousBlogpostHandler# same stuff as before
我们没必要为了使用匿名handlers,就设置一个继承自BaseHandler的代理handler(“proxy handler”)。 反而是象上例这般直接指向一个匿名资源,更为实用。
.. _working_with_models:
使用Models(Working with Models)
Piston可以绑定某个model,但并不依赖该model。这样做的好处很明显:
- 没有覆写read/create/update/delete时,Piston就提供适用的默认处理(前提是方法已经出现在 
allow_methods中)。 - 没必要必须指定 
fields和exclude(但你可以这么做,因为它们之间并不是互斥的) - 如果已经在某个handler中使用了某个model,那么Piston会记住该model的 
fields/exclude设置,并在同样返回该model的其他handlers中继续使用该设置(除非被覆写)。 
正如我们之前所见的,在handler中绑定某个model就只需设置 model 类参数这么简单。
扩展阅读: 为什么Piston要记住之前的handlers所记录的fields信息 <http://bitbucket.org/jespern/django-piston/wiki/FAQ#why-does-piston-use-fields-from-previous-handlers>_
配置Handlers(Configuring Handlers)
Handlers使用用四个参数进行配置。
Model
用于绑定model,查看 :ref:working_with_models.
.. _fields_and_exclude:
Fields/Exclude
返回的数据中应包含和排除的字段列表。允许内嵌,可以是外键字段以及多对多字段。
也可以是编译后的正则表达式,例如::
#!pythonimport reclass FooHandler(BaseHandler):fields = ('title', 'content', ('author', ('username', 'first_name')))exclude = ('id', re.compile('^private_'))
用户可以通过Many2many/ForeignKey字段访问博文,如下::
class UserHandler(BaseHandler):model = Userfields = ('name', ('posts', ('title', 'date')))
返回的数据会包含用户名称以及该用户发布的博文标题和日期。
对于fields中列表为空的内嵌资源,Piston会使用默认的handler,如下::
class PostHandler(BaseHandler):model = Postexclude = ('date',)class UserHandler(BaseHandler):model = Userfields = ('name', ('posts', ()))
UserHandler会显示一个用户所有博文的所有字段,但不包括博文的发布日期date。
fields 和 exclude 都不是必须的,二者皆无时Piston也可以使用。
Anonymous
指向可替代的匿名资源。查看 :ref:anonymous_resources
认证(Authentication)
Piston通过一个简单的接口支持可插换的认证,自带两种内置的认证机制,分别是 piston.authentication.HttpBasicAuthentication 和 piston.authentication.OAuthAuthentication 。
Basic auth handler非常简单,如果我们要编写自有的认证handler,它无疑很有参考价值。
Note: 在apache或nginx下使用 piston.authentication.HttpBasicAuthentication ,须要在服务器或虚拟主机配置时添加
WSGIPassAuthorization On 指令,否则django-piston不会
从 request.META 的 HTTP_AUTHORIZATION 中读取认证数据。详看: http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPassAuthorization.
认证Handler必须是一个类,且必须实现 is_authenticated 和 challenge 两个方法。
is_authenticated 只接收一个参数,即Django接收的 request 对象的一个拷贝。
该对象包含认证一个用户所需的全部信息,比如 request.META.get('HTTP_AUTHENTICATION').
认证成功之后,该函式必须将 request.user 设为正确的 django.contrib.auth.models.User 对象。
这样做是考虑到随后的handlers要确认是哪个用户已经登录。
该函式必须返回True或False,以表示该用户是否登录成功。
对于认证失败的情况,就交由 challenge 处理。
challenge 不接收任何参数,必须返回一个包含正确cahllenge指令的 HttpResponse 对象。
对于Basic auth来说,该函式返回一个带有 WWW-Authenticate 报头的,状态码为401的空响应(response)。
并通知接收端要提供认证信息。
针对匿名handlers,Piston提供了一个特珠的类: piston.authentication 中的 NoAuthentication 。
该类始终返回 is_authenticated 为 True。
OAuth
OAuth是首选的认证方式,因为它能分辨消费者”consumers”,也就是说,它能识别使用该API的已认证的应用。 Piston深知和尊重这一点,并对此善加利用,比如当我们使用 @throttle 装饰器时, 它于底层限制每一个消费者应用,维系服务运转,即使当中某个服务已经达到限制配额。
表单验证(Form Validation)
Django拥有一套出色的内置表单验证机制,Piston对此善加利用。
我们可以对某个方法使用@validate装饰器,该装饰器接收两个参数。
第一个参数是必须的,即用于验证的表单,第二个参数是可选的,即提交数据的动作。
对于 create ,默认的动作是 ‘POST’ ,对于 update ,动作就是 ‘PUT’ 。
举个例子(使用ModelForm)::
#!pythonfrom django import formsfrom piston.utils import validatefrom mysite.myapp.models import Blogpostclass BlogpostForm(forms.ModelForm):class Meta:model = Blogpost...@validate(BlogpostForm)def create(request, ...):...
使用一个普通form::
#!pythonfrom django import formsfrom piston.utils import validateclass DataForm(forms.Form):data = forms.CharField(max_length=128)is_private = forms.BooleanField(default=True, required=False)...@validate(DataForm, 'PUT')def update(...):...
若某个方法被@validate装饰,那么发送给该方法的数据如果没有通过表单本身的 is_clean 方法验证,
Piston就会禹客户端返回错误,而不运行任何操作。如果通过了验证,
表单对象就会附加到请求(request)对象中。
然后我们可以通过 request.form 获取表单(可以进一步通过cleaned_data取得验证后的数据),如下例::
#!python@validate(EchoForm, 'GET')def read(self, request):return {'msg': request.form.cleaned_data['msg']}
辅助方法,工具类 & 装饰器(Helpers, utils & @decorators)
出于方便,Piston提供了一组辅助和工具方法。
其中一个便是 piston.utils 中的 rc ,它包含了一组标准的响应返回。
在Piston的动作中做为响应码返回给客户端,以表示某个特定的状态。
但在最新版本的Piston, rc 返回的则是一个 新的 HttpResponse的实例(此前版本仅仅是返回状态码),
使用如下::
#!pythonresp = rc.CREATEDresp.write("Everything went fine!")return respresp = rc.CREATEDresp.write("This will not have the previous 'fine' text in it.")return resp
新版本的Piston在返回码上的更改是后端兼容的,因为Piston仅仅是覆写了 __getattr__ 方法以返回一个新实例,而非之前的符号。
================== ================================ ====================== 变量(Variable) 结果(Result) 描述(Description) ================== ================================ ====================== rc.ALL_OK 200 OK 操作成功(Everything went well) rc.CREATED 201 Created 对象创建成功(Object was created) rc.DELETED 204 (Empty body, as per RFC2616) 对象删除成功(Object was deleted) rc.BAD_REQUEST 400 Bad Request 客户端请求错误或无法理解(Request was malformed/not understood) rc.FORBIDDEN 401 Forbidden 权限不足(Permission denied) rc.NOT_FOUND 404 Not Found 资源未找到(Resource not found) rc.DUPLICATE_ENTRY 409 Conflict/Duplicate 对象已存在(Object already exists) rc.NOT_HERE 410 Gone 对象不存在(Object does not exist) rc.NOT_IMPLEMENTED 501 Not Implemented 动作不可用(Action not available) rc.THROTTLED 503 Throttled 客户端请求受限(Request was throttled) ================== ================================ ======================
限制客户端请求(Throttling)
有些场合,我们并不想让用户在短时间内过于频繁地调用某个动作。 这时,可以让Piston在全局的基础层面上限制用户请求,在限制期限内有效地阻止客户端访问。
Piston在使用OAuth的情况下,会利用OAhth在每个消费者应用的底层对客户端访问进行限制。 若没有使用OAuth,受限规则会取决于已登录用户;对于匿名请求,受限规则则取决于客户端IP地址。
限制客户端请求是通过添加@throttle装饰器实现的。该装饰器有两个必填参数和一个可选参数。
第一个参数是某个时间段内允许的请求数量。
第二个参数就是该时间段的时长,以秒为单位。
第三个参数是一个可选的字符串, 会被添加到缓存key中。我们可以利用该参数为一组或一个动作进行限制。不提供该参数的话,Piston默认只对当前动作进行限制。
举个例子::
#!python@throttle(5, 10*60)def create(...):
在这个例子中,客户端在10分钟内调用’create’的次数会限制在5次。
也可以对动作分组限制::
#!python@throttle(5, 10*60, 'user_writes')def create(...):@throttle(5, 10*60, 'user_writes')def update(...):
生成文档(Generating Documentation)
公开发布API时提供相应的API使用文档。写文档本来就很沉闷繁琐,更改代码后再同步更新文档就更是如此。
幸运地是,Piston为此已经做了很多工作,使得我们可以方便地书写文档。
piston.doc 库提供了一组方法,方便我们使用Django的Views和Template生成文档。
generate_doc 函式返回一个 HandlerDocumentation 实例,该实例有下列几个方法::
- .model (get_model) 返回该handler的名称。
 - .doc (get_doc) 返回给定的handler的docstring。
 - .get_methods 返回一组可用的方法列表。该方法接收一个可选的关键字参数 
include_defaults(默认为False),在参数为True且并没有重写默认方法的情况下,该方法的返回列表会包含默认方法。我们想使用默认方法并想将其包含在文档中时,该参数就能派上用场。 
get_methods 包含一系列有趣的 HandlerMethod 。
- .signature (get_signature) 返回 //signature// 方法, 前两个固定参数是Piston自动指定的 ‘self’ 和 ‘request’ 。客户端无须指定这两个参数,所以不必关心。还有一个可选的关键字参数 
parse_optional(默认为 True),用来将默认为None的关键字参数转变成 ““ 。  - .doc (get_doc) 返回某个动作的docstring,所以我们应该保证handler/action都有详细的文档描述。
 - .iter_args() 会返回一个结构为(参数名,默认参数值/None)的二元组。如果默认参数值也是None,该参数值会被转换成字符串’None’。这样做是为了避免和None相混淆.因此我们就能区分返回的默认参数值终究是None还是空。
 
例子::
#!pythonfrom piston.handler import BaseHandlerfrom piston.doc import generate_docclass BlogpostHandler(BaseHandler):model = Blogpostdef read(self, request, post_slug=None):"""Reads all blogposts, or a specific blogpost if`post_slug` is supplied."""...@staticmethoddef resource_uri():return ('api_blogpost_handler', ['id'])doc = generate_doc(BlogpostHandler)print doc.name # -> 'BlogpostHandler'print doc.model # -> <class 'Blogpost'>print doc.resource_uri_template # -> '/api/post/{id}'methods = doc.get_methods()for method in methods:print method.name # -> 'read'print method.signature # -> 'read(post_slug=<optional>)'sig = ''for argn, argdef in method.iter_args():sig += argnif argdef:sig += "=%s" % argdefsig += ', 'sig = sig.rstrip(",")print sig # -> 'read(repo_slug=None)'
资源URL(Resource URIs)
每个资源都可以对应一个URI。调用资源的 .resource_uri() 方法就可以在Handler中访问该资源。
详见 [[FAQ#what-is-a-uri-template|FAQ: What is a URI Template]].
测试(Tests)
Piston的初始测试案例放在在tests/下。要运行该测试需要安装zc.buildout,并使用zc.buildout创建一个Django下的隔离环境。 该案例有两组测试,分别是 tests/bin/test-1.0 和 tests/bin/test-1.1 ,各自对应不同版本的Django。完成下面两个步骤就可以运行测试。
运行测试很简单::
$ python bootstrap.pyCreating directory './bin'.[snip]Generated script './bin/buildout'.$ ./bin/buildout -vDevelop: 'tests/..'Getting distribution for 'djangorecipe'.Got djangorecipe 0.17.3.Getting distribution for 'zc.recipe.egg'.Got zc.recipe.egg 1.2.2.Uninstalling django-1.0.Installing django-1.0.django: Downloading Django from: http://www.djangoproject.com/download/1.0.2/tarball/Generated script './bin/django-1.0'.Generated script './bin/test-1.0'.$ ./bin/test-1.0Creating test database...[snip]...----------------------------------------------------------------------Ran 6 tests in 0.283sOKDestroying test database...
运行buildout时一定要加上-v参数,之所以这么做,是因为不使用 “-v” 参数的话,在django下创建测试脚本时会导致脚本一直挂起。
如果您愿意为Piston贡献力量,欢迎您添加更多测试。因为当前测试仅仅覆盖了基本操作,还有很多欠缺之处。
接收数据(Receiving data)
Piston运行在HTTP协议上,对于post的数据处理得很好,对于其他格式(JSON或YAML)的数据也处理得不错。
Piston既能接收链值对数据,也很容易接收结构化数据。 Piston会尝试通过一组”loader”来反序列化传入的非form数据,这些”loader”取决于客户端指定的内容类型(Content-type)。
举个例子,如果我们给一个handler发送JSON数据,JSON内容类型(Content-Type)是 “application/json” ,Piston会做下面两件事情:
将反序列化后的数据存放在 request.data 中
将 request.content_type 设为 application/json 。对于form提交的数据而言,request.content_type 则总是 None.
可以如下这般使用 (查看  testapp/handlers.py <http://bitbucket.org/jespern/django-piston/src/7042cd328873/tests/test_project/apps/testapp/handlers.py>_)::
#!pythondef create(self, request):if request.content_type:data = request.dataem = self.model(title=data['title'], content=data['content'])em.save()for comment in data['comments']:Comment(parent=em, content=comment['content']).save()return rc.CREATEDelse:super(ExpressiveTestModel, self).create(request)
如果我们传送下列JSON数据,也可以被很好的处理::
#!python{"content": "test", "comments": [{"content": "test1"}, {"content": "test2"}], "title": "test"}
handler接收的任何数据都会被正常的反序列化,所以只要内容一致,无论格式是YAML还是XML,handler都能正常处理。
如果handler不接受客户端发送的数据(可能需要其他格式),可以用 utils.require_mime 装饰器来指定某种数据类型来解决这个问题。
该装饰器可以接收四种数据格式,以其短语名称来指定,分别是 ‘json’, ‘yaml’, ‘xml’ 和 ‘pickle’ 。
比如::
#!pythonclass SomeHandler(BaseHandler):@require_mime('json', 'yaml')def create(...@require_extendeddef update(...
.. _streaming:
流(Streaming)
Piston支持流输出至客户端。不过默认情况下该功能是被禁用的,原因是:
- Django下的流输出会中止 
ConditionalGetMiddleware和CommonMiddleware。 
要绕过这一点不利因素,Piston提供了两个代理中间件(“proxy middleware classes”),它们在流输出的情况下不会运行,因此不会在客户端收到数据之前 查看和截断数据。如果不使用这两个中间件,Django就会跟踪输出的内容(以计算E-Tags和Content-Length),因此会导致随后的接收的peek内容为空。
在 piston.middleware 中有两个类用于替换 ConditionalGetMiddleware 和 CommonMiddleware 。
在 settings.py::
#!pythonMIDDLEWARE_CLASSES = (# ...'piston.middleware.ConditionalMiddlewareCompatProxy','piston.middleware.CommonMiddlewareCompatProxy',# ...)
支除任何对 ConditionalGetMiddleware 和 CommonMiddleware 的引用,或者令这两中间件无效。
若有其他中间件需要在流输出之前查看数据,也要使用代理中间件对其进行封装,如下::
#!pythonfrom piston.middleware import compat_middleware_factoryclass MyMiddleware(...):...MyMiddlewareCompatProxy = compat_middleware_factory(MyMiddleware)
然后在setting.py设置 MyMiddlewareCompatProxy 以取代原有中间件。
配置项(Configuration variables)
Piston 可以使用如下几个配置项来在某些方面做精细地控制,而不必修改代码。
==============================   ==========
Setting                          Meaning
==============================   ==========
settings.PISTON_EMAIL_ERRORS     如果Piston崩溃,是否给管理员发邮件记录回溯信息(类似Django在 DEBUG = True 时会显示调试信息一样)
settings.PISTON_DISPLAY_ERRORS   Piston崩溃之后, 是否在客户端显示简短的回溯信息(包括预期的函数名)
settings.PISTON_STREAM_OUTPUT    为True时,Piston会通知Django将内容流输出至客户端,不过之前请详细阅 :ref:streaming 。
==============================   ==========
