告别传统GUI:用FastAPI + PyWebView + 当代前端技能打造Python应用界面 ...

光之使者  论坛元老 | 2025-4-7 10:52:25 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1374|帖子 1374|积分 4122

告别传统GUI:用FastAPI + PyWebView + 当代前端技能打造Python应用界面

引言

在Python应用步伐开辟中,GUI(图形用户界面)的实现一直是一个痛点。传统的GUI库如 PySide6TkinterwxPython 等虽然功能强盛,但开辟复杂、样式定制困难,且难以适应当代前端技能的快速发展。此外,像 Kivy 这样的库虽然支持跨平台和丰富的交互结果,但其学习曲线较陡,且对当代Web技能的支持有限。而 Dear PyGuiPySimpleGUI 等新兴库虽然简化了开辟流程,但在复杂应用场景中仍存在肯定的局限性。
为相识决这一问题,我们提出了一种全新的解决方案:使用 FastAPI 作为后端服务,结合 PyWebView 和当代前端技能(如 React、Vue.js 或 Tailwind CSS),构建当代化的 Python 应用步伐 GUI。这种架构不仅可以或许充实使用当代前端技能的灵活性和强盛功能,还能通过 FastAPI 提供高效的后端支持,实现前后端分离的开辟模式,从而显著提升开辟服从和用户体验。
这种方案的核心思想是:

  • 后端使用FastAPI:提供高性能的API和WebSocket支持。
  • 前端使用当代前端技能:无论是React、Vue、Angular等前端框架,还是Tailwind CSS、Bootstrap等CSS框架,乃至是Three.js、D3.js等可视化库,都可以无缝集成。
  • 通过WebView嵌入前端页面:将前端页面直接嵌入到桌面应用中,实现无缝的GUI体验。
为了帮助开辟者更好地理解和实践这一方案,我创建了一个示例项目:fastapi-blog-tutorial。这个项目展示了如何使用FastAPI、WebView和当代前端技能构建一个完整的Python应用步伐,涵盖了从后端API设计到前端页面开辟的完整流程。如果你访问不了github,可以使用gitee链接 fastapi-blog-tutorial
接下来的讲授将基于此项目,逐步解说如何实现以下功能:


  • 使用FastAPI构建RESTful API和WebSocket服务。
  • 使用WebView将前端页面嵌入到Python应用中。
  • 使用当代前端技能(如HTML、CSS、JavaScript)构建雅观的用户界面。
  • 实现前后端的实时通讯和数据交互。
通过这个项目,你将掌握如何使用FastAPI和当代前端技能,快速构建当代化、高性能的Python应用步伐。无论你是Python开辟者,还是前端开辟者,都可以从中获得开导和实用的技巧。

架构畅想

1. 架构图

  1. +-------------------+       +-------------------+       +-------------------+
  2. |                   |       |                   |       |                   |
  3. |   FastAPI 后端    |<----->|   WebView 前端    |<----->|   现代前端技术    |
  4. |                   |       |                   |       |                   |
  5. +-------------------+       +-------------------+       +-------------------+
复制代码
2. 组件说明



  • FastAPI 后端

    • 提供RESTful API和WebSocket支持。
    • 处理惩罚业务逻辑、数据存储和通讯。
    • 通过uvicorn运行,支持高并发和实时通讯。

  • WebView 前端

    • 使用pywebview库将前端页面嵌入到桌面应用中。
    • 提供原生的窗口管理功能(如最小化、最大化、关闭)。
    • 支持与后端的双向通讯(通过HTTP API或WebSocket)。

  • 当代前端技能

    • 使用React、Vue、Angular等前端框架构建用户界面。
    • 使用Tailwind CSS、Bootstrap等CSS框架实现快速样式开辟。
    • 集成Three.js、D3.js等可视化库,实现复杂的数据可视化。
    • 通过WebSocket与后端实时交互,实现动态数据更新。


优缺点分析

优点


  • 开辟服从高

    • 前端使用当代前端技能,开辟速度快,样式定制灵活。
    • 后端使用FastAPI,代码简洁,易于维护。

  • 跨平台支持

    • WebView和当代前端技能天然支持跨平台,可以在Windows、macOS和Linux上运行。

  • 当代化UI

    • 使用当代前端技能(如Flexbox、CSS Grid、动画结果)实现雅观的用户界面。

  • 实时通讯

    • 通过WebSocket实现前后端的实时通讯,适合必要实时更新的应用场景。

  • 可扩展性强

    • 前后端分离,便于团队协作和功能扩展。

