博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用Flask构建RESTful API – TDD方法
阅读量:2511 次
发布时间:2019-05-11

本文共 29986 字,大约阅读时间需要 99 分钟。

Great things are done by a series of small things brought together – Vincent Van Gogh

伟大的事情由一系列小事情共同完成 – Vincent Van Gogh

This article's intention is to provide a easy-to-follow project-based process on how to create a RESTful API using the Flask framework.

本文旨在提供一个易于遵循的基于项目的过程,说明如何使用Flask框架创建RESTful API。

为什么选择烧瓶? (Why Flask?)

A bit of context – I've written a bunch of articles on Django-driven RESTful APIs. Though a great resource for Django enthusiasts, not everyone wants to code in Django. Besides, it's always good to acquaint yourself with other frameworks.

一些背景信息–我写了很多关于Django驱动的RESTful API的文章。 尽管这对Django爱好者来说是一个很好的资源,但并不是每个人都想在Django中进行编码。 此外,熟悉其他框架始终是一件好事。

Learning Flask is easier and faster. It's super easy to setup and get things running. Unlike Django (which is heavier), you'll never have functionality lying around that you aren't using.

学习Flask更容易,更快捷。 设置和运行程序超级容易。 与Django(较重)不同,您永远不会拥有不使用的功能。

Typical of all our web apps, we'll use the TDD approach. It's really simple. Here's how we do Test Driven Development:

在所有网络应用程序中,典型的做法是使用TDD方法。 真的很简单。 这是我们进行测试驱动开发的方法:

  • Write a test. – The test will help flesh out some functionality in our app

    编写测试。 –测试将有助于充实我们应用程序中的某些功能
  • Then, run the test – The test should fail, since there's no code(yet) to make it pass.

    然后,运行测试–测试应该失败,因为还没有代码可以通过。
  • Write the code – To make the test pass

    编写代码–通过测试
  • Run the test – If it passes, we are confident that the code we've written meets the test requirements

    运行测试–如果通过,我们有信心我们编写的代码符合测试要求
  • Refactor code – Remove duplication, prune large objects and make the code more readable. Re-run the tests every time we refactor our code

    重构代码–删除重复项,修剪大对象并使代码更具可读性。 每次我们重构代码时都重新运行测试
  • Repeat – That's it!

    重复–就这样!

( )

We're going to develop an API for a bucketlist. A bucketlist is a list of all the goals you want to achieve, dreams you want to fulfill and life experiences you desire to experience before you die (or hit the bucket). The API shall therefore have the ability to:

