#!/usr/bin/env python
"""
If run as main, ``dumpdistmeta.py`` will print out either a pretty-printed dict
full of the metadata found in the specified distribution or just the value
of a single piece of metadata if metadata-item is specified on the command
line. The distribution can be in the form of an installed egg, a zipped
egg, or a gzipped tar file containing a distutils distribution.
usage: ``dumpdistmeta.py distribution [metadata-item]``
Example output:
::
$ dumpdistmeta.py pyparsing-1.5.1-py2.5.egg
{'SOURCES': ['README',
'pyparsing.py',
'setup.py',
'pyparsing.egg-info/PKG-INFO',
'pyparsing.egg-info/SOURCES.txt',
'pyparsing.egg-info/dependency_links.txt',
'pyparsing.egg-info/top_level.txt'],
'author': 'Paul McGuire',
'author-email': 'ptmcg@users.sourceforge.net',
'classifier': 'Programming Language :: Python',
'dependency_links': [],
'description': 'UNKNOWN',
'download-url': 'http://sourceforge.net/project/showfiles.php?group_id=97203',
'entry_points': {},
'home-page': 'http://pyparsing.wikispaces.com/',
'license': 'MIT License',
'metadata-version': '1.0',
'name': 'pyparsing',
'platform': None,
'py_version': '2.5',
'summary': 'Python parsing module',
'top_level': ['pyparsing'],
'version': '1.5.1',
'zip-safe': False}
Example output:
::
$ dumpdistmeta.py pyparsing-1.5.1-py2.5.egg license
MIT License
"""
import sys
import os
import os.path
import rfc822
import StringIO
import fnmatch
import pprint
from pkg_resources import get_entry_map, find_distributions, working_set, Requirement
import zipfile
import tarfile
def _match(name, inlist):
"""Return True if the given name matches any of the
contents of the list of glob patterns inlist.
"""
for pat in inlist:
if fnmatch.fnmatchcase(name, pat):
return True
return False
def _parse_txt(name, meta, metadata):
metaname = os.path.splitext(os.path.basename(name))[0]
metadata[metaname] = [x.strip() for x in meta.splitlines()
if x.strip() != '']
[docs]def get_resource_files(dist, exList=None, incList=None, dirname=''):
"""A generator that retrieves resource file pathnames from
within a distribution.
"""
exlist = exList or []
inclist = incList or ['*']
for res in dist.resource_listdir(dirname):
if dirname != '':
respath = '/'.join([dirname, res])
else:
respath = res
if dist.resource_isdir(respath):
for r in get_resource_files(dist, exlist, inclist, respath):
if _match(r, inclist) and not _match(r, exlist):
yield r
else:
if _match(respath, inclist) and not _match(respath, exlist):
yield respath
[docs]def get_dist_metadata(dist, dirname=''):
"""Retrieve metadata from within a distribution.
Returns a dict.
"""
metadata = {}
for name in dist.metadata_listdir(dirname):
if dirname != '':
path = '/'.join([dirname, name])
else:
path = name
if dist.metadata_isdir(path):
for md in get_dist_metadata(dist, path):
metadata[md[0]] = md[1]
elif name.endswith('.txt'):
_parse_txt(name, dist.get_metadata(path), metadata)
elif name == 'PKG-INFO':
instr = StringIO.StringIO(dist.get_metadata(name))
message = rfc822.Message(instr)
for k,v in message.items():
metadata[k] = v
elif name == 'not-zip-safe':
metadata['zip-safe'] = False
elif name == 'zip-safe':
metadata['zip-safe'] = True
else:
metadata[path] = dist.get_metadata(path)
if not dirname and len(metadata) == 0:
# look for .egg-info file at peer level. Apparently, windows and linux use two
# different naming conventions for egg_infos. Windows includes the python version
# in the name and linux (at least ubuntu) doesn't.
for pyver in ['-py%s.%s' % sys.version_info[:2], '']:
try:
f = open(os.path.join(dist.location,
'%s-%s%s.egg-info'%(dist.project_name,
dist.version, pyver)),'r')
message = rfc822.Message(f)
metadata = message
except:
pass
else:
break
return metadata
def _meta_from_tarfile(path):
"""Retrieve metadata from a tar file of a distutils distribution.
Returns a dict.
"""
metadata = {}
oldpkginfo = None
tf = tarfile.open(path, mode='r')
for name in tf.getnames():
if '.egg-info/' in name and not name.endswith('.egg-info/'):
metaname = os.path.splitext(os.path.basename(name))[0]
meta = tf.extractfile(name).read().strip()
if name.endswith('/PKG-INFO'):
instr = StringIO.StringIO(meta)
message = rfc822.Message(instr)
for k,v in message.items():
metadata[k] = v
elif name.endswith('.txt'):
_parse_txt(name, meta, metadata)
elif name.endswith('/not-zip-safe'):
metadata['zip-safe'] = False
elif name.endswith('/zip-safe'):
metadata['zip-safe'] = True
else:
continue
elif name.endswith('/PKG-INFO'):
oldpkginfo = name
# if there's no egg-info, use old PKG-INFO if found
if len(metadata) == 0 and oldpkginfo:
message = rfc822.Message(tf.extractfile(oldpkginfo))
for k,v in message.items():
metadata[k] = v
return metadata
def _meta_from_zipfile(path):
"""Retrieve metadata from a zip file of a distutils distribution.
Returns a dict.
"""
metadata = {}
oldpkginfo = None
zf = zipfile.ZipFile(path, 'r')
for name in zf.namelist():
if '.egg-info/' in name and not name.endswith('.egg-info/'):
metaname = os.path.splitext(os.path.basename(name))[0]
meta = zf.read(name).strip()
if name.endswith('/PKG-INFO'):
instr = StringIO.StringIO(meta)
message = rfc822.Message(instr)
for k,v in message.items():
metadata[k] = v
elif name.endswith('.txt'):
_parse_txt(name, meta, metadata)
elif name.endswith('/not-zip-safe'):
metadata['zip-safe'] = False
elif name.endswith('/zip-safe'):
metadata['zip-safe'] = True
else:
continue
elif name.endswith('/PKG-INFO'):
oldpkginfo = name
# if there's no egg-info, use old PKG-INFO if found
if len(metadata) == 0 and oldpkginfo:
instr = StringIO.StringIO(zf.read(oldpkginfo).strip())
message = rfc822.Message(instr)
for k,v in message.items():
metadata[k] = v
return metadata
def _meta_from_zipped_egg(path):
"""Retrieve metadata from a zipped egg file.
Returns a dict.
"""
metadata = {}
zf = zipfile.ZipFile(path, 'r')
for name in zf.namelist():
if name.startswith('EGG-INFO/'):
meta = zf.read(name).strip()
if name.endswith('/PKG-INFO'):
instr = StringIO.StringIO(meta)
message = rfc822.Message(instr)
for k,v in message.items():
metadata[k] = v
elif name.endswith('.txt'):
_parse_txt(name, meta, metadata)
elif name.endswith('/not-zip-safe'):
metadata['zip-safe'] = False
elif name.endswith('/zip-safe'):
metadata['zip-safe'] = True
else:
continue
return metadata
[docs]def get_metadata(path):
"""Retrieve metadata from a file or directory specified by path,
or from the name of a distribution that happens to be installed.
`path` can be an installed egg, a zipped egg file, or a
zipped or unzipped tar file of a Python distutils or setuptools
source distribution.
Returns a dict.
"""
dist = None
if os.path.isdir(path):
dists = [x for x in find_distributions(path, only=True)]
if len(dists) == 0:
raise RuntimeError('%s is not a zipped egg or an installed distribution'%path)
dist = dists[0]
if os.path.abspath(path).lower() != os.path.abspath(dist.location).lower():
raise RuntimeError('%s is not a valid distribution (dist.location=%s)'%
(os.path.abspath(path),dist.location))
metadata = get_dist_metadata(dist)
elif os.path.isfile(path):
if path.lower().endswith('.tar') or '.tar.gz' in path.lower():
# it's a tar file or gzipped tar file
return _meta_from_tarfile(path)
# getting access to metadata in a zipped egg doesn't seem to work
# right, so just use a ZipFile to access files under the EGG-INFO
# directory to retrieve the metadata.
if path.lower().endswith('.egg'):
# it's a zip file
metadata = _meta_from_zipped_egg(path)
elif path.lower().endswith('.zip'):
metadata = _meta_from_zipfile(path)
else:
raise RuntimeError('cannot process file %s: unknown file type' %
path)
else:
# look for an installed dist with the given name
for d in working_set:
if path == d.egg_name().split('-')[0]:
dist = d
metadata = get_dist_metadata(dist)
break
else:
raise RuntimeError("Could not locate distribution '%s'" % path)
if dist is not None:
metadata['py_version'] = dist.py_version
if 'platform' not in metadata or metadata['platform']=='UNKNOWN':
metadata['platform'] = dist.platform
metadata['entry_points'] = {}
for gname,group in get_entry_map(dist, group=None).items():
metadata['entry_points'][gname] = [ep for ep in group]
return metadata
if __name__ == '__main__': # pragma no cover
if len(sys.argv) > 2:
print get_metadata(sys.argv[1])[sys.argv[2]]
elif len(sys.argv) > 1:
pprint.pprint(get_metadata(sys.argv[1]))
else:
print 'usage: python dumpdistmeta.py distribution [metadata-item]'