Google App Engine: desplegar aplicaciones web con web.py



Se trata de una guía para desarrollar aplicaciones con web.py y ejecutarlas en la infraestructura de Google. Veremos qué es Google App Engine, registro, instalación, cómo desarrollar una aplicación web básica y desplegarla al servidor, configuración, templates y almacenamiento de datos. Algún conocimiento previo sobre web.py es preferbile; para ello, véase Introducción a web.py.

Puedes descargar de aquí la aplicación final como resultado al aplicar los pasos brindados por el artículo.

¿Qué es Google App Engine?

Es una plataforma proporcionada por Google que permite al usuario ejecutar sus propias aplicaciones web en la red de servidores de la empresa. De esta manera, la última se encarga de manejar las peticiones HTTP, atribuyendo a la aplicación mayores o menores recursos en relación a la demanda. En otras palabras, el desarrollador debe preocuparse únicamente por el código dejando a un lado el mantenimiento del servidor. Presenta un panel de control por el cual se administra toda la aplicación, y permite utilizar dominios propios a través de Google Apps.

Si tu objetivo es portar una aplicación web a App Engine, te adelanto que tienes (probablemente bastante) trabajo de migración por delante (según el tamaño). GAE utiliza su propia base de datos y algunas convencionales, y no todas las funciones y módulos de Python están disponibles (véase El entorno).

Si bien es gratis, consiste en diversas cuotas las cuales limitan el alcance de la aplicación. Por ejemplo, si 50 visitantes acceden a mi página, es probable que dicha cuota se encuentre en, aproximadamente, un 10%. Esto indicaría que si accedieran otros 450 visitantes se completaría la cuota, llegando al 100%, y mi aplicación quedaría pausada hasta el reinicio de todas las cuotas (cada 24hs). Otro ejemplo sería el almacenamiento de datos (el cual veremos en detalle posteriormente). Por ejemplo, si la cuota estándar (gratis) se limita a los 1000 MB de datos almacenados y nuestra aplicación alcanza dicha cantidad, podrá seguir recibiendo peticiones (si es que no se ha alcanzado el máximo de ésta cuota), pero no podrá continuar almacenando en la base de datos.

También depende del usuario dismunuir el uso de la cuota, de lo contrario se acabará rápidamente. Por ejemplo, se recomienda utilizar el caché de la base de datos para evitar el aumento de la cuota de operaciones relacionadas con el almacenamiento. Sin embargo, es bastante complejo mantener una web con visitas diarias medianas haciendo uso de la cuota gratuita. Las cuentas premium permiten exceder la cuota gratuita abonando únicamente por lo que se utiliza.

Registro

GAE brinda espacio para 10 aplicaciones de forma gratuita. Para comenzar, debes crear una cuenta de Google (dirección de correo en Gmail) y acceder con ella en este enlace. Una vez registrado e ingresado, si nunca has trabajado con App Engine o no has creado ninguna aplicación deberías ver lo siguiente:

Posteriormente retomaremos esta pantalla para crear nuestra primera aplicación.

Instalación

Como segundo paso deberás descargar el SDK. El despliegue de la aplicación hacia la infraestructura de Google se realiza a través del mismo, a diferencia de otros servicios de hosting gratuito en los cuales los archivos deben ser subidos manualmente a través de una interfaz web. El kit se encuentra constantemente en desarrollo, por lo que al haber nuevas versiones el usuario es notificado. Lamentablemente no hay un sistema de actualizaciones automáticas, es tarea del usuario el descargar e instalar. Incluye un servidor web que simula al que es empleado una vez desplegada la aplicación, para realizar tareas localmente. GAE soporta varios lenguajes: PHP, Python, Java y Go. Puedes encontrar las descargas en este enlace, ¡y asegúrate de descargar el SDK de Python! Nótese que el SDK corre en cualquier computadora con Python 2.7 (actualmente no hay soporte para Python 3), en Windows, Mac OS X y Linux. En los dos primeros, el SDK provee el «Google App Engine Launcher», un programa con interfaz gráfica que simplifica las tareas del usuario permitiendo reemplazar a éste por la consola.

El entorno

