pathier
16class Pathier(pathlib.Path): 17 """Subclasses the standard library pathlib.Path class.""" 18 19 def __new__(cls, *args, **kwargs): 20 if cls is Pathier: 21 cls = WindowsPath if os.name == "nt" else PosixPath 22 self = cls._from_parts(args) 23 if not self._flavour.is_supported: 24 raise NotImplementedError( 25 "cannot instantiate %r on your system" % (cls.__name__,) 26 ) 27 return self 28 29 # ===============================================stats=============================================== 30 @property 31 def dob(self) -> datetime.datetime | None: 32 """Returns the creation date of this file or directory as a `dateime.datetime` object.""" 33 if self.exists(): 34 return datetime.datetime.fromtimestamp(self.stat().st_ctime) 35 else: 36 return None 37 38 @property 39 def age(self) -> float | None: 40 """Returns the age in seconds of this file or directory.""" 41 if self.exists(): 42 return (datetime.datetime.now() - self.dob).total_seconds() 43 else: 44 return None 45 46 @property 47 def mod_date(self) -> datetime.datetime | None: 48 """Returns the modification date of this file or directory as a `datetime.datetime` object.""" 49 if self.exists(): 50 return datetime.datetime.fromtimestamp(self.stat().st_mtime) 51 else: 52 return None 53 54 @property 55 def mod_delta(self) -> float | None: 56 """Returns how long ago in seconds this file or directory was modified.""" 57 if self.exists(): 58 return (datetime.datetime.now() - self.mod_date).total_seconds() 59 else: 60 return None 61 62 @property 63 def last_read_time(self) -> datetime.datetime | None: 64 """Returns the last time this object made a call to `self.read_text()`, `self.read_bytes()`, or `self.open(mode="r"|"rb")`. 65 Returns `None` if the file hasn't been read from. 66 67 Note: This property is only relative to the lifetime of this `Pathier` instance, not the file itself. 68 i.e. This property will reset if you create a new `Pathier` object pointing to the same file.""" 69 if self._last_read_time: 70 return datetime.datetime.fromtimestamp(self._last_read_time) 71 else: 72 return self._last_read_time 73 74 @property 75 def modified_since_last_read(self) -> bool: 76 """Returns `True` if this file hasn't been read from or has been modified since the last time this object 77 made a call to `self.read_text()`, `self.read_bytes()`, or `self.open(mode="r"|"rb")`. 78 79 Note: This property is only relative to the lifetime of this `Pathier` instance, not the file itself. 80 i.e. This property will reset if you create a new `Pathier` object pointing to the same file. 81 82 #### Caveat: 83 May not be accurate if the file was modified within a couple of seconds of checking this property. 84 (For instance, on my machine `self.mod_date` is consistently 1-1.5s in the future from when `self.write_text()` was called according to `time.time()`.) 85 """ 86 87 return self.last_read_time is None or self.mod_date > self.last_read_time 88 89 def size(self, format: bool = False) -> int | str | None: 90 """Returns the size in bytes of this file or directory. 91 Returns `None` if this path doesn't exist. 92 93 #### :params: 94 95 `format`: If `True`, return value as a formatted string.""" 96 if not self.exists(): 97 return None 98 if self.is_file(): 99 size = self.stat().st_size 100 if self.is_dir(): 101 size = sum(file.stat().st_size for file in self.rglob("*.*")) 102 if format: 103 return self.format_size(size) 104 return size 105 106 @staticmethod 107 def format_size(size: int) -> str: 108 """Format `size` with common file size abbreviations and rounded to two decimal places. 109 >>> 1234 -> "1.23 kb" """ 110 for unit in ["bytes", "kb", "mb", "gb", "tb", "pb"]: 111 if unit != "bytes": 112 size *= 0.001 113 if size < 1000 or unit == "pb": 114 return f"{round(size, 2)} {unit}" 115 116 def is_larger(self, path: Self) -> bool: 117 """Returns whether this file or folder is larger than the one pointed to by `path`.""" 118 return self.size() > path.size() 119 120 def is_older(self, path: Self) -> bool: 121 """Returns whether this file or folder is older than the one pointed to by `path`.""" 122 return self.dob < path.dob 123 124 def modified_more_recently(self, path: Self) -> bool: 125 """Returns whether this file or folder was modified more recently than the one pointed to by `path`.""" 126 return self.mod_date > path.mod_date 127 128 # ===============================================navigation=============================================== 129 def mkcwd(self): 130 """Make this path your current working directory.""" 131 os.chdir(self) 132 133 @property 134 def in_PATH(self) -> bool: 135 """Return `True` if this path is in `sys.path`.""" 136 return str(self) in sys.path 137 138 def add_to_PATH(self, index: int = 0): 139 """Insert this path into `sys.path` if it isn't already there. 140 141 #### :params: 142 143 `index`: The index of `sys.path` to insert this path at.""" 144 path = str(self) 145 if not self.in_PATH: 146 sys.path.insert(index, path) 147 148 def append_to_PATH(self): 149 """Append this path to `sys.path` if it isn't already there.""" 150 path = str(self) 151 if not self.in_PATH: 152 sys.path.append(path) 153 154 def remove_from_PATH(self): 155 """Remove this path from `sys.path` if it's in `sys.path`.""" 156 if self.in_PATH: 157 sys.path.remove(str(self)) 158 159 def moveup(self, name: str) -> Self: 160 """Return a new `Pathier` object that is a parent of this instance. 161 162 `name` is case-sensitive and raises an exception if it isn't in `self.parts`. 163 >>> p = Pathier("C:\some\directory\in\your\system") 164 >>> print(p.moveup("directory")) 165 >>> "C:\some\directory" 166 >>> print(p.moveup("yeet")) 167 >>> "Exception: yeet is not a parent of C:\some\directory\in\your\system" """ 168 if name not in self.parts: 169 raise Exception(f"{name} is not a parent of {self}") 170 return Pathier(*(self.parts[: self.parts.index(name) + 1])) 171 172 def __sub__(self, levels: int) -> Self: 173 """Return a new `Pathier` object moved up `levels` number of parents from the current path. 174 >>> p = Pathier("C:\some\directory\in\your\system") 175 >>> new_p = p - 3 176 >>> print(new_p) 177 >>> "C:\some\directory" """ 178 path = self 179 for _ in range(levels): 180 path = path.parent 181 return path 182 183 def move_under(self, name: str) -> Self: 184 """Return a new `Pathier` object such that the stem is one level below the given folder `name`. 185 186 `name` is case-sensitive and raises an exception if it isn't in `self.parts`. 187 >>> p = Pathier("a/b/c/d/e/f/g") 188 >>> print(p.move_under("c")) 189 >>> 'a/b/c/d'""" 190 if name not in self.parts: 191 raise Exception(f"{name} is not a parent of {self}") 192 return self - (len(self.parts) - self.parts.index(name) - 2) 193 194 def separate(self, name: str, keep_name: bool = False) -> Self: 195 """Return a new `Pathier` object that is the relative child path after `name`. 196 197 `name` is case-sensitive and raises an exception if it isn't in `self.parts`. 198 199 #### :params: 200 201 `keep_name`: If `True`, the returned path will start with `name`. 202 >>> p = Pathier("a/b/c/d/e/f/g") 203 >>> print(p.separate("c")) 204 >>> 'd/e/f/g' 205 >>> print(p.separate("c", True)) 206 >>> 'c/d/e/f/g'""" 207 if name not in self.parts: 208 raise Exception(f"{name} is not a parent of {self}") 209 if keep_name: 210 return Pathier(*self.parts[self.parts.index(name) :]) 211 return Pathier(*self.parts[self.parts.index(name) + 1 :]) 212 213 # ============================================write and read============================================ 214 def mkdir(self, mode: int = 511, parents: bool = True, exist_ok: bool = True): 215 """Create this directory. 216 217 Same as `Path().mkdir()` except `parents` and `exist_ok` default to `True` instead of `False`.""" 218 super().mkdir(mode, parents, exist_ok) 219 220 def touch(self): 221 """Create file (and parents if necessary).""" 222 self.parent.mkdir() 223 super().touch() 224 225 def open(self, mode="r", buffering=-1, encoding=None, errors=None, newline=None): 226 """ 227 Open the file pointed by this path and return a file object, as 228 the built-in open() function does. 229 """ 230 stream = super().open(mode, buffering, encoding, errors, newline) 231 if "r" in mode: 232 self._last_read_time = time.time() 233 return stream 234 235 def write_text( 236 self, 237 data: Any, 238 encoding: Any | None = None, 239 errors: Any | None = None, 240 newline: Any | None = None, 241 parents: bool = True, 242 ): 243 """Write data to file. 244 245 If a `TypeError` is raised, the function will attempt to cast `data` to a `str` and try the write again. 246 247 If a `FileNotFoundError` is raised and `parents = True`, `self.parent` will be created.""" 248 write = functools.partial( 249 super().write_text, 250 encoding=encoding, 251 errors=errors, 252 newline=newline, 253 ) 254 try: 255 write(data) 256 except TypeError: 257 data = str(data) 258 write(data) 259 except FileNotFoundError: 260 if parents: 261 self.parent.mkdir(parents=True) 262 write(data) 263 else: 264 raise 265 except Exception as e: 266 raise 267 268 def write_bytes(self, data: bytes, parents: bool = True): 269 """Write bytes to file. 270 271 #### :params: 272 273 `parents`: If `True` and the write operation fails with a `FileNotFoundError`, 274 make the parent directory and retry the write.""" 275 try: 276 super().write_bytes(data) 277 except FileNotFoundError: 278 if parents: 279 self.parent.mkdir(parents=True) 280 super().write_bytes(data) 281 else: 282 raise 283 except Exception as e: 284 raise 285 286 def append(self, data: str, new_line: bool = True, encoding: Any | None = None): 287 """Append `data` to the file pointed to by this `Pathier` object. 288 289 #### :params: 290 291 `new_line`: If `True`, add `\\n` to `data`. 292 293 `encoding`: The file encoding to use.""" 294 if new_line: 295 data += "\n" 296 with self.open("a", encoding=encoding) as file: 297 file.write(data) 298 299 def replace( 300 self, 301 old_string: str, 302 new_string: str, 303 count: int = -1, 304 encoding: Any | None = None, 305 ): 306 """Replace `old_string` in a file with `new_string`. 307 308 #### :params: 309 310 `count`: Only replace this many occurences of `old_string`. 311 By default (`-1`), all occurences are replaced. 312 313 `encoding`: The file encoding to use. 314 315 e.g. 316 >>> path = Pathier("somefile.txt") 317 >>> 318 >>> path.replace("hello", "yeet") 319 equivalent to 320 >>> path.write_text(path.read_text().replace("hello", "yeet"))""" 321 self.write_text( 322 self.read_text(encoding).replace(old_string, new_string, count), 323 encoding=encoding, 324 ) 325 326 def join(self, data: list[str], encoding: Any | None = None, sep: str = "\n"): 327 """Write a list of strings, joined by `sep`, to the file pointed at by this instance. 328 329 Equivalent to `Pathier("somefile.txt").write_text(sep.join(data), encoding=encoding)` 330 331 #### :params: 332 333 `encoding`: The file encoding to use. 334 335 `sep`: The separator to use when joining `data`.""" 336 self.write_text(sep.join(data), encoding=encoding) 337 338 def split(self, encoding: Any | None = None, keepends: bool = False) -> list[str]: 339 """Returns the content of the pointed at file as a list of strings, splitting at new line characters. 340 341 Equivalent to `Pathier("somefile.txt").read_text(encoding=encoding).splitlines()` 342 343 #### :params: 344 345 `encoding`: The file encoding to use. 346 347 `keepend`: If `True`, line breaks will be included in returned strings.""" 348 return self.read_text(encoding=encoding).splitlines(keepends) 349 350 def json_loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 351 """Load json file.""" 352 return json.loads(self.read_text(encoding, errors)) 353 354 def json_dumps( 355 self, 356 data: Any, 357 encoding: Any | None = None, 358 errors: Any | None = None, 359 newline: Any | None = None, 360 sort_keys: bool = False, 361 indent: Any | None = None, 362 default: Any | None = None, 363 parents: bool = True, 364 ) -> Any: 365 """Dump `data` to json file.""" 366 self.write_text( 367 json.dumps(data, indent=indent, default=default, sort_keys=sort_keys), 368 encoding, 369 errors, 370 newline, 371 parents, 372 ) 373 374 def toml_loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 375 """Load toml file.""" 376 return tomlkit.loads(self.read_text(encoding, errors)) 377 378 def toml_dumps( 379 self, 380 data: Any, 381 encoding: Any | None = None, 382 errors: Any | None = None, 383 newline: Any | None = None, 384 sort_keys: bool = False, 385 parents: bool = True, 386 ): 387 """Dump `data` to toml file.""" 388 self.write_text( 389 tomlkit.dumps(data, sort_keys), encoding, errors, newline, parents 390 ) 391 392 def loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 393 """Load a json or toml file based off this instance's suffix.""" 394 match self.suffix: 395 case ".json": 396 return self.json_loads(encoding, errors) 397 case ".toml": 398 return self.toml_loads(encoding, errors) 399 400 def dumps( 401 self, 402 data: Any, 403 encoding: Any | None = None, 404 errors: Any | None = None, 405 newline: Any | None = None, 406 sort_keys: bool = False, 407 indent: Any | None = None, 408 default: Any | None = None, 409 parents: bool = True, 410 ): 411 """Dump `data` to a json or toml file based off this instance's suffix.""" 412 match self.suffix: 413 case ".json": 414 self.json_dumps( 415 data, encoding, errors, newline, sort_keys, indent, default, parents 416 ) 417 case ".toml": 418 self.toml_dumps(data, encoding, errors, newline, sort_keys, parents) 419 420 def delete(self, missing_ok: bool = True): 421 """Delete the file or folder pointed to by this instance. 422 423 Uses `self.unlink()` if a file and uses `shutil.rmtree()` if a directory.""" 424 if self.is_file(): 425 self.unlink(missing_ok) 426 elif self.is_dir(): 427 shutil.rmtree(self) 428 429 def copy( 430 self, new_path: Self | pathlib.Path | str, overwrite: bool = False 431 ) -> Self: 432 """Copy the path pointed to by this instance 433 to the instance pointed to by `new_path` using `shutil.copyfile` 434 or `shutil.copytree`. 435 436 Returns the new path. 437 438 #### :params: 439 440 `new_path`: The copy destination. 441 442 `overwrite`: If `True`, files already existing in `new_path` will be overwritten. 443 If `False`, only files that don't exist in `new_path` will be copied.""" 444 new_path = Pathier(new_path) 445 if self.is_dir(): 446 if overwrite or not new_path.exists(): 447 shutil.copytree(self, new_path, dirs_exist_ok=True) 448 else: 449 files = self.rglob("*.*") 450 for file in files: 451 dst = new_path.with_name(file.name) 452 if not dst.exists(): 453 shutil.copyfile(file, dst) 454 elif self.is_file(): 455 if overwrite or not new_path.exists(): 456 shutil.copyfile(self, new_path) 457 return new_path 458 459 def backup(self, timestamp: bool = False) -> Self | None: 460 """Create a copy of this file or directory with `_backup` appended to the path stem. 461 If the path to be backed up doesn't exist, `None` is returned. 462 Otherwise a `Pathier` object for the backup is returned. 463 464 #### :params: 465 466 `timestamp`: Add a timestamp to the backup name to prevent overriding previous backups. 467 468 >>> path = Pathier("some_file.txt") 469 >>> path.backup() 470 >>> list(path.iterdir()) 471 >>> ['some_file.txt', 'some_file_backup.txt'] 472 >>> path.backup(True) 473 >>> list(path.iterdir()) 474 >>> ['some_file.txt', 'some_file_backup.txt', 'some_file_backup_04-28-2023-06_25_52_PM.txt']""" 475 if not self.exists(): 476 return None 477 backup_stem = f"{self.stem}_backup" 478 if timestamp: 479 backup_stem = f"{backup_stem}_{datetime.datetime.now().strftime('%m-%d-%Y-%I_%M_%S_%p')}" 480 backup_path = self.with_stem(backup_stem) 481 self.copy(backup_path, True) 482 return backup_path 483 484 def execute(self, command: str = "", args: str = "") -> int: 485 """Make a call to `os.system` using the path pointed to by this Pathier object. 486 487 #### :params: 488 489 `command`: Program/command to precede the path with. 490 491 `args`: Any arguments that should come after the path. 492 493 :returns: The integer output of `os.system`. 494 495 e.g. 496 >>> path = Pathier("mydirectory") / "myscript.py" 497 then 498 >>> path.execute("py", "--iterations 10") 499 equivalent to 500 >>> os.system(f"py {path} --iterations 10")""" 501 return os.system(f"{command} {self} {args}")
Subclasses the standard library pathlib.Path class.
Returns the creation date of this file or directory as a dateime.datetime object.
Returns the modification date of this file or directory as a datetime.datetime object.
Returns the last time this object made a call to self.read_text(), self.read_bytes(), or self.open(mode="r"|"rb").
Returns None if the file hasn't been read from.
Note: This property is only relative to the lifetime of this Pathier instance, not the file itself.
i.e. This property will reset if you create a new Pathier object pointing to the same file.
Returns True if this file hasn't been read from or has been modified since the last time this object
made a call to self.read_text(), self.read_bytes(), or self.open(mode="r"|"rb").
Note: This property is only relative to the lifetime of this Pathier instance, not the file itself.
i.e. This property will reset if you create a new Pathier object pointing to the same file.
Caveat:
May not be accurate if the file was modified within a couple of seconds of checking this property.
(For instance, on my machine self.mod_date is consistently 1-1.5s in the future from when self.write_text() was called according to time.time().)
89 def size(self, format: bool = False) -> int | str | None: 90 """Returns the size in bytes of this file or directory. 91 Returns `None` if this path doesn't exist. 92 93 #### :params: 94 95 `format`: If `True`, return value as a formatted string.""" 96 if not self.exists(): 97 return None 98 if self.is_file(): 99 size = self.stat().st_size 100 if self.is_dir(): 101 size = sum(file.stat().st_size for file in self.rglob("*.*")) 102 if format: 103 return self.format_size(size) 104 return size
Returns the size in bytes of this file or directory.
Returns None if this path doesn't exist.
:params:
format: If True, return value as a formatted string.
106 @staticmethod 107 def format_size(size: int) -> str: 108 """Format `size` with common file size abbreviations and rounded to two decimal places. 109 >>> 1234 -> "1.23 kb" """ 110 for unit in ["bytes", "kb", "mb", "gb", "tb", "pb"]: 111 if unit != "bytes": 112 size *= 0.001 113 if size < 1000 or unit == "pb": 114 return f"{round(size, 2)} {unit}"
Format size with common file size abbreviations and rounded to two decimal places.
>>> 1234 -> "1.23 kb"
116 def is_larger(self, path: Self) -> bool: 117 """Returns whether this file or folder is larger than the one pointed to by `path`.""" 118 return self.size() > path.size()
Returns whether this file or folder is larger than the one pointed to by path.
120 def is_older(self, path: Self) -> bool: 121 """Returns whether this file or folder is older than the one pointed to by `path`.""" 122 return self.dob < path.dob
Returns whether this file or folder is older than the one pointed to by path.
124 def modified_more_recently(self, path: Self) -> bool: 125 """Returns whether this file or folder was modified more recently than the one pointed to by `path`.""" 126 return self.mod_date > path.mod_date
Returns whether this file or folder was modified more recently than the one pointed to by path.
138 def add_to_PATH(self, index: int = 0): 139 """Insert this path into `sys.path` if it isn't already there. 140 141 #### :params: 142 143 `index`: The index of `sys.path` to insert this path at.""" 144 path = str(self) 145 if not self.in_PATH: 146 sys.path.insert(index, path)
Insert this path into sys.path if it isn't already there.
:params:
index: The index of sys.path to insert this path at.
148 def append_to_PATH(self): 149 """Append this path to `sys.path` if it isn't already there.""" 150 path = str(self) 151 if not self.in_PATH: 152 sys.path.append(path)
Append this path to sys.path if it isn't already there.
154 def remove_from_PATH(self): 155 """Remove this path from `sys.path` if it's in `sys.path`.""" 156 if self.in_PATH: 157 sys.path.remove(str(self))
Remove this path from sys.path if it's in sys.path.
159 def moveup(self, name: str) -> Self: 160 """Return a new `Pathier` object that is a parent of this instance. 161 162 `name` is case-sensitive and raises an exception if it isn't in `self.parts`. 163 >>> p = Pathier("C:\some\directory\in\your\system") 164 >>> print(p.moveup("directory")) 165 >>> "C:\some\directory" 166 >>> print(p.moveup("yeet")) 167 >>> "Exception: yeet is not a parent of C:\some\directory\in\your\system" """ 168 if name not in self.parts: 169 raise Exception(f"{name} is not a parent of {self}") 170 return Pathier(*(self.parts[: self.parts.index(name) + 1]))
Return a new Pathier object that is a parent of this instance.
name is case-sensitive and raises an exception if it isn't in self.parts.
>>> p = Pathier("C:\some\directory\in\your\system")
>>> print(p.moveup("directory"))
>>> "C:\some\directory"
>>> print(p.moveup("yeet"))
>>> "Exception: yeet is not a parent of C:\some\directory\in\your\system"
183 def move_under(self, name: str) -> Self: 184 """Return a new `Pathier` object such that the stem is one level below the given folder `name`. 185 186 `name` is case-sensitive and raises an exception if it isn't in `self.parts`. 187 >>> p = Pathier("a/b/c/d/e/f/g") 188 >>> print(p.move_under("c")) 189 >>> 'a/b/c/d'""" 190 if name not in self.parts: 191 raise Exception(f"{name} is not a parent of {self}") 192 return self - (len(self.parts) - self.parts.index(name) - 2)
194 def separate(self, name: str, keep_name: bool = False) -> Self: 195 """Return a new `Pathier` object that is the relative child path after `name`. 196 197 `name` is case-sensitive and raises an exception if it isn't in `self.parts`. 198 199 #### :params: 200 201 `keep_name`: If `True`, the returned path will start with `name`. 202 >>> p = Pathier("a/b/c/d/e/f/g") 203 >>> print(p.separate("c")) 204 >>> 'd/e/f/g' 205 >>> print(p.separate("c", True)) 206 >>> 'c/d/e/f/g'""" 207 if name not in self.parts: 208 raise Exception(f"{name} is not a parent of {self}") 209 if keep_name: 210 return Pathier(*self.parts[self.parts.index(name) :]) 211 return Pathier(*self.parts[self.parts.index(name) + 1 :])
Return a new Pathier object that is the relative child path after name.
name is case-sensitive and raises an exception if it isn't in self.parts.
:params:
keep_name: If True, the returned path will start with name.
>>> p = Pathier("a/b/c/d/e/f/g")
>>> print(p.separate("c"))
>>> 'd/e/f/g'
>>> print(p.separate("c", True))
>>> 'c/d/e/f/g'
214 def mkdir(self, mode: int = 511, parents: bool = True, exist_ok: bool = True): 215 """Create this directory. 216 217 Same as `Path().mkdir()` except `parents` and `exist_ok` default to `True` instead of `False`.""" 218 super().mkdir(mode, parents, exist_ok)
Create this directory.
Same as Path().mkdir() except parents and exist_ok default to True instead of False.
220 def touch(self): 221 """Create file (and parents if necessary).""" 222 self.parent.mkdir() 223 super().touch()
Create file (and parents if necessary).
225 def open(self, mode="r", buffering=-1, encoding=None, errors=None, newline=None): 226 """ 227 Open the file pointed by this path and return a file object, as 228 the built-in open() function does. 229 """ 230 stream = super().open(mode, buffering, encoding, errors, newline) 231 if "r" in mode: 232 self._last_read_time = time.time() 233 return stream
Open the file pointed by this path and return a file object, as the built-in open() function does.
235 def write_text( 236 self, 237 data: Any, 238 encoding: Any | None = None, 239 errors: Any | None = None, 240 newline: Any | None = None, 241 parents: bool = True, 242 ): 243 """Write data to file. 244 245 If a `TypeError` is raised, the function will attempt to cast `data` to a `str` and try the write again. 246 247 If a `FileNotFoundError` is raised and `parents = True`, `self.parent` will be created.""" 248 write = functools.partial( 249 super().write_text, 250 encoding=encoding, 251 errors=errors, 252 newline=newline, 253 ) 254 try: 255 write(data) 256 except TypeError: 257 data = str(data) 258 write(data) 259 except FileNotFoundError: 260 if parents: 261 self.parent.mkdir(parents=True) 262 write(data) 263 else: 264 raise 265 except Exception as e: 266 raise
Write data to file.
If a TypeError is raised, the function will attempt to cast data to a str and try the write again.
If a FileNotFoundError is raised and parents = True, self.parent will be created.
268 def write_bytes(self, data: bytes, parents: bool = True): 269 """Write bytes to file. 270 271 #### :params: 272 273 `parents`: If `True` and the write operation fails with a `FileNotFoundError`, 274 make the parent directory and retry the write.""" 275 try: 276 super().write_bytes(data) 277 except FileNotFoundError: 278 if parents: 279 self.parent.mkdir(parents=True) 280 super().write_bytes(data) 281 else: 282 raise 283 except Exception as e: 284 raise
Write bytes to file.
:params:
parents: If True and the write operation fails with a FileNotFoundError,
make the parent directory and retry the write.
286 def append(self, data: str, new_line: bool = True, encoding: Any | None = None): 287 """Append `data` to the file pointed to by this `Pathier` object. 288 289 #### :params: 290 291 `new_line`: If `True`, add `\\n` to `data`. 292 293 `encoding`: The file encoding to use.""" 294 if new_line: 295 data += "\n" 296 with self.open("a", encoding=encoding) as file: 297 file.write(data)
Append data to the file pointed to by this Pathier object.
:params:
new_line: If True, add \n to data.
encoding: The file encoding to use.
299 def replace( 300 self, 301 old_string: str, 302 new_string: str, 303 count: int = -1, 304 encoding: Any | None = None, 305 ): 306 """Replace `old_string` in a file with `new_string`. 307 308 #### :params: 309 310 `count`: Only replace this many occurences of `old_string`. 311 By default (`-1`), all occurences are replaced. 312 313 `encoding`: The file encoding to use. 314 315 e.g. 316 >>> path = Pathier("somefile.txt") 317 >>> 318 >>> path.replace("hello", "yeet") 319 equivalent to 320 >>> path.write_text(path.read_text().replace("hello", "yeet"))""" 321 self.write_text( 322 self.read_text(encoding).replace(old_string, new_string, count), 323 encoding=encoding, 324 )
Replace old_string in a file with new_string.
:params:
count: Only replace this many occurences of old_string.
By default (-1), all occurences are replaced.
encoding: The file encoding to use.
e.g.
>>> path = Pathier("somefile.txt")
>>>
>>> path.replace("hello", "yeet")
equivalent to
>>> path.write_text(path.read_text().replace("hello", "yeet"))
326 def join(self, data: list[str], encoding: Any | None = None, sep: str = "\n"): 327 """Write a list of strings, joined by `sep`, to the file pointed at by this instance. 328 329 Equivalent to `Pathier("somefile.txt").write_text(sep.join(data), encoding=encoding)` 330 331 #### :params: 332 333 `encoding`: The file encoding to use. 334 335 `sep`: The separator to use when joining `data`.""" 336 self.write_text(sep.join(data), encoding=encoding)
Write a list of strings, joined by sep, to the file pointed at by this instance.
Equivalent to Pathier("somefile.txt").write_text(sep.join(data), encoding=encoding)
:params:
encoding: The file encoding to use.
sep: The separator to use when joining data.
338 def split(self, encoding: Any | None = None, keepends: bool = False) -> list[str]: 339 """Returns the content of the pointed at file as a list of strings, splitting at new line characters. 340 341 Equivalent to `Pathier("somefile.txt").read_text(encoding=encoding).splitlines()` 342 343 #### :params: 344 345 `encoding`: The file encoding to use. 346 347 `keepend`: If `True`, line breaks will be included in returned strings.""" 348 return self.read_text(encoding=encoding).splitlines(keepends)
Returns the content of the pointed at file as a list of strings, splitting at new line characters.
Equivalent to Pathier("somefile.txt").read_text(encoding=encoding).splitlines()
:params:
encoding: The file encoding to use.
keepend: If True, line breaks will be included in returned strings.
350 def json_loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 351 """Load json file.""" 352 return json.loads(self.read_text(encoding, errors))
Load json file.
354 def json_dumps( 355 self, 356 data: Any, 357 encoding: Any | None = None, 358 errors: Any | None = None, 359 newline: Any | None = None, 360 sort_keys: bool = False, 361 indent: Any | None = None, 362 default: Any | None = None, 363 parents: bool = True, 364 ) -> Any: 365 """Dump `data` to json file.""" 366 self.write_text( 367 json.dumps(data, indent=indent, default=default, sort_keys=sort_keys), 368 encoding, 369 errors, 370 newline, 371 parents, 372 )
Dump data to json file.
374 def toml_loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 375 """Load toml file.""" 376 return tomlkit.loads(self.read_text(encoding, errors))
Load toml file.
378 def toml_dumps( 379 self, 380 data: Any, 381 encoding: Any | None = None, 382 errors: Any | None = None, 383 newline: Any | None = None, 384 sort_keys: bool = False, 385 parents: bool = True, 386 ): 387 """Dump `data` to toml file.""" 388 self.write_text( 389 tomlkit.dumps(data, sort_keys), encoding, errors, newline, parents 390 )
Dump data to toml file.
392 def loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 393 """Load a json or toml file based off this instance's suffix.""" 394 match self.suffix: 395 case ".json": 396 return self.json_loads(encoding, errors) 397 case ".toml": 398 return self.toml_loads(encoding, errors)
Load a json or toml file based off this instance's suffix.
400 def dumps( 401 self, 402 data: Any, 403 encoding: Any | None = None, 404 errors: Any | None = None, 405 newline: Any | None = None, 406 sort_keys: bool = False, 407 indent: Any | None = None, 408 default: Any | None = None, 409 parents: bool = True, 410 ): 411 """Dump `data` to a json or toml file based off this instance's suffix.""" 412 match self.suffix: 413 case ".json": 414 self.json_dumps( 415 data, encoding, errors, newline, sort_keys, indent, default, parents 416 ) 417 case ".toml": 418 self.toml_dumps(data, encoding, errors, newline, sort_keys, parents)
Dump data to a json or toml file based off this instance's suffix.
420 def delete(self, missing_ok: bool = True): 421 """Delete the file or folder pointed to by this instance. 422 423 Uses `self.unlink()` if a file and uses `shutil.rmtree()` if a directory.""" 424 if self.is_file(): 425 self.unlink(missing_ok) 426 elif self.is_dir(): 427 shutil.rmtree(self)
Delete the file or folder pointed to by this instance.
Uses self.unlink() if a file and uses shutil.rmtree() if a directory.
429 def copy( 430 self, new_path: Self | pathlib.Path | str, overwrite: bool = False 431 ) -> Self: 432 """Copy the path pointed to by this instance 433 to the instance pointed to by `new_path` using `shutil.copyfile` 434 or `shutil.copytree`. 435 436 Returns the new path. 437 438 #### :params: 439 440 `new_path`: The copy destination. 441 442 `overwrite`: If `True`, files already existing in `new_path` will be overwritten. 443 If `False`, only files that don't exist in `new_path` will be copied.""" 444 new_path = Pathier(new_path) 445 if self.is_dir(): 446 if overwrite or not new_path.exists(): 447 shutil.copytree(self, new_path, dirs_exist_ok=True) 448 else: 449 files = self.rglob("*.*") 450 for file in files: 451 dst = new_path.with_name(file.name) 452 if not dst.exists(): 453 shutil.copyfile(file, dst) 454 elif self.is_file(): 455 if overwrite or not new_path.exists(): 456 shutil.copyfile(self, new_path) 457 return new_path
Copy the path pointed to by this instance
to the instance pointed to by new_path using shutil.copyfile
or shutil.copytree.
Returns the new path.
:params:
new_path: The copy destination.
overwrite: If True, files already existing in new_path will be overwritten.
If False, only files that don't exist in new_path will be copied.
459 def backup(self, timestamp: bool = False) -> Self | None: 460 """Create a copy of this file or directory with `_backup` appended to the path stem. 461 If the path to be backed up doesn't exist, `None` is returned. 462 Otherwise a `Pathier` object for the backup is returned. 463 464 #### :params: 465 466 `timestamp`: Add a timestamp to the backup name to prevent overriding previous backups. 467 468 >>> path = Pathier("some_file.txt") 469 >>> path.backup() 470 >>> list(path.iterdir()) 471 >>> ['some_file.txt', 'some_file_backup.txt'] 472 >>> path.backup(True) 473 >>> list(path.iterdir()) 474 >>> ['some_file.txt', 'some_file_backup.txt', 'some_file_backup_04-28-2023-06_25_52_PM.txt']""" 475 if not self.exists(): 476 return None 477 backup_stem = f"{self.stem}_backup" 478 if timestamp: 479 backup_stem = f"{backup_stem}_{datetime.datetime.now().strftime('%m-%d-%Y-%I_%M_%S_%p')}" 480 backup_path = self.with_stem(backup_stem) 481 self.copy(backup_path, True) 482 return backup_path
Create a copy of this file or directory with _backup appended to the path stem.
If the path to be backed up doesn't exist, None is returned.
Otherwise a Pathier object for the backup is returned.
:params:
timestamp: Add a timestamp to the backup name to prevent overriding previous backups.
>>> path = Pathier("some_file.txt")
>>> path.backup()
>>> list(path.iterdir())
>>> ['some_file.txt', 'some_file_backup.txt']
>>> path.backup(True)
>>> list(path.iterdir())
>>> ['some_file.txt', 'some_file_backup.txt', 'some_file_backup_04-28-2023-06_25_52_PM.txt']
484 def execute(self, command: str = "", args: str = "") -> int: 485 """Make a call to `os.system` using the path pointed to by this Pathier object. 486 487 #### :params: 488 489 `command`: Program/command to precede the path with. 490 491 `args`: Any arguments that should come after the path. 492 493 :returns: The integer output of `os.system`. 494 495 e.g. 496 >>> path = Pathier("mydirectory") / "myscript.py" 497 then 498 >>> path.execute("py", "--iterations 10") 499 equivalent to 500 >>> os.system(f"py {path} --iterations 10")""" 501 return os.system(f"{command} {self} {args}")
Make a call to os.system using the path pointed to by this Pathier object.
:params:
command: Program/command to precede the path with.
args: Any arguments that should come after the path.
:returns: The integer output of os.system.
e.g.
>>> path = Pathier("mydirectory") / "myscript.py"
then
>>> path.execute("py", "--iterations 10")
equivalent to
>>> os.system(f"py {path} --iterations 10")
Inherited Members
- pathlib.Path
- cwd
- home
- samefile
- iterdir
- glob
- rglob
- absolute
- resolve
- stat
- owner
- group
- read_bytes
- read_text
- readlink
- chmod
- lchmod
- unlink
- rmdir
- lstat
- rename
- symlink_to
- hardlink_to
- link_to
- exists
- is_dir
- is_file
- is_mount
- is_symlink
- is_block_device
- is_char_device
- is_fifo
- is_socket
- expanduser
- pathlib.PurePath
- as_posix
- as_uri
- drive
- root
- anchor
- name
- suffix
- suffixes
- stem
- with_name
- with_stem
- with_suffix
- relative_to
- is_relative_to
- parts
- joinpath
- parent
- parents
- is_absolute
- is_reserved
- match