packaging.database.rst 10.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
:mod:`packaging.database` --- Database of installed distributions
=================================================================

.. module:: packaging.database
   :synopsis: Functions to query and manipulate installed distributions.


This module provides an implementation of :PEP:`376`.  It was originally
intended to land in :mod:`pkgutil`, but with the inclusion of Packaging in the
standard library, it was thought best to include it in a submodule of
:mod:`packaging`, leaving :mod:`pkgutil` to deal with imports.

Installed Python distributions are represented by instances of
:class:`Distribution`, or :class:`EggInfoDistribution` for legacy egg formats.
Most functions also provide an extra argument ``use_egg_info`` to take legacy
distributions into account.


Classes representing installed distributions
--------------------------------------------

.. class:: Distribution(path)

   Class representing an installed distribution.  It is different from
   :class:`packaging.dist.Distribution` which holds the list of files, the
   metadata and options during the run of a Packaging command.

   Instantiate with the *path* to a ``.dist-info`` directory.  Instances can be
   compared and sorted.  Other available methods are:

   .. XXX describe how comparison works

   .. method:: get_distinfo_file(path, binary=False)

      Return a read-only file object for a file located at
36
      :file:`{project}-{version}.dist-info/{path}`.  *path* should be a
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
      ``'/'``-separated path relative to the ``.dist-info`` directory or an
      absolute path; if it is an absolute path and doesn't start with the path
      to the :file:`.dist-info` directory, a :class:`PackagingError` is raised.

      If *binary* is ``True``, the file is opened in binary mode.

   .. method:: get_resource_path(relative_path)

      .. TODO

   .. method:: list_distinfo_files(local=False)

      Return an iterator over all files located in the :file:`.dist-info`
      directory.  If *local* is ``True``, each returned path is transformed into
      a local absolute path, otherwise the raw value found in the :file:`RECORD`
      file is returned.

   .. method::  list_installed_files(local=False)

      Iterate over the files installed with the distribution and registered in
      the :file:`RECORD` file and yield a tuple ``(path, md5, size)`` for each
      line.  If *local* is ``True``, the returned path is transformed into a
      local absolute path, otherwise the raw value is returned.

      A local absolute path is an absolute path in which occurrences of ``'/'``
      have been replaced by :data:`os.sep`.

   .. method:: uses(path)

      Check whether *path* was installed by this distribution (i.e. if the path
      is present in the :file:`RECORD` file).  *path* can be a local absolute
      path or a relative ``'/'``-separated path.  Returns a boolean.

   Available attributes:

   .. attribute:: metadata

      Instance of :class:`packaging.metadata.Metadata` filled with the contents
75
      of the :file:`{project}-{version}.dist-info/METADATA` file.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215

   .. attribute:: name

      Shortcut for ``metadata['Name']``.

   .. attribute:: version

      Shortcut for ``metadata['Version']``.

   .. attribute:: requested

      Boolean indicating whether this distribution was requested by the user of
      automatically installed as a dependency.


.. class:: EggInfoDistribution(path)

   Class representing a legacy distribution.  It is compatible with distutils'
   and setuptools' :file:`.egg-info` and :file:`.egg` files and directories.

   .. FIXME should be named EggDistribution

   Instantiate with the *path* to an egg file or directory.  Instances can be
   compared and sorted.  Other available methods are:

   .. method:: list_installed_files(local=False)

   .. method:: uses(path)

   Available attributes:

   .. attribute:: metadata

      Instance of :class:`packaging.metadata.Metadata` filled with the contents
      of the :file:`{project-version}.egg-info/PKG-INFO` or
      :file:`{project-version}.egg` file.

   .. attribute:: name

      Shortcut for ``metadata['Name']``.

   .. attribute:: version

      Shortcut for ``metadata['Version']``.


Functions to work with the database
-----------------------------------

.. function:: get_distribution(name, use_egg_info=False, paths=None)

   Return an instance of :class:`Distribution` or :class:`EggInfoDistribution`
   for the first installed distribution matching *name*.  Egg distributions are
   considered only if *use_egg_info* is true; if both a dist-info and an egg
   file are found, the dist-info prevails.  The directories to be searched are
   given in *paths*, which defaults to :data:`sys.path`.  Return ``None`` if no
   matching distribution is found.

   .. FIXME param should be named use_egg