App Engine presenta un entorno de Python puro. Por lo tanto, no puedes incluir módulos escritos en C o que necesiten ser compilados. Sin embargo, sí puedes añadir tus propios módulos o paquetes de terceros, siempre que sean compatibles con el entorno. Por ejemplo, las funciones de entrada y salida se encuentran deshabilitadas. En el caso que se precise almacenar, por ejemplo, una imágen, GAE presenta sus propios métodos para lograrlo, almacenando la información en sus bases de datos. Podremos encontrar dos bibliotecas: el conjunto de paquetes y módulos estándar (con ciertas limitacions) y la librería propia de App Engine (como el API de acceso a la base de datos y el framework webapp2). Además de éstas, incluye por defecto Django, jinja2, PyYAML, y varias más. Si deseas incluir un paquete adicional debes posicionarlo en la misma carpeta en donde se encuentra tu aplicación, para que sea desplegado junto con ella. Los módulos estándar ftplib, socket, select, imp y marshal se encuentran vacíos. cPickle no es soportado, por lo que se importa como pickle.

Desarrollo

Este artículo explica el desarrollo de una aplicación web con el framework web.py. Sin embargo, también puedes utilizar algún otro que sea de tu agrado y soportado por la plataforma (como Django). Incluso GAE ofrece su propio web framework, webapp2.

Primer paso: creación de la aplicación

Para poder comenzar deberás crear una nueva aplicación desde appengine.google.com. Una vez ingresado, presiona en el botón «Create Application». No te preocupes por el espacio, todas las aplicaciones pueden ser desactivadas o removidas.

Completa los campos teniendo en cuenta lo siguiente:

  • Application Identifier: dominio por el cual tendremos acceso a nuestra aplicación. Utiliza el botón «Check Availability» para verificar que esté disponible.
  • Application Title: título para la aplicación. No es muy relevante pero no puede ser cambiado. Te recomiendo un nombre en minúsculas y sin espacios.

En los demás campos deja los valores por defecto. Por último presiona Create Application. Una vez creada, dirígete nuevamente a appengine.google.com y verás tu aplicación en la lista.

Segundo paso: configuración, estructura y código

Ahora deberás crear una carpeta en donde estará situado todo el contenido de tu aplicación, que será desplegado posteriormente a los servidores de Google. Para una mayor organización te recomiendo que dicha carpeta lleve el nombre o identificador de tu aplicación (el que has especificado en el paso anterior como «Application Title»). Una vez creada la carpeta el primer paso es crear un archivo app.yaml dentro de la misma, con el siguiente contenido:

application: nombreapp # identificador de la aplicación (coloca el tuyo aquí)
version: 1 # versión de la aplicación
runtime: python27 # intérprete
api_version: 1 # versión del API del entorno de tiempo de ejecución
threadsafe: yes
# Estructura URL
handlers:
- url: /
  script: main.app

Luego crea dos archivos más, main.app y main.py. El primero no debe tener contenido, el segundo será el script principal de nuestra aplicación.

main.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import web

urls = (
    "/", "Index"
)

app = web.application(urls, globals()).wsgifunc()


class Index:
    
    def GET(self):
        return u"<h3>Bienvenido</h3>"


if __name__ == "__main__":
    app = app.gaerun()

Nótese la función gaerun() en la última línea en lugar de la convencional run(). Si por alguna razón tu instalación de web.py no cuenta con dicha función, considera añadirla tú mismo, en el archivo application.py dentro de la carpeta web (ubicada en la instalación de Python) como un método de la clase application:

    def gaerun(self, *middleware):
        """
        Starts the program in a way that will work with Google app engine,
        no matter which version you are using (2.5 / 2.7)

        If it is 2.5, just normally start it with app.gaerun()

        If it is 2.7, make sure to change the app.yaml handler to point to the
        global variable that contains the result of app.gaerun()

        For example:

        in app.yaml (where code.py is where the main code is located)

            handlers:
            - url: /.*
              script: code.app

        Make sure that the app variable is globally accessible
        """
        wsgiapp = self.wsgifunc(*middleware)
        try:
            # check what version of python is running
            version = sys.version_info[:2]
            major   = version[0]
            minor   = version[1]

            if major != 2:
                raise EnvironmentError("Google App Engine only supports python 2.5 and 2.7")

            # if 2.7, return a function that can be run by gae
            if minor == 7:
                return wsgiapp
            # if 2.5, use run_wsgi_app
            elif minor == 5:
                from google.appengine.ext.webapp.util import run_wsgi_app
                return run_wsgi_app(wsgiapp)
            else:
                raise EnvironmentError("Not a supported platform, use python 2.5 or 2.7")
        except ImportError:
            return wsgiref.handlers.CGIHandler().run(wsgiapp)

