使用 Quart (Flask 的异步版本) 和 FastAPI 构建异步服务

Flask (使用 Quart) 服务器端代码

python 复制代码
pip install quart pydantic

服务器端

python 复制代码
from quart import Quart, jsonify, request
from pydantic import BaseModel, ValidationError, conint
import logging
import asyncio
import uuid
from datetime import datetime

# 创建 Quart 应用实例
app = Quart(__name__)

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 定义请求数据模型
class RequestModel(BaseModel):
    count: conint(ge=1, le=1000)  # count 必须在 1 到 1000 之间

@app.route('/data', methods=['GET', 'POST'])
async def get_data():
    try:
        if request.method == 'GET':
            # 处理 GET 请求参数并进行验证
            params = request.args
        else:
            # 处理 POST 请求参数并进行验证
            if request.content_type == 'application/json':
                params = await request.json
            else:
                params = await request.form

        data = RequestModel(count=int(params.get('count', 100)))

        # 异步操作 1,例如数据库查询
        async def fetch_data_1():
            await asyncio.sleep(1)
            return {'data_1': list(range(data.count))}

        # 异步操作 2,例如外部 API 调用
        async def fetch_data_2():
            await asyncio.sleep(1)
            return {'data_2': list(range(data.count, data.count * 2))}

        # 并行执行异步操作
        result_1, result_2 = await asyncio.gather(fetch_data_1(), fetch_data_2())

        # 构建响应数据
        response_data = {
            'message': 'Hello, this is async data!',
            'result_1': result_1,
            'result_2': result_2,
            'metadata': {
                'request_id': str(uuid.uuid4()),
                'timestamp': datetime.utcnow().isoformat()
            }
        }
        return jsonify(response_data)
    except ValidationError as ve:
        logger.warning(f"Validation error: {ve}")
        return jsonify({'error': ve.errors()}), 400
    except Exception as e:
        logger.error(f"Server error: {e}")
        return jsonify({'error': 'Internal Server Error'}), 500

# 定义错误处理程序
@app.errorhandler(404)
async def page_not_found(e):
    return jsonify({'error': 'Not Found'}), 404

# 运行应用
if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)

FastAPI 服务器端代码

python 复制代码
pip install fastapi uvicorn pydantic

服务器端

python 复制代码
from fastapi import FastAPI, HTTPException, Request, Form
from fastapi.responses import JSONResponse
from pydantic import BaseModel, conint
import logging
import asyncio
import uuid
from datetime import datetime
from typing import Union

# 创建 FastAPI 应用实例
app = FastAPI()

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 定义请求数据模型
class RequestModel(BaseModel):
    count: conint(ge=1, le=1000)  # count 必须在 1 到 1000 之间

@app.get("/data")
async def get_data(count: int = 100):
    try:
        # 验证请求参数
        data = RequestModel(count=count)

        # 异步操作 1,例如数据库查询
        async def fetch_data_1():
            await asyncio.sleep(1)
            return {'data_1': list(range(data.count))}

        # 异步操作 2,例如外部 API 调用
        async def fetch_data_2():
            await asyncio.sleep(1)
            return {'data_2': list(range(data.count, data.count * 2))}

        # 并行执行异步操作
        result_1, result_2 = await asyncio.gather(fetch_data_1(), fetch_data_2())

        # 构建响应数据
        response_data = {
            'message': 'Hello, this is async data!',
            'result_1': result_1,
            'result_2': result_2,
            'metadata': {
                'request_id': str(uuid.uuid4()),
                'timestamp': datetime.utcnow().isoformat()
            }
        }
        return JSONResponse(content=response_data)
    except HTTPException as he:
        logger.warning(f"Client error: {he.detail}")
        raise he
    except Exception as e:
        logger.error(f"Server error: {e}")
        raise HTTPException(status_code=500, detail="Internal Server Error")

