Package: import-guard
import-guardEnforce that some modules can't be imported from other modules. In runtime!If you need a static analysis tools, take a look atflake8-import-graph.Features:works in runtimechecks dynamic importscustomizable rulesThis library has some performance overhead.
In some cases it may lead to 1.5-2x slower import time (and startup time respectively).
It's recommended to enable import_guard only during the development.Installationpipinstallimport-guardUsagefromimport_guardimportguard,modguard.set_deny_rules({# deny `csv` import from `test_proj` and submodules"test_proj":"csv",# the same as mod("csv")# deny `selenium` and top_level `test_proj.tasks` imports from test_proj.api# but allow `test_proj.tasks` import inside the function (lazy import)# the same as mod("selenium") | (mod("test_proj.tasks") & Flags.TopLevel)"test_proj.api":["selenium",mod.top_level("test_proj.tasks")],# deny `test_proj.api` and `test_proj.business_logic` imports from `test_proj.core`"test_proj.core":mod.matches(r"test_proj\.(api|business_logic)"),# deny all imports except `logging` and `yaml`"test_proj.logging":~mod.explicit(["logging","yaml"]),})# raise ForbiddenImportErrorguard.enable(strict=True)Rulesthe code below is copy-pastable into the Python interpreter
and assumes the following imports:fromimportlibimportreloadfromimport_guardimportguard,mod# enable guard in advanceguard.enable()Exact matchguard.set_deny_rules({"<stdin>":"decimal"})# shortcut for mod("decimal")fromdecimalimportDecimal# shows warningfromenumimportEnum# okExplicit matchConsider the following code:guard.set_deny_rules({"<stdin>":"re"})importcsv# shows warning!What happened?csvimports some modules under the hood, e.g.reorio.
We implicitly initiated loading of theremodule through thecsvmodule (rule matches at depth = 1).
This is the default behavior. You can check only explicit imports usingmod.explicit("re")function.guard.set_deny_rules({"<stdin>":mod.explicit("re")})reload(csv)# allowedimportre# shows warningMatch multiple modulesguard.set_deny_rules({"<stdin>":["logging","json"]})# the same as mod.any(["logging", "json'])# the same as mod("logging") | mod("json")importjson# shows warningfromloggingimportgetLogger# shows warningMatch by regular expressionguard.set_deny_rules({"<stdin>":mod.matches("log.*")})# shows multiple warningsfromlogging.configimportdictConfigInversionguard.set_deny_rules({"<stdin>":~mod.matches("log.*")})importio# shows warningMatch only module-level importsIt's common practice doing a local import instead of a global one to break a
cycle import or to postpone importing until you run code that actually needs
the module you're importing.# deny module-level importsguard.set_deny_rules({"<stdin>":mod.top_level("array")})defsome_function():importarray# allowed (lazy import)some_function()importarray# shows warningMatch star importguard.set_deny_rules({"<stdin>":mod.star("csv")})fromcsvimport*# shows warningComplex rulesRules are very flexible. You can combine them together in a different ways
and build very complex conditions.mod.explicit(~mod.top_level(["math","json"])|mod.matches("log.*"))Nice examples:deny non-lazy imports in some module:guard.set_deny_rules({"test_proj.business_logic":mod.top_level(mod.matches(".*")),})deny start imports in project:guard.set_deny_rules({"test_proj":mod.star(mod.explicit(mod.matches(".*"))),})Non-strict mode# not enabled for `prod`ifenv=="staging":# warn on forbidden importguard.enable(strict=False)elifenv=="local":# raise ForbiddenImportErrorguard.enable(strict=True)Rules hierarchyThe set of deny rule for a module also affects its submodules.guard.set_deny_rules({"test_proj":"json","test_proj.api":["selenum","pandas"],"test_proj.core":"celery"})test_proj.coredisallowsjsonandceleryimports.test_proj.api.viewsdisallowsjson,selenium,pandasimports.Lazy moduleConsider the following project structure:# main.pyimportapi# api.pydefview():importtasks# tasks.pyimportpandasHeremain.pyimportsapi, which importstaskslazily, which importspandasat module level.import_guardhandles this case as lazy module import and will think that pandas being imported lazily.
Thus, in this case, the following rules do not raise a warning:guard.set_deny_rules({"tasks":mod.top_level("pandas")})Custom module matcherdefis_relative_import(import_info,caller_info):returnimport_info.level>1# deny relative importguard.set_deny_rules({"proj":mod.hook(is_relative_import)})from.apiimportview# shows warningfromproj.apiimportview# okTestingRulesTesting rules directly:rule=mod.top_level(mod.matches(".*"))# True; mod1 imported at the module level in mod2rule.test("mod1",caller="mod2")# False; mod1 doesn't match the top_level constraintrule.test("mod1",caller="<stdin>",top_level=False)Testing deny rules through the guard:guard.is_import_allowed("csv",caller="test_proj.api")# Falseguard.is_import_allowed("logging",caller="test_proj.api")# Trueguard.is_import_allowed("selenium",caller="test_proj.api")# Falseguard.is_import_allowed("test_proj.tasks",caller="test_proj.api")# Falseguard.is_import_allowed("test_proj.tasks",caller="test_proj.api",top_level=False)# TrueUnit testsTesting with current Python interpreter:$python-munittestdiscovertests-vTesting with different Python versions and interpreters:$tox