edq.util.dirent
Operations relating to directory entries (dirents).
These operations are designed for clarity and compatibility, not performance.
Only directories, files, and links will be handled. Other types of dirents may result in an error being raised.
In general, all recursive operations do not follow symlinks by default and instead treat the link as a file.
1""" 2Operations relating to directory entries (dirents). 3 4These operations are designed for clarity and compatibility, not performance. 5 6Only directories, files, and links will be handled. 7Other types of dirents may result in an error being raised. 8 9In general, all recursive operations do not follow symlinks by default and instead treat the link as a file. 10""" 11 12import atexit 13import os 14import shutil 15import tempfile 16import typing 17import uuid 18 19import edq.util.constants 20import edq.util.hash 21 22DEFAULT_ENCODING: str = edq.util.constants.DEFAULT_ENCODING 23""" The default encoding that will be used when reading and writing. """ 24 25DEPTH_LIMIT: int = 10000 26 27def exists(path: str) -> bool: 28 """ 29 Check if a path exists. 30 This will transparently call os.path.lexists(), 31 which will include broken links. 32 """ 33 34 return os.path.lexists(path) 35 36def get_temp_path(prefix: str = '', suffix: str = '', rm: bool = True) -> str: 37 """ 38 Get a path to a valid (but not currently existing) temp dirent. 39 If rm is True, then the dirent will be attempted to be deleted on exit 40 (no error will occur if the path is not there). 41 """ 42 43 path = None 44 while ((path is None) or exists(path)): 45 path = os.path.join(tempfile.gettempdir(), prefix + str(uuid.uuid4()) + suffix) 46 47 path = os.path.realpath(path) 48 49 if (rm): 50 atexit.register(remove, path) 51 52 return path 53 54def get_temp_dir(prefix: str = '', suffix: str = '', rm: bool = True) -> str: 55 """ 56 Get a temp directory. 57 The directory will exist when returned. 58 """ 59 60 path = get_temp_path(prefix = prefix, suffix = suffix, rm = rm) 61 mkdir(path) 62 return path 63 64def mkdir(raw_path: str) -> None: 65 """ 66 Make a directory (including any required parent directories). 67 Does not complain if the directory (or parents) already exist 68 (this includes if the directory or parents are links to directories). 69 """ 70 71 path = os.path.abspath(raw_path) 72 73 if (exists(path)): 74 if (os.path.isdir(path)): 75 return 76 77 raise ValueError(f"Target of mkdir already exists, and is not a dir: '{raw_path}'.") 78 79 _check_parent_dirs(raw_path) 80 81 os.makedirs(path, exist_ok = True) 82 83def _check_parent_dirs(raw_path: str) -> None: 84 """ 85 Check all parents to ensure that they are all dirs (or don't exist). 86 This is naturally handled by os.makedirs(), 87 but the error messages are not consistent between POSIX and Windows. 88 """ 89 90 path = os.path.abspath(raw_path) 91 92 parent_path = path 93 for _ in range(DEPTH_LIMIT): 94 new_parent_path = os.path.dirname(parent_path) 95 if (parent_path == new_parent_path): 96 # We have reached root (are our own parent). 97 return 98 99 parent_path = new_parent_path 100 101 if (os.path.exists(parent_path) and (not os.path.isdir(parent_path))): 102 raise ValueError(f"Target of mkdir contains parent ('{os.path.basename(parent_path)}') that exists and is not a dir: '{raw_path}'.") 103 104 raise ValueError("Depth limit reached.") 105 106def remove(path: str) -> None: 107 """ 108 Remove the given path. 109 The path can be of any type (dir, file, link), 110 and does not need to exist. 111 """ 112 113 if (not exists(path)): 114 return 115 116 if (os.path.isfile(path) or os.path.islink(path)): 117 os.remove(path) 118 elif (os.path.isdir(path)): 119 shutil.rmtree(path) 120 else: 121 raise ValueError(f"Unknown type of dirent: '{path}'.") 122 123def same(a: str, b: str) -> bool: 124 """ 125 Check if two paths represent the same dirent. 126 If either (or both) paths do not exist, false will be returned. 127 If either paths are links, they are resolved before checking 128 (so a link and the target file are considered the "same"). 129 """ 130 131 return (exists(a) and exists(b) and os.path.samefile(a, b)) 132 133def move(raw_source: str, raw_dest: str, no_clobber: bool = False) -> None: 134 """ 135 Move the source dirent to the given destination. 136 Any existing destination will be removed before moving. 137 """ 138 139 source = os.path.abspath(raw_source) 140 dest = os.path.abspath(raw_dest) 141 142 if (not exists(source)): 143 raise ValueError(f"Source of move does not exist: '{raw_source}'.") 144 145 # If dest is a dir, then resolve the path. 146 if (os.path.isdir(dest)): 147 dest = os.path.abspath(os.path.join(dest, os.path.basename(source))) 148 149 # Skip if this is self. 150 if (same(source, dest)): 151 return 152 153 # Check for clobber. 154 if (exists(dest)): 155 if (no_clobber): 156 raise ValueError(f"Destination of move already exists: '{raw_dest}'.") 157 158 remove(dest) 159 160 # Create any required parents. 161 os.makedirs(os.path.dirname(dest), exist_ok = True) 162 163 shutil.move(source, dest) 164 165def copy(raw_source: str, raw_dest: str, no_clobber: bool = False) -> None: 166 """ 167 Copy a dirent or directory to a destination. 168 169 The destination will be overwritten if it exists (and no_clobber is false). 170 For copying the contents of a directory INTO another directory, use copy_contents(). 171 172 No copy is made if the source and dest refer to the same dirent. 173 """ 174 175 source = os.path.abspath(raw_source) 176 dest = os.path.abspath(raw_dest) 177 178 if (same(source, dest)): 179 return 180 181 if (not exists(source)): 182 raise ValueError(f"Source of copy does not exist: '{raw_source}'.") 183 184 if (contains_path(source, dest)): 185 raise ValueError(f"Source of copy cannot contain the destination. Source: '{raw_source}', Destination: '{raw_dest}'.") 186 187 if (contains_path(dest, source)): 188 raise ValueError(f"Destination of copy cannot contain the source. Destination: '{raw_dest}', Source: '{raw_source}'.") 189 190 if (exists(dest)): 191 if (no_clobber): 192 raise ValueError(f"Destination of copy already exists: '{raw_dest}'.") 193 194 remove(dest) 195 196 mkdir(os.path.dirname(dest)) 197 198 if (os.path.islink(source)): 199 # shutil.copy2() can generally handle (broken) links, but Windows is inconsistent (between 3.11 and 3.12) on link handling. 200 link_target = os.readlink(source) 201 os.symlink(link_target, dest) 202 elif (os.path.isfile(source)): 203 shutil.copy2(source, dest, follow_symlinks = False) 204 elif (os.path.isdir(source)): 205 mkdir(dest) 206 207 for child in sorted(os.listdir(source)): 208 copy(os.path.join(raw_source, child), os.path.join(raw_dest, child)) 209 else: 210 raise ValueError(f"Source of copy is not a dir, fie, or link: '{raw_source}'.") 211 212def copy_contents(raw_source: str, raw_dest: str, no_clobber: bool = False) -> None: 213 """ 214 Copy a file or the contents of a directory (excluding the top-level directory itself) into a destination. 215 If the destination exists, it must be a directory. 216 217 The source and destination should not be the same file. 218 219 For a file, this is equivalent to `mkdir -p dest && cp source dest` 220 For a dir, this is equivalent to `mkdir -p dest && cp -r source/* dest` 221 """ 222 223 source = os.path.abspath(raw_source) 224 dest = os.path.abspath(raw_dest) 225 226 if (same(source, dest)): 227 raise ValueError(f"Source and destination of contents copy cannot be the same: '{raw_source}'.") 228 229 if (exists(dest) and (not os.path.isdir(dest))): 230 raise ValueError(f"Destination of contents copy exists and is not a dir: '{raw_dest}'.") 231 232 mkdir(dest) 233 234 if (os.path.isfile(source) or os.path.islink(source)): 235 copy(source, os.path.join(dest, os.path.basename(source)), no_clobber = no_clobber) 236 elif (os.path.isdir(source)): 237 for child in sorted(os.listdir(source)): 238 copy(os.path.join(raw_source, child), os.path.join(raw_dest, child), no_clobber = no_clobber) 239 else: 240 raise ValueError(f"Source of contents copy is not a dir, fie, or link: '{raw_source}'.") 241 242def read_file( 243 raw_path: str, 244 strip: bool = True, 245 encoding: typing.Union[str, None] = DEFAULT_ENCODING, 246 ) -> str: 247 """ Read the contents of a file. """ 248 249 if (encoding is None): 250 encoding = DEFAULT_ENCODING 251 252 path = os.path.abspath(raw_path) 253 254 if (not exists(path)): 255 raise ValueError(f"Source of read does not exist: '{raw_path}'.") 256 257 with open(path, 'r', encoding = encoding) as file: 258 contents = file.read() 259 260 if (strip): 261 contents = contents.strip() 262 263 return contents 264 265def write_file( 266 raw_path: str, 267 contents: typing.Union[str, None], 268 strip: bool = True, 269 newline: bool = True, 270 encoding: typing.Union[str, None] = DEFAULT_ENCODING, 271 no_clobber: bool = False, 272 ) -> None: 273 """ 274 Write the contents of a file. 275 If clobbering, any existing dirent will be removed before write. 276 """ 277 278 if (encoding is None): 279 encoding = DEFAULT_ENCODING 280 281 path = os.path.abspath(raw_path) 282 283 if (exists(path)): 284 if (no_clobber): 285 raise ValueError(f"Destination of write already exists: '{raw_path}'.") 286 287 remove(path) 288 289 if (contents is None): 290 contents = '' 291 292 if (strip): 293 contents = contents.strip() 294 295 if (newline): 296 contents += "\n" 297 298 with open(path, 'w', encoding = encoding) as file: 299 file.write(contents) 300 301def read_file_bytes(raw_path: str) -> bytes: 302 """ Read the contents of a file as bytes. """ 303 304 path = os.path.abspath(raw_path) 305 306 if (not exists(path)): 307 raise ValueError(f"Source of read bytes does not exist: '{raw_path}'.") 308 309 with open(path, 'rb') as file: 310 return file.read() 311 312def write_file_bytes( 313 raw_path: str, contents: typing.Union[bytes, str, None], 314 no_clobber: bool = False) -> None: 315 """ 316 Write the contents of a file as bytes. 317 If clobbering, any existing dirent will be removed before write. 318 """ 319 320 if (contents is None): 321 contents = b'' 322 323 if (isinstance(contents, str)): 324 contents = contents.encode(DEFAULT_ENCODING) 325 326 path = os.path.abspath(raw_path) 327 328 if (exists(path)): 329 if (no_clobber): 330 raise ValueError(f"Destination of write bytes already exists: '{raw_path}'.") 331 332 remove(path) 333 334 with open(path, 'wb') as file: 335 file.write(contents) 336 337def contains_path(parent: str, child: str) -> bool: 338 """ 339 Check if the parent path contains the child path. 340 This is pure lexical analysis, no dirent stats are checked. 341 Will return false if the (absolute) paths are the same 342 (this function does not allow a path to contain itself). 343 """ 344 345 if ((parent == '') or (child == '')): 346 return False 347 348 parent = os.path.abspath(parent) 349 child = os.path.abspath(child) 350 351 child = os.path.dirname(child) 352 for _ in range(DEPTH_LIMIT): 353 if (parent == child): 354 return True 355 356 new_child = os.path.dirname(child) 357 if (child == new_child): 358 return False 359 360 child = new_child 361 362 raise ValueError("Depth limit reached.") 363 364def hash_file(raw_path: str) -> str: 365 """ 366 Compute the SHA256 hash of the file (see edq.util.hash.sha256_hex()). 367 Links will has their path (according to os.readlink()). 368 Directories will raise an exception. 369 """ 370 371 path = os.path.abspath(raw_path) 372 373 contents: typing.Any = None 374 375 if (not exists(path)): 376 raise ValueError(f"Target of hash file does not exist: '{raw_path}'.") 377 378 if (os.path.islink(path)): 379 contents = os.readlink(path) 380 elif (os.path.isfile(path)): 381 contents = read_file_bytes(raw_path) 382 else: 383 raise ValueError(f"Target of hash file is not a file: '{raw_path}'.") 384 385 return edq.util.hash.sha256_hex(contents) 386 387def tree(raw_path: str, hash_files: bool = False) -> typing.Dict[str, typing.Union[None, str, typing.Dict[str, typing.Any]]]: 388 """ 389 Return a tree structure that includes all descendants of the given dirent (including the dirent itself). 390 If `hash_files` is true, then the value of non-dir keys will be the SHA256 hash of the file (see hash_file()), 391 otherwise the value will be None. 392 """ 393 394 path = os.path.abspath(raw_path) 395 396 if (not exists(path)): 397 raise ValueError(f"Target of tree does not exist: '{raw_path}'.") 398 399 return { 400 os.path.basename(path): _tree(path, hash_files, 0), 401 } 402 403def _tree(path: str, hash_files: bool, level: int) -> typing.Union[str, None, typing.Dict[str, typing.Any]]: 404 """ Recursive helper for tree(). """ 405 406 if (level > DEPTH_LIMIT): 407 raise ValueError("Depth limit reached.") 408 409 if (not os.path.isdir(path)): 410 if (hash_files): 411 return hash_file(path) 412 413 return None 414 415 result = {} 416 for child in sorted(os.listdir(path)): 417 result[child] = _tree(os.path.join(path, child), hash_files, level + 1) 418 419 return result
The default encoding that will be used when reading and writing.
28def exists(path: str) -> bool: 29 """ 30 Check if a path exists. 31 This will transparently call os.path.lexists(), 32 which will include broken links. 33 """ 34 35 return os.path.lexists(path)
Check if a path exists. This will transparently call os.path.lexists(), which will include broken links.
37def get_temp_path(prefix: str = '', suffix: str = '', rm: bool = True) -> str: 38 """ 39 Get a path to a valid (but not currently existing) temp dirent. 40 If rm is True, then the dirent will be attempted to be deleted on exit 41 (no error will occur if the path is not there). 42 """ 43 44 path = None 45 while ((path is None) or exists(path)): 46 path = os.path.join(tempfile.gettempdir(), prefix + str(uuid.uuid4()) + suffix) 47 48 path = os.path.realpath(path) 49 50 if (rm): 51 atexit.register(remove, path) 52 53 return path
Get a path to a valid (but not currently existing) temp dirent. If rm is True, then the dirent will be attempted to be deleted on exit (no error will occur if the path is not there).
55def get_temp_dir(prefix: str = '', suffix: str = '', rm: bool = True) -> str: 56 """ 57 Get a temp directory. 58 The directory will exist when returned. 59 """ 60 61 path = get_temp_path(prefix = prefix, suffix = suffix, rm = rm) 62 mkdir(path) 63 return path
Get a temp directory. The directory will exist when returned.
65def mkdir(raw_path: str) -> None: 66 """ 67 Make a directory (including any required parent directories). 68 Does not complain if the directory (or parents) already exist 69 (this includes if the directory or parents are links to directories). 70 """ 71 72 path = os.path.abspath(raw_path) 73 74 if (exists(path)): 75 if (os.path.isdir(path)): 76 return 77 78 raise ValueError(f"Target of mkdir already exists, and is not a dir: '{raw_path}'.") 79 80 _check_parent_dirs(raw_path) 81 82 os.makedirs(path, exist_ok = True)
Make a directory (including any required parent directories). Does not complain if the directory (or parents) already exist (this includes if the directory or parents are links to directories).
107def remove(path: str) -> None: 108 """ 109 Remove the given path. 110 The path can be of any type (dir, file, link), 111 and does not need to exist. 112 """ 113 114 if (not exists(path)): 115 return 116 117 if (os.path.isfile(path) or os.path.islink(path)): 118 os.remove(path) 119 elif (os.path.isdir(path)): 120 shutil.rmtree(path) 121 else: 122 raise ValueError(f"Unknown type of dirent: '{path}'.")
Remove the given path. The path can be of any type (dir, file, link), and does not need to exist.
124def same(a: str, b: str) -> bool: 125 """ 126 Check if two paths represent the same dirent. 127 If either (or both) paths do not exist, false will be returned. 128 If either paths are links, they are resolved before checking 129 (so a link and the target file are considered the "same"). 130 """ 131 132 return (exists(a) and exists(b) and os.path.samefile(a, b))
Check if two paths represent the same dirent. If either (or both) paths do not exist, false will be returned. If either paths are links, they are resolved before checking (so a link and the target file are considered the "same").
134def move(raw_source: str, raw_dest: str, no_clobber: bool = False) -> None: 135 """ 136 Move the source dirent to the given destination. 137 Any existing destination will be removed before moving. 138 """ 139 140 source = os.path.abspath(raw_source) 141 dest = os.path.abspath(raw_dest) 142 143 if (not exists(source)): 144 raise ValueError(f"Source of move does not exist: '{raw_source}'.") 145 146 # If dest is a dir, then resolve the path. 147 if (os.path.isdir(dest)): 148 dest = os.path.abspath(os.path.join(dest, os.path.basename(source))) 149 150 # Skip if this is self. 151 if (same(source, dest)): 152 return 153 154 # Check for clobber. 155 if (exists(dest)): 156 if (no_clobber): 157 raise ValueError(f"Destination of move already exists: '{raw_dest}'.") 158 159 remove(dest) 160 161 # Create any required parents. 162 os.makedirs(os.path.dirname(dest), exist_ok = True) 163 164 shutil.move(source, dest)
Move the source dirent to the given destination. Any existing destination will be removed before moving.
166def copy(raw_source: str, raw_dest: str, no_clobber: bool = False) -> None: 167 """ 168 Copy a dirent or directory to a destination. 169 170 The destination will be overwritten if it exists (and no_clobber is false). 171 For copying the contents of a directory INTO another directory, use copy_contents(). 172 173 No copy is made if the source and dest refer to the same dirent. 174 """ 175 176 source = os.path.abspath(raw_source) 177 dest = os.path.abspath(raw_dest) 178 179 if (same(source, dest)): 180 return 181 182 if (not exists(source)): 183 raise ValueError(f"Source of copy does not exist: '{raw_source}'.") 184 185 if (contains_path(source, dest)): 186 raise ValueError(f"Source of copy cannot contain the destination. Source: '{raw_source}', Destination: '{raw_dest}'.") 187 188 if (contains_path(dest, source)): 189 raise ValueError(f"Destination of copy cannot contain the source. Destination: '{raw_dest}', Source: '{raw_source}'.") 190 191 if (exists(dest)): 192 if (no_clobber): 193 raise ValueError(f"Destination of copy already exists: '{raw_dest}'.") 194 195 remove(dest) 196 197 mkdir(os.path.dirname(dest)) 198 199 if (os.path.islink(source)): 200 # shutil.copy2() can generally handle (broken) links, but Windows is inconsistent (between 3.11 and 3.12) on link handling. 201 link_target = os.readlink(source) 202 os.symlink(link_target, dest) 203 elif (os.path.isfile(source)): 204 shutil.copy2(source, dest, follow_symlinks = False) 205 elif (os.path.isdir(source)): 206 mkdir(dest) 207 208 for child in sorted(os.listdir(source)): 209 copy(os.path.join(raw_source, child), os.path.join(raw_dest, child)) 210 else: 211 raise ValueError(f"Source of copy is not a dir, fie, or link: '{raw_source}'.")
Copy a dirent or directory to a destination.
The destination will be overwritten if it exists (and no_clobber is false). For copying the contents of a directory INTO another directory, use copy_contents().
No copy is made if the source and dest refer to the same dirent.
213def copy_contents(raw_source: str, raw_dest: str, no_clobber: bool = False) -> None: 214 """ 215 Copy a file or the contents of a directory (excluding the top-level directory itself) into a destination. 216 If the destination exists, it must be a directory. 217 218 The source and destination should not be the same file. 219 220 For a file, this is equivalent to `mkdir -p dest && cp source dest` 221 For a dir, this is equivalent to `mkdir -p dest && cp -r source/* dest` 222 """ 223 224 source = os.path.abspath(raw_source) 225 dest = os.path.abspath(raw_dest) 226 227 if (same(source, dest)): 228 raise ValueError(f"Source and destination of contents copy cannot be the same: '{raw_source}'.") 229 230 if (exists(dest) and (not os.path.isdir(dest))): 231 raise ValueError(f"Destination of contents copy exists and is not a dir: '{raw_dest}'.") 232 233 mkdir(dest) 234 235 if (os.path.isfile(source) or os.path.islink(source)): 236 copy(source, os.path.join(dest, os.path.basename(source)), no_clobber = no_clobber) 237 elif (os.path.isdir(source)): 238 for child in sorted(os.listdir(source)): 239 copy(os.path.join(raw_source, child), os.path.join(raw_dest, child), no_clobber = no_clobber) 240 else: 241 raise ValueError(f"Source of contents copy is not a dir, fie, or link: '{raw_source}'.")
Copy a file or the contents of a directory (excluding the top-level directory itself) into a destination. If the destination exists, it must be a directory.
The source and destination should not be the same file.
For a file, this is equivalent to mkdir -p dest && cp source dest
For a dir, this is equivalent to mkdir -p dest && cp -r source/* dest
243def read_file( 244 raw_path: str, 245 strip: bool = True, 246 encoding: typing.Union[str, None] = DEFAULT_ENCODING, 247 ) -> str: 248 """ Read the contents of a file. """ 249 250 if (encoding is None): 251 encoding = DEFAULT_ENCODING 252 253 path = os.path.abspath(raw_path) 254 255 if (not exists(path)): 256 raise ValueError(f"Source of read does not exist: '{raw_path}'.") 257 258 with open(path, 'r', encoding = encoding) as file: 259 contents = file.read() 260 261 if (strip): 262 contents = contents.strip() 263 264 return contents
Read the contents of a file.
266def write_file( 267 raw_path: str, 268 contents: typing.Union[str, None], 269 strip: bool = True, 270 newline: bool = True, 271 encoding: typing.Union[str, None] = DEFAULT_ENCODING, 272 no_clobber: bool = False, 273 ) -> None: 274 """ 275 Write the contents of a file. 276 If clobbering, any existing dirent will be removed before write. 277 """ 278 279 if (encoding is None): 280 encoding = DEFAULT_ENCODING 281 282 path = os.path.abspath(raw_path) 283 284 if (exists(path)): 285 if (no_clobber): 286 raise ValueError(f"Destination of write already exists: '{raw_path}'.") 287 288 remove(path) 289 290 if (contents is None): 291 contents = '' 292 293 if (strip): 294 contents = contents.strip() 295 296 if (newline): 297 contents += "\n" 298 299 with open(path, 'w', encoding = encoding) as file: 300 file.write(contents)
Write the contents of a file. If clobbering, any existing dirent will be removed before write.
302def read_file_bytes(raw_path: str) -> bytes: 303 """ Read the contents of a file as bytes. """ 304 305 path = os.path.abspath(raw_path) 306 307 if (not exists(path)): 308 raise ValueError(f"Source of read bytes does not exist: '{raw_path}'.") 309 310 with open(path, 'rb') as file: 311 return file.read()
Read the contents of a file as bytes.
313def write_file_bytes( 314 raw_path: str, contents: typing.Union[bytes, str, None], 315 no_clobber: bool = False) -> None: 316 """ 317 Write the contents of a file as bytes. 318 If clobbering, any existing dirent will be removed before write. 319 """ 320 321 if (contents is None): 322 contents = b'' 323 324 if (isinstance(contents, str)): 325 contents = contents.encode(DEFAULT_ENCODING) 326 327 path = os.path.abspath(raw_path) 328 329 if (exists(path)): 330 if (no_clobber): 331 raise ValueError(f"Destination of write bytes already exists: '{raw_path}'.") 332 333 remove(path) 334 335 with open(path, 'wb') as file: 336 file.write(contents)
Write the contents of a file as bytes. If clobbering, any existing dirent will be removed before write.
338def contains_path(parent: str, child: str) -> bool: 339 """ 340 Check if the parent path contains the child path. 341 This is pure lexical analysis, no dirent stats are checked. 342 Will return false if the (absolute) paths are the same 343 (this function does not allow a path to contain itself). 344 """ 345 346 if ((parent == '') or (child == '')): 347 return False 348 349 parent = os.path.abspath(parent) 350 child = os.path.abspath(child) 351 352 child = os.path.dirname(child) 353 for _ in range(DEPTH_LIMIT): 354 if (parent == child): 355 return True 356 357 new_child = os.path.dirname(child) 358 if (child == new_child): 359 return False 360 361 child = new_child 362 363 raise ValueError("Depth limit reached.")
Check if the parent path contains the child path. This is pure lexical analysis, no dirent stats are checked. Will return false if the (absolute) paths are the same (this function does not allow a path to contain itself).
365def hash_file(raw_path: str) -> str: 366 """ 367 Compute the SHA256 hash of the file (see edq.util.hash.sha256_hex()). 368 Links will has their path (according to os.readlink()). 369 Directories will raise an exception. 370 """ 371 372 path = os.path.abspath(raw_path) 373 374 contents: typing.Any = None 375 376 if (not exists(path)): 377 raise ValueError(f"Target of hash file does not exist: '{raw_path}'.") 378 379 if (os.path.islink(path)): 380 contents = os.readlink(path) 381 elif (os.path.isfile(path)): 382 contents = read_file_bytes(raw_path) 383 else: 384 raise ValueError(f"Target of hash file is not a file: '{raw_path}'.") 385 386 return edq.util.hash.sha256_hex(contents)
Compute the SHA256 hash of the file (see edq.util.hash.sha256_hex()). Links will has their path (according to os.readlink()). Directories will raise an exception.
388def tree(raw_path: str, hash_files: bool = False) -> typing.Dict[str, typing.Union[None, str, typing.Dict[str, typing.Any]]]: 389 """ 390 Return a tree structure that includes all descendants of the given dirent (including the dirent itself). 391 If `hash_files` is true, then the value of non-dir keys will be the SHA256 hash of the file (see hash_file()), 392 otherwise the value will be None. 393 """ 394 395 path = os.path.abspath(raw_path) 396 397 if (not exists(path)): 398 raise ValueError(f"Target of tree does not exist: '{raw_path}'.") 399 400 return { 401 os.path.basename(path): _tree(path, hash_files, 0), 402 }
Return a tree structure that includes all descendants of the given dirent (including the dirent itself).
If hash_files is true, then the value of non-dir keys will be the SHA256 hash of the file (see hash_file()),
otherwise the value will be None.