news 2026/4/17 2:46:16

BrowserUse10-源码-FileSystem模块-整理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
BrowserUse10-源码-FileSystem模块-整理

BrowserUse10-源码-FileSystem模块-整理


FileSystem模块-整理

1-源代码部分

importasyncioimportbase64importosimportreimportshutilfromabcimportABC,abstractmethodfromconcurrent.futuresimportThreadPoolExecutorfrompathlibimportPathfromtypingimportAnyfrompydanticimportBaseModel,Fieldfromreportlab.lib.pagesizesimportletterfromreportlab.lib.stylesimportgetSampleStyleSheetfromreportlab.platypusimportParagraph,SimpleDocTemplate,Spacer# 无效文件名错误信息:文件名格式无效,必须为字母数字字符并带有支持的扩展名。INVALID_FILENAME_ERROR_MESSAGE='Error: Invalid filename format. Must be alphanumeric with supported extension.'# 默认文件系统路径DEFAULT_FILE_SYSTEM_PATH='browseruse_agent_data'classFileSystemError(Exception):"""用于文件系统操作的自定义异常,应显示给LLM"""passclassBaseFile(BaseModel,ABC):"""所有文件类型的基类"""# 文件名name:str# 文件内容,默认为空字符串content:str=''# --- 子类必须定义此属性 ---@property@abstractmethoddefextension(self)->str:"""文件扩展名(例如 'txt', 'md')"""passdefwrite_file_content(self,content:str)->None:"""更新内部内容(已格式化)"""self.update_content(content)defappend_file_content(self,content:str)->None:"""将内容追加到内部内容中"""self.update_content(self.content+content)# --- 这些是共享的且在此处实现 ---defupdate_content(self,content:str)->None:self.content=contentdefsync_to_disk_sync(self,path:Path)->None:# 构建完整文件路径file_path=path/self.full_name# 同步写入文件内容file_path.write_text(self.content)asyncdefsync_to_disk(self,path:Path)->None:# 构建完整文件路径file_path=path/self.full_name# 使用线程池执行同步写入操作withThreadPoolExecutor()asexecutor:awaitasyncio.get_event_loop().run_in_executor(executor,lambda:file_path.write_text(self.content))asyncdefwrite(self,content:str,path:Path)->None:# 更新文件内容self.write_file_content(content)# 异步同步到磁盘awaitself.sync_to_disk(path)asyncdefappend(self,content:str,path:Path)->None:# 追加文件内容self.append_file_content(content)# 异步同步到磁盘awaitself.sync_to_disk(path)defread(self)->str:# 返回文件内容returnself.content@propertydeffull_name(self)->str:# 返回完整的文件名(包含扩展名)returnf'{self.name}.{self.extension}'@propertydefget_size(self)->int:# 返回文件内容大小(字节数)returnlen(self.content)@propertydefget_line_count(self)->int:# 返回文件行数returnlen(self.content.splitlines())classMarkdownFile(BaseFile):"""Markdown文件实现"""@propertydefextension(self)->str:# 扩展名为 mdreturn'md'classTxtFile(BaseFile):"""纯文本文件实现"""@propertydefextension(self)->str:# 扩展名为 txtreturn'txt'classJsonFile(BaseFile):"""JSON文件实现"""@propertydefextension(self)->str:# 扩展名为 jsonreturn'json'classCsvFile(BaseFile):"""CSV文件实现"""@propertydefextension(self)->str:# 扩展名为 csvreturn'csv'classJsonlFile(BaseFile):"""JSONL(JSON行)文件实现"""@propertydefextension(self)->str:# 扩展名为 jsonlreturn'jsonl'classPdfFile(BaseFile):"""PDF文件实现"""@propertydefextension(self)->str:# 扩展名为 pdfreturn'pdf'defsync_to_disk_sync(self,path:Path)->None:# 构建完整文件路径file_path=path/self.full_nametry:# 创建PDF文档doc=SimpleDocTemplate(str(file_path),pagesize=letter)styles=getSampleStyleSheet()story=[]# 将markdown内容转换为简单文本并添加到PDF中# 对于基本实现,我们将内容视为纯文本# 这避免了AGPL许可证问题,同时保持功能content_lines=self.content.split('\n')forlineincontent_lines:ifline.strip():# 处理基本的markdown标题ifline.startswith('# '):para=Paragraph(line[2:],styles['Title'])elifline.startswith('## '):para=Paragraph(line[3:],styles['Heading1'])elifline.startswith('### '):para=Paragraph(line[4:],styles['Heading2'])else:para=Paragraph(line,styles['Normal'])story.append(para)else:story.append(Spacer(1,6))# 构建PDF文档doc.build(story)exceptExceptionase:# 抛出自定义文件系统错误raiseFileSystemError(f"Error: Could not write to file '{self.full_name}'.{str(e)}")asyncdefsync_to_disk(self,path:Path)->None:# 使用线程池执行同步写入操作withThreadPoolExecutor()asexecutor:awaitasyncio.get_event_loop().run_in_executor(executor,lambda:self.sync_to_disk_sync(path))classDocxFile(BaseFile):"""DOCX文件实现"""@propertydefextension(self)->str:# 扩展名为 docxreturn'docx'defsync_to_disk_sync(self,path:Path)->None:# 构建完整文件路径file_path=path/self.full_nametry:fromdocximportDocument doc=Document()# 将内容转换为DOCX段落content_lines=self.content.split('\n')forlineincontent_lines:ifline.strip():# 处理基本的markdown标题ifline.startswith('# '):doc.add_heading(line[2:],level=1)elifline.startswith('## '):doc.add_heading(line[3:],level=2)elifline.startswith('### '):doc.add_heading(line[4:],level=3)else:doc.add_paragraph(line)else:doc.add_paragraph()# 空段落用于间距# 保存文档doc.save(str(file_path))exceptExceptionase:# 抛出自定义文件系统错误raiseFileSystemError(f"Error: Could not write to file '{self.full_name}'.{str(e)}")asyncdefsync_to_disk(self,path:Path)->None:# 使用线程池执行同步写入操作withThreadPoolExecutor()asexecutor:awaitasyncio.get_event_loop().run_in_executor(executor,lambda:self.sync_to_disk_sync(path))classFileSystemState(BaseModel):"""文件系统的可序列化状态"""# 文件列表,键为完整文件名,值为文件数据files:dict[str,dict[str,Any]]=Field(default_factory=dict)# 基础目录路径base_dir:str# 提取内容计数extracted_content_count:int=0classFileSystem:"""增强型文件系统,支持内存存储和多种文件类型"""def__init__(self,base_dir:str|Path,create_default_files:bool=True):# 在调用 super().__init__ 之前处理 Path 转换# 设置基础目录self.base_dir=Path(base_dir)ifisinstance(base_dir,str)elsebase_dir# 创建基础目录(如果不存在)self.base_dir.mkdir(parents=True,exist_ok=True)# 创建专用子文件夹用于所有操作# 设置数据目录self.data_dir=self.base_dir/DEFAULT_FILE_SYSTEM_PATHifself.data_dir.exists():# 清理数据目录shutil.rmtree(self.data_dir)self.data_dir.mkdir(exist_ok=True)# 定义支持的文件类型映射self._file_types:dict[str,type[BaseFile]]={'md':MarkdownFile,'txt':TxtFile,'json':JsonFile,'jsonl':JsonlFile,'csv':CsvFile,'pdf':PdfFile,'docx':DocxFile,}# 存储文件对象self.files={}ifcreate_default_files:# 默认文件列表self.default_files=['todo.md']# 创建默认文件self._create_default_files()# 提取内容计数self.extracted_content_count=0defget_allowed_extensions(self)->list[str]:"""获取允许的扩展名列表"""returnlist(self._file_types.keys())def_get_file_type_class(self,extension:str)->type[BaseFile]|None:"""根据扩展名获取相应的文件类"""returnself._file_types.get(extension.lower(),None)def_create_default_files(self)->None:"""创建默认结果和待办文件"""forfull_filenameinself.default_files:# 解析文件名name_without_ext,extension=self._parse_filename(full_filename)# 获取文件类file_class=self._get_file_type_class(extension)ifnotfile_class:raiseValueError(f"Error: Invalid file extension '{extension}' for file '{full_filename}'.")# 创建文件对象file_obj=file_class(name=name_without_ext)# 使用完整文件名作为键存储self.files[full_filename]=file_obj# 同步写入磁盘file_obj.sync_to_disk_sync(self.data_dir)def_is_valid_filename(self,file_name:str)->bool:"""检查文件名是否符合要求的模式:name.extension"""# 从 _file_types 构建扩展名模式extensions='|'.join(self._file_types.keys())# 正则表达式模式pattern=rf'^[a-zA-Z0-9_\-\u4e00-\u9fff]+\.({extensions})$'# 获取文件名基础部分file_name_base=os.path.basename(file_name)# 匹配模式returnbool(re.match(pattern,file_name_base))def_parse_filename(self,filename:str)->tuple[str,str]:"""将文件名解析为名称和扩展名。始终在检查 _is_valid_filename 之后使用"""# 分割文件名和扩展名name,extension=filename.rsplit('.',1)# 返回小写的扩展名returnname,extension.lower()defget_dir(self)->Path:"""获取文件系统目录"""returnself.data_dirdefget_file(self,full_filename:str)->BaseFile|None:"""通过完整文件名获取文件对象"""# 检查文件名是否有效ifnotself._is_valid_filename(full_filename):returnNone# 使用完整文件名作为键returnself.files.get(full_filename)deflist_files(self)->list[str]:"""列出系统中的所有文件"""# 返回所有文件的完整文件名列表return[file_obj.full_nameforfile_objinself.files.values()]defdisplay_file(self,full_filename:str)->str|None:"""使用特定于文件的显示方法显示文件内容"""# 检查文件名是否有效ifnotself._is_valid_filename(full_filename):returnNone# 获取文件对象file_obj=self.get_file(full_filename)ifnotfile_obj:returnNone# 返回文件内容returnfile_obj.read()asyncdefread_file_structured(self,full_filename:str,external_file:bool=False)->dict[str,Any]:"""读取文件并返回结构化数据,包括图片(如适用)。 返回: 包含以下键的字典: - 'message': str - 要显示的消息 - 'images': list[dict] | None - 如果文件是图片,则包含图片数据:[{"name": str, "data": base64_str}] """result:dict[str,Any]={'message':'','images':None}ifexternal_file:try:try:_,extension=self._parse_filename(full_filename)exceptException:# 错误消息result['message']=(f'Error: Invalid filename format{full_filename}. Must be alphanumeric with a supported extension.')returnresult# 支持的文件类型ifextensionin['md','txt','json','jsonl','csv']:importanyio# 异步打开文件并读取内容asyncwithawaitanyio.open_file(full_filename,'r')asf:content=awaitf.read()# 构建消息result['message']=f'Read from file{full_filename}.\n<content>\n{content}\n</content>'returnresultelifextension=='docx':fromdocximportDocument# 加载DOCX文档doc=Document(full_filename)# 提取所有段落文本content='\n'.join([para.textforparaindoc.paragraphs])# 构建消息result['message']=f'Read from file{full_filename}.\n<content>\n{content}\n</content>'returnresultelifextension=='pdf':importpypdf# 加载PDF文档reader=pypdf.PdfReader(full_filename)num_pages=len(reader.pages)MAX_PDF_PAGES=20extra_pages=num_pages-MAX_PDF_PAGES extracted_text=''# 提取前20页的内容forpageinreader.pages[:MAX_PDF_PAGES]:extracted_text+=page.extract_text()# 添加额外页数信息extra_pages_text=f'{extra_pages}more pages...'ifextra_pages>0else''# 构建消息result['message']=(f'Read from file{full_filename}.\n<content>\n{extracted_text}\n{extra_pages_text}</content>')returnresultelifextensionin['jpg','jpeg','png']:importanyio# 异步读取图像文件并转换为base64asyncwithawaitanyio.open_file(full_filename,'rb')asf:img_data=awaitf.read()base64_str=base64.b64encode(img_data).decode('utf-8')# 构建消息result['message']=f'Read image file{full_filename}.'# 包含图像数据result['images']=[{'name':os.path.basename(full_filename),'data':base64_str}]returnresultelse:# 不支持的扩展名result['message']=f'Error: Cannot read file{full_filename}as{extension}extension is not supported.'returnresultexceptFileNotFoundError:# 文件未找到result['message']=f"Error: File '{full_filename}' not found."returnresultexceptPermissionError:# 权限被拒绝result['message']=f"Error: Permission denied to read file '{full_filename}'."returnresultexceptExceptionase:# 其他错误result['message']=f"Error: Could not read file '{full_filename}'.{str(e)}"returnresult# 对于内部文件,仅支持非图片类型# 检查文件名是否有效ifnotself._is_valid_filename(full_filename):result['message']=INVALID_FILENAME_ERROR_MESSAGEreturnresult# 获取文件对象file_obj=self.get_file(full_filename)ifnotfile_obj:result['message']=f"File '{full_filename}' not found."returnresulttry:# 读取文件内容content=file_obj.read()# 构建消息result['message']=f'Read from file{full_filename}.\n<content>\n{content}\n</content>'returnresultexceptFileSystemErrorase:# 文件系统错误result['message']=str(e)returnresultexceptExceptionase:# 其他错误result['message']=f"Error: Could not read file '{full_filename}'.{str(e)}"returnresultasyncdefread_file(self,full_filename:str,external_file:bool=False)->str:"""使用特定于文件的读取方法读取文件内容,并返回适当的LLM消息。 注意:对于图像文件,请使用 read_file_structured() 以获取图像数据。 """# 获取结构化读取结果result=awaitself.read_file_structured(full_filename,external_file)# 返回消息returnresult['message']asyncdefwrite_file(self,full_filename:str,content:str)->str:"""使用特定于文件的写入方法将内容写入文件"""# 检查文件名是否有效ifnotself._is_valid_filename(full_filename):returnINVALID_FILENAME_ERROR_MESSAGEtry:# 解析文件名name_without_ext,extension=self._parse_filename(full_filename)# 获取文件类file_class=self._get_file_type_class(extension)ifnotfile_class:raiseValueError(f"Error: Invalid file extension '{extension}' for file '{full_filename}'.")# 创建或获取现有文件对象# 使用完整文件名作为键iffull_filenameinself.files:file_obj=self.files[full_filename]else:file_obj=file_class(name=name_without_ext)self.files[full_filename]=file_obj# 使用特定于文件的写入方法awaitfile_obj.write(content,self.data_dir)# 成功消息returnf'Data written to file{full_filename}successfully.'exceptFileSystemErrorase:# 文件系统错误returnstr(e)exceptExceptionase:# 其他错误returnf"Error: Could not write to file '{full_filename}'.{str(e)}"asyncdefappend_file(self,full_filename:str,content:str)->str:"""使用特定于文件的追加方法向文件追加内容"""# 检查文件名是否有效ifnotself._is_valid_filename(full_filename):returnINVALID_FILENAME_ERROR_MESSAGE# 获取文件对象file_obj=self.get_file(full_filename)ifnotfile_obj:returnf"File '{full_filename}' not found."try:# 使用特定于文件的追加方法awaitfile_obj.append(content,self.data_dir)# 成功消息returnf'Data appended to file{full_filename}successfully.'exceptFileSystemErrorase:# 文件系统错误returnstr(e)exceptExceptionase:# 其他错误returnf"Error: Could not append to file '{full_filename}'.{str(e)}"asyncdefreplace_file_str(self,full_filename:str,old_str:str,new_str:str)->str:"""在文件中将 old_str 替换为 new_str"""# 检查文件名是否有效ifnotself._is_valid_filename(full_filename):returnINVALID_FILENAME_ERROR_MESSAGE# 不能替换空字符串ifnotold_str:return'Error: Cannot replace empty string. Please provide a non-empty string to replace.'# 获取文件对象file_obj=self.get_file(full_filename)ifnotfile_obj:returnf"File '{full_filename}' not found."try:# 读取文件内容content=file_obj.read()# 替换字符串content=content.replace(old_str,new_str)# 写回文件awaitfile_obj.write(content,self.data_dir)# 成功消息returnf'Successfully replaced all occurrences of "{old_str}" with "{new_str}" in file{full_filename}'exceptFileSystemErrorase:# 文件系统错误returnstr(e)exceptExceptionase:# 其他错误returnf"Error: Could not replace string in file '{full_filename}'.{str(e)}"asyncdefsave_extracted_content(self,content:str)->str:"""将提取的内容保存到编号文件中"""# 初始文件名initial_filename=f'extracted_content_{self.extracted_content_count}'# 完整文件名extracted_filename=f'{initial_filename}.md'# 创建Markdown文件对象file_obj=MarkdownFile(name=initial_filename)# 写入内容awaitfile_obj.write(content,self.data_dir)# 存储文件对象self.files[extracted_filename]=file_obj# 增加计数器self.extracted_content_count+=1# 返回文件名returnextracted_filenamedefdescribe(self)->str:"""使用特定于文件的显示方法列出所有文件及其内容信息"""# 显示字符数限制DISPLAY_CHARS=400description=''forfile_objinself.files.values():# 跳过 todo.md 的描述iffile_obj.full_name=='todo.md':continue# 读取文件内容content=file_obj.read()# 处理空文件ifnotcontent:description+=f'<file>\n{file_obj.full_name}- [empty file]\n</file>\n'continue# 按行分割lines=content.splitlines()line_count=len(lines)# 对于小文件,显示全部内容whole_file_description=(f'<file>\n{file_obj.full_name}-{line_count}lines\n<content>\n{content}\n</content>\n</file>\n')iflen(content)<int(1.5*DISPLAY_CHARS):description+=whole_file_descriptioncontinue# 对于大文件,显示开头和结尾预览half_display_chars=DISPLAY_CHARS//2# 获取开头预览start_preview=''start_line_count=0chars_count=0forlineinlines:ifchars_count+len(line)+1>half_display_chars:breakstart_preview+=line+'\n'chars_count+=len(line)+1start_line_count+=1# 获取结尾预览end_preview=''end_line_count=0chars_count=0forlineinreversed(lines):ifchars_count+len(line)+1>half_display_chars:breakend_preview=line+'\n'+end_preview chars_count+=len(line)+1end_line_count+=1# 计算中间的行数middle_line_count=line_count-start_line_count-end_line_countifmiddle_line_count<=0:description+=whole_file_descriptioncontinue# 清理预览start_preview=start_preview.strip('\n').rstrip()end_preview=end_preview.strip('\n').rstrip()# 格式化输出ifnot(start_previeworend_preview):description+=f'<file>\n{file_obj.full_name}-{line_count}lines\n<content>\n{middle_line_count}lines...\n</content>\n</file>\n'else:description+=f'<file>\n{file_obj.full_name}-{line_count}lines\n<content>\n{start_preview}\n'description+=f'...{middle_line_count}more lines ...\n'description+=f'{end_preview}\n'description+='</content>\n</file>\n'# 去除末尾换行符returndescription.strip('\n')defget_todo_contents(self)->str:"""获取待办文件内容"""# 获取 todo.md 文件todo_file=self.get_file('todo.md')# 返回内容,如果文件不存在则返回空字符串returntodo_file.read()iftodo_fileelse''defget_state(self)->FileSystemState:"""获取文件系统的可序列化状态"""# 存储文件数据files_data={}forfull_filename,file_objinself.files.items():files_data[full_filename]={'type':file_obj.__class__.__name__,'data':file_obj.model_dump()}# 构建状态对象returnFileSystemState(files=files_data,base_dir=str(self.base_dir),extracted_content_count=self.extracted_content_count)defnuke(self)->None:"""删除文件系统目录"""# 删除数据目录shutil.rmtree(self.data_dir)@classmethoddeffrom_state(cls,state:FileSystemState)->'FileSystem':"""从可序列化状态恢复文件系统,位置完全相同"""# 创建文件系统但不创建默认文件fs=cls(base_dir=Path(state.base_dir),create_default_files=False)# 恢复提取内容计数fs.extracted_content_count=state.extracted_content_count# 恢复所有文件forfull_filename,file_datainstate.files.items():file_type=file_data['type']file_info=file_data['data']# 根据类型创建适当的文件对象iffile_type=='MarkdownFile':file_obj=MarkdownFile(**file_info)eliffile_type=='TxtFile':file_obj=TxtFile(**file_info)eliffile_type=='JsonFile':file_obj=JsonFile(**file_info)eliffile_type=='JsonlFile':file_obj=JsonlFile(**file_info)eliffile_type=='CsvFile':file_obj=CsvFile(**file_info)eliffile_type=='PdfFile':file_obj=PdfFile(**file_info)eliffile_type=='DocxFile':file_obj=DocxFile(**file_info)else:# 跳过未知文件类型continue# 添加到文件字典并同步到磁盘fs.files[full_filename]=file_obj file_obj.sync_to_disk_sync(fs.data_dir)returnfs

