笹田  洋介

笹田 洋介

1656746661

如何使用 Flask 和 APIFairy 輕鬆創建 RESTful API

本教程演示瞭如何使用 Flask 和 APIFairy 輕鬆創建 RESTful API。

目標

在本教程結束時,您將能夠:

  1. 使用 APIFairy 提供的裝飾器在 Flask 中創建 API 端點
  2. 利用 Flask-Marshmallow 定義 API 端點的輸入/輸出模式
  3. 使用 APIFairy 生成 API 文檔
  4. 將關係數據庫與 API 端點集成
  5. 使用 Flask-HTTPAuth 實現基本和令牌認證

什麼是 APIFairy?

APIFairy是由 Miguel Grinberg 編寫的 API 框架,它允許使用 Flask 輕鬆創建 API。

APIFairy 為在 Flask 中輕鬆創建 API 提供了四個關鍵組件:

  1. 裝飾器
  2. 模式
  3. 驗證
  4. 文檔

讓我們詳細探索每一個......

裝飾器

APIFairy 提供了一組裝飾器,用於定義每個 API 端點的輸入、輸出和身份驗證:

APIFairy 裝飾器

APIFairy 提供了五個核心裝飾器:

  1. @arguments - 在 URL 的查詢字符串中指定輸入參數
  2. @body - 將輸入 JSON 正文指定為模式
  3. @response - 將輸出 JSON 正文指定為模式
  4. @other_responses - 指定可以返回的附加響應(通常是錯誤)(僅限文檔
  5. @authenticate - 指定認證過程

模式

API 端點的輸入(使用@body裝飾器)和輸出(使用@response裝飾器)被定義為模式:

class EntrySchema(ma.Schema):
    """Schema defining the attributes in a journal entry."""
    id = ma.Integer()
    entry = ma.String()
    user_id = ma.Integer()

這些模式利用棉花糖將數據類型定義為類。

驗證

@authenticate裝飾器用於檢查 URL 請求中提供給每個 API 端點的身份驗證標頭。身份驗證方案是使用Flask-HTTPAuth實現的,它也是由 Miguel Grinberg 創建的。

典型的 API 身份驗證方法是定義基本身份驗證以保護檢索身份驗證令牌的路由:

basic_auth = HTTPBasicAuth()

@basic_auth.verify_password
def verify_password(email, password):
    user = User.query.filter_by(email=email).first()
    if user.is_password_correct(password):
        return user

並且還定義令牌身份驗證以保護基於時間敏感的身份驗證令牌的大多數路由:

token_auth = HTTPTokenAuth()

@token_auth.verify_token
def verify_token(auth_token):
    return User.verify_auth_token(auth_token)

文檔

APIFairy 的一大特色是自動生成的精美 API 文檔:

API 文檔 - 主頁

該文檔是根據源代碼中的文檔字符串以及以下配置變量生成的:

  1. APIFAIRY_TITLE- 項目名稱
  2. APIFAIRY_VERSION- 項目的版本字符串
  3. APIFAIRY_UI- API 文檔的格式

對於APIFAIRY_UI,您可以從以下 OpenAPI 文檔渲染器之一生成模板:

  1. 招搖用戶界面
  2. 重新文檔
  3. RapiDoc
  4. 元素

有關可用配置變量的完整列表,請參閱配置文檔。

我們在建造什麼?

您將在本教程中開發一個日誌 API,允許用戶每天記錄事件。您可以在 GitLab 上的flask-journal-api存儲庫中找到完整的源代碼。

使用的關鍵 Python 包:

  1. Flask:用於 Python Web 應用程序開發的微框架
  2. APIFairy:Flask 的 API 框架,它使用 -
  3. Flask-SQLAlchemy : Flask 的 ORM(對象關係映射器)

您將逐步開發 API:

  1. 創建用於處理日記條目的 API 端點
  2. 生成 API 文檔
  3. 添加用於存儲日記帳分錄的關係數據庫
  4. 添加身份驗證以保護 API 端點

API 端點

讓我們開始使用 Flask 和 APIFairy 創建 API……

項目初始化

Start by creating a new project folder and a virtual environment:

$ mkdir flask-journal-api
$ cd flask-journal-api
$ python3 -m venv venv
$ source venv/bin/activate
(venv)$

Feel free to swap out virtualenv and Pip for Poetry or Pipenv. For more, review Modern Python Environments.

Go ahead and add the following files and folders:

├── app.py
├── instance
│   └── .gitkeep
├── project
│   ├── __init__.py
│   └── journal_api
│       ├── __init__.py
│       └── routes.py
└── requirements.txt

Next, to install the necessary Python packages, add the dependencies to the requirements.txt file in the project root:

apifairy==0.9.1
Flask==2.1.2
Flask-SQLAlchemy==2.5.1
marshmallow-sqlalchemy==0.28.0

Install:

(venv)$ pip install -r requirements.txt

This Flask project will utilize two best practices for Flask applications:

  1. Application Factory - used for creating the Flask application in a function
  2. Blueprints - used for organizing a group of related views

Application Factory

Start by defining the Application Factory function in project/__init__.py:

from apifairy import APIFairy
from flask import Flask, json
from flask_marshmallow import Marshmallow

# -------------
# Configuration
# -------------

# Create the instances of the Flask extensions in the global scope,
# but without any arguments passed in. These instances are not
# attached to the Flask application at this point.
apifairy = APIFairy()
ma = Marshmallow()


# ----------------------------
# Application Factory Function
# ----------------------------

def create_app():
    # Create the Flask application
    app = Flask(__name__)

    initialize_extensions(app)
    register_blueprints(app)
    return app


# ----------------
# Helper Functions
# ----------------

def initialize_extensions(app):
    # Since the application instance is now created, pass it to each Flask
    # extension instance to bind it to the Flask application instance (app)
    apifairy.init_app(app)
    ma.init_app(app)


def register_blueprints(app):
    # Import the blueprints
    from project.journal_api import journal_api_blueprint

    # Since the application instance is now created, register each Blueprint
    # with the Flask application instance (app)
    app.register_blueprint(journal_api_blueprint, url_prefix='/journal')

With the Application Factory function defined, it can be called in app.py in the top-level folder of the project:

from project import create_app


# Call the application factory function to construct a Flask application
# instance using the development configuration
app = create_app()

Blueprint

Let's define the journal_api blueprint. Start by defining the journal_api blueprint in project/journal_api/__init__.py:

"""
The 'journal_api' blueprint handles the API for managing journal entries.
Specifically, this blueprint allows for journal entries to be added, edited,
and deleted.
"""
from flask import Blueprint

journal_api_blueprint = Blueprint('journal_api', __name__, template_folder='templates')

from . import routes

Now it's time to define the API endpoints for the journal in project/journal_api/routes.py.

Start with the necessary imports:

from apifairy import body, other_responses, response
from flask import abort

from project import ma
from . import journal_api_blueprint

For this initial version of the Flask Journal API, the database will be a list of journal entries:

# --------
# Database
# --------

messages = [
    dict(id=1, entry='The sun was shining when I woke up this morning.'),
    dict(id=2, entry='I tried a new fruit mixture in my oatmeal for breakfast.'),
    dict(id=3, entry='Today I ate a great sandwich for lunch.')
]

Next, define the schemas for creating a new journal entry and for returning the journal entries:

# -------
# Schemas
# -------

class NewEntrySchema(ma.Schema):
    """Schema defining the attributes when creating a new journal entry."""
    entry = ma.String(required=True)


class EntrySchema(ma.Schema):
    """Schema defining the attributes in a journal entry."""
    id = ma.Integer()
    entry = ma.String()


new_entry_schema = NewEntrySchema()
entry_schema = EntrySchema()
entries_schema = EntrySchema(many=True)

Both of these schema classes inherit from ma.Schema, which is provided by Flask-Marshmallow. It's also a good idea to create objects of these schemas, as this allows you to define a schema that can return multiple entries (using the many=True argument).

Now we're ready to define the API endpoints!

Routes

Start with retrieving all the journal entries:

@journal_api_blueprint.route('/', methods=['GET'])
@response(entries_schema)
def journal():
    """Return all journal entries"""
    return messages

This view function uses the @response decorator to define that multiple entries are returned. The view function returns the full list of journal entries (return messages).

Next, create the API endpoint for adding a new journal entry:

@journal_api_blueprint.route('/', methods=['POST'])
@body(new_entry_schema)
@response(entry_schema, 201)
def add_journal_entry(kwargs):
    """Add a new journal entry"""
    new_message = dict(**kwargs, id=messages[-1]['id']+1)
    messages.append(new_message)
    return new_message

This view function uses the @body decorator to define the input to the API endpoint and the @response decorator to define the output from the API endpoint.

從裝飾器解析的輸入數據作為(關鍵字參數)參數傳遞@bodyadd_journal_entry()視圖函數。然後使用此數據創建新的日記帳分錄並將其添加到數據庫中:kwargs

new_message = dict(**kwargs, id=messages[-1]['id']+1)
messages.append(new_message)

然後返回新創建的日記帳分錄 ( return new_message)。請注意@response裝飾器如何將返回碼定義為 201(已創建),以表明日記帳分錄已添加到數據庫中。

創建用於檢索特定日記條目的 API 端點:

@journal_api_blueprint.route('/<int:index>', methods=['GET'])
@response(entry_schema)
@other_responses({404: 'Entry not found'})
def get_journal_entry(index):
    """Return a journal entry"""
    if index >= len(messages):
        abort(404)

    return messages[index]

此視圖函數使用@other_responses裝飾器指定非標準響應。

裝飾器@other_responses僅用於文檔目的!它不提供任何返回錯誤代碼的功能。

創建用於更新日誌條目的 API 端點:

@journal_api_blueprint.route('/<int:index>', methods=['PUT'])
@body(new_entry_schema)
@response(entry_schema)
@other_responses({404: 'Entry not found'})
def update_journal_entry(data, index):
    """Update a journal entry"""
    if index >= len(messages):
        abort(404)

    messages[index] = dict(data, id=index+1)
    return messages[index]

這個視圖函數使用@body@response裝飾器來定義這個 API 端點的輸入和輸出(分別)。此外,@other_responses如果找不到日記帳分錄,裝飾器會定義非標準響應。

最後,創建用於刪除日誌條目的 API 端點:

@journal_api_blueprint.route('/<int:index>', methods=['DELETE'])
@other_responses({404: 'Entry not found'})
def delete_journal_entry(index):
    """Delete a journal entry"""
    if index >= len(messages):
        abort(404)

    messages.pop(index)
    return '', 204

此視圖函數不使用@bodyand@response裝飾器,因為此 API 端點沒有輸入或輸出。如果日誌條目被成功刪除,則返回 204(無內容)狀態代碼且無數據。

運行 Flask 應用程序

要進行測試,請在一個終端窗口中配置 Flask 應用程序並運行開發服務器:

(venv) $ export FLASK_APP=app.py
(venv) $ export FLASK_ENV=development
(venv) $ flask run

然後,在不同的終端窗口中,您可以與 API 進行交互。在這裡隨意使用您選擇的工具,例如 cURL、HTTPieRequestsPostman

請求示例:

$ python3

>>> import requests
>>>
>>> r = requests.get('http://127.0.0.1:5000/journal/')
>>> print(r.text)
>>>
>>> post_data = {'entry': "some message"}
>>> r = requests.post('http://127.0.0.1:5000/journal/', json=post_data)
>>> print(r.text)

想要更輕鬆地測試 API 端點?查看此腳本,它添加了用於與 API 端點交互以檢索、創建、更新和刪除日記條目的 CLI 命令。

文檔

APIFairy 的一個令人難以置信的功能是自動創建 API 文檔!

配置 API 文檔有三個關鍵方面:

  1. API 端點的文檔字符串(即視圖函數)
  2. 整個 API 項目的文檔字符串
  3. 用於指定 API 文檔外觀的配置變量

We already covered the first item in the previous section since we included the docstrings for each view function. For example, the journal() view function has a short description of the purpose of this API endpoint:

@journal_api_blueprint.route('/', methods=['GET'])
@response(entries_schema)
def journal():
    """Return all journal entries"""
    return messages

Next, we need to include the docstring to describe the overall project at the very top of the project/__init__.py file:

"""
Welcome to the documentation for the Flask Journal API!

## Introduction

The Flask Journal API is an API (Application Programming Interface) for creating a **daily journal** that documents events that happen each day.

## Key Functionality

The Flask Journal API has the following functionality:

1. Work with journal entries:
  * Create a new journal entry
  * Update a journal entry
  * Delete a journal entry
  * View all journal entries
2. <More to come!>

## Key Modules

This project is written using Python 3.10.1.

The project utilizes the following modules:

* **Flask**: micro-framework for web application development which includes the following dependencies:
  * **click**: package for creating command-line interfaces (CLI)
  * **itsdangerous**: cryptographically sign data
  * **Jinja2**: templating engine
  * **MarkupSafe**: escapes characters so text is safe to use in HTML and XML
  * **Werkzeug**: set of utilities for creating a Python application that can talk to a WSGI server
* **APIFairy**: API framework for Flask which includes the following dependencies:
  * **Flask-Marshmallow** - Flask extension for using Marshmallow (object serialization/deserialization library)
  * **Flask-HTTPAuth** - Flask extension for HTTP authentication
  * **apispec** - API specification generator that supports the OpenAPI specification
* **pytest**: framework for testing Python projects
"""
...

This docstring is used to describe the overall project, including the key functionality provided and the key Python packages used by the project.

Finally, some configuration variables need to be defined to specify the look of the API documentation. Update the create_app() function in project/__init__.py:

def create_app():
    # Create the Flask application
    app = Flask(__name__)

    # Configure the API documentation
    app.config['APIFAIRY_TITLE'] = 'Flask Journal API'
    app.config['APIFAIRY_VERSION'] = '0.1'
    app.config['APIFAIRY_UI'] = 'elements'

    initialize_extensions(app)
    register_blueprints(app)

    return app

準備好查看項目的文檔了嗎?通過 啟動 Flask 開發服務器flask run,然後導航到http://127.0.0.1:5000/docs以查看 APIFairy 創建的 API 文檔:

API 文檔 - 主頁

在左側窗格中,有一個journal_api藍圖的 API 端點列表。單擊其中一個端點會顯示有關該端點的所有詳細信息:

API 文檔 - 獲取日誌條目 API 端點

這個 API 文檔的驚人之處在於能夠查看 API 端點是如何工作的(假設 Flask 開發服務器正在運行)。在文檔的右側窗格中,輸入日記帳分錄索引,然後單擊“發送 API 請求”。然後顯示 API 響應:

API 文檔 - 獲取日誌條目 API 響應

這個交互式文檔使用戶可以輕鬆理解 API!

數據庫

出於演示目的,本教程將使用 SQLite 數據庫。

配置

由於本教程開始時已經安裝了Flask-SQLAlchemy,我們需要在project/__init__.py文件中進行配置。

首先SQLAlchemy()在“配置”部分創建一個對象:

...

from apifairy import APIFairy
from flask import Flask, json
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy  # <-- NEW!!


# -------------
# Configuration
# -------------

# Create the instances of the Flask extensions in the global scope,
# but without any arguments passed in. These instances are not
# attached to the Flask application at this point.
apifairy = APIFairy()
ma = Marshmallow()
database = SQLAlchemy()  # <-- NEW!!

...

接下來,更新create_app()函數以指定必要的配置變量:

def create_app():
    # Create the Flask application
    app = Flask(__name__)

    # Configure the API documentation
    app.config['APIFAIRY_TITLE'] = 'Flask Journal API'
    app.config['APIFAIRY_VERSION'] = '0.1'
    app.config['APIFAIRY_UI'] = 'elements'

    # NEW!
    # Configure the SQLite database (intended for development only!)
    app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{os.path.join(os.getcwd(), 'instance', 'app.db')}"
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

    initialize_extensions(app)
    register_blueprints(app)

    return app

將導入添加到頂部:

import os

配置變量對於SQLALCHEMY_DATABASE_URI識別 SQLite 數據庫的位置至關重要。對於本教程,數據庫存儲在instance/app.db中。

最後,更新initialize_extensions()函數以初始化 Flask-SQLAlchemy 對象:

def initialize_extensions(app):
    # Since the application instance is now created, pass it to each Flask
    # extension instance to bind it to the Flask application instance (app)
    apifairy.init_app(app)
    ma.init_app(app)
    database.init_app(app)  # <-- NEW!!

想了解更多關於這個 Flask 應用程序是如何連接在一起的嗎?查看我關於如何構建、測試和部署 Flask 應用程序的課程:

數據庫模型

創建一個新的project/models.py文件來定義數據庫表來表示日誌條目:

from project import database


class Entry(database.Model):
    """Class that represents a journal entry."""
    __tablename__ = 'entries'

    id = database.Column(database.Integer, primary_key=True)
    entry = database.Column(database.String, nullable=False)

    def __init__(self, entry: str):
        self.entry = entry

    def update(self, entry: str):
        self.entry = entry

    def __repr__(self):
        return f'<Entry: {self.entry}>'

這個新類Entry指定entries數據庫表將包含兩個元素(現在!)來表示日記帳分錄:

  1. id- 表的主鍵 ( primary_key=True),這意味著它是表中每個元素(行)的唯一標識符
  2. entry- 用於存儲日記帳分錄文本的字符串

雖然models.py定義了數據庫表,但它不會在 SQLite 數據庫中創建表。要創建表,請在終端窗口中啟動 Flask shell:

(venv)$ flask shell

>>> from project import database
>>> database.drop_all()
>>> database.create_all()
>>> quit()

(venv)$

期刊 API 更新

由於我們正在繼續使用 SQLite 數據庫,因此首先刪除在project/journal_api/routes.pydatabase中定義的臨時(Python 列表):

# --------
# Database
# --------

messages = [
    dict(id=1, entry='The sun was shining when I woke up this morning.'),
    dict(id=2, entry='I tried a new fruit mixture in my oatmeal for breakfast.'),
    dict(id=3, entry='Today I ate a great sandwich for lunch.')
]

接下來,我們需要更新每個 API 端點(即視圖函數)以利用 SQLite 數據庫。

首先更新journal()視圖函數:

@journal_api_blueprint.route('/', methods=['GET'])
@response(entries_schema)
def journal():
    """Return all journal entries"""
    return Entry.query.all()

現在從 SQLite 數據庫中檢索到完整的日記帳條目列表。注意這個視圖函數的模式或裝飾器是如何不需要改變的……只有改變用戶的底層過程!

添加導入:

from project.models import Entry

接下來,更新add_journal_entry()視圖函數:

@journal_api_blueprint.route('/', methods=['POST'])
@body(new_entry_schema)
@response(entry_schema, 201)
def add_journal_entry(kwargs):
    """Add a new journal entry"""
    new_message = Entry(**kwargs)
    database.session.add(new_message)
    database.session.commit()
    return new_message

此視圖函數的輸入由 指定new_entry_schema

class NewEntrySchema(ma.Schema):
    """Schema defining the attributes when creating a new journal entry."""
    entry = ma.String(required=True)

new_entry_schema = NewEntrySchema()

entry字符串用於創建Entry該類的新實例(在models.py中定義),然後將此日記條目添加到數據庫中。

添加導入:

from project import database

接下來,更新get_journal_entry()

@journal_api_blueprint.route('/<int:index>', methods=['GET'])
@response(entry_schema)
@other_responses({404: 'Entry not found'})
def get_journal_entry(index):
    """Return a journal entry"""
    entry = Entry.query.filter_by(id=index).first_or_404()
    return entry

此函數現在嘗試查找指定的日記帳分錄(基於index):

entry = Entry.query.filter_by(id=index).first_or_404()

如果條目存在,則將其返回給用戶。如果該條目不存在,則返回 404(未找到)錯誤。

接下來,更新update_journal_entry()

@journal_api_blueprint.route('/<int:index>', methods=['PUT'])
@body(new_entry_schema)
@response(entry_schema)
@other_responses({404: 'Entry not found'})
def update_journal_entry(data, index):
    """Update a journal entry"""
    entry = Entry.query.filter_by(id=index).first_or_404()
    entry.update(data['entry'])
    database.session.add(entry)
    database.session.commit()
    return entry

view 函數現在update_journal_entry()嘗試檢索指定的日記帳分錄:

entry = Entry.query.filter_by(id=index).first_or_404()

如果日記條目存在,則使用新文本更新條目,然後將其保存到數據庫中。

最後,更新delete_journal_entry()

@journal_api_blueprint.route('/<int:index>', methods=['DELETE'])
@other_responses({404: 'Entry not found'})
def delete_journal_entry(index):
    """Delete a journal entry"""
    entry = Entry.query.filter_by(id=index).first_or_404()
    database.session.delete(entry)
    database.session.commit()
    return '', 204

如果找到指定的日記帳分錄,則將其從數據庫中刪除。

運行開發服務器。測試每個端點以確保它們仍然有效。

錯誤處理

由於這個 Flask 項目是一個 API,錯誤代碼應該以 JSON 格式返回,而不是典型的 HTML 格式。

在 Flask 項目中,這可以通過使用自定義錯誤處理程序來完成。在project/__init__.pyregister_error_handlers()中,在文件底部定義一個新函數 ( ):

def register_error_handlers(app):
    @app.errorhandler(HTTPException)
    def handle_http_exception(e):
        """Return JSON instead of HTML for HTTP errors."""
        # Start with the correct headers and status code from the error
        response = e.get_response()
        # Replace the body with JSON
        response.data = json.dumps({
            'code': e.code,
            'name': e.name,
            'description': e.description,
        })
        response.content_type = 'application/json'
        return response

此函數註冊一個新的錯誤處理程序,用於何時HTTPException引發將輸出轉換為 JSON 格式。

添加導入:

from werkzeug.exceptions import HTTPException

此外,更新應用程序工廠函數create_app(), 以調用此新函數:

def create_app():
    # Create the Flask application
    app = Flask(__name__)

    # Configure the API documentation
    app.config['APIFAIRY_TITLE'] = 'Flask Journal API'
    app.config['APIFAIRY_VERSION'] = '0.1'
    app.config['APIFAIRY_UI'] = 'elements'

    # Configure the SQLite database (intended for development only!)
    app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{os.path.join(os.getcwd(), 'instance', 'app.db')}"
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

    initialize_extensions(app)
    register_blueprints(app)
    register_error_handlers(app)  # NEW!!

    return app

驗證

身份驗證是驗證嘗試訪問系統的用戶身份的過程,在這種情況下是 API。

另一方面,授權是驗證特定用戶應該有權訪問哪些特定資源的過程。

APIFairy 使用Flask-HTTPAuth來支持身份驗證。在本教程中,我們將以兩種方式使用 Flask-HTTPAuth:

  1. 基本身份驗證- 用於根據用戶的電子郵件/密碼生成令牌
  2. 令牌身份驗證- 用於在所有其他 API 端點上對用戶進行身份驗證

通過 Flask-HTTPAuth 使用的令牌身份驗證通常稱為承載身份驗證,因為該過程調用授予對令牌“承載者”的訪問權限。令牌必須包含在 Authorization 標頭中的 HTTP 標頭中,例如“Authorization: Bearer”。

下圖說明了新用戶如何與應用程序交互以檢索身份驗證令牌的典型流程:

Flask Journal API 流程圖

配置

由於本教程開始安裝 APIFairy 時已經安裝了 Flask-HTTPAuth,我們只需要在project/__init__.py文件中進行配置即可。

首先為基本身份驗證和令牌身份驗證創建單獨的對象:

...

import os

from apifairy import APIFairy
from flask import Flask, json
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth  # NEW!!
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy
from werkzeug.exceptions import HTTPException


# -------------
# Configuration
# -------------

# Create the instances of the Flask extensions in the global scope,
# but without any arguments passed in. These instances are not
# attached to the Flask application at this point.
apifairy = APIFairy()
ma = Marshmallow()
database = SQLAlchemy()
basic_auth = HTTPBasicAuth()  # NEW!!
token_auth = HTTPTokenAuth()  # NEW!!

...

project/__init__.py中不需要進一步更新。

數據庫模型

project/models.pyUser中,需要創建一個新模型來代表用戶:

class User(database.Model):
    __tablename__ = 'users'

    id = database.Column(database.Integer, primary_key=True)
    email = database.Column(database.String, unique=True, nullable=False)
    password_hashed = database.Column(database.String(128), nullable=False)
    entries = database.relationship('Entry', backref='user', lazy='dynamic')
    auth_token = database.Column(database.String(64), index=True)
    auth_token_expiration = database.Column(database.DateTime)

    def __init__(self, email: str, password_plaintext: str):
        """Create a new User object."""
        self.email = email
        self.password_hashed = self._generate_password_hash(password_plaintext)

    def is_password_correct(self, password_plaintext: str):
        return check_password_hash(self.password_hashed, password_plaintext)

    def set_password(self, password_plaintext: str):
        self.password_hashed = self._generate_password_hash(password_plaintext)

    @staticmethod
    def _generate_password_hash(password_plaintext):
        return generate_password_hash(password_plaintext)

    def generate_auth_token(self):
        self.auth_token = secrets.token_urlsafe()
        self.auth_token_expiration = datetime.utcnow() + timedelta(minutes=60)
        return self.auth_token

    @staticmethod
    def verify_auth_token(auth_token):
        user = User.query.filter_by(auth_token=auth_token).first()
        if user and user.auth_token_expiration > datetime.utcnow():
            return user

    def revoke_auth_token(self):
        self.auth_token_expiration = datetime.utcnow()

    def __repr__(self):
        return f'<User: {self.email}>'

添加導入:

import secrets
from datetime import datetime, timedelta

from werkzeug.security import check_password_hash, generate_password_hash

User模型werkzeug.security用於在將用戶密碼存儲到數據庫之前對其進行哈希處理。

記住:永遠不要將明文密碼存儲在數據庫中!

User模型secrets用於為特定用戶生成身份驗證令牌。此令牌在該generate_auth_token()方法中創建,並包括未來 60 分鐘的到期日期/時間:

def generate_auth_token(self):
    self.auth_token = secrets.token_urlsafe()
    self.auth_token_expiration = datetime.utcnow() + timedelta(minutes=60)
    return self.auth_token

有一個靜態方法,verify_auth_token()用於驗證身份驗證令牌(同時考慮到期時間)並從有效令牌返回用戶:

@staticmethod
def verify_auth_token(auth_token):
    user = User.query.filter_by(auth_token=auth_token).first()
    if user and user.auth_token_expiration > datetime.utcnow():
        return user

另一種有趣的方法是revoke_auth_token(),它用於撤銷特定用戶的身份驗證令牌:

def revoke_auth_token(self):
    self.auth_token_expiration = datetime.utcnow()

入門模型

為了在用戶(“one”)和他們的條目(“many”)之間建立一對多的關係,Entry需要更新模型以將表entriesusers錶鍊接在一起:

class Entry(database.Model):
    """Class that represents a journal entry."""
    __tablename__ = 'entries'

    id = database.Column(database.Integer, primary_key=True)
    entry = database.Column(database.String, nullable=False)
    user_id = database.Column(database.Integer, database.ForeignKey('users.id'))  # <-- NEW!!

    def __init__(self, entry: str):
        self.entry = entry

    def update(self, entry: str):
        self.entry = entry

    def __repr__(self):
        return f'<Entry: {self.entry}>'

User模型已經包含返回entries表格的鏈接:

entries = database.relationship('Entry', backref='user', lazy='dynamic')

用戶 API 藍圖

Flask 項目的用戶管理功能將在一個名為users_api_blueprint.

首先在“項目”中創建一個名為“users_api”的新目錄。在該目錄中創建一個__init__.py文件:

from flask import Blueprint


users_api_blueprint = Blueprint('users_api', __name__)

from . import authentication, routes

這個新藍圖需要在函數中的projects/__init__.pyapp中的 Flask中註冊:register_blueprints()

def register_blueprints(app):
    # Import the blueprints
    from project.journal_api import journal_api_blueprint
    from project.users_api import users_api_blueprint  # NEW!!

    # Since the application instance is now created, register each Blueprint
    # with the Flask application instance (app)
    app.register_blueprint(journal_api_blueprint, url_prefix='/journal')
    app.register_blueprint(users_api_blueprint, url_prefix='/users')  # NEW!!

認證功能

要使用 Flask-HTTPAuth,需要定義幾個函數來處理檢查用戶憑據。

創建一個新的project/users_api/authentication.py文件來處理基本認證和令牌認證。

對於基本身份驗證(檢查用戶的電子郵件和密碼):

from werkzeug.exceptions import Forbidden, Unauthorized

from project import basic_auth, token_auth
from project.models import User


@basic_auth.verify_password
def verify_password(email, password):
    user = User.query.filter_by(email=email).first()
    if user is None:
        return None

    if user.is_password_correct(password):
        return user


@basic_auth.error_handler
def basic_auth_error(status=401):
    error = (Forbidden if status == 403 else Unauthorized)()
    return {
        'code': error.code,
        'message': error.name,
        'description': error.description,
    }, error.code, {'WWW-Authenticate': 'Form'}

verify_password()功能用於檢查用戶是否存在以及他們的密碼是否正確。當需要基本身份驗證時,Flask-HTTPAuth 將使用此函數來驗證密碼(感謝@basic_auth.verify_password裝飾器。)

此外,還為基本身份驗證定義了一個錯誤處理程序,該處理程序以 JSON 格式返回有關錯誤的信息。

對於令牌認證(處理令牌以確定用戶是否有效):

@token_auth.verify_token
def verify_token(auth_token):
    return User.verify_auth_token(auth_token)


@token_auth.error_handler
def token_auth_error(status=401):
    error = (Forbidden if status == 403 else Unauthorized)()
    return {
        'code': error.code,
        'message': error.name,
        'description': error.description,
    }, error.code

verify_token()函數用於檢查身份驗證令牌是否有效。當需要令牌身份驗證時,Flask-HTTPAuth 將使用此函數來驗證令牌(感謝@token_auth.verify_token裝飾器。)

此外,還為令牌身份驗證定義了一個錯誤處理程序,該處理程序以 JSON 格式返回有關錯誤的信息。

用戶路線

在 中users_api_blueprint,將有兩條路線:

  1. 註冊新用戶
  2. 檢索身份驗證令牌

首先,需要在projects/users_api/routes.py中定義一組新模式(使用棉花糖) :

from project import ma

from . import users_api_blueprint

# -------
# Schemas
# -------

class NewUserSchema(ma.Schema):
    """Schema defining the attributes when creating a new user."""
    email = ma.String()
    password_plaintext = ma.String()


class UserSchema(ma.Schema):
    """Schema defining the attributes of a user."""
    id = ma.Integer()
    email = ma.String()


class TokenSchema(ma.Schema):
    """Schema defining the attributes of a token."""
    token = ma.String()


new_user_schema = NewUserSchema()
user_schema = UserSchema()
token_schema = TokenSchema()

這些模式將用於定義此文件中定義的視圖函數的輸入和輸出。

註冊新用戶

接下來,定義註冊新用戶的視圖函數:

@users_api_blueprint.route('/', methods=['POST'])
@body(new_user_schema)
@response(user_schema, 201)
def register(kwargs):
    """Create a new user"""
    new_user = User(**kwargs)
    database.session.add(new_user)
    database.session.commit()
    return new_user

添加導入:

from apifairy import authenticate, body, other_responses, response

from project import basic_auth, database, ma
from project.models import User

此 API 端點使用new_user_schema指定電子郵件和密碼是輸入。

注意:由於電子郵件和密碼被發送到此 API 端點,因此最好記住在開發測試期間使用 HTTP 是可以接受的,但在生產中應始終使用 HTTPS(安全)。

然後解壓縮電子郵件和密碼(定義為kwargs- 關鍵字參數)以創建一個新User對象,並將其保存到數據庫中:

new_user = User(**kwargs)
database.session.add(new_user)
database.session.commit()

API 端點的輸出由 定義user_schema,它是新用戶的 ID 和電子郵件。

檢索身份驗證令牌

在projects/users_api/routes.py中定義的另一個視圖函數用於檢索身份驗證令牌:

@users_api_blueprint.route('/get-auth-token', methods=['POST'])
@authenticate(basic_auth)
@response(token_schema)
@other_responses({401: 'Invalid username or password'})
def get_auth_token():
    """Get authentication token"""
    user = basic_auth.current_user()
    token = user.generate_auth_token()
    database.session.add(user)
    database.session.commit()
    return dict(token=token)

在本@authenticate教程中第一次使用裝飾器,它指定應該使用基本身份驗證來保護此路由:

@authenticate(basic_auth)

當用戶想要檢索他們的身份驗證令牌時,他們需要向此 API 端點發送一個 POST 請求,並將電子郵件和密碼嵌入在“授權”標頭中。例如,可以對該 API 端點執行以下使用Requests包的 Python 命令:

>>> import requests
>>> r = requests.post(
    'http://127.0.0.1:5000/users/get-auth-token',
    auth=('pkennedy@hey.com', 'FlaskIsAwesome123')
)

current_user()如果基本認證成功,則視圖函數使用Flask-HTTPAuth 提供的方法檢索當前用戶:

user = basic_auth.current_user()

為該用戶創建一個新的身份驗證令牌:

token = user.generate_auth_token()

並且該令牌被保存到數據庫中,以便將來可用於對用戶進行身份驗證(至少在接下來的 60 分鐘內!)。

Finally, the new authentication token is returned for the user to save for all subsequent API calls.

API Endpoint Updates

With an authentication process in place, it's time to add some guards to the existing API endpoints to make sure that only valid users can access the application.

These updates are for the view functions defined in projects/journal_api/routes.py.

First, update journal() to only return the journal entries for the current user:

@journal_api_blueprint.route('/', methods=['GET'])
@authenticate(token_auth)
@response(entries_schema)
def journal():
    """Return journal entries"""
    user = token_auth.current_user()
    return Entry.query.filter_by(user_id=user.id).all()

Update the imports at the top like so:

from apifairy import authenticate, body, other_responses, response
from flask import abort

from project import database, ma, token_auth
from project.models import Entry

from . import journal_api_blueprint

The @authenticate decorator specifies that token authentication needs to be used when accessing this API endpoint. As an example, the following GET request could be made using Requests (after the authentication token has been retrieved):

>>> import requests
>>> headers = {'Authorization': f'Bearer {auth_token}'}
>>> r = requests.get('http://127.0.0.1:5000/journal/', headers=headers)

用戶通過身份驗證後,將根據用戶 ID 從數據庫中檢索完整的日記帳條目列表:

user = token_auth.current_user()
return Entry.query.filter_by(user_id=user.id).all()

此 API 端點的輸出由@response裝飾器定義,它是日誌條目(ID、條目、用戶 ID)的列表。

接下來,更新add_journal_entry()

@journal_api_blueprint.route('/', methods=['POST'])
@authenticate(token_auth)
@body(new_entry_schema)
@response(entry_schema, 201)
def add_journal_entry(kwargs):
    """Add a new journal entry"""
    user = token_auth.current_user()
    new_message = Entry(user_id=user.id, **kwargs)
    database.session.add(new_message)
    database.session.commit()
    return new_message

與之前的視圖函數一樣,@authenticate裝飾器用於指定訪問此 API 端點時需要使用令牌認證。此外,現在通過指定應與日記條目關聯的用戶 ID 添加日記條目:

user = token_auth.current_user()
new_message = Entry(user_id=user.id, **kwargs)

新的日誌條目被保存到數據庫中並返回日誌條目(由@response裝飾器定義)。

接下來,更新get_journal_entry()

@journal_api_blueprint.route('/<int:index>', methods=['GET'])
@authenticate(token_auth)
@response(entry_schema)
@other_responses({403: 'Forbidden', 404: 'Entry not found'})
def get_journal_entry(index):
    """Return a journal entry"""
    user = token_auth.current_user()
    entry = Entry.query.filter_by(id=index).first_or_404()

    if entry.user_id != user.id:
        abort(403)
    return entry

添加裝飾器@authenticate以指定訪問此 API 端點需要令牌身份驗證。

嘗試檢索日記條目時,會添加額外的檢查以確保嘗試訪問日記條目的用戶是條目的實際“所有者”。如果不是,則通過abort()Flask 中的函數返回 403(禁止)錯誤代碼:

if entry.user_id != user.id:
        abort(403)

請注意,此 API 端點有兩個由@other_responses裝飾器指定的非標稱響應:

@other_responses({403: 'Forbidden', 404: 'Entry not found'})

提醒:@other_responses裝飾器僅用於文檔;引發這些錯誤是視圖函數的責任。

接下來,更新update_journal_entry()

@journal_api_blueprint.route('/<int:index>', methods=['PUT'])
@authenticate(token_auth)
@body(new_entry_schema)
@response(entry_schema)
@other_responses({403: 'Forbidden', 404: 'Entry not found'})
def update_journal_entry(data, index):
    """Update a journal entry"""
    user = token_auth.current_user()
    entry = Entry.query.filter_by(id=index).first_or_404()

    if entry.user_id != user.id:
        abort(403)

    entry.update(data['entry'])
    database.session.add(entry)
    database.session.commit()
    return entry

此視圖函數的更新類似於本節中的其他視圖函數:

  1. @authenticate裝飾器指定訪問此 API 端點需要令牌身份驗證
  2. 只有“擁有”日誌條目的用戶才被允許更新條目(否則,403(禁止))

最後,更新delete_journal_entry()

@journal_api_blueprint.route('/<int:index>', methods=['DELETE'])
@authenticate(token_auth)
@other_responses({403: 'Forbidden', 404: 'Entry not found'})
def delete_journal_entry(index):
    """Delete a journal entry"""
    user = token_auth.current_user()
    entry = Entry.query.filter_by(id=index).first_or_404()

    if entry.user_id != user.id:
        abort(403)

    database.session.delete(entry)
    database.session.commit()
    return '', 204

結論

本教程提供瞭如何使用 APIFairy 在 Flask 中輕鬆快速地構建 API 的演練。

裝飾器是定義 API 端點的關鍵:

  • 輸入
    • @arguments- 從 URL 的查詢字符串輸入參數
    • @body- JSON 請求的結構
  • 輸出
    • @response- JSON 響應的結構
  • 身份驗證
    • @authenticate- 使用 Flask-HTTPAuth 的身份驗證方法
  • 錯誤
    • @other_responses- 非標稱響應,例如 HTTP 錯誤代碼

此外,APIFairy 生成的 API 文檔非常出色,為應用程序的用戶提供了關鍵信息。

來源:  https ://testdriven.io

#api #flask 

What is GEEK

Buddha Community

如何使用 Flask 和 APIFairy 輕鬆創建 RESTful API
Wilford  Pagac

Wilford Pagac

1594289280

What is REST API? An Overview | Liquid Web

What is REST?

The REST acronym is defined as a “REpresentational State Transfer” and is designed to take advantage of existing HTTP protocols when used for Web APIs. It is very flexible in that it is not tied to resources or methods and has the ability to handle different calls and data formats. Because REST API is not constrained to an XML format like SOAP, it can return multiple other formats depending on what is needed. If a service adheres to this style, it is considered a “RESTful” application. REST allows components to access and manage functions within another application.

REST was initially defined in a dissertation by Roy Fielding’s twenty years ago. He proposed these standards as an alternative to SOAP (The Simple Object Access Protocol is a simple standard for accessing objects and exchanging structured messages within a distributed computing environment). REST (or RESTful) defines the general rules used to regulate the interactions between web apps utilizing the HTTP protocol for CRUD (create, retrieve, update, delete) operations.

What is an API?

An API (or Application Programming Interface) provides a method of interaction between two systems.

What is a RESTful API?

A RESTful API (or application program interface) uses HTTP requests to GET, PUT, POST, and DELETE data following the REST standards. This allows two pieces of software to communicate with each other. In essence, REST API is a set of remote calls using standard methods to return data in a specific format.

The systems that interact in this manner can be very different. Each app may use a unique programming language, operating system, database, etc. So, how do we create a system that can easily communicate and understand other apps?? This is where the Rest API is used as an interaction system.

When using a RESTful API, we should determine in advance what resources we want to expose to the outside world. Typically, the RESTful API service is implemented, keeping the following ideas in mind:

  • Format: There should be no restrictions on the data exchange format
  • Implementation: REST is based entirely on HTTP
  • Service Definition: Because REST is very flexible, API can be modified to ensure the application understands the request/response format.
  • The RESTful API focuses on resources and how efficiently you perform operations with it using HTTP.

The features of the REST API design style state:

  • Each entity must have a unique identifier.
  • Standard methods should be used to read and modify data.
  • It should provide support for different types of resources.
  • The interactions should be stateless.

For REST to fit this model, we must adhere to the following rules:

  • Client-Server Architecture: The interface is separate from the server-side data repository. This affords flexibility and the development of components independently of each other.
  • Detachment: The client connections are not stored on the server between requests.
  • Cacheability: It must be explicitly stated whether the client can store responses.
  • Multi-level: The API should work whether it interacts directly with a server or through an additional layer, like a load balancer.

#tutorials #api #application #application programming interface #crud #http #json #programming #protocols #representational state transfer #rest #rest api #rest api graphql #rest api json #rest api xml #restful #soap #xml #yaml

An API-First Approach For Designing Restful APIs | Hacker Noon

I’ve been working with Restful APIs for some time now and one thing that I love to do is to talk about APIs.

So, today I will show you how to build an API using the API-First approach and Design First with OpenAPI Specification.

First thing first, if you don’t know what’s an API-First approach means, it would be nice you stop reading this and check the blog post that I wrote to the Farfetchs blog where I explain everything that you need to know to start an API using API-First.

Preparing the ground

Before you get your hands dirty, let’s prepare the ground and understand the use case that will be developed.

Tools

If you desire to reproduce the examples that will be shown here, you will need some of those items below.

  • NodeJS
  • OpenAPI Specification
  • Text Editor (I’ll use VSCode)
  • Command Line

Use Case

To keep easy to understand, let’s use the Todo List App, it is a very common concept beyond the software development community.

#api #rest-api #openai #api-first-development #api-design #apis #restful-apis #restful-api

Lets Cms

Lets Cms

1652251528

Opencart REST API extensions - V3.x | Rest API Integration, Affiliate

Opencart REST API extensions - V3.x | Rest API Integration : OpenCart APIs is fully integrated with the OpenCart REST API. This is interact with your OpenCart site by sending and receiving data as JSON (JavaScript Object Notation) objects. Using the OpenCart REST API you can register the customers and purchasing the products and it provides data access to the content of OpenCart users like which is publicly accessible via the REST API. This APIs also provide the E-commerce Mobile Apps.

Opencart REST API 
OCRESTAPI Module allows the customer purchasing product from the website it just like E-commerce APIs its also available mobile version APIs.

Opencart Rest APIs List 
Customer Registration GET APIs.
Customer Registration POST APIs.
Customer Login GET APIs.
Customer Login POST APIs.
Checkout Confirm GET APIs.
Checkout Confirm POST APIs.


If you want to know Opencart REST API Any information, you can contact us at -
Skype: jks0586,
Email: letscmsdev@gmail.com,
Website: www.letscms.com, www.mlmtrees.com
Call/WhatsApp/WeChat: +91–9717478599.

Download : https://www.opencart.com/index.php?route=marketplace/extension/info&extension_id=43174&filter_search=ocrest%20api
View Documentation : https://www.letscms.com/documents/api/opencart-rest-api.html
More Information : https://www.letscms.com/blog/Rest-API-Opencart
VEDIO : https://vimeo.com/682154292  

#opencart_api_for_android #Opencart_rest_admin_api #opencart_rest_api #Rest_API_Integration #oc_rest_api #rest_api_ecommerce #rest_api_mobile #rest_api_opencart #rest_api_github #rest_api_documentation #opencart_rest_admin_api #rest_api_for_opencart_mobile_app #opencart_shopping_cart_rest_api #opencart_json_api

Lets Cms

Lets Cms

1652251629

Unilevel MLM Wordpress Rest API FrontEnd | UMW Rest API Woocommerce

Unilevel MLM Wordpress Rest API FrontEnd | UMW Rest API Woocommerce Price USA, Philippines : Our API’s handle the Unilevel MLM woo-commerce end user all functionalities like customer login/register. You can request any type of information which is listed below, our API will provide you managed results for your all frontend needs, which will be useful for your applications like Mobile App etc.
Business to Customer REST API for Unilevel MLM Woo-Commerce will empower your Woo-commerce site with the most powerful Unilevel MLM Woo-Commerce REST API, you will be able to get and send data to your marketplace from other mobile apps or websites using HTTP Rest API request.
Our plugin is used JWT authentication for the authorization process.

REST API Unilevel MLM Woo-commerce plugin contains following APIs.
User Login Rest API
User Register Rest API
User Join Rest API
Get User info Rest API
Get Affiliate URL Rest API 
Get Downlines list Rest API
Get Bank Details Rest API
Save Bank Details Rest API
Get Genealogy JSON Rest API
Get Total Earning Rest API
Get Current Balance Rest API
Get Payout Details Rest API
Get Payout List Rest API
Get Commissions List Rest API
Withdrawal Request Rest API
Get Withdrawal List Rest API

If you want to know more information and any queries regarding Unilevel MLM Rest API Woocommerce WordPress Plugin, you can contact our experts through 
Skype: jks0586, 
Mail: letscmsdev@gmail.com,
Website: www.letscms.com, www.mlmtrees.com,
Call/WhatsApp/WeChat: +91-9717478599.  

more information : https://www.mlmtrees.com/product/unilevel-mlm-woocommerce-rest-api-addon

Visit Documentation : https://letscms.com/documents/umw_apis/umw-apis-addon-documentation.html

#Unilevel_MLM_WooCommerce_Rest_API's_Addon #umw_mlm_rest_api #rest_api_woocommerce_unilevel #rest_api_in_woocommerce #rest_api_woocommerce #rest_api_woocommerce_documentation #rest_api_woocommerce_php #api_rest_de_woocommerce #woocommerce_rest_api_in_android #woocommerce_rest_api_in_wordpress #Rest_API_Woocommerce_unilevel_mlm #wp_rest_api_woocommerce

Adonis  Kerluke

Adonis Kerluke

1596509565

RESTful API Design Driven Approach

In this tutorial I will show you the fundamentals of designing a RESTful API specification by applying REST principles and best practices, then you’ll be ready to try my online tutorial: How to design a REST API with API Designer?

If you already know what is meant by API in the context of RESTful web services, you can skip to the next section. If not, read on.

Level-Set on API

The abbreviation API stands for Application Programming Interface this in itself, does not help us understand what it is, however in the context of web services, it can refer to one of two things:

  1. The RESTful API specification is written using a modeling language such as Open API specification or RAML (RESTful API Modeling Language) that defines a contract for how software components can interact with a service.
  2. The implementation of a web service or microservice whose contract is designed by REST principles that describe how other services must interact with it.

In this post, I will use the first understanding of this term. Even though both are correct, the most technically relevant for this post is the first: an API is a contract for how software applications talk to each other.

Level-Set on REST

The acronym REST stands for REpresentational State Transfer. It is an architectural style used to represent the transmission of data from one application component to another. In the context of web services, we are talking about the representation of resources (i.e. data) transferred over HTTP by calling a URI that represents the data and via an HTTP method that represents the action to perform against the given data.

What Is RESTful API design?

RESTful API design is the activity of describing the behavior of a web service in terms of its data structures and the actions you allow other application components to perform on its data by the principles of REST. Those principles are covered later in this blog.

Why Design a RESTful API?

Imagine that you are an Architect (the kind the design building) and you set out to build an office block without a blueprint. You turn up on the first day with a truck full of bricks and some cement. What are the chances that you’ll be successful and build a structure that conforms to code and more importantly, doesn’t fall? It’s about zero. Without a blueprint the chance of failure is high.

The same approach applies to web service development. You need a blueprint, or more appropriately, an API specification. This is necessary to evaluate the API design and solicit feedback before even starting to build the implementation.

In addition to providing a specification for the web service’s development, an API contract serves to document its expected behavior, data types, and security requirements.

You should now be satisfied that API design is necessary for a RESTful web service, and should start to wonder how is the best approach to actually designing an API specification.

API Design Tooling

The tooling chosen by an API designer has substantial influence over the designer’s productivity. Highly productive tools such as the Anypoint API Designer from MuleSoft is perfect for designing APIs with OAS (swagger) or RAML.

#integration #api #rest #rest api #restful #api design #raml #rest api design