缺点


  • 性能开销

    • WebView和前端渲染必要肯定的体系资源,大概不适合对性能要求极高的场景。

  • 依靠欣赏器引擎

    • WebView依靠于体系或内置的欣赏器引擎,大概存在兼容性问题。

  • 打包体积较大

    • 由于必要嵌入欣赏器引擎,打包后的应用步伐体积较大。

  • 学习曲线

    • 必要掌握当代前端技能,对纯Python开辟者大概有肯定学习成本。


实用场景



  • 桌面应用:必要当代化UI的Python桌面应用步伐。
  • 实时数据展示:如监控体系、实时数据仪表盘。
  • 跨平台工具:必要在多个操作体系上运行的工具类应用。
  • 快速原型开辟:必要快速构建和迭代的应用场景。

通过这种方案,开辟者可以摆脱传统GUI库的束缚,充实使用当代前端技能的上风,快速构建当代化、高性能的Python应用步伐。如果你正在为Python GUI开辟而烦恼,不妨试试这种全新的解决方案!
环境预备

在开始之前,确保你已经安装了以下Python库:


  • fastapi
  • uvicorn
  • loguru
  • jinja2
你可以通过以下下令安装这些依靠:
  1. pip install fastapi uvicorn loguru jinja2
复制代码

项目布局

以下是项目的目次布局:
  1. .
  2. ├── fastapi-blog-tutorial/                 # 应用代码目录
  3. │   ├── main.py          # 主入口文件
  4. │   ├── routers/         # 路由定义
  5. │   ├── templates/       # 模板文件
  6. │   └── models/          # 数据模型(如果使用数据库)
  7. ├── tests/               # 测试代码目录
  8. ├── requirements.txt     # 项目依赖文件
  9. └── README.md            # 项目介绍文件
复制代码


  • main.py:主步伐入口,包含FastAPI应用实例。
  • app.py:路由发现及注册器。
  • routers/:存放全部路由模块的目次。
  • templates/:存放Jinja2模板文件的目次。
  • static/:存放静态文件的目次。

代码解析

1. WebSocket下令处理惩罚

我们实现了一个WebSocket服务器,支持多种下令处理惩罚。以下是核心代码:
1.1 导入必要的库

  1. # 导入必要的库和模块
  2. import datetime
  3. import json
  4. from fastapi import APIRouter, WebSocket, WebSocketDisconnect
  5. from loguru import logger
  6. # 创建一个API路由实例,用于定义WebSocket端点
  7. router = APIRouter()
  8. # 定义一个字典来存储命令及其对应的处理函数
  9. command_handlers = {}
复制代码
1.2 注册下令处理惩罚器

我们定义了一个装饰器register_command,用于注册下令处理惩罚器:
  1. def register_command(command):
  2.     """
  3.     注册命令处理器的装饰器函数。
  4.     参数:
  5.         command (str): 要注册的命令名称。
  6.     返回:
  7.         decorator (function): 实际的装饰器函数,用于包装命令处理函数。
  8.     """
  9.     def decorator(func):
  10.         # 记录正在注册的命令及其处理函数名
  11.         logger.debug(f"正在注册命令 '{command}',处理函数为 '{func.__name__}'")
  12.         command_handlers[command] = func
  13.         # 记录当前所有已注册的命令处理器
  14.         logger.info(
  15.             f"当前注册的命令处理器: {', '.join(f'{cmd}: {handler.__name__}' for cmd, handler in command_handlers.items())}")
  16.         return func  # 返回原始函数,不改变其行为
  17.     return decorator
复制代码
1.3 定义下令处理惩罚器