2-自动测试

#!/usr/bin/env python3""" FileSystem 文件系统读取测试用例 测试读取本地文件并打印具体内容,覆盖所有支持的文件类型 """# 添加项目根目录到 Python 路径以支持导入importsysfrompathlibimportPath sys.path.insert(0,str(Path(__file__).parent.parent.parent))importasyncioimportjsonimportloggingfromreportlab.lib.pagesizesimportletterfromreportlab.platypusimportSimpleDocTemplate,Paragraphfrombrowser_use_manual.filesystem.file_systemimport(FileSystem)# 配置日志logging.basicConfig(level=logging.INFO,format='%(levelname)s - %(name)s - %(message)s')logger=logging.getLogger(__name__)classTestFileSystemReadAllTypes:"""测试文件系统读取所有文件类型"""def__init__(self):"""每个测试方法执行前的准备工作"""# 创建临时目录用于测试self.file_dir=Path("/Users/rong/Documents/EnzoApplication/WorkSpace/Python/20251209_1_Python_playwright_manual/browser-use-manual-file")logger.info(f"测试文件目录地址:{self.file_dir}")# 创建文件系统实例self.fs=FileSystem(self.file_dir,create_default_files=False)defcreate_test_file(self,filename:str,content:str):"""创建测试文件"""# 创建完整的文件系统路径fs_data_dir=self.file_dir fs_data_dir.mkdir(parents=True,exist_ok=True)full_path=fs_data_dir/filename full_path.write_text(content,encoding='utf-8')logger.info(f"创建测试文件:{full_path}")returnstr(full_path)deftest_read_markdown_file(self):"""测试读取 Markdown 文件"""filename="textContent.md""""测试外部文件读取"""file_full_path=self.file_dir/filenametry:result=asyncio.run(self.fs.read_file(str(file_full_path),external_file=True))logger.info(f"读取 MARKDOWN 文件结果-第1次:{result}")exceptExceptionase:logger.info(f"读取 MARKDOWN 文件失败-第1次:{str(e)}")"""测试外部文件写入"""content="# 测试标题\n\n这是一个测试的 Markdown 文件。\n\n- 列表项1\n- 列表项2\n"file_path=self.create_test_file(filename,content)result=asyncio.run(self.fs.read_file(str(file_path),external_file=True))logger.info(f"读取 MARKDOWN 文件结果-第2次:{result}")deftest_read_text_file(self):"""测试读取 TEXT 文件"""filename="textContent.txt""""测试外部文件读取"""file_full_path=self.file_dir/filenametry:result=asyncio.run(self.fs.read_file(str(file_full_path),external_file=True))logger.info(f"读取 TEXT 文件结果-第1次:{result}")exceptExceptionase:logger.info(f"读取 TEXT 文件失败-第1次:{str(e)}")"""测试外部文件写入"""content="这是测试文本文件的内容。\n第二行内容。\n第三行内容。"file_path=self.create_test_file(filename,content)result=asyncio.run(self.fs.read_file(str(file_path),external_file=True))logger.info(f"读取 TEXT 文件结果-第2次:{result}")deftest_read_json_file(self):"""测试读取 JSON 文件"""filename="textContent.json""""测试外部文件读取"""file_full_path=self.file_dir/filenametry:result=asyncio.run(self.fs.read_file(str(file_full_path),external_file=True))logger.info(f"读取 JSON 文件结果-第1次:{result}")exceptExceptionase:logger.info(f"读取 JSON 文件失败-第1次:{str(e)}")"""测试外部文件写入"""content=json.dumps({"name":"测试用户","age":30,"skills":["Python","JavaScript","Java"]},ensure_ascii=False,indent=2)file_path=self.create_test_file(filename,content)result=asyncio.run(self.fs.read_file(str(file_path),external_file=True))logger.info(f"读取 JSON 文件结果-第2次:{result}")deftest_read_csv_file(self):"""测试读取 CSV 文件"""filename="textContent.csv""""测试外部文件读取"""file_full_path=self.file_dir/filenametry:result=asyncio.run(self.fs.read_file(str(file_full_path),external_file=True))logger.info(f"读取 CSV 文件结果-第1次:{result}")exceptExceptionase:logger.info(f"读取 CSV 文件失败-第1次:{str(e)}")"""测试外部文件写入"""content="姓名,年龄,城市\n张三,25,北京\n李四,30,上海\n王五,28,广州"file_path=self.create_test_file(filename,content)result=asyncio.run(self.fs.read_file(str(file_path),external_file=True))logger.info(f"读取 CSV 文件结果-第2次:{result}")deftest_read_jsonl_file(self):"""测试读取 JSONL 文件"""filename="textContent.jsonl""""测试外部文件读取"""file_full_path=self.file_dir/filenametry:result=asyncio.run(self.fs.read_file(str(file_full_path),external_file=True))logger.info(f"读取 JSONL 文件结果-第1次:{result}")exceptExceptionase:logger.info(f"读取 JSONL 文件失败-第1次:{str(e)}")"""测试外部文件写入"""lines=[json.dumps({"id":1,"name":"项目1"},ensure_ascii=False),json.dumps({"id":2,"name":"项目2"},ensure_ascii=False),json.dumps({"id":3,"name":"项目3"},ensure_ascii=False)]content="\n".join(lines)file_path=self.create_test_file(filename,content)result=asyncio.run(self.fs.read_file(str(file_path),external_file=True))logger.info(f"读取 JSONL 文件结果-第2次:{result}")deftest_read_pdf_file(self):"""测试读取 PDF 文件"""filename="textContent.pdf""""测试外部文件读取"""file_full_path=self.file_dir/filenametry:result=asyncio.run(self.fs.read_file(str(file_full_path),external_file=True))logger.info(f"读取 PDF 文件结果-第1次:{result}")exceptExceptionase:logger.info(f"读取 PDF 文件失败-第1次:{str(e)}")"""测试外部文件写入"""# 使用 reportlab 创建 PDFcontent="这是测试 PDF 文件的内容。"doc=SimpleDocTemplate(str(self.file_dir/filename),pagesize=letter)story=[Paragraph(content)]doc.build(story)file_path=self.create_test_file(filename,content)result=asyncio.run(self.fs.read_file(str(file_path),external_file=True))logger.info(f"读取 PDF 文件结果-第2次:{result}")deftest_read_docx_file(self):"""测试读取 DOCX 文件"""try:fromdocximportDocument has_docx=TrueexceptImportError:has_docx=Falseifnothas_docx:logger.info("未安装 python-docx,跳过 DOCX 文件测试")returnfilename="textContent.docx""""测试外部文件读取"""file_full_path=self.file_dir/filenametry:result=asyncio.run(self.fs.read_file(str(file_full_path),external_file=True))logger.info(f"读取 DOCX 文件结果-第1次:{result}")exceptExceptionase:logger.info(f"读取 DOCX 文件失败-第1次:{str(e)}")"""测试外部文件写入"""# 创建 DOCX 文件content="这是测试 DOCX 文件的内容。\n第二段内容。"doc=Document()doc.add_heading('测试标题',level=1)doc.add_paragraph('这是测试 DOCX 文件的内容。')doc.add_paragraph('第二段内容。')doc.save(str(self.file_dir/filename))file_path=self.create_test_file(filename,content)result=asyncio.run(self.fs.read_file(str(file_path),external_file=True))logger.info(f"读取 DOCX 文件结果-第2次:{result}")deftest_read_image_file(self):"""测试读取图像文件"""# 创建一个简单的测试图像文件filename="test_image.png""""测试外部文件读取"""file_full_path=self.file_dir/filenametry:result=asyncio.run(self.fs.read_file(str(file_full_path),external_file=True))logger.info(f"读取 IMAGE 文件结果-第1次:{result}")exceptExceptionase:logger.info(f"读取 IMAGE 文件失败-第1次:{str(e)}")"""测试外部文件写入"""# 创建 PNG 文件content="fake png content for testing"file_path=self.create_test_file(filename,content)result=asyncio.run(self.fs.read_file(str(file_path),external_file=True))logger.info(f"读取 IMAGE 文件结果-第2次:{result}")deftest_read_all_supported_types(self):"""测试读取所有支持的文件类型"""logger.info("\n=== 测试读取所有支持的文件类型 ===")# 测试的文件类型和内容test_files=[("test1.md","# Markdown 测试\n\n这是 Markdown 内容。"),("test2.txt","纯文本测试内容。\n第二行。"),("test3.json",'{"key": "value", "number": 42}'),("test4.csv","列1,列2,列3\n值1,值2,值3\n值4,值5,值6"),("test5.jsonl",'{"line": 1}\n{"line": 2}\n{"line": 3}'),]# 创建所有测试文件created_files=[]forfilename,contentintest_files:file_path=self.create_test_file(filename,content)created_files.append((filename,content))logger.info(f"已创建测试文件:{file_path}")# 逐个读取并验证文件forfilename,expected_contentincreated_files:logger.info(f"\n--- 读取文件:{filename}---")file_full_path=self.file_dir/filenametry:result=asyncio.run(self.fs.read_file(str(file_full_path),external_file=True))logger.info(f"读取结果:{result}")exceptExceptionase:logger.info(f"读取文件{filename}失败:{str(e)}")logger.info("\n=== 所有文件类型测试完成 ===")deftest_invalid_file_types(self):"""测试不支持的文件类型"""filename="textContent.xyz"# 不支持的扩展名content="无效文件类型测试"file_path=self.create_test_file(filename,content)logger.info(f"无效文件类型测试路径:{file_path}")# 测试外部文件读取file_full_path=self.file_dir/filename result=asyncio.run(self.fs.read_file(str(file_full_path),external_file=True))logger.info(f"读取不支持的文件类型结果:{result}")# 验证返回错误信息assert"extension is not supported"inresultif__name__=="__main__":# 运行所有测试test_instance=TestFileSystemReadAllTypes()# 运行各个测试方法test_instance.test_read_markdown_file()test_instance.test_read_text_file()test_instance.test_read_json_file()test_instance.test_read_csv_file()test_instance.test_read_jsonl_file()test_instance.test_read_pdf_file()test_instance.test_read_docx_file()test_instance.test_read_image_file()test_instance.test_read_all_supported_types()test_instance.test_invalid_file_types()

