用python 和 flask 建立Web API 的简单入门

本文通过学习 https://programminghistorian.org/en/lessons/creating-apis-with-python-and-flask 而来,例子和数据库例子也是来自该文。

本文先介绍api的使用,然后通过几个例子一步步解说rest api 的设计。例子包括:第一个基本例子,有返回的例子,带参数查询的例子,数据库查询的例子。理解这几个例子,你就入门了。

理解api

下面是使用api的例子:

http://chroniclingamerica.loc.gov/search/pages/results/?format=json&proxtext=fire

这个返回一个json 数据集。

这里包括以下三个部分。

基本的url 部分是

http://chroniclingamerica.loc.gov

 路径是:

/search/pages/results/

参数部分是:

?format=json&proxtext=fire

准备

安装好python 3 和 flask

pip install flask

建立一个目录projects,然后在这个目录下建立一个api的目录。

第一个例子

在这个目录下建立一个api.py 的python文件,内容如下:

import flask

app = flask.Flask(__name__)
app.config["DEBUG"] = True


@app.route('/', methods=['GET'])
def home():
    return "<h1>Distant Reading Archive</h1><p>This site is a prototype API for distant reading of science fiction novels.</p>"

app.run()

在命令行下执行python api.py

可以看到类似下面的提示内容:

* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

按照这个提示在浏览器里输入 http://127.0.0.1:5000/ 就可以看到网页:

这是显示的第一个界面。

有数据返回的例子

下面添加一点内容,增加返回一个数据表这个api。更改这个文件的代码如下:

import flask
from flask import request, jsonify

app = flask.Flask(__name__)
app.config["DEBUG"] = True

# Create some test data for our catalog in the form of a list of dictionaries.
books = [
    {'id': 0,
     'title': 'A Fire Upon the Deep',
     'author': 'Vernor Vinge',
     'first_sentence': 'The coldsleep itself was dreamless.',
     'year_published': '1992'},
    {'id': 1,
     'title': 'The Ones Who Walk Away From Omelas',
     'author': 'Ursula K. Le Guin',
     'first_sentence': 'With a clamor of bells that set the swallows soaring, the Festival of Summer came to the city Omelas, bright-towered by the sea.',
     'published': '1973'},
    {'id': 2,
     'title': 'Dhalgren',
     'author': 'Samuel R. Delany',
     'first_sentence': 'to wound the autumnal city.',
     'published': '1975'}
]


@app.route('/', methods=['GET'])
def home():
    return '''<h1>Distant Reading Archive</h1>
<p>A prototype API for distant reading of science fiction novels.</p>'''


# A route to return all of the available entries in our catalog.
@app.route('/api/v1/resources/books/all', methods=['GET'])
def api_all():
    return jsonify(books)

app.run()

 同样运行python api.py,当然开始要ctrl +c 关闭上个运行的代码。

这次我们浏览器输入的是:

http://127.0.0.1:5000/api/v1/resources/books/all

在浏览器中看到这三个条目的JSON输出。 Flask提供了一个jsonify函数,该函数允许将列表和字典转换为JSON格式。 在程序中,书籍条目从Python词典列表转换为JSON,然后返回给用户。

至此,已经创建了一个有效的API。 在下一部分中,将允许用户通过更具体的数据(例如条目的ID)查找图书。

有具体数据查询的例子

继续修改api.py内容为:

import flask
from flask import request, jsonify

app = flask.Flask(__name__)
app.config["DEBUG"] = True

# Create some test data for our catalog in the form of a list of dictionaries.
books = [
    {'id': 0,
     'title': 'A Fire Upon the Deep',
     'author': 'Vernor Vinge',
     'first_sentence': 'The coldsleep itself was dreamless.',
     'year_published': '1992'},
    {'id': 1,
     'title': 'The Ones Who Walk Away From Omelas',
     'author': 'Ursula K. Le Guin',
     'first_sentence': 'With a clamor of bells that set the swallows soaring, the Festival of Summer came to the city Omelas, bright-towered by the sea.',
     'published': '1973'},
    {'id': 2,
     'title': 'Dhalgren',
     'author': 'Samuel R. Delany',
     'first_sentence': 'to wound the autumnal city.',
     'published': '1975'}
]


@app.route('/', methods=['GET'])
def home():
    return '''<h1>Distant Reading Archive</h1>
<p>A prototype API for distant reading of science fiction novels.</p>'''


@app.route('/api/v1/resources/books/all', methods=['GET'])
def api_all():
    return jsonify(books)