@app.post("/data")
async def post_data(request: Request, count: Union[int, None] = Form(None)):
    try:
        if request.headers['content-type'] == 'application/json':
            params = await request.json()
        else:
            params = await request.form()
        
        if count is None:
            count = int(params.get('count', 100))
        
        data = RequestModel(count=count)

        # 异步操作 1,例如数据库查询
        async def fetch_data_1():
            await asyncio.sleep(1)
            return {'data_1': list(range(data.count))}

        # 异步操作 2,例如外部 API 调用
        async def fetch_data_2():
            await asyncio.sleep(1)
            return {'data_2': list(range(data.count, data.count * 2))}

        # 并行执行异步操作
        result_1, result_2 = await asyncio.gather(fetch_data_1(), fetch_data_2())

        # 构建响应数据
        response_data = {
            'message': 'Hello, this is async data!',
            'result_1': result_1,
            'result_2': result_2,
            'metadata': {
                'request_id': str(uuid.uuid4()),
                'timestamp': datetime.utcnow().isoformat()
            }
        }
        return JSONResponse(content=response_data)
    except HTTPException as he:
        logger.warning(f"Client error: {he.detail}")
        raise he
    except Exception as e:
        logger.error(f"Server error: {e}")
        raise HTTPException(status_code=500, detail="Internal Server Error")

@app.exception_handler(404)
async def not_found_handler(request: Request, exc: HTTPException):
    return JSONResponse(status_code=404, content={'error': 'Not Found'})

if __name__ == '__main__':
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")

客户端代码 (使用 asyncio 和 aiohttp)

python 复制代码
import aiohttp
import asyncio
import json

async def fetch_data(url, method='GET', data=None):
    try:
        async with aiohttp.ClientSession() as session:
            if method == 'GET':
                async with session.get(url) as response:
                    if response.status == 200:
                        return await response.json()
                    else:
                        print(f"Failed to fetch data: {response.status}")
                        return None
            elif method == 'POST':
                async with session.post(url, json=data) as response:
                    if response.status == 200:
                        return await response.json()
                    else:
                        print(f"Failed to fetch data: {response.status}")
                        return None
    except aiohttp.ClientError as e:
        print(f"HTTP request failed: {e}")
        return None
    except json.JSONDecodeError as e:
        print(f"Failed to decode JSON: {e}")
        return None
    except Exception as e:
        print(f"An error occurred: {e}")
        return None

