__init__.py 3.61 KB
Newer Older
Louis's avatar
Louis committed
1
2
3
4
#!/usr/bin/env python3

import collections
import datetime
5
6
7
import itertools
import jinja2
import pkg_resources
8
import random
Louis's avatar
Louis committed
9
import slumber
Louis's avatar
Louis committed
10
import sys
Louis's avatar
Louis committed
11
import urllib
Louis's avatar
Louis committed
12
13
14
15
16
17

try:
    import xmlrpclib
except ImportError:
    import xmlrpc.client as xmlrpclib

18
19
20
21
_client = xmlrpclib.ServerProxy("http://pypi.python.org/pypi")

class PackageList:
    def __init__(self, user=None, pkg=None):
Louis's avatar
Louis committed
22
        self.packages = {}
23
24
25
26
        package_list = list(self._get_packages(user, pkg))
        if not package_list:
            package_list = random.sample([package for package, _ in _client.top_packages(1000)], 10)
        for name in sorted(package_list):
Louis's avatar
Louis committed
27
28
29
30
31
            try:
                self.packages[name] = Package(name)
            except ValueError:
                sys.stderr.write("Ignoring package '{}' with no releases.\n".format(name))
                continue
32

Louis's avatar
Louis committed
33
34
35
36
37
38
39
40
    @staticmethod
    def _get_packages(users, packages):
        if users is not None:
            for user in users:
                for _, package in _client.user_packages(user):
                    try:
                        yield package
                    except urllib.error.HTTPError as error:
Louis's avatar
Louis committed
41
                        sys.stderr.write("Ignoring user '{}': {}.".format(
Louis's avatar
Louis committed
42
43
44
45
46
                            user,
                            str(error),
                            ))
        if packages is not None:
            yield from packages
47
48
49
50

    def __iter__(self):
        yield from self.packages

Louis's avatar
Louis committed
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
    def values(self):
        yield from self.packages.values()

    def __getitem__(self, key):
        return self.packages[key]

def DateTime2datetime(date):
    timetuple = date.timetuple()
    return datetime.datetime(
            timetuple.tm_year,
            timetuple.tm_mon,
            timetuple.tm_mday,
            timetuple.tm_hour,
            timetuple.tm_min,
            timetuple.tm_sec,
            )

68
69
class Package:

Louis's avatar
Louis committed
70
    docs_api = {
71
        'readthedocs': slumber.API(base_url="http://readthedocs.io/api/v1/"),
Louis's avatar
Louis committed
72
73
        }

74
    def __init__(self, name):
Louis's avatar
Louis committed
75
        sys.stderr.write("Retrieving information about pypi package '{}'...\n".format(name))
76
        self.name = name
Louis's avatar
Louis committed
77
78
79
80
81
82
83
84
85
        self.releases = {}
        for release in _client.package_releases(name, True):
            release_urls = _client.release_urls(name, release)
            if not release_urls:
                continue
            date = DateTime2datetime(min((url['upload_time'] for url in release_urls)))
            self.releases[date] = {}
            self.releases[date]['downloads'] = sum((url['downloads'] for url in release_urls))
            self.releases[date]['version'] = release
Louis's avatar
Louis committed
86
87
        if not self.releases:
            raise ValueError("Package '{}' does not exist or haven't any release.".format(name))
Louis's avatar
Louis committed
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

        downloads = 0
        for release in sorted(self.releases):
            self.releases[release]['previous'] = downloads
            downloads += self.releases[release]['downloads']

        self.data = _client.release_data(name, self.releases[max(sorted(self.releases))]['version'])

    def iter_dates(self):
        yield from self.releases.keys()

    @property
    def total_downloads(self):
        last = sorted(self.releases.keys())[-1]
        return self.releases[last]['downloads'] + self.releases[last]['previous']
103

Louis's avatar
Louis committed
104
105
106
107
108
109
110
111
112
113
    def get_documentation(self):
        # Readthedocs
        if self.docs_api['readthedocs'].project.get(slug=self.name.lower())['objects']:
            return (
                'readthedocs', 
                None,
                )

        # Pypi docs
        if self.data['docs_url'] is not None:
Louis's avatar
Louis committed
114
            return ("pypi", None)
Louis's avatar
Louis committed
115
116

        # No documentation
Louis's avatar
Louis committed
117
        return (None, None)