使用 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)
相关推荐
凤枭香7 分钟前
Python OpenCV 傅里叶变换
开发语言·图像处理·python·opencv
测试杂货铺14 分钟前
外包干了2年,快要废了。。
自动化测试·软件测试·python·功能测试·测试工具·面试·职场和发展
艾派森18 分钟前
大数据分析案例-基于随机森林算法的智能手机价格预测模型
人工智能·python·随机森林·机器学习·数据挖掘
小码的头发丝、44 分钟前
Django中ListView 和 DetailView类的区别
数据库·python·django
Chef_Chen2 小时前
从0开始机器学习--Day17--神经网络反向传播作业
python·神经网络·机器学习
千澜空2 小时前
celery在django项目中实现并发任务和定时任务
python·django·celery·定时任务·异步任务
斯凯利.瑞恩2 小时前
Python决策树、随机森林、朴素贝叶斯、KNN(K-最近邻居)分类分析银行拉新活动挖掘潜在贷款客户附数据代码
python·决策树·随机森林
yannan201903132 小时前
【算法】(Python)动态规划
python·算法·动态规划
蒙娜丽宁2 小时前
《Python OpenCV从菜鸟到高手》——零基础进阶,开启图像处理与计算机视觉的大门!
python·opencv·计算机视觉
光芒再现dev3 小时前
已解决,部署GPTSoVITS报错‘AsyncRequest‘ object has no attribute ‘_json_response_data‘
运维·python·gpt·语言模型·自然语言处理