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