我们定义了三个示例下令处理惩罚器:
  1. @register_command('echo')
  2. async def echo_handler(message, websocket):
  3.     """
  4.     处理 'echo' 命令的异步函数。
  5.     参数:
  6.         message (str): 收到的消息内容。
  7.         websocket (WebSocket): WebSocket连接对象。
  8.     """
  9.     logger.debug(f"echo_handler 被调用,消息为: {message}")
  10.     try:
  11.         # 向客户端发送回显消息
  12.         await websocket.send_text(f"回显: {message}")
  13.     except Exception as e:
  14.         # 捕获并记录任何异常
  15.         logger.error(f"echo_handler 中发生错误: {e}")
  16. @register_command('custom')
  17. async def custom_handler(message, websocket):
  18.     """
  19.     处理 'custom' 命令的异步函数。
  20.     参数:
  21.         message (str): 收到的消息内容。
  22.         websocket (WebSocket): WebSocket连接对象。
  23.     """
  24.     logger.debug(f"custom_handler 被调用,消息为: {message}")
  25.     try:
  26.         # 向客户端发送自定义回显消息
  27.         await websocket.send_text(f"自定义回显: {message}")
  28.     except Exception as e:
  29.         # 捕获并记录任何异常
  30.         logger.error(f"custom_handler 中发生错误: {e}")
  31. @register_command('time')
  32. async def time_handler(message, websocket):
  33.     """
  34.     处理 'time' 命令的异步函数。
  35.     参数:
  36.         message (str): 收到的消息内容(本例中未使用)。
  37.         websocket (WebSocket): WebSocket连接对象。
  38.     """
  39.     logger.debug(f"time_handler 被调用,消息为: {message}")
  40.     try:
  41.         # 获取当前时间并格式化为ISO8601字符串
  42.         now = datetime.datetime.now().isoformat()
  43.         # 向客户端发送服务器时间
  44.         await websocket.send_text(f"服务器时间是 {now}")
  45.     except Exception as e:
  46.         # 捕获并记录任何异常
  47.         logger.error(f"time_handler 中发生错误: {e}")
复制代码
1.4 定义WebSocket路由

我们定义了一个WebSocket路由,处理惩罚客户端连接和消息:
  1. @router.websocket("/ws")
  2. async def websocket_endpoint(websocket: WebSocket):
  3.     """
  4.     WebSocket端点的主处理函数。
  5.     参数:
  6.         websocket (WebSocket): WebSocket连接对象。
  7.     """
  8.     await websocket.accept()  # 接受WebSocket连接
  9.     logger.info("正在处理连接")
  10.     try:
  11.         while True:
  12.             # 接收来自客户端的消息
  13.             data = await websocket.receive_text()
  14.             # 尝试将接收到的数据解析为JSON格式
  15.             try:
  16.                 request = json.loads(data)
  17.             except json.JSONDecodeError:
  18.                 logger.error(f"无法解码JSON消息: {data}")
  19.                 await websocket.send_text("无效的JSON格式")
  20.                 continue
  21.             # 提取命令和数据
  22.             command = request.get('command')
  23.             data = request.get('data', '')
  24.             # 检查命令是否合法
  25.             if command not in command_handlers:
  26.                 logger.warning(f"非法命令: {command}")
  27.                 await websocket.send_text("非法命令")
  28.                 continue
  29.             # 记录解析出的命令和数据
  30.             logger.debug(f"解析命令: {command}, 数据: {data}")
  31.             # 查找并调用相应的命令处理函数
  32.             if command in command_handlers:
  33.                 logger.debug(f"找到命令 '{command}',调用处理函数 '{command_handlers[command].__name__}'")
  34.                 await command_handlers[command](data, websocket)
  35.             else:
  36.                 logger.warning(f"未知命令: {command}")
  37.                 await websocket.send_text(f"未知命令: {command}")
  38.     except WebSocketDisconnect:
  39.         # 处理客户端断开连接的情况
  40.         logger.info("客户端已断开连接")
  41.     except Exception as e:
  42.         # 捕获并记录其他任何异常
  43.         logger.error(f"处理消息时发生错误: {data}, 错误: {e}")
复制代码

2. 模板渲染

我们使用Jinja2模板引擎渲染动态HTML页面。以下是核心代码:
2.1 导入必要的库

  1. from fastapi import APIRouter, Request
  2. from fastapi.templating import Jinja2Templates
  3. # 创建一个APIRouter实例,用于定义API的路由
  4. router = APIRouter()
  5. # 初始化Jinja2Templates实例,指定模板文件的目录
  6. templates = Jinja2Templates(directory='templates')
复制代码
2.2 定义HTTP路由

