sql >> Base de Datos >  >> RDS >> Database

Creación de una aplicación web sencilla con Bottle, SQLAlchemy y la API de Twitter

En octubre pasado, desafiamos a la audiencia de PyBites a crear una aplicación web para navegar mejor por el feed de consejos diarios de Python. En este artículo, compartiré lo que construí y aprendí en el camino.

En este artículo aprenderás:

  1. Cómo clonar el repositorio del proyecto y configurar la aplicación.
  2. Cómo usar la API de Twitter a través del módulo Tweepy para cargar los tweets.
  3. Cómo usar SQLAlchemy para almacenar y administrar los datos (consejos y hashtags).
  4. Cómo construir una aplicación web simple con Bottle, un micro-framework web similar a Flask.
  5. Cómo usar el marco pytest para agregar pruebas.
  6. Cómo la orientación de Better Code Hub condujo a un código más fácil de mantener.

Si desea seguir leyendo el código en detalle (y posiblemente contribuir), le sugiero que bifurque el repositorio. Comencemos.


Configuración del proyecto

En primer lugar, los espacios de nombres son una excelente idea así que hagamos nuestro trabajo en un entorno virtual. Usando Anaconda lo creo así:

$ virtualenv -p <path-to-python-to-use> ~/virtualenvs/pytip

Cree una base de datos de producción y de prueba en Postgres:

$ psql
psql (9.6.5, server 9.6.2)
Type "help" for help.

# create database pytip;
CREATE DATABASE
# create database pytip_test;
CREATE DATABASE

Necesitaremos credenciales para conectarnos a la base de datos y la API de Twitter (primero cree una nueva aplicación). Según las mejores prácticas, la configuración debe almacenarse en el entorno, no en el código. Coloque las siguientes variables env al final de ~/virtualenvs/pytip/bin/activate , el script que maneja la activación/desactivación de su entorno virtual, asegurándose de actualizar las variables de su entorno:

export DATABASE_URL='postgres://postgres:password@localhost:5432/pytip'
# twitter
export CONSUMER_KEY='xyz'
export CONSUMER_SECRET='xyz'
export ACCESS_TOKEN='xyz'
export ACCESS_SECRET='xyz'
# if deploying it set this to 'heroku'
export APP_LOCATION=local

En la función de desactivación de la misma secuencia de comandos, los desconfiguré para mantener las cosas fuera del alcance del shell al desactivar (abandonar) el entorno virtual:

unset DATABASE_URL
unset CONSUMER_KEY
unset CONSUMER_SECRET
unset ACCESS_TOKEN
unset ACCESS_SECRET
unset APP_LOCATION

Ahora es un buen momento para activar el entorno virtual:

$ source ~/virtualenvs/pytip/bin/activate

Clona el repositorio y, con el entorno virtual habilitado, instala los requisitos:

$ git clone https://github.com/pybites/pytip && cd pytip
$ pip install -r requirements.txt

A continuación, importamos la colección de tweets con:

$ python tasks/import_tweets.py

Luego, verifique que se crearon las tablas y se agregaron los tweets:

$ psql

\c pytip

pytip=# \dt
          List of relations
 Schema |   Name   | Type  |  Owner
--------+----------+-------+----------
 public | hashtags | table | postgres
 public | tips     | table | postgres
(2 rows)

pytip=# select count(*) from tips;
 count
-------
   222
(1 row)

pytip=# select count(*) from hashtags;
 count
-------
    27
(1 row)

pytip=# \q

Ahora hagamos las pruebas:

$ pytest
========================== test session starts ==========================
platform darwin -- Python 3.6.2, pytest-3.2.3, py-1.4.34, pluggy-0.4.0
rootdir: realpython/pytip, inifile:
collected 5 items

tests/test_tasks.py .
tests/test_tips.py ....

========================== 5 passed in 0.61 seconds ==========================

Y, por último, ejecuta la aplicación Bottle con:

$ python app.py

Vaya a http://localhost:8080 y voilà:debería ver los consejos ordenados de forma descendente según la popularidad. Al hacer clic en un enlace de hashtag a la izquierda, o usando el cuadro de búsqueda, puede filtrarlos fácilmente. Aquí vemos a los pandas consejos, por ejemplo:

El diseño lo hice con MUI, un marco CSS liviano que sigue las pautas de diseño de materiales de Google.



Detalles de implementación


La base de datos y SQLAlchemy

Usé SQLAlchemy para interactuar con la base de datos para evitar tener que escribir una gran cantidad de SQL (redundante).