if __name__ == '__main__':
    url_get_fastapi = 'http://127.0.0.1:8000/data?count=50'  # 对应 FastAPI GET 服务
    url_post_fastapi = 'http://127.0.0.1:8000/data'  # 对应 FastAPI POST 服务
    url_get_quart = 'http://127.0.0.1:5000/data?count=50'  # 对应 Quart GET 服务
    url_post_quart = 'http://127.0.0.1:5000/data'  # 对应 Quart POST 服务
    
    # FastAPI GET 请求
    data = asyncio.run(fetch_data(url_get_fastapi))
    if data:
        print("FastAPI GET response:")
        print(json.dumps(data, indent=4))
    else:
        print("Failed to fetch data with FastAPI GET.")

    # FastAPI POST 请求
    post_data = {"count": 50}
    data = asyncio.run(fetch_data(url_post_fastapi, method='POST', data=post_data))
    if data:
        print("FastAPI POST response:")
        print(json.dumps(data, indent=4))
    else:
        print("Failed to fetch data with FastAPI POST.")

    # Quart GET 请求
    data = asyncio.run(fetch_data(url_get_quart))
    if data:
        print("Quart GET response:")
        print(json.dumps(data, indent=4))
    else:
        print("Failed to fetch data with Quart GET.")

    # Quart POST 请求
    data = asyncio.run(fetch_data(url_post_quart, method='POST', data=post_data

总结

在本示例中,我们展示了如何使用 Quart 和 FastAPI 构建异步 Web 服务器,并通过客户端异步获取数据。这些代码示例涵盖了多种请求方式和数据处理方法,适用于高并发和异步处理的场景。

1. Quart (Flask 的异步版本) 服务器端

  • 请求处理:

    • 支持 GET 和 POST 请求。
    • 支持 application/json 和 application/x-www-form-urlencoded 内容类型。
    • 使用 Pydantic 进行数据验证。
  • 异步操作:

    • 通过 asyncio.gather 并行执行异步操作,例如模拟的数据库查询和外部 API 调用。
  • 错误处理:

    • 处理 404 错误并返回 JSON 格式的错误消息。
    • 捕获和记录服务器内部错误,返回 500 错误。

2. FastAPI 服务器端

  • 请求处理:
    • 支持 GET 和 POST 请求。
    • 对于 GET 请求,直接使用查询参数。
    • 对于 POST 请求,支持 application/json 和表单数据 (application/x-www-form-urlencoded)。
  • 异步操作:
    • 使用 asyncio.gather 并行执行异步操作,例如模拟的数据库查询和外部 API 调用。
  • 错误处理:
    • 捕获和处理 HTTP 异常,记录客户端和服务器错误。
    • 处理 404 错误并返回 JSON 格式的错误消息。

3. 客户端代码 (使用 aiohttp)

  • 功能:
    • 异步地发送 GET 和 POST 请求到服务器。
    • 支持处理 JSON 响应和错误。
  • 使用示例:
    • 演示如何分别请求 Quart 和 FastAPI 服务器。
    • 打印响应数据,帮助验证服务器端的功能。

应用场景

这些代码示例适用于需要异步处理和高并发的应用场景,例如:

  • 实时数据处理系统
  • 高并发的 API 服务
  • 需要与外部服务进行异步交互的应用

通过使用 Quart 和 FastAPI,我们可以有效地处理并发请求,提高应用的响应速度和处理能力。


补充

Pydantic 简介

pydantic 是一个数据验证和设置管理的 Python 库,提供了基于类型注解的数据验证功能。它主要用于定义和验证数据模型,确保数据符合预期的格式和约束。

主要组件
  • BaseModel:

    • BaseModel 是 pydantic 中的一个基类,用于定义数据模型。它允许你使用类型注解来定义数据字段,并提供自动验证和数据转换的功能。
  • conint:

    • conint 是 pydantic 提供的一个约束类型,用于限制整数值的范围。通过 conint,你可以定义一个整数字段,设置最小值和最大值约束。
示例代码

下面是一个简单的 pydantic 示例,展示了如何定义一个数据模型并验证数据:

python 复制代码
from pydantic import BaseModel, conint, ValidationError

# 定义一个数据模型
class RequestModel(BaseModel):
    count: conint(ge=1, le=1000)  # count 字段必须是 1 到 1000 之间的整数

# 正确的数据示例
try:
    valid_data = RequestModel(count=50)
    print("Valid data:", valid_data)
except ValidationError as e:
    print("Validation error:", e)

# 错误的数据示例(超出范围)
try:
    invalid_data = RequestModel(count=1500)
except ValidationError as e:
    print("Validation error:", e)
相关推荐
谢眠9 分钟前
深度学习day3-自动微分
python·深度学习·机器学习
z千鑫19 分钟前
【人工智能】深入理解PyTorch:从0开始完整教程!全文注解
人工智能·pytorch·python·gpt·深度学习·ai编程
MessiGo42 分钟前
Python 爬虫 (1)基础 | 基础操作
开发语言·python
肥猪猪爸1 小时前
使用卡尔曼滤波器估计pybullet中的机器人位置
数据结构·人工智能·python·算法·机器人·卡尔曼滤波·pybullet
LZXCyrus2 小时前
【杂记】vLLM如何指定GPU单卡/多卡离线推理
人工智能·经验分享·python·深度学习·语言模型·llm·vllm
Enougme2 小时前
Appium常用的使用方法(一)
python·appium
懷淰メ2 小时前
PyQt飞机大战游戏(附下载地址)
开发语言·python·qt·游戏·pyqt·游戏开发·pyqt5
hummhumm2 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
hummhumm2 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
每天吃饭的羊3 小时前
python里的数据结构
开发语言·python