我们定义了三个HTTP路由,用于渲染HTML页面:
  1. # 定义根路径的GET请求处理函数
  2. # 返回index.html模板,同时传入一个空的request对象
  3. @router.get("/")
  4. async def get():
  5.     # 使用TemplateResponse方法渲染index.html模板,并传递一个空的request对象
  6.     return templates.TemplateResponse("index.html", {"request": {}})
  7. # 定义/test路径的GET请求处理函数
  8. # 在控制台打印"test",并返回"test"作为HTTP响应
  9. @router.get("/test")
  10. async def test():
  11.     # 在控制台中打印 "test"
  12.     print("test")
  13.     # 返回 "test" 作为响应
  14.     return "test"
  15. # 定义/info路径的GET请求处理函数
  16. # 接收Request对象作为参数,用于获取请求相关数据
  17. @router.get("/info")
  18. async def info(request: Request):
  19.     # 下面是用于演示的变量,包含不同类型的参数
  20.     custom_param = "这是一个自定义参数"  # 自定义字符串参数
  21.     number_param = 42  # 整数参数
  22.     list_param = ["item1", "item2", "item3"]  # 列表参数
  23.     dict_param = {"key1": "value1", "key2": "value2"}  # 字典参数
  24.     # 构造静态文件的URL,这里假设你有一个 style.css 文件在 static 目录下
  25.     static_file_url = request.url_for('static', path='style.css')
  26.     # 返回info.html模板,传入request对象和多个参数
  27.     return templates.TemplateResponse("info.html", {
  28.         "request": request,  # 传递request对象,用于模板渲染
  29.         "custom_param": custom_param,  # 传递自定义字符串参数
  30.         "number_param": number_param,  # 传递整数参数
  31.         "list_param": list_param,  # 传递列表参数
  32.         "dict_param": dict_param,  # 传递字典参数
  33.         "static_file_url": static_file_url  # 传递静态文件的URL
  34.     })
复制代码

3. APP路由注册中转

在app.py中,我们使用主动注册函数发现并注册全部路由地点,并挂载静态资源:
  1. # 导入必要的模块
  2. from importlib import import_module  # 动态导入模块
  3. from pathlib import Path  # 操作文件路径
  4. from pkgutil import iter_modules  # 遍历包中的模块
  5. from fastapi import FastAPI, APIRouter  # 创建FastAPI应用和API路由
  6. from fastapi.staticfiles import StaticFiles  # 提供静态文件服务
  7. from loguru import logger  # 记录日志
  8. # 创建FastAPI应用实例
  9. app = FastAPI()
  10. # 挂载静态文件目录,使得可以通过/static/访问静态资源
  11. app.mount("/static", StaticFiles(directory="static"), name="static")
  12. # 定义一个字典用于缓存已导入的模块,避免重复导入
  13. _imported_modules = {}
  14. def register_routers(package_name='routers'):
  15.     """
  16.     自动注册指定包下的所有API路由。
  17.     参数:
  18.         package_name (str): 包含API路由的包名,默认为'routers'
  19.     """
  20.     # 获取当前文件所在目录,并拼接上包名得到包的实际路径
  21.     package_dir = Path(__file__).resolve().parent / package_name
  22.     # 记录正在注册路由的日志信息
  23.     logger.info(f"正在注册路由,包目录: {package_dir}")
  24.     try:
  25.         # 遍历包中的所有模块
  26.         for (_, module_name, _) in iter_modules([str(package_dir)]):
  27.             # 如果模块已经导入过,则直接使用缓存中的模块
  28.             if module_name in _imported_modules:
  29.                 module = _imported_modules[module_name]
  30.             else:
  31.                 # 否则动态导入模块并缓存
  32.                 module = import_module(f"{package_name}.{module_name}")
  33.                 _imported_modules[module_name] = module
  34.             # 记录成功导入模块的日志信息
  35.             logger.debug(f"导入模块: {module_name}")
  36.             # 尝试从模块中获取名为'router'的对象
  37.             router = getattr(module, 'router', None)
  38.             # 如果获取到的对象是APIRouter实例,则将其注册到FastAPI应用中
  39.             if isinstance(router, APIRouter):
  40.                 app.include_router(router)
  41.                 logger.debug(f"已注册路由: {module_name}")
  42.             else:
  43.                 # 如果未找到有效的APIRouter实例,记录警告日志
  44.                 logger.warning(f"模块 {module_name} 没有找到有效的 APIRouter 实例")
  45.     except Exception as e:
  46.         # 如果发生任何异常,记录错误日志
  47.         logger.error(f"注册路由时发生错误: {e}")
  48. # 调用函数注册所有路由
  49. register_routers()
