This repository has been archived by the owner on Jan 12, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 117
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
194 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
# built-in | ||
from argparse import ArgumentParser | ||
from collections import defaultdict | ||
from datetime import date, timedelta | ||
from itertools import zip_longest | ||
from typing import Iterable, Iterator | ||
|
||
import attr | ||
import requests | ||
|
||
# app | ||
from ..config import builders | ||
from .base import BaseCommand | ||
|
||
|
||
@attr.s() | ||
class DateList: | ||
start = attr.ib() | ||
end = attr.ib() | ||
_data = attr.ib(factory=dict, repr=False) | ||
|
||
def add(self, date: str, value: int): | ||
self._data[date] = value | ||
|
||
def __iter__(self) -> Iterator[int]: | ||
moment = self.start | ||
while moment <= self.end: | ||
yield self._data.get(str(moment), 0) | ||
moment += timedelta(1) | ||
|
||
|
||
class PackageDownloadsCommand(BaseCommand): | ||
recent_url = 'https://pypistats.org/api/packages/{}/recent' | ||
categories = dict( | ||
pythons='https://pypistats.org/api/packages/{}/python_minor', | ||
systems='https://pypistats.org/api/packages/{}/system', | ||
) | ||
ticks = '_▁▂▃▄▅▆▇█' | ||
|
||
@classmethod | ||
def get_parser(cls): | ||
parser = ArgumentParser( | ||
prog='dephell package downloads', | ||
description='Show downloads statistic for package from PyPI.org.', | ||
) | ||
builders.build_config(parser) | ||
builders.build_output(parser) | ||
builders.build_api(parser) | ||
builders.build_other(parser) | ||
parser.add_argument('name', help='package name') | ||
return parser | ||
|
||
def __call__(self): | ||
name = self.args.name.replace('_', '-') | ||
data = dict() | ||
|
||
url = self.recent_url.format(name) | ||
response = requests.get(url) | ||
if response.status_code != 200: | ||
self.logger.error('invalid status code', extra=dict( | ||
code=response.status_code, | ||
url=url, | ||
)) | ||
return False | ||
body = response.json()['data'] | ||
data['total'] = dict( | ||
day=body['last_day'], | ||
week=body['last_week'], | ||
month=body['last_month'], | ||
) | ||
|
||
for category_name, category_url in self.categories.items(): | ||
url = category_url.format(name) | ||
response = requests.get(url) | ||
if response.status_code != 200: | ||
self.logger.error('invalid status code', extra=dict( | ||
code=response.status_code, | ||
url=url, | ||
)) | ||
return False | ||
body = response.json()['data'] | ||
|
||
yesterday = date.today() - timedelta(1) | ||
grouped = defaultdict(lambda: DateList(start=yesterday - timedelta(30), end=yesterday)) | ||
for line in body: | ||
category = line['category'].replace('.', '') | ||
grouped[category].add(date=line['date'], value=line['downloads']) | ||
|
||
data[category_name] = [] | ||
for category, downloads in grouped.items(): | ||
downloads = list(downloads) | ||
if sum(downloads) == 0: | ||
continue | ||
data[category_name].append(dict( | ||
category=category, | ||
day=downloads[-1], | ||
week=sum(downloads[-7:]), | ||
month=sum(downloads), | ||
chart=self.make_chart(downloads[-28:], group=7), | ||
)) | ||
|
||
print(self.get_value(data=data, key=self.config.get('filter'))) | ||
|
||
def make_chart(self, values: Iterable[int], group: int = None) -> str: | ||
peek = max(values) | ||
if peek == 0: | ||
chart = self.ticks[-1] * len(values) | ||
else: | ||
chart = '' | ||
for value in values: | ||
index = round((len(self.ticks) - 1) * value / peek) | ||
chart += self.ticks[int(index)] | ||
if group: | ||
chunks = map(''.join, zip_longest(*[iter(chart)] * group, fillvalue=' ')) | ||
chart = ' '.join(chunks).strip() | ||
return chart |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# dephell package downloads | ||
|
||
Get downloads statistic for package from [PyPI.org](https://pypi.org/). This command works with amazing [PyPI Stats](https://pypistats.org/) API, God bless this service. | ||
|
||
```bash | ||
$ dephell package downloads textdistance | ||
{ | ||
"pythons": [ | ||
{ | ||
"category": "35", | ||
"chart": "▅▂▄▃▁▂▄ ▇█▄▄▆█▄ ▂▂▄▅▁▁▄ ▅▄▅▄▁▁▄", | ||
"day": 120, | ||
"month": 3726, | ||
"week": 786 | ||
}, | ||
... | ||
], | ||
"systems": [ | ||
{ | ||
"category": "Linux", | ||
"chart": "▄▄▅▃▂▁█ ▇█▄▄▄▄▄ ▅▃▄▅▁▁▄ ▅▃▅▄▁▁▄", | ||
"day": 259, | ||
"month": 6947, | ||
"week": 1421 | ||
}, | ||
... | ||
], | ||
"total": { | ||
"day": 284, | ||
"month": 8751, | ||
"week": 1731 | ||
} | ||
} | ||
``` | ||
|
||
## Fields | ||
|
||
+ `pythons` -- statistic by python versions. | ||
+ `systems` -- statistic by operating systems. | ||
+ `total.day` -- total downloads yesterday. | ||
+ `total.week` -- total downloads for previous 7 days. | ||
+ `total.month` -- total downloads from yesterday to the same day in the previous month. | ||
+ `pythons.#.chart` and `system.#.chart` -- downloads bar chart for last 28 days grouped by 7 days. | ||
+ `pythons.#.day` and `system.#.day` -- total downloads yesterday. | ||
+ `pythons.#.week` and `system.#.week` -- total downloads for previous 7 days. | ||
+ `pythons.#.month` and `system.#.month` -- total downloads for previous 30 days. | ||
|
||
## Filtering | ||
|
||
This command, as all commands with JSON output, supports [filtering](filters). For example, get only month stat for pythons: | ||
|
||
```bash | ||
dephell package downloads textdistance --filter="pythons.#.category+month.each()" | ||
[ | ||
{ | ||
"category": "27", | ||
"month": 332 | ||
}, | ||
... | ||
] | ||
``` | ||
|
||
## See also | ||
|
||
1. [How to filter commands JSON output](filters). | ||
1. [dephell package show](cmd-package-show) to get information about package. | ||
1. [dephell package search](cmd-package-search) to search packages on [PyPI](https://pypi.org/). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters