Quart

Quart és un micro-framework per crear aplicacions web.

Començarem per crear un entorn virtual, un dels passos més importants a l'hora de crear una nova aplicació.

$ python3 -m venv .venv
$ source venv/bin/activate

Un cop creat i activat l'entorn virtual, podem instal·lar Quart mitjançant pip:

$ pip install quart 

Crea la teva aplicació

Ha arribat el moment d'escriure la nostra primera aplicació web!

Primer, hem d'importar la classe principal del nostre framework

Ara creem una variable que inicialitzarà l'aplicació. El primer paràmetre (i l'únic necessari) s'utilitza per resoldre el directori on està instal·lada l'aplicació Quart (perquè Quart sàpiga on buscar recursos, plantilles, fitxers estàtics, etc.). Normalment s’utilitza la variable `name`` (que és bàsicament igual al nom del mòdul que s'executa el programa).

from quart import Quart

app = Quart(__name__)

@app.route("/")
async def hello():
    return {"message": "hello"}

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

Finalment, obrim un terminal arrenquem el servidor en mode --reload perquè cada cop que canviem algun fitxer el servidor torni a carregar l’aplicació amb els nous canvis.

$ quart run --reload -h 0.0.0.0
 * Serving Quart app 'app'
 * Debug mode: False
 * Please use an ASGI server (e.g. Hypercorn) directly in production
 * Running on http://0.0.0.0:5000 (CTRL + C to quit)
[2024-04-26 15:41:20 +0000] [3241] [INFO] Running on http://0.0.0.0:5000 (CTRL + C to quit)

Si tot va bé la consola mostrarà diversos missatges, l'últim dels quals contindrà l'adreça on l'aplicació és accessible (el valor predeterminat és 127.0.0.1:5000).

Abre otro terminal, e instala httpie para interactuar con el servidor web mediante la linea de comandos:

$ sudo apt install -y httpie

Ja podem interactuar amb el servidor amb l’eina http:

$ http localhost:5000
HTTP/1.1 200 
content-length: 20
content-type: application/json
date: Fri, 26 Apr 2024 15:50:09 GMT
server: hypercorn-h11

{
    "message": "hello"
}

Podem veure com el servidor respon a la sol.licitut GET / amb un string JSON {"message": "hello"}, World! i un codi 200.

Quart convierte de manera automática un diccionario Python en un string JSON.

Route

Funció “externa”

Un servidor web es composa de diferents “paths” (camis) que criden diferents funcions d’entrada.

D’aquesta manera un path és un “nom” extern de la nostre funció.

Fem servir el nom de path (camí) perquè fa molts anys els servidors web només implementaven una sola funció que mapejava el path a una part del sistema de fitxers d’un servidor.

Per exemple, si el servidor web està configurat per servir el directori /home/david/web/, llavors una petició amb el path /docs/python.html retorna el contingut del fitxer /home/david/web/docs/python.html.

Però els servidors web han evolucionat i encara que el path es fa servir a vegades com hem explicat, la majoria de les vegades un servidor web està implementat per moltes funciones que responen a un path determinat.

Si tenim una funció hello i volem que aquesta sigui invocada amb el path /, llavors hem de marcar aquesta funció amb el decorator @app.route('/'):

@app.route("/bye")
async def bye():
    return {"message": "bye"}

Amb el decorador @app.route, estem dient al programa que la següent funció estarà lligada a un path específic entre parèntesis ( '/bye' en el nostre cas).

Quan aquesta funció sigui invocada retornarà el JSON {"message": "bye"} que serà enviat al client que ha realitzat la petició al path /bye.

Tingues en compte que la funció pot tenir qualsevol nom: el que és fa servir per decidir quina funció ha de processar la sol.licituts d’entrada (request) és el que diu el decorator @app.route.

Si enviem una sol.licitut http al servidor amb el path /bye, la resposta serà aquesta:

$ http -b localhost:5000/bye
{
    "message": "bye"
}

Si enviem una sol.licitut http al servidor amb un path que no està vinculat a cap funció, el servidor respondrà amb un error per a l’usuari humà (una pàgina HTML):

Si mires les capçaleres HTTP pots veure el codi d’error:

Gestió de paths En el cas més senzill, un path correspon a una funció. Creem dues paths per a la nostra aplicació: una per a la pàgina principal i una altra per a la pàgina d'informació a /about. Ho podem fer declarant funcions de vista (view functions) igual que amb el Hello world a dalt:

Model-View-Controller (MVC). Un dels patrons de disseny d’interficies és el MVC, i per aquest motiu es parla de view functions. No obstant el termés més acertat seria funcions amb visibilitat pública, que poden ser invocades de manera exerna mitjançant un nom extern. Una de les propietats importants d'un decorador és la capacitat d'utilitzar diversos decoradors per a una única funció. Suposem que tenim diverses pàgines en desenvolupament. El que volem mostrar és un missatge al respecte. Podem fer el següent: @app.route('/all') @app.route('/about') def construction_site(): return "Construction Site\n"

En aquest cas, tornarem la mateixa resposta (és la mateixa funció “externa”) per a /all/ i /about/.

Paràmetres La majoria de les funcions, per ser útils, han d’estar parametritzades: han de poder rebre uns paràmetres i produïr un resultat diferent en funció d’aquests paràmetres. La primera funció original dels servidors web rebia tot el path com a paràmetre i el feia servir per localitzar el fitxer que havia de generar la reposta. En canvi, si tenim varies funcions, una part del path s’ha d’utilitzar per identificar la funció, i l’altre part del path el pot fer servir la funció com a paràmetres d’entrada. Per exemple, si tenim una funció que mostra el perfil de cada empleat podem fer servir el path/employee/, on /employee és el nom extern de la funció i name és l’argument de la funció. @app.route("/employee//") def show_profile(name): return "Employee Name: " + name + "\n"

Aquesta funció pot ser invocada externament amb els paths /employee/ivan/, employee/anna/, o employee/alex/: on ivan, anna i alex són l’argument name de la funció show_profile:

A l'exemple anterior, el valor dins de claudàtors <> indica una variable que ens permet declarar regles de ruta. En aquest cas, la regla és que la funció show_profile gestionarà totes les sol·licituds que coincideixin amb la plantilla /employee//. Diversos paràmetres Moltes funcions necessiten més d’un paràmetre. Si volem fer servir el path per cridar la funció haurem de fer servir diversos components del path com arguments de la funció. Si tenim una funció que retorna una llista de películes parametritzada per gènere i llenguatge, podem fer servir els dos últims components del path com arguments de la funció:

Conversió de tipus Un path és una string, de manera que les dades obtingudes per defecte es passen a la funció en forma de string. Pots veure-ho per tu mateix fent servir type().

Si el tipus de l’argument de la funció no és un string, pots indicar a Flask el tipus que ha de tenir l’argument passat a la funció, i Flask s’encarregarà de realizar la conversió:

En aquest enllaç trobarás tots els conversos disponibles, i com definir conversors específics: API — Flask Documentation (3.0.x) Exercicis 1.- Escriu un programa amb una funció de visualització que retorni el missatge Information about this page quan accediu a l’adreça /about. @app.route() def render_about(): # your code here

2.- Suposem que creeu un servei de vídeo que inclogui /videos/. En executar la ruta /videos/cats , hauríeu de mostrar el missatge següent Here will be a video with cats. De la mateixa manera, amb qualsevol altra paraula en comptes de cats, canvieu el missatge adequat. Per exemple, per /videos/hedgehogs, el missatge seria Here will be a video with hedgehogs. @app.route() def render_videos(): pass # your code here

3.- Tens dos adreces /ref i /link i una pàgina connect.html. Escriu un programa que surti connect.html quan accedeixes /ref i /link. @app.route() @app.route()

your code

Request El protocol HTTP és un model molt senzill de sol.liciut (request) i resposta (Response) sense estat: cada sol.licitut del client genera una respost del servidor. Això és equiparable a com funciona un funció: cada cop que crido una funció em retorna un resultat. I de la mateixa manera que una funció no té estat, la sortida depén únicament dels paràmetres d’entrada, el protocol HTTP tampoc té estat: la resposta només depen del que consta en la sol.licitut. Objectes Les sol.licitus i respostes HTTP són més complexes que els paràmetres d’entrada i la sortida de els funcions que hem vist fins ara. Molts cops necessites accedir a aquesta informació (Request) o generar aquesta informació (Response), i la manera en que es gestiona la diferent informació és agrupant aquesta en un objecte.

Tota aquesta infromació està disponible a Flask gràcies a una biblioteca anomenada Werkzeug . De moment és suficient dir que Werkzeug proporciona un conjunt d'utilitats per permetre que una aplicació Python es comuniqui amb un servidor web processant peticions i respostes. Paràmetres POST Una funció no només pot rebre paràmetres a través del decorator @app.route, sino que pot accedir a ells a través de l’objecte request. Anem a escriure una funció que gestioni l’autenticació del client: A més d’importa Flask també hem d’importar request : from flask import Flask from flask import request

request és un objecte global que Flask utilitza per posar-hi les dades de la propera sol·licitud que es processarà. L’objecte request és una instància de la subclasse “build-in” Request (observa la majúscula!). Fixa’t en que aquest cop afegim el paràmetre methods per indicar a Flask quin tipus de sol·licituds acceptarà la nostra funció, ja que el valor per defecte de l’argument methods és només ['GET'] (el tipus habitual de la majoria de sol.licituts):

@app.route('/', methods=['POST', 'GET'])

Com ja sabeu, hi ha dos tipus de sol·licituds principals: GET i POST. No oblideu que aquests són només els noms; HTTP no us obliga a utilitzar GET per obtenir informació i POST per reenviar-lo. Tot i que això és el que s'implica més sovint. Com que la funció ha d’acceptar dos tipus de sol.licitus diferents, hem de consultar la propietat method de l’objecte request per executar una lògica diferent en funció del tipus de sol.licitut. Ara podem verificar que la funció respon diferent si el tipus de la sol.licitut és GET o POST:

El servidor sap que una “request” és POST perquè el client envia aquesta informació al servidor:

Mitjançant la propietat form de l’objecte request podem accedir a totes les dades que envia el client en el cos del missatge i no pas en la URL. T’imagines que la teva contrasenya l’haguessis de trasmetre en la URL ?

Pots veure com la funció respon de manera diferent en funció de la dada token :

Pots veure que les dades s’envien com multipart/form-data :

Paràmetres de consulta Una URL ha més de tenir path pot tenir a continuació de manera opcional una query. Els paràmetres de consultat s’afegeixen a l'URL de la sol·licitud principal, separats per un signe d'interrogació ?, i es fan servir per passar paràmetres per nom a la funció corresponent. Els paràmetres de consulta estan fets de parelles key=value separades per un ampersand &. Un exemple d'URL de sol·licitud amb una string de consulta és el següent:

⚠ Atenció: Si passes més d’un paràmetre de consulta a curl has de passar la URL entre “ “. Tampoc oblidis la barra final; per exemple 127.0.0.1:5000/users/ ⚠ TODO: modificar captures A diferència de quan passem paràmetres a la funció mitjançant el path que són posicionals, aquí els paràmetres no són posicionals i l’ordre no importa. La sol.licitut d’abans també la prodriem escriure d’aquesta manera:

curl -v 127.0.0.1:5000/users/?city=Barcelona&age=24

Els paràmetres de consulta s'utilitzen habitualment per filtrar o limitar dades. A l'exemple anterior, diguem que tenim a /users endpoint que retorna tots els usuaris presents en una base de dades. De vegades, no els necessitem tots, i aquí és on és més fàcil especificar quin tipus d'usuaris vol veure exactament el client, fins i tot abans d'enviar una sol·licitud. En aquest cas, el servidor filtrarà només els usuaris de Londres, de 20 anys. La funció “externa” pot accedir a aquests arguments mitjançant la propietat args de l’objecte request:

Com que l’atribut args retorna un diccionari pots utilitzar els mètodes estàndard del diccionari. Aquí, hem recuperat els paràmetres mitjançant el mètode get() per evitar una excepció KeyError. La funció users fa servir els paràmetres de la query per retornar un resultat diferent en funció dels valors dels paràmetres d’entrada:

A més dels atributs method i args, l'objecte de sol·licitud té algunes propietats que també es fan servir de manera habitual. Pots consultar la documentació de la classe flask.Request. Exercicis 1.- Sou programador d'una gran empresa que té el seu propi lloc web. Heu d'escriure una funció que utilitzi el mètodes PUT i DELETE. Si el tipus de sol·licitud és DELETE, torna l’string Deleted. Si el tipus és PUT, torna l’string Updated.

2.- Escriu un programa que només contingui una funció de vista per a la pàgina principal i que pugui gestionar POST, GET, PUT, i CREATE. Aquest últim és el nou tipus de sol·licitud que hem creat per a aquesta tasca. Recordeu que no hi ha límits. Per a cada tipus de sol·licitud, visualitzeu el missatge corresponent: request message POST Successfully authorized! GET Welcome there! PUT Successfully published! CREATE Created a new web page!

3.- Escriu un programa que rebi un petició GET amb paràmetres de consulta i retorna la resposta 200 OK amb el text Received beautiful parameters i els noms i valors dels paràmetres de consulta separats per punt i coma. Per exemple, oer a la sol·licitud amb la cadena de consulta següent: /?name=Bob&age=22&city=Denver La sortida és: Received beautiful parameters! name: Bob, age: 22, city: Denver.

Assegureu-vos que el vostre codi funcioni amb qualsevol string de consulta rebuda; aquí teniu alguns exemples més: /?city=London&weather=rainy /?item_type=food&item_name=bread /?id=4815162342

Response HTTP és un model client-servidor sense estat en que cada Request es contesta amb un Response. Fins ara per retornar un resposta has retornat un String i Flask ja s’ha ocupat del demés. Però a vegades hem de modificar algunes propietats de la resposta- Necessitarem un objecte response, una instància de la classe Response. Ens permet configurar propietats especials com ara afegir capçaleres a una pàgina web normal (o més aviat a la resposta que la retorna), especificar l'idioma que s'utilitza per a la pàgina web, llistar els mètodes permesos, com ara POST, PUT, GET, i molts més. Podem crear un objecte de resposta a Flask amb el mètode make_response :

De moment no hi ha cap diferència entre retornar un String i un objecte Response. Però, per exemple, al crear l’objecte response podem fer-ho amb el codi 400, enlloc del 200 que és el valor per defecte i significa OK:

Encara que el text sigui el mateix que el retornat amb un String, el navegador o el teu programa pot verificar el codi d’estat i veure que la resposta dona error i el codi és el 400. En aquesta enllaç tens tots els codis de resposta: HTTP response status codes La lógica no és diferent a la d’una funció Python: la funció retorna una resposta amb un valor (en el cas de HTTP sempre és un String) i un codi d’status:

Jsonify La resposta pot ser una pàgina HTML que serà renderitzada per un navegador, encara que aquesta no és l’opció més habitual encara que això et pugui sorprende al principi. Les aplicacions client que desenvolupes en DAW-6 - Desenvolupmanet web en entorn client no volen pàgines HTML sino dades en format JSON. De la mateix manera, totes les APIs de serveis web que pots utilitzar tornen dades en format JSON, CSV o XML. Per retorna un conjunt de dades en format JSON fem servir el mètode jsonify , que crea un objecte Response a partir d’un diccionari amb els camps adequats.

En aquest codi estem retornant respostes per poder ser utilitzades per aplicacions client.

Per exemple, podem fer servir l’eina jq per … tal com s’explica en aquest article: Guide to Linux jq Command for JSON Processing sudo apt install -y jq

Podem seleccionar una part de les dades tal com feiem a DAW-4 - Llenguatges de marques

Truc útil. Si us interessa guardar la resposta recordeu que ho podeu fer amb l’operador de redirecció ‘>’:

Exercicis

1.- Echo Page Escriu un programa que prengui un argument via URL en accedir a la pàgina principal. L'argument és una string. El programa l'hauria de retornar. També, l’objecte Response s'ha d'enviar amb el codi 200. Consell: podem obtenir l'argument de dades directament a través d'un URL. Està entre claudàtors triangulars. Indiqueu un nom de la variable que utilitzareu a la funció de vista 2.- Dual View Escriu un programa que encaminarà dues funcions de vista: La URL /data/main_info hauria de retornar un objecte Response amb el missatge

Hello there, it's me -- my own worst enemy!

i el codi de resposta 200. La URL /the_wall hauria de retornar un object Response amb el missatge

Welkommen!

i el codi de resposta 200. 3.- The Clock Escriu un programa que retorni un objecte Response en accedir a la pàgina principal ( /). El missatge hauria de mostrar la data actual en format AAAA.MM.DD i el codi de resposta 200. Consell: podeu obtenir el temps amb el mòdul datetime . 4.- The Code Sender Escriu una funció que rebi un text i un codi de resposta com a arguments, i després crea i retorna un objecte de resposta. @app.route('/') def response_maker(data): text, status_code = data.split(';')

Tingueu en compte que la variable data que proporciona response_maker() que conté el text i el codi de resposta dividit per un punt i coma. Consell: utilitzeu el mètode jsonify per crear una resposta amb les dades. [TODO] Fer captura de l’exemple http://127.0.0.1/hola-amic-meu;202:

Activitat. 1.- Crear un servidor d’un Hospital com el que varem fer a DAWBIO-14-UF1-8 - Pandas :: Projecte (Hospital) que permeti a aplicacions client consumir les dades de l’hospital. Crea almenys un mètode tipus GET: Un que retorni un dataframe de dades públiques d’un hospital en format JSON (no totes les del dataframe) Si et sobra temps, que mostri un gràfic amb informació pública (això ho veurem a la propera sessió) A la pròxima sessió també veuràs com posar més funcionalitats.

Pista 1: Aquí teniu com retornar el dataframe del titanic que ve per defecte amb la llibreria Seaborn.

@app.route('/titanic/') def titanic(): titanic_df = sns.load_dataset("titanic") dades_json = titanic_df.to_dict(orient='records') return jsonify(dades_json)

Pista 2: Per a volcar un gràfic a una pàgina web: DAWBIO-14-UF1-11 - Plottly