versions.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. #!/usr/bin/env python
  2. """
  3. Query the github API for the git tags of a project, and return a list of
  4. version tags for recent releases, or the default release.
  5. The default release is the most recent non-RC version.
  6. Recent is a list of unqiue major.minor versions, where each is the most
  7. recent version in the series.
  8. For example, if the list of versions is:
  9. 1.8.0-rc2
  10. 1.8.0-rc1
  11. 1.7.1
  12. 1.7.0
  13. 1.7.0-rc1
  14. 1.6.2
  15. 1.6.1
  16. `default` would return `1.7.1` and
  17. `recent -n 3` would return `1.8.0-rc2 1.7.1 1.6.2`
  18. """
  19. from __future__ import absolute_import
  20. from __future__ import print_function
  21. from __future__ import unicode_literals
  22. import argparse
  23. import itertools
  24. import operator
  25. from collections import namedtuple
  26. import requests
  27. GITHUB_API = 'https://api.github.com/repos'
  28. class Version(namedtuple('_Version', 'major minor patch rc')):
  29. @classmethod
  30. def parse(cls, version):
  31. version = version.lstrip('v')
  32. version, _, rc = version.partition('-')
  33. major, minor, patch = version.split('.', 3)
  34. return cls(int(major), int(minor), int(patch), rc)
  35. @property
  36. def major_minor(self):
  37. return self.major, self.minor
  38. @property
  39. def order(self):
  40. """Return a representation that allows this object to be sorted
  41. correctly with the default comparator.
  42. """
  43. # rc releases should appear before official releases
  44. rc = (0, self.rc) if self.rc else (1, )
  45. return (self.major, self.minor, self.patch) + rc
  46. def __str__(self):
  47. rc = '-{}'.format(self.rc) if self.rc else ''
  48. return '.'.join(map(str, self[:3])) + rc
  49. def group_versions(versions):
  50. """Group versions by `major.minor` releases.
  51. Example:
  52. >>> group_versions([
  53. Version(1, 0, 0),
  54. Version(2, 0, 0, 'rc1'),
  55. Version(2, 0, 0),
  56. Version(2, 1, 0),
  57. ])
  58. [
  59. [Version(1, 0, 0)],
  60. [Version(2, 0, 0), Version(2, 0, 0, 'rc1')],
  61. [Version(2, 1, 0)],
  62. ]
  63. """
  64. return list(
  65. list(releases)
  66. for _, releases
  67. in itertools.groupby(versions, operator.attrgetter('major_minor'))
  68. )
  69. def get_latest_versions(versions, num=1):
  70. """Return a list of the most recent versions for each major.minor version
  71. group.
  72. """
  73. versions = group_versions(versions)
  74. return [versions[index][0] for index in range(num)]
  75. def get_default(versions):
  76. """Return a :class:`Version` for the latest non-rc version."""
  77. for version in versions:
  78. if not version.rc:
  79. return version
  80. def get_github_releases(project):
  81. """Query the Github API for a list of version tags and return them in
  82. sorted order.
  83. See https://developer.github.com/v3/repos/#list-tags
  84. """
  85. url = '{}/{}/tags'.format(GITHUB_API, project)
  86. response = requests.get(url)
  87. response.raise_for_status()
  88. versions = [Version.parse(tag['name']) for tag in response.json()]
  89. return sorted(versions, reverse=True, key=operator.attrgetter('order'))
  90. def parse_args(argv):
  91. parser = argparse.ArgumentParser(description=__doc__)
  92. parser.add_argument('project', help="Github project name (ex: docker/docker)")
  93. parser.add_argument('command', choices=['recent', 'default'])
  94. parser.add_argument('-n', '--num', type=int, default=2,
  95. help="Number of versions to return from `recent`")
  96. return parser.parse_args(argv)
  97. def main(argv=None):
  98. args = parse_args(argv)
  99. versions = get_github_releases(args.project)
  100. if args.command == 'recent':
  101. print(' '.join(map(str, get_latest_versions(versions, args.num))))
  102. elif args.command == 'default':
  103. print(get_default(versions))
  104. else:
  105. raise ValueError("Unknown command {}".format(args.command))
  106. if __name__ == "__main__":
  107. main()