复制代码

4. 主步伐入口

在main.py中,我们整合了全部路由并启动FastAPI应用:
  1. # 导入必要的库和模块
  2. import os  # 提供与操作系统交互的功能,如文件路径操作
  3. import threading  # 允许程序并发运行多个线程,用于同时启动服务器和webview窗口
  4. import time  # 提供时间处理函数,如休眠
  5. import argparse  # 解析命令行参数
  6. import uvicorn  # 用于运行FastAPI应用的ASGI服务器实现
  7. import webview  # 创建原生桌面GUI窗口,用于显示HTML页面
  8. from loguru import logger  # 强大的日志记录库,提供简洁的日志输出
  9. from app import app  # 导入FastAPI应用实例
  10. class WebSocketServer:
  11.     def __init__(self, **kwargs):
  12.         """
  13.         初始化WebSocketServer类的实例。
  14.         参数:
  15.             kwargs (dict): 包含配置项的字典,支持以下键值:
  16.                 - host (str): 服务器监听地址,默认为 '0.0.0.0'
  17.                 - port (int): 服务器监听端口,默认为 8000
  18.                 - index_path (str): 网页入口文件路径,默认为 "templates/index.html"
  19.         """
  20.         self.host = kwargs.get('host', '0.0.0.0')  # 设置服务器监听地址
  21.         self.port = kwargs.get('port', 8000)  # 设置服务器监听端口
  22.         self.index_path = kwargs.get('index_path', os.path.join("templates", "index.html"))  # 设置网页入口文件路径
  23.         self.app = app  # 绑定FastAPI应用实例
  24.     def start_webview(self):
  25.         """
  26.         启动webview窗口,加载指定的HTML文件。
  27.         """
  28.         webview.create_window('Web Socket Example', str(self.index_path), width=1000, height=800, resizable=True)
  29.         # 创建一个名为'Web Socket Example'的窗口,加载index_path指定的HTML文件,
  30.         # 设置窗口宽度为1000px,高度为800px,并允许调整大小
  31.         webview.start(self.start_server, gui='cef')  # 启动webview事件循环,保持窗口打开,并在启动时调用start_server方法
  32.     def start_server(self):
  33.         """
  34.         启动FastAPI服务器。
  35.         """
  36.         uvicorn.run(self.app, host=self.host, port=self.port)
  37.         # 使用uvicorn运行绑定的FastAPI应用实例,
  38.         # 监听指定的host和port
  39. if __name__ == "__main__":
  40.     # 创建参数解析器
  41.     parser = argparse.ArgumentParser(description='启动WebSocket服务器和Webview窗口')
  42.     parser.add_argument('--server', action='store_true', help='仅启动服务器')
  43.     parser.add_argument('--host', default='0.0.0.0', help='服务器监听地址')
  44.     parser.add_argument('--port', type=int, default=8000, help='服务器监听端口')
  45.     parser.add_argument('--index-path', default=os.path.join("templates", "index.html"), help='网页入口文件路径')
  46.     # 解析命令行参数
  47.     args = parser.parse_args()
  48.     # 检查 index 文件是否存在
  49.     if not os.path.exists(args.index_path):
  50.         logger.error(f"文件 {args.index_path} 不存在,无法启动服务器。")
  51.     else:
  52.         logger.info(
  53.             f"服务器已启动,地址为 http://localhost:{args.port} 和 ws://localhost:{args.port}/ws")
  54.         # 实例化WebSocketServer类
  55.         server = WebSocketServer(host=args.host, port=args.port, index_path=args.index_path)
  56.         if args.server:
  57.             # 仅启动服务器
  58.             server.start_server()
  59.         else:
  60.             # 启动webview窗口
  61.             server.start_webview()
复制代码

网页部分

1. 前端页面设计

我们使用HTML、CSS和JavaScript实现了一个雅观的WebSocket客户端页面。以下是核心代码:
1.1 HTML布局

[code]<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket 客户端 v1.1</title>
    <link rel="stylesheet" href="/static/style.css">
</head>
<body>
    <h1>WebSocket 客户端 v1.1</h1>
    <div id="messages"></div>
    <div class="button-container">
        <button class="button" onclick="sendMessage('echo', 'Hello Server!')">

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

光之使者

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表