Aprende Python en 5 días (V)

Ricardo Vega / 21 enero 2016
⏰ 9 minutos
Como ya te adelanté en la anterior entrega, hoy, en el último capítulo de 'Aprende Python en 5 días', vamos a ver una aplicación completa que va a integrar todos los conocimientos con los que hemos trabajado hasta ahora y va a añadir algunas nuevas cuestiones para que puedas seguir trabajando por tu cuenta.
Descarga e Instalación
La aplicación completa la tienes disponible en GitHub donde continuaré con su desarrollo con el objetivo de mejorar su funcionamiento y añadir más posibilidades.
Para instalarlo en tu equipo tan sólo tienes que:
git clone https://github.com/ricveal/temperatureApp.git
virtualenv temperatureApp
cd temperatureApp
source bin/activate
pip install -r requirements.txt
Con estos pasos, tendrás la última versión del código y montarás un entorno virtual donde tener todas las dependencias controladas. Quiero que te des cuenta como el proyecto tiene varias cosas con las que nunca hemos trabajado y que no son objeto de esta entrada como todo lo relacionado con las carpetas front, assets, fonts, maps, scripts o styles. Estas carpetas contienen una mini-versión muy muy básica con la que mostrar los datos que vamos a crear.
Objetivos: Funcionalidad
Nuestra aplicación está relacionada con el Internet de las Cosas y la domótica tal y como te prometí. Concretamente va a hacer varias tareas:
- Recoger información de un sensor conectado a un Arduino + Ethernet Shield que, a su vez, está conectado al router y por tanto es accesible desde cualquier equipo de nuestra red local.
- Recoger datos de la API de Yahoo Weather.
- Almacenar esos datos en una Base de Datos Sqlite.
- Ejecutar estas tareas de forma automática cada 'x' tiempo.
- Levantar un servicio web con una API que accederá y devolverá los 100 últimos valores almacenados en nuestra base de datos.
El programa está fraccionado en varios ficheros y clases para una comprensión más sencilla y así poder también aumentar su modularidad.
Lo que ya sabemos.
De esta forma, puedes ver que arduinoData, infoTiempo y tiempo son simples clases que almacenaran la información que vamos a recoger de Internet y de nuestro Arduino mientras que myData hará lo correspondiente esta vez con los datos extraídos de nuestra BBDD justo antes de ser enviados al cliente que se conecte a la API.
Nota: verás que en arduinoData, estamos recogiendo también la humedad aunque no estamos haciendo nada con ella. Para futuras revisiones^^.
Conditions es una clase utilidad que se encarga de toda la lógica de adquisición de datos tanto de Arduino como de Yahoo. Puedes ver como tenemos unos atributos y unos métodos que principalmente sirven para "rellenar" estos atributos.
La lógica, con lo que hemos visto, creo que la puedes seguir sin demasiados problemas pero me gustaría hacer una serie de observaciones. En primer lugar, estamos usando 3 librerías:
-
BeautifulSoup. Con ella, parseamos los datos que recibimos de una web con la que obtenemos el código necesario para llamar a la API de Yahoo. En general es muy práctica para obtener datos de aquellas webs que no disponen de APIs a las que podamos acceder para consumir sus datos.
-
Requests. La introdujimos el anterior episodio. Se encarga de hacer las peticiones a URLs, dándonos herramientas con las que tratar las respuestas.
-
json. Gracias a esta librería, podemos manejar y hacer conversiones de archivos json.
Por otra parte, hay varios datos de configuración que estamos obteniendo accediendo al fichero directamente desde nuestro programa y usando su contenido. Concretamente, está implementado el acceso al archivo y la escritura en él de los datos que faltan para evitar la búsqueda del código cada vez que ejecutemos el programa.
En dbConnector tenemos la lógica de acceso a la base de datos encapsulada en diferentes métodos para lo que hacemos uso del módulo sqlite3.
También tenemos un módulo con métodos utilidades (utilities.py). Concretamente, podemos convertir de ºF a ºC y obtener la fecha actual en el formato HH:MM (horas:minutos). Para ésta última funcionalidad, hacemos uso de una librería más (datetime) que también es muy útil y muy empleada.
A partir de este punto, seguramente venga lo más novedoso:
Las novedades de nuestra aplicación
Para empezar, debes saber que algunas de estas librerías son de terceros y requieren su instalación a través de pip (u otros métodos), aunque al ejecutar "pip install -r requirements.txt" al instalar la aplicación, no nos hemos tenido que preocupar por ello. Sin embargo, otras de las librerías de las que hemos hablado como datetime o json pertenecen al propio sistema Python y tan sólo deben ser cargadas.
Ese es el caso también de logging, el módulo que Python nos da para estas tareas. En el archivo logger.py puedes ver como se configura.
import logging
def setupLogger(level, name):
log = logging.Logger(name)
log.setLevel(level)
# fileHandler
fh = logging.FileHandler('debug.log')
fh.setLevel(logging.DEBUG)
# consoleHandler
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
log.addHandler(fh)
log.addHandler(ch)
return log
Como ves, estamos configurando un único logger pero que maneja eventos de dos formas diferentes: guardándolo en el archivo debug.log si la traza es de nivel DEBUG o superior y sacándolo por pantalla en el caso de que sea ERROR o superior. También estamos fijando el formato de la traza para guardar más información que un simple mensaje. Puedes leer más de los logger de Python en la documentación oficial.
Otro tema interesante es cómo se importan los módulos. Podrás ver por todos los archivos import a, from a import b, import a as c ... Su significado es sencillo y autodescriptivo. En el primer caso estamos importando todo un módulo, en el segundo podemos especificar que métodos/funciones queremos y en el tercero estamos estableciendo el nombre con el que trabajaremos.
Si pensamos en el módulo logger:
- import logger. Importo todo y debo usarlo así en mi código: logger.setupLogger(level, name)
- from logger import setupLogger. Importo sólo esa función y puedo emplearla con tan sólo setupLogger(level, name).
- import logger as lg. Aunque importo todo, le cambio el nombre para usarlo así: lg.setupLogger(level, name)
Vayamos ahora al archivo que nos falta y con el cual arrancamos el programa:
python server.py
Usamos gran cantidad de módulos y librerías entre los que se encuentran muchos de nuestros archivos ya que será aquí donde los empleemos. Una vez vistos los import, bajemos abajo del todo:
def main():
check_db()
w = threading.Thread(target=saveData, name='SaveData Daemon')
w.setDaemon(True)
w.start()
app.run()
if __name__ == '__main__':
main()
La forma clásica de iniciar un programa/aplicación en Python, llamando a una función main() que ejecuta el 'core' de nuestro programa. Lo primero que hacemos es llamar a check_db que nos comprueba si existe la DB y en caso contrario lo hace para evitar errores. Las 3 siguientes líneas configuran un hilo que se ejecutará como demonio en nuestro sistema.
Como este concepto se escapa un poco al objetivo de este curso, dejémoslo en que hace que una porción de nuestro código se esté ejecutando siempre por debajo hasta que nosotros le digamos que pare. Si te fijas, la función saveData a la que llama (que se ejecuta siempre [while = true] cada 2 minutos [sleep(2*60seg)]) es la que realmente lleva el flujo del trabajo llamando al resto de archivos que hemos visto hasta ahora.
Entonces, ¿qué hace el resto del archivo? Por no complicarlo demasiado centrémonos en lo siguiente [todo el resto es para mostrar gráficamente los resultado y de eso ya hablaremos en otro momento]:
app = Flask(__name__)
LOG_LEVEL = "DEBUG"
log = setupLogger(LOG_LEVEL, APP_NAME)
@app.route('/api')
def show_data():
data = db.get_last_values()
d = []
for i in data:
d.append(
{'fecha': i.fecha, 'temps': {'int': i.temp_int, 'ext': i.temp_ext}})
info = json.dumps(dict(data=d))
resp = Response(info, status=200, mimetype='application/json')
resp.headers['Link'] = 'http://ricveal.com'
return resp
@app.errorhandler(500)
def internal_error(error):
log.error(error)
return "Internal Error"
@app.errorhandler(404)
def pageNotFound(error):
return "Page Not Found"
Flask es un microframework con el que podemos crear potentes webs basadas en Python. Su potencia y agilidad es increíble, permitiéndonos construir en relativamente poco tiempo proyectos que de otra forma sería imposible.
Todo el código que he pegado, exceptuando la parte que configura el logger que
ya hemos visto, corresponde a su configuración. Nos introduce un nuevo concepto
muy avanzado como son los decoradores o decoratos (las funciones con el @)
pero es otro de los puntos que no vamos a tocar aquí y te dejo como deberes para
seguir progresando. Simplemente comentaré que @app.route('/api') hace que
show_data se ejecute cuando entremos en http://localhost:5000/api
(por defecto
funciona en el puerto 5000) y las otras dos funciones responden a los errores
500 y 404 respectivamente.
La lógica de show_data es bastante sencilla y se encarga de "fabricar" la
respuesta que devolveremos vía web al entrar en http://localhost:5000/api
,
concretamente en nuestro caso, estamos construyendo un json de respuesta.
Conclusiones y despedida
Y esto es todo. Cualquier duda que te surja, no dudes en consultarme. Si encuentras algún error (que los habrá) coméntalo y lo corregiré. Como te he dicho, es un proyecto que seguiré evolucionando ;)
A lo largo de 5 episodios, hemos ido explicando los fundamentos de uno de los lenguajes de programación más empleados del mundo y que es capaz de aportarnos una gran flexibilidad a la hora de prototipar aplicaciones o hacer desarrollos mucho más serios.
Con esta aplicación hemos cubierto prácticamente todos los conceptos explicados y te he intentado poner en dirección a conceptos más avanzados como hilos o decoradores para que puedas seguir avanzando por tu cuenta.
Es hora de que te sumerjas en la multitud de librerías disponibles y veas con tus propios ojos que, con Python, prácticamente todo es posible.
Muchas gracias por participar de este primer curso que organizo en el blog. Si te ha gustado, recomiéndalo a tus amigos en tus redes sociales y déjame tus comentarios aquí abajo.
¡Nos vemos!
Subscríbete a mi newsletter
Actualmente estoy llevando a cabo algunos cambios en mi Newsletter y no es posible apuntarse 😞