/ appfigures.py
appfigures.py
  1  #!/usr/bin/env python3
  2  
  3  
  4  def openURL(urlFragment):
  5      from os import environ
  6      from urllib.request import Request, urlopen
  7  
  8      CLIENT_KEY = environ["CLIENT_KEY"]
  9      BASE = "https://api.appfigures.com/v2/"
 10      AUTH_HEADER = environ["AUTH_HEADER"]
 11  
 12      return urlopen(
 13          Request(
 14              BASE + urlFragment,
 15              headers={"X-Client-Key": CLIENT_KEY, "Authorization": AUTH_HEADER},
 16          )
 17      )
 18  
 19  
 20  def getData(pj):
 21      from datetime import datetime
 22  
 23      # Remove last couple days, since they're 0
 24      return list(
 25          zip(
 26              *[
 27                  (
 28                      datetime.strptime(z["date"], "%Y-%m-%d"),
 29                      z["downloads"],
 30                      z["updates"],
 31                      z["downloads"] - z["uninstalls"],
 32                  )
 33                  for z in sorted(pj.values(), key=lambda _: _["date"])
 34              ][:-4]
 35          )
 36      )
 37  
 38  
 39  def combineDaily(pj, func, numDays):
 40      assert numDays in (1, 7)
 41      return tuple([func(pj[i : i + numDays]) for i in range(0, len(pj), numDays)])
 42  
 43  
 44  def getLastElem(arr):
 45      return arr[-1]
 46  
 47  
 48  def getAxes(t, y_axis_choice, numDays):
 49      assert len(t[0]) == len(t[y_axis_choice])
 50      assert y_axis_choice in (1, 2, 3)
 51      assert numDays in (1, 7)
 52      assert t[0] == combineDaily(t[0], getLastElem, 1)
 53      assert t[y_axis_choice] == combineDaily(t[y_axis_choice], sum, 1)
 54  
 55      return (
 56          combineDaily(t[0], getLastElem, numDays),
 57          combineDaily(t[y_axis_choice], sum, numDays),
 58      )
 59  
 60  
 61  def plotData(filename, ylabel, timespan, x, y):
 62      # allow this to run on a headless server
 63      import matplotlib
 64  
 65      matplotlib.use("Agg")
 66  
 67      import matplotlib.pyplot as plt
 68      import matplotlib.dates as mdates
 69  
 70      assert len(x) == len(y)
 71  
 72      # https://stackoverflow.com/questions/9627686/plotting-dates-on-the-x-axis-with-pythons-matplotlib#9627970
 73      plt.clf()
 74      plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d"))
 75      plt.gca().xaxis.set_major_locator(mdates.AutoDateLocator())
 76      plt.xlabel("Date")
 77  
 78      assert ylabel in ("Downloads", "Updates", "Net New Installs")
 79      plt.ylabel(ylabel)
 80  
 81      assert timespan in ("Daily", "Weekly")
 82      plt.title("Status.im %s %s (Appfigures)" % (timespan, ylabel))
 83  
 84      plt.plot(x, y)
 85      plt.gcf().autofmt_xdate()
 86      plt.savefig(filename, dpi=150)
 87  
 88  
 89  def checkOutputPath(path):
 90      from os import access, F_OK
 91  
 92      return access(path, F_OK)
 93  
 94  
 95  def main():
 96      from json import loads
 97      from os.path import join
 98      from sys import argv
 99  
100      outputPath = argv[1]
101  
102      # TOCTOU, but doesn't much matter. Just helpful to detect
103      # failures early if possible.
104      assert checkOutputPath(outputPath)
105  
106      getPath = lambda n: join(outputPath, n)
107  
108      data = getData(loads(openURL("sales/dates/-120/0").read()))
109  
110      # data has, in order: date, downloads, updates, and net new installs
111      for interval, numDays in (("Daily", 1), ("Weekly", 7)):
112          fi = interval.lower()
113          plotData(
114              getPath("downloads_%s.png" % fi),
115              "Downloads",
116              interval,
117              *getAxes(data, 1, numDays)
118          )
119          plotData(
120              getPath("updates_%s.png" % fi),
121              "Updates",
122              interval,
123              *getAxes(data, 2, numDays)
124          )
125          plotData(
126              getPath("netnewinstalls_%s.png" % fi),
127              "Net New Installs",
128              interval,
129              *getAxes(data, 3, numDays)
130          )
131  
132  
133  main()