:mod:`pkgutil` --- Package utilities
====================================

.. module:: pkgutil
   :synopsis: Utilities to support packages.

.. TODO Follow the reST conventions used in the stdlib

This module provides functions to manipulate packages, as well as
the necessary functions to provide support for the "Importer Protocol" as
described in :PEP:`302` and for working with the database of installed Python
distributions which is specified in :PEP:`376`. In addition to the functions
required in :PEP:`376`, back support for older ``.egg`` and ``.egg-info``
distributions is provided as well. These distributions are represented by the
class :class:`~distutils2._backport.pkgutil.EggInfoDistribution` and most
functions provide an extra argument ``use_egg_info`` which indicates if
they should consider these old styled distributions. This document details
first the functions and classes available and then presents several use cases.


.. function:: extend_path(path, name)

   Extend the search path for the modules which comprise a package. Intended use is
   to place the following code in a package's :file:`__init__.py`::

      from pkgutil import extend_path
      __path__ = extend_path(__path__, __name__)

   This will add to the package's ``__path__`` all subdirectories of directories on
   ``sys.path`` named after the package.  This is useful if one wants to distribute
   different parts of a single logical package as multiple directories.

   It also looks for :file:`\*.pkg` files beginning where ``*`` matches the *name*
   argument.  This feature is similar to :file:`\*.pth` files (see the :mod:`site`
   module for more information), except that it doesn't special-case lines starting
   with ``import``.  A :file:`\*.pkg` file is trusted at face value: apart from
   checking for duplicates, all entries found in a :file:`\*.pkg` file are added to
   the path, regardless of whether they exist on the filesystem.  (This is a
   feature.)

   If the input path is not a list (as is the case for frozen packages) it is
   returned unchanged.  The input path is not modified; an extended copy is
   returned.  Items are only appended to the copy at the end.

   It is assumed that ``sys.path`` is a sequence.  Items of ``sys.path`` that are
   not strings referring to existing directories are ignored. Unicode items on
   ``sys.path`` that cause errors when used as filenames may cause this function
   to raise an exception (in line with :func:`os.path.isdir` behavior).

.. function:: get_data(package, resource)

   Get a resource from a package.

   This is a wrapper for the :pep:`302` loader :func:`get_data` API. The package
   argument should be the name of a package, in standard module format
   (foo.bar). The resource argument should be in the form of a relative
   filename, using ``/`` as the path separator. The parent directory name
   ``..`` is not allowed, and nor is a rooted name (starting with a ``/``).

   The function returns a binary string that is the contents of the
   specified resource.

   For packages located in the filesystem, which have already been imported,
   this is the rough equivalent of::

       d = os.path.dirname(sys.modules[package].__file__)
       data = open(os.path.join(d, resource), 'rb').read()

   If the package cannot be located or loaded, or it uses a :pep:`302` loader
   which does not support :func:`get_data`, then None is returned.


API Reference
=============

.. automodule:: distutils2._backport.pkgutil
   :members:

Caching
+++++++

For performance purposes, the list of distributions is being internally
cached. It is enabled by default, but you can turn it off or clear
it using :func:`~distutils2._backport.pkgutil.enable_cache`,
:func:`~distutils2._backport.pkgutil.disable_cache` and
:func:`~distutils2._backport.pkgutil.clear_cache`.



Example Usage
=============

Print All Information About a Distribution
++++++++++++++++++++++++++++++++++++++++++

Given a path to a ``.dist-info`` distribution, we shall print out all
information that can be obtained using functions provided in this module::

  from distutils2._backport import pkgutil
  import sys

  path = raw_input() # read the path from the keyboard
  # first create the Distribution instance
  try:
      dist = pkgutil.Distribution(path)
  except IOError:
      print('No such distribution')
      sys.exit(1)

  print('Information about %s' % dist.name)
  print('Files')
  print('=====')
  for (path, md5, size) in dist.get_installed_files():
      print('* Path: %s' % path)
      print('  Hash %s, Size: %s bytes' % (md5, size))
  print('Metadata')
  print('========')
  for key, value in dist.metadata.items():
      print('%20s: %s' % (key, value))
  print('Extra')
  print('=====')
  if dist.requested:
      print('* It was installed by user request')
  else:
      print('* It was installed as a dependency')

If we save the script above as ``print_info.py`` and we are intested in the
distribution located at
``/home/josip/dev/distutils2/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9``
then by typing in the console:

.. code-block:: bash

  $ echo /home/josip/dev/distutils2/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info | python print_info.py

we get the following output:

.. code-block:: none

  Information about choxie
  Files
  =====
  * Path: ../home/josip/dev/distutils2/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9/truffles.py
    Hash 5e052db6a478d06bad9ae033e6bc08af, Size: 111 bytes
  * Path: ../home/josip/dev/distutils2/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py
    Hash ac56bf496d8d1d26f866235b95f31030, Size: 214 bytes
  * Path: ../home/josip/dev/distutils2/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py
    Hash 416aab08dfa846f473129e89a7625bbc, Size: 25 bytes
  * Path: ../home/josip/dev/distutils2/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER
    Hash d41d8cd98f00b204e9800998ecf8427e, Size: 0 bytes
  * Path: ../home/josip/dev/distutils2/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA
    Hash 696a209967fef3c8b8f5a7bb10386385, Size: 225 bytes
  * Path: ../home/josip/dev/distutils2/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED
    Hash d41d8cd98f00b204e9800998ecf8427e, Size: 0 bytes
  * Path: ../home/josip/dev/distutils2/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD
    Hash None, Size: None bytes
  Metadata
  ========
      Metadata-Version: 1.2
                  Name: choxie
               Version: 2.0.0.9
              Platform: []
    Supported-Platform: UNKNOWN
               Summary: Chocolate with a kick!
           Description: UNKNOWN
              Keywords: []
             Home-page: UNKNOWN
                Author: UNKNOWN
          Author-email: UNKNOWN
            Maintainer: UNKNOWN
      Maintainer-email: UNKNOWN
               License: UNKNOWN
            Classifier: []
          Download-URL: UNKNOWN
        Obsoletes-Dist: ['truffles (<=0.8,>=0.5)', 'truffles (<=0.9,>=0.6)']
           Project-URL: []
         Provides-Dist: ['truffles (1.0)']
         Requires-Dist: ['towel-stuff (0.1)']
       Requires-Python: UNKNOWN
     Requires-External: []
  Extra
  =====
  * It was installed as a dependency

Find Out Obsoleted Distributions
++++++++++++++++++++++++++++++++

Now, we take tackle a different problem, we are interested in finding out
which distributions have been obsoleted. This can be easily done as follows::

  from distutils2._backport import pkgutil

  # iterate over all distributions in the system
  for dist in pkgutil.get_distributions():
      name = dist.name
      version = dist.metadata['Version']
      # find out which distributions obsolete this name/version combination
      for obsoleted_by in pkgutil.obsoletes_distribution(name, version):
          print('%s(%s) is obsoleted by %s' % (name, version, obsoleted_by.name))

This is how the output might look like:

.. code-block:: none

  strawberry(0.6) is obsoleted by choxie
  grammar(1.0a4) is obsoleted by towel-stuff