我们将为存储清单开发API。 遗愿清单是您要实现的所有目标,想要实现的梦想以及想要在去世(或遭受打击)之前经历的生活经验的列表。 因此,API应具有以下能力:

  • Create a bucketlist (by giving it a name/title)

    创建存储区列表(通过为其命名/标题)
  • Retrieve an existing bucketlist

    检索现有的存储桶列表
  • Update it (by changing it's name/title)

    更新(通过更改名称/标题)
  • Delete an existing bucketlist

    删除现有的存储区列表

先决条件 (Prerequisites)

  • - A programming language that lets us work more quickly (The universe loves speed!).

    一种编程语言,它使我们可以更快地工作(Universe喜欢速度!)。
  • - A microframework for Python based on Werkzeug, Jinja 2 and good intentions

    基于Werkzeug,Jinja 2和良好意图的Python微框架
  • - A tool to create isolated virtual environments

    创建隔离虚拟环境的工具

Let's start with configuring our Flask app structure!

让我们从配置Flask应用程序结构开始!

( )

First, we'll create our application directory. On the terminal, create an empty directory called bucketlist with mkdir bucketlist. Then, Cd into the directory. Create an isolated virtual environment:

首先,我们将创建应用程序目录。 在终端上,使用mkdir bucketlist创建一个名为bucketlist的空目录。 然后,将Cd放入目录。 创建一个隔离的虚拟环境:

$ virtualenv  venv

Install globally using pip install autoenv Here's why – Autoenv helps us to set commands that will run every time we cd into our directory. It reads the .env file and executes for us whatever is in there.

使用pip install autoenv 全局 pip install autoenv的原因-Autoenv帮助我们设置每次cd进入目录时都会运行的命令。 它读取.env文件并为我们执行其中的任何内容。

Create a .env file and add the following:

创建一个.env文件并添加以下内容:

source env/bin/activateexport FLASK_APP="run.py"export SECRET="some-very-long-string-of-random-characters-CHANGE-TO-YOUR-LIKING"export APP_SETTINGS="development"export DATABASE_URL="postgresql://localhost/flask_api"

The first line activates our virtual environment venv that we just created. Line 2, 3 and 4 export our FLASK_APP, SECRET, APP_SETTINGS and DATABASE_URL variables. We'll integrate these variables as we progress through the development process.

第一行激活我们刚刚创建的虚拟环境venv 。 第2、3和4行导出我们的FLASK_APP, SECRET, APP_SETTINGS and DATABASE_URL变量。 在开发过程中,我们将整合这些变量。

Run the following to update and refresh your .bashrc:

运行以下命令以更新和刷新.bashrc:

$echo "source `which activate.sh`" >> ~/.bashrc$ source ~/.bashrc

You will see something like this on your terminal

您会在终端上看到类似的内容

Sometimes autoenv might not work if you have installed. A good workaround would be to simply source the .env file and we are set.

如果安装了有时autoenv可能无法正常工作。 一个好的解决方法是简单地获取.env文件,然后设置好。

$source .env

Conversely, if you don't want to automate things for the long run, you don't have to use autoenv. A simple export directly from the terminal would do.

相反,如果您不想长期实现自动化,则不必使用autoenv 。 直接从终端进行简单导出即可。

$export FLASK_APP="run.py"$ export APP_SETTINGS="development"$ export SECRET="a-long-string-of-random-characters-CHANGE-TO-YOUR-LIKING"$ export DATABASE_URL="postgresql://localhost/flask_api"

Inside our virtual environment, we'll create a bunch of files to lay out our app directory stucture. Here's what it should look like:

在我们的虚拟环境中,我们将创建一堆文件来布置我们的应用程序目录结构。 它应该是这样的:

├── bucketlist(this is the directory we cd into)    ├── app    │   ├── __init__.py    │   └── models.py      ├── instance    │   └── __init__.py    ├── manage.py    ├── requirements.txt    ├── run.py    └── test_bucketlist.py

After doing this, install Flask using .

完成之后,使用安装Flask。

(venv)$ pip install flask

( )

Flask needs some sought of configuration to be available before the app starts. Since environments (development, production or testing) require specific settings to be configured, we'll have to set environment-specific things such as a secret key, debug mode and test mode in our configurations file.

在应用启动之前,Flask需要一些寻求的配置才可用。 由于环境(开发,生产或测试)需要配置特定的设置,因此我们必须在配置文件中设置特定于环境的内容,例如secret keydebug modetest mode

If you haven't already, create a directory and call it instance. Inside this directory, create a file called config.py and also init.py. Inside our config file, we'll add the following code:

如果尚未创建目录,则将其命名为instance 。 在这个目录里,创建一个名为config.py初始化的.py。 在我们的配置文件中,我们将添加以下代码:

# /instance/config.pyimport osclass Config(object):    """Parent configuration class."""    DEBUG = False    CSRF_ENABLED = True    SECRET = os.getenv('SECRET')    SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL')class DevelopmentConfig(Config):    """Configurations for Development."""    DEBUG = Trueclass TestingConfig(Config):    """Configurations for Testing, with a separate test database."""    TESTING = True    SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/test_db'    DEBUG = Trueclass StagingConfig(Config):    """Configurations for Staging."""    DEBUG = Trueclass ProductionConfig(Config):    """Configurations for Production."""    DEBUG = False    TESTING = Falseapp_config = {
'development': DevelopmentConfig, 'testing': TestingConfig, 'staging': StagingConfig, 'production': ProductionConfig,}

The Config class contains the general settings that we want all environments to have by default. Other environment classes inherit from it and can be used to set settings that are only unique to them. Additionally, the dictionary app_config is used to export the 4 environments we've specified. It's convenient to have it so that we can import the config under its name tag in future.

Config类包含我们希望所有环境默认具有的常规设置。 其他环境类继承自它,并且可以用于设置仅对它们唯一的设置。 此外,字典app_config用于导出我们指定的4个环境。 拥有它很方便,以便将来我们可以在其名称标签下导入配置。

