Automatic inclusion of package dependencies =========================================== The z3c.autoinclude.dependency module uses an egg's install_requires information (in the project's setup.py) to find and implicitly load zcml from all dependencies of a project. We have created a test environment to simulate setuptools dependencies. ``APackage`` depends on ``BCPackage`` ``BCPackage`` depends on ``SiblingPackage`` Given the distribution for the project named ``APackage``, we can ask for the requirements of that distribution:: >>> import a >>> from z3c.autoinclude.utils import distributionForPackage >>> a_dist = distributionForPackage(a) >>> reqs = a_dist.requires() >>> pprint(sorted(reqs, key=lambda r:r.project_name)) [Requirement.parse('BCPackage'), Requirement.parse('TestDirective'), Requirement.parse('z3c.autoinclude')] We can turn this requirement into a distribution:: >>> from pkg_resources import get_provider >>> b_dist = get_provider(reqs[0]) We can adapt a distribution to a DependencyFinder:: >>> from z3c.autoinclude.dependency import DependencyFinder >>> a_include_finder = DependencyFinder(a_dist) >>> b_include_finder = DependencyFinder(b_dist) >>> import x.y.z >>> xyz_dist = distributionForPackage(x.y.z) >>> xyz_include_finder = DependencyFinder(xyz_dist) >>> import F.G >>> sibling_dist = distributionForPackage(F.G) >>> sibling_include_finder = DependencyFinder(sibling_dist) The include finder provides functionality to determine what namespace packages exist in the distribution. In the case of ``APackage``, there are no namespace packages:: >>> a_include_finder.namespaceDottedNames() [] ``BPackage`` does have a namespace package, ``b``:: >>> b_include_finder.namespaceDottedNames() ['b'] ``XYZPackage`` has a namespace package too, ``x.y`` (``x`` is also a namespace package):: >>> xyz_include_finder.namespaceDottedNames() ['x', 'x.y'] We can also get the dotted names of the actual packages that we want to inspect in a distribution. For a project without namespace packages, this will be the packages directly in the packages:: >>> a_include_finder.dottedNames() ['a'] For a project with namespace packages, it will be the packages that are in the namespace packages:: >>> b_include_finder.dottedNames() ['b.c'] For a nested namespace package, it should still be the innermost package:: >>> xyz_include_finder.dottedNames() ['x.y.z'] What we need to know in the end is which packages in the requirements of a distribution have files we want to include (``configure.zcml``, ``meta.zcml``). So, given a distribution, let's retrieve all packages that it depends on that have ``configure.zcml`` or ``meta.zcml``. Note that the individual lists within ``includableInfo`` preserve the package order defined in ``setup.py``:: >>> a_include_finder.includableInfo(['configure.zcml', 'meta.zcml']) {'configure.zcml': ['b.c'], 'meta.zcml': ['z3c.autoinclude', 'testdirective']} For a nested namespace package with two siblings ``SiblingPackage``, we should get the same expected results. The sibling package ``SiblingPackage`` does have a namespace package:: >>> sibling_include_finder.namespaceDottedNames() ['F'] For a namespace package with 2 sibling namespaces, we get both sibling packages:: >>> sibling_include_finder.dottedNames() ['F.G', 'F.H'] And we should be able to pick up the files we need to include from both dotted names:: >>> pprint(b_include_finder.includableInfo(['configure.zcml', ... 'meta.zcml'])) {'configure.zcml': ['F.H'], 'meta.zcml': ['testdirective', 'F.G', 'F.H']} ``APackage`` depends on ``BCPackage``, which depends on ``SiblingPackage``. ``APackage`` and ``BCPackage`` both contain the autoinclude directive, which will automatically include any meta.zcml and configure.zcml files (in that order) that their dependencies contain. These dependencies' zcml actually contain a test directive that will append a logging message to a global variable in testdirective.zcml. So let's trigger the loading of the configure.zcml in ``APackage`` and see whether its ``BCPackage`` dependency, and ``BCPackage``'s dependencies, were indeed loaded and in the correct order:: >>> from pkg_resources import resource_filename >>> from zope.configuration import xmlconfig >>> import a >>> dummy = xmlconfig.file(resource_filename('a', 'configure.zcml'), ... package=a) >>> from testdirective.zcml import test_log >>> pprint(test_log) [u'f.g meta has been loaded', u'f.h has been loaded', u'BCPackage has been loaded'] There is also a directive for including overrides, which calls ``autoIncludeOverridesDirective``; however, I have no idea how to test this. Finally, there is a convenience API for finding the files we need to include from the requirements of a given package:: >>> from z3c.autoinclude import package_includes >>> pprint(package_includes('BCPackage')) {'configure.zcml': ['F.H'], 'meta.zcml': ['testdirective', 'F.G', 'F.H'], 'overrides.zcml': []} As with ``includableInfo``, we can also supply a list of ZCML filenames to search for:: >>> pprint(package_includes('BCPackage', ['configure.zcml', 'silly.zcml'])) {'configure.zcml': ['F.H'], 'silly.zcml': []} Note that it will not catch DistributionNotFound errors:: >>> package_includes('NonexistentPackage') Traceback (most recent call last): ... DistributionNotFound: NonexistentPackage