Skip to content

Commit 4e7bfdb

Browse files
authored
Merge pull request #304 from tekktrik/dev/pypi-stats
Add weekly PyPI stats to daily report
2 parents eaa233b + c760ed6 commit 4e7bfdb

File tree

3 files changed

+129
-5
lines changed

3 files changed

+129
-5
lines changed

adabot/circuitpython_libraries.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import datetime
1010
import inspect
1111
import logging
12+
import os
1213
import re
1314
import sys
1415
import traceback
@@ -176,6 +177,13 @@ def run_library_checks(validators, kw_args, error_depth):
176177
logger.info("")
177178
logger.info("State of CircuitPython + Libraries + Blinka")
178179

180+
logger.info("")
181+
logger.info("**This report contains information from the previous seven days.**")
182+
logger.info(
183+
"**Any changes (PRs merged, etc.) made today are not included in this report.**"
184+
)
185+
logger.info("")
186+
179187
logger.info("### Overall")
180188
print_pr_overview(lib_insights, core_insights, blinka_insights)
181189
print_issue_overview(lib_insights, core_insights, blinka_insights)
@@ -216,6 +224,27 @@ def run_library_checks(validators, kw_args, error_depth):
216224
logger.info("* Core download stats available at https://circuitpython.org/stats")
217225

218226
logger.info("")
227+
228+
# Get PyPI stats
229+
if os.environ.get("BIGQUERY_PRIVATE_KEY") and os.environ["BIGQUERY_CLIENT_EMAIL"]:
230+
pypi_stats = dl_stats.retrieve_pypi_stats(repos)
231+
library_pypi_names = [
232+
repo["name"].replace("_", "-").lower() for repo in common_funcs.list_repos()
233+
]
234+
total_library_pypi_stats = 0
235+
library_pypi_stats = [
236+
stat for stat in pypi_stats if stat.name in library_pypi_names
237+
]
238+
top_library_pypi_stats = library_pypi_stats[:10]
239+
total_library_pypi_stats = 0
240+
for stat in library_pypi_stats:
241+
total_library_pypi_stats += stat.num_downloads
242+
blinka_pypi_stats = [
243+
stat for stat in pypi_stats if stat.name == "adafruit-blinka"
244+
][0]
245+
else:
246+
pypi_stats = None
247+
219248
logger.info("### Libraries")
220249
print_pr_overview(lib_insights)
221250
logger.info(" * Merged pull requests:")
@@ -247,6 +276,30 @@ def run_library_checks(validators, kw_args, error_depth):
247276

248277
logger.info("* https://circuitpython.org/contributing")
249278

279+
logger.info("")
280+
logger.info("#### Library PyPI Weekly Download Stats")
281+
if pypi_stats:
282+
logger.info("* **Total Library Stats**")
283+
logger.info(
284+
" * %d PyPI downloads over %d libraries",
285+
total_library_pypi_stats,
286+
len(library_pypi_names),
287+
)
288+
logger.info("* **Top 10 Libraries by PyPI Downloads**")
289+
for lib_stat in top_library_pypi_stats:
290+
logger.info(
291+
" * %s: %d",
292+
lib_stat.name,
293+
lib_stat.num_downloads,
294+
)
295+
else:
296+
logger.info(
297+
"*This info is not printed because the cron is unable to access secrets.*"
298+
)
299+
logger.info(
300+
"*This is expected if this was run as part of the CI for a pull request.*"
301+
)
302+
250303
logger.info("")
251304
logger.info("#### Library updates in the last seven days:")
252305
if len(new_libs) != 0:
@@ -295,7 +348,20 @@ def run_library_checks(validators, kw_args, error_depth):
295348
print_issue_overview(blinka_insights)
296349
logger.info("* %s open issues", len(blinka_insights["open_issues"]))
297350
logger.info(" * https://github.com/adafruit/Adafruit_Blinka/issues")
298-
blinka_dl = dl_stats.piwheels_stats().get("adafruit-blinka", {}).get("month", "N/A")
351+
blinka_dl = (
352+
dl_stats.retrieve_piwheels_stats()
353+
.get("adafruit-blinka", {})
354+
.get("month", "N/A")
355+
)
356+
if pypi_stats:
357+
logger.info(
358+
"* PyPI downloads in the last week: %d",
359+
blinka_pypi_stats.num_downloads,
360+
)
361+
else:
362+
logger.info(
363+
"* PyPI download stats unavailable - this is normal for CI runs triggered by PRs"
364+
)
299365
logger.info("* Piwheels Downloads in the last month: %s", blinka_dl)
300366
logger.info("Number of supported boards: %s", blinka_funcs.board_count())
301367

