BS 0.1.0 重建反射与EasySave

This commit is contained in:
2025-07-09 23:52:24 +08:00
parent 06a3edfafe
commit 39f8320c49
5 changed files with 337 additions and 684 deletions

View File

@@ -7,6 +7,7 @@ import traceback
import datetime import datetime
import platform import platform
import time import time
import os
from colorama import Fore as ConsoleFrontColor, Back as ConsoleBackgroundColor, Style as ConsoleStyle from colorama import Fore as ConsoleFrontColor, Back as ConsoleBackgroundColor, Style as ConsoleStyle
def format_traceback_info(char:str='\n', back:int=1): def format_traceback_info(char:str='\n', back:int=1):

View File

@@ -9,42 +9,40 @@ def SetInternalEasySaveDebug(debug:bool) -> None:
global _Internal_EasySave_Debug global _Internal_EasySave_Debug
_Internal_EasySave_Debug = debug _Internal_EasySave_Debug = debug
class EasySaveSetting(BaseModel, any_class): class EasySaveSetting(BaseModel):
key: str = Field(description="目标键", default="easy") key: str = Field(description="目标键", default="easy")
# 从目标文件进行序列化/反序列化 # 从目标文件进行序列化/反序列化
file: str = Field(description="目标文件") file: str = Field(description="目标文件")
# 序列化/反序列化的格式方法 # 序列化/反序列化的格式方法
format: Literal["json", "binary"] = Field(description="保存模式", default="json") formatMode: Literal["json", "binary"] = Field(description="保存模式", default="json")
# TODO: refChain: bool = Field(description="是否以保留引用的方式保存", default=True) # TODO: refChain: bool = Field(description="是否以保留引用的方式保存", default=True)
# 文件形式与参数 # 文件形式与参数
# TODO: encoding: str = Field(description="编码", default="utf-8") # TODO: encoding: str = Field(description="编码", default="utf-8")
is_backup: bool = Field(description="是否备份", default=True) isBackup: bool = Field(description="是否备份", default=True)
backup_suffix: str = Field(description="备份后缀", default=".backup") backup_suffix: str = Field(description="备份后缀", default=".backup")
# 序列化/反序列化时, 如果设置了忽略字段的谓词, 则被谓词选中的字段将不会工作 # 序列化/反序列化时, 如果设置了忽略字段的谓词, 则被谓词选中的字段将不会工作
# 如果设置了选择字段的谓词, 则被选中的字段才会工作 # 如果设置了选择字段的谓词, 则被选中的字段才会工作
ignore_pr: Optional[Callable[[FieldInfo], bool]] = Field(description="忽略字段的谓词", default=None) ignorePr: Optional[Callable[[FieldInfo], bool]] = Field(description="忽略字段的谓词", default=None)
select_pr: Optional[Callable[[FieldInfo], bool]] = Field(description="选择字段的谓词", default=None) selectPr: Optional[Callable[[FieldInfo], bool]] = Field(description="选择字段的谓词", default=None)
class ESWriter(BaseModel, any_class): class ESWriter(BaseModel):
setting: EasySaveSetting = Field(description="设置") setting: EasySaveSetting = Field(description="设置")
@sealed
def _GetFields(self, rtype:RefType) -> List[FieldInfo]: def _GetFields(self, rtype:RefType) -> List[FieldInfo]:
''' '''
获取字段 获取字段
''' '''
fields: List[FieldInfo] = [] fields: List[FieldInfo] = []
if self.setting.ignore_pr is not None and self.setting.select_pr is not None: if self.setting.ignorePr is not None and self.setting.selectPr is not None:
fields = [ field for field in rtype.GetAllFields() if self.setting.select_pr(field) and not self.setting.ignore_pr(field) ] fields = [ field for field in rtype.GetAllFields() if self.setting.selectPr(field) and not self.setting.ignorePr(field) ]
elif self.setting.select_pr is None and self.setting.ignore_pr is None: elif self.setting.selectPr is None and self.setting.ignorePr is None:
fields = rtype.GetFields() fields = rtype.GetFields()
elif self.setting.ignore_pr is not None: elif self.setting.ignorePr is not None:
fields = [ field for field in rtype.GetAllFields() if not self.setting.ignore_pr(field) ] fields = [ field for field in rtype.GetAllFields() if not self.setting.ignorePr(field) ]
else: else:
fields = [ field for field in rtype.GetAllFields() if self.setting.select_pr(field) ] fields = [ field for field in rtype.GetAllFields() if self.setting.selectPr(field) ]
return fields return fields
@sealed
def _DoJsonSerialize(self, result_file:ToolFile, rtype:RefType, rinstance:Any) -> Any: def _DoJsonSerialize(self, result_file:ToolFile, rtype:RefType, rinstance:Any) -> Any:
''' '''
序列化: json格式 序列化: json格式
@@ -77,7 +75,7 @@ class ESWriter(BaseModel, any_class):
raise ReflectionException(f"{ConsoleFrontColor.RED}容器<{rtype.RealType}>"\ raise ReflectionException(f"{ConsoleFrontColor.RED}容器<{rtype.RealType}>"\
f"在序列化时遇到错误:{ConsoleFrontColor.RESET}\n{e}") from e f"在序列化时遇到错误:{ConsoleFrontColor.RESET}\n{e}") from e
raise NotImplementedError(f"{ConsoleFrontColor.RED}不支持的容器: {rinstance}"\ raise NotImplementedError(f"{ConsoleFrontColor.RED}不支持的容器: {rinstance}"\
f"<{rtype.print_str(verbose=GetInternalEasySaveDebug())}>{ConsoleFrontColor.RESET}") f"<{rtype.Print2Str(verbose=GetInternalEasySaveDebug())}>{ConsoleFrontColor.RESET}")
elif hasattr(rtype.RealType, "__easy_serialize__"): elif hasattr(rtype.RealType, "__easy_serialize__"):
custom_data, is_need_type = rtype.RealType.__easy_serialize__(rinstance) custom_data, is_need_type = rtype.RealType.__easy_serialize__(rinstance)
if is_need_type: if is_need_type:
@@ -104,85 +102,79 @@ class ESWriter(BaseModel, any_class):
return layer return layer
layers: Dict[str, Any] = {} layers: Dict[str, Any] = {}
if result_file.exists(): if result_file.Exists():
filedata = result_file.load() filedata = result_file.LoadAsJson()
if isinstance(filedata, dict): if isinstance(filedata, dict):
layers = filedata layers = filedata
layers[self.setting.key] = { layers[self.setting.key] = {
"__type": AssemblyTypen(rtype.RealType), "__type": AssemblyTypen(rtype.RealType),
"value": dfs(rtype, rinstance) "value": dfs(rtype, rinstance)
} }
result_file.data = layers result_file.SaveAsJson(layers)
result_file.save_as_json()
@sealed
def _DoBinarySerialize(self, result_file:ToolFile, rinstance:Any) -> Any: def _DoBinarySerialize(self, result_file:ToolFile, rinstance:Any) -> Any:
''' '''
序列化: 二进制格式 序列化: 二进制格式
''' '''
result_file.data = rinstance result_file.SaveAsBinary(rinstance)
result_file.save_as_binary()
@virtual
def Serialize(self, result_file:ToolFile, rtype:RefType, rinstance:Any) -> Any: def Serialize(self, result_file:ToolFile, rtype:RefType, rinstance:Any) -> Any:
''' '''
序列化 序列化
''' '''
if self.setting.format == "json": if self.setting.formatMode == "json":
self._DoJsonSerialize(result_file, rtype, rinstance) self._DoJsonSerialize(result_file, rtype, rinstance)
elif self.setting.format == "binary": elif self.setting.formatMode == "binary":
self._DoBinarySerialize(result_file, rinstance) self._DoBinarySerialize(result_file, rinstance)
else: else:
raise NotImplementedError(f"不支持的格式: {self.setting.format}") raise NotImplementedError(f"不支持的格式: {self.setting.formatMode}")
@virtual
def Write[T](self, rinstance:T) -> ToolFile: def Write[T](self, rinstance:T) -> ToolFile:
''' '''
写入数据 写入数据
''' '''
result_file: ToolFile = ToolFile(self.setting.file) result_file: ToolFile = ToolFile(self.setting.file)
backup_file: ToolFile = None backup_file: ToolFile = None
if result_file.dirpath is not None and not ToolFile(result_file.dirpath).exists(): if result_file.GetDir() is not None and not ToolFile(result_file.GetDir()).Exists():
raise FileNotFoundError(f"文件路径不存在: {result_file.dirpath}") raise FileNotFoundError(f"文件路径不存在: {result_file.GetDir()}")
if result_file.exists() and self.setting.is_backup: if result_file.Exists() and self.setting.isBackup:
if result_file.dirpath is not None: if result_file.GetDir() is not None:
backup_file = result_file.dirpath | (result_file.get_filename(True) + self.setting.backup_suffix) backup_file = ToolFile(result_file.GetDir()) | (result_file.GetFilename(True) + self.setting.backup_suffix)
else: else:
backup_file = ToolFile(result_file.get_filename(True) + self.setting.backup_suffix) backup_file = ToolFile(result_file.GetFilename(True) + self.setting.backup_suffix)
result_file.copy(backup_file) result_file.Copy(backup_file)
try: try:
self.Serialize(result_file, TypeManager.GetInstance().CreateOrGetRefType(rinstance), rinstance) self.Serialize(result_file, TypeManager.GetInstance().CreateOrGetRefType(rinstance), rinstance)
except Exception: except Exception:
if backup_file is not None: if backup_file is not None:
result_file.remove() result_file.Remove()
backup_file.copy(result_file) backup_file.Copy(result_file)
backup_file.remove() backup_file.Remove()
raise raise
finally: finally:
if backup_file is not None: if backup_file is not None:
backup_file.remove() backup_file.Remove()
return result_file return result_file
class ESReader(BaseModel, any_class): class ESReader(BaseModel):
setting: EasySaveSetting = Field(description="设置") setting: EasySaveSetting = Field(description="设置")
@sealed
def _GetFields(self, rtype:RefType) -> List[FieldInfo]: def _GetFields(self, rtype:RefType) -> List[FieldInfo]:
''' '''
获取字段 获取字段
''' '''
fields: List[FieldInfo] = [] fields: List[FieldInfo] = []
if self.setting.ignore_pr is not None and self.setting.select_pr is not None: if self.setting.ignorePr is not None and self.setting.selectPr is not None:
fields = [ field for field in rtype.GetAllFields() if self.setting.select_pr(field) and not self.setting.ignore_pr(field) ] fields = [ field for field in rtype.GetAllFields() if self.setting.selectPr(field) and not self.setting.ignorePr(field) ]
elif self.setting.select_pr is None and self.setting.ignore_pr is None: elif self.setting.selectPr is None and self.setting.ignorePr is None:
fields = rtype.GetFields() fields = rtype.GetFields()
elif self.setting.ignore_pr is not None: elif self.setting.ignorePr is not None:
fields = [ field for field in rtype.GetAllFields() if not self.setting.ignore_pr(field) ] fields = [ field for field in rtype.GetAllFields() if not self.setting.ignorePr(field) ]
else: else:
fields = [ field for field in rtype.GetAllFields() if self.setting.select_pr(field) ] fields = [ field for field in rtype.GetAllFields() if self.setting.selectPr(field) ]
return fields return fields
def get_rtype_from_typen(self, type_label:str) -> RefType: def GetRtypeFromTypen(self, type_label:str) -> RefType:
''' '''
从类型标签中获取类型 从类型标签中获取类型
''' '''
@@ -217,12 +209,12 @@ class ESReader(BaseModel, any_class):
ValueError: 当rinstance不为None时抛出 ValueError: 当rinstance不为None时抛出
''' '''
# 从文件中加载JSON数据 # 从文件中加载JSON数据
layers: Dict[str, Any] = read_file.load_as_json() layers: Dict[str, Any] = read_file.LoadAsJson()
if self.setting.key not in layers: if self.setting.key not in layers:
raise ValueError(f"{ConsoleFrontColor.RED}文件中不包含目标键: {ConsoleFrontColor.RESET}{self.setting.key}") raise ValueError(f"{ConsoleFrontColor.RED}文件中不包含目标键: {ConsoleFrontColor.RESET}{self.setting.key}")
# 如果未指定类型, 则从JSON数据中获取类型信息 # 如果未指定类型, 则从JSON数据中获取类型信息
if rtype is None: if rtype is None:
rtype: RefType = self.get_rtype_from_typen(layers["__type"]) rtype: RefType = self.GetRtypeFromTypen(layers["__type"])
layers: Dict[str, Any] = layers[self.setting.key]["value"] layers: Dict[str, Any] = layers[self.setting.key]["value"]
result_instance: Any = None result_instance: Any = None
@@ -240,7 +232,7 @@ class ESReader(BaseModel, any_class):
''' '''
# 如果类型为None且当前层包含类型信息, 则获取类型 # 如果类型为None且当前层包含类型信息, 则获取类型
if isinstance(layer, dict) and "__type" in layer: if isinstance(layer, dict) and "__type" in layer:
rtype = self.get_rtype_from_typen(layer["__type"]) rtype = self.GetRtypeFromTypen(layer["__type"])
if rtype is None: if rtype is None:
raise ValueError(f"{ConsoleFrontColor.RED}当前层不包含类型信息: {ConsoleFrontColor.RESET}{LimitStringLength(str(layer), 100)}") raise ValueError(f"{ConsoleFrontColor.RED}当前层不包含类型信息: {ConsoleFrontColor.RESET}{LimitStringLength(str(layer), 100)}")
if GetInternalEasySaveDebug(): if GetInternalEasySaveDebug():
@@ -280,7 +272,7 @@ class ESReader(BaseModel, any_class):
except Exception as e: except Exception as e:
raise ReflectionException(f"容器<{LimitStringLength(str(layer), 100)}>在反序列化时遇到错误:\n{e}") from e raise ReflectionException(f"容器<{LimitStringLength(str(layer), 100)}>在反序列化时遇到错误:\n{e}") from e
raise NotImplementedError(f"{ConsoleFrontColor.RED}不支持的容器: {LimitStringLength(str(layer), 100)}"\ raise NotImplementedError(f"{ConsoleFrontColor.RED}不支持的容器: {LimitStringLength(str(layer), 100)}"\
f"<{rtype.print_str(verbose=GetInternalEasySaveDebug())}>{ConsoleFrontColor.RESET}") f"<{rtype.Print2Str(verbose=GetInternalEasySaveDebug())}>{ConsoleFrontColor.RESET}")
# 处理对象类型 # 处理对象类型
elif isinstance(rtype.RealType, type) and hasattr(rtype.RealType, "__easy_deserialize__"): elif isinstance(rtype.RealType, type) and hasattr(rtype.RealType, "__easy_deserialize__"):
return rtype.RealType.__easy_deserialize__(layer) return rtype.RealType.__easy_deserialize__(layer)
@@ -288,7 +280,7 @@ class ESReader(BaseModel, any_class):
rinstance = rtype.CreateInstance() rinstance = rtype.CreateInstance()
if GetInternalEasySaveDebug(): if GetInternalEasySaveDebug():
print_colorful(ConsoleFrontColor.YELLOW, f"rinstance rtype target: {ConsoleFrontColor.RESET}"\ print_colorful(ConsoleFrontColor.YELLOW, f"rinstance rtype target: {ConsoleFrontColor.RESET}"\
f"{rtype.print_str(verbose=True, flags=RefTypeFlag.Field|RefTypeFlag.Instance|RefTypeFlag.Public)}") f"{rtype.Print2Str(verbose=True, flags=RefTypeFlag.Field|RefTypeFlag.Instance|RefTypeFlag.Public)}")
fields:List[FieldInfo] = self._GetFields(rtype) fields:List[FieldInfo] = self._GetFields(rtype)
for field in fields: for field in fields:
if field.FieldName not in layer: if field.FieldName not in layer:
@@ -336,50 +328,47 @@ class ESReader(BaseModel, any_class):
result_instance = dfs(rtype, layers) result_instance = dfs(rtype, layers)
return result_instance return result_instance
@sealed
def _DoBinaryDeserialize(self, read_file:ToolFile, rtype:RefType) -> Any: def _DoBinaryDeserialize(self, read_file:ToolFile, rtype:RefType) -> Any:
''' '''
反序列化: 二进制格式 反序列化: 二进制格式
''' '''
return read_file.load_as_binary() return read_file.LoadAsBinary()
@virtual
def Deserialize(self, read_file:ToolFile, rtype:Optional[RefType]=None) -> Any: def Deserialize(self, read_file:ToolFile, rtype:Optional[RefType]=None) -> Any:
''' '''
反序列化 反序列化
''' '''
if self.setting.format == "json": if self.setting.formatMode == "json":
return self._DoJsonDeserialize(read_file, rtype) return self._DoJsonDeserialize(read_file, rtype)
elif self.setting.format == "binary": elif self.setting.formatMode == "binary":
return self._DoBinaryDeserialize(read_file, rtype) return self._DoBinaryDeserialize(read_file, rtype)
else: else:
raise NotImplementedError(f"不支持的格式: {self.setting.format}") raise NotImplementedError(f"不支持的格式: {self.setting.formatMode}")
@virtual
def Read[T](self, rtype:Optional[RTypen[T]]=None) -> T: def Read[T](self, rtype:Optional[RTypen[T]]=None) -> T:
''' '''
读取数据 读取数据
''' '''
read_file: ToolFile = ToolFile(self.setting.file) read_file: ToolFile = ToolFile(self.setting.file)
if not read_file.exists(): if not read_file.Exists():
raise FileNotFoundError(f"文件不存在: {read_file}") raise FileNotFoundError(f"文件不存在: {read_file}")
if read_file.is_dir(): if read_file.IsDir():
raise IsADirectoryError(f"文件是目录: {read_file}") raise IsADirectoryError(f"文件是目录: {read_file}")
return self.Deserialize(read_file, rtype) return self.Deserialize(read_file, rtype)
class EasySave(any_class): class EasySave:
@staticmethod @staticmethod
def Write[T](rinstance:T, file:tool_file_or_str=None, *, setting:Optional[EasySaveSetting]=None) -> ToolFile: def Write[T](rinstance:T, file:Optional[ToolFile|str]=None, *, setting:Optional[EasySaveSetting]=None) -> ToolFile:
''' '''
写入数据 写入数据
''' '''
return ESWriter(setting=(setting if setting is not None else EasySaveSetting(file=UnwrapperFile2Str(file)))).Write(rinstance) return ESWriter(setting=(setting if setting is not None else EasySaveSetting(file=str(file)))).Write(rinstance)
@overload @overload
@staticmethod @staticmethod
def Read[T]( def Read[T](
rtype: Typen[T], rtype: Typen[T],
file: tool_file_or_str = None, file: Optional[ToolFile|str] = None,
*, *,
setting: Optional[EasySaveSetting] = None setting: Optional[EasySaveSetting] = None
) -> T: ) -> T:
@@ -388,7 +377,7 @@ class EasySave(any_class):
@staticmethod @staticmethod
def Read[T]( def Read[T](
rtype: RTypen[T], rtype: RTypen[T],
file: tool_file_or_str = None, file: Optional[ToolFile|str] = None,
*, *,
setting: Optional[EasySaveSetting] = None setting: Optional[EasySaveSetting] = None
) -> T: ) -> T:
@@ -396,7 +385,7 @@ class EasySave(any_class):
@staticmethod @staticmethod
def Read[T]( def Read[T](
rtype: RTypen[T]|type, rtype: RTypen[T]|type,
file: tool_file_or_str = None, file: Optional[ToolFile|str] = None,
*, *,
setting: Optional[EasySaveSetting] = None setting: Optional[EasySaveSetting] = None
) -> T: ) -> T:
@@ -405,4 +394,4 @@ class EasySave(any_class):
''' '''
if isinstance(rtype, type): if isinstance(rtype, type):
rtype = TypeManager.GetInstance().CreateOrGetRefType(rtype) rtype = TypeManager.GetInstance().CreateOrGetRefType(rtype)
return ESReader(setting=(setting if setting is not None else EasySaveSetting(file=UnwrapperFile2Str(file)))).Read(rtype) return ESReader(setting=(setting if setting is not None else EasySaveSetting(file=str(file)))).Read(rtype)

