Permalink
Fetching contributors…
Cannot retrieve contributors at this time
executable file 240 lines (187 sloc) 7.02 KB
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Calculates the total number of downloads that a particular PyPI package has
received across all versions tracked by PyPI
author : codekoala (not me)
License : ask codekoala
source : http://www.codekoala.com/blog/2010/pypi-download-stats/
Added a save method
"""
import argparse
from datetime import datetime
import locale
import sys
import xmlrpclib
from os import path, environ
from json import load
from time import sleep
import shutil
locale.setlocale(locale.LC_ALL, '')
class PyPIDownloadAggregator(object):
def __init__(self, package_name, include_hidden=True, quiet=False ):
self.package_name = package_name
self.include_hidden = include_hidden
self.proxy = xmlrpclib.Server('https://pypi.python.org/pypi')
self._downloads = {}
self.quiet = quiet
self.first_upload = None
self.first_upload_rel = None
self.last_upload = None
self.last_upload_rel = None
@property
def releases(self):
"""Retrieves the release number for each uploaded release"""
result = self.proxy.package_releases(self.package_name, self.include_hidden)
if len(result) == 0:
# no matching package--search for possibles, and limit to 15 results
results = self.proxy.search({
'name': self.package_name,
'description': self.package_name
}, 'or')[:15]
# make sure we only get unique package names
matches = []
for match in results:
name = match['name']
if name not in matches:
matches.append(name)
# if only one package was found, return it
if len(matches) == 1:
self.package_name = matches[0]
return self.releases
error = """No such package found: %s
Possible matches include:
%s
""" % (self.package_name, '\n'.join('\t- %s' % n for n in matches))
sys.exit(error)
return result
@property
def downloads(self, force=False):
"""Calculate the total number of downloads for the package"""
if len(self._downloads) == 0 or force:
for release in self.releases:
urls = self.proxy.release_urls(self.package_name, release)
self._downloads[release] = 0
for url in urls:
# upload times
uptime = datetime.strptime(url['upload_time'].value, "%Y%m%dT%H:%M:%S")
if self.first_upload is None or uptime < self.first_upload:
self.first_upload = uptime
self.first_upload_rel = release
if self.last_upload is None or uptime > self.last_upload:
self.last_upload = uptime
self.last_upload_rel = release
self._downloads[release] += url['downloads']
return self._downloads
def total(self):
return sum(self.downloads.values())
def average(self):
return self.total() / len(self.downloads)
def max(self):
return max(self.downloads.values())
def min(self):
return min(self.downloads.values())
def save(self):
from os import path, environ
from json import load, dump, dumps
sep = str
from time import mktime
from datetime import date
save=path.join(environ["HOME"], ".pipy.stat.json" )
json = dict(
date = "%s" % date.today(),
name = self.package_name,
first_upload = '%s' % self.first_upload,
first_release = self.first_upload_rel,
last_upload = '%s' % self.last_upload,
last_release = sep(self.last_upload_rel),
nb_release = sep(len(self.releases)),
max_dl = sep(self.max()),
min_dl = sep(self.min()),
av_dl = sep(self.average()),
total_dl = sep(self.total()),
)
if not path.exists(save):
with file(save,"w") as f:
f.write("[]")
with open(save) as stored:
result = filter(
lambda r : r["name"] != self.package_name or
r["date"] != str(date.today()),
load(stored)
)
result += [ json ]
dump(result,open("%s.bak" % save ,"w"))
try:
## move are atomic operations less dangerous than previous
shutil.move(save, "%s.old" % save)
shutil.move("%s.bak" % save,save)
except Exception as e:
print("something weired occured")
print("""
check : %s.bak (new stats), %s.old (backup of old stats) %s
"""% (save)* 3)
raise( e )
def stats(self):
"""Prints a nicely formatted list of statistics about the package"""
self.downloads # explicitly call, so we have first/last upload data
params = (
self.package_name,
self.first_upload,
self.first_upload_rel,
self.last_upload,
self.last_upload_rel,
len(self.releases),
self.max(),
self.min(),
self.average(),
self.total(),
)
if not self.quiet:
print( """PyPI Package statistics for: %s
First Upload: %40s (%r)
Last Upload: %40s (%r)
Number of releases: %34s
Most downloads: %35s
Fewest downloads: %35s
Average downloads: %35s
Total downloads: %35s
""" % params)
self.save()
"""sur recommandation de grenoya :)"""
parser = argparse.ArgumentParser(description = '''
Gathers download stats from pypi regarding the download information
of the geiven package. Print them, and stores them in a file for further
use. If no package name are provided, it will try to get all packages
known from previously fetched stats.
-q will print less output (only warnings and errors)
''')
parser.add_argument("-q", "--quiet",
action="store_true",
help="less verbose output",
default=False,
)
parser.add_argument('_package', metavar='package_name' ,
nargs='*', help='package name to be retrieved')
options = parser.parse_args()
guessed = set()
if not options._package:
saved=path.join(environ["HOME"], ".pipy.stat.json" )
try:
with open(saved ) as f:
guessed = reduce(
set.union,
map(
lambda x : set( [ x['name'] ] ), load(f)
)
)
print( "found %s" % ",".join(guessed) )
except:
print( "could not load previously stored stats to guess packages" )
for pkg in options._package or guessed :
locale.setlocale(locale.LC_ALL, '')
from time import sleep
try:
PyPIDownloadAggregator(pkg.strip(),True, options.quiet).stats()
except Exception as e:
print( "fetching stats for *%s* failed (%r)" % ( pkg, e) )