44
55from .base .exceptions import PathNotFoundError
66
7- from typing import Optional
7+ from typing import Optional , cast
88import tempfile as _tempfile
99import difflib as _difflib
1010import shutil as _shutil
@@ -37,9 +37,9 @@ def __get__(self, obj, owner=None):
3737class Path :
3838 """This class provides methods to work with file and directory paths."""
3939
40- cwd : str = _Cwd () # type: ignore[assignment]
40+ cwd : str = cast ( str , _Cwd ())
4141 """The path to the current working directory."""
42- script_dir : str = _ScriptDir () # type: ignore[assignment]
42+ script_dir : str = cast ( str , _ScriptDir ())
4343 """The path to the directory of the current script."""
4444
4545 @staticmethod
@@ -63,68 +63,82 @@ def extend(
6363 it will be searched in the `search_in` directory/s.<br>
6464 If the `rel_path` is still not found, it returns `None` or
6565 raises a `PathNotFoundError` if `raise_error` is true."""
66- if rel_path in {"" , None }:
66+ search_dirs : list [str ] = []
67+
68+ if not isinstance (rel_path , str ):
69+ raise TypeError (f"The 'rel_path' parameter must be a string, got { type (rel_path )} " )
70+ if search_in is not None :
71+ if isinstance (search_in , str ):
72+ search_dirs .extend ([search_in ])
73+ elif isinstance (search_in , list ):
74+ if not all (isinstance (item , str ) for item in search_in ):
75+ raise TypeError ("All items in the 'search_in' list must be strings." )
76+ search_dirs .extend (search_in )
77+ else :
78+ raise TypeError (f"The 'search_in' parameter must be a string or a list of strings, got { type (search_in )} " )
79+ if not isinstance (raise_error , bool ):
80+ raise TypeError (f"The 'raise_error' parameter must be a boolean, got { type (raise_error )} " )
81+ if not isinstance (use_closest_match , bool ):
82+ raise TypeError (f"The 'use_closest_match' parameter must be a boolean, got { type (use_closest_match )} " )
83+
84+ if rel_path == "" :
6785 if raise_error :
6886 raise PathNotFoundError ("Path is empty." )
69- return None
87+ else :
88+ return None
7089 elif _os .path .isabs (rel_path ):
7190 return rel_path
7291
7392 def get_closest_match (dir : str , part : str ) -> Optional [str ]:
7493 try :
75- files_and_dirs = _os .listdir (dir )
76- matches = _difflib .get_close_matches (part , files_and_dirs , n = 1 , cutoff = 0.6 )
94+ matches = _difflib .get_close_matches (part , _os .listdir (dir ), n = 1 , cutoff = 0.6 )
7795 return matches [0 ] if matches else None
7896 except Exception :
7997 return None
8098
8199 def find_path (start : str , parts : list [str ]) -> Optional [str ]:
82100 current = start
101+
83102 for part in parts :
84103 if _os .path .isfile (current ):
85104 return current
86105 closest_match = get_closest_match (current , part ) if use_closest_match else part
87106 current = _os .path .join (current , closest_match ) if closest_match else None
88107 if current is None :
89108 return None
109+
90110 return current if _os .path .exists (current ) and current != start else None
91111
92112 def expand_env_path (p : str ) -> str :
93113 if "%" not in p :
94114 return p
95- parts = p . split ( "%" )
96- for i in range (1 , len (parts ), 2 ):
115+
116+ for i in range (1 , len (parts := p . split ( "%" ) ), 2 ):
97117 if parts [i ].upper () in _os .environ :
98118 parts [i ] = _os .environ [parts [i ].upper ()]
119+
99120 return "" .join (parts )
100121
101122 rel_path = _os .path .normpath (expand_env_path (rel_path ))
123+
102124 if _os .path .isabs (rel_path ):
103125 drive , rel_path = _os .path .splitdrive (rel_path )
104126 rel_path = rel_path .lstrip (_os .sep )
105- search_dirs = [(drive + _os .sep ) if drive else _os .sep ]
127+ search_dirs . extend ( [(drive + _os .sep ) if drive else _os .sep ])
106128 else :
107129 rel_path = rel_path .lstrip (_os .sep )
108- base_dir = Path .script_dir
109- search_dirs = [
110- _os .getcwd (),
111- base_dir ,
112- _os .path .expanduser ("~" ),
113- _tempfile .gettempdir (),
114- ]
115- if search_in :
116- search_dirs .extend ([search_in ] if isinstance (search_in , str ) else search_in )
117- path_parts = rel_path .split (_os .sep )
130+ search_dirs .extend ([_os .getcwd (), Path .script_dir , _os .path .expanduser ("~" ), _tempfile .gettempdir ()])
131+
118132 for search_dir in search_dirs :
119- full_path = _os .path .join (search_dir , rel_path )
120- if _os .path .exists (full_path ):
133+ if _os .path .exists (full_path := _os .path .join (search_dir , rel_path )):
121134 return full_path
122- match = find_path (search_dir , path_parts ) if use_closest_match else None
123- if match :
135+ if (match := find_path (search_dir , rel_path .split (_os .sep )) if use_closest_match else None ):
124136 return match
137+
125138 if raise_error :
126139 raise PathNotFoundError (f"Path '{ rel_path } ' not found in specified directories." )
127- return None
140+ else :
141+ return None
128142
129143 @staticmethod
130144 def extend_or_make (
@@ -151,12 +165,24 @@ def extend_or_make(
151165 even though the `rel_path` doesn't exist there.<br>
152166 If `prefer_script_dir` is false, it will instead make a path
153167 that points to where the `rel_path` would be in the CWD."""
168+ # THE 'rel_path' PARAM IS CHECKED IN 'Path.extend()'
169+ # THE 'search_in' PARAM IS CHECKED IN 'Path.extend()'
170+ if not isinstance (prefer_script_dir , bool ):
171+ raise TypeError (f"The 'prefer_script_dir' parameter must be a boolean, got { type (prefer_script_dir )} " )
172+ # THE 'use_closest_match' PARAM IS CHECKED IN 'Path.extend()'
173+
154174 try :
155- return str (Path .extend (rel_path , search_in , raise_error = True , use_closest_match = use_closest_match ))
175+ return str (Path .extend ( \
176+ rel_path = rel_path ,
177+ search_in = search_in ,
178+ raise_error = True ,
179+ use_closest_match = use_closest_match ,
180+ ))
156181 except PathNotFoundError :
157- normalized_rel_path = _os .path .normpath (rel_path )
158- base = Path .script_dir if prefer_script_dir else _os .getcwd ()
159- return _os .path .join (base , normalized_rel_path )
182+ return _os .path .join (
183+ Path .script_dir if prefer_script_dir else _os .getcwd (),
184+ _os .path .normpath (rel_path ),
185+ )
160186
161187 @staticmethod
162188 def remove (path : str , only_content : bool = False ) -> None :
@@ -165,13 +191,20 @@ def remove(path: str, only_content: bool = False) -> None:
165191 - `path` -⠀the path to the directory or file to remove
166192 - `only_content` -⠀if true, only the content of the directory is removed
167193 and the directory itself is kept"""
194+ if not isinstance (path , str ):
195+ raise TypeError (f"The 'path' parameter must be a string, got { type (path )} " )
196+ if not isinstance (only_content , bool ):
197+ raise TypeError (f"The 'only_content' parameter must be a boolean, got { type (only_content )} " )
198+
168199 if not _os .path .exists (path ):
169200 return None
201+
170202 if not only_content :
171203 if _os .path .isfile (path ) or _os .path .islink (path ):
172204 _os .unlink (path )
173205 elif _os .path .isdir (path ):
174206 _shutil .rmtree (path )
207+
175208 elif _os .path .isdir (path ):
176209 for filename in _os .listdir (path ):
177210 file_path = _os .path .join (path , filename )
0 commit comments