Por último deberás copiar la carpeta web (directorio de instalación de web.py, ubicada en la instalación de Python) en el directorio de tu aplicación. De esta manera la última podrá acceder al framework una vez desplegada a Google.

Luego de realizar las tareas anteriores, el árbol de directorios debería quedar de la siguiente manera:

- nombreapp/
     |
     | - app.yaml
     | - main.py
     | - main.app
     | - web/  <-- framework web.py
          |
          | - application.py
          | - db.py
          | - etc.

Tercer paso: configuración del Launcher

Nota: solo para usuarios de Windows y Mac OS X. De lo contrario, dirígete al cuarto paso.

El SDK para Windows y Mac OS X incluye el «Google App Engine Launcher», un programa adicional para evitar el uso de la línea de comandos y facilitar las tareas de ejecución y despliegue. Para poder realizar estas dos últimas debemos configurar nuestra aplicación. Ábrelo y verás algo como lo siguiente:

Dirígete al menú File -> Add Existing Application... o presiona Ctrl + Shift + N. Se abrirá una nueva ventana. En Application Path, presiona el botón Browse ... y selecciona la ubicación de la carpeta que contiene la aplicación. Especifica el puerto en el que deseas que corra tu aplicación (8080 por defecto) y presiona Add al terminar. Una vez añadida verás el identificador en la lista de aplicaciones. A partir de ahora podrás ejecutar y desplegar tu aplicación directamente desde el Launcher.

Cuarto paso: servidor de desarrollo local

Nuestra aplicación ya está lista para ser desplegada hacia la infraestructura de Google. Sin embargo, sería óptimo probar la aplicación de manera local antes de subirla al servidor. En Linux, para iniciar el servidor de desarrollo local indica lo siguiente en la terminal:

dev_appserver.py ubicacionapp

Por defecto el puerto 8080 será utilizado. Para cambiarlo, indica:

dev_appserver.py --port=8081 ubicacionapp/

Con el número que desees.

En Windows y Mac OS X, abre el Launcher, selecciona la aplicación en la lista de aplicaciones y presiona el botón Run. Una vez ejecutado ingresa http://localhost:8080/ en la barra de direcciones de tu navegador y verás el mensaje de bienvenida. Para acceder al panel de control de la aplicación ingresa a http://localhost:8080/_ah/admin.

Para finalizar el servidor de desarrollo presiona Ctrl + C en Linux o el botón Stop en el Launcher.

Despliegue

Ya hemos desarrollado una aplicación y testeado su funcionalidad con el servidor de desarrollo local. Es momento de desplegarla a la infraestructura de Google. Para usuarios de Linux el procedimiento será vía consola. Para Windows y Mac OS X utilizaremos el Launcher.

Linux

Abre la terminal y ejecuta:

appcfg.py --email=yo@gmail.com update ubicacionapp/

La opción --email indica la cuenta de Google en la cual se encuentra registrada la aplicación. Si tu dirección es @gmail.com, éste puede omitirse (--email=yo). Luego, será solicitada la contraseña. ubicacionapp/ indica la carpeta o ruta en donde se encuentra el archivo app.yaml junto con los demás, pero este primero determinará la existencia de una aplicación en dicha ruta para appcfg.py. En caso de haber especificado erróneamente la ubicación, se quejará diciendo:

appcfg.py: error: Directory does not contain an Documents.yaml configuration file.

Si todo va bien, el output se verá más o menos así:

11:52 AM Host: appengine.google.com
11:52 AM Application: myapp; version: 1
11:52 AM
Starting update of app: myapp, version: 1
11:52 AM Getting current resource limits.
Password for yo: ********
11:52 AM Scanning files on local disk.
11:52 AM Cloning 25 application files.
11:52 AM Uploading 1 files and blobs.
11:52 AM Uploaded 1 files and blobs
11:52 AM Compilation starting.
11:52 AM Compilation completed.
11:52 AM Starting deployment.
11:52 AM Checking if deployment succeeded.
11:52 AM Will check again in 1 seconds.
11:52 AM Checking if deployment succeeded.
11:52 AM Deployment successful.
11:52 AM Checking if updated app version is serving.
11:53 AM Completed update of app: myapp, version: 1

En ciertas ocasiones appcfg.py se puede tomar varios segundos, incluso minutos, chequeando si el despliegue tuvo éxito.

Windows y Mac OS X

Ejecuta Google App Engine Launcher y selecciona tu aplicación en la lista de aplicaciones. Luego, presiona el botón «Deploy» en la barra superior. La dirección de correo y contraseña serán solicitados. Al finalizar el proceso, la ventana «Delpoyment to Google» indicará que la misma puede ser cerrada.

Conclusión

La aplicación ya se encuentra en funcionamiento. Vista tuapp.appspot.com (siendo tuapp el identificador de tu aplicación) y verás en pantalla el mensaje de bienvenida.

Véase más sobre la subida de una aplicación Python en este enlace.

Templates

web.py compila los templates a código fuente de Python haciendo uso del módulo estándar parser. Lamentablemente este último no está disponible en el entorno de GAE, por lo que para poder utilizar templates en nuestra aplicación debemos compilarlos previamente. Por lo tanto vamos a modificar la estructura de nuestra aplicación, de la siguiente manera:

- nombreapp/
     |
     | - app.yaml
     | - main.py
     | - main.app
     | - templates/
     |     |
     |     | - index.html
     | - web/
          |
          | - application.py
          | - db.py
          | - etc.

index.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
	<meta http-equiv="content-type" content="text/html;charset=utf-8" />
</head>
<body>
    Bienvenido, <strong>$name</strong>!
</body>
</html>

main.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import web

urls = (
    "/(.*)", "Index"
)

app = web.application(urls, globals()).wsgifunc()
render = web.template.render("templates")


class Index:
    
    def GET(self, name):
        return render.index(name)


if __name__ == "__main__":
    app = app.gaerun()

app.yaml: cambiar el valor de url.

handlers:
- url: /(.*)
  script: main.app

Nótese en el último código que al cambiar la estructura de los URLs en main.py, también deben modifcarse en app.yaml.

Si en este momento desplegamos nuestra aplicación e ingresamos, obtendríamos un error como el siguiente:

Error: Server Error
The server encountered an error and could not complete your request. If the problem persists, please report your problem and mention this error message and the query that caused it.

Luego, al dirigirte a los Logs desde el panel de administración de la aplicación verás:

ImportError: No module named templates

Por lo tanto, antes del despliegue deberás compilar los templates abriendo la terminal e insertando:

python web/template.py --compile templates

En Windows puedes crear un archivo compile_templates.bat y posicionarlo en la carpeta de tu aplicación con el contenido:

C:/python27/python.exe web/template.py --compile templates

Ambos métodos deben ser ejecutados siempre que se realicen modificaciones a los templates, antes de desplegar la aplicación al servidor. Una vez hecho esto, visita tuapp.appspot.com/RecursosPython y verás: «Bienvenido, RecursosPython!».

Almacenamiento de datos

Si bien es posible utilizar MySQL en el entorno de App Engine, en este apartado explicaré cómo usar la base de datos nativa de Google a través del API. Si bien se pueden realizar consultas SQL, se trata de un subconjunto del lenguaje denominado GQL (Google Query Language). Además, el almacenamiento de datos se realiza a través de los propios objetos. Todas las operaciones se llevan a cabo con el módulo google.appengine.ext.db.

Teniendo en cuenta el código anterior, nuestra aplicación le da la bienvenida a cada usuario que se presenta luego de la barra «/». Ahora haremos que almacene el nombre de cada uno de ellos, y luego los muestre al acceder a «/stats». Antes de comenzar con la base de datos, accede a main.py y reorganicemos la estructura de los URLs:

urls = (
    "/stats", "Stats",
    "/(.*)", "Index"
)

Tambíen en app.yaml:

handlers:
- url: /stats
script: main.app
- url: /(.*)
script: main.app

Nótese que primero se indica «/stats», para que al ser accedido la petición se enviada a la clase Stats y no a Index (prioridad).

A continuación, comencemos con el código de la base de datos importando el módulo:

from google.appengine.ext import db

Anteriormente dije que nuestra aplicación va a registrar a cada visitante, por lo que necesitaremos crear una clase Visitor con un atributo name, que equivaldría a una tabla y una columna, respectivamente, en las bases de datos relacionales.

class Visitor(db.Model):
    name = db.StringProperty()

Obsérvese que toda clase que represente el almacenamiento de datos debe heredar de db.Model. A continuación, se especifican sus atributos. En este caso name es una cadena, pero podríamos haber especificado otros más como IntegerPropery, BooleanProperty, o cualquiera de las brindadas en esta lista. Por ejemplo:

class User(db.Model):
    name = db.StringProperty()
    alias = db.StringProperty()
    summary = db.TextProperty()
    register_date = db.DateTimeProperty()
    age = IntegerProperty()
    score = FloatProperty()

Continuando con la aplicación, para añadir un campo a la base de datos como un visitante, debemos crear una instancia de la clase Visitor y llamar al método put().

visitor = Visitor()
visitor.name = "Nombre"
visitor.put()

De esta manera ya habría un campo en nuestra base de datos del tipo Visitor denominado «Nombre». Añade el siguiente código a la clase Index:

class Index:
    
    def GET(self, name):
        if not visitor_exists(name):
            visitor = Visitor()
            visitor.name = name
            visitor.put()
        
        return render.index(name)

Así, cada usuario que ingrese a nuestro sitio será almacenado como un visitante. La función visitor_exists() verifica que el usuario no se encuentre en la base de datos:

def visitor_exists(name):
    return Visitor().gql("WHERE name = :1", name).get() is not None

Al ser una sola línea se ve algo confuso. Primero, crea una instancia de la clase Visitor, y a partir de ésta realiza una consulta GQL (recuerda, el subconjunto de SQL de Google). Como podrás ver es similar al lenguaje convencional, una sentencia FROM table SELECT column WHERE field = value. La diferencia es que como la consulta es llamada desde una clase, no es necesario especificar la tabla ni las columnas, ya que retornará un nuevo objeto del tipo Visitor con todas sus «columnas» (en este caso solo una, name). El método get() de la clase GqlQuery retorna el primer objeto en la tabla, o None si la misma está vacía.

Por último, la clase Stats deberá retornar la lista de los visitantes:

class Stats:
    
    def GET(self):
        visitors = db.GqlQuery("SELECT * FROM Visitor").fetch(None)
        if visitors:
            return ("Lista de visitantes: <br />" +
                    "<br />".join([v.name for v in visitors]))
        else:
            return "No hay visitantes."

También se puede realizar una consulta instanciando directamente desde GqlQuery, pasando como argumento una sentencia SQL convencional SELECT columnas FROM tabla. El método fetch retorna un iterable con todos los objetos de la tabla. El parámetro None indica que no hay un límite.

Finalmente despliega la aplicación y visita tuapp.appspot.com/nombre reiteradas veces. Luego ingresa a tuapp.appspot.com/stats y verás los resultados. También puedes dirigirte al Datastore Viewer del panel de control de tu aplicación.

Código completo:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import web

from google.appengine.ext import db


urls = (
    "/stats", "Stats",
    "/(.*)", "Index"
)

app = web.application(urls, globals()).wsgifunc()
render = web.template.render("templates")


def visitor_exists(name):
    return Visitor().gql("WHERE name = :1", name).get() is not None


class Visitor(db.Model):
    name = db.StringProperty()


class Index:
    
    def GET(self, name):
        if not visitor_exists(name):
            visitor = Visitor()
            visitor.name = name
            visitor.put()
        
        return render.index(name)


class Stats:
    
    def GET(self):
        visitors = db.GqlQuery("SELECT * FROM Visitor").fetch(None)
        if visitors:
            return ("Lista de visitantes: <br />" +
                    "<br />".join([v.name for v in visitors]))
        else:
            return "No hay visitantes."


if __name__ == "__main__":
    app = app.gaerun()

Versión

Python 2.7



Deja un comentario