3-代码使用

#!/usr/bin/env python3""" FileSystem 文件系统读取测试用例 测试读取本地文件并打印具体内容,覆盖所有支持的文件类型 """# 添加项目根目录到 Python 路径以支持导入importsysfrompathlibimportPath sys.path.insert(0,str(Path(__file__).parent.parent.parent))importloggingfrombrowser_use_manual.filesystem.file_systemimport(MarkdownFile)# 配置日志logging.basicConfig(level=logging.INFO,format='%(levelname)s - %(name)s - %(message)s')logger=logging.getLogger(__name__)fromdatetimeimportdateprint()if__name__=="__main__":# 指定文件路径file_dir=Path("/Users/rong/Documents/EnzoApplication/WorkSpace/Python/20251209_1_Python_playwright_manual/browser-use-manual-file/")file_name="popyu_"+str(date.today())logger.info(f"测试文件的创建地址:{file_dir}/{file_name}")# 创建一个指定的文件类型markdown_file=MarkdownFile(name=file_name)markdown_file.append_file_content("This is a test markdown file.")markdown_file.sync_to_disk_sync(file_dir)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/13 22:08:28

BrowserUse14-源码-ScreenShot模块-整理

BrowserUse14-源码-ScreenShot模块-整理ScreenShot模块 1-源码部分【下载】基于图片的Base64进行图片的&#xff0c;【查看】指定本地的文件路径进行查看""" 浏览器使用代理的截图存储服务。 """import base64 from pathlib import Pathimport a…

