feat(python3.10): 适配 Python 3.10 并修复 SNMP 问题
修改内容: 1. 修复 SNMP 凭据显示问题:确保设备列表返回 snmp_credential_id 2. 修复清除 SNMP 凭据功能:新增 clear_snmp_credential 参数 3. 前端适配:处理清除凭据时的参数转换 4. 更新 requirements.txt:添加 Python 3.10 兼容说明和 typing-extensions 5. 添加 PYTHON_3_10_COMPATIBILITY.md 兼容性说明文档 验证: - 所有代码无 Python 3.11 特有语法 - 依赖包版本均支持 Python 3.10 - typing-extensions 提供向后兼容支持
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
# Python 3.10 兼容性说明
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
本分支 (python3.10) 专门针对 Python 3.10 环境优化,确保所有代码和依赖都能在 Python 3.10 下正常运行。
|
||||||
|
|
||||||
|
## 兼容性调整
|
||||||
|
|
||||||
|
### 已验证的兼容特性
|
||||||
|
✅ 所有依赖包版本均支持 Python 3.10
|
||||||
|
✅ 无 Python 3.11 特有语法(如 `tomllib`、`asyncio.TaskGroup`、`typing.Self` 等)
|
||||||
|
✅ 类型注解完全兼容 Python 3.10
|
||||||
|
✅ `match` 关键字未用作 Python 3.10 的模式匹配语句(仅用作 `re.match()` 的变量名)
|
||||||
|
|
||||||
|
### 依赖版本要求
|
||||||
|
- `typing-extensions>=4.5.0` - 提供额外的类型支持
|
||||||
|
- 所有核心依赖(FastAPI、SQLAlchemy、Pydantic 等)均已验证兼容
|
||||||
|
|
||||||
|
## 已知的 Python 3.11+ 特性未使用
|
||||||
|
项目中未使用以下 Python 3.11+ 特有的功能:
|
||||||
|
- `tomllib` 标准库(如有需要可使用 `tomli` 第三方库)
|
||||||
|
- `asyncio.TaskGroup`
|
||||||
|
- `typing.Self`
|
||||||
|
- `ExceptionGroup` / `except*`
|
||||||
|
- `StrEnum` / `IntEnum` 的新特性
|
||||||
|
|
||||||
|
## 安装说明
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## 升级提示
|
||||||
|
如果未来需要升级到 Python 3.11+,可以:
|
||||||
|
1. 使用 `tomllib` 替代可能的 TOML 解析需求
|
||||||
|
2. 考虑使用 `asyncio.TaskGroup` 改进异步代码结构
|
||||||
|
3. 使用 `typing.Self` 简化类型注解
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
可以使用以下命令验证 Python 版本兼容性:
|
||||||
|
```bash
|
||||||
|
python --version # 应为 3.10.x
|
||||||
|
python -c "import sys; assert sys.version_info >= (3, 10), 'Python 3.10+ required'"
|
||||||
|
```
|
||||||
@@ -248,6 +248,7 @@ def get_network_devices(
|
|||||||
# 补充手写的关联字段(datetime 已经由 serialize_dt_fields 序列化)
|
# 补充手写的关联字段(datetime 已经由 serialize_dt_fields 序列化)
|
||||||
item["device_type"] = d.device_type.value if hasattr(d.device_type, 'value') else d.device_type
|
item["device_type"] = d.device_type.value if hasattr(d.device_type, 'value') else d.device_type
|
||||||
item["credential_name"] = cred_map.get(d.snmp_credential_id)
|
item["credential_name"] = cred_map.get(d.snmp_credential_id)
|
||||||
|
item["snmp_credential_id"] = d.snmp_credential_id # 显式添加,确保前端能拿到
|
||||||
item["last_polled_at"] = item.get("last_polled_at") # 已是 ISO 字符串
|
item["last_polled_at"] = item.get("last_polled_at") # 已是 ISO 字符串
|
||||||
item["last_successful_poll"] = item.get("last_successful_poll")
|
item["last_successful_poll"] = item.get("last_successful_poll")
|
||||||
item["arp_poll_interval"] = d.arp_poll_interval
|
item["arp_poll_interval"] = d.arp_poll_interval
|
||||||
@@ -325,6 +326,7 @@ def update_network_device(
|
|||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
ip_address: Optional[str] = None,
|
ip_address: Optional[str] = None,
|
||||||
snmp_credential_id: Optional[int] = None,
|
snmp_credential_id: Optional[int] = None,
|
||||||
|
clear_snmp_credential: bool = False, # 新增:专门的标志
|
||||||
port: Optional[int] = None,
|
port: Optional[int] = None,
|
||||||
device_type: Optional[str] = None,
|
device_type: Optional[str] = None,
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
@@ -350,7 +352,13 @@ def update_network_device(
|
|||||||
device.name = name
|
device.name = name
|
||||||
if ip_address:
|
if ip_address:
|
||||||
device.ip_address = ip_address
|
device.ip_address = ip_address
|
||||||
if snmp_credential_id is not None:
|
# 处理 SNMP 凭据:
|
||||||
|
# - clear_snmp_credential=True → 清除凭据
|
||||||
|
# - snmp_credential_id > 0 → 设置为该值
|
||||||
|
# - 否则(都不做任何操作
|
||||||
|
if clear_snmp_credential:
|
||||||
|
device.snmp_credential_id = None
|
||||||
|
elif snmp_credential_id is not None and snmp_credential_id > 0:
|
||||||
device.snmp_credential_id = snmp_credential_id
|
device.snmp_credential_id = snmp_credential_id
|
||||||
if port:
|
if port:
|
||||||
device.port = port
|
device.port = port
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from datetime import datetime
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pysnmp.hlapi import (
|
from pysnmp.hlapi.asyncio import (
|
||||||
SnmpEngine, CommunityData, UsmUserData,
|
SnmpEngine, CommunityData, UsmUserData,
|
||||||
UdpTransportTarget, ContextData,
|
UdpTransportTarget, ContextData,
|
||||||
ObjectType, ObjectIdentity,
|
ObjectType, ObjectIdentity,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# Python 3.10+ 兼容版本
|
||||||
fastapi==0.110.0
|
fastapi==0.110.0
|
||||||
uvicorn[standard]==0.27.1
|
uvicorn[standard]==0.27.1
|
||||||
sqlalchemy==2.0.28
|
sqlalchemy==2.0.28
|
||||||
@@ -15,3 +16,6 @@ python-dotenv==1.0.1
|
|||||||
passlib[bcrypt]==1.7.4
|
passlib[bcrypt]==1.7.4
|
||||||
httpx==0.27.0
|
httpx==0.27.0
|
||||||
python-jose[cryptography]
|
python-jose[cryptography]
|
||||||
|
bcrypt==3.2.2
|
||||||
|
# typing-extensions 提供 Python 3.10+ 的向后兼容支持
|
||||||
|
typing-extensions>=4.5.0
|
||||||
|
|||||||
@@ -398,7 +398,13 @@ const saveDevice = async () => {
|
|||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
if (isDeviceEdit.value) {
|
if (isDeviceEdit.value) {
|
||||||
await snmpApi.updateDevice(deviceForm.id, deviceForm)
|
// 处理清除凭据:snmp_credential_id=null 时发送 clear_snmp_credential=true
|
||||||
|
const updateData = { ...deviceForm }
|
||||||
|
if (updateData.snmp_credential_id === null) {
|
||||||
|
delete updateData.snmp_credential_id
|
||||||
|
updateData.clear_snmp_credential = true
|
||||||
|
}
|
||||||
|
await snmpApi.updateDevice(deviceForm.id, updateData)
|
||||||
ElMessage.success('更新成功')
|
ElMessage.success('更新成功')
|
||||||
} else {
|
} else {
|
||||||
await snmpApi.createDevice(deviceForm)
|
await snmpApi.createDevice(deviceForm)
|
||||||
|
|||||||
Reference in New Issue
Block a user