A couple of config variables to note:

需要注意几个配置变量:

  • The SECRET_KEY – is a random string of characters that's used to generate hashes that secure various things in an app. It should never be public to prevent malicious attackers from accessing it.

    SECRET_KEY –是一个随机的字符串,用于生成可保护应用程序中各种内容的哈希。 它应该是公共的,以防止恶意攻击者访问它。
  • DEBUG – tells the app to run under debugging mode when set to True, allowing us to use the Flask debugger. It also automagically reloads the application when it's updated. However, it should be set to False in production.

    DEBUG –设置为True ,告诉应用程序在调试模式下运行,从而允许我们使用Flask调试器。 它还会在更新后自动重新加载应用程序。 但是,在生产中应将其设置为False

( )

安装要求 (Installation requirements)

The tools we need for our database to be up and running are:

我们的数据库启动和运行所需的工具是:

  • – Postgres database offers many over others.

    – Postgres数据库比其他数据库具有许多 。
  • – A Python adapter for Postgres.

    –用于Postgres的Python适配器。
  • ** ** – A Flask extension that provides support for .

    ** ** – Flask扩展,提供对支持。
  • – Offers SQLAlchemy database migrations for Flask apps using .

    –使用为Flask应用程序提供SQLAlchemy数据库迁移。

We might have used a easy-to-setup database such as SQLite. But since we want to learn something new, powerful and awesome, we'll go with PostgreSQL.

我们可能使用了易于设置的数据库,例如SQLite。 但是由于我们想学习新的,功能强大的和很棒的东西,因此我们将使用PostgreSQL。

SQLAlchemy is our Object Relational Mapper (ORM). Why should we use an ORM, you ask? An ORM converts the raw SQL data (called querysets) into data we can understand called objects in a process called serialization and vice versa (deserialization). Instead of painstakingly writing complex raw SQL queries, why not use a tested tool developed just for this purpose?

SQLAlchemy是我们的对象关系映射器(ORM)。 您问我们为什么要使用ORM? ORM将原始SQL数据(称为查询集)转换为我们可以在称为serialization的过程中理解的称为对象的数据,反之亦然(反序列化)。 与其费力地编写复杂的原始SQL查询,不如使用为此目的而开发的经过测试的工具?

Let's install the requirements as follows:

让我们如下安装需求:

(venv)$    pip install flask-sqlalchemy psycopg2 flask-migrate

Ensure you have installed PostgresSQL in your computer and it's server is running locally on port 5432

确保您已在计算机上安装PostgresSQL,并且服务器的服务器在port 5432上本地运行

In your terminal, create a Postgres database:

在您的终端中,创建一个Postgres数据库:

(venv) $ createdb test_db(venv) $ createdb flask_api

Createdb is a wrapper around the SQL command CREATE DATABASE. We created

Createdb是SQL命令CREATE DATABASE的包装。 我们创造了

  • test database test_db for our testing environment.

    在我们的测试环境中测试数据库test_db
  • main database flask_api for development environment.

    用于开发环境的主数据库flask_api

We've used two databases so that we do not interfere with the integrity of our main database when running our tests.

我们使用了两个数据库,以便在运行测试时不会干扰主数据库的完整性。

( )

It's time to right some code! Since we are creating an API, we'll install Flask-API extension.

是时候修改一些代码了! 由于我们正在创建API,因此我们将安装Flask-API扩展。

(venv)$ pip install Flask-API

Flask API is an implementation of the same web browsable APIs that provides. It'll helps us implement our own browsable API.

Flask API是提供的相同网络可浏览API的实现。 它可以帮助我们实现自己的可浏览API。

In our empty app/__init__.py file, we'll add the following:

在我们的空app/__init__.py文件中,添加以下内容:

# app/__init__.pyfrom flask_api import FlaskAPIfrom flask_sqlalchemy import SQLAlchemy# local importfrom instance.config import app_config# initialize sql-alchemydb = SQLAlchemy()def create_app(config_name):    app = FlaskAPI(__name__, instance_relative_config=True)    app.config.from_object(app_config[config_name])    app.config.from_pyfile('config.py')    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False    db.init_app(app)    return app

The create_app function wraps the creation of a new Flask object, and returns it after it's loaded up with configuration settings using app.config and connected to the DB using db.init_app(app).