En consejos/modelos.py , definimos nuestros modelos - Hashtag y Tip - que SQLAlchemy se asignará a tablas DB:

from sqlalchemy import Column, Sequence, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class Hashtag(Base):
    __tablename__ = 'hashtags'
    id = Column(Integer, Sequence('id_seq'), primary_key=True)
    name = Column(String(20))
    count = Column(Integer)

    def __repr__(self):
        return "<Hashtag('%s', '%d')>" % (self.name, self.count)


class Tip(Base):
    __tablename__ = 'tips'
    id = Column(Integer, Sequence('id_seq'), primary_key=True)
    tweetid = Column(String(22))
    text = Column(String(300))
    created = Column(DateTime)
    likes = Column(Integer)
    retweets = Column(Integer)

    def __repr__(self):
        return "<Tip('%d', '%s')>" % (self.id, self.text)

En consejos/db.py , importamos estos modelos y ahora es fácil trabajar con la base de datos, por ejemplo, para interactuar con el Hashtag modelo:

def get_hashtags():
    return session.query(Hashtag).order_by(Hashtag.name.asc()).all()

Y:

def add_hashtags(hashtags_cnt):
    for tag, count in hashtags_cnt.items():
        session.add(Hashtag(name=tag, count=count))
    session.commit()


Consulta la API de Twitter

Necesitamos recuperar los datos de Twitter. Para eso, creé tasks/import_tweets.py . Empaqué esto en tareas porque debe ejecutarse en un cronjob diario para buscar nuevos consejos y actualizar las estadísticas (número de me gusta y retweets) en los tweets existentes. En aras de la simplicidad, tengo las tablas recreadas diariamente. Si comenzamos a confiar en las relaciones FK con otras tablas, definitivamente deberíamos elegir declaraciones de actualización en lugar de eliminar + agregar.

Usamos este script en la configuración del proyecto. Veamos qué hace con más detalle.

Primero, creamos un objeto de sesión API que pasamos a tweepy.Cursor. Esta característica de la API es realmente agradable:se ocupa de la paginación, iterando a través de la línea de tiempo. Por la cantidad de propinas, 222 en el momento en que escribo esto, es realmente rápido. El exclude_replies=True y include_rts=False Los argumentos son convenientes porque solo queremos los propios tweets de Daily Python Tip (no re-tweets).

Extraer hashtags de las sugerencias requiere muy poco código.

Primero, definí una expresión regular para una etiqueta:

TAG = re.compile(r'#([a-z0-9]{3,})')

Luego, usé findall para obtener todas las etiquetas.

Los pasé a colecciones. Contador, que devuelve un objeto similar a un dictado con las etiquetas como claves, y cuenta como valores, ordenados en orden descendente por valores (los más comunes). Excluí la etiqueta de python demasiado común que sesgaría los resultados.

def get_hashtag_counter(tips):
    blob = ' '.join(t.text.lower() for t in tips)
    cnt = Counter(TAG.findall(blob))

    if EXCLUDE_PYTHON_HASHTAG:
        cnt.pop('python', None)

    return cnt

Finalmente, el import_* funciones en tasks/import_tweets.py haga la importación real de los tweets y hashtags, llamando a add_* Métodos DB de los consejos directorio/paquete.



Haga una aplicación web simple con Bottle

Con este trabajo previo hecho, crear una aplicación web es sorprendentemente fácil (o no tan sorprendente si usó Flask antes).

En primer lugar conoce a Bottle:

Bottle es un marco web micro WSGI rápido, simple y liviano para Python. Se distribuye como un módulo de archivo único y no tiene otras dependencias que la biblioteca estándar de Python.

Lindo. La aplicación web resultante se compone de <30 LOC y se puede encontrar en app.py.

Para esta sencilla aplicación, todo lo que se necesita es un único método con un argumento de etiqueta opcional. Similar a Flask, el enrutamiento se maneja con decoradores. Si se llama con una etiqueta, filtra los consejos sobre la etiqueta; de lo contrario, los muestra todos. El decorador de vistas define la plantilla a usar. Al igual que Flask (y Django), devolvemos un dict para usar en la plantilla.

@route('/')
@route('/<tag>')
@view('index')
def index(tag=None):
    tag = tag or request.query.get('tag') or None
    tags = get_hashtags()
    tips = get_tips(tag)

    return {'search_tag': tag or '',
            'tags': tags,
            'tips': tips}

Según la documentación, para trabajar con archivos estáticos, agrega este fragmento en la parte superior, después de las importaciones:

@route('/static/<filename:path>')
def send_static(filename):
    return static_file(filename, root='static')

Finalmente, queremos asegurarnos de que solo ejecutamos en modo de depuración en localhost, de ahí el APP_LOCATION variable env que definimos en la configuración del proyecto:

if os.environ.get('APP_LOCATION') == 'heroku':
    run(host="0.0.0.0", port=int(os.environ.get("PORT", 5000)))
else:
    run(host='localhost', port=8080, debug=True, reloader=True)


Plantillas de botellas

Bottle viene con un motor de plantillas integrado rápido, potente y fácil de aprender llamado SimpleTemplate.

En el subdirectorio de vistas definí un header.tpl , índice.tpl y footer.tpl . Para la nube de etiquetas, utilicé un CSS simple en línea que aumentaba el tamaño de la etiqueta por conteo, consulte header.tpl :

% for tag in tags:
  <a style="font-size: {{ tag.count/10 + 1 }}em;" href="/{{ tag.name }}">#{{ tag.name }}</a>&nbsp;&nbsp;
% end

En index.tpl repasamos los consejos:

% for tip in tips:
  <div class='tip'>
    <pre>{{ !tip.text }}</pre>
    <div class="mui--text-dark-secondary"><strong>{{ tip.likes }}</strong> Likes / <strong>{{ tip.retweets }}</strong> RTs / {{ tip.created }} / <a href="https://twitter.com/python_tip/status/{{ tip.tweetid }}" target="_blank">Share</a></div>
  </div>
% end
{{ tip.me gusta }} Me gusta / {{ tip.retweets }} RT / {{ tip.created }} / Compartir
% end

Si está familiarizado con Flask y Jinja2, esto debería parecerle muy familiar. Incrustar Python es aún más fácil, con menos tipeo:(% ... vs {% ... %} ).

Todos los css, imágenes (y JS si lo usaríamos) van a la subcarpeta estática.

Y eso es todo lo que hay que hacer para crear una aplicación web básica con Bottle. Una vez que haya definido correctamente la capa de datos, es bastante sencillo.



Añadir pruebas con pytest

Ahora hagamos este proyecto un poco más sólido agregando algunas pruebas. Probar la base de datos requirió un poco más de investigación en el marco de pytest, pero terminé usando el decorador pytest.fixture para configurar y derribar una base de datos con algunos tweets de prueba.

En lugar de llamar a la API de Twitter, utilicé algunos datos estáticos proporcionados en tweets.json .Y, en lugar de usar la base de datos en vivo, en tips/db.py , verifico si pytest es la persona que llama (sys.argv[0] ). Si es así, uso la base de datos de prueba. Probablemente refactorizaré esto, porque Bottle admite trabajar con archivos de configuración.

La parte del hashtag fue más fácil de probar (test_get_hashtag_counter ) porque solo podría agregar algunos hashtags a una cadena de varias líneas. No se necesitan accesorios.



La calidad del código importa - Better Code Hub

Better Code Hub lo guía en la escritura, bueno, mejor código. Antes de escribir las pruebas, el proyecto obtuvo un 7:

No está mal, pero podemos hacerlo mejor:

  1. Lo subí a 9 haciendo el código más modular, sacando la lógica de la base de datos de app.py (aplicación web), colocándolo en la carpeta/paquete de consejos (refactorizaciones 1 y 2)

  2. Luego, con las pruebas realizadas, el proyecto obtuvo un puntaje de 10:




Conclusión y aprendizaje

Nuestro Code Challenge #40 ofreció algunas buenas prácticas:

  1. Desarrollé una aplicación útil que se puede expandir (quiero agregar una API).
  2. Usé algunos módulos geniales que vale la pena explorar:Tweepy, SQLAlchemy y Bottle.
  3. Aprendí un poco más de pytest porque necesitaba accesorios para probar la interacción con la base de datos.
  4. Sobre todo, al tener que hacer que el código fuera comprobable, la aplicación se volvió más modular, lo que facilitó su mantenimiento. Better Code Hub fue de gran ayuda en este proceso.
  5. Implementé la aplicación en Heroku usando nuestra guía paso a paso.

Te desafiamos

La mejor manera de aprender y mejorar tus habilidades de codificación es practicar. En PyBites solidificamos este concepto organizando desafíos de código Python. ¡Vea nuestra creciente colección, bifurque el repositorio y comience a programar!

Háganos saber si construye algo genial haciendo una solicitud de extracción de su trabajo. Hemos visto personas que realmente se esfuerzan por superar estos desafíos, y nosotros también.

¡Feliz codificación!