fix(mypy+ci): 清零 mypy 存量报错 + 修复 Integration Tests PG 依赖 #1637

Merged
xiaoxia merged 19 commits from fix/ci-mypy-integ into develop 2026-09-03 10:40:28 +08:00
14 changed files with 76 additions and 120 deletions
+2 -2
View File
@@ -334,7 +334,7 @@ jobs:
PIP_NO_CACHE_DIR: ''
DATABASE_URL: postgresql+psycopg://postgres:postgres@host.docker.internal:5432/xiaoxia_saas
USE_IN_MEMORY_DB: 'false'
CI_USE_SHARED_PG: 'true'
CI_USE_SHARED_PG: 'false'
permissions:
contents: read
steps:
@@ -518,7 +518,7 @@ jobs:
env:
DATABASE_URL: postgresql+psycopg://postgres:postgres@host.docker.internal:5432/xiaoxia_saas
USE_IN_MEMORY_DB: 'false'
CI_USE_SHARED_PG: 'true'
CI_USE_SHARED_PG: 'false'
OSS_ACCESS_KEY_ID: placeholder
OSS_ACCESS_KEY_SECRET: placeholder
OSS_BUCKET_NAME: xiaoxia-autocut
+1 -1
View File
@@ -290,7 +290,7 @@ def list_assets(
else:
total = asset_repository.count_by_project_ids(project_ids, status=status_list)
# 跨项目分页:逐项目累积直到凑够一页
paged_items: list = []
paged_items = []
offset = skip
remaining = limit
for pid in project_ids:
+1 -1
View File
@@ -456,7 +456,7 @@ async def wechat_callback(
user = user_repository.find_by_id(response.user_id)
binding_complete = False
if user:
binding_complete = (
binding_complete = bool(
user.phone_verified and user.email_verified and user.email and "@wechat.local" not in user.email
)
+3 -3
View File
@@ -513,7 +513,7 @@ def generate_cover(
if generation_task_id:
try:
task = gen_task_repo.get(generation_task_id)
if task and getattr(task, "cover_url", ""):
if task and getattr(task, "cover_url", ""): # type: ignore[arg-type]
cover_url_from_task = task.cover_url
logger.info(
"[封面生成] 统一管道封面(步骤A-direct): plan_id=%s task_id=%s url=%s",
@@ -538,7 +538,7 @@ def generate_cover(
gv_task_id = getattr(gv, "generation_task_id", "") or ""
if gv_task_id:
task_a2 = gen_task_repo.get(gv_task_id)
if task_a2 and getattr(task_a2, "cover_url", ""):
if task_a2 and getattr(task_a2, "cover_url", ""): # type: ignore[arg-type]
cover_url_from_task = task_a2.cover_url
logger.info(
"[封面生成] 封面(步骤A2-video-task): plan_id=%s video_id=%s url=%s",
@@ -747,7 +747,7 @@ def generate_cover(
if cover_url_from_task:
# 标题已在预览视频渲染时烧录(ASS字幕),封面帧自然包含标题
cover_data = {
cover_data: dict[str, object] = { # type: ignore[no-redef]
"type": "ai_frame",
"image_url": cover_url_from_task,
"frame_time": 0.0,
@@ -98,7 +98,7 @@ def _resolve_strategy_id_from_template(template_id: str, db: Session, user_id: s
try:
new_repo = SQLAlchemyEditTemplateRepository(db)
new_template = new_repo.get(template_id)
if new_template and getattr(new_template, "editing_mode", ""):
if new_template and getattr(new_template, "editing_mode", ""): # type: ignore[arg-type]
mode = new_template.editing_mode.strip()
if mode:
logger.info(
+1 -1
View File
@@ -385,7 +385,7 @@ def create_generation_task(
use_case = CreateGenerationTaskUseCase(generation_task_repository)
count = request.count
created_tasks = []
created_tasks: list = []
failed_tasks = []
user_id = authenticated_user.user.id
# 同批次任务共享 batch_id,用于视频查重时批次内比对
@@ -1056,8 +1056,8 @@ def _update_mediakit_recommendations_async( # pragma: no cover
try:
existing_meta = dict(getattr(asset, "metadata", None) or {})
existing_meta["scene_change_points"] = scene_changes
asset.metadata = existing_meta
asset_repo.update(asset)
asset.metadata = existing_meta # type: ignore[attr-defined]
asset_repo.update(asset) # type: ignore[arg-type]
logger.info(
"后台任务: 场景点已写入素材缓存: asset_id=%s points=%d",
asset_id,
@@ -41,17 +41,17 @@ def list_editor_transition_presets(
_: AuthenticatedUser = Depends(get_current_user),
) -> TransitionPresetListResponse:
"""获取转场预设列表"""
from packages.domain.transition_presets import TRANSITION_PRESETS
from packages.domain.transition_presets import TRANSITION_PRESET_LIBRARY
items = [
{
"id": p["id"],
"name": p["name"],
"category": p.get("category", "通用"),
"duration": p.get("default_duration", 0.5),
"description": p.get("description", ""),
"id": p.id,
"name": p.name,
"category": p.category,
"duration": p.default_duration,
"description": p.description,
}
for p in TRANSITION_PRESETS
for p in TRANSITION_PRESET_LIBRARY
]
return TransitionPresetListResponse(items=items, total=len(items))
@@ -123,17 +123,17 @@ def list_editor_filter_presets(
_: AuthenticatedUser = Depends(get_current_user),
) -> FilterPresetListResponse:
"""获取滤镜预设列表"""
from packages.domain.filter_presets import FILTER_PRESETS
from packages.domain.filter_presets import FILTER_PRESET_LIBRARY
items = [
{
"id": p["id"],
"name": p["name"],
"category": p.get("category", "通用"),
"thumbnail": p.get("thumbnail", ""),
"description": p.get("description", ""),
"id": p.id,
"name": p.name,
"category": p.category,
"thumbnail": p.lut_url,
"description": p.description,
}
for p in FILTER_PRESETS
for p in FILTER_PRESET_LIBRARY
]
return FilterPresetListResponse(items=items, total=len(items))
+2 -2
View File
@@ -99,8 +99,8 @@ class SessionStore(SessionStorePort):
session_id: str,
user_id: str,
refresh_token: str,
device_info: str,
ip_address: str,
device_info: str = "",
ip_address: str = "",
expires_in_seconds: int = 30 * 24 * 60 * 60, # 30 天
) -> bool:
"""
+2 -2
View File
@@ -96,7 +96,7 @@ class EmailService(EmailServicePort):
except Exception as e:
return False, str(e)
def send_verification_email(
def send_verification_email( # type: ignore[override]
self,
to_email: str,
username: str,
@@ -165,7 +165,7 @@ class EmailService(EmailServicePort):
return self.send_email(to_email, subject, html_body, text_body)
def send_password_reset_email(
def send_password_reset_email( # type: ignore[override]
self,
to_email: str,
username: str,
+1 -1
View File
@@ -51,7 +51,7 @@ def parse_titles_from_response(content: str) -> list[str]:
pass
# 尝试按行解析
titles: list[str] = []
titles = []
for line in content.strip().split("\n"):
line = line.strip()
if not line:
+1 -1
View File
@@ -100,7 +100,7 @@ def _check_ssrf_domain(hostname: str) -> None:
raise UrlSecurityError(f"域名解析失败: {hostname}")
for info in infos:
ip_str = info[4][0]
ip_str = str(info[4][0])
try:
_check_ssrf_ip_base(ip_str)
except ValueError:
+22 -47
View File
@@ -52,70 +52,45 @@ bash scripts/ci/step_install_ffmpeg.sh
# 需要用宿主机IP访问映射端口
# 检测策略:host.docker.internal -> docker0桥接IP -> 容器IP直连 -> 默认网关 -> 127.0.0.1
detect_docker_host() {
local test_port="${1:-${CI_LOCAL_PG_PORT}}"
# 候选IP列表
local candidates=()
# 目标:找到宿主机IP(DooD模式下CI容器访问宿主机上其他容器用)
# 不依赖特定端口TCP探测,直接用网络拓扑信息
# 1. host.docker.internalrunner配置了--add-host时可用)
if python3 -c "import socket; socket.gethostbyname('host.docker.internal')" 2>/dev/null; then
candidates+=("host.docker.internal")
echo "host.docker.internal"
return 0
fi
# 2. docker0 桥接网关 (172.17.0.1)
candidates+=("172.17.0.1")
# 3. 默认网关(容器网络的网关即宿主机)
# 2. 默认网关(Docker bridge模式下网关即宿主机)
local gw=""
gw=$(ip route 2>/dev/null | grep default | awk '{print $3}' | head -1)
if [ -n "$gw" ] && [ "$gw" != "127.0.0.1" ]; then
candidates+=("$gw")
echo "$gw"
return 0
fi
# 4. 宿主机可能的IP:容器同网段的.1或.254
local my_ip=""
my_ip=$(hostname -I 2>/dev/null | awk '{print $1}')
if [ -n "$my_ip" ]; then
# 尝试同网段的常见宿主机IP
local subnet=$(echo "$my_ip" | cut -d. -f1-3)
candidates+=("${subnet}.1")
candidates+=("${subnet}.254")
# 3. docker0 桥接网关
if [ -n "$(ip addr show docker0 2>/dev/null)" ]; then
echo "172.17.0.1"
return 0
fi
# 5. 127.0.0.1 最后尝试
candidates+=("127.0.0.1")
# 4. 通过 git server hostname 反查(runner 配置了 ExtraHosts host-gateway
local git_host_ip=""
git_host_ip=$(python3 -c "import socket; print(socket.gethostbyname('git.xiaoxiajianji.com'))" 2>/dev/null || true)
if [ -n "$git_host_ip" ] && [ "$git_host_ip" != "127.0.0.1" ]; then
echo "$git_host_ip"
return 0
fi
# 测试每个候选IP
for candidate in "${candidates[@]}"; do
if python3 -c "
import socket
s = socket.socket()
s.settimeout(2)
try:
s.connect(('$candidate', $test_port))
s.close()
print('ok')
except:
pass
" 2>/dev/null | grep -q ok; then
echo "$candidate"
return 0
fi
done
# 都失败则返回127.0.0.1
# 5. 最终 fallback
echo "127.0.0.1"
return 1
return 0
}
# 获取宿主机IP(先尝试用共享PG端口5433测试,再回退到其他端口)
if [ -S /var/run/docker.sock ]; then
# 先用共享PG端口5433探测
DOCKER_HOST_IP=$(detect_docker_host "${CI_SHARED_PG_PORT}")
if [ "$DOCKER_HOST_IP" = "127.0.0.1" ]; then
# 如果共享PG端口探测失败,说明不在DooD或共享PG不可用,再试其他端口
DOCKER_HOST_IP=$(detect_docker_host 22)
fi
DOCKER_HOST_IP=$(detect_docker_host)
echo "检测到DooD模式(/var/run/docker.sock已挂载),宿主机地址: $DOCKER_HOST_IP"
else
DOCKER_HOST_IP="127.0.0.1"
@@ -227,7 +202,7 @@ else
postgres:16
PG_PORT=$(docker port "$PG_CONTAINER" ${CI_LOCAL_PG_PORT}/tcp | cut -d: -f2)
echo "PostgreSQL port: $PG_PORT"
export DATABASE_URL="postgresql+psycopg://${CI_SHARED_PG_USER}:${CI_SHARED_PG_PASSWORD}@${PG_HOST}:${PG_PORT}/${CI_DEFAULT_DB}"
export DATABASE_URL="postgresql+psycopg://postgres:postgres@${PG_HOST}:${PG_PORT}/${CI_DEFAULT_DB}"
# 等待容器健康
for i in $(seq 1 30); do
+23 -42
View File
@@ -59,56 +59,40 @@ echo ""
# ============================================================
detect_docker_host() {
local test_port="${1:-${CI_LOCAL_PG_PORT}}"
# 目标:找到宿主机IP(DooD模式下CI容器访问宿主机上其他容器用)
# 不依赖特定端口TCP探测,直接用网络拓扑信息
local candidates=()
# 1. host.docker.internal
# 1. host.docker.internalrunner配置了--add-host时可用)
if python3 -c "import socket; socket.gethostbyname('host.docker.internal')" 2>/dev/null; then
candidates+=("host.docker.internal")
echo "host.docker.internal"
return 0
fi
# 2. docker0 桥接网关
candidates+=("172.17.0.1")
# 3. 默认网关
# 2. 默认网关(Docker bridge模式下网关即宿主机)
local gw=""
gw=$(ip route 2>/dev/null | grep default | awk '{print $3}' | head -1)
if [ -n "$gw" ] && [ "$gw" != "127.0.0.1" ]; then
candidates+=("$gw")
echo "$gw"
return 0
fi
# 4. 宿主机同网段的.1或.254
local my_ip=""
my_ip=$(hostname -I 2>/dev/null | awk '{print $1}')
if [ -n "$my_ip" ]; then
local subnet=$(echo "$my_ip" | cut -d. -f1-3)
candidates+=("${subnet}.1")
candidates+=("${subnet}.254")
# 3. docker0 桥接网关
if [ -n "$(ip addr show docker0 2>/dev/null)" ]; then
echo "172.17.0.1"
return 0
fi
# 5. 127.0.0.1 最后尝试
candidates+=("127.0.0.1")
for candidate in "${candidates[@]}"; do
if python3 -c "
import socket
s = socket.socket()
s.settimeout(2)
try:
s.connect(('$candidate', $test_port))
s.close()
print('ok')
except:
pass
" 2>/dev/null | grep -q ok; then
echo "$candidate"
return 0
fi
done
# 4. 通过 git server hostname 反查(runner 配置了 ExtraHosts host-gateway
local git_host_ip=""
git_host_ip=$(python3 -c "import socket; print(socket.gethostbyname('git.xiaoxiajianji.com'))" 2>/dev/null || true)
if [ -n "$git_host_ip" ] && [ "$git_host_ip" != "127.0.0.1" ]; then
echo "$git_host_ip"
return 0
fi
# 5. 最终 fallback
echo "127.0.0.1"
return 1
return 0
}
# 指数退避TCP连接检查
@@ -132,10 +116,7 @@ wait_tcp_ready() {
# 获取宿主机IP
if [ -S /var/run/docker.sock ]; then
DOCKER_HOST_IP=$(detect_docker_host "${CI_SHARED_PG_PORT}")
if [ "$DOCKER_HOST_IP" = "127.0.0.1" ]; then
DOCKER_HOST_IP=$(detect_docker_host 22)
fi
DOCKER_HOST_IP=$(detect_docker_host)
echo "检测到DooD模式,宿主机地址: $DOCKER_HOST_IP"
else
DOCKER_HOST_IP="127.0.0.1"
@@ -213,7 +194,7 @@ else
postgres:16-alpine
PG_PORT=$(docker port "$PG_CONTAINER" ${CI_LOCAL_PG_PORT}/tcp | cut -d: -f2)
echo "PostgreSQL port: $PG_PORT"
export DATABASE_URL="postgresql+psycopg://${CI_SHARED_PG_USER}:${CI_SHARED_PG_PASSWORD}@${PG_HOST}:${PG_PORT}/${CI_DEFAULT_DB}"
export DATABASE_URL="postgresql+psycopg://postgres:postgres@${PG_HOST}:${PG_PORT}/${CI_DEFAULT_DB}"
# 等待容器健康
for i in $(seq 1 30); do