refactor(project): make pyright & ruff happy
This commit is contained in:
105
app/utils.py
105
app/utils.py
@@ -1,7 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable, Sequence
|
||||
from datetime import datetime
|
||||
import functools
|
||||
import inspect
|
||||
from io import BytesIO
|
||||
from typing import Any, ParamSpec, TypeVar
|
||||
|
||||
from fastapi import HTTPException
|
||||
from PIL import Image
|
||||
@@ -151,75 +156,117 @@ def check_image(content: bytes, size: int, width: int, height: int) -> None:
|
||||
def simplify_user_agent(user_agent: str | None, max_length: int = 200) -> str | None:
|
||||
"""
|
||||
简化 User-Agent 字符串,只保留 osu! 和关键设备系统信息浏览器
|
||||
|
||||
|
||||
Args:
|
||||
user_agent: 原始 User-Agent 字符串
|
||||
max_length: 最大长度限制
|
||||
|
||||
|
||||
Returns:
|
||||
简化后的 User-Agent 字符串,或 None
|
||||
"""
|
||||
import re
|
||||
|
||||
|
||||
if not user_agent:
|
||||
return None
|
||||
|
||||
|
||||
# 如果长度在限制内,直接返回
|
||||
if len(user_agent) <= max_length:
|
||||
return user_agent
|
||||
|
||||
|
||||
# 提取操作系统信息
|
||||
os_info = ""
|
||||
os_patterns = [
|
||||
r'(Windows[^;)]*)',
|
||||
r'(Mac OS[^;)]*)',
|
||||
r'(Linux[^;)]*)',
|
||||
r'(Android[^;)]*)',
|
||||
r'(iOS[^;)]*)',
|
||||
r'(iPhone[^;)]*)',
|
||||
r'(iPad[^;)]*)'
|
||||
r"(Windows[^;)]*)",
|
||||
r"(Mac OS[^;)]*)",
|
||||
r"(Linux[^;)]*)",
|
||||
r"(Android[^;)]*)",
|
||||
r"(iOS[^;)]*)",
|
||||
r"(iPhone[^;)]*)",
|
||||
r"(iPad[^;)]*)",
|
||||
]
|
||||
|
||||
|
||||
for pattern in os_patterns:
|
||||
match = re.search(pattern, user_agent, re.IGNORECASE)
|
||||
if match:
|
||||
os_info = match.group(1).strip()
|
||||
break
|
||||
|
||||
|
||||
# 提取浏览器信息
|
||||
browser_info = ""
|
||||
browser_patterns = [
|
||||
r'(osu![^)]*)', # osu! 客户端
|
||||
r'(Chrome/[\d.]+)',
|
||||
r'(Firefox/[\d.]+)',
|
||||
r'(Safari/[\d.]+)',
|
||||
r'(Edge/[\d.]+)',
|
||||
r'(Opera/[\d.]+)'
|
||||
r"(osu![^)]*)", # osu! 客户端
|
||||
r"(Chrome/[\d.]+)",
|
||||
r"(Firefox/[\d.]+)",
|
||||
r"(Safari/[\d.]+)",
|
||||
r"(Edge/[\d.]+)",
|
||||
r"(Opera/[\d.]+)",
|
||||
]
|
||||
|
||||
|
||||
for pattern in browser_patterns:
|
||||
match = re.search(pattern, user_agent, re.IGNORECASE)
|
||||
if match:
|
||||
browser_info = match.group(1).strip()
|
||||
# 如果找到了 osu! 客户端,优先使用
|
||||
if 'osu!' in browser_info.lower():
|
||||
if "osu!" in browser_info.lower():
|
||||
break
|
||||
|
||||
|
||||
# 构建简化的 User-Agent
|
||||
parts = []
|
||||
if os_info:
|
||||
parts.append(os_info)
|
||||
if browser_info:
|
||||
parts.append(browser_info)
|
||||
|
||||
|
||||
if parts:
|
||||
simplified = '; '.join(parts)
|
||||
simplified = "; ".join(parts)
|
||||
else:
|
||||
# 如果没有识别到关键信息,截断原始字符串
|
||||
simplified = user_agent[:max_length-3] + "..."
|
||||
|
||||
simplified = user_agent[: max_length - 3] + "..."
|
||||
|
||||
# 确保不超过最大长度
|
||||
if len(simplified) > max_length:
|
||||
simplified = simplified[:max_length-3] + "..."
|
||||
|
||||
simplified = simplified[: max_length - 3] + "..."
|
||||
|
||||
return simplified
|
||||
|
||||
|
||||
# https://github.com/encode/starlette/blob/master/starlette/_utils.py
|
||||
T = TypeVar("T")
|
||||
AwaitableCallable = Callable[..., Awaitable[T]]
|
||||
|
||||
|
||||
def is_async_callable(obj: Any) -> bool:
|
||||
while isinstance(obj, functools.partial):
|
||||
obj = obj.func
|
||||
|
||||
return inspect.iscoroutinefunction(obj)
|
||||
|
||||
|
||||
P = ParamSpec("P")
|
||||
|
||||
|
||||
async def run_in_threadpool(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
|
||||
func = functools.partial(func, *args, **kwargs)
|
||||
return await asyncio.get_event_loop().run_in_executor(None, func)
|
||||
|
||||
|
||||
class BackgroundTasks:
|
||||
def __init__(self, tasks: Sequence[asyncio.Task] | None = None):
|
||||
self.tasks = set(tasks) if tasks else set()
|
||||
|
||||
def add_task(self, func: Callable[P, Any], *args: P.args, **kwargs: P.kwargs) -> None:
|
||||
if is_async_callable(func):
|
||||
coro = func(*args, **kwargs)
|
||||
else:
|
||||
coro = run_in_threadpool(func, *args, **kwargs)
|
||||
task = asyncio.create_task(coro)
|
||||
self.tasks.add(task)
|
||||
task.add_done_callback(self.tasks.discard)
|
||||
|
||||
def stop(self) -> None:
|
||||
for task in self.tasks:
|
||||
task.cancel()
|
||||
self.tasks.clear()
|
||||
|
||||
|
||||
bg_tasks = BackgroundTasks()
|
||||
|
||||
Reference in New Issue
Block a user