mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-06-05 07:26:48 +00:00
fix: 修复前端代理服务器设置清空保存后,httpx 持续报 `Unknown scheme for proxy URL (#5899)
This commit is contained in:
@@ -9,10 +9,10 @@ import sys
|
||||
import threading
|
||||
from asyncio import AbstractEventLoop
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type, Union, get_origin, get_args
|
||||
from urllib.parse import quote, urlencode, urlparse
|
||||
|
||||
from dotenv import set_key
|
||||
from dotenv import set_key, unset_key
|
||||
from pydantic import BaseModel, Field, ConfigDict, model_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
@@ -690,6 +690,18 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
||||
if isinstance(value, str):
|
||||
value = value.strip()
|
||||
|
||||
# 处理 Optional 类型:当值为空字符串且类型允许 None 时,转为 None
|
||||
# 兼容 typing.Union (Python 3.9) 与 types.UnionType (Python 3.10+ PEP 604)
|
||||
origin = get_origin(expected_type)
|
||||
is_union = origin is Union or getattr(origin, "__name__", None) == "UnionType"
|
||||
if (
|
||||
is_union
|
||||
and type(None) in get_args(expected_type)
|
||||
and isinstance(value, str)
|
||||
and not value
|
||||
):
|
||||
return default, str(default) != str(original_value)
|
||||
|
||||
try:
|
||||
if expected_type is bool:
|
||||
if isinstance(value, bool):
|
||||
@@ -812,13 +824,19 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
||||
logger.warning(message)
|
||||
return False, message
|
||||
else:
|
||||
# 当值为 None 时,从 env 文件中删除该键,恢复为默认值
|
||||
if converted_value is None:
|
||||
unset_key(
|
||||
dotenv_path=SystemUtils.get_env_path(),
|
||||
key_to_unset=field_name,
|
||||
)
|
||||
logger.info(f"配置项 '{field_name}' 已清空,从 'app.env' 中移除")
|
||||
return True, message
|
||||
# 如果是列表、字典或集合类型,将其转换为JSON字符串
|
||||
if isinstance(converted_value, (list, dict, set)):
|
||||
value_to_write = json.dumps(converted_value)
|
||||
else:
|
||||
value_to_write = (
|
||||
str(converted_value) if converted_value is not None else ""
|
||||
)
|
||||
value_to_write = str(converted_value)
|
||||
|
||||
set_key(
|
||||
dotenv_path=SystemUtils.get_env_path(),
|
||||
@@ -967,7 +985,7 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
||||
|
||||
@property
|
||||
def PROXY(self):
|
||||
if self.PROXY_HOST:
|
||||
if self.PROXY_HOST and self.PROXY_HOST.strip():
|
||||
return {
|
||||
"http": self.PROXY_HOST,
|
||||
"https": self.PROXY_HOST,
|
||||
@@ -1009,7 +1027,7 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
||||
|
||||
@property
|
||||
def PROXY_SERVER(self):
|
||||
if self.PROXY_HOST:
|
||||
if self.PROXY_HOST and self.PROXY_HOST.strip():
|
||||
try:
|
||||
parsed = urlparse(self.PROXY_HOST)
|
||||
if not parsed.scheme:
|
||||
|
||||
@@ -92,6 +92,9 @@ def _get_shared_async_transport(
|
||||
会话级状态由调用方在外层 AsyncClient(transport=...) 实例化时单独配置,
|
||||
每次调用用完即销毁,因此天然无 jar 累积串扰。
|
||||
"""
|
||||
# 规范化代理:拒绝空字符串等非法值,防止 httpx 抛出 Unknown scheme for proxy URL
|
||||
if proxy is not None and (not proxy or not proxy.strip()):
|
||||
proxy = None
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
@@ -899,12 +902,17 @@ class AsyncRequestUtils:
|
||||
|
||||
# 如果已经是字符串格式,直接返回
|
||||
if isinstance(proxies, str):
|
||||
return proxies
|
||||
return proxies.strip() or None
|
||||
|
||||
# 如果是字典格式,提取http或https代理
|
||||
if isinstance(proxies, dict):
|
||||
# 优先使用https代理,如果没有则使用http代理
|
||||
proxy_url = proxies.get("https") or proxies.get("http")
|
||||
# 先各自 strip,避免空白字符串阻断裂合取或回退到 http 代理
|
||||
https_proxy = proxies.get("https")
|
||||
http_proxy = proxies.get("http")
|
||||
https_proxy = https_proxy.strip() if isinstance(https_proxy, str) else None
|
||||
http_proxy = http_proxy.strip() if isinstance(http_proxy, str) else None
|
||||
proxy_url = https_proxy or http_proxy
|
||||
if proxy_url:
|
||||
return proxy_url
|
||||
|
||||
|
||||
Reference in New Issue
Block a user