Skip to content
This repository has been archived by the owner on Jan 12, 2021. It is now read-only.

Commit

Permalink
Merge branch 'command-downloads'
Browse files Browse the repository at this point in the history
  • Loading branch information
orsinium committed Apr 2, 2019
2 parents 21cb696 + 7d82bb1 commit a164314
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 4 deletions.
5 changes: 4 additions & 1 deletion dephell/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .jail_install import JailInstallCommand
from .jail_list import JailListCommand
from .jail_remove import JailRemoveCommand
from .package_downloads import PackageDownloadsCommand
from .package_install import PackageInstallCommand
from .package_list import PackageListCommand
from .package_search import PackageSearchCommand
Expand Down Expand Up @@ -41,8 +42,9 @@
'JailInstallCommand',
'JailListCommand',
'JailRemoveCommand',
'PackageListCommand',
'PackageDownloadsCommand',
'PackageInstallCommand',
'PackageListCommand',
'PackageSearchCommand',
'PackageShowCommand',
'VenvCreateCommand',
Expand Down Expand Up @@ -71,6 +73,7 @@
'jail list': JailListCommand,
'jail remove': JailRemoveCommand,
# 'jail update': ...,
'package downloads': PackageDownloadsCommand,
'package install': PackageInstallCommand,
'package list': PackageListCommand,
'package search': PackageSearchCommand,
Expand Down
7 changes: 4 additions & 3 deletions dephell/commands/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,19 @@ def validate(self):

@classmethod
def get_value(cls, data, key, sep: Optional[str] = '-'):
json_params = dict(indent=2, sort_keys=True, ensure_ascii=False)
# print all config
if not key:
return json.dumps(data, indent=2, sort_keys=True)
return json.dumps(data, **json_params)

if sep is None:
return json.dumps(data[key], indent=2, sort_keys=True)
return json.dumps(data[key], **json_params)

keys = key.replace('.', sep).split(sep)
value = reduce(getitem, keys, data)
# print config section
if isinstance(value, (dict, list)):
return json.dumps(value, indent=2, sort_keys=True)
return json.dumps(value, **json_params)

# print one value
return value
1 change: 1 addition & 0 deletions dephell/commands/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def _each(value):
'min()': lambda v: min(v),
'reverse()': lambda v: v[::-1],
'sort()': lambda v: sorted(v),
'sum()': lambda v: sum(v),
'type()': lambda v: type(v).__name__,
'zip()': lambda v: list(map(list, zip(*v))),

Expand Down
116 changes: 116 additions & 0 deletions dephell/commands/package_downloads.py
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
67 changes: 67 additions & 0 deletions docs/cmd-package-downloads.md
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/).
1 change: 1 addition & 0 deletions docs/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Functions:
+ `min()` -- get minimum value from a list.
+ `reverse()` or `reversed()` -- reverse values in a list.
+ `sort()` or `sorted()` -- sort values in a list.
+ `sum()` -- sum of values in a list.
+ `type()` -- get value type.
+ `zip()` -- transpose output. `[[a, b], [c, d], [e, f]]` will be converted into `[[a, c, e], [b, d, f]]`.

Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
cmd-jail-install
cmd-jail-list
cmd-jail-remove
cmd-package-downloads
cmd-package-install
cmd-package-list
cmd-package-search
Expand Down

0 comments on commit a164314

Please sign in to comment.