adabot/circuitpython_library_download_stats.py

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,23 @@
66
and each library.
77
"""
88

9+
import os
910
import datetime
1011
import sys
1112
import argparse
1213
import traceback
1314
import operator
1415
import requests
1516

17+
import github as pygithub
18+
from google.cloud import bigquery
19+
import google.oauth2.service_account
20+
1621
from adabot import github_requests as gh_reqs
1722
from adabot.lib import common_funcs
1823

24+
GH_INTERFACE = pygithub.Github(os.environ.get("ADABOT_GITHUB_ACCESS_TOKEN"))
25+
1926
# Setup ArgumentParser
2027
cmd_line_parser = argparse.ArgumentParser(
2128
description="Adabot utility for CircuitPython Library download stats."
@@ -53,7 +60,7 @@
5360
PIWHEELS_PACKAGES_URL = "https://www.piwheels.org/packages.json"
5461

5562

56-
def piwheels_stats():
63+
def retrieve_piwheels_stats():
5764
"""Get data dump of piwheels download stats"""
5865
stats = {}
5966
response = requests.get(PIWHEELS_PACKAGES_URL)
@@ -68,12 +75,12 @@ def piwheels_stats():
6875
return stats
6976

7077

71-
def get_pypi_stats():
78+
def parse_piwheels_stats():
7279
"""Map piwheels download stats for each repo"""
7380
successful_stats = {}
7481
failed_stats = []
7582
repos = common_funcs.list_repos()
76-
dl_stats = piwheels_stats()
83+
dl_stats = retrieve_piwheels_stats()
7784
for repo in repos:
7885
if repo["owner"]["login"] == "adafruit" and repo["name"].startswith(
7986
"Adafruit_CircuitPython"
@@ -101,6 +108,55 @@ def get_pypi_stats():
101108
return successful_stats, failed_stats
102109

103110

111+
def retrieve_pypi_stats(repos):
112+
"""Get data dump of PyPI download stats (for the last 7 days)"""
113+
# Create access info dictionary
114+
access_info = {
115+
"private_key": os.environ["BIGQUERY_PRIVATE_KEY"],
116+
"client_email": os.environ["BIGQUERY_CLIENT_EMAIL"],
117+
"token_uri": "https://oauth2.googleapis.com/token",
118+
}
119+
120+
# Use credentials to create a BigQuery client object
121+
credentials = google.oauth2.service_account.Credentials.from_service_account_info(
122+
access_info
123+
)
124+
client = bigquery.Client("circuitpython-stats", credentials=credentials)
125+
126+
# Get the list of PyPI package names
127+
packages = [repo["name"].replace("_", "-").lower() for repo in repos]
128+
129+
# Construct the query to use
130+
query = """
131+
SELECT
132+
file.project as name, COUNT(*) AS num_downloads,
133+
FROM
134+
`bigquery-public-data.pypi.file_downloads`
135+
WHERE DATE(timestamp)
136+
BETWEEN DATE_TRUNC(DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY), DAY)
137+
AND DATE_TRUNC(DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY), DAY)
138+
AND file.project in (
139+
"""
140+
packages_query = ["?" for _ in packages]
141+
query_parameters = [
142+
bigquery.ScalarQueryParameter(None, "STRING", package) for package in packages
143+
]
144+
query += ",".join(packages_query)
145+
query += """
146+
)
147+
GROUP BY file.project
148+
ORDER BY num_downloads DESC
149+
"""
150+
151+
# Configure and run the query
152+
job_config = bigquery.QueryJobConfig(query_parameters=query_parameters)
153+
query_job = client.query(
154+
query,
155+
job_config=job_config,
156+
)
157+
return query_job.result()
158+
159+
104160
def get_bundle_stats(bundle):
105161
"""Returns the download stats for 'bundle'. Uses release tag names to compile download
106162
stats for the last 7 days. This assumes an Adabot release within that time frame, and
@@ -172,7 +228,7 @@ def run_stat_check():
172228
]
173229
output_handler("Adafruit CircuitPython Library Piwheels downloads:")
174230
output_handler()
175-
pypi_downloads, pypi_failures = get_pypi_stats()
231+
pypi_downloads, pypi_failures = parse_piwheels_stats()
176232
for stat in sorted(
177233
pypi_downloads.items(), key=operator.itemgetter(1, 1), reverse=True
178234
):

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ parse==1.19.0
1515
GitPython==3.1.27
1616
PyGithub==1.55
1717
typing-extensions~=4.0
18+
google-auth~=2.13
19+
google-cloud-bigquery~=3.3

0 commit comments

Comments
 (0)