28

If I have built a python package employing distutils.core, e.g. via

setup(
    ext_package="foo",
    author="me",
    version="1.0",
    description="foo package",
    packages=["foo",],
)

where does all the metadata go (what is it intended for?) and how can I access it from within python. Specifically, how can I access the author information from the python console after doing something like

>>> import foo
CypherX
  • 7,019
  • 3
  • 25
  • 37
dastrobu
  • 1,600
  • 1
  • 18
  • 32

7 Answers7

21

With python3.8 being released, you might want to use the new importlib.metadata[1] module to parse any installed package's metadata.

Getting the author information would look like this:

>>> from importlib import metadata
>>> metadata.metadata('foo')['Author']  # let's say you called your package 'foo'
'Arne'

And getting the version of your install:

>>> from importlib import metadata
>>> metadata.version('foo')
'0.1.0'

Which is a lot more straight forward than what you had to do before.


[1] Also available as backport for Python2.7 and 3.5+ as importlib-metadata, thanks to @ChrisHunt for pointing that out.

Arne
  • 17,706
  • 5
  • 83
  • 99
  • 5
    This is also available as a backport for Python 2.7 and 3.5+ in [importlib-metadata](https://pypi.org/project/importlib-metadata/). – Chris Hunt Dec 28 '19 at 03:37
5

One way to access the metadata is to use :

import pip

package = [pckg for pckg in pip.get_installed_distributions() 
            if pckg.project_name == 'package_name'][0]
#  package var will contain some metadata: version, project_name and others.

or pkg_resources

from pkg_resources import get_distribution

pkg = get_distribution('package_name')  # also contains a metadata
Alexander Zhukov
  • 4,357
  • 1
  • 20
  • 31
  • Unfortunately I don't have `pip` installed right now, so can't test it. The `pkg_resources.get_distribution` approach doesn't seem to work either for an extension that was installed via a simple `python setup.py build`. In this case I get a `DistributionNotFound: foo` exception. – dastrobu Dec 19 '13 at 14:03
  • 1
    python setup.py build does not install. – merwok Dec 21 '13 at 08:51
  • 9
    Use of `pip` adds an immense delay at the time of import, and adds a substantial memory footprint to your own code. It internally is just using `pkg_resources` and nets you an identical result; basically, `pip` here is extraneous and a misleading "solution". The answer also does not actually get the desired information; from a `Distribution` object it's extremely non-obvious how to get version, author, and description, amongst the other fields, back out. `pkg._get_metadata(pkg.PKG_INFO)` gets you the individual lines from the metadata file as a list, for starters. – amcgregor May 04 '16 at 15:07
4

The metadata are stored inside the <package>-<version>-<py version>.egg-info file.

when you create your module, you should have this line :

Writing /usr/lib/python2.7/site-packages/foobar-1.0-py2.7.egg-info

This file contain the Metadata :

Metadata-Version: 1.0
Name: Foobar
Version: 1.0
Summary: foobar
Home-page: http://foobar.com/
Author: foobar
Author-email: foobar@foobar.net
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN

If you want to access it, the best way is with pip or pkg_resources (as said Alexander Zhukov) ex :

>>> import pkg_resources
>>> d = pkg_resources.get_distribution('Foobar')
>>> d.version
'1.0'
>>> d.location
'/usr/lib/python2.7/site-packages'
onionpsy
  • 1,502
  • 11
  • 15
  • 3
    Missing needed step to actually read back the metadata: `d._get_metadata(d.PKG_INFO)` The bare Distribution object has no particularly obvious way of accessing the information. – amcgregor May 04 '16 at 15:09
  • @amcgregor your comment was actually the info I missed ! This would deserve being an answer of it's own – Joachim Jablon Jan 30 '19 at 15:14
3

Solution

Although I prefer using importlib.metadata, since there is another answer already showing how to do that, I will show you another alternative.

For the purpose of illustration, I will use one of my own packages genespeak from PyPI.

With metadata

try:
    from importlib import metadata
except ImportError:  # for Python<3.8
    import importlib_metadata as metadata

print(metadata.name('genespeak')) # genespeak
print(metadata.version('genespeak')) # 0.0.7

Use pkginfo PyPI

We will use the following 5 ways of accessing package information.

from pkginfo import SDist, BDist, Wheel, Installed, Develop

A. Check Package Info from Source Distributions

Typically you would create the source distribution file with:

python setup.py sdist

Assuming you have a .tar.gz file at path: ./dist/genespeak-0.0.7.tar.gz, here is what you need to extract the package info.

from pkginfo import SDist

pkg = SDist("./dist/genespeak-0.0.7.tar.gz")
# Now you can access the metadata fields from 
# PKG-INFO file inside the source file:
# `./dist/genespeak-0.0.7.tar.gz`
print(pkg.name) # genespeak
print(pkg.version) # 0.0.7

B. Check Package Info from Binary Distributions

Typically you would create the binary distribution files (.egg) with:

python setup.py bdist_egg

Assuming you have a .egg file at path: ./dist/genespeak-0.0.7-py38.egg, here is what you need to extract the package info.

from pkginfo import BDist

pkg = BDist("./dist/genespeak-0.0.7-py38.egg")
# Now you can access the metadata fields from 
# the binary distribution file (*.egg):
# `./dist/genespeak-0.0.7-py38.egg`
print(pkg.name) # genespeak
print(pkg.version) # 0.0.7

C. Check Package Info from Wheel

Typically you would create the binary distribution wheel files (.whl) with:

python setup.py bdist_wheel

Assuming you have a .whl file at path: ./dist/genespeak-0.0.7-py3-none-any-whl, here is what you need to extract the package info.

from pkginfo import Wheel

pkg = Wheel("./dist/genespeak-0.0.7-py3-none-any-whl")
# Now you can access the metadata fields from 
# PKG-INFO file inside the source file:
# `./dist/genespeak-0.0.7-py3-none-any.whl`
print(pkg.name) # genespeak
print(pkg.version) # 0.0.7

D. Check Package Info from Installed Packages

See here for more details

from pkginfo import Installed
import genespeak

pkg = Installed(genespeak)
# Now you can access the metadata fields from 
# PKG-INFO file inside the source file:
# `./dist/genespeak-0.0.7.tar.gz`
print(pkg.name) # genespeak
print(pkg.version) # 0.0.7

E. Check Package Info from Development Directory

from pkginfo import Develop

dev = Develop(".")
# Now you can access the metadata fields from 
# PKG-INFO file under `genespeak.egg-info` 
# directory under the project root.
print(dev.name) # genespeak
print(dev.version) # 0.0.7

References

CypherX
  • 7,019
  • 3
  • 25
  • 37
  • @dastrobu Perhaps your question has been long answered. However, another option could always come handy. – CypherX Jan 07 '22 at 04:13
2

Concerning the version metadata only, I found it quite unreliable to use the various tools available as most of them do not cover all cases. For example

  • built-in modules
  • modules not installed but just added to the python path (by your IDE for example)
  • two versions of the same module available (one in python path superseding the one installed)

Since we needed a reliable way to get the version of any package, module or submodule, I ended up writing getversion. It is quite simple to use:

from getversion import get_module_version
import foo
version, details = get_module_version(foo)

See the documentation for details.

smarie
  • 4,568
  • 24
  • 39
1

Given setup.py as follows:

from distutils.core import setup

setup(
    name         = 'TestApp',
    version      = '0.0.1',
    author       = 'saaj',
    py_modules   = ['app'],
    test_suite   = 'test'
)

For some scripting and automation without installing the package, where pip, easy_install and even setuptools don't provide command line options or public APIs for reading all metadata (e.g. test_suite), here's a little hacky way:

python3 -c "import sys, types; m = types.ModuleType('distutils.core'); \
    m.setup = lambda **kwargs: print(kwargs); \
    sys.modules['distutils.core'] = m; import setup" 

This will print a dict of keyword arguments passed to setup().

{'author': 'saaj', 'version': '0.0.1', 'name': 'TestApp', 
    'test_suite': 'test', 'py_modules': ['app']}

You can replace print in the lambda to whatever output you need. If your setup.py imports setup() from setuptools, which is actually the recommended way, just replace "distutils.core" with "setuptools" in the snippet.

Formatted snippet follows:

import sys
import types

m = types.ModuleType('distutils.core')
m.setup = lambda **kwargs: print(kwargs)
sys.modules['distutils.core'] = m

import setup  # import you setup.py with mocked setup()
saaj
  • 23,253
  • 3
  • 104
  • 105
-2

One use of this data is that it is displayed on Pypi (http://pypi.python.org/) if you were to publish your package there. One way to structure it is like so:

in the top level of your foo module:

__author__= "me"
__version__= "1.0"
__description__= "foo package"

in setup.py:

import foo
setup(

    author = foo.__author__,
    version = foo.__version__,
    description = foo.__description__,
    packages = ["foo",],

)

This way you only need to update your metadata in one place, and as the data is defined in your packages main module, it will be accessible from there.

Holy Mackerel
  • 3,259
  • 1
  • 25
  • 41
  • 4
    There's a vitally important catch-22 here. Your setup script now depends on the package it's installing being already installed (or, at least, already importable). That's not a good thing. (Down vote for potentially dangerous practice.) – amcgregor May 04 '16 at 15:12