View File

@@ -16,24 +16,24 @@ from typing import *
from pathlib import Path from pathlib import Path
try: try:
from pydub import AudioSegment from pydub import AudioSegment
except ImportError: except ImportError as e:
ImportingThrow("File", ["pydub"]) ImportingThrow(e, "File", ["pydub"])
try: try:
from PIL import Image, ImageFile from PIL import Image, ImageFile
except ImportError: except ImportError as e:
ImportingThrow("File", ["Pillow"]) ImportingThrow(e, "File", ["Pillow"])
try: try:
from docx import Document from docx import Document
from docx.document import Document as DocumentObject from docx.document import Document as DocumentObject
except ImportError: except ImportError as e:
ImportingThrow("File", ["python-docx"]) ImportingThrow(e, "File", ["python-docx"])
from .String import Bytes2String from .String import Bytes2String
def get_extension_name(file:str): def GetExtensionName(file:str):
return os.path.splitext(file)[1][1:] return os.path.splitext(file)[1][1:]
def get_base_filename(file:str): def GetBaseFilename(file:str):
return os.path.basename(file) return os.path.basename(file)
dir_name_type = str dir_name_type = str
@@ -67,15 +67,8 @@ class PermissionError(FileOperationError):
"""权限操作异常""" """权限操作异常"""
pass pass
try: from pydantic import BaseModel, GetCoreSchemaHandler
from pydantic import BaseModel, GetCoreSchemaHandler from pydantic_core import core_schema
from pydantic_core import core_schema
except ImportError:
class BaseModel:
def __init__(self,*args,**kwargs)->None:
pass
type GetCoreSchemaHandler = Any
type core_schema = Any
class ToolFile(BaseModel): class ToolFile(BaseModel):
OriginFullPath:str OriginFullPath:str
@@ -101,42 +94,20 @@ class ToolFile(BaseModel):
): ):
self.OriginFullPath = filePath if isinstance(filePath, str) else filePath.OriginFullPath self.OriginFullPath = filePath if isinstance(filePath, str) else filePath.OriginFullPath
def __del__(self): def __del__(self):
self.close() pass
def __str__(self): def __str__(self):
return self.GetFullPath() return self.GetFullPath()
def __setitem__(self, key:str, value):
self.datas[key] = value
def __getitem__(self, key:str):
if key not in self.datas:
self.datas[key] = None
return self.datas[key]
def __contains__(self, key:str):
return key in self.datas
def __delitem__(self, key:str):
del self.datas[key]
def __iter__(self):
return iter(self.datas)
def __len__(self):
return len(self.datas)
@override
def __enter__(self): def __enter__(self):
if self.is_open():
return self return self
if self.exists() and self.is_file():
self.load()
return self
@override
def __exit__(self, exc_type, exc_val, exc_tb): def __exit__(self, exc_type, exc_val, exc_tb):
self.close() return True
return super().__exit__(exc_type, exc_val, exc_tb)
def __or__(self, other): def __or__(self, other):
if other is None: if other is None:
return ToolFile(self.GetFullPath() if self.is_dir() else self.GetFullPath()+"\\") return ToolFile(self.GetFullPath() if self.IsDir() else self.GetFullPath()+"\\")
else: else:
return ToolFile(os.path.join(self.GetFullPath(), UnWrapper(other))) return ToolFile(os.path.join(self.GetFullPath(), str(other)))
def __idiv__(self, other): def __idiv__(self, other):
self.close()
temp = self.__or__(other) temp = self.__or__(other)
self.OriginFullPath = temp.GetFullPath() self.OriginFullPath = temp.GetFullPath()
@@ -168,213 +139,131 @@ class ToolFile(BaseModel):
return os.path.normpath(self_path) == os.path.normpath(other_path) return os.path.normpath(self_path) == os.path.normpath(other_path)
def to_path(self): def ToPath(self):
return Path(self.OriginFullPath) return Path(self.OriginFullPath)
def __Path__(self): def __Path__(self):
return Path(self.OriginFullPath) return Path(self.OriginFullPath)
def write(self, data:Union[str, bytes]): def Create(self):
self.__file.write(data) if self.Exists() == False:
if self.IsDir():
def create(self): if os.path.exists(self.GetDir()):
if self.exists() == False:
if self.is_dir():
if os.path.exists(self.get_dir()):
os.makedirs(self.OriginFullPath) os.makedirs(self.OriginFullPath)
else: else:
raise FileNotFoundError(f"{self.OriginFullPath} cannt create, because its parent path is not exist") raise FileNotFoundError(f"{self.OriginFullPath} cannt create, because its parent path is not exist")
else: else:
self.open('w') with open(self.OriginFullPath, 'w') as f:
self.close() f.write('')
return self return self
def exists(self): def Exists(self):
return os.path.exists(self.OriginFullPath) return os.path.exists(self.OriginFullPath)
def remove(self): def Remove(self):
self.close() if self.Exists():
if self.exists(): if self.IsDir():
if self.is_dir():
shutil.rmtree(self.OriginFullPath) shutil.rmtree(self.OriginFullPath)
else: else:
os.remove(self.OriginFullPath) os.remove(self.OriginFullPath)
return self return self
def copy(self, to_path:Optional[Union[Self, str]]=None): def Copy(self, targetPath:Optional[Union[Self, str]]=None):
if to_path is None: if targetPath is None:
return ToolFile(self.OriginFullPath) return ToolFile(self.OriginFullPath)
if self.exists() is False: if self.Exists() is False:
raise FileNotFoundError("file not found") raise FileNotFoundError("file not found")
self.close() target_file = ToolFile(str(targetPath))
target_file = ToolFile(UnWrapper(to_path)) if target_file.IsDir():
if target_file.is_dir():
target_file = target_file|self.GetFilename() target_file = target_file|self.GetFilename()
shutil.copy(self.OriginFullPath, UnWrapper(target_file)) shutil.copy(self.OriginFullPath, str(target_file))
return target_file return target_file
def move(self, to_path:Union[Self, str]): def Move(self, targetPath:Union[Self, str]):
if self.exists() is False: if self.Exists() is False:
raise FileNotFoundError("file not found") raise FileNotFoundError("file not found")
self.close() target_file = ToolFile(str(targetPath))
target_file = ToolFile(UnWrapper(to_path)) if target_file.IsDir():
if target_file.is_dir():
target_file = target_file|self.GetFilename() target_file = target_file|self.GetFilename()
shutil.move(self.OriginFullPath, UnWrapper(target_file)) shutil.move(self.OriginFullPath, str(target_file))
self.OriginFullPath = target_file.OriginFullPath self.OriginFullPath = target_file.OriginFullPath
return self return self
def rename(self, newpath:Union[Self, str]): def Rename(self, newpath:Union[Self, str]):
if self.exists() is False: if self.Exists() is False:
raise FileNotFoundError("file not found") raise FileNotFoundError("file not found")
self.close() newpath = str(newpath)
newpath:str = UnWrapper(newpath)
if '\\' in newpath or '/' in newpath: if '\\' in newpath or '/' in newpath:
newpath = get_base_filename(newpath) newpath = GetBaseFilename(newpath)
new_current_path = os.path.join(self.get_dir(), newpath) new_current_path = os.path.join(self.GetDir(), newpath)
os.rename(self.OriginFullPath, new_current_path) os.rename(self.OriginFullPath, new_current_path)
self.OriginFullPath = new_current_path self.OriginFullPath = new_current_path
return self return self
def refresh(self): def LoadAsJson(self) -> Any:
self.load() if self.Exists() is False or 'w' in self.OriginFullPath:
return self with open(self.OriginFullPath, 'r') as f:
def open(self, mode='r', is_refresh=False, encoding:str='utf-8', *args, **kwargs): json_data = json.load(f)
self.close() return json_data
if 'b' in mode:
self.__file = open(self.OriginFullPath, mode, *args, **kwargs)
else: else:
self.__file = open(self.OriginFullPath, mode, encoding=encoding, *args, **kwargs) raise FileNotFoundError("file not found")
if is_refresh:
self.refresh()
return self.__file
def close(self):
if self.__file:
self.__file.close()
if self.__datas_lit_key in self.datas:
self.datas[self.__datas_lit_key] = None
return self.__file
def is_open(self)->bool:
return self.__file is not None
def load(self): def LoadAsCsv(self) -> pd.DataFrame:
if self.OriginFullPath is None: if self.Exists() is False or 'w' in self.OriginFullPath:
if os.path.exists(temp_tool_file_path_name): with open(self.OriginFullPath, 'r') as f:
self.data = pickle.load(open(temp_tool_file_path_name, 'rb')) return pd.read_csv(f)
return self.data
else: else:
raise FileNotFoundError(f"{self.OriginFullPath} not found, but this ToolFile's target is None") raise FileNotFoundError("file not found")
elif self.is_dir(): def LoadAsXml(self) -> pd.DataFrame:
self.__file = open(os.path.join(self.GetFullPath(), temp_tool_file_path_name), 'rb') if self.Exists() is False or 'w' in self.OriginFullPath:
self.data = pickle.load(self.__file) with open(self.OriginFullPath, 'r') as f:
return self.data return pd.read_xml(f)
suffix = self.get_extension()
if suffix == 'json':
self.load_as_json()
elif suffix == 'csv':
self.load_as_csv()
elif suffix == 'xml':
self.load_as_xml()
elif suffix == 'xlsx' or suffix == 'xls':
self.load_as_excel()
elif suffix in text_readable_file_type:
self.load_as_text()
elif suffix == 'docx' or suffix == 'doc':
self.load_as_docx()
elif suffix in audio_file_type:
self.load_as_audio()
elif is_image_file(self.OriginFullPath):
self.load_as_image()
elif is_binary_file(self.OriginFullPath):
self.load_as_binary()
else: else:
self.load_as_unknown(suffix) raise FileNotFoundError("file not found")
return self.data def LoadAsDataframe(self) -> pd.DataFrame:
def load_as_json(self) -> pd.DataFrame: if self.Exists() is False or 'w' in self.OriginFullPath:
if self.is_open() is False or 'w' in self.__file.mode: with open(self.OriginFullPath, 'r') as f:
self.open('r') return pd.read_csv(f)
json_data = json.load(self.__file) else:
#try: raise FileNotFoundError("file not found")
# from pydantic import BaseModel def LoadAsExcel(self) -> pd.DataFrame:
# if "__type" in json_data and "pydantic.BaseModel" in json_data["__type"]: if self.Exists() is False or 'w' in self.OriginFullPath:
# del json_data["__type"] with open(self.OriginFullPath, 'r') as f:
# json_data = BaseModel.model_validate(json_data) return pd.read_excel(f)
#except: else:
# pass raise FileNotFoundError("file not found")
self.data = json_data def LoadAsBinary(self) -> bytes:
return self.data if self.Exists() is False or 'w' in self.OriginFullPath:
def load_as_csv(self) -> pd.DataFrame: with open(self.OriginFullPath, 'rb') as f:
if self.is_open() is False or 'w' in self.__file.mode: return f.read()
self.open('r') else:
self.data = pd.read_csv(self.__file) raise FileNotFoundError("file not found")
return self.data def LoadAsText(self) -> str:
def load_as_xml(self) -> pd.DataFrame: if self.Exists() is False or 'w' in self.OriginFullPath:
if self.is_open() is False or 'w' in self.__file.mode: with open(self.OriginFullPath, 'r') as f:
self.open('r') return f.read()
self.data = pd.read_xml(self.__file) else:
return self.data raise FileNotFoundError("file not found")
def load_as_dataframe(self) -> pd.DataFrame: def LoadAsWav(self):
if self.is_open() is False or 'w' in self.__file.mode: if self.Exists() is False or 'w' in self.OriginFullPath:
self.open('r') return AudioSegment.from_wav(self.OriginFullPath)
self.data = pd.read_csv(self.__file) else:
return self.data raise FileNotFoundError("file not found")
def load_as_excel(self) -> pd.DataFrame: def LoadAsAudio(self):
if self.is_open() is False or 'w' in self.__file.mode: if self.Exists() is False or 'w' in self.OriginFullPath:
self.open('r') return AudioSegment.from_file(self.OriginFullPath)
self.data = pd.read_excel(self.__file) else:
return self.data raise FileNotFoundError("file not found")
def load_as_binary(self) -> bytes: def LoadAsImage(self) -> ImageFile.ImageFile:
if self.is_open() is False or 'w' in self.__file.mode: if self.Exists() is False or 'w' in self.OriginFullPath:
self.open('rb') return Image.open(self.OriginFullPath)
self.data = self.__file.read() else:
return self.data raise FileNotFoundError("file not found")
def load_as_text(self) -> str: def LoadAsDocx(self) -> DocumentObject:
if self.is_open() is False or 'w' in self.__file.mode: if self.Exists() is False or 'w' in self.OriginFullPath:
self.open('r') return Document(self.OriginFullPath)
self.data = list_byte_to_string(self.__file.readlines()) else:
return self.data raise FileNotFoundError("file not found")
def load_as_wav(self): def LoadAsUnknown(self, suffix:str) -> Any:
self.data = AudioSegment.from_wav(self.OriginFullPath) return self.LoadAsText()
return self.data def LoadAsModel(self, model:type[BaseModel]) -> BaseModel:
def load_as_audio(self): return model.model_validate(self.LoadAsJson())
self.data = AudioSegment.from_file(self.OriginFullPath)
return self.data
def load_as_image(self) -> ImageFile.ImageFile:
self.data = Image.open(self.OriginFullPath)
return self.data
def load_as_docx(self) -> DocumentObject:
self.data = Document(self.OriginFullPath)
return self.data
def load_as_unknown(self, suffix:str) -> Any:
return self.load_as_text()
def load_as_model(self, model:type[BaseModel]) -> BaseModel:
return model.model_validate(self.load_as_json())
def save(self, path:Optional[str]=None): def SaveAsJson(self, json_data):
if path is None and self.OriginFullPath is None:
raise Exception('No file path specified')
elif path is None and self.is_dir():
with open(os.path.join(self.OriginFullPath, temp_tool_file_path_name),'wb') as temp_file:
pickle.dump(self.data, temp_file)
return self
suffix = self.get_extension(path)
if suffix == 'json':
self.save_as_json(path)
elif suffix == 'csv':
self.save_as_csv(path)
elif suffix == 'xml':
self.save_as_xml(path)
elif suffix == 'xlsx' or suffix == 'xls':
self.save_as_excel(path)
elif suffix in text_readable_file_type:
self.save_as_text(path)
elif suffix == 'docx':
self.save_as_docx(path)
elif suffix in audio_file_type:
self.save_as_audio(path, suffix)
elif is_binary_file(self.OriginFullPath):
self.save_as_binary(path)
elif is_image_file(self.OriginFullPath):
self.save_as_image(path)
else:
self.save_as_unknown(path)
return self
def save_as_json(self, path:Optional[str]=None):
json_data = self.data
try: try:
from pydantic import BaseModel from pydantic import BaseModel
if isinstance(json_data, BaseModel): if isinstance(json_data, BaseModel):
@@ -382,89 +271,51 @@ class ToolFile(BaseModel):
json_data["__type"] = f"{self.data.__class__.__name__}, pydantic.BaseModel" json_data["__type"] = f"{self.data.__class__.__name__}, pydantic.BaseModel"
except: except:
pass pass
path = path if path is not None else self.OriginFullPath with open(self.OriginFullPath, 'w', encoding='utf-8') as f:
self.close()
with open(path, 'w', encoding='utf-8') as f:
json.dump(json_data, f, indent=4) json.dump(json_data, f, indent=4)
return self return self
def save_as_csv(self, path:Optional[str]=None): def SaveAsCsv(self, csv_data:pd.DataFrame):
path = path if path is not None else self.OriginFullPath csv_data.to_csv(self.OriginFullPath)
self.data.to_csv(path)
return self return self
def save_as_xml(self, path:Optional[str]=None): def SaveAsXml(self, xml_data:pd.DataFrame):
path = path if path is not None else self.OriginFullPath xml_data.to_xml(self.OriginFullPath)
self.data.to_xml(path)
return self return self
def save_as_dataframe(self, path:Optional[str]=None): def SaveAsDataframe(self, dataframe_data:pd.DataFrame):
path = path if path is not None else self.OriginFullPath dataframe_data.to_csv(self.OriginFullPath)
self.data.to_csv(path)
return self return self
def save_as_excel(self, path:Optional[str]=None): def SaveAsExcel(self, excel_data:pd.DataFrame):
path = path if path is not None else self.OriginFullPath excel_data.to_excel(self.OriginFullPath, index=False)
self.data.to_excel(path, index=False)
return self return self
def save_as_binary(self, path:Optional[str]=None): def SaveAsBinary(self, binary_data:bytes):
if path is not None: with open(self.OriginFullPath, 'wb') as f:
with open(path, 'wb') as f: f.write(binary_data)
f.write(self.data)
f.flush()
else:
if self.is_open() is False or 'r' in self.__file.mode:
self.open('wb')
self.__file.write(self.data)
self.__file.flush()
return self return self
def save_as_text(self, path:Optional[str]=None): def SaveAsText(self, text_data:str):
if path is not None: with open(self.OriginFullPath, 'w') as f:
with open(path, 'w') as f: f.writelines(text_data)
f.writelines(self.data)
f.flush()
else:
if self.is_open() is False or 'r' in self.__file.mode:
self.open('w')
self.__file.writelines(self.data)
self.__file.flush()
return self return self
def save_as_audio(self, path:Optional[str]=None): def SaveAsAudio(self, audio_data:AudioSegment):
path = path if path is not None else self.OriginFullPath audio_data.export(self.OriginFullPath, format=self.get_extension(self.OriginFullPath))
self.data.export(path, format=self.get_extension(path))
return self return self
def save_as_image(self, path:Optional[str]=None): def SaveAsImage(self, image_data:ImageFile.ImageFile):
path = path if path is not None else self.OriginFullPath image_data.save(self.OriginFullPath)
self.data.save(path)
return self return self
def save_as_docx(self, path:Optional[str]=None): def SaveAsDocx(self, docx_data:DocumentObject):
if self.data is str: docx_data.save(self.OriginFullPath)
self.data = Document()
table = self.data.add_table(rows=1, cols=1)
table.cell(0, 0).text = self.data
path = path if path is not None else self.OriginFullPath
self.data.save(path)
return self return self
def save_as_unknown(self, path:Optional[str]=None): def SaveAsUnknown(self, unknown_data:Any):
self.save_as_text(path) self.SaveAsBinary(unknown_data)
def save_as_model(self, model:type[BaseModel], path:Optional[str]=None): def SaveAsModel(self, model:type[BaseModel]):
self.save_as_json(path) self.SaveAsJson(model)
def get_size(self) -> int: def GetSize(self) -> int:
''' '''
return: return:
return size of directory return size of directory
''' '''
return os.path.getsize(self.OriginFullPath) return os.path.getsize(self.OriginFullPath)
def get_data_type(self) -> type: def GetExtension(self):
return type(self.data) return GetExtensionName(self.OriginFullPath)
def has_data_type_is(self, types:Union[type, Sequence[type]]) -> bool:
if isinstance(types, Sequence) is False:
return self.get_data_type() == types
return self.get_data_type() in types
def get_extension(self, path:str=None):
if self.is_dir() and path is None:
raise Exception("Cannot get extension of a directory")
path = path if path is not None else self.OriginFullPath
if path is None:
raise Exception("Cannot get extension without target path")
return get_extension_name(path)
def GetFullPath(self) -> str: def GetFullPath(self) -> str:
return self.OriginFullPath return self.OriginFullPath
def GetFilename(self, is_without_extension = False): def GetFilename(self, is_without_extension = False):
@@ -473,90 +324,85 @@ class ToolFile(BaseModel):
if target path is a directory, it return top directory name if target path is a directory, it return top directory name
''' '''
if is_without_extension and '.' in self.OriginFullPath: if is_without_extension and '.' in self.OriginFullPath:
return get_base_filename(self.OriginFullPath)[:-(len(self.get_extension())+1)] return GetBaseFilename(self.OriginFullPath)[:-(len(self.GetExtension())+1)]
elif self.OriginFullPath[-1] == '/' or self.OriginFullPath[-1] == '\\': elif self.OriginFullPath[-1] == '/' or self.OriginFullPath[-1] == '\\':
return get_base_filename(self.OriginFullPath[:-1]) return GetBaseFilename(self.OriginFullPath[:-1])
else: else:
return get_base_filename(self.OriginFullPath) return GetBaseFilename(self.OriginFullPath)
def get_dir(self): def GetDir(self):
return os.path.dirname(self.OriginFullPath) return os.path.dirname(self.OriginFullPath)
def get_dir_tool_file(self): def GetDirToolFile(self):
return ToolFile(self.get_dir()) return ToolFile(self.GetDir())
def get_current_dir_name(self): def GetCurrentDirName(self):
return os.path.dirname(self.OriginFullPath) return os.path.dirname(self.OriginFullPath)
def is_dir(self): def IsDir(self):
if self.OriginFullPath[-1] == '\\' or self.GetFullPath()[-1] == '/': if self.OriginFullPath[-1] == '\\' or self.GetFullPath()[-1] == '/':
return True return True
else: else:
return os.path.isdir(self.OriginFullPath) return os.path.isdir(self.OriginFullPath)
def is_file(self): def IsFile(self):
return os.path.isfile(self.OriginFullPath) return os.path.isfile(self.OriginFullPath)
def is_binary_file(self):
return is_binary_file(self.__file)
def is_image(self):
return is_image_file(self.OriginFullPath)
def try_create_parent_path(self): def TryCreateParentPath(self):
dir_path = os.path.dirname(self.OriginFullPath) dir_path = os.path.dirname(self.OriginFullPath)
if dir_path == '': if dir_path == '':
return self return self
if not os.path.exists(dir_path): if not os.path.exists(dir_path):
os.makedirs(dir_path) os.makedirs(dir_path)
return self return self
def dir_iter(self): def DirIter(self):
return os.listdir(self.OriginFullPath) return os.listdir(self.OriginFullPath)
def dir_tool_file_iter(self): def DirToolFileIter(self):
result = [self] result = [self]
result.clear() result.clear()
for file in os.listdir(self.OriginFullPath): for file in os.listdir(self.OriginFullPath):
result.append(self|file) result.append(self|file)
return result return result
def back_to_parent_dir(self): def BackToParentDir(self):
self.close() self.OriginFullPath = self.GetDir()
self.OriginFullPath = self.get_dir()
return self return self
def get_parent_dir(self): def GetParentDir(self):
return ToolFile(self.get_dir()) return ToolFile(self.GetDir())
def dir_count(self, ignore_folder:bool = True): def DirCount(self, ignore_folder:bool = True):
iter = self.dir_iter() iter = self.DirIter()
result = 0 result = 0
for content in iter: for content in iter:
if ignore_folder and os.path.isdir(os.path.join(self.OriginFullPath, content)): if ignore_folder and os.path.isdir(os.path.join(self.OriginFullPath, content)):
continue continue
result += 1 result += 1
return result return result
def dir_clear(self): def DirClear(self):
for file in self.dir_tool_file_iter(): for file in self.DirToolFileIter():
file.remove() file.Remove()
return self return self
def first_file_with_extension(self, extension:str): def FirstFileWithExtension(self, extension:str):
target_dir = self if self.is_dir() else ToolFile(self.get_dir()) target_dir = self if self.IsDir() else ToolFile(self.GetDir())
for file in target_dir.dir_tool_file_iter(): for file in target_dir.DirToolFileIter():
if file.is_dir() is False and file.get_extension() == extension: if file.IsDir() is False and file.GetExtension() == extension:
return file return file
return None return None
def first_file(self, pr:Callable[[str], bool]): def FirstFile(self, pr:Callable[[str], bool]):
target_dir = self if self.is_dir() else ToolFile(self.get_dir()) target_dir = self if self.IsDir() else ToolFile(self.GetDir())
for file in target_dir.dir_tool_file_iter(): for file in target_dir.DirToolFileIter():
if pr(file.GetFilename()): if pr(file.GetFilename()):
return file return file
return None return None
def find_file_with_extension(self, extension:str): def FindFileWithExtension(self, extension:str):
target_dir = self if self.is_dir() else ToolFile(self.get_dir()) target_dir = self if self.IsDir() else ToolFile(self.GetDir())
result:List[ToolFile] = [] result:List[ToolFile] = []
for file in target_dir.dir_tool_file_iter(): for file in target_dir.DirToolFileIter():
if file.is_dir() is False and file.get_extension() == extension: if file.IsDir() is False and file.GetExtension() == extension:
result.append(file) result.append(file)
return result return result
def find_file(self, pr:Callable[[str], bool]): def FindFile(self, pr:Callable[[str], bool]):
target_dir = self if self.is_dir() else ToolFile(self.get_dir()) target_dir = self if self.IsDir() else ToolFile(self.GetDir())
result:List[ToolFile] = [] result:List[ToolFile] = []
for file in target_dir.dir_tool_file_iter(): for file in target_dir.DirToolFileIter():
if pr(file.GetFilename()): if pr(file.GetFilename()):
result.append(file) result.append(file)
return result return result
def dir_walk( def DirWalk(
self, self,
top, top,
topdown: bool = True, topdown: bool = True,
@@ -565,77 +411,28 @@ class ToolFile(BaseModel):
) -> Iterator[tuple[dir_name_type, list[dir_name_type], list[file_name_type]]]: ) -> Iterator[tuple[dir_name_type, list[dir_name_type], list[file_name_type]]]:
return os.walk(self.OriginFullPath, top=top, topdown=topdown, onerror=onerror, followlinks=followlinks) return os.walk(self.OriginFullPath, top=top, topdown=topdown, onerror=onerror, followlinks=followlinks)
def append_text(self, line:str):
if self.has_data_type_is(type(str)):
self.data += line
elif self.has_data_type_is(type(DocumentObject)):
self.data.add_paragraph(line)
else:
raise TypeError(f"Unsupported data type for {sys._getframe().f_code.co_name}")
return self
def bool(self): def bool(self):
return self.exists() return self.Exists()
def __bool__(self): def __bool__(self):
return self.exists() return self.Exists()
def must_exists_path(self): def MustExistsPath(self):
self.close() self.TryCreateParentPath()
self.try_create_parent_path() self.Create()
self.create()
return self return self
def make_file_inside(self, data:Self, is_delete_source = False): def MakeFileInside(self, data:Self, is_delete_source = False):
if self.is_dir() is False: if self.IsDir() is False:
raise Exception("Cannot make file inside a file, because this object target is not a directory") raise Exception("Cannot make file inside a file, because this object target is not a directory")
result = self|data.GetFilename() result:ToolFile = self|data.GetFilename()
if is_delete_source: if is_delete_source:
data.move(result) data.Move(result)
else: else:
data.copy(result) data.Copy(result)
return self return self
@property def Compress(self, output_path: Optional[str] = None, format: str = 'zip') -> 'ToolFile':
def extension(self):
if self.is_dir():
return None
return self.get_extension()
@property
def filename(self):
if self.is_dir():
return None
return self.GetFilename(True)
@property
def dirname(self):
if self.is_dir():
return self.get_current_dir_name()
return None
@property
def dirpath(self):
if self.is_dir():
return self.get_current_dir_name()
return None
@property
def shortname(self):
return self.GetFilename(False)
@property
def fullpath(self):
return self.GetFullPath()
def make_lib_path(self) -> Path:
return Path(self.OriginFullPath)
def in_extensions(self, *args:str) -> bool:
return self.get_extension() in args
@override
def SymbolName(self):
return f"ToolFile<{self.GetFullPath()}>"
@override
def ToString(self):
return self.GetFullPath()
def compress(self, output_path: Optional[str] = None, format: str = 'zip') -> Self:
""" """
压缩文件或目录 压缩文件或目录
Args: Args:
@@ -644,7 +441,7 @@ class ToolFile(BaseModel):
Returns: Returns:
压缩后的文件对象 压缩后的文件对象
""" """
if not self.exists(): if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}") raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
if output_path is None: if output_path is None:
@@ -653,7 +450,7 @@ class ToolFile(BaseModel):
try: try:
if format == 'zip': if format == 'zip':
with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf: with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
if self.is_dir(): if self.IsDir():
for root, _, files in os.walk(self.GetFullPath()): for root, _, files in os.walk(self.GetFullPath()):
for file in files: for file in files:
file_path = os.path.join(root, file) file_path = os.path.join(root, file)
@@ -663,7 +460,7 @@ class ToolFile(BaseModel):
zipf.write(self.GetFullPath(), self.GetFilename()) zipf.write(self.GetFullPath(), self.GetFilename())
elif format == 'tar': elif format == 'tar':
with tarfile.open(output_path, 'w') as tarf: with tarfile.open(output_path, 'w') as tarf:
if self.is_dir(): if self.IsDir():
tarf.add(self.GetFullPath(), arcname=self.GetFilename()) tarf.add(self.GetFullPath(), arcname=self.GetFilename())
else: else:
tarf.add(self.GetFullPath(), arcname=self.GetFilename()) tarf.add(self.GetFullPath(), arcname=self.GetFilename())
@@ -674,7 +471,7 @@ class ToolFile(BaseModel):
except Exception as e: except Exception as e:
raise CompressionError(f"Compression failed: {str(e)}") raise CompressionError(f"Compression failed: {str(e)}")
def decompress(self, output_path: Optional[str] = None) -> Self: def Decompress(self, output_path: Optional[str] = None) -> 'ToolFile':
""" """
解压文件 解压文件
Args: Args:
@@ -682,27 +479,27 @@ class ToolFile(BaseModel):
Returns: Returns:
解压后的目录对象 解压后的目录对象
""" """
if not self.exists(): if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}") raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
if output_path is None: if output_path is None:
output_path = self.GetFullPath() + '_extracted' output_path = self.GetFullPath() + '_extracted'
try: try:
if self.get_extension() == 'zip': if self.GetExtension() == 'zip':
with zipfile.ZipFile(self.GetFullPath(), 'r') as zipf: with zipfile.ZipFile(self.GetFullPath(), 'r') as zipf:
zipf.extractall(output_path) zipf.extractall(output_path)
elif self.get_extension() == 'tar': elif self.GetExtension() == 'tar':
with tarfile.open(self.GetFullPath(), 'r') as tarf: with tarfile.open(self.GetFullPath(), 'r') as tarf:
tarf.extractall(output_path) tarf.extractall(output_path)
else: else:
raise CompressionError(f"Unsupported archive format: {self.get_extension()}") raise CompressionError(f"Unsupported archive format: {self.GetExtension()}")
return ToolFile(output_path) return ToolFile(output_path)
except Exception as e: except Exception as e:
raise CompressionError(f"Decompression failed: {str(e)}") raise CompressionError(f"Decompression failed: {str(e)}")
def encrypt(self, key: str, algorithm: str = 'AES') -> Self: def Encrypt(self, key: str, algorithm: str = 'AES') -> 'ToolFile':
""" """
加密文件 加密文件
Args: Args:
@@ -714,7 +511,7 @@ class ToolFile(BaseModel):
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
if not self.exists(): if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}") raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try: try:
@@ -759,7 +556,7 @@ class ToolFile(BaseModel):
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
if not self.exists(): if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}") raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try: try:
@@ -804,7 +601,7 @@ class ToolFile(BaseModel):
Returns: Returns:
文件的哈希值(十六进制字符串) 文件的哈希值(十六进制字符串)
""" """
if not self.exists(): if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}") raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try: try:
@@ -834,7 +631,7 @@ class ToolFile(BaseModel):
Returns: Returns:
是否匹配 是否匹配
""" """
if not self.exists(): if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}") raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try: try:
@@ -852,7 +649,7 @@ class ToolFile(BaseModel):
Returns: Returns:
哈希值文件对象 哈希值文件对象
""" """
if not self.exists(): if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}") raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try: try:
@@ -891,7 +688,7 @@ class ToolFile(BaseModel):
""" """
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
if not self.exists(): if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}") raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try: try:
@@ -972,15 +769,15 @@ class ToolFile(BaseModel):
Returns: Returns:
备份文件对象 备份文件对象
""" """
if not self.exists(): if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}") raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try: try:
# 生成备份目录 # 生成备份目录
if backup_dir is None: if backup_dir is None:
backup_dir = os.path.join(self.get_dir(), '.backup') backup_dir = os.path.join(self.GetDir(), '.backup')
backup_dir:Self = ToolFile(backup_dir) backup_dir:Self = ToolFile(backup_dir)
backup_dir.must_exists_path() backup_dir.MustExistsPath()
# 生成备份文件名 # 生成备份文件名
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
@@ -990,7 +787,7 @@ class ToolFile(BaseModel):
if backup_format == 'zip': if backup_format == 'zip':
backup_path = backup_dir | f"{backup_name}.zip" backup_path = backup_dir | f"{backup_name}.zip"
with zipfile.ZipFile(backup_path.GetFullPath(), 'w', zipfile.ZIP_DEFLATED) as zipf: with zipfile.ZipFile(backup_path.GetFullPath(), 'w', zipfile.ZIP_DEFLATED) as zipf:
if self.is_dir(): if self.IsDir():
for root, _, files in os.walk(self.GetFullPath()): for root, _, files in os.walk(self.GetFullPath()):
for file in files: for file in files:
file_path = os.path.join(root, file) file_path = os.path.join(root, file)
@@ -1001,7 +798,7 @@ class ToolFile(BaseModel):
elif backup_format == 'tar': elif backup_format == 'tar':
backup_path = backup_dir | f"{backup_name}.tar" backup_path = backup_dir | f"{backup_name}.tar"
with tarfile.open(backup_path.GetFullPath(), 'w') as tarf: with tarfile.open(backup_path.GetFullPath(), 'w') as tarf:
if self.is_dir(): if self.IsDir():
tarf.add(self.GetFullPath(), arcname=self.GetFilename()) tarf.add(self.GetFullPath(), arcname=self.GetFilename())
else: else:
tarf.add(self.GetFullPath(), arcname=self.GetFilename()) tarf.add(self.GetFullPath(), arcname=self.GetFilename())
@@ -1014,7 +811,7 @@ class ToolFile(BaseModel):
'original_path': self.GetFullPath(), 'original_path': self.GetFullPath(),
'backup_time': timestamp, 'backup_time': timestamp,
'file_size': self.get_size(), 'file_size': self.get_size(),
'is_directory': self.is_dir(), 'is_directory': self.IsDir(),
'hash': self.calculate_hash() 'hash': self.calculate_hash()
} }
metadata_path = backup_dir | f"{backup_name}.meta.json" metadata_path = backup_dir | f"{backup_name}.meta.json"
@@ -1026,7 +823,7 @@ class ToolFile(BaseModel):
backups = backup_dir.find_file(lambda f: ToolFile(f).GetFilename().startswith(self.GetFilename() + '_')) backups = backup_dir.find_file(lambda f: ToolFile(f).GetFilename().startswith(self.GetFilename() + '_'))
backups.sort(key=lambda f: f.GetFilename(), reverse=True) backups.sort(key=lambda f: f.GetFilename(), reverse=True)
for old_backup in backups[max_backups:]: for old_backup in backups[max_backups:]:
old_backup.remove() old_backup.Remove()
return backup_path return backup_path
@@ -1051,7 +848,7 @@ class ToolFile(BaseModel):
if not isinstance(backup_file, ToolFile): if not isinstance(backup_file, ToolFile):
backup_file:Self = ToolFile(backup_file) backup_file:Self = ToolFile(backup_file)
if not backup_file.exists(): if not backup_file.Exists():
raise FileNotFoundError(f"Backup file not found: {backup_file.GetFullPath()}") raise FileNotFoundError(f"Backup file not found: {backup_file.GetFullPath()}")
try: try:
@@ -1091,12 +888,12 @@ class ToolFile(BaseModel):
Returns: Returns:
备份文件列表 备份文件列表
""" """
if not self.exists(): if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}") raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try: try:
backup_dir:Self = ToolFile(os.path.join(self.get_dir(), '.backup')) backup_dir:Self = ToolFile(os.path.join(self.GetDir(), '.backup'))
if not backup_dir.exists(): if not backup_dir.Exists():
return [] return []
backups = backup_dir.find_file(lambda f: ToolFile(f).GetFilename().startswith(self.GetFilename() + '_')) backups = backup_dir.find_file(lambda f: ToolFile(f).GetFilename().startswith(self.GetFilename() + '_'))
@@ -1116,7 +913,7 @@ class ToolFile(BaseModel):
- execute: 是否可执行 - execute: 是否可执行
- hidden: 是否隐藏 - hidden: 是否隐藏
""" """
if not self.exists(): if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}") raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try: try:
@@ -1149,7 +946,7 @@ class ToolFile(BaseModel):
Returns: Returns:
文件对象本身 文件对象本身
""" """
if not self.exists(): if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}") raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try: try:
@@ -1189,13 +986,13 @@ class ToolFile(BaseModel):
else: # Unix/Linux/Mac else: # Unix/Linux/Mac
if hidden: if hidden:
if not self.GetFilename().startswith('.'): if not self.GetFilename().startswith('.'):
self.rename('.' + self.GetFilename()) self.Rename('.' + self.GetFilename())
else: else:
if self.GetFilename().startswith('.'): if self.GetFilename().startswith('.'):
self.rename(self.GetFilename()[1:]) self.Rename(self.GetFilename()[1:])
# 递归设置目录权限 # 递归设置目录权限
if recursive and self.is_dir(): if recursive and self.IsDir():
for root, _, files in os.walk(self.GetFullPath()): for root, _, files in os.walk(self.GetFullPath()):
for file in files: for file in files:
file_path = os.path.join(root, file) file_path = os.path.join(root, file)
@@ -1252,12 +1049,6 @@ class ToolFile(BaseModel):
""" """
return self.get_permissions()['hidden'] return self.get_permissions()['hidden']
def WrapperFile(file) -> ToolFile:
if isinstance(file, ToolFile):
return file
else:
return ToolFile(UnWrapper(file))
def split_elements( def split_elements(
file: Union[ToolFile, str], file: Union[ToolFile, str],
*, *,
@@ -1268,8 +1059,6 @@ def split_elements(
output_must_exist: bool = True, output_must_exist: bool = True,
output_callback: Optional[Callable[[ToolFile], None]] = None output_callback: Optional[Callable[[ToolFile], None]] = None
) -> List[List[ToolFile]]: ) -> List[List[ToolFile]]:
if is_loss_tool_file(file):
return []
result: List[List[ToolFile]] = tool_split_elements(WrapperFile(file).dir_tool_file_iter(), result: List[List[ToolFile]] = tool_split_elements(WrapperFile(file).dir_tool_file_iter(),
ratios=ratios, ratios=ratios,
pr=pr, pr=pr,
@@ -1278,12 +1067,12 @@ def split_elements(
return result return result
for i in range(min(len(output_dirs), len(result))): for i in range(min(len(output_dirs), len(result))):
output_dir: ToolFile = output_dirs[i] output_dir: ToolFile = output_dirs[i]
if output_dir.is_dir() is False: if output_dir.IsDir() is False:
raise Exception("Outputs must be directory") raise Exception("Outputs must be directory")
if output_must_exist: if output_must_exist:
output_dir.must_exists_as_new() output_dir.must_exists_as_new()
for file in result[i]: for file in result[i]:
current = output_dirs[i].make_file_inside(file) current = output_dirs[i].MakeFileInside(file)
if output_callback: if output_callback:
output_callback(current) output_callback(current)

View File

@@ -39,7 +39,7 @@ class ReflectionException(Exception):
self.message = f"{ConsoleFrontColor.RED}{message}{ConsoleFrontColor.RESET}" self.message = f"{ConsoleFrontColor.RED}{message}{ConsoleFrontColor.RESET}"
super().__init__(self.message) super().__init__(self.message)
def get_type_from_string(type_string:str) -> type: def String2Type(type_string:str) -> type:
""" """
根据字符串生成类型,使用缓存提高性能 根据字符串生成类型,使用缓存提高性能
""" """
@@ -84,11 +84,11 @@ def get_type_from_string(type_string:str) -> type:
# 更新缓存 # 更新缓存
if result is not None: if result is not None:
_type_string_cache[type_string] = result _type_string_cache[type_string] = result
return result return result
raise TypeError(f"Cannot find type '{type_string}', type_string is <{type_string}>")
@functools.lru_cache(maxsize=256) @functools.lru_cache(maxsize=256)
def get_type_from_string_with_module(type_string:str, module_name:str) -> type|None: def StringWithModel2Type(type_string:str, module_name:str) -> type|None:
''' '''
根据字符串生成类型,带模块名参数,使用缓存 根据字符串生成类型,带模块名参数,使用缓存
''' '''
@@ -111,7 +111,7 @@ def get_type_from_string_with_module(type_string:str, module_name:str) -> type|N
return None return None
# 获取泛型参数 # 获取泛型参数
def get_generic_args(type_hint: type | Any) -> tuple[type | None, tuple[type, ...] | None]: def GetGenericArgs(type_hint: type | Any) -> tuple[type | None, tuple[type, ...] | None]:
origin = get_origin(type_hint) # 获取原始类型 origin = get_origin(type_hint) # 获取原始类型
args = get_args(type_hint) # 获取泛型参数 args = get_args(type_hint) # 获取泛型参数
@@ -119,7 +119,7 @@ def get_generic_args(type_hint: type | Any) -> tuple[type | None, tuple[type, ..
return None, None return None, None
return origin, args return origin, args
def is_generic(type_hint: type | Any) -> bool: def IsGeneric(type_hint: type | Any) -> bool:
return "__origin__" in dir(type_hint) return "__origin__" in dir(type_hint)
class _SpecialIndictaor: class _SpecialIndictaor:
@@ -134,9 +134,6 @@ class ListIndictaor(_SpecialIndictaor):
return f"ListIndictaor<elementType={self.elementType}>" return f"ListIndictaor<elementType={self.elementType}>"
def __str__(self) -> str: def __str__(self) -> str:
return self.__repr__() return self.__repr__()
@override
def ToString(self) -> str:
return self.__repr__()
def __hash__(self) -> int: def __hash__(self) -> int:
return hash(List[self.elementType]) return hash(List[self.elementType])
@@ -152,9 +149,6 @@ class DictIndictaor(_SpecialIndictaor):
return f"DictIndictaor<keyType={self.keyType}, valueType={self.valueType}>" return f"DictIndictaor<keyType={self.keyType}, valueType={self.valueType}>"
def __str__(self) -> str: def __str__(self) -> str:
return self.__repr__() return self.__repr__()
@override
def ToString(self) -> str:
return self.__repr__()
def __hash__(self) -> int: def __hash__(self) -> int:
return hash(Dict[self.keyType, self.valueType]) return hash(Dict[self.keyType, self.valueType])
@@ -168,9 +162,6 @@ class TupleIndictaor(_SpecialIndictaor):
return f"TupleIndictaor<{', '.join(map(str, self.elementTypes))}>" return f"TupleIndictaor<{', '.join(map(str, self.elementTypes))}>"
def __str__(self) -> str: def __str__(self) -> str:
return self.__repr__() return self.__repr__()
@override
def ToString(self) -> str:
return self.__repr__()
def __hash__(self) -> int: def __hash__(self) -> int:
return hash(Tuple[self.elementTypes]) return hash(Tuple[self.elementTypes])
@@ -184,9 +175,6 @@ class SetIndictaor(_SpecialIndictaor):
return f"SetIndictaor<elementType={self.elementType}>" return f"SetIndictaor<elementType={self.elementType}>"
def __str__(self) -> str: def __str__(self) -> str:
return self.__repr__() return self.__repr__()
@override
def ToString(self) -> str:
return self.__repr__()
def __hash__(self) -> int: def __hash__(self) -> int:
return hash(Set[self.elementType]) return hash(Set[self.elementType])
@@ -205,7 +193,7 @@ def memoize(func):
# 优化to_type函数 # 优化to_type函数
@memoize @memoize
def to_type( def ToType(
typen: type|Any|str, typen: type|Any|str,
*, *,
module_name: str|None=None module_name: str|None=None
@@ -261,9 +249,9 @@ def to_type(
for module in sys.modules.values(): for module in sys.modules.values():
if type_final in module.__dict__: if type_final in module.__dict__:
return module.__dict__[type_final] return module.__dict__[type_final]
return get_type_from_string(typen) return String2Type(typen)
elif is_union(typen): elif IsUnion(typen):
uTypes = get_union_types(typen) uTypes = GetUnionTypes(typen)
uTypes = [uType for uType in uTypes if uType is not type(None)] uTypes = [uType for uType in uTypes if uType is not type(None)]
if len(uTypes) == 1: if len(uTypes) == 1:
return uTypes[0] return uTypes[0]
@@ -286,16 +274,16 @@ def to_type(
else: else:
return type(typen) return type(typen)
def try_to_type(typen:type|Any|str, *, module_name:str|None=None) -> type|List[type]|_SpecialIndictaor|None: def TryToType(typen:type|Any|str, *, module_name:str|None=None) -> type|List[type]|_SpecialIndictaor|None:
try: try:
return to_type(typen, module_name=module_name) return ToType(typen, module_name=module_name)
except Exception: except Exception:
return None return None
def is_union(type_hint: type | Any) -> bool: def IsUnion(type_hint: type | Any) -> bool:
return "__origin__" in dir(type_hint) and type_hint.__origin__ == Union return "__origin__" in dir(type_hint) and type_hint.__origin__ == Union
def get_union_types(type_hint: type | Any) -> List[type]: def GetUnionTypes(type_hint: type | Any) -> List[type]:
return [t for t in type_hint.__args__] return [t for t in type_hint.__args__]
class TypeVarIndictaor: class TypeVarIndictaor:
@@ -306,7 +294,7 @@ class AnyVarIndicator:
# 优化decay_type函数 # 优化decay_type函数
@memoize @memoize
def decay_type( def DecayType(
type_hint: type|Any, type_hint: type|Any,
*, *,
module_name: str|None=None module_name: str|None=None
@@ -318,28 +306,28 @@ def decay_type(
if GetInternalReflectionDebug(): if GetInternalReflectionDebug():
print_colorful(ConsoleFrontColor.YELLOW, f"Decay: {type_hint}") print_colorful(ConsoleFrontColor.YELLOW, f"Decay: {type_hint}")
result:type|List[type] = None result: type|List[type] = None
# 处理字符串类型 # 处理字符串类型
if isinstance(type_hint, str): if isinstance(type_hint, str):
try: try:
result = to_type(type_hint, module_name=module_name) result = ToType(type_hint, module_name=module_name)
except TypeError: except TypeError:
result = Any result = Any
# 处理forward reference # 处理forward reference
elif hasattr(type_hint, "__forward_arg__"): elif hasattr(type_hint, "__forward_arg__"):
result = to_type(type_hint.__forward_arg__, module_name=module_name) result = ToType(type_hint.__forward_arg__, module_name=module_name)
# 处理type类型 # 处理type类型
elif type_hint is type: elif type_hint is type:
result = type_hint result = type_hint
# 处理union类型 # 处理union类型
elif is_union(type_hint): elif IsUnion(type_hint):
result = get_union_types(type_hint) result = GetUnionTypes(type_hint)
# 处理TypeVar # 处理TypeVar
elif isinstance(type_hint, TypeVar): elif isinstance(type_hint, TypeVar):
result = TypeVarIndictaor result = TypeVarIndictaor
# 处理泛型类型 # 处理泛型类型
elif is_generic(type_hint): elif IsGeneric(type_hint):
result = get_origin(type_hint) result = get_origin(type_hint)
else: else:
raise ReflectionException(f"Invalid type: {type_hint}<{type_hint.__class__}>") raise ReflectionException(f"Invalid type: {type_hint}<{type_hint.__class__}>")
@@ -348,7 +336,7 @@ def decay_type(
print_colorful(ConsoleFrontColor.YELLOW, f"Result: {result}") print_colorful(ConsoleFrontColor.YELLOW, f"Result: {result}")
return result return result
def is_just_defined_in_current_class(member_name:str, current_class:type) -> bool: def IsJustDefinedInCurrentClass(member_name:str, current_class:type) -> bool:
''' '''
检查成员是否只在当前类中定义,而不是在父类中定义 检查成员是否只在当前类中定义,而不是在父类中定义
''' '''
@@ -363,104 +351,11 @@ def is_just_defined_in_current_class(member_name:str, current_class:type) -> boo
return False return False
return True return True
class light_reflection(any_class): class BaseInfo(BaseModel):
def __init__(self, obj:object, type_str:str=None, *args, **kwargs): def SymbolName(self) -> str:
if obj is not None: return "BaseInfo"
self.obj = obj def ToString(self) -> str:
elif type_str is not None: return self.SymbolName()
self.obj = self.create_instance(type_str, args, kwargs)
@override
def SymbolName(self):
return "light_reflection"
@override
def ToString(self):
return f"ToolReflection<{type(self.obj).__name__}>"
def get_attributes(self):
"""获取对象的所有属性和它们的值"""
return {attr: getattr(self.obj, attr) for attr in dir(self.obj) if not callable(getattr(self.obj, attr)) and not attr.startswith("__")}
def get_methods(self):
"""获取对象的所有方法"""
return {method: getattr(self.obj, method) for method in dir(self.obj) if callable(getattr(self.obj, method)) and not method.startswith("__")}
def contains_attribute(self, attr_name):
"""检查对象是否具有某个属性"""
return hasattr(self.obj, attr_name)
def contains_method(self, method_name):
"""检查对象是否具有某个方法"""
return hasattr(self.obj, method_name) and callable(getattr(self.obj, method_name))
def call_method(self, method_name, *args, **kwargs):
"""调用对象的方法"""
if self.contains_method(method_name):
return getattr(self.obj, method_name)(*args, **kwargs)
else:
raise AttributeError(f"{self.obj.__class__.__name__} object has no method '{method_name}'")
def set_attribute(self, attr_name, value):
"""设置对象的属性值"""
if self.contains_attribute(attr_name):
setattr(self.obj, attr_name, value)
else:
raise AttributeError(f"{self.obj.__class__.__name__} object has no attribute '{attr_name}'")
def set(self, field:str, value):
self.set_attribute(field, value)
def get_attribute(self, attr_name):
"""获取对象的属性值"""
if self.contains_attribute(attr_name):
return getattr(self.obj, attr_name)
else:
raise AttributeError(f"{self.obj.__class__.__name__} object has no attribute '{attr_name}'")
def get(self, field:str):
return self.get_attribute(field)
def create_instance(self, type_string:str, *args, **kwargs):
"""根据类型字符串生成类型的实例"""
type_ = get_type_from_string(type_string)
return type_(*args, **kwargs)
def create_instance_ex(self, type_string:str, params: Union[Dict[str,object], object]={}):
"""根据类型字符串生成类型的实例"""
typen = get_type_from_string(type_string)
if type_string in type_symbols:
return typen(params)
if params is None or len(params) == 0:
return typen()
# 获取构造函数参数信息
constructor_params = inspect.signature(typen.__init__).parameters
if len(constructor_params) == 0:
return typen()
# 准备构造函数参数
init_args = {'args':None, 'kwargs':None}
for param_name, param in constructor_params.items():
if param_name == 'self':
continue
if param_name in params:
init_args[param_name] = params[param_name]
elif param.default is not param.empty:
init_args[param_name] = param.default
elif param_name == 'args' or param_name == 'kwargs':
continue
else:
raise TypeError(f"Cannot instantiate type '{type_string}' without required parameter '{param_name}'")
return typen(**init_args)
class BaseInfo(BaseModel, any_class):
def __init__(self, **kwargs):
BaseModel.__init__(self, **kwargs)
any_class.__init__(self)
@virtual
def __str__(self) -> str:
return self.ToString()
class MemberInfo(BaseInfo): class MemberInfo(BaseInfo):
_MemberName: str = PrivateAttr(default="") _MemberName: str = PrivateAttr(default="")
@@ -479,7 +374,7 @@ class MemberInfo(BaseInfo):
def MemberName(self) -> str: def MemberName(self) -> str:
return self._MemberName return self._MemberName
@property @property
def ParentType(self) -> type: def ParentType(self) -> Optional[type]:
return self._ParentType return self._ParentType
@property @property
def IsStatic(self) -> bool: def IsStatic(self) -> bool:
@@ -516,9 +411,9 @@ class ValueInfo(BaseInfo):
@property @property
def IsUnion(self) -> bool: def IsUnion(self) -> bool:
return is_union(self._RealType) return IsUnion(self._RealType)
@property @property
def RealType(self): def RealType(self) -> type|Any:
return self._RealType return self._RealType
@property @property
def IsCollection(self) -> bool: def IsCollection(self) -> bool:
@@ -609,7 +504,7 @@ class ValueInfo(BaseInfo):
if valueType is type(None): if valueType is type(None):
return True return True
if self.IsUnion: if self.IsUnion:
return any(ValueInfo(uType).Verify(valueType) for uType in get_union_types(self.RealType)) return any(ValueInfo(uType).Verify(valueType) for uType in GetUnionTypes(self.RealType))
elif self.RealType is Any: elif self.RealType is Any:
return True return True
elif self.RealType is type(None): elif self.RealType is type(None):
@@ -623,7 +518,7 @@ class ValueInfo(BaseInfo):
def DecayToList(self) -> List[Self]: def DecayToList(self) -> List[Self]:
result:List[Self] = [] result:List[Self] = []
if self.IsUnion: if self.IsUnion:
for uType in get_union_types(self.RealType): for uType in GetUnionTypes(self.RealType):
result.extend(ValueInfo(uType).DecayToList()) result.extend(ValueInfo(uType).DecayToList())
else: else:
result.append(self) result.append(self)
@@ -649,7 +544,7 @@ class ValueInfo(BaseInfo):
module_name: Optional[str] = None, module_name: Optional[str] = None,
SelfType: type|Any|None = None, SelfType: type|Any|None = None,
**kwargs **kwargs
) -> Self: ) -> 'ValueInfo':
if GetInternalReflectionDebug(): if GetInternalReflectionDebug():
print_colorful(ConsoleFrontColor.BLUE, f"Current ValueInfo.Create Frame: "\ print_colorful(ConsoleFrontColor.BLUE, f"Current ValueInfo.Create Frame: "\
f"metaType={metaType}, SelfType={SelfType}") f"metaType={metaType}, SelfType={SelfType}")
@@ -665,17 +560,17 @@ class ValueInfo(BaseInfo):
else: else:
return ValueInfo(metaType, **kwargs) return ValueInfo(metaType, **kwargs)
elif isinstance(metaType, str): elif isinstance(metaType, str):
type_ = try_to_type(metaType, module_name=module_name) type_ = TryToType(metaType, module_name=module_name)
if type_ is None: if type_ is None:
return ValueInfo(metaType, **kwargs) return ValueInfo(metaType, **kwargs)
else: else:
return ValueInfo(type_, **kwargs) return ValueInfo(type_, **kwargs)
elif metaType is Self: elif isinstance(metaType, Self)#metaType is Self:
if SelfType is None: if SelfType is None:
raise ReflectionException("SelfType is required when metaType is <Self>") raise ReflectionException("SelfType is required when metaType is <Self>")
return ValueInfo.Create(SelfType, **kwargs) return ValueInfo.Create(SelfType, **kwargs)
elif isinstance(metaType, TypeVar): elif isinstance(metaType, TypeVar):
gargs = get_generic_args(metaType) gargs = GetGenericArgs(metaType)
if len(gargs) == 1: if len(gargs) == 1:
return ValueInfo(gargs[0], **kwargs) return ValueInfo(gargs[0], **kwargs)
else: else:
@@ -687,7 +582,7 @@ class ValueInfo(BaseInfo):
elif oType is dict: elif oType is dict:
return ValueInfo(dict, [get_args(metaType)[0], get_args(metaType)[1]]) return ValueInfo(dict, [get_args(metaType)[0], get_args(metaType)[1]])
elif oType is tuple: elif oType is tuple:
return ValueInfo(tuple, to_list(get_args(metaType))) return ValueInfo(tuple, list(get_args(metaType)))
elif oType is set: elif oType is set:
return ValueInfo(set, [get_args(metaType)[0]]) return ValueInfo(set, [get_args(metaType)[0]])
return ValueInfo(metaType, **kwargs) return ValueInfo(metaType, **kwargs)
@@ -721,7 +616,7 @@ class FieldInfo(MemberInfo):
@property @property
def IsUnion(self) -> bool: def IsUnion(self) -> bool:
return self._MetaType.IsUnion return self.ValueType.IsUnion
@property @property
def FieldName(self) -> str: def FieldName(self) -> str:
''' '''
@@ -729,19 +624,18 @@ class FieldInfo(MemberInfo):
''' '''
return self.MemberName return self.MemberName
@property @property
def ValueType(self): def ValueType(self) -> ValueInfo:
return self._MetaType return self._MetaType
@property @property
def FieldType(self): def FieldType(self):
''' '''
字段类型 字段类型
''' '''
return self._MetaType.RealType return self.ValueType.RealType
def Verify(self, valueType:type) -> bool: def Verify(self, valueType:type) -> bool:
return self._MetaType.Verify(valueType) return self.ValueType.Verify(valueType)
@virtual
def GetValue(self, obj:Any) -> Any: def GetValue(self, obj:Any) -> Any:
if self.IsStatic: if self.IsStatic:
return getattr(self.ParentType, self.MemberName) return getattr(self.ParentType, self.MemberName)
@@ -751,7 +645,7 @@ class FieldInfo(MemberInfo):
f"{ConsoleFrontColor.RED} , parent type mismatch, expected {self.ParentType}, got {type(obj)}"\ f"{ConsoleFrontColor.RED} , parent type mismatch, expected {self.ParentType}, got {type(obj)}"\
f"{ConsoleFrontColor.RESET}") f"{ConsoleFrontColor.RESET}")
return getattr(obj, self.MemberName) return getattr(obj, self.MemberName)
@virtual
def SetValue(self, obj:Any, value:Any) -> None: def SetValue(self, obj:Any, value:Any) -> None:
if self.IsStatic: if self.IsStatic:
if self.Verify(type(value)): if self.Verify(type(value)):
@@ -934,7 +828,7 @@ class MethodInfo(MemberInfo):
method: Callable, method: Callable,
ctype: Optional[type] = None, ctype: Optional[type] = None,
module_name: Optional[str] = None module_name: Optional[str] = None
) -> Self: ) -> 'MethodInfo':
''' '''
创建MethodInfo对象 创建MethodInfo对象
name: 方法名 name: 方法名
@@ -999,7 +893,7 @@ class RefType(ValueInfo):
_FieldInfos: List[FieldInfo] = PrivateAttr() _FieldInfos: List[FieldInfo] = PrivateAttr()
_MethodInfos: List[MethodInfo] = PrivateAttr() _MethodInfos: List[MethodInfo] = PrivateAttr()
_MemberNames: List[str] = PrivateAttr() _MemberNames: List[str] = PrivateAttr()
_BaseTypes: List[Self] = PrivateAttr(default=None) _BaseTypes: List[Self] = PrivateAttr(default=[])
_initialized: bool = PrivateAttr(default=False) _initialized: bool = PrivateAttr(default=False)
_BaseMemberNamesSet: Set[str] = PrivateAttr(default_factory=set) _BaseMemberNamesSet: Set[str] = PrivateAttr(default_factory=set)
_member_cache: Dict[Tuple[str, RefTypeFlag], Optional[MemberInfo]] = PrivateAttr(default_factory=dict) _member_cache: Dict[Tuple[str, RefTypeFlag], Optional[MemberInfo]] = PrivateAttr(default_factory=dict)
@@ -1012,12 +906,12 @@ class RefType(ValueInfo):
super().__init__(dict, generic_args=[metaType.keyType, metaType.valueType]) super().__init__(dict, generic_args=[metaType.keyType, metaType.valueType])
metaType = dict metaType = dict
elif isinstance(metaType, TupleIndictaor): elif isinstance(metaType, TupleIndictaor):
super().__init__(tuple, generic_args=metaType.elementTypes) super().__init__(tuple, generic_args=list(metaType.elementTypes))
metaType = tuple metaType = tuple
elif isinstance(metaType, SetIndictaor): elif isinstance(metaType, SetIndictaor):
super().__init__(set, generic_args=[metaType.elementType]) super().__init__(set, generic_args=[metaType.elementType])
metaType = set metaType = set
elif is_generic(metaType): elif IsGeneric(metaType):
raise NotImplementedError("Generic type is not supported") raise NotImplementedError("Generic type is not supported")
else: else:
super().__init__(metaType) super().__init__(metaType)
@@ -1128,7 +1022,7 @@ class RefType(ValueInfo):
for name, annotation in annotations_dict.items(): for name, annotation in annotations_dict.items():
if name not in self._BaseMemberNamesSet and name not in field_names and not name.startswith('__'): if name not in self._BaseMemberNamesSet and name not in field_names and not name.startswith('__'):
field_info = FieldInfo( field_info = FieldInfo(
metaType=decay_type(annotation), metaType=DecayType(annotation),
name=name, name=name,
ctype=metaType, ctype=metaType,
is_static=False, is_static=False,
@@ -1220,26 +1114,6 @@ class RefType(ValueInfo):
else: else:
raise ReflectionException(f"Method {name} not found") raise ReflectionException(f"Method {name} not found")
def TryGetFieldValue[T](self, obj:object, lv:left_value_reference[T], name:str, flags:RefTypeFlag=RefTypeFlag.Default) -> bool:
try:
lv.ref_value = self.GetFieldValue(obj, name, flags)
return True
except ReflectionException:
return False
def TrySetFieldValue[T](self, obj:object, name:str, value:T, flags:RefTypeFlag=RefTypeFlag.Default) -> bool:
try:
self.SetFieldValue(obj, name, value, flags)
return True
except ReflectionException:
return False
def TryInvokeMethod[T](self, obj:object, lv:left_value_reference[T],
name:str, flags:RefTypeFlag=RefTypeFlag.Default, *args, **kwargs) -> bool:
try:
lv.ref_value = self.InvokeMethod(obj, name, flags, *args, **kwargs)
return True
except ReflectionException:
return False
def CreateInstance(self, *args, **kwargs) -> object: def CreateInstance(self, *args, **kwargs) -> object:
return self.RealType(*args, **kwargs) return self.RealType(*args, **kwargs)
@@ -1252,7 +1126,7 @@ class RefType(ValueInfo):
@override @override
def ToString(self) -> str: def ToString(self) -> str:
return f"RefType<type={self.RealType}, generic={self.GenericArgs}>" return f"RefType<type={self.RealType}, generic={self.GenericArgs}>"
def print_str(self, verbose:bool=False, flags:RefTypeFlag=RefTypeFlag.Default) -> str: def Print2Str(self, verbose:bool=False, flags:RefTypeFlag=RefTypeFlag.Default) -> str:
fields: List[str] = [] fields: List[str] = []
methods: List[str] = [] methods: List[str] = []
for field in self.GetFields(flags): for field in self.GetFields(flags):
@@ -1264,8 +1138,7 @@ class RefType(ValueInfo):
return f"RefType<type={self.RealType}{', fields=' if len(fields)!=0 else ''}{', '.join(fields)}{ConsoleFrontColor.RESET}"\ return f"RefType<type={self.RealType}{', fields=' if len(fields)!=0 else ''}{', '.join(fields)}{ConsoleFrontColor.RESET}"\
f"{', methods=' if len(methods)!=0 else ''}{', '.join(methods)}{ConsoleFrontColor.RESET}>" f"{', methods=' if len(methods)!=0 else ''}{', '.join(methods)}{ConsoleFrontColor.RESET}>"
@sealed def Print2Tree(self, indent:int=4) -> str:
def tree(self, indent:int=4) -> str:
type_set: set = set() type_set: set = set()
def dfs(currentType:RefType) -> Dict[str, Dict[str, Any]|Any]: def dfs(currentType:RefType) -> Dict[str, Dict[str, Any]|Any]:
if currentType.IsPrimitive: if currentType.IsPrimitive:
@@ -1312,7 +1185,7 @@ class RefType(ValueInfo):
return self.RealType == other.RealType return self.RealType == other.RealType
# 添加新的优化方法,避免重复检查 # 添加新的优化方法,避免重复检查
def _init_base_types_if_needed(self): def _InitBaseTypesIfNeeded(self):
"""初始化基类类型,只在需要时执行""" """初始化基类类型,只在需要时执行"""
if self._BaseTypes is None: if self._BaseTypes is None:
self._BaseTypes = [] self._BaseTypes = []
@@ -1327,7 +1200,7 @@ class RefType(ValueInfo):
@functools.lru_cache(maxsize=128) @functools.lru_cache(maxsize=128)
def GetBaseFields(self, flag:RefTypeFlag=RefTypeFlag.Default) -> List[FieldInfo]: def GetBaseFields(self, flag:RefTypeFlag=RefTypeFlag.Default) -> List[FieldInfo]:
if self._BaseTypes is None: if self._BaseTypes is None:
self._init_base_types_if_needed() self._InitBaseTypesIfNeeded()
result = [] result = []
for baseType in self._BaseTypes: for baseType in self._BaseTypes:
result.extend(baseType.GetFields(flag)) result.extend(baseType.GetFields(flag))
@@ -1336,7 +1209,7 @@ class RefType(ValueInfo):
@functools.lru_cache(maxsize=128) @functools.lru_cache(maxsize=128)
def GetAllBaseFields(self) -> List[FieldInfo]: def GetAllBaseFields(self) -> List[FieldInfo]:
if self._BaseTypes is None: if self._BaseTypes is None:
self._init_base_types_if_needed() self._InitBaseTypesIfNeeded()
result = [] result = []
for baseType in self._BaseTypes: for baseType in self._BaseTypes:
result.extend(baseType.GetAllFields()) result.extend(baseType.GetAllFields())
@@ -1346,7 +1219,7 @@ class RefType(ValueInfo):
@functools.lru_cache(maxsize=128) @functools.lru_cache(maxsize=128)
def GetBaseMethods(self, flag:RefTypeFlag=RefTypeFlag.Default) -> List[MethodInfo]: def GetBaseMethods(self, flag:RefTypeFlag=RefTypeFlag.Default) -> List[MethodInfo]:
if self._BaseTypes is None: if self._BaseTypes is None:
self._init_base_types_if_needed() self._InitBaseTypesIfNeeded()
result = [] result = []
for baseType in self._BaseTypes: for baseType in self._BaseTypes:
result.extend(baseType.GetMethods(flag)) result.extend(baseType.GetMethods(flag))
@@ -1355,7 +1228,7 @@ class RefType(ValueInfo):
@functools.lru_cache(maxsize=128) @functools.lru_cache(maxsize=128)
def GetAllBaseMethods(self) -> List[MethodInfo]: def GetAllBaseMethods(self) -> List[MethodInfo]:
if self._BaseTypes is None: if self._BaseTypes is None:
self._init_base_types_if_needed() self._InitBaseTypesIfNeeded()
result = [] result = []
for baseType in self._BaseTypes: for baseType in self._BaseTypes:
result.extend(baseType.GetAllMethods()) result.extend(baseType.GetAllMethods())
@@ -1364,7 +1237,7 @@ class RefType(ValueInfo):
@functools.lru_cache(maxsize=128) @functools.lru_cache(maxsize=128)
def GetBaseMembers(self, flag:RefTypeFlag=RefTypeFlag.Default) -> List[MemberInfo]: def GetBaseMembers(self, flag:RefTypeFlag=RefTypeFlag.Default) -> List[MemberInfo]:
if self._BaseTypes is None: if self._BaseTypes is None:
self._init_base_types_if_needed() self._InitBaseTypesIfNeeded()
result = [] result = []
for baseType in self._BaseTypes: for baseType in self._BaseTypes:
result.extend(baseType.GetMembers(flag)) result.extend(baseType.GetMembers(flag))
@@ -1373,7 +1246,7 @@ class RefType(ValueInfo):
@functools.lru_cache(maxsize=128) @functools.lru_cache(maxsize=128)
def GetAllBaseMembers(self) -> List[MemberInfo]: def GetAllBaseMembers(self) -> List[MemberInfo]:
if self._BaseTypes is None: if self._BaseTypes is None:
self._init_base_types_if_needed() self._InitBaseTypesIfNeeded()
result = [] result = []
for baseType in self._BaseTypes: for baseType in self._BaseTypes:
result.extend(baseType.GetAllMembers()) result.extend(baseType.GetAllMembers())
@@ -1434,7 +1307,7 @@ RTypen[T] 是 T 类型的 RefType
_Internal_TypeManager:Optional['TypeManager'] = None _Internal_TypeManager:Optional['TypeManager'] = None
class TypeManager(BaseModel, any_class): class TypeManager(BaseModel):
_RefTypes: Dict[type|_SpecialIndictaor, RefType] = PrivateAttr(default_factory=dict) _RefTypes: Dict[type|_SpecialIndictaor, RefType] = PrivateAttr(default_factory=dict)
_is_preheated: bool = PrivateAttr(default=False) _is_preheated: bool = PrivateAttr(default=False)
_weak_refs: Dict[int, "weakref.ref[RefType]"] = PrivateAttr(default_factory=dict) # 使用真正的弱引用 _weak_refs: Dict[int, "weakref.ref[RefType]"] = PrivateAttr(default_factory=dict) # 使用真正的弱引用
@@ -1442,7 +1315,7 @@ class TypeManager(BaseModel, any_class):
_string_to_type_cache: Dict[str, Any] = PrivateAttr(default_factory=dict) # 字符串到类型的缓存 _string_to_type_cache: Dict[str, Any] = PrivateAttr(default_factory=dict) # 字符串到类型的缓存
@classmethod @classmethod
def GetInstance(cls) -> Self: def GetInstance(cls) -> 'TypeManager':
global _Internal_TypeManager global _Internal_TypeManager
if _Internal_TypeManager is None: if _Internal_TypeManager is None:
_Internal_TypeManager = cls() _Internal_TypeManager = cls()
@@ -1457,7 +1330,7 @@ class TypeManager(BaseModel, any_class):
# 常用的基础类型列表 # 常用的基础类型列表
common_types = [ common_types = [
int, float, str, bool, list, dict, tuple, set, int, float, str, bool, list, dict, tuple, set,
object, type, None.__class__, Exception, BaseModel, any_class object, type, None.__class__, Exception, BaseModel
] ]
# 预加载的类型和它们之间常见的关系,减少后续运行时计算 # 预加载的类型和它们之间常见的关系,减少后续运行时计算
@@ -1498,12 +1371,12 @@ class TypeManager(BaseModel, any_class):
# 尝试使用to_type函数 # 尝试使用to_type函数
try: try:
return to_type(data, module_name=module_name) return ToType(data, module_name=module_name)
except Exception: except Exception:
pass pass
# 尝试使用try_to_type函数作为回退 # 尝试使用try_to_type函数作为回退
metaType = try_to_type(data, module_name=module_name) metaType = TryToType(data, module_name=module_name)
if metaType is None or isinstance(metaType, list): if metaType is None or isinstance(metaType, list):
metaType = data metaType = data
return metaType return metaType

View File

@@ -20,7 +20,8 @@ setup(
"colorama", "colorama",
"pydantic", "pydantic",
"python-docx", "python-docx",
"Pillow" "Pillow",
"pydub"
], ],
exclude_package_data={"": ["*.meta"]}, exclude_package_data={"": ["*.meta"]},
) )