create_app函数包装新Flask对象的创建,并在使用app.config加载配置设置并使用db.init_app(app)连接到数据库之后将其返回。

We've also disabled track modifications for SQLAlchemy because it'll be deprecated in future due to it's significant performance overhead. For debugging enthusiasts, you can set it to True for now.

我们还禁用了SQLAlchemy的音轨修改,因为由于其巨大的性能开销,以后将不推荐使用它。 对于调试爱好者,您现在可以将其设置为True

Now, we need to define an entry point to start our app. Let's edit the run.py file.

现在,我们需要定义一个入口点来启动我们的应用程序。 让我们编辑run.py文件。

import osfrom app import create_appconfig_name = os.getenv('APP_SETTINGS') # config_name = "development"app = create_app(config_name)if __name__ == '__main__':    app.run()

( )

Now we can run the application on our terminal to see if it works:

现在,我们可以在终端上运行该应用程序,以查看其是否有效:

(venv)$   flask run

We can also run it using python run.py. We should see something like this:

我们也可以使用python run.py运行它。 我们应该看到这样的东西:

( )

It's time to create our bucketlist model. A model is a representation of a table in a database. Add the following inside the empty models.py file:

是时候创建我们的存储桶模型了。 模型是数据库中表的表示。 在空的models.py文件中添加以下内容:

# app/models.pyfrom app import dbclass Bucketlist(db.Model):    """This class represents the bucketlist table."""    __tablename__ = 'bucketlists'    id = db.Column(db.Integer, primary_key=True)    name = db.Column(db.String(255))    date_created = db.Column(db.DateTime, default=db.func.current_timestamp())    date_modified = db.Column(        db.DateTime, default=db.func.current_timestamp(),        onupdate=db.func.current_timestamp())    def __init__(self, name):        """initialize with name."""        self.name = name    def save(self):        db.session.add(self)        db.session.commit()    @staticmethod    def get_all():        return Bucketlist.query.all()    def delete(self):        db.session.delete(self)        db.session.commit()    def __repr__(self):        return "
".format(self.name)

Here's what we've done in the models.py file:

这是我们在models.py文件中所做的工作:

  • We imported our db connection from the app/__init__.py.

    我们从app/__init__.py导入了db连接。
  • Next, we created a Bucketlist class that inherits from db.Model and assigned a table. name bucketlists (it should always be plural). We've therefore created a table to store our bucketlists.

    接下来,我们创建了一个从db.Model继承的Bucketlist类,并分配了一个表。 名称存储bucketlists (应始终为复数形式)。 因此,我们创建了一个表来存储我们的存储桶列表。
  • The id field contains the primary key, the name field will store the name of the bucketlist.

    id字段包含主键, name字段将存储存储桶列表的名称。
  • The __repr__ method represents the object instance of the model whenever it is queries.

    每次查询时, __repr__方法都代表模型的对象实例。
  • The get_all() method is a static method that'll be used to get all the bucketlists in a single query.

    get_all()方法是一个静态方法,将用于在单个查询中获取所有get_all()
  • The save() method will be used to add a new bucketlist to the DB.

    save()方法将用于向数据库添加新的存储桶列表。
  • The delete() method will be used to delete an existing bucketlist from the DB.

    delete()方法将用于从数据库中删除现有的存储桶列表。

( )

Migrations is a way of propagating changes we make to our models (like adding a field, deleting a model, etc.) into your database schema. Now that we've a defined model in place, we need to tell the database to create the relevant schema.

迁移是将我们对模型所做的更改(例如添加字段,删除模型等)传播到数据库架构中的一种方法。 现在我们已经有了定义的模型,我们需要告诉数据库创建相关的架构。

Flask-Migrate uses to autogenerate migrations for us. It will serve this purpose.

Flask-Migrate使用为我们自动生成迁移。 它将达到这个目的。

迁移脚本 (The migration script)

A migration script will conveniently help us make and apply migrations everytime we edit our models. It's good practice to separate migration tasks and not mix them with the code in our app.

每次编辑模型时,迁移脚本都将方便地帮助我们进行和应用迁移。 最好将迁移任务分开,不要将其与我们应用程序中的代码混合使用。

That said, we'll create a new file called manage.py.

