Package: pytest-archon
pytest-archonpytest-archonis a little tool that helps you structure (large) Python projects. This
tool allows you to define architectural boundaries in your code, also known asforbidden dependencies.Explicitly defined architectural boundaries helps you keep your code in shape. It avoids
the creation of circular dependencies. New people on the project are made aware of the
structure through a simple set of rules, instead of lore.InstallationThe simple way:pipinstallpytest-archonUsagepytest-archoncan be used to define architectural boundaries from (unit) tests.
Because they're tests, they can be closely tied to the actual application.You can usepytest-archonin tests by simply importing thearchrulefunction. Using
this function you can construct import tests:frompytest_archonimportarchruledeftest_rule_basic():(archrule("name",comment="some comment").match("pytest_archon.col*").exclude("pytest_archon.colgate").should_not_import("pytest_archon.import_finder").should_import("pytest_archon.core*").check("pytest_archon"))To match the modules and constraints,fnmatchsyntax is used (the
default). You can also useregular expressionsby supplying theuse_regex=Trueargument toarchrule(). Example:archrule(..., use_regex=True).match(...)..exclude()is optional.should_import()and.should_not_import()can be combined and can occur multiple
times..may_import()can be used in combination with.should_not_import()..check()needs either a module object or a stringThecheck()method can have a few optional parameters, that alter the way the checks
are performed.Without parameters, the whole file is checked for imports. So imports done in
functions and methods are also found. Transitive dependencies are also checkedOptiononly_toplevel_imports=Truewill only check for toplevel imports. Conditional
imports and import in functions and methods are ignored.skip_type_checking=Truewill check all imports, but skip imports defined inif typing.TYPE_CHECKINGblocks.only_direct_imports=Truewill only check for imports performed by the module
directly and will not check transitive imports.Ifonly_toplevel_imports=Trueis set,skip_type_checking=Truehas no effect.Options can be combined.Check toplevel importsCheckTYPE_CHECKINGimportsCheck conditional imports, and imports in functions and methodsCheck transitive importsno options enabled✓✓✓✓skip_type_checking=True✓✗✓✓only_toplevel_imports=True✓✗✗✓only_direct_imports=True✓✓✓✗ExampleDomain model has no dependenciesdeftest_domain():# test if the domain model does not import other submodules# (the domain model should be standing on its own and be used by other modules)(archrule("domain",comment="domain does not import any other submodules").match("packageX.domain*")# matches packageX.domain and packageX.domain.*.should_not_import("packageX*").may_import("packageX.domain.*").check("packageX"))utilmodule is used at more than one placeYou can also supply custom constraints as predicate functions.If you, for example, have a common or util module, you might want to make sure that it
is used at least at two places (otherwise it would not make sense to have a separate
module).frompytest_archonimportarchruledeftest_utils_are_shared():defhave_at_least_two_users(util_module,direct_imports,all_imports):# iterate through all imports and find modules using the util_module in questionusers=[kfork,vinall_imports.items()ifutil_moduleinv]# return True if more than two modules use the util_modulereturnlen(users)>2archrule("util_is_shared").match("pkg.util").should(have_at_least_two_users).check("pkg")See alsoThe blog postHow to tame your Python codebaseis also a good overview.Similar projectsArchunit(Java)Dependency Cruiser(Javascript)import-linter(Python)pytestarch(Python)Maintain A Clean Architecture With Dependency Rules - sourcery.ai(Python)