Saturday, April 06, 2024

Python 2.7 -> 3.8 on App Engine. Ordered checklist with an unfolding stub.


The context here is a kind of "resource pump" webapp, a common enough migration of early static web sites, during the mid-web-2.0-era (say 2008), to Python 2 with WSGI on Google App Engine. The simple python program does very little, allowing the app.yaml file to define the work, serving folders full of static files.

So, how do we move these sites to App Engine with Python 3.8 and Flask 2? 

Here's an ordered checklist, that is, a set of instructions with ordered dependencies. 

That's also typical of unfolding sequences in software, but in the latter, there tends to be more creativity and judgment involved ... and the steps are not instructions ... instead they're helpful issues to consider at that moment. 

Most of the steps below simply need to happen, in that order. So they are instructions. 

There's only one somewhat creative step here (5) ... yet it highlights the point where more steps might be written and inserted, to serve a wider range of migrations.

  1. I assume this is a directory with git source control.


    If so:

    git commit -a -m 'starting migration from python 2.7' 

     If not: 

    git init   
    git add app.yaml  
    git commit -a -m 'starting source control'

  2. mkdir templates

  3. mv index.html templates

    and git add templates/index.html

  4. add requirements.txt 

    and git add requirements.txt

  5. add main.py (if there's a route table in the WSGI version, move the routes to flask. This is the one creative task. It's kind of a stub: this is where all the creative tasks in a sequence would go, to serve a greater range of programs.) 

    and git add main.py

  6. change the app.yaml head

  7. change the app.yaml tail 

    and git commit -m 'first python 3.8 changes'

  8. (if it makes sense, set up the local test environment)

  9. (if it makes sense, run gunicorn & test) Note that gunicorn does not use app.yaml, so your mileage may vary, in using this local test environment. If you don't need to debug the server-side python, see deployment test steps 12-14

  10. (if you created a virtual environment, add the <project env> directory (see below) to .gcloudignore)

  11. gcloud app deploy --project <migrating site> --no-promote

  12. Go to (cloud console->app engine -> versions), find the new version, launch and test

  13. Is the test good? Select the new version and click “migrate traffic”.

If you want to setup a local test environment (again, useful if there's more server code to test):

virtualenv -p python3.8.2 <project env>

source ./<project env>/bin/activate

pip install -r requirements.txt

(or

pip install gunicorn

pip install flask

pip install google-cloud-datastore

pip list

)

gunicorn -b :8080 main:app

(test in browser at localhost:8080)

^c

deactivate


old app.yaml head:

runtime: python27

api_version: 1

threadsafe: false


new app.yaml head:

runtime: python38

app_engine_apis: true


old app.yaml tail:

- url: /.*

  script: <migrating site>.app

  secure: always

  redirect_http_response_code: 301


new app.yaml tail:

- url: /.*

  script: auto

  secure: always

  redirect_http_response_code: 301


new requirements.txt:

Flask==2.2.2

google-cloud-datastore==2.7.0

appengine-python-standard>=1.0.0

google-auth==2.17.1

google-auth-oauthlib==1.0.0

google-auth-httplib2==0.1.0

werkzeug==2.2.2

And here's the one creative step in this checklist's sequence: migrating the route table. It's only a stub for further creative-and-judged unfolding steps, if one is migrating server-side application logic:

new main.py:

from flask import Flask, render_template, request

app = Flask(__name__)


@app.route('/')

@app.route('/endpoin_one')

@app.route('/endpoint_two')

def root():

    # NB: index.html must be in /templates

    return render_template('index.html')

if __name__ == '__main__':

    app.run()


old <migrating site>.py:

# universal index.html delivery

# in python27 as a service

# on Google App Engine

import cgi

import os

import webapp2

from google.appengine.ext.webapp import template

from google.appengine.api import users

class MainPage(webapp2.RequestHandler):

    def get(self):

        template_values = {

            }

        path = os.path.join(os.path.dirname(__file__), 'index.html')

        self.response.out.write(template.render(path, template_values))

app = webapp2.WSGIApplication(

                                     [('/', MainPage)

                                     ,('/endpoint_one',MainPage)

                                     ,('/endpoint_two',MainPage)

                                      ],

                                     debug=True)


No comments: