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