__init__.py 3.46 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
16
VERSION = "0.2.0-dev"

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

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

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

    def __iter__(self):
        yield from self.packages

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

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

56
57
class Package:

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

62
    def __init__(self, name):
Louis's avatar
Louis committed
63
        sys.stderr.write("Retrieving information about pypi package '{}'...\n".format(name))
64
65
66
67
        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))
68
        self.name = name
69
        self.info = self.rawdata["info"]
Louis's avatar
Louis committed
70
        self.releases = {}
71
72
        for version, files in self.rawdata['releases'].items():
            if not files:
Louis's avatar
Louis committed
73
                continue
74
75
76
77
            date = datetime.datetime.strptime(
                min((url['upload_time'] for url in files)),
                "%Y-%m-%dT%H:%M:%S",
                )
Louis's avatar
Louis committed
78
            self.releases[date] = {}
79
80
            self.releases[date]['downloads'] = sum((url['downloads'] for url in files))
            self.releases[date]['version'] = version
Louis's avatar
Louis committed
81
82
        if not self.releases:
            raise ValueError("Package '{}' does not exist or haven't any release.".format(name))
Louis's avatar
Louis committed
83
84
85
86
87
88
89
90
91
92
93
94
95

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

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

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

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