如何检查 Python 包中的任何模块是否从另一个包导入?
How to check if any module in a Python package imports from another package?
我想确保一个包 ("pkg-foo
") 中的所有模块都不会从另一个包 ("pkg-block
") 导入。
更新:由于Python的动态性,我知道有很多黑魔法导入模块的方法。但是,我只对检查显式导入感兴趣(例如 import pkg.block
或 from pkg.block import ...
)。
我想通过 pkg-foo
中的单元测试强制执行此操作,以确保它永远不会从 pkg-block
.
导入
我怎样才能做到这一点?我使用 Python 3.8+ 并希望使用内置插件或 setuptools
.
当前半生不熟的解决方案
# pkg_resources is from setuptools
from pkg_resources import Distribution, working_set
# Confirm pgk-block is not in pkg-foo's install_requires
foo_pkg: Distribution = working_set.by_key[f"foo-pkg"]
for req in foo_pkg.requires():
assert "pkg-block" not in str(req)
然而,仅仅因为 pkg-block
没有在 setup.py
的 install_requires
中声明并不意味着它没有被导入包中。所以,这只是一个半生不熟的解决方案。
我的想法是我需要抓取 pkg-foo
中的所有模块并检查每个模块是否从 pgk-block
导入。
所以我的建议是从概念上把这个问题分成两部分。
第一个子问题:确定在pkg-foo
中导入的所有模块。让我们使用 mod_foo
作为 pkg-foo
中的任意导入模块
第二个子问题:判断是否有mod_foo
来自pkg-block
。如果这些模块中有 none 个在 pkg-block
中,则通过单元测试,否则,单元测试失败。
要解决第一个子问题,您可以使用class modulefinder.ModuleFinder
。如the example from the documentation所示,可以对pkg-foo
中的每个模块做modulefinder.ModuleFinder.run_script(pathname)
。然后你可以通过从字典 modulefinder.ModuleFinder.modules
中抓取键来获取模块名称。所有这些模块都将成为您的 mod-foo
个模块。
要解决第二个子问题,可以使用mod_foo.__spec__
如前所述here, mod_foo.__spec__
will be an instance of 'importlib.machinery.ModuleSpec' which is defined here。如刚刚链接到的文档中所述,此对象将具有属性 name
,即:
A string for the fully-qualified name of the module.
因此我们需要检查 pkg-block
是否在 mod_foo.__spec__.name
为每个 mod_foo
给出的完全限定名称中。
将所有这些放在一起,按照以下代码行应该可以满足您的需要:
import modulefinder
def verify_no_banned_package(pkg_foo_modules, pkg_ban):
"""
Package Checker
:param pkg_foo_modules: list of the pathnames of the modules in pkg-foo
:param pkg_ban: banned package
:return: True if banned package not present in imports, False otherwise
"""
imported_modules = set()
for mod in pkg_foo_modules:
mod_finder = modulefinder.ModuleFinder()
mod_finder.run_script(mod)
mod_foo_import_mods = mod_finder.modules.keys()
imported_modules.update(mod_foo_import_mods)
for mod_foo in imported_modules:
mod_foo_parent_full_name = mod_foo.__spec__.name
if pkg_ban in mod_foo_parent_full_name.split(sep="."):
return False
return True
我认为在你的情况下最简单的方法是模拟一个不应导入的模块,即 pkg-block
。为了模拟一个模块把它放在 sys.path
的开头,最简单的方法可能是在项目文件夹中创建一个假模块(或临时替换原来的模块),所以它最终会在你的 PYTHONPATH
马上。这会将所有导入“重定向”到您的模拟模块。在 mock 模块本身中放置如下内容:
print(f'Module {__name__} imported')
def __getattr__(name):
print(f'Imported attribute {name} from module {__name__}')
上面的代码将打印一行,通知您模块或其属性已导入,这适用于直接模块导入和 from
导入,函数内部导入,importlib
导入(除了通过路径导入,但我觉得这太深奥了)。为了简化单元测试中导入的检测,您可能希望引发异常而不仅仅是打印。您也不必进行特定测试来验证导入是否未发生,而只需 运行 您的常规测试套件 pkg-foo
并查看是否有任何测试因特定于导入的异常而失败。
示例:
假设文件结构如下:
|---main.py
|
\---pkg
|---block.py
|---foo.py
|---__init__.py
其中 pkg/block.py
print(f'Module {__name__} imported')
def __getattr__(name):
print(f'Imported attribute {name} from module {__name__}')
pkg/foo.py
import importlib
import pkg.block
from . import block
from .block import abc
importlib.import_module('pkg.block')
def from_function():
from pkg.block import from_function
main.py
import pkg.foo
if __name__ == '__main__':
pkg.foo.from_function()
然后在执行 main.py
之后你会得到:
Module pkg.block imported
Imported attribute __path__ from module pkg.block
Imported attribute abc from module pkg.block
Imported attribute abc from module pkg.block
Imported attribute __path__ from module pkg.block
Imported attribute from_function from module pkg.block
Imported attribute from_function from module pkg.block
我想确保一个包 ("pkg-foo
") 中的所有模块都不会从另一个包 ("pkg-block
") 导入。
更新:由于Python的动态性,我知道有很多黑魔法导入模块的方法。但是,我只对检查显式导入感兴趣(例如 import pkg.block
或 from pkg.block import ...
)。
我想通过 pkg-foo
中的单元测试强制执行此操作,以确保它永远不会从 pkg-block
.
我怎样才能做到这一点?我使用 Python 3.8+ 并希望使用内置插件或 setuptools
.
当前半生不熟的解决方案
# pkg_resources is from setuptools
from pkg_resources import Distribution, working_set
# Confirm pgk-block is not in pkg-foo's install_requires
foo_pkg: Distribution = working_set.by_key[f"foo-pkg"]
for req in foo_pkg.requires():
assert "pkg-block" not in str(req)
然而,仅仅因为 pkg-block
没有在 setup.py
的 install_requires
中声明并不意味着它没有被导入包中。所以,这只是一个半生不熟的解决方案。
我的想法是我需要抓取 pkg-foo
中的所有模块并检查每个模块是否从 pgk-block
导入。
所以我的建议是从概念上把这个问题分成两部分。
第一个子问题:确定在pkg-foo
中导入的所有模块。让我们使用 mod_foo
作为 pkg-foo
第二个子问题:判断是否有mod_foo
来自pkg-block
。如果这些模块中有 none 个在 pkg-block
中,则通过单元测试,否则,单元测试失败。
要解决第一个子问题,您可以使用class modulefinder.ModuleFinder
。如the example from the documentation所示,可以对pkg-foo
中的每个模块做modulefinder.ModuleFinder.run_script(pathname)
。然后你可以通过从字典 modulefinder.ModuleFinder.modules
中抓取键来获取模块名称。所有这些模块都将成为您的 mod-foo
个模块。
要解决第二个子问题,可以使用mod_foo.__spec__
如前所述here, mod_foo.__spec__
will be an instance of 'importlib.machinery.ModuleSpec' which is defined here。如刚刚链接到的文档中所述,此对象将具有属性 name
,即:
A string for the fully-qualified name of the module.
因此我们需要检查 pkg-block
是否在 mod_foo.__spec__.name
为每个 mod_foo
给出的完全限定名称中。
将所有这些放在一起,按照以下代码行应该可以满足您的需要:
import modulefinder
def verify_no_banned_package(pkg_foo_modules, pkg_ban):
"""
Package Checker
:param pkg_foo_modules: list of the pathnames of the modules in pkg-foo
:param pkg_ban: banned package
:return: True if banned package not present in imports, False otherwise
"""
imported_modules = set()
for mod in pkg_foo_modules:
mod_finder = modulefinder.ModuleFinder()
mod_finder.run_script(mod)
mod_foo_import_mods = mod_finder.modules.keys()
imported_modules.update(mod_foo_import_mods)
for mod_foo in imported_modules:
mod_foo_parent_full_name = mod_foo.__spec__.name
if pkg_ban in mod_foo_parent_full_name.split(sep="."):
return False
return True
我认为在你的情况下最简单的方法是模拟一个不应导入的模块,即 pkg-block
。为了模拟一个模块把它放在 sys.path
的开头,最简单的方法可能是在项目文件夹中创建一个假模块(或临时替换原来的模块),所以它最终会在你的 PYTHONPATH
马上。这会将所有导入“重定向”到您的模拟模块。在 mock 模块本身中放置如下内容:
print(f'Module {__name__} imported')
def __getattr__(name):
print(f'Imported attribute {name} from module {__name__}')
上面的代码将打印一行,通知您模块或其属性已导入,这适用于直接模块导入和 from
导入,函数内部导入,importlib
导入(除了通过路径导入,但我觉得这太深奥了)。为了简化单元测试中导入的检测,您可能希望引发异常而不仅仅是打印。您也不必进行特定测试来验证导入是否未发生,而只需 运行 您的常规测试套件 pkg-foo
并查看是否有任何测试因特定于导入的异常而失败。
示例:
假设文件结构如下:
|---main.py
|
\---pkg
|---block.py
|---foo.py
|---__init__.py
其中 pkg/block.py
print(f'Module {__name__} imported')
def __getattr__(name):
print(f'Imported attribute {name} from module {__name__}')
pkg/foo.py
import importlib
import pkg.block
from . import block
from .block import abc
importlib.import_module('pkg.block')
def from_function():
from pkg.block import from_function
main.py
import pkg.foo
if __name__ == '__main__':
pkg.foo.from_function()
然后在执行 main.py
之后你会得到:
Module pkg.block imported
Imported attribute __path__ from module pkg.block
Imported attribute abc from module pkg.block
Imported attribute abc from module pkg.block
Imported attribute __path__ from module pkg.block
Imported attribute from_function from module pkg.block
Imported attribute from_function from module pkg.block