modutils.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. # Copyright (c) 2014-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
  2. # Copyright (c) 2014 Google, Inc.
  3. # Copyright (c) 2014 Denis Laxalde <denis.laxalde@logilab.fr>
  4. # Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
  5. # Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
  6. # Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
  7. # Copyright (c) 2015 Radosław Ganczarek <radoslaw@ganczarek.in>
  8. # Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
  9. # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
  10. # Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
  11. # Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
  12. # Copyright (c) 2018 Mario Corchero <mcorcherojim@bloomberg.net>
  13. # Copyright (c) 2018 Mario Corchero <mariocj89@gmail.com>
  14. # Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
  15. # Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
  16. # Copyright (c) 2019 markmcclain <markmcclain@users.noreply.github.com>
  17. # Copyright (c) 2019 BasPH <BasPH@users.noreply.github.com>
  18. # Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
  19. # Copyright (c) 2020 Peter Kolbus <peter.kolbus@gmail.com>
  20. # Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
  21. # Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
  22. # Copyright (c) 2021 Keichi Takahashi <hello@keichi.dev>
  23. # Copyright (c) 2021 Nick Drozd <nicholasdrozd@gmail.com>
  24. # Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
  25. # Copyright (c) 2021 DudeNr33 <3929834+DudeNr33@users.noreply.github.com>
  26. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  27. # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
  28. """Python modules manipulation utility functions.
  29. :type PY_SOURCE_EXTS: tuple(str)
  30. :var PY_SOURCE_EXTS: list of possible python source file extension
  31. :type STD_LIB_DIRS: set of str
  32. :var STD_LIB_DIRS: directories where standard modules are located
  33. :type BUILTIN_MODULES: dict
  34. :var BUILTIN_MODULES: dictionary with builtin module names has key
  35. """
  36. # We disable the import-error so pylint can work without distutils installed.
  37. # pylint: disable=no-name-in-module,useless-suppression
  38. import importlib
  39. import importlib.machinery
  40. import importlib.util
  41. import itertools
  42. import os
  43. import platform
  44. import sys
  45. import types
  46. from distutils.errors import DistutilsPlatformError # pylint: disable=import-error
  47. from distutils.sysconfig import get_python_lib # pylint: disable=import-error
  48. from typing import Dict, Set
  49. from astroid.interpreter._import import spec, util
  50. # distutils is replaced by virtualenv with a module that does
  51. # weird path manipulations in order to get to the
  52. # real distutils module.
  53. if sys.platform.startswith("win"):
  54. PY_SOURCE_EXTS = ("py", "pyw")
  55. PY_COMPILED_EXTS = ("dll", "pyd")
  56. else:
  57. PY_SOURCE_EXTS = ("py",)
  58. PY_COMPILED_EXTS = ("so",)
  59. try:
  60. # The explicit sys.prefix is to work around a patch in virtualenv that
  61. # replaces the 'real' sys.prefix (i.e. the location of the binary)
  62. # with the prefix from which the virtualenv was created. This throws
  63. # off the detection logic for standard library modules, thus the
  64. # workaround.
  65. STD_LIB_DIRS = {
  66. get_python_lib(standard_lib=True, prefix=sys.prefix),
  67. # Take care of installations where exec_prefix != prefix.
  68. get_python_lib(standard_lib=True, prefix=sys.exec_prefix),
  69. get_python_lib(standard_lib=True),
  70. }
  71. # get_python_lib(standard_lib=1) is not available on pypy, set STD_LIB_DIR to
  72. # non-valid path, see https://bugs.pypy.org/issue1164
  73. except DistutilsPlatformError:
  74. STD_LIB_DIRS = set()
  75. if os.name == "nt":
  76. STD_LIB_DIRS.add(os.path.join(sys.prefix, "dlls"))
  77. try:
  78. # real_prefix is defined when running inside virtual environments,
  79. # created with the **virtualenv** library.
  80. # Deprecated in virtualenv==16.7.9
  81. # See: https://github.com/pypa/virtualenv/issues/1622
  82. STD_LIB_DIRS.add(os.path.join(sys.real_prefix, "dlls")) # type: ignore[attr-defined]
  83. except AttributeError:
  84. # sys.base_exec_prefix is always defined, but in a virtual environment
  85. # created with the stdlib **venv** module, it points to the original
  86. # installation, if the virtual env is activated.
  87. try:
  88. STD_LIB_DIRS.add(os.path.join(sys.base_exec_prefix, "dlls"))
  89. except AttributeError:
  90. pass
  91. if platform.python_implementation() == "PyPy":
  92. # The get_python_lib(standard_lib=True) function does not give valid
  93. # result with pypy in a virtualenv.
  94. # In a virtual environment, with CPython implementation the call to this function returns a path toward
  95. # the binary (its libraries) which has been used to create the virtual environment.
  96. # Not with pypy implementation.
  97. # The only way to retrieve such information is to use the sys.base_prefix hint.
  98. # It's worth noticing that under CPython implementation the return values of
  99. # get_python_lib(standard_lib=True) and get_python_lib(santdard_lib=True, prefix=sys.base_prefix)
  100. # are the same.
  101. # In the lines above, we could have replace the call to get_python_lib(standard=True)
  102. # with the one using prefix=sys.base_prefix but we prefer modifying only what deals with pypy.
  103. STD_LIB_DIRS.add(get_python_lib(standard_lib=True, prefix=sys.base_prefix))
  104. _root = os.path.join(sys.prefix, "lib_pypy")
  105. STD_LIB_DIRS.add(_root)
  106. try:
  107. # real_prefix is defined when running inside virtualenv.
  108. STD_LIB_DIRS.add(os.path.join(sys.base_prefix, "lib_pypy"))
  109. except AttributeError:
  110. pass
  111. del _root
  112. if os.name == "posix":
  113. # Need the real prefix if we're in a virtualenv, otherwise
  114. # the usual one will do.
  115. # Deprecated in virtualenv==16.7.9
  116. # See: https://github.com/pypa/virtualenv/issues/1622
  117. try:
  118. prefix = sys.real_prefix # type: ignore[attr-defined]
  119. except AttributeError:
  120. prefix = sys.prefix
  121. def _posix_path(path):
  122. base_python = "python%d.%d" % sys.version_info[:2]
  123. return os.path.join(prefix, path, base_python)
  124. STD_LIB_DIRS.add(_posix_path("lib"))
  125. if sys.maxsize > 2 ** 32:
  126. # This tries to fix a problem with /usr/lib64 builds,
  127. # where systems are running both 32-bit and 64-bit code
  128. # on the same machine, which reflects into the places where
  129. # standard library could be found. More details can be found
  130. # here http://bugs.python.org/issue1294959.
  131. # An easy reproducing case would be
  132. # https://github.com/PyCQA/pylint/issues/712#issuecomment-163178753
  133. STD_LIB_DIRS.add(_posix_path("lib64"))
  134. EXT_LIB_DIRS = {get_python_lib(), get_python_lib(True)}
  135. IS_JYTHON = platform.python_implementation() == "Jython"
  136. BUILTIN_MODULES = dict.fromkeys(sys.builtin_module_names, True)
  137. class NoSourceFile(Exception):
  138. """exception raised when we are not able to get a python
  139. source file for a precompiled file
  140. """
  141. def _normalize_path(path: str) -> str:
  142. """Resolve symlinks in path and convert to absolute path.
  143. Note that environment variables and ~ in the path need to be expanded in
  144. advance.
  145. This can be cached by using _cache_normalize_path.
  146. """
  147. return os.path.normcase(os.path.realpath(path))
  148. def _path_from_filename(filename, is_jython=IS_JYTHON):
  149. if not is_jython:
  150. return filename
  151. head, has_pyclass, _ = filename.partition("$py.class")
  152. if has_pyclass:
  153. return head + ".py"
  154. return filename
  155. def _handle_blacklist(blacklist, dirnames, filenames):
  156. """remove files/directories in the black list
  157. dirnames/filenames are usually from os.walk
  158. """
  159. for norecurs in blacklist:
  160. if norecurs in dirnames:
  161. dirnames.remove(norecurs)
  162. elif norecurs in filenames:
  163. filenames.remove(norecurs)
  164. _NORM_PATH_CACHE: Dict[str, str] = {}
  165. def _cache_normalize_path(path: str) -> str:
  166. """Normalize path with caching."""
  167. # _module_file calls abspath on every path in sys.path every time it's
  168. # called; on a larger codebase this easily adds up to half a second just
  169. # assembling path components. This cache alleviates that.
  170. try:
  171. return _NORM_PATH_CACHE[path]
  172. except KeyError:
  173. if not path: # don't cache result for ''
  174. return _normalize_path(path)
  175. result = _NORM_PATH_CACHE[path] = _normalize_path(path)
  176. return result
  177. def load_module_from_name(dotted_name: str) -> types.ModuleType:
  178. """Load a Python module from its name.
  179. :type dotted_name: str
  180. :param dotted_name: python name of a module or package
  181. :raise ImportError: if the module or package is not found
  182. :rtype: module
  183. :return: the loaded module
  184. """
  185. try:
  186. return sys.modules[dotted_name]
  187. except KeyError:
  188. pass
  189. return importlib.import_module(dotted_name)
  190. def load_module_from_modpath(parts):
  191. """Load a python module from its split name.
  192. :type parts: list(str) or tuple(str)
  193. :param parts:
  194. python name of a module or package split on '.'
  195. :raise ImportError: if the module or package is not found
  196. :rtype: module
  197. :return: the loaded module
  198. """
  199. return load_module_from_name(".".join(parts))
  200. def load_module_from_file(filepath: str):
  201. """Load a Python module from it's path.
  202. :type filepath: str
  203. :param filepath: path to the python module or package
  204. :raise ImportError: if the module or package is not found
  205. :rtype: module
  206. :return: the loaded module
  207. """
  208. modpath = modpath_from_file(filepath)
  209. return load_module_from_modpath(modpath)
  210. def check_modpath_has_init(path, mod_path):
  211. """check there are some __init__.py all along the way"""
  212. modpath = []
  213. for part in mod_path:
  214. modpath.append(part)
  215. path = os.path.join(path, part)
  216. if not _has_init(path):
  217. old_namespace = util.is_namespace(".".join(modpath))
  218. if not old_namespace:
  219. return False
  220. return True
  221. def _get_relative_base_path(filename, path_to_check):
  222. """Extracts the relative mod path of the file to import from
  223. Check if a file is within the passed in path and if so, returns the
  224. relative mod path from the one passed in.
  225. If the filename is no in path_to_check, returns None
  226. Note this function will look for both abs and realpath of the file,
  227. this allows to find the relative base path even if the file is a
  228. symlink of a file in the passed in path
  229. Examples:
  230. _get_relative_base_path("/a/b/c/d.py", "/a/b") -> ["c","d"]
  231. _get_relative_base_path("/a/b/c/d.py", "/dev") -> None
  232. """
  233. importable_path = None
  234. path_to_check = os.path.normcase(path_to_check)
  235. abs_filename = os.path.abspath(filename)
  236. if os.path.normcase(abs_filename).startswith(path_to_check):
  237. importable_path = abs_filename
  238. real_filename = os.path.realpath(filename)
  239. if os.path.normcase(real_filename).startswith(path_to_check):
  240. importable_path = real_filename
  241. # if "var" in path_to_check:
  242. # breakpoint()
  243. if importable_path:
  244. base_path = os.path.splitext(importable_path)[0]
  245. relative_base_path = base_path[len(path_to_check) :]
  246. return [pkg for pkg in relative_base_path.split(os.sep) if pkg]
  247. return None
  248. def modpath_from_file_with_callback(filename, path=None, is_package_cb=None):
  249. filename = os.path.expanduser(_path_from_filename(filename))
  250. paths_to_check = sys.path.copy()
  251. if path:
  252. paths_to_check += path
  253. for pathname in itertools.chain(
  254. paths_to_check, map(_cache_normalize_path, paths_to_check)
  255. ):
  256. if not pathname:
  257. continue
  258. modpath = _get_relative_base_path(filename, pathname)
  259. if not modpath:
  260. continue
  261. if is_package_cb(pathname, modpath[:-1]):
  262. return modpath
  263. raise ImportError(
  264. "Unable to find module for {} in {}".format(filename, ", \n".join(sys.path))
  265. )
  266. def modpath_from_file(filename, path=None):
  267. """Get the corresponding split module's name from a filename
  268. This function will return the name of a module or package split on `.`.
  269. :type filename: str
  270. :param filename: file's path for which we want the module's name
  271. :type Optional[List[str]] path:
  272. Optional list of path where the module or package should be
  273. searched (use sys.path if nothing or None is given)
  274. :raise ImportError:
  275. if the corresponding module's name has not been found
  276. :rtype: list(str)
  277. :return: the corresponding split module's name
  278. """
  279. return modpath_from_file_with_callback(filename, path, check_modpath_has_init)
  280. def file_from_modpath(modpath, path=None, context_file=None):
  281. return file_info_from_modpath(modpath, path, context_file).location
  282. def file_info_from_modpath(modpath, path=None, context_file=None):
  283. """given a mod path (i.e. split module / package name), return the
  284. corresponding file, giving priority to source file over precompiled
  285. file if it exists
  286. :type modpath: list or tuple
  287. :param modpath:
  288. split module's name (i.e name of a module or package split
  289. on '.')
  290. (this means explicit relative imports that start with dots have
  291. empty strings in this list!)
  292. :type path: list or None
  293. :param path:
  294. optional list of path where the module or package should be
  295. searched (use sys.path if nothing or None is given)
  296. :type context_file: str or None
  297. :param context_file:
  298. context file to consider, necessary if the identifier has been
  299. introduced using a relative import unresolvable in the actual
  300. context (i.e. modutils)
  301. :raise ImportError: if there is no such module in the directory
  302. :rtype: (str or None, import type)
  303. :return:
  304. the path to the module's file or None if it's an integrated
  305. builtin module such as 'sys'
  306. """
  307. if context_file is not None:
  308. context = os.path.dirname(context_file)
  309. else:
  310. context = context_file
  311. if modpath[0] == "xml":
  312. # handle _xmlplus
  313. try:
  314. return _spec_from_modpath(["_xmlplus"] + modpath[1:], path, context)
  315. except ImportError:
  316. return _spec_from_modpath(modpath, path, context)
  317. elif modpath == ["os", "path"]:
  318. # FIXME: currently ignoring search_path...
  319. return spec.ModuleSpec(
  320. name="os.path",
  321. location=os.path.__file__,
  322. module_type=spec.ModuleType.PY_SOURCE,
  323. )
  324. return _spec_from_modpath(modpath, path, context)
  325. def get_module_part(dotted_name, context_file=None):
  326. """given a dotted name return the module part of the name :
  327. >>> get_module_part('astroid.as_string.dump')
  328. 'astroid.as_string'
  329. :type dotted_name: str
  330. :param dotted_name: full name of the identifier we are interested in
  331. :type context_file: str or None
  332. :param context_file:
  333. context file to consider, necessary if the identifier has been
  334. introduced using a relative import unresolvable in the actual
  335. context (i.e. modutils)
  336. :raise ImportError: if there is no such module in the directory
  337. :rtype: str or None
  338. :return:
  339. the module part of the name or None if we have not been able at
  340. all to import the given name
  341. XXX: deprecated, since it doesn't handle package precedence over module
  342. (see #10066)
  343. """
  344. # os.path trick
  345. if dotted_name.startswith("os.path"):
  346. return "os.path"
  347. parts = dotted_name.split(".")
  348. if context_file is not None:
  349. # first check for builtin module which won't be considered latter
  350. # in that case (path != None)
  351. if parts[0] in BUILTIN_MODULES:
  352. if len(parts) > 2:
  353. raise ImportError(dotted_name)
  354. return parts[0]
  355. # don't use += or insert, we want a new list to be created !
  356. path = None
  357. starti = 0
  358. if parts[0] == "":
  359. assert (
  360. context_file is not None
  361. ), "explicit relative import, but no context_file?"
  362. path = [] # prevent resolving the import non-relatively
  363. starti = 1
  364. while parts[starti] == "": # for all further dots: change context
  365. starti += 1
  366. context_file = os.path.dirname(context_file)
  367. for i in range(starti, len(parts)):
  368. try:
  369. file_from_modpath(
  370. parts[starti : i + 1], path=path, context_file=context_file
  371. )
  372. except ImportError:
  373. if i < max(1, len(parts) - 2):
  374. raise
  375. return ".".join(parts[:i])
  376. return dotted_name
  377. def get_module_files(src_directory, blacklist, list_all=False):
  378. """given a package directory return a list of all available python
  379. module's files in the package and its subpackages
  380. :type src_directory: str
  381. :param src_directory:
  382. path of the directory corresponding to the package
  383. :type blacklist: list or tuple
  384. :param blacklist: iterable
  385. list of files or directories to ignore.
  386. :type list_all: bool
  387. :param list_all:
  388. get files from all paths, including ones without __init__.py
  389. :rtype: list
  390. :return:
  391. the list of all available python module's files in the package and
  392. its subpackages
  393. """
  394. files = []
  395. for directory, dirnames, filenames in os.walk(src_directory):
  396. if directory in blacklist:
  397. continue
  398. _handle_blacklist(blacklist, dirnames, filenames)
  399. # check for __init__.py
  400. if not list_all and "__init__.py" not in filenames:
  401. dirnames[:] = ()
  402. continue
  403. for filename in filenames:
  404. if _is_python_file(filename):
  405. src = os.path.join(directory, filename)
  406. files.append(src)
  407. return files
  408. def get_source_file(filename, include_no_ext=False):
  409. """given a python module's file name return the matching source file
  410. name (the filename will be returned identically if it's already an
  411. absolute path to a python source file...)
  412. :type filename: str
  413. :param filename: python module's file name
  414. :raise NoSourceFile: if no source file exists on the file system
  415. :rtype: str
  416. :return: the absolute path of the source file if it exists
  417. """
  418. filename = os.path.abspath(_path_from_filename(filename))
  419. base, orig_ext = os.path.splitext(filename)
  420. for ext in PY_SOURCE_EXTS:
  421. source_path = f"{base}.{ext}"
  422. if os.path.exists(source_path):
  423. return source_path
  424. if include_no_ext and not orig_ext and os.path.exists(base):
  425. return base
  426. raise NoSourceFile(filename)
  427. def is_python_source(filename):
  428. """
  429. rtype: bool
  430. return: True if the filename is a python source file
  431. """
  432. return os.path.splitext(filename)[1][1:] in PY_SOURCE_EXTS
  433. def is_standard_module(modname, std_path=None):
  434. """try to guess if a module is a standard python module (by default,
  435. see `std_path` parameter's description)
  436. :type modname: str
  437. :param modname: name of the module we are interested in
  438. :type std_path: list(str) or tuple(str)
  439. :param std_path: list of path considered has standard
  440. :rtype: bool
  441. :return:
  442. true if the module:
  443. - is located on the path listed in one of the directory in `std_path`
  444. - is a built-in module
  445. """
  446. modname = modname.split(".")[0]
  447. try:
  448. filename = file_from_modpath([modname])
  449. except ImportError:
  450. # import failed, i'm probably not so wrong by supposing it's
  451. # not standard...
  452. return False
  453. # modules which are not living in a file are considered standard
  454. # (sys and __builtin__ for instance)
  455. if filename is None:
  456. # we assume there are no namespaces in stdlib
  457. return not util.is_namespace(modname)
  458. filename = _normalize_path(filename)
  459. for path in EXT_LIB_DIRS:
  460. if filename.startswith(_cache_normalize_path(path)):
  461. return False
  462. if std_path is None:
  463. std_path = STD_LIB_DIRS
  464. return any(filename.startswith(_cache_normalize_path(path)) for path in std_path)
  465. def is_relative(modname, from_file):
  466. """return true if the given module name is relative to the given
  467. file name
  468. :type modname: str
  469. :param modname: name of the module we are interested in
  470. :type from_file: str
  471. :param from_file:
  472. path of the module from which modname has been imported
  473. :rtype: bool
  474. :return:
  475. true if the module has been imported relatively to `from_file`
  476. """
  477. if not os.path.isdir(from_file):
  478. from_file = os.path.dirname(from_file)
  479. if from_file in sys.path:
  480. return False
  481. return bool(
  482. importlib.machinery.PathFinder.find_spec(
  483. modname.split(".", maxsplit=1)[0], [from_file]
  484. )
  485. )
  486. # internal only functions #####################################################
  487. def _spec_from_modpath(modpath, path=None, context=None):
  488. """given a mod path (i.e. split module / package name), return the
  489. corresponding spec
  490. this function is used internally, see `file_from_modpath`'s
  491. documentation for more information
  492. """
  493. assert modpath
  494. location = None
  495. if context is not None:
  496. try:
  497. found_spec = spec.find_spec(modpath, [context])
  498. location = found_spec.location
  499. except ImportError:
  500. found_spec = spec.find_spec(modpath, path)
  501. location = found_spec.location
  502. else:
  503. found_spec = spec.find_spec(modpath, path)
  504. if found_spec.type == spec.ModuleType.PY_COMPILED:
  505. try:
  506. location = get_source_file(found_spec.location)
  507. return found_spec._replace(
  508. location=location, type=spec.ModuleType.PY_SOURCE
  509. )
  510. except NoSourceFile:
  511. return found_spec._replace(location=location)
  512. elif found_spec.type == spec.ModuleType.C_BUILTIN:
  513. # integrated builtin module
  514. return found_spec._replace(location=None)
  515. elif found_spec.type == spec.ModuleType.PKG_DIRECTORY:
  516. location = _has_init(found_spec.location)
  517. return found_spec._replace(location=location, type=spec.ModuleType.PY_SOURCE)
  518. return found_spec
  519. def _is_python_file(filename):
  520. """return true if the given filename should be considered as a python file
  521. .pyc and .pyo are ignored
  522. """
  523. return filename.endswith((".py", ".so", ".pyd", ".pyw"))
  524. def _has_init(directory):
  525. """if the given directory has a valid __init__ file, return its path,
  526. else return None
  527. """
  528. mod_or_pack = os.path.join(directory, "__init__")
  529. for ext in PY_SOURCE_EXTS + ("pyc", "pyo"):
  530. if os.path.exists(mod_or_pack + "." + ext):
  531. return mod_or_pack + "." + ext
  532. return None
  533. def is_namespace(specobj):
  534. return specobj.type == spec.ModuleType.PY_NAMESPACE
  535. def is_directory(specobj):
  536. return specobj.type == spec.ModuleType.PKG_DIRECTORY
  537. def is_module_name_part_of_extension_package_whitelist(
  538. module_name: str, package_whitelist: Set[str]
  539. ) -> bool:
  540. """
  541. Returns True if one part of the module name is in the package whitelist
  542. >>> is_module_name_part_of_extension_package_whitelist('numpy.core.umath', {'numpy'})
  543. True
  544. """
  545. parts = module_name.split(".")
  546. return any(
  547. ".".join(parts[:x]) in package_whitelist for x in range(1, len(parts) + 1)
  548. )