.. function:: get_distributions(use_egg_info=False, paths=None)

   Return an iterator of :class:`Distribution` instances for all installed
   distributions found in *paths* (defaults to :data:`sys.path`).  If
   *use_egg_info* is true, also return instances of :class:`EggInfoDistribution`
   for legacy distributions found.


.. function:: get_file_users(path)

   Return an iterator over all distributions using *path*, a local absolute path
   or a relative ``'/'``-separated path.

   .. XXX does this work with prefixes or full file path only?


.. function:: obsoletes_distribution(name, version=None, use_egg_info=False)

   Return an iterator over all distributions that declare they obsolete *name*.
   *version* is an optional argument to match only specific releases (see
   :mod:`packaging.version`).  If *use_egg_info* is true, legacy egg
   distributions will be considered as well.


.. function:: provides_distribution(name, version=None, use_egg_info=False)

   Return an iterator over all distributions that declare they provide *name*.
   *version* is an optional argument to match only specific releases (see
   :mod:`packaging.version`).  If *use_egg_info* is true, legacy egg
   distributions will be considered as well.


Utility functions
-----------------

.. function:: distinfo_dirname(name, version)

   Escape *name* and *version* into a filename-safe form and return the
   directory name built from them, for example
   :file:`{safename}-{safeversion}.dist-info.`  In *name*, runs of
   non-alphanumeric characters are replaced with one ``'_'``; in *version*,
   spaces become dots, and runs of other non-alphanumeric characters (except
   dots) a replaced by one ``'-'``.

   .. XXX wth spaces in version numbers?

For performance purposes, the list of distributions is being internally
cached.   Caching is enabled by default, but you can control it with these
functions:

.. function:: clear_cache()

   Clear the cache.

.. function:: disable_cache()

   Disable the cache, without clearing it.

.. function:: enable_cache()

   Enable the internal cache, without clearing it.


Examples
--------

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::

   import sys
   import packaging.database

   path = input()
   # first create the Distribution instance
   try:
       dist = packaging.database.Distribution(path)
216
   except FileNotFoundError:
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
       sys.exit('No such distribution')

   print('Information about %r' % dist.name)
   print()

   print('Files')
   print('=====')
   for path, md5, size in dist.list_installed_files():
       print('* Path: %s' % path)
       print('  Hash %s, Size: %s bytes' % (md5, size))
   print()

   print('Metadata')
   print('========')
   for key, value in dist.metadata.items():
       print('%20s: %s' % (key, value))
   print()

   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``, we can use it to extract
information from a :file:`.dist-info` directory.  By typing in the console:

.. code-block:: sh

   $ echo /tmp/choxie/choxie-2.0.0.9.dist-info | python3 print_info.py

we get the following output:

.. code-block:: none

   Information about 'choxie'

   Files
   =====
   * Path: ../tmp/distutils2/tests/fake_dists/choxie-2.0.0.9/truffles.py
     Hash 5e052db6a478d06bad9ae033e6bc08af, Size: 111 bytes
   * Path: ../tmp/distutils2/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py
     Hash ac56bf496d8d1d26f866235b95f31030, Size: 214 bytes
   * Path: ../tmp/distutils2/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py
     Hash 416aab08dfa846f473129e89a7625bbc, Size: 25 bytes
   * Path: ../tmp/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER
     Hash d41d8cd98f00b204e9800998ecf8427e, Size: 0 bytes
   * Path: ../tmp/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA
     Hash 696a209967fef3c8b8f5a7bb10386385, Size: 225 bytes
   * Path: ../tmp/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED
     Hash d41d8cd98f00b204e9800998ecf8427e, Size: 0 bytes
   * Path: ../tmp/distutils2/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::

  import packaging.database

  # iterate over all distributions in the system
  for dist in packaging.database.get_distributions():
      name, version = dist.name, dist.version
      # find out which distributions obsolete this name/version combination
      replacements = packaging.database.obsoletes_distribution(name, version)
      if replacements:
          print('%r %s is obsoleted by' % (name, version),
                ', '.join(repr(r.name) for r in replacements))

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'