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