pathier
13class Pathier(pathlib.Path): 14 """Subclasses the standard library pathlib.Path class.""" 15 16 def __new__(cls, *args, **kwargs): 17 if cls is Pathier: 18 cls = WindowsPath if os.name == "nt" else PosixPath 19 self = cls._from_parts(args) 20 if not self._flavour.is_supported: 21 raise NotImplementedError( 22 "cannot instantiate %r on your system" % (cls.__name__,) 23 ) 24 return self 25 26 @property 27 def dob(self) -> datetime.datetime | None: 28 """Returns the creation date of this file 29 or directory as a dateime.datetime object.""" 30 if self.exists(): 31 return datetime.datetime.fromtimestamp(self.stat().st_ctime) 32 else: 33 return None 34 35 @property 36 def age(self) -> float | None: 37 """Returns the age in seconds of this file or directory.""" 38 if self.exists(): 39 return (datetime.datetime.now() - self.dob).total_seconds() 40 else: 41 return None 42 43 @property 44 def mod_date(self) -> datetime.datetime | None: 45 """Returns the modification date of this file 46 or directory as a datetime.datetime object.""" 47 if self.exists(): 48 return datetime.datetime.fromtimestamp(self.stat().st_mtime) 49 else: 50 return None 51 52 @property 53 def mod_delta(self) -> float | None: 54 """Returns how long ago in seconds this file 55 or directory was modified.""" 56 if self.exists(): 57 return (datetime.datetime.now() - self.mod_date).total_seconds() 58 else: 59 return None 60 61 def size(self, format: bool = False) -> int | str | None: 62 """Returns the size in bytes of this file or directory. 63 Returns None if this path doesn't exist. 64 65 :param format: If True, return value as a formatted string.""" 66 if not self.exists(): 67 return None 68 if self.is_file(): 69 size = self.stat().st_size 70 if self.is_dir(): 71 size = sum(file.stat().st_size for file in self.rglob("*.*")) 72 if format: 73 return self.format_size(size) 74 return size 75 76 @staticmethod 77 def format_size(size: int) -> str: 78 """Format 'size' with common file size abbreviations 79 and rounded to two decimal places. 80 >>> 1234 -> "1.23 kb" """ 81 for unit in ["bytes", "kb", "mb", "gb", "tb", "pb"]: 82 if unit != "bytes": 83 size *= 0.001 84 if size < 1000 or unit == "pb": 85 return f"{round(size, 2)} {unit}" 86 87 def is_larger(self, path: Self) -> bool: 88 """Returns whether this file or folder is larger than 89 the one pointed to by 'path'.""" 90 return self.size() > path.size() 91 92 def is_older(self, path: Self) -> bool: 93 """Returns whether this file or folder is older than 94 the one pointed to by 'path'.""" 95 return self.dob < path.dob 96 97 def modified_more_recently(self, path: Self) -> bool: 98 """Returns whether this file or folder was modified 99 more recently than the one pointed to by 'path'.""" 100 return self.mod_date > path.mod_date 101 102 def moveup(self, name: str) -> Self: 103 """Return a new Pathier object that is a parent of this instance. 104 'name' is case-sensitive and raises an exception if it isn't in self.parts. 105 >>> p = Pathier("C:\some\directory\in\your\system") 106 >>> print(p.moveup("directory")) 107 >>> "C:\some\directory" 108 >>> print(p.moveup("yeet")) 109 >>> "Exception: yeet is not a parent of C:\some\directory\in\your\system" """ 110 if name not in self.parts: 111 raise Exception(f"{name} is not a parent of {self}") 112 return Pathier(*(self.parts[: self.parts.index(name) + 1])) 113 114 def __sub__(self, levels: int) -> Self: 115 """Return a new Pathier object moved up 'levels' number of parents from the current path. 116 >>> p = Pathier("C:\some\directory\in\your\system") 117 >>> new_p = p - 3 118 >>> print(new_p) 119 >>> "C:\some\directory" """ 120 path = self 121 for _ in range(levels): 122 path = path.parent 123 return path 124 125 def move_under(self, name: str) -> Self: 126 """Return a new Pathier object such that the stem 127 is one level below the folder 'name'. 128 'name' is case-sensitive and raises an exception if it isn't in self.parts. 129 >>> p = Pathier("a/b/c/d/e/f/g") 130 >>> print(p.move_under("c")) 131 >>> 'a/b/c/d'""" 132 if name not in self.parts: 133 raise Exception(f"{name} is not a parent of {self}") 134 return self - (len(self.parts) - self.parts.index(name) - 2) 135 136 def separate(self, name: str, keep_name: bool = False) -> Self: 137 """Return a new Pathier object that is the 138 relative child path after 'name'. 139 'name' is case-sensitive and raises an exception if it isn't in self.parts. 140 141 :param keep_name: If True, the returned path will start with 'name'. 142 >>> p = Pathier("a/b/c/d/e/f/g") 143 >>> print(p.separate("c")) 144 >>> 'd/e/f/g' 145 >>> print(p.separate("c", True)) 146 >>> 'c/d/e/f/g'""" 147 if name not in self.parts: 148 raise Exception(f"{name} is not a parent of {self}") 149 if keep_name: 150 return Pathier(*self.parts[self.parts.index(name) :]) 151 return Pathier(*self.parts[self.parts.index(name) + 1 :]) 152 153 def mkdir(self, mode: int = 511, parents: bool = True, exist_ok: bool = True): 154 """Create this directory. 155 Same as Path().mkdir() except 156 'parents' and 'exist_ok' default 157 to True instead of False.""" 158 super().mkdir(mode, parents, exist_ok) 159 160 def touch(self): 161 """Create file and parents if necessary.""" 162 self.parent.mkdir() 163 super().touch() 164 165 def write_text( 166 self, 167 data: Any, 168 encoding: Any | None = None, 169 errors: Any | None = None, 170 newline: Any | None = None, 171 parents: bool = True, 172 ): 173 """Write data to file. If a TypeError is raised, the function 174 will attempt to case data to a str and try the write again. 175 If a FileNotFoundError is raised and parents = True, 176 self.parent will be created.""" 177 write = functools.partial( 178 super().write_text, 179 encoding=encoding, 180 errors=errors, 181 newline=newline, 182 ) 183 try: 184 write(data) 185 except TypeError: 186 data = str(data) 187 write(data) 188 except FileNotFoundError: 189 if parents: 190 self.parent.mkdir(parents=True) 191 write(data) 192 else: 193 raise 194 except Exception as e: 195 raise 196 197 def write_bytes(self, data: bytes, parents: bool = True): 198 """Write bytes to file. 199 200 :param parents: If True and the write operation fails 201 with a FileNotFoundError, make the parent directory 202 and retry the write.""" 203 try: 204 super().write_bytes(data) 205 except FileNotFoundError: 206 if parents: 207 self.parent.mkdir(parents=True) 208 super().write_bytes(data) 209 else: 210 raise 211 except Exception as e: 212 raise 213 214 def json_loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 215 """Load json file.""" 216 return json.loads(self.read_text(encoding, errors)) 217 218 def json_dumps( 219 self, 220 data: Any, 221 encoding: Any | None = None, 222 errors: Any | None = None, 223 newline: Any | None = None, 224 sort_keys: bool = False, 225 indent: Any | None = None, 226 default: Any | None = None, 227 parents: bool = True, 228 ) -> Any: 229 """Dump data to json file.""" 230 self.write_text( 231 json.dumps(data, indent=indent, default=default, sort_keys=sort_keys), 232 encoding, 233 errors, 234 newline, 235 parents, 236 ) 237 238 def toml_loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 239 """Load toml file.""" 240 return tomlkit.loads(self.read_text(encoding, errors)) 241 242 def toml_dumps( 243 self, 244 data: Any, 245 encoding: Any | None = None, 246 errors: Any | None = None, 247 newline: Any | None = None, 248 sort_keys: bool = False, 249 parents: bool = True, 250 ): 251 """Dump data to toml file.""" 252 self.write_text( 253 tomlkit.dumps(data, sort_keys), encoding, errors, newline, parents 254 ) 255 256 def loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 257 """Load a json or toml file based off this instance's suffix.""" 258 match self.suffix: 259 case ".json": 260 return self.json_loads(encoding, errors) 261 case ".toml": 262 return self.toml_loads(encoding, errors) 263 264 def dumps( 265 self, 266 data: Any, 267 encoding: Any | None = None, 268 errors: Any | None = None, 269 newline: Any | None = None, 270 sort_keys: bool = False, 271 indent: Any | None = None, 272 default: Any | None = None, 273 parents: bool = True, 274 ): 275 """Dump data to a json or toml file based off this instance's suffix.""" 276 match self.suffix: 277 case ".json": 278 self.json_dumps( 279 data, encoding, errors, newline, sort_keys, indent, default, parents 280 ) 281 case ".toml": 282 self.toml_dumps(data, encoding, errors, newline, sort_keys, parents) 283 284 def delete(self, missing_ok: bool = True): 285 """Delete the file or folder pointed to by this instance. 286 Uses self.unlink() if a file and uses shutil.rmtree() if a directory.""" 287 if self.is_file(): 288 self.unlink(missing_ok) 289 elif self.is_dir(): 290 shutil.rmtree(self) 291 292 def copy( 293 self, new_path: Self | pathlib.Path | str, overwrite: bool = False 294 ) -> Self: 295 """Copy the path pointed to by this instance 296 to the instance pointed to by new_path using shutil.copyfile 297 or shutil.copytree. Returns the new path. 298 299 :param new_path: The copy destination. 300 301 :param overwrite: If True, files already existing in new_path 302 will be overwritten. If False, only files that don't exist in new_path 303 will be copied.""" 304 new_path = Pathier(new_path) 305 if self.is_dir(): 306 if overwrite or not new_path.exists(): 307 shutil.copytree(self, new_path, dirs_exist_ok=True) 308 else: 309 files = self.rglob("*.*") 310 for file in files: 311 dst = new_path.with_name(file.name) 312 if not dst.exists(): 313 shutil.copyfile(file, dst) 314 elif self.is_file(): 315 if overwrite or not new_path.exists(): 316 shutil.copyfile(self, new_path) 317 return new_path
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.
61 def size(self, format: bool = False) -> int | str | None: 62 """Returns the size in bytes of this file or directory. 63 Returns None if this path doesn't exist. 64 65 :param format: If True, return value as a formatted string.""" 66 if not self.exists(): 67 return None 68 if self.is_file(): 69 size = self.stat().st_size 70 if self.is_dir(): 71 size = sum(file.stat().st_size for file in self.rglob("*.*")) 72 if format: 73 return self.format_size(size) 74 return size
Returns the size in bytes of this file or directory. Returns None if this path doesn't exist.
Parameters
- format: If True, return value as a formatted string.
76 @staticmethod 77 def format_size(size: int) -> str: 78 """Format 'size' with common file size abbreviations 79 and rounded to two decimal places. 80 >>> 1234 -> "1.23 kb" """ 81 for unit in ["bytes", "kb", "mb", "gb", "tb", "pb"]: 82 if unit != "bytes": 83 size *= 0.001 84 if size < 1000 or unit == "pb": 85 return f"{round(size, 2)} {unit}"
Format 'size' with common file size abbreviations and rounded to two decimal places.
>>> 1234 -> "1.23 kb"
87 def is_larger(self, path: Self) -> bool: 88 """Returns whether this file or folder is larger than 89 the one pointed to by 'path'.""" 90 return self.size() > path.size()
Returns whether this file or folder is larger than the one pointed to by 'path'.
92 def is_older(self, path: Self) -> bool: 93 """Returns whether this file or folder is older than 94 the one pointed to by 'path'.""" 95 return self.dob < path.dob
Returns whether this file or folder is older than the one pointed to by 'path'.
97 def modified_more_recently(self, path: Self) -> bool: 98 """Returns whether this file or folder was modified 99 more recently than the one pointed to by 'path'.""" 100 return self.mod_date > path.mod_date
Returns whether this file or folder was modified more recently than the one pointed to by 'path'.
102 def moveup(self, name: str) -> Self: 103 """Return a new Pathier object that is a parent of this instance. 104 'name' is case-sensitive and raises an exception if it isn't in self.parts. 105 >>> p = Pathier("C:\some\directory\in\your\system") 106 >>> print(p.moveup("directory")) 107 >>> "C:\some\directory" 108 >>> print(p.moveup("yeet")) 109 >>> "Exception: yeet is not a parent of C:\some\directory\in\your\system" """ 110 if name not in self.parts: 111 raise Exception(f"{name} is not a parent of {self}") 112 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"
125 def move_under(self, name: str) -> Self: 126 """Return a new Pathier object such that the stem 127 is one level below the folder 'name'. 128 'name' is case-sensitive and raises an exception if it isn't in self.parts. 129 >>> p = Pathier("a/b/c/d/e/f/g") 130 >>> print(p.move_under("c")) 131 >>> 'a/b/c/d'""" 132 if name not in self.parts: 133 raise Exception(f"{name} is not a parent of {self}") 134 return self - (len(self.parts) - self.parts.index(name) - 2)
Return a new Pathier object such that the stem is one level below the folder 'name'. 'name' is case-sensitive and raises an exception if it isn't in self.parts.
>>> p = Pathier("a/b/c/d/e/f/g")
>>> print(p.move_under("c"))
>>> 'a/b/c/d'
136 def separate(self, name: str, keep_name: bool = False) -> Self: 137 """Return a new Pathier object that is the 138 relative child path after 'name'. 139 'name' is case-sensitive and raises an exception if it isn't in self.parts. 140 141 :param keep_name: If True, the returned path will start with 'name'. 142 >>> p = Pathier("a/b/c/d/e/f/g") 143 >>> print(p.separate("c")) 144 >>> 'd/e/f/g' 145 >>> print(p.separate("c", True)) 146 >>> 'c/d/e/f/g'""" 147 if name not in self.parts: 148 raise Exception(f"{name} is not a parent of {self}") 149 if keep_name: 150 return Pathier(*self.parts[self.parts.index(name) :]) 151 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.
Parameters
- 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'
153 def mkdir(self, mode: int = 511, parents: bool = True, exist_ok: bool = True): 154 """Create this directory. 155 Same as Path().mkdir() except 156 'parents' and 'exist_ok' default 157 to True instead of False.""" 158 super().mkdir(mode, parents, exist_ok)
Create this directory. Same as Path().mkdir() except 'parents' and 'exist_ok' default to True instead of False.
160 def touch(self): 161 """Create file and parents if necessary.""" 162 self.parent.mkdir() 163 super().touch()
Create file and parents if necessary.
165 def write_text( 166 self, 167 data: Any, 168 encoding: Any | None = None, 169 errors: Any | None = None, 170 newline: Any | None = None, 171 parents: bool = True, 172 ): 173 """Write data to file. If a TypeError is raised, the function 174 will attempt to case data to a str and try the write again. 175 If a FileNotFoundError is raised and parents = True, 176 self.parent will be created.""" 177 write = functools.partial( 178 super().write_text, 179 encoding=encoding, 180 errors=errors, 181 newline=newline, 182 ) 183 try: 184 write(data) 185 except TypeError: 186 data = str(data) 187 write(data) 188 except FileNotFoundError: 189 if parents: 190 self.parent.mkdir(parents=True) 191 write(data) 192 else: 193 raise 194 except Exception as e: 195 raise
Write data to file. If a TypeError is raised, the function will attempt to case data to a str and try the write again. If a FileNotFoundError is raised and parents = True, self.parent will be created.
197 def write_bytes(self, data: bytes, parents: bool = True): 198 """Write bytes to file. 199 200 :param parents: If True and the write operation fails 201 with a FileNotFoundError, make the parent directory 202 and retry the write.""" 203 try: 204 super().write_bytes(data) 205 except FileNotFoundError: 206 if parents: 207 self.parent.mkdir(parents=True) 208 super().write_bytes(data) 209 else: 210 raise 211 except Exception as e: 212 raise
Write bytes to file.
Parameters
- parents: If True and the write operation fails with a FileNotFoundError, make the parent directory and retry the write.
214 def json_loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 215 """Load json file.""" 216 return json.loads(self.read_text(encoding, errors))
Load json file.
218 def json_dumps( 219 self, 220 data: Any, 221 encoding: Any | None = None, 222 errors: Any | None = None, 223 newline: Any | None = None, 224 sort_keys: bool = False, 225 indent: Any | None = None, 226 default: Any | None = None, 227 parents: bool = True, 228 ) -> Any: 229 """Dump data to json file.""" 230 self.write_text( 231 json.dumps(data, indent=indent, default=default, sort_keys=sort_keys), 232 encoding, 233 errors, 234 newline, 235 parents, 236 )
Dump data to json file.
238 def toml_loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 239 """Load toml file.""" 240 return tomlkit.loads(self.read_text(encoding, errors))
Load toml file.
242 def toml_dumps( 243 self, 244 data: Any, 245 encoding: Any | None = None, 246 errors: Any | None = None, 247 newline: Any | None = None, 248 sort_keys: bool = False, 249 parents: bool = True, 250 ): 251 """Dump data to toml file.""" 252 self.write_text( 253 tomlkit.dumps(data, sort_keys), encoding, errors, newline, parents 254 )
Dump data to toml file.
256 def loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 257 """Load a json or toml file based off this instance's suffix.""" 258 match self.suffix: 259 case ".json": 260 return self.json_loads(encoding, errors) 261 case ".toml": 262 return self.toml_loads(encoding, errors)
Load a json or toml file based off this instance's suffix.
264 def dumps( 265 self, 266 data: Any, 267 encoding: Any | None = None, 268 errors: Any | None = None, 269 newline: Any | None = None, 270 sort_keys: bool = False, 271 indent: Any | None = None, 272 default: Any | None = None, 273 parents: bool = True, 274 ): 275 """Dump data to a json or toml file based off this instance's suffix.""" 276 match self.suffix: 277 case ".json": 278 self.json_dumps( 279 data, encoding, errors, newline, sort_keys, indent, default, parents 280 ) 281 case ".toml": 282 self.toml_dumps(data, encoding, errors, newline, sort_keys, parents)
Dump data to a json or toml file based off this instance's suffix.
284 def delete(self, missing_ok: bool = True): 285 """Delete the file or folder pointed to by this instance. 286 Uses self.unlink() if a file and uses shutil.rmtree() if a directory.""" 287 if self.is_file(): 288 self.unlink(missing_ok) 289 elif self.is_dir(): 290 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.
292 def copy( 293 self, new_path: Self | pathlib.Path | str, overwrite: bool = False 294 ) -> Self: 295 """Copy the path pointed to by this instance 296 to the instance pointed to by new_path using shutil.copyfile 297 or shutil.copytree. Returns the new path. 298 299 :param new_path: The copy destination. 300 301 :param overwrite: If True, files already existing in new_path 302 will be overwritten. If False, only files that don't exist in new_path 303 will be copied.""" 304 new_path = Pathier(new_path) 305 if self.is_dir(): 306 if overwrite or not new_path.exists(): 307 shutil.copytree(self, new_path, dirs_exist_ok=True) 308 else: 309 files = self.rglob("*.*") 310 for file in files: 311 dst = new_path.with_name(file.name) 312 if not dst.exists(): 313 shutil.copyfile(file, dst) 314 elif self.is_file(): 315 if overwrite or not new_path.exists(): 316 shutil.copyfile(self, new_path) 317 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.
Parameters
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.
Inherited Members
- pathlib.Path
- cwd
- home
- samefile
- iterdir
- glob
- rglob
- absolute
- resolve
- stat
- owner
- group
- open
- read_bytes
- read_text
- readlink
- chmod
- lchmod
- unlink
- rmdir
- lstat
- rename
- replace
- 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