也就是说,我们将创建一个名为manage.py的新文件。

Our directory structure should now look like this:

现在,我们的目录结构应如下所示:

├── bucketlist    ├── app    │   ├── __init__.py    │   └── models.py      ├── instance    │   ├── __init__.py    │   └── config.py    ├── manage.py    ├── requirements.txt    ├── run.py    └── test_bucketlist.py

Add the following code to manage.py:

将以下代码添加到manage.py

# manage.pyimport osfrom flask_script import Manager # class for handling a set of commandsfrom flask_migrate import Migrate, MigrateCommandfrom app import db, create_appfrom app import modelsapp = create_app(config_name=os.getenv('APP_SETTINGS'))migrate = Migrate(app, db)manager = Manager(app)manager.add_command('db', MigrateCommand)if __name__ == '__main__':    manager.run()

The Manager class keeps track of all the commands and handles how they are called from the command line. The MigrateCommand contains a set of migration commands. We've also imported the models so that the script can find the models to be migrated. The manager also adds the migration commands and enforces that they start with db.

Manager类跟踪所有命令并处理如何从命令行调用它们。 MigrateCommand包含一组迁移命令。 我们还导入了模型,以便脚本可以找到要迁移的模型。 管理器还添加了迁移命令,并强制它们以db开头。

We will run migrations initialization, using the db init command as follows:

我们将使用db init命令运行迁移初始化,如下所示:

(venv)$   python manage.py db init

You'll notice a newly created folder called migrations. This holds the setup necessary for running migrations. Inside of “migrations” is a folder called “versions”, which will contain the migration scripts as they are created.

您会注意到一个新创建的文件夹,名为migrations 。 这保留了运行迁移所需的设置。 在“迁移”内部是一个名为“版本”的文件夹,该文件夹将包含创建时的迁移脚本。

Next, we'll run the actual migrations using the db migrate command:

接下来,我们将使用db migrate命令运行实际的迁移:

(venv)$   python manage.py db migrate  INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.  INFO  [alembic.runtime.migration] Will assume transactional DDL.  INFO  [alembic.autogenerate.compare] Detected added table 'results'    Generating /bucketlist/migrations/versions/63dba2060f71_.py    ...done

You’ll also notice that in your versions folder there is a migration file. This file is auto-generated by Alembic based on the model.

您还会注意到,在您的版本文件夹中有一个迁移文件。 该文件由Alembic根据模型自动生成。

Finally, we’ll apply the migrations to the database using the db upgrade command:

最后,我们将使用db upgrade命令将迁移应用于数据库:

(venv)$   python manage.py db upgradeINFO  [alembic.runtime.migration] Context impl PostgresqlImpl.INFO  [alembic.runtime.migration] Will assume transactional DDL.INFO  [alembic.runtime.migration] Running upgrade  -> 536e84635828, empty message

Our DB is now updated with our bucketlists table. If you jump into the psql prompt, here's a screenshot on how you can confirm if the table exists:

现在,我们的数据库已使用我们的存储bucketlists列表进行了更新。 如果您跳到psql提示符,这是有关如何确认表是否存在的屏幕截图:

( )

Inside our tests directory, let's create tests. Creating tests that fail is the first step of TD.(Failing is good). These tests will help guide us in creating our functionality. It might seem daunting at first to write tests but it's really easy once you get practicing.

在我们的测试目录中,让我们创建测试。 创建失败的测试是TD的第一步(失败是好的)。 这些测试将帮助指导我们创建功能。 起初编写测试似乎令人生畏,但是一旦您开始练习,这确实很容易。

On the parent directory, create a test file called test_bucketlist.py. This file will contain the following:

在父目录上,创建一个名为test_bucketlist.py的测试文件。 该文件将包含以下内容:

  • Test Case class to house all our API tests.

    测试用例类来容纳我们所有的API测试。
  • setUp() methods to initialize our app and it's test client and create our test database within the app's context.

    setUp()方法可初始化我们的应用程序及其测试客户端,并在应用程序的上下文中创建测试数据库。
  • tearDown() method to tear down test variables and delete our test database after testing is done.

    tearDown()方法可删除测试变量并在测试完成后删除我们的测试数据库。
  • tests to test whether our API can create, read, update and delete a bucketlist.

    测试以测试我们的API是否可以创建,读取,更新和删除存储段列表。
