def delete_files(cls, labbook: LabBook, section: str, relative_paths: List[str]) -> None: """Delete file (or directory) from inside lb section. The list of paths is deleted in series. Only provide "parent" nodes in the file tree. This is because deletes on directories will remove all child objects, so subsequent deletes of individual files will then fail. Args: labbook(LabBook): Subject LabBook section(str): Section name (code, input, output) relative_paths(list(str)): a list of relative paths from labbook root to target Returns: None """ labbook.validate_section(section) if not isinstance(relative_paths, list): raise ValueError("Must provide list of paths to remove") for file_path in relative_paths: relative_path = LabBook.make_path_relative(file_path) target_path = os.path.join(labbook.root_dir, section, relative_path) if not os.path.exists(target_path): raise ValueError( f"Attempted to delete non-existent path at `{target_path}`" ) else: labbook.git.remove(target_path, force=True, keep_file=False) if os.path.exists(target_path): raise IOError(f"Failed to delete path: {target_path}") labbook.sweep_uncommitted_changes(show=True)
def test_makedir_simple(self, mock_labbook): # Note that "score" refers to the count of .gitkeep files. lb = mock_labbook[2] long_dir = "code/non/existant/dir/should/now/be/made" dirs = ["code/cat_dir", "code/dog_dir", "code/mouse_dir/", "code/mouse_dir/new_dir", long_dir] for d in dirs: FO.makedir(lb, d) assert os.path.isdir(os.path.join(lb.root_dir, d)) assert os.path.isfile(os.path.join(lb.root_dir, d, '.gitkeep')) score = 0 for root, dirs, files in os.walk(os.path.join(lb.root_dir, 'code', 'non')): for f in files: if f == '.gitkeep': score += 1 # Ensure that count of .gitkeep files equals the number of subdirs, excluding the code dir. assert score == len(LabBook.make_path_relative(long_dir).split(os.sep)) - 1
def test_make_path_relative(self): vectors = [ # In format of input: expected output (None, None), ('', ''), ('/', ''), ('//', ''), ('/////cats', 'cats'), ('//cats///', 'cats///'), ('cats', 'cats'), ('/cats/', 'cats/'), ('complex/.path/.like/this', 'complex/.path/.like/this'), ('//complex/.path/.like/this', 'complex/.path/.like/this') ] for sample_input, expected_output in vectors: assert LabBook.make_path_relative(sample_input) == expected_output
def makedir(cls, labbook: LabBook, relative_path: str, make_parents: bool = True, create_activity_record: bool = False) -> None: """Make a new directory inside the labbook directory. Args: labbook: Subject LabBook relative_path(str): Path within the labbook to make directory make_parents(bool): If true, create intermediary directories create_activity_record(bool): If true, create commit and activity record Returns: str: Absolute path of new directory """ if not relative_path: raise ValueError("relative_path argument cannot be None or empty") relative_path = LabBook.make_path_relative(relative_path) new_directory_path = os.path.join(labbook.root_dir, relative_path) if os.path.exists(new_directory_path): return else: logger.info(f"Making new directory in `{new_directory_path}`") os.makedirs(new_directory_path, exist_ok=make_parents) new_dir = '' for d in relative_path.split(os.sep): new_dir = os.path.join(new_dir, d) full_new_dir = os.path.join(labbook.root_dir, new_dir) gitkeep_path = os.path.join(full_new_dir, '.gitkeep') if not os.path.exists(gitkeep_path): with open(gitkeep_path, 'w') as gitkeep: gitkeep.write( "This file is necessary to keep this directory tracked by Git" " and archivable by compression tools. Do not delete or modify!" ) labbook.git.add(gitkeep_path) if create_activity_record: # Create detail record activity_type, activity_detail_type, section_str = labbook.infer_section_from_relative_path( relative_path) adr = ActivityDetailRecord(activity_detail_type, show=False, importance=0, action=ActivityAction.CREATE) msg = f"Created new {section_str} directory `{relative_path}`" commit = labbook.git.commit(msg) adr.add_value('text/markdown', msg) # Create activity record ar = ActivityRecord(activity_type, message=msg, linked_commit=commit.hexsha, show=True, importance=255, tags=['directory-create']) ar.add_detail_object(adr) # Store ars = ActivityStore(labbook) ars.create_activity_record(ar)
def move_file(cls, labbook: LabBook, section: str, src_rel_path: str, dst_rel_path: str) \ -> List[Dict[str, Any]]: """Move a file or directory within a labbook, but not outside of it. Wraps underlying "mv" call. Args: labbook: Subject LabBook section(str): Section name (code, input, output) src_rel_path(str): Source file or directory dst_rel_path(str): Target file name and/or directory """ # Start with Validations labbook.validate_section(section) if not src_rel_path: raise ValueError("src_rel_path cannot be None or empty") if dst_rel_path is None: raise ValueError("dst_rel_path cannot be None or empty") src_rel_path = LabBook.make_path_relative(src_rel_path) dst_rel_path = LabBook.make_path_relative(dst_rel_path) src_abs_path = os.path.join(labbook.root_dir, section, src_rel_path.replace('..', '')) dst_abs_path = os.path.join(labbook.root_dir, section, dst_rel_path.replace('..', '')) if not os.path.exists(src_abs_path): raise ValueError(f"No src file exists at `{src_abs_path}`") try: src_type = 'directory' if os.path.isdir(src_abs_path) else 'file' logger.info( f"Moving {src_type} `{src_abs_path}` to `{dst_abs_path}`") labbook.git.remove(src_abs_path, keep_file=True) final_dest = shutil.move(src_abs_path, dst_abs_path) commit_msg = f"Moved {src_type} `{src_rel_path}` to `{dst_rel_path}`" cls._make_move_activity_record(labbook, section, dst_abs_path, commit_msg) if os.path.isfile(final_dest): t = final_dest.replace(os.path.join(labbook.root_dir, section), '') return [cls.get_file_info(labbook, section, t or "/")] else: moved_files = list() t = final_dest.replace(os.path.join(labbook.root_dir, section), '') moved_files.append( cls.get_file_info(labbook, section, t or "/")) for root, dirs, files in os.walk(final_dest): rt = root.replace(os.path.join(labbook.root_dir, section), '') rt = _make_path_relative(rt) for d in sorted(dirs): dinfo = cls.get_file_info(labbook, section, os.path.join(rt, d)) moved_files.append(dinfo) for f in filter(lambda n: n != '.gitkeep', sorted(files)): finfo = cls.get_file_info(labbook, section, os.path.join(rt, f)) moved_files.append(finfo) return moved_files except Exception as e: logger.critical( "Failed moving file in labbook. Repository may be in corrupted state." ) logger.exception(e) raise