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

import collections
4
5
import json
import requests
Louis's avatar
Louis committed
6
import datetime
7
8
import itertools
import jinja2
9
import random
Louis's avatar
Louis committed
10
import slumber
Louis's avatar
Louis committed
11
import sys
Louis's avatar
Louis committed
12
import urllib
13
import xmlrpc.client as xmlrpclib
Louis's avatar
Louis committed
14

15
_client = xmlrpclib.ServerProxy('https://pypi.python.org/pypi')
16
17
18

class PackageList:
    def __init__(self, user=None, pkg=None):
Louis's avatar
Louis committed
19
        self.packages = {}
20
21
        package_list = list(self._get_packages(user, pkg))
        if not package_list:
22
            raise NotImplementedError("TODO")
23
        for name in sorted(package_list):
Louis's avatar
Louis committed
24
25
            try:
                self.packages[name] = Package(name)
26
27
            except ValueError as error:
                sys.stderr.write("Ignoring package '{}' with no releases: {}.\n".format(name, error))
Louis's avatar
Louis committed
28
                continue
29

Louis's avatar
Louis committed
30
31
32
33
34
35
36
37
    @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
38
                        sys.stderr.write("Ignoring user '{}': {}.".format(
Louis's avatar
Louis committed
39
40
41
42
43
                            user,
                            str(error),
                            ))
        if packages is not None:
            yield from packages
44
45
46
47

    def __iter__(self):
        yield from self.packages

Louis's avatar
Louis committed
48
49
50
51
52
53
    def values(self):
        yield from self.packages.values()

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

54
55
class Package:

Louis's avatar
Louis committed
56
    docs_api = {
57
        'readthedocs': slumber.API(base_url="http://readthedocs.io/api/v1/"),
Louis's avatar
Louis committed
58
59
        }

60
    def __init__(self, name):
Louis's avatar
Louis committed
61
        sys.stderr.write("Retrieving information about pypi package '{}'...\n".format(name))
62
63
64
65
        try:
            self.rawdata = json.loads(requests.get("http://pypi.python.org/pypi/{}/json".format(name)).text)
        except Exception as error:
            raise Exception("Error while getting data for package '{}': {}.".format(name, error))
66
        self.name = name
67
        self.info = self.rawdata["info"]
Louis's avatar
Louis committed
68
        self.releases = {}
69
70
        for version, files in self.rawdata['releases'].items():
            if not files:
Louis's avatar
Louis committed
71
                continue
72
73
74
75
            date = datetime.datetime.strptime(
                min((url['upload_time'] for url in files)),
                "%Y-%m-%dT%H:%M:%S",
                )
Louis's avatar
Louis committed
76
            self.releases[date] = {}
77
78
            self.releases[date]['downloads'] = sum((url['downloads'] for url in files))
            self.releases[date]['version'] = version
Louis's avatar
Louis committed
79
80
        if not self.releases:
            raise ValueError("Package '{}' does not exist or haven't any release.".format(name))
Louis's avatar
Louis committed
81
82
83
84
85
86
87
88
89
90
91
92
93

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

    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']
94

Louis's avatar
Louis committed
95
96
97
98
99
100
101
102
103
    def get_documentation(self):
        # Readthedocs
        if self.docs_api['readthedocs'].project.get(slug=self.name.lower())['objects']:
            return (
                'readthedocs', 
                None,
                )

        # Pypi docs
104
        if self.info['docs_url'] is not None:
Louis's avatar
Louis committed
105
            return ("pypi", None)
Louis's avatar
Louis committed
106
107

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