Optimize codes and comments with cursor (#550)

This commit is contained in:
Wu Clan
2025-03-28 10:08:32 +08:00
committed by GitHub
parent 92fe1e7554
commit e492cec7d7
152 changed files with 3428 additions and 2073 deletions
+17 -12
View File
@@ -7,7 +7,12 @@ from backend.utils.serializers import RowData, select_list_serialize
def get_tree_nodes(row: Sequence[RowData]) -> list[dict[str, Any]]:
"""获取所有树形结构节点"""
"""
获取所有树形结构节点
:param row: 原始数据行序列
:return:
"""
tree_nodes = select_list_serialize(row)
tree_nodes.sort(key=lambda x: x['sort'])
return tree_nodes
@@ -17,10 +22,10 @@ def traversal_to_tree(nodes: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""
通过遍历算法构造树形结构
:param nodes:
:param nodes: 树节点列表
:return:
"""
tree = []
tree: list[dict[str, Any]] = []
node_dict = {node['id']: node for node in nodes}
for node in nodes:
@@ -45,16 +50,16 @@ def recursive_to_tree(nodes: list[dict[str, Any]], *, parent_id: int | None = No
"""
通过递归算法构造树形结构(性能影响较大)
:param nodes:
:param parent_id:
:param nodes: 树节点列表
:param parent_id: 父节点 ID,默认为 None 表示根节点
:return:
"""
tree = []
tree: list[dict[str, Any]] = []
for node in nodes:
if node['parent_id'] == parent_id:
child_node = recursive_to_tree(nodes, parent_id=node['id'])
if child_node:
node['children'] = child_node
child_nodes = recursive_to_tree(nodes, parent_id=node['id'])
if child_nodes:
node['children'] = child_nodes
tree.append(node)
return tree
@@ -65,9 +70,9 @@ def get_tree_data(
"""
获取树形结构数据
:param row:
:param build_type:
:param parent_id:
:param row: 原始数据行序列
:param build_type: 构建树形结构的算法类型,默认为遍历算法
:param parent_id: 父节点 ID,仅在递归算法中使用
:return:
"""
nodes = get_tree_nodes(row)
+6 -2
View File
@@ -6,9 +6,13 @@ from backend.common.exception import errors
from backend.core.conf import settings
async def demo_site(request: Request):
"""演示站点"""
async def demo_site(request: Request) -> None:
"""
演示站点
:param request: FastAPI 请求对象
:return:
"""
method = request.method
path = request.url.path
if (
+18 -7
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import hashlib
import os
from typing import Any
@@ -13,9 +14,14 @@ from backend.common.log import log
class AESCipher:
def __init__(self, key: bytes | str):
"""AES 加密器"""
def __init__(self, key: bytes | str) -> None:
"""
初始化 AES 加密器
:param key: 密钥,16/24/32 bytes 或 16 进制字符串
:return:
"""
self.key = key if isinstance(key, bytes) else bytes.fromhex(key)
@@ -40,7 +46,7 @@ class AESCipher:
"""
AES 解密
:param ciphertext: 解密前的密文, bytes 或 16 进制字符串
:param ciphertext: 解密前的密文bytes 或 16 进制字符串
:return:
"""
ciphertext = ciphertext if isinstance(ciphertext, bytes) else bytes.fromhex(ciphertext)
@@ -55,6 +61,8 @@ class AESCipher:
class Md5Cipher:
"""MD5 加密器"""
@staticmethod
def encrypt(plaintext: bytes | str) -> str:
"""
@@ -63,8 +71,6 @@ class Md5Cipher:
:param plaintext: 加密前的明文
:return:
"""
import hashlib
md5 = hashlib.md5()
if not isinstance(plaintext, bytes):
plaintext = str(plaintext).encode('utf-8')
@@ -73,15 +79,20 @@ class Md5Cipher:
class ItsDCipher:
def __init__(self, key: bytes | str):
"""ItsDangerous 加密器"""
def __init__(self, key: bytes | str) -> None:
"""
初始化 ItsDangerous 加密器
:param key: 密钥,16/24/32 bytes 或 16 进制字符串
:return:
"""
self.key = key if isinstance(key, bytes) else bytes.fromhex(key)
def encrypt(self, plaintext: Any) -> str:
"""
ItsDangerous 加密 (可能失败,如果 plaintext 无法序列化,则会加密为 MD5)
ItsDangerous 加密
:param plaintext: 加密前的明文
:return:
@@ -96,7 +107,7 @@ class ItsDCipher:
def decrypt(self, ciphertext: str) -> Any:
"""
ItsDangerous 解密 (可能失败,如果 ciphertext 无法反序列化,则解密失败, 返回原始密文)
ItsDangerous 解密
:param ciphertext: 解密前的密文
:return:
+7 -6
View File
@@ -14,11 +14,11 @@ from backend.core.path_conf import UPLOAD_DIR
from backend.utils.timezone import timezone
def build_filename(file: UploadFile):
def build_filename(file: UploadFile) -> str:
"""
构建文件名
:param file:
:param file: FastAPI 上传文件对象
:return:
"""
timestamp = int(timezone.now().timestamp())
@@ -32,14 +32,15 @@ def file_verify(file: UploadFile, file_type: FileType) -> None:
"""
文件验证
:param file:
:param file_type:
:param file: FastAPI 上传文件对象
:param file_type: 文件类型枚举
:return:
"""
filename = file.filename
file_ext = filename.split('.')[-1].lower()
if not file_ext:
raise errors.ForbiddenError(msg='未知的文件类型')
if file_type == FileType.image:
if file_ext not in settings.UPLOAD_IMAGE_EXT_INCLUDE:
raise errors.ForbiddenError(msg='此图片格式暂不支持')
@@ -52,11 +53,11 @@ def file_verify(file: UploadFile, file_type: FileType) -> None:
raise errors.ForbiddenError(msg='视频超出最大限制,请重新选择')
async def upload_file(file: UploadFile):
async def upload_file(file: UploadFile) -> str:
"""
上传文件
:param file:
:param file: FastAPI 上传文件对象
:return:
"""
filename = build_filename(file)
+13 -14
View File
@@ -12,7 +12,8 @@ from backend.core.path_conf import JINJA2_TEMPLATE_DIR
class GenTemplate:
def __init__(self):
def __init__(self) -> None:
"""初始化模板生成器"""
self.env = Environment(
loader=FileSystemLoader(JINJA2_TEMPLATE_DIR),
autoescape=select_autoescape(enabled_extensions=['jinja']),
@@ -25,18 +26,17 @@ class GenTemplate:
def get_template(self, jinja_file: str) -> Template:
"""
获取模文件
获取模文件
:param jinja_file:
:param jinja_file: Jinja2 模板文件
:return:
"""
return self.env.get_template(jinja_file)
@staticmethod
def get_template_paths() -> list[str]:
"""
获取模文件路径
获取模文件路径列表
:return:
"""
@@ -53,26 +53,25 @@ class GenTemplate:
"""
获取代码生成路径列表
:param business:
:param business: 代码生成业务对象
:return:
"""
app_name = business.app_name
module_name = business.table_name_en
target_files = [
return [
f'{generator_settings.TEMPLATE_BACKEND_DIR_NAME}/{app_name}/api/{business.api_version}/{module_name}.py',
f'{generator_settings.TEMPLATE_BACKEND_DIR_NAME}/{app_name}/crud/crud_{module_name}.py',
f'{generator_settings.TEMPLATE_BACKEND_DIR_NAME}/{app_name}/model/{module_name}.py',
f'{generator_settings.TEMPLATE_BACKEND_DIR_NAME}/{app_name}/schema/{module_name}.py',
f'{generator_settings.TEMPLATE_BACKEND_DIR_NAME}/{app_name}/service/{module_name}_service.py',
]
return target_files
def get_code_gen_path(self, tpl_path: str, business: GenBusiness) -> str:
"""
获取代码生成路径
:param tpl_path:
:param business:
:param tpl_path: 模板文件路径
:param business: 代码生成业务对象
:return:
"""
target_files = self.get_code_gen_paths(business)
@@ -80,12 +79,12 @@ class GenTemplate:
return code_gen_path_mapping[tpl_path]
@staticmethod
def get_vars(business: GenBusiness, models: Sequence[GenModel]) -> dict:
def get_vars(business: GenBusiness, models: Sequence[GenModel]) -> dict[str, str | Sequence[GenModel]]:
"""
获取模变量
获取模变量
:param business:
:param models:
:param business: 代码生成业务对象
:param models: 代码生成模型对象列表
:return:
"""
return {
+5 -5
View File
@@ -12,7 +12,7 @@ def ensure_unique_route_names(app: FastAPI) -> None:
"""
检查路由名称是否唯一
:param app:
:param app: FastAPI 应用实例
:return:
"""
temp_routes = set()
@@ -23,13 +23,13 @@ def ensure_unique_route_names(app: FastAPI) -> None:
temp_routes.add(route.name)
async def http_limit_callback(request: Request, response: Response, expire: int):
async def http_limit_callback(request: Request, response: Response, expire: int) -> None:
"""
请求限制时的默认回调函数
:param request:
:param response:
:param expire: 剩余毫秒
:param request: FastAPI 请求对象
:param response: FastAPI 响应对象
:param expire: 剩余毫秒
:return:
"""
expires = ceil(expire / 1000)
+9 -9
View File
@@ -3,36 +3,36 @@
import importlib
from functools import lru_cache
from typing import Any
from typing import Any, Type, TypeVar
from backend.common.exception import errors
from backend.common.log import log
T = TypeVar('T')
@lru_cache(maxsize=512)
def import_module_cached(module_path: str) -> Any:
"""
缓存导入模块
:param module_path:
:param module_path: 模块路径
:return:
"""
return importlib.import_module(module_path)
def dynamic_import_data_model(module_path: str) -> Any:
def dynamic_import_data_model(module_path: str) -> Type[T]:
"""
动态导入数据模型
:param module_path:
:param module_path: 模块路径,格式为 'module_path.class_name'
:return:
"""
module_path, class_or_func = module_path.rsplit('.', 1)
try:
module_path, class_name = module_path.rsplit('.', 1)
module = import_module_cached(module_path)
ins = getattr(module, class_or_func)
return getattr(module, class_name)
except (ImportError, AttributeError) as e:
log.error(e)
log.error(f'动态导入数据模型失败:{e}')
raise errors.ServerError(msg='数据模型列动态解析失败,请联系系统超级管理员')
return ins
+2 -2
View File
@@ -6,9 +6,9 @@ from fastapi.routing import APIRoute
def simplify_operation_ids(app: FastAPI) -> None:
"""
简化操作 ID,以便生成的客户端具有更简单的 api 函数名称
简化操作 ID,以便生成的客户端具有更简单的 API 函数名称
:param app:
:param app: FastAPI 应用实例
:return:
"""
for route in app.routes:
+21 -17
View File
@@ -3,41 +3,45 @@
import re
def search_string(pattern, text) -> bool:
def search_string(pattern: str, text: str) -> bool:
"""
全字段正则匹配
:param pattern:
:param text:
:param pattern: 正则表达式模式
:param text: 待匹配的文本
:return:
"""
result = re.search(pattern, text)
if result:
return True
else:
if not pattern or not text:
return False
result = re.search(pattern, text)
return result is not None
def match_string(pattern, text) -> bool:
def match_string(pattern: str, text: str) -> bool:
"""
从字段开头正则匹配
:param pattern:
:param text:
:param pattern: 正则表达式模式
:param text: 待匹配的文本
:return:
"""
result = re.match(pattern, text)
if result:
return True
else:
if not pattern or not text:
return False
result = re.match(pattern, text)
return result is not None
def is_phone(text: str) -> bool:
"""
检查手机号码
检查手机号码格式
:param text:
:param text: 待检查的手机号码
:return:
"""
return match_string(r'^1[3-9]\d{9}$', text)
if not text:
return False
phone_pattern = r'^1[3-9]\d{9}$'
return match_string(phone_pattern, text)
+33 -12
View File
@@ -6,27 +6,48 @@ from backend.utils.server_info import server_info
class RedisInfo:
@staticmethod
async def get_info():
async def get_info() -> dict[str, str]:
"""获取 Redis 服务器信息"""
# 获取原始信息
info = await redis_client.info()
fmt_info = {}
# 格式化信息
fmt_info: dict[str, str] = {}
for key, value in info.items():
if isinstance(value, dict):
value = ','.join({f'{k}={v}' for k, v in value.items()})
# 将字典格式化为字符串
fmt_info[key] = ','.join(f'{k}={v}' for k, v in value.items())
else:
value = str(value)
fmt_info[key] = value
fmt_info[key] = str(value)
# 添加数据库大小信息
db_size = await redis_client.dbsize()
fmt_info.update({'keys_num': db_size})
fmt_uptime = server_info.fmt_seconds(fmt_info.get('uptime_in_seconds', 0))
fmt_info.update({'uptime_in_seconds': fmt_uptime})
fmt_info['keys_num'] = str(db_size)
# 格式化运行时间
uptime = int(fmt_info.get('uptime_in_seconds', '0'))
fmt_info['uptime_in_seconds'] = server_info.fmt_seconds(uptime)
return fmt_info
@staticmethod
async def get_stats():
stats_list = []
async def get_stats() -> list[dict[str, str]]:
"""获取 Redis 命令统计信息"""
# 获取命令统计信息
command_stats = await redis_client.info('commandstats')
for k, v in command_stats.items():
stats_list.append({'name': k.split('_')[-1], 'value': str(v.get('calls', ''))})
# 格式化统计信息
stats_list: list[dict[str, str]] = []
for key, value in command_stats.items():
if not isinstance(value, dict):
continue
command_name = key.split('_')[-1]
call_count = str(value.get('calls', '0'))
stats_list.append({'name': command_name, 'value': call_count})
return stats_list
+37 -20
View File
@@ -15,28 +15,32 @@ from backend.database.redis import redis_client
def get_request_ip(request: Request) -> str:
"""获取请求的 ip 地址"""
"""
获取请求的 IP 地址
:param request: FastAPI 请求对象
:return:
"""
real = request.headers.get('X-Real-IP')
if real:
ip = real
else:
forwarded = request.headers.get('X-Forwarded-For')
if forwarded:
ip = forwarded.split(',')[0]
else:
ip = request.client.host
return real
forwarded = request.headers.get('X-Forwarded-For')
if forwarded:
return forwarded.split(',')[0]
# 忽略 pytest
if ip == 'testclient':
ip = '127.0.0.1'
return ip
if request.client.host == 'testclient':
return '127.0.0.1'
return request.client.host
async def get_location_online(ip: str, user_agent: str) -> dict | None:
"""
在线获取 ip 地址属地,无法保证可用性,准确率较高
在线获取 IP 地址属地,无法保证可用性,准确率较高
:param ip:
:param user_agent:
:param ip: IP 地址
:param user_agent: 用户代理字符串
:return:
"""
async with httpx.AsyncClient(timeout=3) as client:
@@ -47,16 +51,16 @@ async def get_location_online(ip: str, user_agent: str) -> dict | None:
if response.status_code == 200:
return response.json()
except Exception as e:
log.error(f'在线获取 ip 地址属地失败,错误信息:{e}')
log.error(f'在线获取 IP 地址属地失败,错误信息:{e}')
return None
@sync_to_async
def get_location_offline(ip: str) -> dict | None:
"""
离线获取 ip 地址属地,无法保证准确率,100%可用
离线获取 IP 地址属地,无法保证准确率,100% 可用
:param ip:
:param ip: IP 地址
:return:
"""
try:
@@ -71,23 +75,30 @@ def get_location_offline(ip: str) -> dict | None:
'city': data[3] if data[3] != '0' else None,
}
except Exception as e:
log.error(f'离线获取 ip 地址属地失败,错误信息:{e}')
log.error(f'离线获取 IP 地址属地失败,错误信息:{e}')
return None
async def parse_ip_info(request: Request) -> IpInfo:
"""
解析请求的 IP 信息
:param request: FastAPI 请求对象
:return:
"""
country, region, city = None, None, None
ip = get_request_ip(request)
location = await redis_client.get(f'{settings.IP_LOCATION_REDIS_PREFIX}:{ip}')
if location:
country, region, city = location.split('|')
return IpInfo(ip=ip, country=country, region=region, city=city)
location_info = None
if settings.IP_LOCATION_PARSE == 'online':
location_info = await get_location_online(ip, request.headers.get('User-Agent'))
elif settings.IP_LOCATION_PARSE == 'offline':
location_info = await get_location_offline(ip)
else:
location_info = None
if location_info:
country = location_info.get('country')
region = location_info.get('regionName')
@@ -101,6 +112,12 @@ async def parse_ip_info(request: Request) -> IpInfo:
def parse_user_agent_info(request: Request) -> UserAgentInfo:
"""
解析请求的用户代理信息
:param request: FastAPI 请求对象
:return:
"""
user_agent = request.headers.get('User-Agent')
_user_agent = parse(user_agent)
os = _user_agent.get_os()
+15 -20
View File
@@ -14,43 +14,38 @@ RowData = Row | RowMapping | Any
R = TypeVar('R', bound=RowData)
def select_columns_serialize(row: R) -> dict:
def select_columns_serialize(row: R) -> dict[str, Any]:
"""
Serialize SQLAlchemy select table columns, does not contain relational columns
序列化 SQLAlchemy 查询表的列,不包含关联列
:param row:
:param row: SQLAlchemy 查询结果行
:return:
"""
result = {}
for column in row.__table__.columns.keys():
v = getattr(row, column)
if isinstance(v, Decimal):
v = decimal_encoder(v)
result[column] = v
value = getattr(row, column)
if isinstance(value, Decimal):
value = decimal_encoder(value)
result[column] = value
return result
def select_list_serialize(row: Sequence[R]) -> list[dict[str, Any]]:
"""
Serialize SQLAlchemy select list
序列化 SQLAlchemy 查询列表
:param row:
:param row: SQLAlchemy 查询结果列表
:return:
"""
result = [select_columns_serialize(_) for _ in row]
return result
return [select_columns_serialize(item) for item in row]
def select_as_dict(row: R, use_alias: bool = False) -> dict:
def select_as_dict(row: R, use_alias: bool = False) -> dict[str, Any]:
"""
Converting SQLAlchemy select to dict, which can contain relational data,
depends on the properties of the select object itself
SQLAlchemy 查询结果转换为字典,可以包含关联数据
If set use_alias is True, the column name will be returned as alias,
If alias doesn't exist in columns, we don't recommend setting it to True
:param row:
:param use_alias:
:param row: SQLAlchemy 查询结果行
:param use_alias: 是否使用别名作为列名
:return:
"""
if not use_alias:
@@ -70,7 +65,7 @@ def select_as_dict(row: R, use_alias: bool = False) -> dict:
class MsgSpecJSONResponse(JSONResponse):
"""
JSON response using the high-performance msgspec library to serialize data to JSON.
使用高性能的 msgspec 库将数据序列化为 JSON 的响应类
"""
def render(self, content: Any) -> bytes:
+50 -42
View File
@@ -1,3 +1,5 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import platform
import socket
@@ -5,7 +7,6 @@ import sys
from datetime import datetime, timedelta
from datetime import timezone as tz
from typing import List
import psutil
@@ -14,8 +15,13 @@ from backend.utils.timezone import timezone
class ServerInfo:
@staticmethod
def format_bytes(size) -> str:
"""格式化字节"""
def format_bytes(size: int | float) -> str:
"""
格式化字节大小
:param size: 字节大小
:return:
"""
factor = 1024
for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
if abs(size) < factor:
@@ -25,64 +31,64 @@ class ServerInfo:
@staticmethod
def fmt_seconds(seconds: int) -> str:
"""
格式化秒数为可读的时间字符串
:param seconds: 秒数
:return:
"""
days, rem = divmod(int(seconds), 86400)
hours, rem = divmod(rem, 3600)
minutes, seconds = divmod(rem, 60)
parts = []
if days:
parts.append('{}'.format(days))
parts.append(f'{days}')
if hours:
parts.append('{} 小时'.format(hours))
parts.append(f'{hours} 小时')
if minutes:
parts.append('{} 分钟'.format(minutes))
parts.append(f'{minutes} 分钟')
if seconds:
parts.append('{}'.format(seconds))
if len(parts) == 0:
return '0 秒'
else:
return ' '.join(parts)
parts.append(f'{seconds}')
return ' '.join(parts) if parts else '0 秒'
@staticmethod
def fmt_timedelta(td: timedelta) -> str:
"""格式化时间差"""
"""
格式化时间差
:param td: 时间差对象
:return:
"""
total_seconds = round(td.total_seconds())
return ServerInfo.fmt_seconds(total_seconds)
@staticmethod
def get_cpu_info() -> dict:
def get_cpu_info() -> dict[str, float | int]:
"""获取 CPU 信息"""
cpu_info = {'usage': round(psutil.cpu_percent(percpu=False), 2)} # %
# 检查是否是 Apple M系列芯片
if platform.system() == 'Darwin' and 'arm' in platform.machine().lower():
cpu_info['max_freq'] = 0
cpu_info['min_freq'] = 0
cpu_info['current_freq'] = 0
else:
try:
# CPU 频率信息,最大、最小和当前频率
cpu_freq = psutil.cpu_freq()
cpu_info['max_freq'] = round(cpu_freq.max, 2) # MHz
cpu_info['min_freq'] = round(cpu_freq.min, 2) # MHz
cpu_info['current_freq'] = round(cpu_freq.current, 2) # MHz
except FileNotFoundError:
# 处理无法获取频率的情况
cpu_info['max_freq'] = 0
cpu_info['min_freq'] = 0
cpu_info['current_freq'] = 0
except AttributeError:
# 处理属性不存在的情况(更安全的做法)
cpu_info['max_freq'] = 0
cpu_info['min_freq'] = 0
cpu_info['current_freq'] = 0
try:
# CPU 频率信息,最大、最小和当前频率
cpu_freq = psutil.cpu_freq()
cpu_info.update({
'max_freq': round(cpu_freq.max, 2), # MHz
'min_freq': round(cpu_freq.min, 2), # MHz
'current_freq': round(cpu_freq.current, 2), # MHz
})
except Exception:
cpu_info.update({'max_freq': 0, 'min_freq': 0, 'current_freq': 0})
# CPU 逻辑核心数,物理核心数
cpu_info['logical_num'] = psutil.cpu_count(logical=True)
cpu_info['physical_num'] = psutil.cpu_count(logical=False)
cpu_info.update({
'logical_num': psutil.cpu_count(logical=True),
'physical_num': psutil.cpu_count(logical=False),
})
return cpu_info
@staticmethod
def get_mem_info() -> dict:
def get_mem_info() -> dict[str, float]:
"""获取内存信息"""
mem = psutil.virtual_memory()
return {
@@ -93,7 +99,7 @@ class ServerInfo:
}
@staticmethod
def get_sys_info() -> dict:
def get_sys_info() -> dict[str, str]:
"""获取服务器信息"""
try:
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sk:
@@ -101,6 +107,7 @@ class ServerInfo:
ip = sk.getsockname()[0]
except socket.gaierror:
ip = '127.0.0.1'
return {
'name': socket.gethostname(),
'ip': ip,
@@ -109,7 +116,7 @@ class ServerInfo:
}
@staticmethod
def get_disk_info() -> List[dict]:
def get_disk_info() -> list[dict[str, str]]:
"""获取磁盘信息"""
disk_info = []
for disk in psutil.disk_partitions():
@@ -126,11 +133,12 @@ class ServerInfo:
return disk_info
@staticmethod
def get_service_info():
def get_service_info() -> dict[str, str | datetime]:
"""获取服务信息"""
process = psutil.Process(os.getpid())
mem_info = process.memory_info()
start_time = timezone.f_datetime(datetime.utcfromtimestamp(process.create_time()).replace(tzinfo=tz.utc))
return {
'name': 'Python3',
'version': platform.python_version(),
@@ -140,7 +148,7 @@ class ServerInfo:
'mem_rss': ServerInfo.format_bytes(mem_info.rss), # 常驻内存, 即当前进程实际使用的物理内存
'mem_free': ServerInfo.format_bytes(mem_info.vms - mem_info.rss), # 空闲内存
'startup': start_time,
'elapsed': f'{ServerInfo.fmt_timedelta(timezone.now() - start_time)}',
'elapsed': ServerInfo.fmt_timedelta(timezone.now() - start_time),
}
+18 -16
View File
@@ -9,32 +9,34 @@ from backend.core.conf import settings
class TimeZone:
def __init__(self, tz: str = settings.DATETIME_TIMEZONE):
def __init__(self, tz: str = settings.DATETIME_TIMEZONE) -> None:
"""
初始化时区转换器
:param tz: 时区名称,默认为 settings.DATETIME_TIMEZONE
:return:
"""
self.tz_info = zoneinfo.ZoneInfo(tz)
def now(self) -> datetime:
"""
获取时区时间
:return:
"""
"""获取当前时区时间"""
return datetime.now(self.tz_info)
def f_datetime(self, dt: datetime) -> datetime:
"""
datetime 时间转时区时间
datetime 对象转换为当前时区时间
:param dt:
:param dt: 需要转换的 datetime 对象
:return:
"""
return dt.astimezone(self.tz_info)
def f_str(self, date_str: str, format_str: str = settings.DATETIME_FORMAT) -> datetime:
"""
时间字符串转时区时间
时间字符串转换为当前时区的 datetime 对象
:param date_str:
:param format_str:
:param date_str: 时间字符串
:param format_str: 时间格式字符串,默认为 settings.DATETIME_FORMAT
:return:
"""
return datetime.strptime(date_str, format_str).replace(tzinfo=self.tz_info)
@@ -42,10 +44,10 @@ class TimeZone:
@staticmethod
def t_str(dt: datetime, format_str: str = settings.DATETIME_FORMAT) -> str:
"""
时间转时间字符串
将 datetime 对象转换为指定格式的时间字符串
:param dt:
:param format_str:
:param dt: datetime 对象
:param format_str: 时间格式字符串,默认为 settings.DATETIME_FORMAT
:return:
"""
return dt.strftime(format_str)
@@ -53,9 +55,9 @@ class TimeZone:
@staticmethod
def f_utc(dt: datetime) -> datetime:
"""
时区时间转 UTCGMT)时区
将 datetime 对象转换为 UTC (GMT) 时区时间
:param dt:
:param dt: 需要转换的 datetime 对象
:return:
"""
return dt.astimezone(datetime_timezone.utc)
+6
View File
@@ -6,4 +6,10 @@ from backend.core.conf import settings
def get_request_trace_id(request: Request) -> str:
"""
从请求头中获取追踪 ID
:param request: FastAPI 请求对象
:return:
"""
return request.headers.get(settings.TRACE_ID_REQUEST_HEADER_KEY) or settings.LOG_CID_DEFAULT_VALUE
+7 -8
View File
@@ -6,9 +6,9 @@ from backend.core.conf import settings
def sql_type_to_sqlalchemy(typing: str) -> str:
"""
Converts a sql type to a SQLAlchemy type.
将 SQL 类型转换为 SQLAlchemy 类型
:param typing:
:param typing: SQL 类型字符串
:return:
"""
if settings.DATABASE_TYPE == 'mysql':
@@ -22,17 +22,16 @@ def sql_type_to_sqlalchemy(typing: str) -> str:
def sql_type_to_pydantic(typing: str) -> str:
"""
Converts a sql type to a pydantic type.
将 SQL 类型转换为 Pydantic 类型
:param typing:
:param typing: SQL 类型字符串
:return:
"""
try:
if settings.DATABASE_TYPE == 'mysql':
return GenModelMySQLColumnType[typing].value
else:
if typing == 'CHARACTER VARYING': # postgresql 中 DDL VARCHAR 的别名
return 'str'
return GenModelPostgreSQLColumnType[typing].value
if typing == 'CHARACTER VARYING': # postgresql 中 DDL VARCHAR 的别名
return 'str'
return GenModelPostgreSQLColumnType[typing].value
except KeyError:
return 'str'