package.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. # -*- coding: utf-8 -*-
  2. # (The MIT License)
  3. #
  4. # Copyright (c) 2014 Kura
  5. #
  6. # Permission is hereby granted, free of charge, to any person obtaining a copy
  7. # of this software and associated documentation files (the 'Software'), to deal
  8. # in the Software without restriction, including without limitation the rights
  9. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. # copies of the Software, and to permit persons to whom the Software is
  11. # furnished to do so, subject to the following conditions:
  12. #
  13. # The above copyright notice and this permission notice shall be included in
  14. # all copies or substantial portions of the Software.
  15. #
  16. # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. # SOFTWARE.
  23. try:
  24. import simplejson as json
  25. except ImportError:
  26. import json
  27. from collections import namedtuple
  28. import re
  29. from .release import Release
  30. class Package(object):
  31. """
  32. A PyPI package.
  33. :param pypi_dict: A dictionary retrieved from the PyPI server.
  34. """
  35. def __init__(self, pypi_dict):
  36. self._package = pypi_dict['info']
  37. self._releases = pypi_dict['releases']
  38. def __repr__(self):
  39. return "<Package {0}>".format(self.name)
  40. @property
  41. def name(self):
  42. """
  43. >>> package = yarg.get('yarg')
  44. >>> package.name
  45. u'yarg'
  46. """
  47. return self._package['name']
  48. @property
  49. def pypi_url(self):
  50. """
  51. >>> package = yarg.get('yarg')
  52. >>> package.url
  53. u'https://pypi.python.org/pypi/yarg'
  54. """
  55. return self._package['package_url']
  56. @property
  57. def summary(self):
  58. """
  59. >>> package = yarg.get('yarg')
  60. >>> package.summary
  61. u'Some random summary stuff'
  62. """
  63. return self._package['summary']
  64. @property
  65. def description(self):
  66. """
  67. >>> package = yarg.get('yarg')
  68. >>> package.description
  69. u'A super long description, usually uploaded from the README'
  70. """
  71. return self._package['description']
  72. @property
  73. def homepage(self):
  74. """
  75. >>> package = yarg.get('yarg')
  76. >>> package.homepage
  77. u'https://kura.io/yarg/'
  78. """
  79. if ('home_page' not in self._package or
  80. self._package['home_page'] == ""):
  81. return None
  82. return self._package['home_page']
  83. @property
  84. def bugtracker(self):
  85. """
  86. >>> package = yarg.get('yarg')
  87. >>> package.bugtracker
  88. u'https://github.com/kura/yarg/issues'
  89. """
  90. if ('bugtrack_url' not in self._package or
  91. self._package['bugtrack_url'] == ""):
  92. return None
  93. return self._package['bugtrack_url']
  94. @property
  95. def docs(self):
  96. """
  97. >>> package = yarg.get('yarg')
  98. >>> package.docs
  99. u'https://yarg.readthedocs.org/en/latest'
  100. """
  101. if ('docs_url' not in self._package or
  102. self._package['docs_url'] == ""):
  103. return None
  104. return self._package['docs_url']
  105. @property
  106. def author(self):
  107. """
  108. >>> package = yarg.get('yarg')
  109. >>> package.author
  110. Author(name=u'Kura', email=u'kura@kura.io')
  111. """
  112. author = namedtuple('Author', 'name email')
  113. return author(name=self._package['author'],
  114. email=self._package['author_email'])
  115. @property
  116. def maintainer(self):
  117. """
  118. >>> package = yarg.get('yarg')
  119. >>> package.maintainer
  120. Maintainer(name=u'Kura', email=u'kura@kura.io')
  121. """
  122. maintainer = namedtuple('Maintainer', 'name email')
  123. return maintainer(name=self._package['maintainer'],
  124. email=self._package['maintainer_email'])
  125. @property
  126. def license(self):
  127. """
  128. >>> package = yarg.get('yarg')
  129. >>> package.license
  130. u'MIT'
  131. """
  132. return self._package['license']
  133. @property
  134. def license_from_classifiers(self):
  135. """
  136. >>> package = yarg.get('yarg')
  137. >>> package.license_from_classifiers
  138. u'MIT License'
  139. """
  140. if len(self.classifiers) > 0:
  141. for c in self.classifiers:
  142. if c.startswith("License"):
  143. return c.split(" :: ")[-1]
  144. @property
  145. def downloads(self):
  146. """
  147. >>> package = yarg.get('yarg')
  148. >>> package.downloads
  149. Downloads(day=50100, week=367941, month=1601938) # I wish
  150. """
  151. _downloads = self._package['downloads']
  152. downloads = namedtuple('Downloads', 'day week month')
  153. return downloads(day=_downloads['last_day'],
  154. week=_downloads['last_week'],
  155. month=_downloads['last_month'])
  156. @property
  157. def classifiers(self):
  158. """
  159. >>> package = yarg.get('yarg')
  160. >>> package.classifiers
  161. [u'License :: OSI Approved :: MIT License',
  162. u'Programming Language :: Python :: 2.7',
  163. u'Programming Language :: Python :: 3.4']
  164. """
  165. return self._package['classifiers']
  166. @property
  167. def python_versions(self):
  168. """
  169. Returns a list of Python version strings that
  170. the package has listed in :attr:`yarg.Release.classifiers`.
  171. >>> package = yarg.get('yarg')
  172. >>> package.python_versions
  173. [u'2.6', u'2.7', u'3.3', u'3.4']
  174. """
  175. version_re = re.compile(r"""Programming Language \:\: """
  176. """Python \:\: \d\.\d""")
  177. return [c.split(' :: ')[-1] for c in self.classifiers
  178. if version_re.match(c)]
  179. @property
  180. def python_implementations(self):
  181. """
  182. Returns a list of Python implementation strings that
  183. the package has listed in :attr:`yarg.Release.classifiers`.
  184. >>> package = yarg.get('yarg')
  185. >>> package.python_implementations
  186. [u'CPython', u'PyPy']
  187. """
  188. return [c.split(' :: ')[-1] for c in self.classifiers
  189. if c.startswith("""Programming Language :: """
  190. """Python :: Implementation""")]
  191. @property
  192. def latest_release_id(self):
  193. """
  194. >>> package = yarg.get('yarg')
  195. >>> package.latest_release_id
  196. u'0.1.0'
  197. """
  198. return self._package['version']
  199. @property
  200. def latest_release(self):
  201. """
  202. A list of :class:`yarg.release.Release` objects for each file in the
  203. latest release.
  204. >>> package = yarg.get('yarg')
  205. >>> package.latest_release
  206. [<Release 0.1.0>, <Release 0.1.0>]
  207. """
  208. release_id = self.latest_release_id
  209. return self.release(release_id)
  210. @property
  211. def has_wheel(self):
  212. """
  213. Returns `True` if one of the :class:`yarg.release.Release` objects
  214. in the latest set of release files is `wheel` format. Returns
  215. `False` if not.
  216. >>> package = yarg.get('yarg')
  217. >>> package.has_wheel
  218. True
  219. """
  220. for release in self.latest_release:
  221. if release.package_type in ('wheel', 'bdist_wheel'):
  222. return True
  223. return False
  224. @property
  225. def has_egg(self):
  226. """
  227. Returns `True` if one of the :class:`yarg.release.Release` objects
  228. in the latest set of release files is `egg` format. Returns
  229. `False` if not.
  230. >>> package = yarg.get('yarg')
  231. >>> package.has_egg
  232. False
  233. """
  234. for release in self.latest_release:
  235. if release.package_type in ('egg', 'bdist_egg'):
  236. return True
  237. return False
  238. @property
  239. def has_source(self):
  240. """
  241. Returns `True` if one of the :class:`yarg.release.Release` objects
  242. in the latest set of release files is `source` format. Returns
  243. `False` if not.
  244. >>> package = yarg.get('yarg')
  245. >>> package.has_source
  246. True
  247. """
  248. for release in self.latest_release:
  249. if release.package_type in ('source', 'sdist'):
  250. return True
  251. return False
  252. @property
  253. def release_ids(self):
  254. """
  255. >>> package = yarg.get('yarg')
  256. >>> package.release_ids
  257. [u'0.0.1', u'0.0.5', u'0.1.0']
  258. """
  259. r = [(k, self._releases[k][0]['upload_time'])
  260. for k in self._releases.keys()
  261. if len(self._releases[k]) > 0]
  262. return [k[0] for k in sorted(r, key=lambda k: k[1])]
  263. def release(self, release_id):
  264. """
  265. A list of :class:`yarg.release.Release` objects for each file in a
  266. release.
  267. :param release_id: A pypi release id.
  268. >>> package = yarg.get('yarg')
  269. >>> last_release = yarg.releases[-1]
  270. >>> package.release(last_release)
  271. [<Release 0.1.0>, <Release 0.1.0>]
  272. """
  273. if release_id not in self.release_ids:
  274. return None
  275. return [Release(release_id, r) for r in self._releases[release_id]]
  276. def json2package(json_content):
  277. """
  278. Returns a :class:`yarg.release.Release` object from JSON content from the
  279. PyPI server.
  280. :param json_content: JSON encoded content from the PyPI server.
  281. """
  282. return Package(json.loads(json_content))