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 mkdir(self, mode: int = 511, parents: bool = True, exist_ok: bool = True): 137 """Create this directory. 138 Same as Path().mkdir() except 139 'parents' and 'exist_ok' default 140 to True instead of False.""" 141 super().mkdir(mode, parents, exist_ok) 142 143 def touch(self): 144 """Create file and parents if necessary.""" 145 self.parent.mkdir() 146 super().touch() 147 148 def write_text( 149 self, 150 data: Any, 151 encoding: Any | None = None, 152 errors: Any | None = None, 153 newline: Any | None = None, 154 parents: bool = True, 155 ): 156 """Write data to file. If a TypeError is raised, the function 157 will attempt to case data to a str and try the write again. 158 If a FileNotFoundError is raised and parents = True, 159 self.parent will be created.""" 160 write = functools.partial( 161 super().write_text, 162 encoding=encoding, 163 errors=errors, 164 newline=newline, 165 ) 166 try: 167 write(data) 168 except TypeError: 169 data = str(data) 170 write(data) 171 except FileNotFoundError: 172 if parents: 173 self.parent.mkdir(parents=True) 174 write(data) 175 else: 176 raise 177 except Exception as e: 178 raise 179 180 def write_bytes(self, data: bytes, parents: bool = True): 181 """Write bytes to file. 182 183 :param parents: If True and the write operation fails 184 with a FileNotFoundError, make the parent directory 185 and retry the write.""" 186 try: 187 super().write_bytes(data) 188 except FileNotFoundError: 189 if parents: 190 self.parent.mkdir(parents=True) 191 super().write_bytes(data) 192 else: 193 raise 194 except Exception as e: 195 raise 196 197 def json_loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 198 """Load json file.""" 199 return json.loads(self.read_text(encoding, errors)) 200 201 def json_dumps( 202 self, 203 data: Any, 204 encoding: Any | None = None, 205 errors: Any | None = None, 206 newline: Any | None = None, 207 sort_keys: bool = False, 208 indent: Any | None = None, 209 default: Any | None = None, 210 parents: bool = True, 211 ) -> Any: 212 """Dump data to json file.""" 213 self.write_text( 214 json.dumps(data, indent=indent, default=default, sort_keys=sort_keys), 215 encoding, 216 errors, 217 newline, 218 parents, 219 ) 220 221 def toml_loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 222 """Load toml file.""" 223 return tomlkit.loads(self.read_text(encoding, errors)) 224 225 def toml_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 parents: bool = True, 233 ): 234 """Dump data to toml file.""" 235 self.write_text( 236 tomlkit.dumps(data, sort_keys), encoding, errors, newline, parents 237 ) 238 239 def loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 240 """Load a json or toml file based off this instance's suffix.""" 241 match self.suffix: 242 case ".json": 243 return self.json_loads(encoding, errors) 244 case ".toml": 245 return self.toml_loads(encoding, errors) 246 247 def dumps( 248 self, 249 data: Any, 250 encoding: Any | None = None, 251 errors: Any | None = None, 252 newline: Any | None = None, 253 sort_keys: bool = False, 254 indent: Any | None = None, 255 default: Any | None = None, 256 parents: bool = True, 257 ): 258 """Dump data to a json or toml file based off this instance's suffix.""" 259 match self.suffix: 260 case ".json": 261 self.json_dumps( 262 data, encoding, errors, newline, sort_keys, indent, default, parents 263 ) 264 case ".toml": 265 self.toml_dumps(data, encoding, errors, newline, sort_keys, parents) 266 267 def delete(self, missing_ok: bool = True): 268 """Delete the file or folder pointed to by this instance. 269 Uses self.unlink() if a file and uses shutil.rmtree() if a directory.""" 270 if self.is_file(): 271 self.unlink(missing_ok) 272 elif self.is_dir(): 273 shutil.rmtree(self) 274 275 def copy( 276 self, new_path: Self | pathlib.Path | str, overwrite: bool = False 277 ) -> Self: 278 """Copy the path pointed to by this instance 279 to the instance pointed to by new_path using shutil.copyfile 280 or shutil.copytree. Returns the new path. 281 282 :param new_path: The copy destination. 283 284 :param overwrite: If True, files already existing in new_path 285 will be overwritten. If False, only files that don't exist in new_path 286 will be copied.""" 287 new_path = Pathier(new_path) 288 if self.is_dir(): 289 if overwrite or not new_path.exists(): 290 shutil.copytree(self, new_path, dirs_exist_ok=True) 291 else: 292 files = self.rglob("*.*") 293 for file in files: 294 dst = new_path.with_name(file.name) 295 if not dst.exists(): 296 shutil.copyfile(file, dst) 297 elif self.is_file(): 298 if overwrite or not new_path.exists(): 299 shutil.copyfile(self, new_path) 300 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 mkdir(self, mode: int = 511, parents: bool = True, exist_ok: bool = True): 137 """Create this directory. 138 Same as Path().mkdir() except 139 'parents' and 'exist_ok' default 140 to True instead of False.""" 141 super().mkdir(mode, parents, exist_ok)
Create this directory. Same as Path().mkdir() except 'parents' and 'exist_ok' default to True instead of False.
143 def touch(self): 144 """Create file and parents if necessary.""" 145 self.parent.mkdir() 146 super().touch()
Create file and parents if necessary.
148 def write_text( 149 self, 150 data: Any, 151 encoding: Any | None = None, 152 errors: Any | None = None, 153 newline: Any | None = None, 154 parents: bool = True, 155 ): 156 """Write data to file. If a TypeError is raised, the function 157 will attempt to case data to a str and try the write again. 158 If a FileNotFoundError is raised and parents = True, 159 self.parent will be created.""" 160 write = functools.partial( 161 super().write_text, 162 encoding=encoding, 163 errors=errors, 164 newline=newline, 165 ) 166 try: 167 write(data) 168 except TypeError: 169 data = str(data) 170 write(data) 171 except FileNotFoundError: 172 if parents: 173 self.parent.mkdir(parents=True) 174 write(data) 175 else: 176 raise 177 except Exception as e: 178 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.
180 def write_bytes(self, data: bytes, parents: bool = True): 181 """Write bytes to file. 182 183 :param parents: If True and the write operation fails 184 with a FileNotFoundError, make the parent directory 185 and retry the write.""" 186 try: 187 super().write_bytes(data) 188 except FileNotFoundError: 189 if parents: 190 self.parent.mkdir(parents=True) 191 super().write_bytes(data) 192 else: 193 raise 194 except Exception as e: 195 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.
197 def json_loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 198 """Load json file.""" 199 return json.loads(self.read_text(encoding, errors))
Load json file.
201 def json_dumps( 202 self, 203 data: Any, 204 encoding: Any | None = None, 205 errors: Any | None = None, 206 newline: Any | None = None, 207 sort_keys: bool = False, 208 indent: Any | None = None, 209 default: Any | None = None, 210 parents: bool = True, 211 ) -> Any: 212 """Dump data to json file.""" 213 self.write_text( 214 json.dumps(data, indent=indent, default=default, sort_keys=sort_keys), 215 encoding, 216 errors, 217 newline, 218 parents, 219 )
Dump data to json file.
221 def toml_loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 222 """Load toml file.""" 223 return tomlkit.loads(self.read_text(encoding, errors))
Load toml file.
225 def toml_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 parents: bool = True, 233 ): 234 """Dump data to toml file.""" 235 self.write_text( 236 tomlkit.dumps(data, sort_keys), encoding, errors, newline, parents 237 )
Dump data to toml file.
239 def loads(self, encoding: Any | None = None, errors: Any | None = None) -> Any: 240 """Load a json or toml file based off this instance's suffix.""" 241 match self.suffix: 242 case ".json": 243 return self.json_loads(encoding, errors) 244 case ".toml": 245 return self.toml_loads(encoding, errors)
Load a json or toml file based off this instance's suffix.
247 def dumps( 248 self, 249 data: Any, 250 encoding: Any | None = None, 251 errors: Any | None = None, 252 newline: Any | None = None, 253 sort_keys: bool = False, 254 indent: Any | None = None, 255 default: Any | None = None, 256 parents: bool = True, 257 ): 258 """Dump data to a json or toml file based off this instance's suffix.""" 259 match self.suffix: 260 case ".json": 261 self.json_dumps( 262 data, encoding, errors, newline, sort_keys, indent, default, parents 263 ) 264 case ".toml": 265 self.toml_dumps(data, encoding, errors, newline, sort_keys, parents)
Dump data to a json or toml file based off this instance's suffix.
267 def delete(self, missing_ok: bool = True): 268 """Delete the file or folder pointed to by this instance. 269 Uses self.unlink() if a file and uses shutil.rmtree() if a directory.""" 270 if self.is_file(): 271 self.unlink(missing_ok) 272 elif self.is_dir(): 273 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.
275 def copy( 276 self, new_path: Self | pathlib.Path | str, overwrite: bool = False 277 ) -> Self: 278 """Copy the path pointed to by this instance 279 to the instance pointed to by new_path using shutil.copyfile 280 or shutil.copytree. Returns the new path. 281 282 :param new_path: The copy destination. 283 284 :param overwrite: If True, files already existing in new_path 285 will be overwritten. If False, only files that don't exist in new_path 286 will be copied.""" 287 new_path = Pathier(new_path) 288 if self.is_dir(): 289 if overwrite or not new_path.exists(): 290 shutil.copytree(self, new_path, dirs_exist_ok=True) 291 else: 292 files = self.rglob("*.*") 293 for file in files: 294 dst = new_path.with_name(file.name) 295 if not dst.exists(): 296 shutil.copyfile(file, dst) 297 elif self.is_file(): 298 if overwrite or not new_path.exists(): 299 shutil.copyfile(self, new_path) 300 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