Skip to content

Commit 72a0af1

Browse files
The script
1 parent 23cd636 commit 72a0af1

File tree

4 files changed

+125
-0
lines changed

4 files changed

+125
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,5 @@ cython_debug/
158158
# and can be added to the global gitignore or merged into this file. For a more nuclear
159159
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160160
#.idea/
161+
162+
.envrc

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,28 @@
11
# pyreadiness-parser
22
Parses https://pyreadiness.org
3+
4+
```sh
5+
python parser
6+
```
7+
8+
Sample output:
9+
```
10+
rating ready name url languages
11+
-------- ------- ------------------ ------------------------------------------- --------------------------------------------------------------
12+
0 + boto3 https://pypi.org/project/boto3 Python:100.00%
13+
1 + awscli https://pypi.org/project/awscli Python:99.92% Shell:0.04% Batchfile:0.04%
14+
2 + botocore https://pypi.org/project/botocore Python:99.32% Gherkin:0.68%
15+
3 + urllib3 https://pypi.org/project/urllib3 Python:99.97% Shell:0.03%
16+
4 + requests https://pypi.org/project/requests Python:99.77% Makefile:0.23%
17+
5 + charset-normalizer https://pypi.org/project/charset-normalizer Python:99.71% Shell:0.29%
18+
6 + typing-extensions https://pypi.org/project/typing-extensions Python:100.00%
19+
7 - setuptools https://pypi.org/project/setuptools Python:98.97% C:1.02% HTML:0.01% CMake:0.01%
20+
8 + certifi https://pypi.org/project/certifi Python:97.78% Makefile:2.22%
21+
9 - python-dateutil https://pypi.org/project/python-dateutil Python:99.62% Shell:0.32% Batchfile:0.06%
22+
10 + google-api-core https://pypi.org/project/google-api-core Python:95.76% Shell:3.98% Dockerfile:0.27%
23+
11 + s3transfer https://pypi.org/project/s3transfer Python:100.00%
24+
12 + idna https://pypi.org/project/idna Python:100.00%
25+
13 - six https://pypi.org/project/six Python:100.00%
26+
14 + pyyaml https://pypi.org/project/pyyaml Python:81.44% Cython:18.05% Makefile:0.26% Shell:0.17% C:0.08%
27+
28+
```

parser/__main__.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import logging
2+
from dataclasses import dataclass, asdict
3+
from itertools import count
4+
from typing import Iterator
5+
6+
import requests
7+
from bs4 import BeautifulSoup
8+
from more_itertools import one
9+
from ratelimit import limits
10+
from ratelimit.exception import RateLimitException
11+
from tabulate import tabulate
12+
from tenacity import retry, retry_if_exception_type, wait_fixed
13+
from tqdm import tqdm
14+
15+
session = requests.Session()
16+
log = logging.getLogger()
17+
18+
19+
@dataclass
20+
class PypiProject:
21+
name: str
22+
rating: int
23+
is_ready: bool
24+
25+
@property
26+
def url(self) -> str:
27+
return f'https://pypi.org/project/{self.name}'
28+
29+
def __str__(self) -> str:
30+
return self.name
31+
32+
def get_info(self) -> dict:
33+
response = session.get(f'https://pypi.org/pypi/{self.name}/json')
34+
response.raise_for_status()
35+
return response.json()
36+
37+
@retry(
38+
wait=wait_fixed(1),
39+
retry=retry_if_exception_type(RateLimitException),
40+
)
41+
@limits(calls=1, period=4)
42+
def get_language_ratio(self) -> dict[str, float]:
43+
44+
links = self.get_info()['info']['project_urls']
45+
try:
46+
github_url = one({url for url in links.values() if url.startswith('https://github.com') and url.count('/') == 4})
47+
except ValueError:
48+
log.warning('Cannot detect github url for %s: %s', self.name, links)
49+
return {}
50+
51+
owner, repo = github_url.removeprefix('https://github.com/').split('/')
52+
response = session.get(f'https://api.github.com/repos/{owner}/{repo}/languages')
53+
response.raise_for_status()
54+
languages = response.json()
55+
56+
total = sum(languages.values())
57+
return {language: value/total for language, value in languages.items()}
58+
59+
60+
def parse_pyreadiness(version: str = '3.11') -> Iterator[PypiProject]:
61+
response = session.get(f'https://pyreadiness.org/{version}')
62+
response.raise_for_status()
63+
64+
counter = count()
65+
soup = BeautifulSoup(response.text, 'html.parser')
66+
lists = soup.find_all('div', class_='list')
67+
for list_ in lists:
68+
projects = list_.find_all('a')
69+
for project in projects:
70+
name, sign = project.text.strip().split(' ')
71+
yield PypiProject(
72+
name=name,
73+
rating=next(counter),
74+
is_ready=sign == '✓',
75+
)
76+
77+
78+
if __name__ == '__main__':
79+
print(tabulate(
80+
[
81+
(
82+
project.rating,
83+
'+' if project.is_ready else '-',
84+
project.name,
85+
project.url,
86+
' '.join(f'{language}:{value*100:.2f}%' for language, value in project.get_language_ratio().items()),
87+
) for project in tqdm(parse_pyreadiness())
88+
],
89+
headers=('rating', 'ready', 'name', 'url', 'languages'),
90+
))

requirements.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
requests==2.31.0
2+
beautifulsoup4==4.12.2
3+
more-itertools==9.1.0
4+
tabulate==0.9.0
5+
tqdm==4.65.0
6+
ratelimit==2.2.1
7+
tenacity==8.2.2

0 commit comments

Comments
 (0)