mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-08 17:08:35 +08:00
fix: 修复前端代理服务器设置清空保存后,httpx 持续报 `Unknown scheme for proxy URL (#5899)
This commit is contained in:
+25
-7
@@ -9,10 +9,10 @@ import sys
|
|||||||
import threading
|
import threading
|
||||||
from asyncio import AbstractEventLoop
|
from asyncio import AbstractEventLoop
|
||||||
from pathlib import Path
|
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 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 import BaseModel, Field, ConfigDict, model_validator
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
@@ -690,6 +690,18 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
value = value.strip()
|
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:
|
try:
|
||||||
if expected_type is bool:
|
if expected_type is bool:
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
@@ -812,13 +824,19 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
logger.warning(message)
|
logger.warning(message)
|
||||||
return False, message
|
return False, message
|
||||||
else:
|
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字符串
|
# 如果是列表、字典或集合类型,将其转换为JSON字符串
|
||||||
if isinstance(converted_value, (list, dict, set)):
|
if isinstance(converted_value, (list, dict, set)):
|
||||||
value_to_write = json.dumps(converted_value)
|
value_to_write = json.dumps(converted_value)
|
||||||
else:
|
else:
|
||||||
value_to_write = (
|
value_to_write = str(converted_value)
|
||||||
str(converted_value) if converted_value is not None else ""
|
|
||||||
)
|
|
||||||
|
|
||||||
set_key(
|
set_key(
|
||||||
dotenv_path=SystemUtils.get_env_path(),
|
dotenv_path=SystemUtils.get_env_path(),
|
||||||
@@ -967,7 +985,7 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def PROXY(self):
|
def PROXY(self):
|
||||||
if self.PROXY_HOST:
|
if self.PROXY_HOST and self.PROXY_HOST.strip():
|
||||||
return {
|
return {
|
||||||
"http": self.PROXY_HOST,
|
"http": self.PROXY_HOST,
|
||||||
"https": self.PROXY_HOST,
|
"https": self.PROXY_HOST,
|
||||||
@@ -1009,7 +1027,7 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def PROXY_SERVER(self):
|
def PROXY_SERVER(self):
|
||||||
if self.PROXY_HOST:
|
if self.PROXY_HOST and self.PROXY_HOST.strip():
|
||||||
try:
|
try:
|
||||||
parsed = urlparse(self.PROXY_HOST)
|
parsed = urlparse(self.PROXY_HOST)
|
||||||
if not parsed.scheme:
|
if not parsed.scheme:
|
||||||
|
|||||||
+10
-2
@@ -92,6 +92,9 @@ def _get_shared_async_transport(
|
|||||||
会话级状态由调用方在外层 AsyncClient(transport=...) 实例化时单独配置,
|
会话级状态由调用方在外层 AsyncClient(transport=...) 实例化时单独配置,
|
||||||
每次调用用完即销毁,因此天然无 jar 累积串扰。
|
每次调用用完即销毁,因此天然无 jar 累积串扰。
|
||||||
"""
|
"""
|
||||||
|
# 规范化代理:拒绝空字符串等非法值,防止 httpx 抛出 Unknown scheme for proxy URL
|
||||||
|
if proxy is not None and (not proxy or not proxy.strip()):
|
||||||
|
proxy = None
|
||||||
try:
|
try:
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
@@ -899,12 +902,17 @@ class AsyncRequestUtils:
|
|||||||
|
|
||||||
# 如果已经是字符串格式,直接返回
|
# 如果已经是字符串格式,直接返回
|
||||||
if isinstance(proxies, str):
|
if isinstance(proxies, str):
|
||||||
return proxies
|
return proxies.strip() or None
|
||||||
|
|
||||||
# 如果是字典格式,提取http或https代理
|
# 如果是字典格式,提取http或https代理
|
||||||
if isinstance(proxies, dict):
|
if isinstance(proxies, dict):
|
||||||
# 优先使用https代理,如果没有则使用http代理
|
# 优先使用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:
|
if proxy_url:
|
||||||
return proxy_url
|
return proxy_url
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user