作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
莱安德罗·利马的头像

Leandro Lima

Leandro有15年以上的IT/开发经验. 自2013年开始使用Python,他喜欢构建高效且具有成本效益的系统.

Expertise

Previously At

Embraer
Share

REST api已经成为web之间建立接口的常用方法 back-ends and front-ends,以及在不同的web服务之间. 这种界面的简单性, 以及跨不同网络和框架的无处不在的HTTP和HTTPS协议支持, 使其成为考虑互操作性问题时的一个简单选择.

Bottle 是一个极简的Python web框架. 它轻量级、快速且易于使用,非常适合构建RESTful服务. A 基本的比较 made by 安德烈Kornatskyy 在响应时间和吞吐量(每秒请求数)方面,将其列入前三大框架之列。. 在我自己对DigitalOcean提供的虚拟服务器的测试中, 我发现uWSGI服务器堆栈和Bottle的组合可以实现每个请求低至140μs的开销.

在本文中,我将提供如何使用Bottle构建RESTful API服务的演练.

瓶子:一个快速轻量级的Python Web框架

安装与配置

瓶子框架实现其令人印象深刻的性能,部分原因是由于它的重量轻. 事实上,整个库作为一个单文件模块分发. 这意味着它不像其他框架那样对您有用, 但它也更灵活,可以适应许多不同的技术堆栈. 因此,Bottle最适合于性能和可定制性非常重要的项目, 在这种情况下,重型框架节省时间的优势较少被考虑.

Bottle的灵活性使得对建立平台的深入描述有点徒劳, 因为它可能不反映您自己的堆栈. However, 选项的快速概述, 以及在哪里了解更多关于如何设置它们的信息, 在这里是合适的:

Installation

安装Bottle和安装其他Python包一样简单. 您的选择是:

  • 使用系统的包管理器在您的系统上安装. Debian Jessie(当前稳定版)将版本0打包.12 as python-bottle.
  • 使用Python包索引在您的系统上安装 PIP安装瓶.
  • 在虚拟环境中安装(推荐).

要在虚拟环境中安装Bottle,您需要 virtualenv and pip tools. 要安装它们,请参考 virtualenv and pip 文档,尽管您的系统中可能已经有了.

在Bash中,用Python 3创建一个环境:

$ virtualenv -p ' which python3

抑制 -p ' which python3 ' 参数将导致安装系统上的默认Python解释器-通常是Python 2.7. Python 2.支持Python 7,但本教程假设使用Python 3.4.

现在激活环境并安装Bottle:

$ . env / bin /激活
安装瓶子

That’s it. 瓶子已经安装好,可以使用了. 如果你不熟悉 virtualenv or pip,他们的文档是一流的. Take a look! 它们是非常值得的.

Server

瓶符合Python标准 Web服务器网关接口(WSGI)这意味着它可以与任何符合wsgi的服务器一起使用. This includes uWSGI, Tornado, Gunicorn, Apache, 亚马逊豆茎, b谷歌应用引擎, and others.

每个环境的正确设置方法略有不同. Bottle公开了一个符合WSGI接口的对象, 服务器必须配置为与该对象交互.

要了解更多关于如何设置服务器的信息,请参考服务器的文档,以及Bottle的文档。 here.

Database

Bottle与数据库无关,并不关心数据来自何处. 如果你想在应用中使用数据库, Python包索引 有几个有趣的选择,比如 SQLAlchemy, PyMongo, MongoEngine, CouchDB and Boto for DynamoDB. 您只需要适当的适配器就可以让它与您选择的数据库一起工作.

瓶子框架基础

现在,让我们看看如何在Bottle中制作一个基本的应用程序. For code examples, I will assume Python >= 3.4. 然而,我在这里写的大部分内容将在Python 2上运行.7 as well.

一个基本的应用程序在瓶子看起来像这样:

import bottle

App = application = bottle.default_app()

如果__name__ == '__main__':
    bottle.运行(host = '127 ').0.0.1',端口= 8000)

当我说基本的时候,我的意思是这个程序甚至不会“Hello World”你. (您最后一次访问回答“Hello World”的REST接口是什么时候?)的所有HTTP请求 127.0.0.1:8000 会收到404 Not Found响应状态吗.

Apps in Bottle

Bottle可能创建了多个应用实例, but for the sake of convenience the first instance is created for you; that’s the default app. Bottle将这些实例保存在模块内部的堆栈中. 当你使用Bottle做一些事情时(比如运行应用程序或附加路由),不要指定你在谈论哪个应用程序, 它指的是默认应用. In fact, the App = application = bottle.default_app() Line甚至不需要存在于这个基本的应用程序中, 但它在那里,所以我们可以很容易地调用默认应用程序与Gunicorn, uWSGI或一些通用的WSGI服务器.

多应用程序的可能性一开始可能会让人感到困惑,但它们增加了Bottle的灵活性. 用于应用程序的不同模块, 你可以通过实例化其他的Bottle类来创建专门的Bottle应用,并根据需要用不同的配置来设置它们. 这些不同的应用程序可以通过不同的URL访问,通过Bottle的URL路由器. 我们不会在本教程中对此进行深入研究, 但是我们鼓励你去看一下Bottle的文档 here and here.

服务器调用

脚本的最后一行使用指定的服务器运行Bottle. 如果没有指定服务器, 这里的情况也是如此, 默认服务器是Python内置的WSGI参考服务器, 哪一个只适合发展. 可以这样使用不同的服务器:

bottle.运行(server='gunicorn', host =' 127 ').0.0.1',端口= 8000)

这是语法糖,让我们通过运行这个脚本来启动应用程序. 例如,如果这个文件命名为 main.py,你可以简单地跑 python main.py 要启动应用程序. Bottle carries 相当广泛的服务器适配器列表 可以这样用.

一些WSGI服务器没有Bottle适配器. 这些可以用服务器自己的运行命令启动. 例如,在uWSGI上,您所要做的就是调用 uwsgi like this:

$ uwsgi——http:8000——wsgi-file main.py

文件结构说明

Bottle让你的应用程序的文件结构完全由你决定. 我发现我的文件结构策略因项目而异, 但往往是基于MVC的哲学.

构建REST API

当然,没有人需要对每个请求的URI只返回404的服务器. 我已经向您保证我们将构建一个REST API,所以让我们开始吧.

如果你愿意的话 构建一个接口 它操作一组名称. 在真正的应用中,你可能会使用数据库, 但是在这个例子中,我们只使用内存 set data structure.

API的框架可能是这样的. 您可以将此代码放置在项目中的任何位置, 但我的建议是一个单独的API文件, such as api/names.py.

从瓶子进口请求,响应
从瓶子进口后,取,放,删除

_names = set() #名称集合

