changeset 118:65db090a697e

laterlinks3: add GCS support
author paulo
date Fri, 18 Sep 2020 01:13:29 -0700
parents 476cb019ad9f
children 0a20081612b9
files laterlinks3/Dockerfile laterlinks3/laterlinks_flask_app.py
diffstat 2 files changed, 90 insertions(+), 50 deletions(-) [+]
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/laterlinks3/Dockerfile	Fri Sep 18 01:13:29 2020 -0700
     1.3 @@ -0,0 +1,17 @@
     1.4 +# Use the official lightweight Python image.
     1.5 +# https://hub.docker.com/_/python
     1.6 +FROM python:3.6-slim 
     1.7 +
     1.8 +# Copy local code to the container image.
     1.9 +ENV APP_HOME /app
    1.10 +WORKDIR $APP_HOME
    1.11 +COPY . ./
    1.12 +
    1.13 +# Install production dependencies.
    1.14 +RUN pip install -r requirements.pip
    1.15 +
    1.16 +# Run the web service on container startup. Here we use the gunicorn
    1.17 +# webserver, with one worker process and 8 threads.
    1.18 +# For environments with multiple CPU cores, increase the number of workers
    1.19 +# to be equal to the cores available.
    1.20 +CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 laterlinks_flask_app:app
     2.1 --- a/laterlinks3/laterlinks_flask_app.py	Tue Sep 08 23:52:57 2020 -0700
     2.2 +++ b/laterlinks3/laterlinks_flask_app.py	Fri Sep 18 01:13:29 2020 -0700
     2.3 @@ -1,14 +1,17 @@
     2.4  import csv
     2.5  import datetime
     2.6 +import io
     2.7  import os
     2.8 +import tempfile
     2.9  
    2.10  import flask
    2.11 -
    2.12 +import google.cloud.storage
    2.13  from html3.html3 import HTML
    2.14  
    2.15  app = flask.Flask(__name__)
    2.16  
    2.17 -PIN = os.environ.get('LLPIN')
    2.18 +GCS_BUCKET = google.cloud.storage.Client().get_bucket(os.environ.get("GCS_BUCKET"))
    2.19 +PIN = os.environ.get("LLPIN")
    2.20  STRTIME_FMT = "%Y-%m-%d %H:%M:%S"
    2.21  
    2.22  
    2.23 @@ -39,30 +42,47 @@
    2.24      return "MISSING FIELD(s)!"
    2.25  
    2.26  
    2.27 -def lldb_unread_load():
    2.28 -  return csv.reader(open(LLDB_UNREAD_FN), LLDIALECT)
    2.29 +def gcs_download(fn):
    2.30 +  tmp_f = tempfile.TemporaryFile("w+")
    2.31 +  blob = GCS_BUCKET.get_blob(fn)
    2.32 +  if blob:
    2.33 +    tmp_f.write(str(blob.download_as_string(), encoding="utf-8"))
    2.34 +  return tmp_f
    2.35  
    2.36  
    2.37 -def lldb_add(inp):
    2.38 +def gcs_upload(fn, tmp_f):
    2.39 +  blob = GCS_BUCKET.blob(fn)
    2.40 +  blob.upload_from_file(tmp_f, rewind=True)
    2.41 +   
    2.42 +
    2.43 +def lldb_unread_load(lldb_unread_tmp_f):
    2.44 +  lldb_unread_tmp_f.seek(0)
    2.45 +  return csv.reader(lldb_unread_tmp_f, LLDIALECT)
    2.46 +
    2.47 +
    2.48 +def lldb_add(inp, lldb_tmp_f, lldb_unread_tmp_f):
    2.49    title = inp.get("title")
    2.50    url = inp.get("url")
    2.51    if not (title and url):
    2.52      raise MissingFieldsError()
    2.53  
    2.54    dt_str = datetime.datetime.now().strftime(STRTIME_FMT)
    2.55 -  with open(LLDB_FN, 'a') as lldb_f:
    2.56 -    csv.writer(lldb_f, LLDIALECT).writerow([title, url, dt_str])
    2.57 -  with open(LLDB_UNREAD_FN, 'a') as lldb_f:
    2.58 -    csv.writer(lldb_f, LLDIALECT).writerow([title, url, dt_str])
    2.59  
    2.60 +  lldb_tmp_f.seek(0, io.SEEK_END)
    2.61 +  csv.writer(lldb_tmp_f, LLDIALECT).writerow([title, url, dt_str])
    2.62 +  gcs_upload(LLDB_FN, lldb_tmp_f)
    2.63  
    2.64 -def lldb_unread_delete(inp):
    2.65 +  lldb_unread_tmp_f.seek(0, io.SEEK_END)
    2.66 +  csv.writer(lldb_unread_tmp_f, LLDIALECT).writerow([title, url, dt_str])
    2.67 +  gcs_upload(LLDB_UNREAD_FN, lldb_unread_tmp_f)
    2.68 +
    2.69 +
    2.70 +def lldb_unread_delete(inp, lldb_unread_tmp_f):
    2.71    delete = inp.getlist("delete")
    2.72    if not delete:
    2.73      raise MissingFieldsError()
    2.74  
    2.75 -  lldb_unread = [i for i in lldb_unread_load()]
    2.76 -  lldb_unread_f = open(LLDB_UNREAD_FN, 'w')
    2.77 +  lldb_unread = [i for i in lldb_unread_load(lldb_unread_tmp_f)]
    2.78  
    2.79    try:
    2.80      for i in delete:
    2.81 @@ -71,9 +91,10 @@
    2.82          if i == dt_str:
    2.83            lldb_unread.remove(j)
    2.84    finally:
    2.85 -    csv.writer(lldb_unread_f, LLDIALECT).writerows(lldb_unread)
    2.86 -    lldb_unread_f.close()
    2.87 -  
    2.88 +    lldb_unread_tmp_f.truncate(0)
    2.89 +    lldb_unread_tmp_f.seek(0)
    2.90 +    csv.writer(lldb_unread_tmp_f, LLDIALECT).writerows(lldb_unread)
    2.91 +    gcs_upload(LLDB_UNREAD_FN, lldb_unread_tmp_f)
    2.92      
    2.93  
    2.94  @app.route("/", methods=["GET", "POST"])
    2.95 @@ -82,46 +103,48 @@
    2.96    inp = flask.request.form
    2.97    cookies = flask.request.cookies
    2.98  
    2.99 -  if is_post:
   2.100 -    if not PIN:
   2.101 -      raise PinSetupError
   2.102 -    elif cookies.get("llpin") != PIN:
   2.103 -      raise PinFailError
   2.104 +  with gcs_download(LLDB_UNREAD_FN) as lldb_unread_tmp_f:
   2.105 +    if is_post:
   2.106 +      if not PIN:
   2.107 +        raise PinSetupError
   2.108 +      elif cookies.get("llpin") != PIN:
   2.109 +        raise PinFailError
   2.110  
   2.111 -    if inp["submit"] == "Add":
   2.112 -      lldb_add(inp)
   2.113 -    elif inp["submit"] == "Delete":
   2.114 -      lldb_unread_delete(inp)
   2.115 +      if inp["submit"] == "Add":
   2.116 +        with gcs_download(LLDB_FN) as lldb_tmp_f:
   2.117 +          lldb_add(inp, lldb_tmp_f, lldb_unread_tmp_f)
   2.118 +      elif inp["submit"] == "Delete":
   2.119 +        lldb_unread_delete(inp, lldb_unread_tmp_f)
   2.120  
   2.121 -  title = "later links..."
   2.122 -  root = HTML("html")
   2.123 +    title = "later links..."
   2.124 +    root = HTML("html")
   2.125  
   2.126 -  header = root.head
   2.127 -  header.link(rel="stylesheet", type="text/css", href=flask.url_for("static", filename="index.css"))
   2.128 -  header.title(title)
   2.129 -  
   2.130 -  body = root.body
   2.131 -  body.h1(title)
   2.132 +    header = root.head
   2.133 +    header.link(rel="stylesheet", type="text/css", href=flask.url_for("static", filename="index.css"))
   2.134 +    header.title(title)
   2.135 +    
   2.136 +    body = root.body
   2.137 +    body.h1(title)
   2.138  
   2.139 -  form = body.form(action="/", method="post")
   2.140 +    form = body.form(action="/", method="post")
   2.141  
   2.142 -  table = form.table
   2.143 -  hrow = table.tr
   2.144 -  hrow.th("Link")
   2.145 -  hrow.th("Created")
   2.146 -  hrow.th.input(type="submit", name="submit", value="Delete")
   2.147 +    table = form.table
   2.148 +    hrow = table.tr
   2.149 +    hrow.th("Link")
   2.150 +    hrow.th("Created")
   2.151 +    hrow.th.input(type="submit", name="submit", value="Delete")
   2.152  
   2.153 -  for (title, url, dt_str) in lldb_unread_load():
   2.154 -    row = table.tr
   2.155 -    row.td.a(title, href=url)
   2.156 -    row.td(dt_str)
   2.157 -    row.td.input(type="checkbox", name="delete", value=dt_str)
   2.158 +    for (title, url, dt_str) in lldb_unread_load(lldb_unread_tmp_f):
   2.159 +      row = table.tr
   2.160 +      row.td.a(title, href=url)
   2.161 +      row.td(dt_str)
   2.162 +      row.td.input(type="checkbox", name="delete", value=dt_str)
   2.163  
   2.164 -  p1 = form.p
   2.165 -  p1.label("Title").input(type="text", name="title", size="64")
   2.166 -  p1.br
   2.167 -  p1.label("URL").input(type="text", name="url", size="64")
   2.168 -  p1.br
   2.169 -  p1.input(type="submit", name="submit", value="Add")
   2.170 +    p1 = form.p
   2.171 +    p1.label("Title").input(type="text", name="title", size="64")
   2.172 +    p1.br
   2.173 +    p1.label("URL").input(type="text", name="url", size="64")
   2.174 +    p1.br
   2.175 +    p1.input(type="submit", name="submit", value="Add")
   2.176  
   2.177 -  return str(root).encode("utf-8")
   2.178 +    return str(root).encode("utf-8")