文章目录
-
- [1. 创建 Conda 环境并安装 Poetry](#1. 创建 Conda 环境并安装 Poetry)
- [2. 创建 Poetry 项目](#2. 创建 Poetry 项目)
- [3. 安装依赖](#3. 安装依赖)
- [4. 创建项目文件](#4. 创建项目文件)
-
- [创建 WebSocket 服务端 (`app/server.py`)](#创建 WebSocket 服务端 (
app/server.py)) - [创建 FastAPI 应用(带 WebSocket)(`app/main.py`)](#创建 FastAPI 应用(带 WebSocket)(
app/main.py)) - [创建简单 WebSocket 客户端 (`app/client.py`)](#创建简单 WebSocket 客户端 (
app/client.py)) - 创建启动脚本 (`run.py`)
- [创建 WebSocket 服务端 (`app/server.py`)](#创建 WebSocket 服务端 (
- [5. 安装和运行](#5. 安装和运行)
- [6. 测试 WebSocket](#6. 测试 WebSocket)
- [7. Poetry vs requirements.txt](#7. Poetry vs requirements.txt)
1. 创建 Conda 环境并安装 Poetry
bash
# 创建新的 conda 环境
conda create -n py312 python=3.12
# 激活环境
conda activate py312
# 安装 poetry(如果还没安装)
pip install poetry
2. 创建 Poetry 项目
bash
# 创建项目目录
mkdir py-toys
cd py-toys
# 初始化 poetry 项目
poetry init --name=py-toys --description="python demo project" --author="Your Name" --python="^3.12" --no-interaction
# 配置使用当前 conda 环境的 python
poetry env use $(which python)
也可以使用 poetry new xxx 命令快速创建一个 Poetry 项目, 例如:
bash
.
└── xxx
├── README.md
├── pyproject.toml
├── src
│ └── xxx
│ └── __init__.py
└── tests
└── __init__.py
此时项目中就有了 pyproject.toml 文件, 内容如下:
toml
[project]
name = "py-toys"
version = "0.1.0"
description = "python demo project"
authors = [
{name = "Your Name"}
]
requires-python = "^3.12"
dependencies = [
]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
3. 安装依赖
bash
# 添加 WebSocket 相关依赖
poetry add websockets fastapi uvicorn
# 添加开发依赖
poetry add pytest --group dev
此时项目中就有了 poetry.lock 文件, 并且 pyproject.toml 文件中添加了依赖信息 dependencies 与 dependency-groups,
还有个地方要注意的是,加上 package-mode = false, 是为了确保 poetry install 只会安装依赖,不会尝试安装项目本身,不然 poetry install 会报错。
toml
[project]
name = "py-toys"
version = "0.1.0"
description = "python demo project"
authors = [
{name = "Your Name"}
]
requires-python = "^3.12"
dependencies = [
"websockets (>=16.0,<17.0)",
"fastapi (>=0.135.3,<0.136.0)",
"uvicorn (>=0.42.0,<0.43.0)"
]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
[dependency-groups]
dev = [
"pytest (>=9.0.2,<10.0.0)"
]
# 保持 PEP 621 格式并禁用包模式, 这样 poetry install 只会安装依赖,不会尝试安装项目本身
[tool.poetry]
package-mode = false
4. 创建项目文件
确保项目结构如下:
log
py-toys/
├── pyproject.toml
├── poetry.lock
├── run.py
├── app/
│ ├── __init__.py
│ ├── server.py
│ ├── main.py
│ └── client.py
└── tests/
└── __init__.py
创建 WebSocket 服务端 (app/server.py)
python
import asyncio
import json
import websockets
from datetime import datetime
class WebSocketServer:
def __init__(self):
self.clients = set()
async def register(self, websocket):
"""注册新客户端"""
self.clients.add(websocket)
print(f"客户端连接,当前连接数: {len(self.clients)}")
async def unregister(self, websocket):
"""注销客户端"""
self.clients.remove(websocket)
print(f"客户端断开,当前连接数: {len(self.clients)}")
async def broadcast(self, message):
"""广播消息给所有客户端"""
if self.clients:
await asyncio.wait([client.send(message) for client in self.clients])
async def handle_client(self, websocket):
"""处理单个客户端连接"""
await self.register(websocket)
try:
async for message in websocket:
# 解析接收到的消息
data = json.loads(message)
print(f"收到消息: {data}")
# 处理消息
response = {
"type": "response",
"message": f"服务端收到: {data.get('message', '')}",
"timestamp": datetime.now().isoformat(),
"client_count": len(self.clients)
}
# 发送响应给发送者
await websocket.send(json.dumps(response))
# 广播给其他客户端(可选)
# await self.broadcast(json.dumps(response))
except websockets.exceptions.ConnectionClosed:
pass
finally:
await self.unregister(websocket)
async def main():
server = WebSocketServer()
async with websockets.serve(server.handle_client, "localhost", 8765):
print("WebSocket 服务启动: ws://localhost:8765")
await asyncio.Future() # 保持运行
if __name__ == "__main__":
asyncio.run(main())
创建 FastAPI 应用(带 WebSocket)(app/main.py)
python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
import json
from datetime import datetime
app = FastAPI(title="WebSocket Demo")
class ConnectionManager:
def __init__(self):
self.active_connections: list[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def send_personal_message(self, message: str, websocket: WebSocket):
await websocket.send_text(message)
async def broadcast(self, message: str):
for connection in self.active_connections:
await connection.send_text(message)
manager = ConnectionManager()
# 简单的 HTML 页面
html = """
<!DOCTYPE html>
<html>
<head>
<title>WebSocket 聊天室</title>
</head>
<body>
<h1>WebSocket 聊天室</h1>
<div>
<input type="text" id="message" placeholder="输入消息...">
<button onclick="sendMessage()">发送</button>
</div>
<div id="messages" style="border:1px solid #ccc; margin-top:20px; padding:10px; height:300px; overflow-y:scroll;"></div>
<script>
const ws = new WebSocket("ws://localhost:8000/ws");
const messages = document.getElementById("messages");
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
const messageDiv = document.createElement("div");
messageDiv.textContent = `[${data.timestamp}] ${data.message}`;
messages.appendChild(messageDiv);
messages.scrollTop = messages.scrollHeight;
};
function sendMessage() {
const input = document.getElementById("message");
const message = input.value;
if (message) {
ws.send(JSON.stringify({
message: message,
timestamp: new Date().toISOString()
}));
input.value = "";
}
}
</script>
</body>
</>
"""
@app.get("/")
async def get():
return HTMLResponse(html)
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await manager.connect(websocket)
try:
while True:
data = await websocket.receive_text()
# 解析消息
import json
message_data = json.loads(data)
# 准备响应
response = {
"message": f"收到: {message_data.get('message', '')}",
"timestamp": datetime.now().strftime("%H:%M:%S"),
"client_count": len(manager.active_connections)
}
# 广播给所有连接的客户端
await manager.broadcast(json.dumps(response))
except WebSocketDisconnect:
manager.disconnect(websocket)
await manager.broadcast(json.dumps({
"message": "客户端断开连接",
"timestamp": datetime.now().strftime("%H:%M:%S")
}))
创建简单 WebSocket 客户端 (app/client.py)
python
import asyncio
import websockets
import json
async def client():
uri = "ws://localhost:8765"
async with websockets.connect(uri) as websocket:
# 发送消息
message = {"message": "Hello, WebSocket!", "client": "Python客户端"}
await websocket.send(json.dumps(message))
print(f"发送: {message}")
# 接收响应
response = await websocket.recv()
print(f"收到: {response}")
if __name__ == "__main__":
asyncio.run(client())
创建启动脚本 (run.py)
python
import uvicorn
if __name__ == "__main__":
uvicorn.run(
"app.main:app",
host="127.0.0.1",
port=8000,
reload=True,
log_level="info"
)
5. 安装和运行
bash
# 安装所有依赖
poetry install
# 运行 FastAPI 应用(带 WebSocket 界面)
poetry run python run.py
# 访问 http://localhost:8000
# 或者在另一个终端运行纯 WebSocket 服务端
poetry run python app/server.py
# 运行客户端测试
poetry run python app/client.py
6. 测试 WebSocket
- Web 界面测试 :打开浏览器访问
http://localhost:8000,在聊天框中输入消息 - 命令行测试 :运行
poetry run python app/client.py发送测试消息 - 多客户端测试:打开多个浏览器标签页,观察消息广播
结果展示
-
多客户端测试:

-
命令行测试:
服务端:
log(py312) root$ poetry run python app/server.py WebSocket 服务启动: ws://localhost:8765 客户端连接,当前连接数: 1 收到消息: {'message': 'Hello, WebSocket!', 'client': 'Python客户端'} 客户端断开,当前连接数: 0客户端:
log(py312) root$ poetry run python app/client.py 发送: {'message': 'Hello, WebSocket!', 'client': 'Python客户端'} 收到: {"type": "response", "message": "\u670d\u52a1\u7aef\u6536\u5230: Hello, WebSocket!", "timestamp": "2026-04-02T10:45:19.604730", "client_count": 1}
这个示例项目展示了:
- 纯 WebSocket 服务器(
server.py) - FastAPI + WebSocket 集成(
main.py) - WebSocket 客户端示例(
client.py) - 实时消息广播
- 连接管理
7. Poetry vs requirements.txt
Poetry 的优势
Poetry 使用 pyproject.toml 和 poetry.lock 替代了传统方案:
| 传统方式 | Poetry 方式 |
|---|---|
requirements.txt |
pyproject.toml(依赖声明) |
requirements-dev.txt |
pyproject.toml 中的 dev 分组 |
pip freeze > requirements.txt |
poetry.lock(自动锁定版本) |
| 手动管理 | 自动管理 |
如果仍需要 requirements.txt
某些场景(如 Docker 构建、CI/CD)可能需要,可以导出:
方法一:使用 pip freeze(临时方案)
bash
# 激活 poetry 虚拟环境后
poetry run pip freeze > requirements.txt
# 或者直接导出(如果当前在 poetry 环境中)
pip freeze > requirements.txt
requirements.txt 示例:
text
annotated-doc==0.0.4
annotated-types==0.7.0
anyio==4.13.0
click==8.3.1
fastapi==0.135.3
h11==0.16.0
idna==3.11
iniconfig==2.3.0
packaging==26.0
pluggy==1.6.0
pydantic==2.12.5
pydantic_core==2.41.5
Pygments==2.20.0
pytest==9.0.2
starlette==1.0.0
typing-inspection==0.4.2
typing_extensions==4.15.0
uvicorn==0.42.0
websockets==16.0
方法二:安装 export 插件(推荐)
Poetry 2.0 版本开始,export 命令不再默认内置,而是被拆分到了一个独立的插件中
bash
# 安装 export 插件
poetry self add poetry-plugin-export
# 然后重新运行导出命令
poetry export -f requirements.txt --output requirements.txt --without-hashes
requirements.txt 示例:
text
annotated-doc==0.0.4 ; python_version >= "3.12" and python_version < "4.0"
annotated-types==0.7.0 ; python_version >= "3.12" and python_version < "4.0"
anyio==4.13.0 ; python_version >= "3.12" and python_version < "4.0"
click==8.3.1 ; python_version >= "3.12" and python_version < "4.0"
colorama==0.4.6 ; python_version >= "3.12" and python_version < "4.0" and platform_system == "Windows"
fastapi==0.135.3 ; python_version >= "3.12" and python_version < "4.0"
h11==0.16.0 ; python_version >= "3.12" and python_version < "4.0"
idna==3.11 ; python_version >= "3.12" and python_version < "4.0"
pydantic-core==2.41.5 ; python_version >= "3.12" and python_version < "4.0"
pydantic==2.12.5 ; python_version >= "3.12" and python_version < "4.0"
starlette==1.0.0 ; python_version >= "3.12" and python_version < "4.0"
typing-extensions==4.15.0 ; python_version >= "3.12" and python_version < "4.0"
typing-inspection==0.4.2 ; python_version >= "3.12" and python_version < "4.0"
uvicorn==0.42.0 ; python_version >= "3.12" and python_version < "4.0"
websockets==16.0 ; python_version >= "3.12" and python_version < "4.0"
方法三:在项目中声明插件依赖(便于团队共享)
在 pyproject.toml 中添加以下内容:
toml
[tool.poetry.requires-plugins]
poetry-plugin-export = ">=1.8"
然后运行 poetry install,插件会自动安装。导出仍按方法二导出执行。
实际建议
纯 Poetry 项目 :直接用 pyproject.toml + poetry.lock
- 团队成员:
poetry install - 部署:
poetry install --no-dev
混合场景:
- 需要兼容 pip 时,可以保留导出的
requirements.txt - CI/CD 工具不支持 Poetry 时,导出使用
注意事项
- 不要同时维护
pyproject.toml和requirements.txt,会造成依赖不同步 poetry export生成的requirements.txt是单向的,不能反向导入到 Poetry- 如果团队统一使用 Poetry,完全不需要
requirements.txt