@app.route('/api/v1/resources/books', methods=['GET'])
def api_id():
    # Check if an ID was provided as part of the URL.
    # If ID is provided, assign it to a variable.
    # If no ID is provided, display an error in the browser.
    if 'id' in request.args:
        id = int(request.args['id'])
    else:
        return "Error: No id field provided. Please specify an id."

    # Create an empty list for our results
    results = []

    # Loop through the data and match results that fit the requested ID.
    # IDs are unique, but other fields might return many results
    for book in books:
        if book['id'] == id:
            results.append(book)

    # Use the jsonify function from Flask to convert our list of
    # Python dictionaries to the JSON format.
    return jsonify(results)

app.run()

同样启动python api.py

在浏览器分别输入:

127.0.0.1:5000/api/v1/resources/books?id=0 

127.0.0.1:5000/api/v1/resources/books?id=1 

127.0.0.1:5000/api/v1/resources/books?id=2 

127.0.0.1:5000/api/v1/resources/books?id=3

这些书中的每一个都应返回不同的条目,最后一个除外,后者应返回一个空列表:[],因为没有书的id值为3。(编程中的计数通常从0开始,因此id = 3 将要求不存在的第四项。)在下一部分中,我们将更详细地探讨更新的API。

理解更新的API代码

在代码中,首先使用@ app.route语法创建一个名为api_id的新函数,该语法将该函数映射到路径/ api / v1 / resources / books。 这意味着当我们访问http://127.0.0.1:5000/api/v1/resources/books时,此功能将运行。 请注意,在不提供ID的情况下访问此链接将给出错误消息提示:

return "Error: No id field provided. Please specify an id."

在函数内部,做两件事:

首先,检查提供的URL以获取ID,然后选择与该ID相匹配的图书。 必须按以下方式提供ID:?id = 0。 像这样通过URL传递的数据(在?之后)称为查询参数-是HTTP的功能,用于过滤特定类型的数据。

这部分代码确定是否存在查询参数(例如?id = 0),然后将提供的ID分配给变量。

  if 'id' in request.args:
        id = int(request.args['id'])
    else:
        return "Error: No id field provided. Please specify an id."

接下来本部分将遍历书籍测试目录,匹配给定ID的书籍,将其附加到将返回给用户的列表中:

for book in books:
        if book['id'] == id:
            results.append(book)

最后 return jsonify(results) 将获取结果列表返回浏览器中并呈现为JSON。

到目前为止,创建了一个实际的API。 最后,将建立一个使用数据库的更为复杂的API,但是到目前为止,我们所使用的大多数原理和模式仍然适用。 

包含数据库的例子

rest API的最后一个示例从数据库中提取数据,实现错误处理,并可以按出版日期过滤书籍。 使用的数据库是SQLite,默认情况下Python支持轻量级数据库引擎。 SQLite文件通常以.db文件扩展名结尾。

在修改代码之前,请先从此位置下载示例数据库,也可以这里下载数据库(14k) ,然后使用图形用户界面将文件复制到api文件夹。 将结果返回给用户时,API的最终版本将查询该数据库。

将以下代码复制到您的文本编辑器中。 然后保存为 api_final.py

import flask
from flask import request, jsonify
import sqlite3

app = flask.Flask(__name__)
app.config["DEBUG"] = True

def dict_factory(cursor, row):
    d = {}
    for idx, col in enumerate(cursor.description):
        d[col[0]] = row[idx]
    return d


@app.route('/', methods=['GET'])
def home():
    return '''<h1>Distant Reading Archive</h1>
<p>A prototype API for distant reading of science fiction novels.</p>'''


@app.route('/api/v1/resources/books/all', methods=['GET'])
def api_all():
    conn = sqlite3.connect('books.db')
    conn.row_factory = dict_factory
    cur = conn.cursor()
    all_books = cur.execute('SELECT * FROM books;').fetchall()

    return jsonify(all_books)



@app.errorhandler(404)
def page_not_found(e):
    return "<h1>404</h1><p>The resource could not be found.</p>", 404


@app.route('/api/v1/resources/books', methods=['GET'])
def api_filter():
    query_parameters = request.args

    id = query_parameters.get('id')
    published = query_parameters.get('published')
    author = query_parameters.get('author')

    query = "SELECT * FROM books WHERE"
    to_filter = []

    if id:
        query += ' id=? AND'
        to_filter.append(id)
    if published:
        query += ' published=? AND'
        to_filter.append(published)
    if author:
        query += ' author=? AND'
        to_filter.append(author)
    if not (id or published or author):
        return page_not_found(404)

    query = query[:-4] + ';'

    conn = sqlite3.connect('books.db')
    conn.row_factory = dict_factory
    cur = conn.cursor()

    results = cur.execute(query, to_filter).fetchall()

    return jsonify(results)

