Skip to content

Commit ba1afd3

Browse files
committed
Ignore inaccessible search path entries
1 parent 7e5e64b commit ba1afd3

File tree

2 files changed

+54
-18
lines changed

2 files changed

+54
-18
lines changed

README.rst

+3
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ Changelog
8383

8484
Unreleased
8585

86+
- Declare support for Python 3.13
87+
- Handle situations where an entry on the module search path is not
88+
accessible or does not exist
8689
- Fix warnings due to use of deprecated AST classes
8790

8891
Version 2.5.1 (February 25, 2024)

typeshed_client/finder.py

+51-18
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
Sequence,
2020
Set,
2121
Tuple,
22+
Union,
2223
)
2324

2425
import importlib_resources
@@ -127,10 +128,10 @@ def get_all_stub_files(
127128
# third-party packages
128129
for stub_packages in (True, False):
129130
for search_path_entry in search_context.search_path:
130-
if not search_path_entry.exists():
131+
if not safe_exists(search_path_entry):
131132
continue
132-
for directory in os.scandir(search_path_entry):
133-
if not directory.is_dir():
133+
for directory in safe_scandir(search_path_entry):
134+
if not safe_is_dir(directory):
134135
continue
135136
condition = (
136137
directory.name.endswith("-stubs")
@@ -150,10 +151,10 @@ def get_all_stub_files(
150151
typeshed_dirs.insert(0, search_context.typeshed / "@python2")
151152

152153
for typeshed_dir in typeshed_dirs:
153-
for entry in os.scandir(typeshed_dir):
154-
if entry.is_dir() and entry.name.isidentifier():
154+
for entry in safe_scandir(typeshed_dir):
155+
if safe_is_dir(entry) and entry.name.isidentifier():
155156
module_name = entry.name
156-
elif entry.is_file() and entry.name.endswith(".pyi"):
157+
elif safe_is_file(entry) and entry.name.endswith(".pyi"):
157158
module_name = entry.name[: -len(".pyi")]
158159
else:
159160
continue
@@ -168,7 +169,7 @@ def get_all_stub_files(
168169
and version.in_python2
169170
):
170171
continue
171-
if entry.is_dir():
172+
if safe_is_dir(entry):
172173
seen = yield from _get_all_stub_files_from_directory(
173174
entry, typeshed_dir, seen
174175
)
@@ -188,16 +189,16 @@ def _get_all_stub_files_from_directory(
188189
to_do: List[os.PathLike[str]] = [directory]
189190
while to_do:
190191
current_dir = to_do.pop()
191-
for dir_entry in os.scandir(current_dir):
192-
if dir_entry.is_dir():
192+
for dir_entry in safe_scandir(current_dir):
193+
if safe_is_dir(dir_entry):
193194
if not dir_entry.name.isidentifier():
194195
continue
195196
path = Path(dir_entry)
196-
if (path / "__init__.pyi").is_file() or (
197+
if safe_is_file(path / "__init__.pyi") or safe_is_file(
197198
path / "__init__.py"
198-
).is_file():
199+
):
199200
to_do.append(path)
200-
elif dir_entry.is_file():
201+
elif safe_is_dir(dir_entry):
201202
path = Path(dir_entry)
202203
if path.suffix != ".pyi":
203204
continue
@@ -225,11 +226,43 @@ def get_search_path(typeshed_dir: Path, pyversion: Tuple[int, int]) -> Tuple[Pat
225226
for version in [*versions, str(pyversion[0]), "2and3"]:
226227
for lib_type in ("stdlib", "third_party"):
227228
stubdir = typeshed_dir / lib_type / version
228-
if stubdir.is_dir():
229+
if safe_is_dir(stubdir):
229230
path.append(stubdir)
230231
return tuple(path)
231232

232233

234+
def safe_exists(path: Path) -> bool:
235+
"""Return whether a path exists, assuming it doesn't if we get an error."""
236+
try:
237+
return path.exists()
238+
except OSError:
239+
return False
240+
241+
242+
def safe_is_dir(path: Union[Path, _DirEntry]) -> bool:
243+
"""Return whether a path is a directory, assuming it isn't if we get an error."""
244+
try:
245+
return path.is_dir()
246+
except OSError:
247+
return False
248+
249+
250+
def safe_is_file(path: Union[Path, _DirEntry]) -> bool:
251+
"""Return whether a path is a file, assuming it isn't if we get an error."""
252+
try:
253+
return path.is_file()
254+
except OSError:
255+
return False
256+
257+
258+
def safe_scandir(path: os.PathLike[str]) -> Iterable[_DirEntry]:
259+
"""Return an iterator over the entries in a directory, or no entries if we get an error."""
260+
try:
261+
return os.scandir(path)
262+
except OSError:
263+
return iter([])
264+
265+
233266
def get_stub_file_name(
234267
module_name: ModulePath, search_context: SearchContext
235268
) -> Optional[Path]:
@@ -242,15 +275,15 @@ def get_stub_file_name(
242275
stubs_package = f"{top_level_name}-stubs"
243276
for path in search_context.search_path:
244277
stubdir = path / stubs_package
245-
if stubdir.exists():
278+
if safe_exists(stubdir):
246279
stub = _find_stub_in_dir(stubdir, rest_module_path)
247280
if stub is not None:
248281
return stub
249282

250283
# 4. stubs in normal packages
251284
for path in search_context.search_path:
252285
stubdir = path / top_level_name
253-
if stubdir.exists():
286+
if safe_exists(stubdir):
254287
stub = _find_stub_in_dir(stubdir, rest_module_path)
255288
if stub is not None:
256289
return stub
@@ -314,16 +347,16 @@ def _parse_version(version: str) -> PythonVersion:
314347
def _find_stub_in_dir(stubdir: Path, module: ModulePath) -> Optional[Path]:
315348
if not module:
316349
init_name = stubdir / "__init__.pyi"
317-
if init_name.exists():
350+
if safe_exists(init_name):
318351
return init_name
319352
return None
320353
if len(module) == 1:
321354
stub_name = stubdir / f"{module[0]}.pyi"
322-
if stub_name.exists():
355+
if safe_exists(stub_name):
323356
return stub_name
324357
next_name, *rest = module
325358
next_dir = stubdir / next_name
326-
if next_dir.exists():
359+
if safe_exists(next_dir):
327360
return _find_stub_in_dir(next_dir, ModulePath(tuple(rest)))
328361
return None
329362

0 commit comments

Comments
 (0)