Miniconda + Poetry 实战

文章目录

    • [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`)
    • [5. 安装和运行](#5. 安装和运行)
    • [6. 测试 WebSocket](#6. 测试 WebSocket)
    • [7. Poetry vs requirements.txt](#7. Poetry vs requirements.txt)
      • [Poetry 的优势](#Poetry 的优势)
      • [如果仍需要 requirements.txt](#如果仍需要 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 文件中添加了依赖信息 dependenciesdependency-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

  1. Web 界面测试 :打开浏览器访问 http://localhost:8000,在聊天框中输入消息
  2. 命令行测试 :运行 poetry run python app/client.py 发送测试消息
  3. 多客户端测试:打开多个浏览器标签页,观察消息广播

结果展示

  1. 多客户端测试:

  2. 命令行测试:

    服务端:

    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.tomlpoetry.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.tomlrequirements.txt,会造成依赖不同步
  • poetry export 生成的 requirements.txt 是单向的,不能反向导入到 Poetry
  • 如果团队统一使用 Poetry,完全不需要 requirements.txt
相关推荐
深海空无一人2 小时前
python基础
开发语言·python
极光代码工作室2 小时前
基于NLP的电商评论情感分析系统
python·深度学习·自然语言处理·情感分析·文本挖掘
大尚来也2 小时前
Java多线程实战:从基础创建到返回值获取的深度解析
开发语言
csdn2015_2 小时前
List<DocumentMetadata> 取所有docid,组成List<String>
windows·python·list
沐知全栈开发2 小时前
jQuery 后代选择器详解
开发语言
liuyao_xianhui2 小时前
优选算法_岛屿的最大面积_floodfill算法_C++
java·开发语言·数据结构·c++·算法·leetcode·链表
清水白石0082 小时前
《Python 静态检查链:格式化、Lint、类型检查、安全扫描全攻略——CI 阻断策略与团队平衡实践》
python·安全·ci/cd
xvhao20132 小时前
C++freopen的用法
开发语言·c++
co_wait2 小时前
【C语言】字符串处理函数
c语言·开发语言