@post(' /名称”)
def creation_handler ():
    "处理名称创建"
    pass

@get('/names')
def listing_handler ():
    "处理名单"
    pass

@put('/names/')
def update_handler(名称):
    "处理姓名更新"
    pass

@delete('/names/')
def delete_handler(名称):
    "处理姓名删除"
    pass

Routing

正如我们所看到的,在Bottle中的路由是使用装饰器完成的. 进口的装饰师 post, get, put, and delete 为这四个操作注册处理程序. 了解这些工作如何分解如下:

  • 的快捷方式 default_app 路由修饰符. 例如, @get() 装饰应用 bottle.default_app().get() to the handler.
  • 上的路由方法 default_app 都是捷径吗? route(). So default_app().get('/') 等于 default_app().路线(方法=‘得到’,‘/’).

So @get('/') is the same as @route(方法=‘得到’,‘/’),等于 @bottle.default_app().路线(方法=‘得到’,‘/’),这两个词可以互换使用.

有一点很有用 @route 如果你愿意,装饰器就是这个, for example, 使用相同的处理程序来处理对象更新和删除, 你可以像这样传递一个方法列表:

@route('/names/', method=['PUT', 'DELETE'])
def update_delete_handler(名称):
    "处理姓名的更新和删除"
    pass

那么,让我们实现其中一些处理程序.

使用Bottle Framework创建完美的REST API.

RESTful api是现代web开发的主要内容. 为您的API客户端提供一个带有Bottle后端的强大混合物.

POST:资源创建

我们的POST处理程序可能看起来像这样:

导入re, json

nampattern = re.编译(r ' ^ [a-zA-Z \ d]{64}“美元)

@post(' /名称”)
def creation_handler ():
    "处理名称创建"

    try:
        #解析输入数据
        try:
            data = request.json()
        except:
            提高ValueError

        如果data为None:
            提高ValueError

        #提取并验证名称
        try:
            if namepattern.match(data['name'])是None:
                提高ValueError
            Name = data[' Name ']
        except (TypeError, KeyError):
            提高ValueError

        #检查是否存在
        如果name在_names中:
            raise KeyError

    除了ValueError:
        #如果错误的请求数据,返回400错误请求
        response.status = 400
        return
    
    除了KeyError:
        #如果name已经存在,返回409
        response.status = 409
        return

    # add name
    _names.add(name)
    
    # return 200 Success
    response.headers['Content-Type'] = 'application/json'
    return json.转储({“名称”:名称})

嗯,那是相当多的. 让我们一步一步地回顾这些步骤.

Body Parsing

这个API要求用户在主体处POST一个JSON字符串,属性名为“name”。.

The request 先前从 bottle 总是指向当前请求并保存请求的所有数据. Its body 属性包含请求体的字节流, 它可以被任何能够读取流对象(如读取文件)的函数访问。.

The request.json() 方法检查请求的报头是否为“application/json”内容类型,如果内容类型正确,则解析报头. 如果瓶子检测到一个畸形的身体(e.g.:空或内容类型错误),此方法返回 None 因此我们提出a ValueError. If malformed JSON content is detected by the JSON parser; it raises an exception that we catch and reraise, again as a ValueError.

对象解析和验证

如果没有错误,我们已经将请求体转换为Python对象 data variable. 如果我们接收到一个带有“name”键的字典,我们就可以通过 data['name']. 如果我们接收到一个没有这个键的字典,尝试访问它将会把我们引向 KeyError exception. 如果我们收到的不是字典,我们会得到 TypeError exception. 如果这些错误中的任何一个发生,我们再一次将其提升为a ValueError,表示输入错误.

要检查name键是否具有正确的格式,我们应该针对regex掩码进行测试,例如 namepattern 我们在这里创建的蒙版. If the key name isn’t a string, namepattern.match() will raise a TypeError,如果不匹配,它将返回 None.

使用本例中的掩码, 名称必须是ASCII字母数字,不能有空格,长度范围是1 ~ 64个字符. 这是一个简单的验证,例如,它不会测试具有垃圾数据的对象. 更复杂和完整的验证可以通过使用诸如 FormEncode.

存在性检验

在完成请求之前的最后一个测试是给定的名称是否已经存在于集合中. 在一个更结构化的应用中, 该测试可能应该由专用模块完成,并通过专用异常向我们的API发出信号, 但是由于我们是直接操作一个集合, 我们必须在这里做.

我们通过引发a来表示名称的存在 KeyError.

Error Responses

就像请求对象保存所有请求数据一样, 响应对象对响应数据执行相同的操作. 设置响应状态有两种方式:

response.status = 400

and:

response.status = '400错误请求'

对于我们的例子, 我们选择了更简单的形式, 但是第二种形式可以用来指定错误的文本描述. 在内部,Bottle将拆分第二个字符串并适当地设置数字代码.

成功响应

如果所有步骤都成功,我们通过向集合添加名称来完成请求 _names, setting the Content-Type 响应头,并返回响应. 函数返回的任何字符串都将被视为a的响应体 200 Success 响应,所以我们简单地生成一个 json.dumps.

GET:资源列表

从名称创建开始,我们将实现名称列表处理程序:

@get('/names')
def listing_handler ():
    "处理名单"

    response.headers['Content-Type'] = 'application/json'
    response.headers['Cache-Control'] = 'no-cache'
    return json.转储({“名称”:列表(_names)})

列名字就简单多了,不是吗? 与名称创建相比,这里没有太多要做的事情. 只需设置一些响应头并返回所有名称的JSON表示形式,就完成了.

PUT:资源更新

现在,让我们看看如何实现更新方法. 它与create方法没有太大的不同,但是我们使用这个示例来引入URI参数.

@put('/names/')
def update_handler(名称):
    "处理姓名更新"

    try:
        #解析输入数据
        try:
            data = json.负载(utf8reader(请求.body))
        except:
            提高ValueError

        #提取并验证新名称
        try:
            if namepattern.match(data['name'])是None:
                提高ValueError
            NewName = data[' Name ']
        except (TypeError, KeyError):
            提高ValueError

        检查更新后的名称是否存在
        如果oldname不在_names中:
            提高KeyError (404)

        #检查新名称是否存在
        如果name在_names中:
            提高KeyError (409)

    除了ValueError:
        response.status = 400
        return
    除KeyError外:
        response.status = e.args[0]
        return

    #添加新名称,删除旧名称
    _names.删除(oldname)
    _names.add(newname)

    # return 200 Success
    response.headers['Content-Type'] = 'application/json'
    return json.转储({“名称”:新名称})

更新操作的主体模式与创建操作的主体模式相同, 但现在我们还有一个新的 oldname 参数,由路由定义 @put('/names/').

URI Parameters

如您所见,Bottle对URI参数的表示法非常简单. 您可以使用任意多的参数构建uri. Bottle自动从URI中提取它们并将它们传递给请求处理程序:

@get('//')
Def handler(param1, param2):
    pass

使用级联路由装饰器,你可以构建带有可选参数的uri:

@get('/')
@get('//')
def handler(param1, param2 = None)
    pass

另外,Bottle允许在uri中使用以下路由过滤器:

  • int

只匹配可转换为的参数 int,并将转换后的值传递给处理程序:

@get('/')
def处理程序(参数):
    pass
  • float

The same as int,但使用浮点值:

@get('/')
def处理程序(参数):
    pass
  • re (正则表达式)

只匹配与给定正则表达式匹配的参数:

@get('/')
def处理程序(参数):
    pass
  • path

以灵活的方式匹配URI路径的子段:

@get('//id>')
def处理程序(参数):
    pass

Matches:

  • /x/id, passing x as param.
  • /x/y/id, passing x/y as param.

DELETE:资源删除

与GET方法一样,DELETE方法带给我们的消息也很少. 只要注意返回 None 在不设置状态的情况下,返回一个带有空体和200状态码的响应.

@delete('/names/')
def delete_handler(名称):
    "处理姓名更新"

    try:
        检查name是否存在
        如果name不在_names中:
            raise KeyError
    除了KeyError:
        response.status = 404
        return

    # Remove name
    _names.remove(name)
    return

最后一步:激活API

假设我们把名字保存为 api/names.py ,我们现在可以在主应用程序文件中启用这些路由 main.py.

import bottle
从API导入名称

App = application = bottle.default_app()

如果__name__ == '__main__':
    bottle.运行(host = '127 ').0.0.1',端口= 8000)

注意,我们只导入了 names module. 因为我们已经用附加到默认应用的uri装饰了所有的方法, 不需要做任何进一步的设置. 我们的方法已经就绪,可以访问了.

没有什么比制作精良的REST API更能让前端满意的了. 就像魔法一样!

您可以使用Curl或Postman之类的工具来使用API并手动测试它. (如果您正在使用Curl,则可以使用 JSON formatter 使回复看起来不那么杂乱.)

奖励:跨域资源共享(CORS)

构建REST API的一个常见原因是通过AJAX与JavaScript前端通信. 对于某些应用程序, 应该允许这些请求来自任何域, 而不仅仅是API的主域. 默认情况下,大多数浏览器不允许此行为,因此让我向您展示如何进行设置 跨域资源共享(CORS) 在瓶中允许:

从瓶子进口钩子,路线,响应

_allow_origin = '*'
_allow_methods = 'PUT, GET, POST, DELETE, OPTIONS'
_allow_headers = 'Authorization, Origin, Accept, Content-Type, X-Requested-With'

@hook(“after_request”)
def enable_cors ():
    " '添加标题以启用CORS' "

    response.headers['Access-Control-Allow-Origin'] = _allow_origin .
    response.headers['Access-Control-Allow-Methods'] = _allow_methods
    response.headers['Access-Control-Allow-Headers'] = _allow_headers .

@route('/', method = 'OPTIONS')
@route('/', method = 'OPTIONS')
def options_handler(path = None):
    return

The hook Decorator允许我们在每个请求之前或之后调用函数. 在我们的示例中,要启用CORS,必须设置 Access-Control-Allow-Origin, -Allow-Methods and -Allow-Headers 每个响应的标头. 这些向请求者表明,我们将为指定的请求提供服务.

Also, 客户端可以向服务器发出OPTIONS HTTP请求,看看它是否真的可以用其他方法发出请求. 下面是一个包罗万象的示例, 我们响应与200状态码和空体的所有选项请求.

要启用此功能,只需保存它并从主模块导入它.

Wrap Up

这就是它的全部!

在本教程中, 我已经尝试介绍了使用Bottle web框架为Python应用程序创建REST API的基本步骤.

您可以通过访问它的 tutorial and API参考文档.

就这一主题咨询作者或专家.
预约电话
莱安德罗·利马的头像
Leandro Lima

Located in 圣保罗州 -巴西圣保罗州

Member since 2015年11月19日

作者简介

Leandro有15年以上的IT/开发经验. 自2013年开始使用Python,他喜欢构建高效且具有成本效益的系统.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Expertise

Previously At

Embraer

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal开发者

加入总冠军® community.