app.run()

现在运行 python api_final.py

在浏览器中输入以下做一下测试:

http://127.0.0.1:5000/api/v1/resources/books/all 

http://127.0.0.1:5000/api/v1/resources/books?author=Connie+Willis 

http://127.0.0.1:5000/api/v1/resources/books?author=Connie+Willis&published=1999 

http://127.0.0.1:5000/api/v1/resources/books?published=2010

理解数据库驱动的API

关系数据库允许存储和检索存储在表中的数据。 表格与电子表格类似,因为它们具有列和行-列指示数据代表什么,例如“标题”或“日期”。 行代表单个条目,可以是书籍,用户,交易或任何其他类型的实体。

使用的数据库有五列idpublishedauthortitle和first_sentence。 每行代表一本书,该书在当年出版的标题下赢得了年度雨果奖,其文本以first_sentence列中的句子开头。

api_all函数不是使用应用程序中定义的测试数据,而是从数据库中提取数据:

def api_all():
    conn = sqlite3.connect('books.db')
    conn.row_factory = dict_factory
    cur = conn.cursor()
    all_books = cur.execute('SELECT * FROM books;').fetchall()

    return jsonify(all_books)

api_all请求返回所以书的信息。

首先,使用sqlite3库连接到数据库。表示与数据库的连接的对象已绑定到conn变量。 conn.row_factory = dict_factory这一行使连接对象知道使用我们定义的dict_factory函数,该函数从数据库中将其返回为字典而不是列表,当将它们输出为JSON时,它们会更好地工作。然后,我们创建一个游标对象(cur = conn.cursor()),该对象实际上是在数据库中移动以提取数据的对象。最后,我们使用cur.execute方法执行一个SQL查询,以从数据库的books表中提取所有可用数据(*)。在我们的函数结束时,此数据以JSON的形式返回:jsonify(all_books)。注意,返回数据的另一个函数api_filter将使用类似的方法从数据库中提取数据。

page_not_found函数的目的是创建一个错误页面,供用户在遇到错误或输入未定义的路线时看到:

@app.errorhandler(404)
def page_not_found(e):
    return "<h1>404</h1><p>The resource could not be found.</p>", 404

api_filter函数是对先前的api_id函数的改进,该函数根据其ID返回一本书。 此新功能允许按三个不同的字段进行过滤:id,published和author。 该函数首先获取URL中提供的所有查询参数(请记住,查询参数是URL中位于?之后的部分,例如?id = 10)。

query_parameters = request.args

然后,它提取受支持的参数id,published和author,并将它们绑定到适当的变量:

id = query_parameters.get('id')
published = query_parameters.get('published')
author = query_parameters.get('author')

接下来构建SQL查询,该查询将用于在数据库中查找所需的信息。 用于在数据库中查找数据的SQL查询采用以下形式:

SELECT <columns> FROM <table> WHERE <column=match> AND <column=match>;

为了获得正确的数据,需要构建一个类似于上面的SQL查询,并构建一个包含将要匹配的过滤器的列表。 结合使用查询和用户提供的过滤器,可以从数据库中提取正确的图书。

开始定义查询和过滤器列表:

query = "SELECT * FROM books WHERE"
to_filter = []

然后如果提供id,published或author作为查询参数,则将它们添加到查询和过滤器列表中:

  if id:
        query += ' id=? AND'
        to_filter.append(id)
    if published:
        query += ' published=? AND'
        to_filter.append(published)
    if author:
        query += ' author=? AND'
        to_filter.append(author)

如果用户未提供这些查询参数,则没有任何内容可显示,将其发送到“ 404 Not Found”页面:

if not (id or published or author):
        return page_not_found(404)

为了完善sql查询,删除了query尾随的AND,并加上;

query = query[:-4] + ';'

就像在api_all函数中那样连接到数据库,然后执行使用过滤器列表构建的查询:

conn = sqlite3.connect('books.db')
conn.row_factory = dict_factory
cur = conn.cursor()

results = cur.execute(query, to_filter).fetchall()

最后以json 格式返回sql执行结果给用户。

猜你喜欢

转载自blog.csdn.net/leon_zeng0/article/details/105717691
今日推荐