# test_bucketlist.pyimport unittestimport osimport jsonfrom app import create_app, dbclass BucketlistTestCase(unittest.TestCase):    """This class represents the bucketlist test case"""    def setUp(self):        """Define test variables and initialize app."""        self.app = create_app(config_name="testing")        self.client = self.app.test_client        self.bucketlist = {
'name': 'Go to Borabora for vacation'} # binds the app to the current context with self.app.app_context(): # create all tables db.create_all() def test_bucketlist_creation(self): """Test API can create a bucketlist (POST request)""" res = self.client().post('/bucketlists/', data=self.bucketlist) self.assertEqual(res.status_code, 201) self.assertIn('Go to Borabora', str(res.data)) def test_api_can_get_all_bucketlists(self): """Test API can get a bucketlist (GET request).""" res = self.client().post('/bucketlists/', data=self.bucketlist) self.assertEqual(res.status_code, 201) res = self.client().get('/bucketlists/') self.assertEqual(res.status_code, 200) self.assertIn('Go to Borabora', str(res.data)) def test_api_can_get_bucketlist_by_id(self): """Test API can get a single bucketlist by using it's id.""" rv = self.client().post('/bucketlists/', data=self.bucketlist) self.assertEqual(rv.status_code, 201) result_in_json = json.loads(rv.data.decode('utf-8').replace("'", "\"")) result = self.client().get( '/bucketlists/{}'.format(result_in_json['id'])) self.assertEqual(result.status_code, 200) self.assertIn('Go to Borabora', str(result.data)) def test_bucketlist_can_be_edited(self): """Test API can edit an existing bucketlist. (PUT request)""" rv = self.client().post( '/bucketlists/', data={
'name': 'Eat, pray and love'}) self.assertEqual(rv.status_code, 201) rv = self.client().put( '/bucketlists/1', data={
"name": "Dont just eat, but also pray and love :-)" }) self.assertEqual(rv.status_code, 200) results = self.client().get('/bucketlists/1') self.assertIn('Dont just eat', str(results.data)) def test_bucketlist_deletion(self): """Test API can delete an existing bucketlist. (DELETE request).""" rv = self.client().post( '/bucketlists/', data={
'name': 'Eat, pray and love'}) self.assertEqual(rv.status_code, 201) res = self.client().delete('/bucketlists/1') self.assertEqual(res.status_code, 200) # Test to see if it exists, should return a 404 result = self.client().get('/bucketlists/1') self.assertEqual(result.status_code, 404) def tearDown(self): """teardown all initialized variables.""" with self.app.app_context(): # drop all tables db.session.remove() db.drop_all()# Make the tests conveniently executableif __name__ == "__main__": unittest.main()

A bit of testing explanation. Inside the test_bucketlist_creation(self) we make a POST request using a test client to the /bucketlists/ url. The return value is obtained and its status code is asserted to be equal to a status code of 201(Created). If it's equal to 201, the test assertion is true, making the test pass. Finally, it checks whether the returned response contains the name of the bucketlist we just created. This is done using self.assertIn(a, b) If the assertion evaluates to true, the test passes.

一些测试说明。 在test_bucketlist_creation(self)内部,我们使用测试客户端向/bucketlists/网址发出POST请求。 获取返回值,并声明其状态代码等于状态代码201(Created) 。 如果等于201,则测试断言为true,从而使测试通过。 最后,它检查返回的响应是否包含我们刚刚创建的存储桶列表的名称。 使用self.assertIn(a, b)完成此操作。如果断言评估为true,则测试通过。

Now we'll run the test as follows:

现在,我们将按以下方式运行测试:

(venv)$   python test_bucketlist.py

All the tests must fail. Now don't be scared. This is good because we have no functionality to make the test pass. Now's the time to create the API functionality that will make our tests pass.

所有测试必须失败。 现在不要害怕。 这很好,因为我们没有使测试通过的功能。 现在是时候创建将使我们的测试通过的API功能。

( )

Our API is supposed to handle four HTTP requests