作者头像 李华
网站建设 2026/4/9 17:43:06

品牌营销的“防AI雷区”:MyDetector如何让你的文案和图片双保险

品牌营销的“防AI雷区”&#xff1a;MyDetector 如何让你的文案和图片双保险&#xff08;完整版 1680 字&#xff09; AI 时代&#xff0c;品牌最怕的不是写不出来&#xff0c;而是“写得太像 AI” 在如今的营销圈&#xff0c;AI 已经成了标配&#xff1a; ChatGPT 30 秒出一篇…

作者头像 李华
网站建设 2026/4/15 15:47:31

Lenia完整指南:探索连续细胞自动机的数学生命世界

Lenia完整指南&#xff1a;探索连续细胞自动机的数学生命世界 【免费下载链接】Lenia Lenia - Mathematical Life Forms 项目地址: https://gitcode.com/gh_mirrors/le/Lenia Lenia&#xff08;莱尼亚&#xff09;是一个革命性的连续细胞自动机系统&#xff0c;它打破了…

作者头像 李华
网站建设 2026/4/10 7:55:59

GRASP 10.1.3.0天线仿真软件权威学习指南

软件核心价值与技术定位 【免费下载链接】GRASP101.3.0培训教程公开.pdf分享 本仓库提供了一份极为珍贵的学习资源——GRASP10[1].3.0培训教程。GRASP是一款在天线设计和电磁仿真领域内广泛使用的高级软件工具&#xff0c;它凭借其强大的功能和灵活性&#xff0c;成为了专业工程…

作者头像 李华