metadata.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. """
  2. Tools for converting old- to new-style metadata.
  3. """
  4. import os.path
  5. import re
  6. import textwrap
  7. from collections import namedtuple
  8. import pkg_resources
  9. from .pkginfo import read_pkg_info
  10. # Wheel itself is probably the only program that uses non-extras markers
  11. # in METADATA/PKG-INFO. Support its syntax with the extra at the end only.
  12. EXTRA_RE = re.compile("""^(?P<package>.*?)(;\s*(?P<condition>.*?)(extra == '(?P<extra>.*?)')?)$""")
  13. MayRequiresKey = namedtuple('MayRequiresKey', ('condition', 'extra'))
  14. def requires_to_requires_dist(requirement):
  15. """Compose the version predicates for requirement in PEP 345 fashion."""
  16. requires_dist = []
  17. for op, ver in requirement.specs:
  18. requires_dist.append(op + ver)
  19. if not requires_dist:
  20. return ''
  21. return " (%s)" % ','.join(sorted(requires_dist))
  22. def convert_requirements(requirements):
  23. """Yield Requires-Dist: strings for parsed requirements strings."""
  24. for req in requirements:
  25. parsed_requirement = pkg_resources.Requirement.parse(req)
  26. spec = requires_to_requires_dist(parsed_requirement)
  27. extras = ",".join(parsed_requirement.extras)
  28. if extras:
  29. extras = "[%s]" % extras
  30. yield (parsed_requirement.project_name + extras + spec)
  31. def generate_requirements(extras_require):
  32. """
  33. Convert requirements from a setup()-style dictionary to ('Requires-Dist', 'requirement')
  34. and ('Provides-Extra', 'extra') tuples.
  35. extras_require is a dictionary of {extra: [requirements]} as passed to setup(),
  36. using the empty extra {'': [requirements]} to hold install_requires.
  37. """
  38. for extra, depends in extras_require.items():
  39. condition = ''
  40. if extra and ':' in extra: # setuptools extra:condition syntax
  41. extra, condition = extra.split(':', 1)
  42. extra = pkg_resources.safe_extra(extra)
  43. if extra:
  44. yield ('Provides-Extra', extra)
  45. if condition:
  46. condition = "(" + condition + ") and "
  47. condition += "extra == '%s'" % extra
  48. if condition:
  49. condition = '; ' + condition
  50. for new_req in convert_requirements(depends):
  51. yield ('Requires-Dist', new_req + condition)
  52. def pkginfo_to_metadata(egg_info_path, pkginfo_path):
  53. """
  54. Convert .egg-info directory with PKG-INFO to the Metadata 2.1 format
  55. """
  56. pkg_info = read_pkg_info(pkginfo_path)
  57. pkg_info.replace_header('Metadata-Version', '2.1')
  58. requires_path = os.path.join(egg_info_path, 'requires.txt')
  59. if os.path.exists(requires_path):
  60. with open(requires_path) as requires_file:
  61. requires = requires_file.read()
  62. for extra, reqs in sorted(pkg_resources.split_sections(requires),
  63. key=lambda x: x[0] or ''):
  64. for item in generate_requirements({extra: reqs}):
  65. pkg_info[item[0]] = item[1]
  66. description = pkg_info['Description']
  67. if description:
  68. pkg_info.set_payload(dedent_description(pkg_info))
  69. del pkg_info['Description']
  70. return pkg_info
  71. def pkginfo_unicode(pkg_info, field):
  72. """Hack to coax Unicode out of an email Message() - Python 3.3+"""
  73. text = pkg_info[field]
  74. field = field.lower()
  75. if not isinstance(text, str):
  76. if not hasattr(pkg_info, 'raw_items'): # Python 3.2
  77. return str(text)
  78. for item in pkg_info.raw_items():
  79. if item[0].lower() == field:
  80. text = item[1].encode('ascii', 'surrogateescape') \
  81. .decode('utf-8')
  82. break
  83. return text
  84. def dedent_description(pkg_info):
  85. """
  86. Dedent and convert pkg_info['Description'] to Unicode.
  87. """
  88. description = pkg_info['Description']
  89. # Python 3 Unicode handling, sorta.
  90. surrogates = False
  91. if not isinstance(description, str):
  92. surrogates = True
  93. description = pkginfo_unicode(pkg_info, 'Description')
  94. description_lines = description.splitlines()
  95. description_dedent = '\n'.join(
  96. # if the first line of long_description is blank,
  97. # the first line here will be indented.
  98. (description_lines[0].lstrip(),
  99. textwrap.dedent('\n'.join(description_lines[1:])),
  100. '\n'))
  101. if surrogates:
  102. description_dedent = description_dedent \
  103. .encode("utf8") \
  104. .decode("ascii", "surrogateescape")
  105. return description_dedent