我们的API应该可以处理四个HTTP请求

  • POST – Used to create the bucketlist

    POST –用于创建存储区列表
  • GET – For retrieving one bucketlist using its ID and many bucketlists

    GET –用于使用其ID和多个存储区列表检索一个存储区列表
  • PUT – For updating a bucketlist given its ID

    PUT –用于更新具有其ID的存储段列表
  • DELETE – For deleting a bucketlist given its ID

    删除–用于删除具有其ID的存储桶列表

Let's get this done straight away. Inside our app/__init__.py file, we'll edit it as follows:

让我们马上完成。 在app/__init__.py文件中,我们将对其进行如下编辑:

# app/__init__.py# existing import remainsfrom flask import request, jsonify, abortdef create_app(config_name):    from api.models import Bucketlist    #####################    # existing code remains #    #####################    @app.route('/bucketlists/', methods=['POST', 'GET'])    def bucketlists():        if request.method == "POST":            name = str(request.data.get('name', ''))            if name:                bucketlist = Bucketlist(name=name)                bucketlist.save()                response = jsonify({
'id': bucketlist.id, 'name': bucketlist.name, 'date_created': bucketlist.date_created, 'date_modified': bucketlist.date_modified }) response.status_code = 201 return response else: # GET bucketlists = Bucketlist.get_all() results = [] for bucketlist in bucketlists: obj = {
'id': bucketlist.id, 'name': bucketlist.name, 'date_created': bucketlist.date_created, 'date_modified': bucketlist.date_modified } results.append(obj) response = jsonify(results) response.status_code = 200 return response return app

We've imported

我们已经导入

  • request for handling our requests.

    request处理我们的要求。
  • jsonify to turn the JSON output into a Response object with the application/json mimetype.

    jsonify将JSON输出转换为具有application / json mimetype的Response对象。
  • abort which will abort a request with an HTTP error code early.

    abort ,这将尽早中止带有HTTP错误代码的请求。

We've also added an import from api.models import Bucketlist immediately inside the create_app method so that we get access to the Bucketlist model while preventing the horror of circular imports. Flask provides an @app.route decorator on top of the new function def bucketlists() which enforces us to only accepts GET and POST requests. Our function first checks the type of request it receives. If it's a POST, it creates a bucketlist by extracting the name from the request and saves it using the save() method we defined in our model. It consequently returns the newly created bucketlist as a JSON object. If it's a GET request, it gets all the bucketlists from the bucketlists table and returns a list of bucketlists as JSON objects. If there's no bucketlist on our table, it will return an empty JSON object {}.

我们还在create_app方法内立即from api.models import Bucketlist添加了一个导入from api.models import Bucketlist以便我们可以访问Bucketlist模型,同时又避免了循环导入的恐怖。 Flask在新函数def bucketlists()之上提供了一个@app.route装饰器,该装饰器强制我们仅接受GET和POST请求。 我们的功能首先检查它收到的请求的类型。 如果是POST ,它将通过从请求中提取name来创建存储桶列表,并使用我们在模型中定义的save()方法将其save() 。 因此,它将新创建的存储桶列表作为JSON对象返回。 如果是GET请求,它将从bucketlists表中获取所有bucketlist,并以JSON对象的形式返回bucketlist的列表。 如果我们的表上没有存储区列表,它将返回一个空的JSON对象{}

Now let's see if our new GET and POST functionality makes our tests pass.

现在,让我们看看我们新的GETPOST功能是否使我们的测试通过。

( )

Run the tests as follows:

运行测试,如下所示:

(venv)$ python test_bucketlists.py

2 out of 5 tests should pass. We've now handled the GET and POST requests successfully.

5个测试中有2个应该通过。 现在,我们已经成功处理了GET和POST请求。

At this moment, our API can only create and get all the bucketlists. It cannot get a single bucketlist using its bucketlist ID. Also, it can neither edit a bucketlist nor delete it from the DB. To complete it, we'd want to add these functionalities.

目前,我们的API只能创建并获取所有存储区列表。 它无法使用其存储区列表ID获得单个存储区列表。 而且,它既不能编辑存储区列表,也不能从数据库中删除它。 为了完成它,我们想添加这些功能。

( )

On our app/__init__.py file, let's edit as follows:

在我们的app/__init__.py文件中,进行如下编辑:

