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 platform
import time
import os
from colorama import Fore as ConsoleFrontColor, Back as ConsoleBackgroundColor, Style as ConsoleStyle
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
_Internal_EasySave_Debug = debug
class EasySaveSetting(BaseModel, any_class):
class EasySaveSetting(BaseModel):
key: str = Field(description="目标键", default="easy")
# 从目标文件进行序列化/反序列化
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: 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")
# 序列化/反序列化时, 如果设置了忽略字段的谓词, 则被谓词选中的字段将不会工作
# 如果设置了选择字段的谓词, 则被选中的字段才会工作
ignore_pr: Optional[Callable[[FieldInfo], bool]] = Field(description="忽略字段的谓词", default=None)
select_pr: Optional[Callable[[FieldInfo], bool]] = Field(description="选择字段的谓词", default=None)
ignorePr: 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="设置")
@sealed
def _GetFields(self, rtype:RefType) -> List[FieldInfo]:
'''
获取字段
'''
fields: List[FieldInfo] = []
if self.setting.ignore_pr is not None and self.setting.select_pr is not None:
fields = [ field for field in rtype.GetAllFields() if self.setting.select_pr(field) and not self.setting.ignore_pr(field) ]
elif self.setting.select_pr is None and self.setting.ignore_pr is 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.selectPr(field) and not self.setting.ignorePr(field) ]
elif self.setting.selectPr is None and self.setting.ignorePr is None:
fields = rtype.GetFields()
elif self.setting.ignore_pr is not None:
fields = [ field for field in rtype.GetAllFields() if not self.setting.ignore_pr(field) ]
elif self.setting.ignorePr is not None:
fields = [ field for field in rtype.GetAllFields() if not self.setting.ignorePr(field) ]
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
@sealed
def _DoJsonSerialize(self, result_file:ToolFile, rtype:RefType, rinstance:Any) -> Any:
'''
序列化: json格式
@@ -77,7 +75,7 @@ class ESWriter(BaseModel, any_class):
raise ReflectionException(f"{ConsoleFrontColor.RED}容器<{rtype.RealType}>"\
f"在序列化时遇到错误:{ConsoleFrontColor.RESET}\n{e}") from e
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__"):
custom_data, is_need_type = rtype.RealType.__easy_serialize__(rinstance)
if is_need_type:
@@ -104,85 +102,79 @@ class ESWriter(BaseModel, any_class):
return layer
layers: Dict[str, Any] = {}
if result_file.exists():
filedata = result_file.load()
if result_file.Exists():
filedata = result_file.LoadAsJson()
if isinstance(filedata, dict):
layers = filedata
layers[self.setting.key] = {
"__type": AssemblyTypen(rtype.RealType),
"value": dfs(rtype, rinstance)
}
result_file.data = layers
result_file.save_as_json()
result_file.SaveAsJson(layers)
@sealed
def _DoBinarySerialize(self, result_file:ToolFile, rinstance:Any) -> Any:
'''
序列化: 二进制格式
'''
result_file.data = rinstance
result_file.save_as_binary()
result_file.SaveAsBinary(rinstance)
@virtual
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)
elif self.setting.format == "binary":
elif self.setting.formatMode == "binary":
self._DoBinarySerialize(result_file, rinstance)
else:
raise NotImplementedError(f"不支持的格式: {self.setting.format}")
raise NotImplementedError(f"不支持的格式: {self.setting.formatMode}")
@virtual
def Write[T](self, rinstance:T) -> ToolFile:
'''
写入数据
'''
result_file: ToolFile = ToolFile(self.setting.file)
backup_file: ToolFile = None
if result_file.dirpath is not None and not ToolFile(result_file.dirpath).exists():
raise FileNotFoundError(f"文件路径不存在: {result_file.dirpath}")
if result_file.exists() and self.setting.is_backup:
if result_file.dirpath is not None:
backup_file = result_file.dirpath | (result_file.get_filename(True) + self.setting.backup_suffix)
if result_file.GetDir() is not None and not ToolFile(result_file.GetDir()).Exists():
raise FileNotFoundError(f"文件路径不存在: {result_file.GetDir()}")
if result_file.Exists() and self.setting.isBackup:
if result_file.GetDir() is not None:
backup_file = ToolFile(result_file.GetDir()) | (result_file.GetFilename(True) + self.setting.backup_suffix)
else:
backup_file = ToolFile(result_file.get_filename(True) + self.setting.backup_suffix)
result_file.copy(backup_file)
backup_file = ToolFile(result_file.GetFilename(True) + self.setting.backup_suffix)
result_file.Copy(backup_file)
try:
self.Serialize(result_file, TypeManager.GetInstance().CreateOrGetRefType(rinstance), rinstance)
except Exception:
if backup_file is not None:
result_file.remove()
backup_file.copy(result_file)
backup_file.remove()
result_file.Remove()
backup_file.Copy(result_file)
backup_file.Remove()
raise
finally:
if backup_file is not None:
backup_file.remove()
backup_file.Remove()
return result_file
class ESReader(BaseModel, any_class):
class ESReader(BaseModel):
setting: EasySaveSetting = Field(description="设置")
@sealed
def _GetFields(self, rtype:RefType) -> List[FieldInfo]:
'''
获取字段
'''
fields: List[FieldInfo] = []
if self.setting.ignore_pr is not None and self.setting.select_pr is not None:
fields = [ field for field in rtype.GetAllFields() if self.setting.select_pr(field) and not self.setting.ignore_pr(field) ]
elif self.setting.select_pr is None and self.setting.ignore_pr is 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.selectPr(field) and not self.setting.ignorePr(field) ]
elif self.setting.selectPr is None and self.setting.ignorePr is None:
fields = rtype.GetFields()
elif self.setting.ignore_pr is not None:
fields = [ field for field in rtype.GetAllFields() if not self.setting.ignore_pr(field) ]
elif self.setting.ignorePr is not None:
fields = [ field for field in rtype.GetAllFields() if not self.setting.ignorePr(field) ]
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
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时抛出
'''
# 从文件中加载JSON数据
layers: Dict[str, Any] = read_file.load_as_json()
layers: Dict[str, Any] = read_file.LoadAsJson()
if self.setting.key not in layers:
raise ValueError(f"{ConsoleFrontColor.RED}文件中不包含目标键: {ConsoleFrontColor.RESET}{self.setting.key}")
# 如果未指定类型, 则从JSON数据中获取类型信息
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"]
result_instance: Any = None
@@ -240,7 +232,7 @@ class ESReader(BaseModel, any_class):
'''
# 如果类型为None且当前层包含类型信息, 则获取类型
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:
raise ValueError(f"{ConsoleFrontColor.RED}当前层不包含类型信息: {ConsoleFrontColor.RESET}{LimitStringLength(str(layer), 100)}")
if GetInternalEasySaveDebug():
@@ -280,7 +272,7 @@ class ESReader(BaseModel, any_class):
except Exception as e:
raise ReflectionException(f"容器<{LimitStringLength(str(layer), 100)}>在反序列化时遇到错误:\n{e}") from e
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__"):
return rtype.RealType.__easy_deserialize__(layer)
@@ -288,7 +280,7 @@ class ESReader(BaseModel, any_class):
rinstance = rtype.CreateInstance()
if GetInternalEasySaveDebug():
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)
for field in fields:
if field.FieldName not in layer:
@@ -336,50 +328,47 @@ class ESReader(BaseModel, any_class):
result_instance = dfs(rtype, layers)
return result_instance
@sealed
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:
'''
反序列化
'''
if self.setting.format == "json":
if self.setting.formatMode == "json":
return self._DoJsonDeserialize(read_file, rtype)
elif self.setting.format == "binary":
elif self.setting.formatMode == "binary":
return self._DoBinaryDeserialize(read_file, rtype)
else:
raise NotImplementedError(f"不支持的格式: {self.setting.format}")
raise NotImplementedError(f"不支持的格式: {self.setting.formatMode}")
@virtual
def Read[T](self, rtype:Optional[RTypen[T]]=None) -> T:
'''
读取数据
'''
read_file: ToolFile = ToolFile(self.setting.file)
if not read_file.exists():
if not read_file.Exists():
raise FileNotFoundError(f"文件不存在: {read_file}")
if read_file.is_dir():
if read_file.IsDir():
raise IsADirectoryError(f"文件是目录: {read_file}")
return self.Deserialize(read_file, rtype)
class EasySave(any_class):
class EasySave:
@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
@staticmethod
def Read[T](
rtype: Typen[T],
file: tool_file_or_str = None,
file: Optional[ToolFile|str] = None,
*,
setting: Optional[EasySaveSetting] = None
) -> T:
@@ -388,7 +377,7 @@ class EasySave(any_class):
@staticmethod
def Read[T](
rtype: RTypen[T],
file: tool_file_or_str = None,
file: Optional[ToolFile|str] = None,
*,
setting: Optional[EasySaveSetting] = None
) -> T:
@@ -396,7 +385,7 @@ class EasySave(any_class):
@staticmethod
def Read[T](
rtype: RTypen[T]|type,
file: tool_file_or_str = None,
file: Optional[ToolFile|str] = None,
*,
setting: Optional[EasySaveSetting] = None
) -> T:
@@ -405,4 +394,4 @@ class EasySave(any_class):
'''
if isinstance(rtype, type):
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
try:
from pydub import AudioSegment
except ImportError:
ImportingThrow("File", ["pydub"])
except ImportError as e:
ImportingThrow(e, "File", ["pydub"])
try:
from PIL import Image, ImageFile
except ImportError:
ImportingThrow("File", ["Pillow"])
except ImportError as e:
ImportingThrow(e, "File", ["Pillow"])
try:
from docx import Document
from docx.document import Document as DocumentObject
except ImportError:
ImportingThrow("File", ["python-docx"])
except ImportError as e:
ImportingThrow(e, "File", ["python-docx"])
from .String import Bytes2String
def get_extension_name(file:str):
def GetExtensionName(file:str):
return os.path.splitext(file)[1][1:]
def get_base_filename(file:str):
def GetBaseFilename(file:str):
return os.path.basename(file)
dir_name_type = str
@@ -67,15 +67,8 @@ class PermissionError(FileOperationError):
"""权限操作异常"""
pass
try:
from pydantic import BaseModel, GetCoreSchemaHandler
from pydantic_core import core_schema
except ImportError:
class BaseModel:
def __init__(self,*args,**kwargs)->None:
pass
type GetCoreSchemaHandler = Any
type core_schema = Any
from pydantic import BaseModel, GetCoreSchemaHandler
from pydantic_core import core_schema
class ToolFile(BaseModel):
OriginFullPath:str
@@ -101,42 +94,20 @@ class ToolFile(BaseModel):
):
self.OriginFullPath = filePath if isinstance(filePath, str) else filePath.OriginFullPath
def __del__(self):
self.close()
pass
def __str__(self):
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):
if self.is_open():
return self
if self.exists() and self.is_file():
self.load()
return self
@override
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
return super().__exit__(exc_type, exc_val, exc_tb)
return True
def __or__(self, other):
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:
return ToolFile(os.path.join(self.GetFullPath(), UnWrapper(other)))
return ToolFile(os.path.join(self.GetFullPath(), str(other)))
def __idiv__(self, other):
self.close()
temp = self.__or__(other)
self.OriginFullPath = temp.GetFullPath()
@@ -168,213 +139,131 @@ class ToolFile(BaseModel):
return os.path.normpath(self_path) == os.path.normpath(other_path)
def to_path(self):
def ToPath(self):
return Path(self.OriginFullPath)
def __Path__(self):
return Path(self.OriginFullPath)
def write(self, data:Union[str, bytes]):
self.__file.write(data)
def create(self):
if self.exists() == False:
if self.is_dir():
if os.path.exists(self.get_dir()):
def Create(self):
if self.Exists() == False:
if self.IsDir():
if os.path.exists(self.GetDir()):
os.makedirs(self.OriginFullPath)
else:
raise FileNotFoundError(f"{self.OriginFullPath} cannt create, because its parent path is not exist")
else:
self.open('w')
self.close()
with open(self.OriginFullPath, 'w') as f:
f.write('')
return self
def exists(self):
def Exists(self):
return os.path.exists(self.OriginFullPath)
def remove(self):
self.close()
if self.exists():
if self.is_dir():
def Remove(self):
if self.Exists():
if self.IsDir():
shutil.rmtree(self.OriginFullPath)
else:
os.remove(self.OriginFullPath)
return self
def copy(self, to_path:Optional[Union[Self, str]]=None):
if to_path is None:
def Copy(self, targetPath:Optional[Union[Self, str]]=None):
if targetPath is None:
return ToolFile(self.OriginFullPath)
if self.exists() is False:
if self.Exists() is False:
raise FileNotFoundError("file not found")
self.close()
target_file = ToolFile(UnWrapper(to_path))
if target_file.is_dir():
target_file = ToolFile(str(targetPath))
if target_file.IsDir():
target_file = target_file|self.GetFilename()
shutil.copy(self.OriginFullPath, UnWrapper(target_file))
shutil.copy(self.OriginFullPath, str(target_file))
return target_file
def move(self, to_path:Union[Self, str]):
if self.exists() is False:
def Move(self, targetPath:Union[Self, str]):
if self.Exists() is False:
raise FileNotFoundError("file not found")
self.close()
target_file = ToolFile(UnWrapper(to_path))
if target_file.is_dir():
target_file = ToolFile(str(targetPath))
if target_file.IsDir():
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
return self
def rename(self, newpath:Union[Self, str]):
if self.exists() is False:
def Rename(self, newpath:Union[Self, str]):
if self.Exists() is False:
raise FileNotFoundError("file not found")
self.close()
newpath:str = UnWrapper(newpath)
newpath = str(newpath)
if '\\' in newpath or '/' in newpath:
newpath = get_base_filename(newpath)
new_current_path = os.path.join(self.get_dir(), newpath)
newpath = GetBaseFilename(newpath)
new_current_path = os.path.join(self.GetDir(), newpath)
os.rename(self.OriginFullPath, new_current_path)
self.OriginFullPath = new_current_path
return self
def refresh(self):
self.load()
return self
def open(self, mode='r', is_refresh=False, encoding:str='utf-8', *args, **kwargs):
self.close()
if 'b' in mode:
self.__file = open(self.OriginFullPath, mode, *args, **kwargs)
def LoadAsJson(self) -> Any:
if self.Exists() is False or 'w' in self.OriginFullPath:
with open(self.OriginFullPath, 'r') as f:
json_data = json.load(f)
return json_data
else:
self.__file = open(self.OriginFullPath, mode, encoding=encoding, *args, **kwargs)
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
raise FileNotFoundError("file not found")
def load(self):
if self.OriginFullPath is None:
if os.path.exists(temp_tool_file_path_name):
self.data = pickle.load(open(temp_tool_file_path_name, 'rb'))
return self.data
else:
raise FileNotFoundError(f"{self.OriginFullPath} not found, but this ToolFile's target is None")
elif self.is_dir():
self.__file = open(os.path.join(self.GetFullPath(), temp_tool_file_path_name), 'rb')
self.data = pickle.load(self.__file)
return self.data
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()
def LoadAsCsv(self) -> pd.DataFrame:
if self.Exists() is False or 'w' in self.OriginFullPath:
with open(self.OriginFullPath, 'r') as f:
return pd.read_csv(f)
else:
self.load_as_unknown(suffix)
return self.data
def load_as_json(self) -> pd.DataFrame:
if self.is_open() is False or 'w' in self.__file.mode:
self.open('r')
json_data = json.load(self.__file)
#try:
# from pydantic import BaseModel
# if "__type" in json_data and "pydantic.BaseModel" in json_data["__type"]:
# del json_data["__type"]
# json_data = BaseModel.model_validate(json_data)
#except:
# pass
self.data = json_data
return self.data
def load_as_csv(self) -> pd.DataFrame:
if self.is_open() is False or 'w' in self.__file.mode:
self.open('r')
self.data = pd.read_csv(self.__file)
return self.data
def load_as_xml(self) -> pd.DataFrame:
if self.is_open() is False or 'w' in self.__file.mode:
self.open('r')
self.data = pd.read_xml(self.__file)
return self.data
def load_as_dataframe(self) -> pd.DataFrame:
if self.is_open() is False or 'w' in self.__file.mode:
self.open('r')
self.data = pd.read_csv(self.__file)
return self.data
def load_as_excel(self) -> pd.DataFrame:
if self.is_open() is False or 'w' in self.__file.mode:
self.open('r')
self.data = pd.read_excel(self.__file)
return self.data
def load_as_binary(self) -> bytes:
if self.is_open() is False or 'w' in self.__file.mode:
self.open('rb')
self.data = self.__file.read()
return self.data
def load_as_text(self) -> str:
if self.is_open() is False or 'w' in self.__file.mode:
self.open('r')
self.data = list_byte_to_string(self.__file.readlines())
return self.data
def load_as_wav(self):
self.data = AudioSegment.from_wav(self.OriginFullPath)
return self.data
def load_as_audio(self):
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())
raise FileNotFoundError("file not found")
def LoadAsXml(self) -> pd.DataFrame:
if self.Exists() is False or 'w' in self.OriginFullPath:
with open(self.OriginFullPath, 'r') as f:
return pd.read_xml(f)
else:
raise FileNotFoundError("file not found")
def LoadAsDataframe(self) -> pd.DataFrame:
if self.Exists() is False or 'w' in self.OriginFullPath:
with open(self.OriginFullPath, 'r') as f:
return pd.read_csv(f)
else:
raise FileNotFoundError("file not found")
def LoadAsExcel(self) -> pd.DataFrame:
if self.Exists() is False or 'w' in self.OriginFullPath:
with open(self.OriginFullPath, 'r') as f:
return pd.read_excel(f)
else:
raise FileNotFoundError("file not found")
def LoadAsBinary(self) -> bytes:
if self.Exists() is False or 'w' in self.OriginFullPath:
with open(self.OriginFullPath, 'rb') as f:
return f.read()
else:
raise FileNotFoundError("file not found")
def LoadAsText(self) -> str:
if self.Exists() is False or 'w' in self.OriginFullPath:
with open(self.OriginFullPath, 'r') as f:
return f.read()
else:
raise FileNotFoundError("file not found")
def LoadAsWav(self):
if self.Exists() is False or 'w' in self.OriginFullPath:
return AudioSegment.from_wav(self.OriginFullPath)
else:
raise FileNotFoundError("file not found")
def LoadAsAudio(self):
if self.Exists() is False or 'w' in self.OriginFullPath:
return AudioSegment.from_file(self.OriginFullPath)
else:
raise FileNotFoundError("file not found")
def LoadAsImage(self) -> ImageFile.ImageFile:
if self.Exists() is False or 'w' in self.OriginFullPath:
return Image.open(self.OriginFullPath)
else:
raise FileNotFoundError("file not found")
def LoadAsDocx(self) -> DocumentObject:
if self.Exists() is False or 'w' in self.OriginFullPath:
return Document(self.OriginFullPath)
else:
raise FileNotFoundError("file not found")
def LoadAsUnknown(self, suffix:str) -> Any:
return self.LoadAsText()
def LoadAsModel(self, model:type[BaseModel]) -> BaseModel:
return model.model_validate(self.LoadAsJson())
def save(self, path:Optional[str]=None):
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
def SaveAsJson(self, json_data):
try:
from pydantic import BaseModel
if isinstance(json_data, BaseModel):
@@ -382,89 +271,51 @@ class ToolFile(BaseModel):
json_data["__type"] = f"{self.data.__class__.__name__}, pydantic.BaseModel"
except:
pass
path = path if path is not None else self.OriginFullPath
self.close()
with open(path, 'w', encoding='utf-8') as f:
with open(self.OriginFullPath, 'w', encoding='utf-8') as f:
json.dump(json_data, f, indent=4)
return self
def save_as_csv(self, path:Optional[str]=None):
path = path if path is not None else self.OriginFullPath
self.data.to_csv(path)
def SaveAsCsv(self, csv_data:pd.DataFrame):
csv_data.to_csv(self.OriginFullPath)
return self
def save_as_xml(self, path:Optional[str]=None):
path = path if path is not None else self.OriginFullPath
self.data.to_xml(path)
def SaveAsXml(self, xml_data:pd.DataFrame):
xml_data.to_xml(self.OriginFullPath)
return self
def save_as_dataframe(self, path:Optional[str]=None):
path = path if path is not None else self.OriginFullPath
self.data.to_csv(path)
def SaveAsDataframe(self, dataframe_data:pd.DataFrame):
dataframe_data.to_csv(self.OriginFullPath)
return self
def save_as_excel(self, path:Optional[str]=None):
path = path if path is not None else self.OriginFullPath
self.data.to_excel(path, index=False)
def SaveAsExcel(self, excel_data:pd.DataFrame):
excel_data.to_excel(self.OriginFullPath, index=False)
return self
def save_as_binary(self, path:Optional[str]=None):
if path is not None:
with open(path, 'wb') as f:
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()
def SaveAsBinary(self, binary_data:bytes):
with open(self.OriginFullPath, 'wb') as f:
f.write(binary_data)
return self
def save_as_text(self, path:Optional[str]=None):
if path is not None:
with open(path, 'w') as f:
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()
def SaveAsText(self, text_data:str):
with open(self.OriginFullPath, 'w') as f:
f.writelines(text_data)
return self
def save_as_audio(self, path:Optional[str]=None):
path = path if path is not None else self.OriginFullPath
self.data.export(path, format=self.get_extension(path))
def SaveAsAudio(self, audio_data:AudioSegment):
audio_data.export(self.OriginFullPath, format=self.get_extension(self.OriginFullPath))
return self
def save_as_image(self, path:Optional[str]=None):
path = path if path is not None else self.OriginFullPath
self.data.save(path)
def SaveAsImage(self, image_data:ImageFile.ImageFile):
image_data.save(self.OriginFullPath)
return self
def save_as_docx(self, path:Optional[str]=None):
if self.data is str:
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)
def SaveAsDocx(self, docx_data:DocumentObject):
docx_data.save(self.OriginFullPath)
return self
def save_as_unknown(self, path:Optional[str]=None):
self.save_as_text(path)
def save_as_model(self, model:type[BaseModel], path:Optional[str]=None):
self.save_as_json(path)
def SaveAsUnknown(self, unknown_data:Any):
self.SaveAsBinary(unknown_data)
def SaveAsModel(self, model:type[BaseModel]):
self.SaveAsJson(model)
def get_size(self) -> int:
def GetSize(self) -> int:
'''
return:
return size of directory
'''
return os.path.getsize(self.OriginFullPath)
def get_data_type(self) -> type:
return type(self.data)
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 GetExtension(self):
return GetExtensionName(self.OriginFullPath)
def GetFullPath(self) -> str:
return self.OriginFullPath
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 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] == '\\':
return get_base_filename(self.OriginFullPath[:-1])
return GetBaseFilename(self.OriginFullPath[:-1])
else:
return get_base_filename(self.OriginFullPath)
def get_dir(self):
return GetBaseFilename(self.OriginFullPath)
def GetDir(self):
return os.path.dirname(self.OriginFullPath)
def get_dir_tool_file(self):
return ToolFile(self.get_dir())
def get_current_dir_name(self):
def GetDirToolFile(self):
return ToolFile(self.GetDir())
def GetCurrentDirName(self):
return os.path.dirname(self.OriginFullPath)
def is_dir(self):
def IsDir(self):
if self.OriginFullPath[-1] == '\\' or self.GetFullPath()[-1] == '/':
return True
else:
return os.path.isdir(self.OriginFullPath)
def is_file(self):
def IsFile(self):
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)
if dir_path == '':
return self
if not os.path.exists(dir_path):
os.makedirs(dir_path)
return self
def dir_iter(self):
def DirIter(self):
return os.listdir(self.OriginFullPath)
def dir_tool_file_iter(self):
def DirToolFileIter(self):
result = [self]
result.clear()
for file in os.listdir(self.OriginFullPath):
result.append(self|file)
return result
def back_to_parent_dir(self):
self.close()
self.OriginFullPath = self.get_dir()
def BackToParentDir(self):
self.OriginFullPath = self.GetDir()
return self
def get_parent_dir(self):
return ToolFile(self.get_dir())
def dir_count(self, ignore_folder:bool = True):
iter = self.dir_iter()
def GetParentDir(self):
return ToolFile(self.GetDir())
def DirCount(self, ignore_folder:bool = True):
iter = self.DirIter()
result = 0
for content in iter:
if ignore_folder and os.path.isdir(os.path.join(self.OriginFullPath, content)):
continue
result += 1
return result
def dir_clear(self):
for file in self.dir_tool_file_iter():
file.remove()
def DirClear(self):
for file in self.DirToolFileIter():
file.Remove()
return self
def first_file_with_extension(self, extension:str):
target_dir = self if self.is_dir() else ToolFile(self.get_dir())
for file in target_dir.dir_tool_file_iter():
if file.is_dir() is False and file.get_extension() == extension:
def FirstFileWithExtension(self, extension:str):
target_dir = self if self.IsDir() else ToolFile(self.GetDir())
for file in target_dir.DirToolFileIter():
if file.IsDir() is False and file.GetExtension() == extension:
return file
return None
def first_file(self, pr:Callable[[str], bool]):
target_dir = self if self.is_dir() else ToolFile(self.get_dir())
for file in target_dir.dir_tool_file_iter():
def FirstFile(self, pr:Callable[[str], bool]):
target_dir = self if self.IsDir() else ToolFile(self.GetDir())
for file in target_dir.DirToolFileIter():
if pr(file.GetFilename()):
return file
return None
def find_file_with_extension(self, extension:str):
target_dir = self if self.is_dir() else ToolFile(self.get_dir())
def FindFileWithExtension(self, extension:str):
target_dir = self if self.IsDir() else ToolFile(self.GetDir())
result:List[ToolFile] = []
for file in target_dir.dir_tool_file_iter():
if file.is_dir() is False and file.get_extension() == extension:
for file in target_dir.DirToolFileIter():
if file.IsDir() is False and file.GetExtension() == extension:
result.append(file)
return result
def find_file(self, pr:Callable[[str], bool]):
target_dir = self if self.is_dir() else ToolFile(self.get_dir())
def FindFile(self, pr:Callable[[str], bool]):
target_dir = self if self.IsDir() else ToolFile(self.GetDir())
result:List[ToolFile] = []
for file in target_dir.dir_tool_file_iter():
for file in target_dir.DirToolFileIter():
if pr(file.GetFilename()):
result.append(file)
return result
def dir_walk(
def DirWalk(
self,
top,
topdown: bool = True,
@@ -565,77 +411,28 @@ class ToolFile(BaseModel):
) -> 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)
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):
return self.exists()
return self.Exists()
def __bool__(self):
return self.exists()
return self.Exists()
def must_exists_path(self):
self.close()
self.try_create_parent_path()
self.create()
def MustExistsPath(self):
self.TryCreateParentPath()
self.Create()
return self
def make_file_inside(self, data:Self, is_delete_source = False):
if self.is_dir() is False:
def MakeFileInside(self, data:Self, is_delete_source = False):
if self.IsDir() is False:
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:
data.move(result)
data.Move(result)
else:
data.copy(result)
data.Copy(result)
return self
@property
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:
def Compress(self, output_path: Optional[str] = None, format: str = 'zip') -> 'ToolFile':
"""
压缩文件或目录
Args:
@@ -644,7 +441,7 @@ class ToolFile(BaseModel):
Returns:
压缩后的文件对象
"""
if not self.exists():
if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
if output_path is None:
@@ -653,7 +450,7 @@ class ToolFile(BaseModel):
try:
if format == 'zip':
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 file in files:
file_path = os.path.join(root, file)
@@ -663,7 +460,7 @@ class ToolFile(BaseModel):
zipf.write(self.GetFullPath(), self.GetFilename())
elif format == 'tar':
with tarfile.open(output_path, 'w') as tarf:
if self.is_dir():
if self.IsDir():
tarf.add(self.GetFullPath(), arcname=self.GetFilename())
else:
tarf.add(self.GetFullPath(), arcname=self.GetFilename())
@@ -674,7 +471,7 @@ class ToolFile(BaseModel):
except Exception as 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:
@@ -682,27 +479,27 @@ class ToolFile(BaseModel):
Returns:
解压后的目录对象
"""
if not self.exists():
if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
if output_path is None:
output_path = self.GetFullPath() + '_extracted'
try:
if self.get_extension() == 'zip':
if self.GetExtension() == 'zip':
with zipfile.ZipFile(self.GetFullPath(), 'r') as zipf:
zipf.extractall(output_path)
elif self.get_extension() == 'tar':
elif self.GetExtension() == 'tar':
with tarfile.open(self.GetFullPath(), 'r') as tarf:
tarf.extractall(output_path)
else:
raise CompressionError(f"Unsupported archive format: {self.get_extension()}")
raise CompressionError(f"Unsupported archive format: {self.GetExtension()}")
return ToolFile(output_path)
except Exception as 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:
@@ -714,7 +511,7 @@ class ToolFile(BaseModel):
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
if not self.exists():
if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try:
@@ -759,7 +556,7 @@ class ToolFile(BaseModel):
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
if not self.exists():
if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try:
@@ -804,7 +601,7 @@ class ToolFile(BaseModel):
Returns:
文件的哈希值(十六进制字符串)
"""
if not self.exists():
if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try:
@@ -834,7 +631,7 @@ class ToolFile(BaseModel):
Returns:
是否匹配
"""
if not self.exists():
if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try:
@@ -852,7 +649,7 @@ class ToolFile(BaseModel):
Returns:
哈希值文件对象
"""
if not self.exists():
if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try:
@@ -891,7 +688,7 @@ class ToolFile(BaseModel):
"""
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
if not self.exists():
if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try:
@@ -972,15 +769,15 @@ class ToolFile(BaseModel):
Returns:
备份文件对象
"""
if not self.exists():
if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try:
# 生成备份目录
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.must_exists_path()
backup_dir.MustExistsPath()
# 生成备份文件名
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
@@ -990,7 +787,7 @@ class ToolFile(BaseModel):
if backup_format == 'zip':
backup_path = backup_dir | f"{backup_name}.zip"
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 file in files:
file_path = os.path.join(root, file)
@@ -1001,7 +798,7 @@ class ToolFile(BaseModel):
elif backup_format == 'tar':
backup_path = backup_dir | f"{backup_name}.tar"
with tarfile.open(backup_path.GetFullPath(), 'w') as tarf:
if self.is_dir():
if self.IsDir():
tarf.add(self.GetFullPath(), arcname=self.GetFilename())
else:
tarf.add(self.GetFullPath(), arcname=self.GetFilename())
@@ -1014,7 +811,7 @@ class ToolFile(BaseModel):
'original_path': self.GetFullPath(),
'backup_time': timestamp,
'file_size': self.get_size(),
'is_directory': self.is_dir(),
'is_directory': self.IsDir(),
'hash': self.calculate_hash()
}
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.sort(key=lambda f: f.GetFilename(), reverse=True)
for old_backup in backups[max_backups:]:
old_backup.remove()
old_backup.Remove()
return backup_path
@@ -1051,7 +848,7 @@ class ToolFile(BaseModel):
if not isinstance(backup_file, ToolFile):
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()}")
try:
@@ -1091,12 +888,12 @@ class ToolFile(BaseModel):
Returns:
备份文件列表
"""
if not self.exists():
if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try:
backup_dir:Self = ToolFile(os.path.join(self.get_dir(), '.backup'))
if not backup_dir.exists():
backup_dir:Self = ToolFile(os.path.join(self.GetDir(), '.backup'))
if not backup_dir.Exists():
return []
backups = backup_dir.find_file(lambda f: ToolFile(f).GetFilename().startswith(self.GetFilename() + '_'))
@@ -1116,7 +913,7 @@ class ToolFile(BaseModel):
- execute: 是否可执行
- hidden: 是否隐藏
"""
if not self.exists():
if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try:
@@ -1149,7 +946,7 @@ class ToolFile(BaseModel):
Returns:
文件对象本身
"""
if not self.exists():
if not self.Exists():
raise FileNotFoundError(f"File not found: {self.GetFullPath()}")
try:
@@ -1189,13 +986,13 @@ class ToolFile(BaseModel):
else: # Unix/Linux/Mac
if hidden:
if not self.GetFilename().startswith('.'):
self.rename('.' + self.GetFilename())
self.Rename('.' + self.GetFilename())
else:
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 file in files:
file_path = os.path.join(root, file)
@@ -1252,12 +1049,6 @@ class ToolFile(BaseModel):
"""
return self.get_permissions()['hidden']
def WrapperFile(file) -> ToolFile:
if isinstance(file, ToolFile):
return file
else:
return ToolFile(UnWrapper(file))
def split_elements(
file: Union[ToolFile, str],
*,
@@ -1268,8 +1059,6 @@ def split_elements(
output_must_exist: bool = True,
output_callback: Optional[Callable[[ToolFile], None]] = None
) -> List[List[ToolFile]]:
if is_loss_tool_file(file):
return []
result: List[List[ToolFile]] = tool_split_elements(WrapperFile(file).dir_tool_file_iter(),
ratios=ratios,
pr=pr,
@@ -1278,12 +1067,12 @@ def split_elements(
return result
for i in range(min(len(output_dirs), len(result))):
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")
if output_must_exist:
output_dir.must_exists_as_new()
for file in result[i]:
current = output_dirs[i].make_file_inside(file)
current = output_dirs[i].MakeFileInside(file)
if output_callback:
output_callback(current)

View File

@@ -39,7 +39,7 @@ class ReflectionException(Exception):
self.message = f"{ConsoleFrontColor.RED}{message}{ConsoleFrontColor.RESET}"
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:
_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)
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
# 获取泛型参数
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) # 获取原始类型
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 origin, args
def is_generic(type_hint: type | Any) -> bool:
def IsGeneric(type_hint: type | Any) -> bool:
return "__origin__" in dir(type_hint)
class _SpecialIndictaor:
@@ -134,9 +134,6 @@ class ListIndictaor(_SpecialIndictaor):
return f"ListIndictaor<elementType={self.elementType}>"
def __str__(self) -> str:
return self.__repr__()
@override
def ToString(self) -> str:
return self.__repr__()
def __hash__(self) -> int:
return hash(List[self.elementType])
@@ -152,9 +149,6 @@ class DictIndictaor(_SpecialIndictaor):
return f"DictIndictaor<keyType={self.keyType}, valueType={self.valueType}>"
def __str__(self) -> str:
return self.__repr__()
@override
def ToString(self) -> str:
return self.__repr__()
def __hash__(self) -> int:
return hash(Dict[self.keyType, self.valueType])
@@ -168,9 +162,6 @@ class TupleIndictaor(_SpecialIndictaor):
return f"TupleIndictaor<{', '.join(map(str, self.elementTypes))}>"
def __str__(self) -> str:
return self.__repr__()
@override
def ToString(self) -> str:
return self.__repr__()
def __hash__(self) -> int:
return hash(Tuple[self.elementTypes])
@@ -184,9 +175,6 @@ class SetIndictaor(_SpecialIndictaor):
return f"SetIndictaor<elementType={self.elementType}>"
def __str__(self) -> str:
return self.__repr__()
@override
def ToString(self) -> str:
return self.__repr__()
def __hash__(self) -> int:
return hash(Set[self.elementType])
@@ -205,7 +193,7 @@ def memoize(func):
# 优化to_type函数
@memoize
def to_type(
def ToType(
typen: type|Any|str,
*,
module_name: str|None=None
@@ -261,9 +249,9 @@ def to_type(
for module in sys.modules.values():
if type_final in module.__dict__:
return module.__dict__[type_final]
return get_type_from_string(typen)
elif is_union(typen):
uTypes = get_union_types(typen)
return String2Type(typen)
elif IsUnion(typen):
uTypes = GetUnionTypes(typen)
uTypes = [uType for uType in uTypes if uType is not type(None)]
if len(uTypes) == 1:
return uTypes[0]
@@ -286,16 +274,16 @@ def to_type(
else:
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:
return to_type(typen, module_name=module_name)
return ToType(typen, module_name=module_name)
except Exception:
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
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__]
class TypeVarIndictaor:
@@ -306,7 +294,7 @@ class AnyVarIndicator:
# 优化decay_type函数
@memoize
def decay_type(
def DecayType(
type_hint: type|Any,
*,
module_name: str|None=None
@@ -317,29 +305,29 @@ def decay_type(
if GetInternalReflectionDebug():
print_colorful(ConsoleFrontColor.YELLOW, f"Decay: {type_hint}")
result:type|List[type] = None
result: type|List[type] = None
# 处理字符串类型
if isinstance(type_hint, str):
try:
result = to_type(type_hint, module_name=module_name)
result = ToType(type_hint, module_name=module_name)
except TypeError:
result = Any
# 处理forward reference
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类型
elif type_hint is type:
result = type_hint
# 处理union类型
elif is_union(type_hint):
result = get_union_types(type_hint)
elif IsUnion(type_hint):
result = GetUnionTypes(type_hint)
# 处理TypeVar
elif isinstance(type_hint, TypeVar):
result = TypeVarIndictaor
# 处理泛型类型
elif is_generic(type_hint):
elif IsGeneric(type_hint):
result = get_origin(type_hint)
else:
raise ReflectionException(f"Invalid type: {type_hint}<{type_hint.__class__}>")
@@ -348,7 +336,7 @@ def decay_type(
print_colorful(ConsoleFrontColor.YELLOW, f"Result: {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 True
class light_reflection(any_class):
def __init__(self, obj:object, type_str:str=None, *args, **kwargs):
if obj is not None:
self.obj = obj
elif type_str is not None:
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 BaseInfo(BaseModel):
def SymbolName(self) -> str:
return "BaseInfo"
def ToString(self) -> str:
return self.SymbolName()
class MemberInfo(BaseInfo):
_MemberName: str = PrivateAttr(default="")
@@ -479,7 +374,7 @@ class MemberInfo(BaseInfo):
def MemberName(self) -> str:
return self._MemberName
@property
def ParentType(self) -> type:
def ParentType(self) -> Optional[type]:
return self._ParentType
@property
def IsStatic(self) -> bool:
@@ -516,9 +411,9 @@ class ValueInfo(BaseInfo):
@property
def IsUnion(self) -> bool:
return is_union(self._RealType)
return IsUnion(self._RealType)
@property
def RealType(self):
def RealType(self) -> type|Any:
return self._RealType
@property
def IsCollection(self) -> bool:
@@ -609,7 +504,7 @@ class ValueInfo(BaseInfo):
if valueType is type(None):
return True
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:
return True
elif self.RealType is type(None):
@@ -623,7 +518,7 @@ class ValueInfo(BaseInfo):
def DecayToList(self) -> List[Self]:
result:List[Self] = []
if self.IsUnion:
for uType in get_union_types(self.RealType):
for uType in GetUnionTypes(self.RealType):
result.extend(ValueInfo(uType).DecayToList())
else:
result.append(self)
@@ -649,7 +544,7 @@ class ValueInfo(BaseInfo):
module_name: Optional[str] = None,
SelfType: type|Any|None = None,
**kwargs
) -> Self:
) -> 'ValueInfo':
if GetInternalReflectionDebug():
print_colorful(ConsoleFrontColor.BLUE, f"Current ValueInfo.Create Frame: "\
f"metaType={metaType}, SelfType={SelfType}")
@@ -665,17 +560,17 @@ class ValueInfo(BaseInfo):
else:
return ValueInfo(metaType, **kwargs)
elif isinstance(metaType, str):
type_ = try_to_type(metaType, module_name=module_name)
type_ = TryToType(metaType, module_name=module_name)
if type_ is None:
return ValueInfo(metaType, **kwargs)
else:
return ValueInfo(type_, **kwargs)
elif metaType is Self:
elif isinstance(metaType, Self)#metaType is Self:
if SelfType is None:
raise ReflectionException("SelfType is required when metaType is <Self>")
return ValueInfo.Create(SelfType, **kwargs)
elif isinstance(metaType, TypeVar):
gargs = get_generic_args(metaType)
gargs = GetGenericArgs(metaType)
if len(gargs) == 1:
return ValueInfo(gargs[0], **kwargs)
else:
@@ -687,7 +582,7 @@ class ValueInfo(BaseInfo):
elif oType is dict:
return ValueInfo(dict, [get_args(metaType)[0], get_args(metaType)[1]])
elif oType is tuple:
return ValueInfo(tuple, to_list(get_args(metaType)))
return ValueInfo(tuple, list(get_args(metaType)))
elif oType is set:
return ValueInfo(set, [get_args(metaType)[0]])
return ValueInfo(metaType, **kwargs)
@@ -721,7 +616,7 @@ class FieldInfo(MemberInfo):
@property
def IsUnion(self) -> bool:
return self._MetaType.IsUnion
return self.ValueType.IsUnion
@property
def FieldName(self) -> str:
'''
@@ -729,19 +624,18 @@ class FieldInfo(MemberInfo):
'''
return self.MemberName
@property
def ValueType(self):
def ValueType(self) -> ValueInfo:
return self._MetaType
@property
def FieldType(self):
'''
字段类型
'''
return self._MetaType.RealType
return self.ValueType.RealType
def Verify(self, valueType:type) -> bool:
return self._MetaType.Verify(valueType)
return self.ValueType.Verify(valueType)
@virtual
def GetValue(self, obj:Any) -> Any:
if self.IsStatic:
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.RESET}")
return getattr(obj, self.MemberName)
@virtual
def SetValue(self, obj:Any, value:Any) -> None:
if self.IsStatic:
if self.Verify(type(value)):
@@ -934,7 +828,7 @@ class MethodInfo(MemberInfo):
method: Callable,
ctype: Optional[type] = None,
module_name: Optional[str] = None
) -> Self:
) -> 'MethodInfo':
'''
创建MethodInfo对象
name: 方法名
@@ -999,7 +893,7 @@ class RefType(ValueInfo):
_FieldInfos: List[FieldInfo] = PrivateAttr()
_MethodInfos: List[MethodInfo] = PrivateAttr()
_MemberNames: List[str] = PrivateAttr()
_BaseTypes: List[Self] = PrivateAttr(default=None)
_BaseTypes: List[Self] = PrivateAttr(default=[])
_initialized: bool = PrivateAttr(default=False)
_BaseMemberNamesSet: Set[str] = PrivateAttr(default_factory=set)
_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])
metaType = dict
elif isinstance(metaType, TupleIndictaor):
super().__init__(tuple, generic_args=metaType.elementTypes)
super().__init__(tuple, generic_args=list(metaType.elementTypes))
metaType = tuple
elif isinstance(metaType, SetIndictaor):
super().__init__(set, generic_args=[metaType.elementType])
metaType = set
elif is_generic(metaType):
elif IsGeneric(metaType):
raise NotImplementedError("Generic type is not supported")
else:
super().__init__(metaType)
@@ -1128,7 +1022,7 @@ class RefType(ValueInfo):
for name, annotation in annotations_dict.items():
if name not in self._BaseMemberNamesSet and name not in field_names and not name.startswith('__'):
field_info = FieldInfo(
metaType=decay_type(annotation),
metaType=DecayType(annotation),
name=name,
ctype=metaType,
is_static=False,
@@ -1220,26 +1114,6 @@ class RefType(ValueInfo):
else:
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:
return self.RealType(*args, **kwargs)
@@ -1252,7 +1126,7 @@ class RefType(ValueInfo):
@override
def ToString(self) -> str:
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] = []
methods: List[str] = []
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}"\
f"{', methods=' if len(methods)!=0 else ''}{', '.join(methods)}{ConsoleFrontColor.RESET}>"
@sealed
def tree(self, indent:int=4) -> str:
def Print2Tree(self, indent:int=4) -> str:
type_set: set = set()
def dfs(currentType:RefType) -> Dict[str, Dict[str, Any]|Any]:
if currentType.IsPrimitive:
@@ -1312,7 +1185,7 @@ class RefType(ValueInfo):
return self.RealType == other.RealType
# 添加新的优化方法,避免重复检查
def _init_base_types_if_needed(self):
def _InitBaseTypesIfNeeded(self):
"""初始化基类类型,只在需要时执行"""
if self._BaseTypes is None:
self._BaseTypes = []
@@ -1327,7 +1200,7 @@ class RefType(ValueInfo):
@functools.lru_cache(maxsize=128)
def GetBaseFields(self, flag:RefTypeFlag=RefTypeFlag.Default) -> List[FieldInfo]:
if self._BaseTypes is None:
self._init_base_types_if_needed()
self._InitBaseTypesIfNeeded()
result = []
for baseType in self._BaseTypes:
result.extend(baseType.GetFields(flag))
@@ -1336,7 +1209,7 @@ class RefType(ValueInfo):
@functools.lru_cache(maxsize=128)
def GetAllBaseFields(self) -> List[FieldInfo]:
if self._BaseTypes is None:
self._init_base_types_if_needed()
self._InitBaseTypesIfNeeded()
result = []
for baseType in self._BaseTypes:
result.extend(baseType.GetAllFields())
@@ -1346,7 +1219,7 @@ class RefType(ValueInfo):
@functools.lru_cache(maxsize=128)
def GetBaseMethods(self, flag:RefTypeFlag=RefTypeFlag.Default) -> List[MethodInfo]:
if self._BaseTypes is None:
self._init_base_types_if_needed()
self._InitBaseTypesIfNeeded()
result = []
for baseType in self._BaseTypes:
result.extend(baseType.GetMethods(flag))
@@ -1355,7 +1228,7 @@ class RefType(ValueInfo):
@functools.lru_cache(maxsize=128)
def GetAllBaseMethods(self) -> List[MethodInfo]:
if self._BaseTypes is None:
self._init_base_types_if_needed()
self._InitBaseTypesIfNeeded()
result = []
for baseType in self._BaseTypes:
result.extend(baseType.GetAllMethods())
@@ -1364,7 +1237,7 @@ class RefType(ValueInfo):
@functools.lru_cache(maxsize=128)
def GetBaseMembers(self, flag:RefTypeFlag=RefTypeFlag.Default) -> List[MemberInfo]:
if self._BaseTypes is None:
self._init_base_types_if_needed()
self._InitBaseTypesIfNeeded()
result = []
for baseType in self._BaseTypes:
result.extend(baseType.GetMembers(flag))
@@ -1373,7 +1246,7 @@ class RefType(ValueInfo):
@functools.lru_cache(maxsize=128)
def GetAllBaseMembers(self) -> List[MemberInfo]:
if self._BaseTypes is None:
self._init_base_types_if_needed()
self._InitBaseTypesIfNeeded()
result = []
for baseType in self._BaseTypes:
result.extend(baseType.GetAllMembers())
@@ -1434,7 +1307,7 @@ RTypen[T] 是 T 类型的 RefType
_Internal_TypeManager:Optional['TypeManager'] = None
class TypeManager(BaseModel, any_class):
class TypeManager(BaseModel):
_RefTypes: Dict[type|_SpecialIndictaor, RefType] = PrivateAttr(default_factory=dict)
_is_preheated: bool = PrivateAttr(default=False)
_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) # 字符串到类型的缓存
@classmethod
def GetInstance(cls) -> Self:
def GetInstance(cls) -> 'TypeManager':
global _Internal_TypeManager
if _Internal_TypeManager is None:
_Internal_TypeManager = cls()
@@ -1457,7 +1330,7 @@ class TypeManager(BaseModel, any_class):
# 常用的基础类型列表
common_types = [
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函数
try:
return to_type(data, module_name=module_name)
return ToType(data, module_name=module_name)
except Exception:
pass
# 尝试使用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):
metaType = data
return metaType

View File

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