summaryrefslogtreecommitdiff
path: root/wheel/metadata.py
diff options
context:
space:
mode:
Diffstat (limited to 'wheel/metadata.py')
-rw-r--r--wheel/metadata.py330
1 files changed, 0 insertions, 330 deletions
diff --git a/wheel/metadata.py b/wheel/metadata.py
deleted file mode 100644
index 2b123f9..0000000
--- a/wheel/metadata.py
+++ /dev/null
@@ -1,330 +0,0 @@
-"""
-Tools for converting old- to new-style metadata.
-"""
-
-from collections import namedtuple
-from .pkginfo import read_pkg_info
-from .util import OrderedDefaultDict
-from collections import OrderedDict
-
-import re
-import os.path
-import textwrap
-import pkg_resources
-import email.parser
-
-from . import __version__ as wheel_version
-
-METADATA_VERSION = "2.0"
-
-PLURAL_FIELDS = { "classifier" : "classifiers",
- "provides_dist" : "provides",
- "provides_extra" : "extras" }
-
-SKIP_FIELDS = set()
-
-CONTACT_FIELDS = (({"email":"author_email", "name": "author"},
- "author"),
- ({"email":"maintainer_email", "name": "maintainer"},
- "maintainer"))
-
-# commonly filled out as "UNKNOWN" by distutils:
-UNKNOWN_FIELDS = set(("author", "author_email", "platform", "home_page",
- "license"))
-
-# Wheel itself is probably the only program that uses non-extras markers
-# in METADATA/PKG-INFO. Support its syntax with the extra at the end only.
-EXTRA_RE = re.compile("""^(?P<package>.*?)(;\s*(?P<condition>.*?)(extra == '(?P<extra>.*?)')?)$""")
-KEYWORDS_RE = re.compile("[\0-,]+")
-
-MayRequiresKey = namedtuple('MayRequiresKey', ('condition', 'extra'))
-
-def unique(iterable):
- """
- Yield unique values in iterable, preserving order.
- """
- seen = set()
- for value in iterable:
- if not value in seen:
- seen.add(value)
- yield value
-
-
-def handle_requires(metadata, pkg_info, key):
- """
- Place the runtime requirements from pkg_info into metadata.
- """
- may_requires = OrderedDefaultDict(list)
- for value in sorted(pkg_info.get_all(key)):
- extra_match = EXTRA_RE.search(value)
- if extra_match:
- groupdict = extra_match.groupdict()
- condition = groupdict['condition']
- extra = groupdict['extra']
- package = groupdict['package']
- if condition.endswith(' and '):
- condition = condition[:-5]
- else:
- condition, extra = None, None
- package = value
- key = MayRequiresKey(condition, extra)
- may_requires[key].append(package)
-
- if may_requires:
- metadata['run_requires'] = []
- def sort_key(item):
- # Both condition and extra could be None, which can't be compared
- # against strings in Python 3.
- key, value = item
- if key.condition is None:
- return ''
- return key.condition
- for key, value in sorted(may_requires.items(), key=sort_key):
- may_requirement = OrderedDict((('requires', value),))
- if key.extra:
- may_requirement['extra'] = key.extra
- if key.condition:
- may_requirement['environment'] = key.condition
- metadata['run_requires'].append(may_requirement)
-
- if not 'extras' in metadata:
- metadata['extras'] = []
- metadata['extras'].extend([key.extra for key in may_requires.keys() if key.extra])
-
-
-def pkginfo_to_dict(path, distribution=None):
- """
- Convert PKG-INFO to a prototype Metadata 2.0 (PEP 426) dict.
-
- The description is included under the key ['description'] rather than
- being written to a separate file.
-
- path: path to PKG-INFO file
- distribution: optional distutils Distribution()
- """
-
- metadata = OrderedDefaultDict(lambda: OrderedDefaultDict(lambda: OrderedDefaultDict(OrderedDict)))
- metadata["generator"] = "bdist_wheel (" + wheel_version + ")"
- try:
- unicode
- pkg_info = read_pkg_info(path)
- except NameError:
- with open(path, 'rb') as pkg_info_file:
- pkg_info = email.parser.Parser().parsestr(pkg_info_file.read().decode('utf-8'))
- description = None
-
- if pkg_info['Summary']:
- metadata['summary'] = pkginfo_unicode(pkg_info, 'Summary')
- del pkg_info['Summary']
-
- if pkg_info['Description']:
- description = dedent_description(pkg_info)
- del pkg_info['Description']
- else:
- payload = pkg_info.get_payload()
- if isinstance(payload, bytes):
- # Avoid a Python 2 Unicode error.
- # We still suffer ? glyphs on Python 3.
- payload = payload.decode('utf-8')
- if payload:
- description = payload
-
- if description:
- pkg_info['description'] = description
-
- for key in sorted(unique(k.lower() for k in pkg_info.keys())):
- low_key = key.replace('-', '_')
-
- if low_key in SKIP_FIELDS:
- continue
-
- if low_key in UNKNOWN_FIELDS and pkg_info.get(key) == 'UNKNOWN':
- continue
-
- if low_key in sorted(PLURAL_FIELDS):
- metadata[PLURAL_FIELDS[low_key]] = pkg_info.get_all(key)
-
- elif low_key == "requires_dist":
- handle_requires(metadata, pkg_info, key)
-
- elif low_key == 'provides_extra':
- if not 'extras' in metadata:
- metadata['extras'] = []
- metadata['extras'].extend(pkg_info.get_all(key))
-
- elif low_key == 'home_page':
- metadata['extensions']['python.details']['project_urls'] = {'Home':pkg_info[key]}
-
- elif low_key == 'keywords':
- metadata['keywords'] = KEYWORDS_RE.split(pkg_info[key])
-
- else:
- metadata[low_key] = pkg_info[key]
-
- metadata['metadata_version'] = METADATA_VERSION
-
- if 'extras' in metadata:
- metadata['extras'] = sorted(set(metadata['extras']))
-
- # include more information if distribution is available
- if distribution:
- for requires, attr in (('test_requires', 'tests_require'),):
- try:
- requirements = getattr(distribution, attr)
- if isinstance(requirements, list):
- new_requirements = sorted(convert_requirements(requirements))
- metadata[requires] = [{'requires':new_requirements}]
- except AttributeError:
- pass
-
- # handle contacts
- contacts = []
- for contact_type, role in CONTACT_FIELDS:
- contact = OrderedDict()
- for key in sorted(contact_type):
- if contact_type[key] in metadata:
- contact[key] = metadata.pop(contact_type[key])
- if contact:
- contact['role'] = role
- contacts.append(contact)
- if contacts:
- metadata['extensions']['python.details']['contacts'] = contacts
-
- # convert entry points to exports
- try:
- with open(os.path.join(os.path.dirname(path), "entry_points.txt"), "r") as ep_file:
- ep_map = pkg_resources.EntryPoint.parse_map(ep_file.read())
- exports = OrderedDict()
- for group, items in sorted(ep_map.items()):
- exports[group] = OrderedDict()
- for item in sorted(map(str, items.values())):
- name, export = item.split(' = ', 1)
- exports[group][name] = export
- if exports:
- metadata['extensions']['python.exports'] = exports
- except IOError:
- pass
-
- # copy console_scripts entry points to commands
- if 'python.exports' in metadata['extensions']:
- for (ep_script, wrap_script) in (('console_scripts', 'wrap_console'),
- ('gui_scripts', 'wrap_gui')):
- if ep_script in metadata['extensions']['python.exports']:
- metadata['extensions']['python.commands'][wrap_script] = \
- metadata['extensions']['python.exports'][ep_script]
-
- return metadata
-
-def requires_to_requires_dist(requirement):
- """Compose the version predicates for requirement in PEP 345 fashion."""
- requires_dist = []
- for op, ver in requirement.specs:
- requires_dist.append(op + ver)
- if not requires_dist:
- return ''
- return " (%s)" % ','.join(requires_dist)
-
-def convert_requirements(requirements):
- """Yield Requires-Dist: strings for parsed requirements strings."""
- for req in requirements:
- parsed_requirement = pkg_resources.Requirement.parse(req)
- spec = requires_to_requires_dist(parsed_requirement)
- extras = ",".join(parsed_requirement.extras)
- if extras:
- extras = "[%s]" % extras
- yield (parsed_requirement.project_name + extras + spec)
-
-def generate_requirements(extras_require):
- """
- Convert requirements from a setup()-style dictionary to ('Requires-Dist', 'requirement')
- and ('Provides-Extra', 'extra') tuples.
-
- extras_require is a dictionary of {extra: [requirements]} as passed to setup(),
- using the empty extra {'': [requirements]} to hold install_requires.
- """
- for extra, depends in extras_require.items():
- condition = ''
- if extra and ':' in extra: # setuptools extra:condition syntax
- extra, condition = extra.split(':', 1)
- extra = pkg_resources.safe_extra(extra)
- if extra:
- yield ('Provides-Extra', extra)
- if condition:
- condition += " and "
- condition += "extra == '%s'" % extra
- if condition:
- condition = '; ' + condition
- for new_req in convert_requirements(depends):
- yield ('Requires-Dist', new_req + condition)
-
-def pkginfo_to_metadata(egg_info_path, pkginfo_path):
- """
- Convert .egg-info directory with PKG-INFO to the Metadata 1.3 aka
- old-draft Metadata 2.0 format.
- """
- pkg_info = read_pkg_info(pkginfo_path)
- pkg_info.replace_header('Metadata-Version', '2.0')
- requires_path = os.path.join(egg_info_path, 'requires.txt')
- if os.path.exists(requires_path):
- with open(requires_path) as requires_file:
- requires = requires_file.read()
- for extra, reqs in sorted(pkg_resources.split_sections(requires),
- key=lambda x: x[0] or ''):
- for item in generate_requirements({extra: reqs}):
- pkg_info[item[0]] = item[1]
-
- description = pkg_info['Description']
- if description:
- pkg_info.set_payload(dedent_description(pkg_info))
- del pkg_info['Description']
-
- return pkg_info
-
-
-def pkginfo_unicode(pkg_info, field):
- """Hack to coax Unicode out of an email Message() - Python 3.3+"""
- text = pkg_info[field]
- field = field.lower()
- if not isinstance(text, str):
- if not hasattr(pkg_info, 'raw_items'): # Python 3.2
- return str(text)
- for item in pkg_info.raw_items():
- if item[0].lower() == field:
- text = item[1].encode('ascii', 'surrogateescape')\
- .decode('utf-8')
- break
-
- return text
-
-
-def dedent_description(pkg_info):
- """
- Dedent and convert pkg_info['Description'] to Unicode.
- """
- description = pkg_info['Description']
-
- # Python 3 Unicode handling, sorta.
- surrogates = False
- if not isinstance(description, str):
- surrogates = True
- description = pkginfo_unicode(pkg_info, 'Description')
-
- description_lines = description.splitlines()
- description_dedent = '\n'.join(
- # if the first line of long_description is blank,
- # the first line here will be indented.
- (description_lines[0].lstrip(),
- textwrap.dedent('\n'.join(description_lines[1:])),
- '\n'))
-
- if surrogates:
- description_dedent = description_dedent\
- .encode("utf8")\
- .decode("ascii", "surrogateescape")
-
- return description_dedent
-
-
-if __name__ == "__main__":
- import sys, pprint
- pprint.pprint(pkginfo_to_dict(sys.argv[1]))