# app/__init__.py# existing import remainsdef create_app(config_name):    #####################    # existing code remains #    #####################    ###################################    # The GET and POST code is here    ###################################    @app.route('/bucketlists/
', methods=['GET', 'PUT', 'DELETE']) def bucketlist_manipulation(id, **kwargs): # retrieve a buckelist using it's ID bucketlist = Bucketlist.query.filter_by(id=id).first() if not bucketlist: # Raise an HTTPException with a 404 not found status code abort(404) if request.method == 'DELETE': bucketlist.delete() return {
"message": "bucketlist {} deleted successfully".format(bucketlist.id) }, 200 elif request.method == 'PUT': name = str(request.data.get('name', '')) bucketlist.name = name bucketlist.save() response = jsonify({
'id': bucketlist.id, 'name': bucketlist.name, 'date_created': bucketlist.date_created, 'date_modified': bucketlist.date_modified }) response.status_code = 200 return response else: # GET response = jsonify({
'id': bucketlist.id, 'name': bucketlist.name, 'date_created': bucketlist.date_created, 'date_modified': bucketlist.date_modified }) response.status_code = 200 return response return app

We've now defined a new function def bucketlist_manipulation() which uses a decorator that enforces it to only handle GET, PUT and DELETE Http requests. We query the db to filter using an id of the given bucketlist we want to access. If there's no bucketlist, it aborts and returns a 404 Not Found status. The second if-elif-else code blocks handle deleting, updating or getting a bucketlist respectively.

现在,我们定义了一个新函数def bucketlist_manipulation() ,该函数使用一个装饰器,该装饰器强制其仅处理GET, PUT and DELETE Http请求。 我们查询数据库以使用要访问的给定存储桶列表的ID进行过滤。 如果没有存储桶列表,它将aborts并返回404 Not Found状态。 第二个if-elif-else代码块分别处理删除,更新或获取存储桶列表。

( )

Now, we expect all the tests to pass. Let's run them and see if all of them actually pass.

现在,我们希望所有测试都能通过。 让我们运行它们,看看它们是否全部通过。

(venv)$ python test_bucketlist.py

We should now see all the test passing.

现在,我们应该看到所有测试通过了。

( )

Fire up . Key in http://localhost:5000/bucketlists/ and send a POST request with a name as the payload. We should get a response like this:

。 键入http://localhost:5000/bucketlists/并发送一个名称为有效负载的POST请求。 我们应该得到这样的响应:

We can play around with as well to see it working from our terminal:

我们也可以使用来从终端查看它的运行情况:

( )

We've covered quite a lot on how to create a test-driven RESTful API using Flask. We learnt about configuring our Flask environment, hooking up the models, making and applying migrations to the DB, creating unit tests and making tests pass by refactoring our code.

我们已经介绍了很多有关如何使用Flask创建测试驱动的RESTful API的方法。 我们学习了有关配置Flask环境,连接模型,进行迁移并将其应用于数据库,创建单元测试以及通过重构代码使测试通过的知识。

In Part 2 of this Series, we'll learn how to enforce user authentication and authorization on our API with a continued focus on unit testing.

在本系列的第2部分中,我们将继续关注单元测试,以学习如何在我们的API上执行用户身份验证和授权。

翻译自:

转载地址:http://chuwd.baihongyu.com/

你可能感兴趣的文章
uclibc,eglibc,glibc之间的区别和联系【转】
查看>>
Java魔法堂:找外援的利器——Runtime.exec详解
查看>>
mysql数据库存放路径
查看>>
TestNG(五)常用元素的操作
查看>>
解决 Visual Studio 点击添加引用无反应的问题
查看>>
通过镜像下载Android系统源码
查看>>
python字符串格式化 %操作符 {}操作符---总结
查看>>
windows 不能在 本地计算机 启动 Apache
查看>>
iOS开发报duplicate symbols for architecture x86_64错误的问题
查看>>
Chap-6 6.4.2 堆和栈
查看>>
【Java学习笔记之九】java二维数组及其多维数组的内存应用拓展延伸
查看>>
C# MySql 连接
查看>>
网络抓包分析方法大全
查看>>
sql 数据类型
查看>>
android 截图
查看>>
WebServicer接口类生成方法。
查看>>
POJ 1740
查看>>
【翻译】火影忍者鸣人 疾风传 终级风暴2 制作介绍
查看>>
http和webservice
查看>>
hdu1879------------prim算法模板
查看>>