23. Esercizio pratico: Versione 6
23.1. Introduzione
Torniamo ora alla nostra applicazione per il calcolo delle imposte. Realizzeremo varie applicazioni web basate su di essa.
Nella versione 5 dell'esercizio sulla nostra applicazione, i dati dell'autorità fiscale erano memorizzati in un database. Questa versione 5 consisteva in due applicazioni separate che condividevano livelli comuni:
- un'applicazione che calcolava le imposte in modalità |batch| per i contribuenti memorizzati in un file di testo;
- un'applicazione che calcolava le imposte in modalità |interattiva| per i contribuenti le cui informazioni venivano inserite tramite la tastiera;
La versione 5 dell'applicazione per il calcolo delle imposte in batch presentava la seguente architettura:

Alla fine, la versione web di questa applicazione avrà la seguente architettura:

- il client web [1] comunica con il server web [2], che a sua volta comunica con il DBMS [3];
- il server web [2] mantiene i livelli [business] [8] e [DAO] [9] dell'applicazione originale;
- L'applicazione originale mantiene il proprio script principale [4] e il proprio livello [business] [15]. I livelli [business] [8] e [15] sono identici;
- la comunicazione client/server richiede due livelli aggiuntivi:
- il livello [web] [7], che implementa l'applicazione web;
- il livello [DAO] [5], che funge da client per l'applicazione web [7];
Nella versione finale, il calcolo delle imposte in batch può essere eseguito in due modi:
- la logica di business per il calcolo delle imposte è gestita dal livello [business] del server. Lo script [main] utilizzerà questo metodo;
- la logica di business per il calcolo delle imposte è gestita dal livello [business] del client. Lo script [main2] utilizzerà questo metodo;
D'ora in poi, svilupperemo diverse applicazioni client/server del tipo sopra descritto, ciascuna delle quali illustrerà una o più nuove tecnologie di sviluppo web.
23.2. Il server web per il calcolo delle imposte
23.2.1. Versione 1

Lo script [server_01] è la seguente applicazione web:

- In [1], utilizziamo un URL parametrizzato in cui passiamo tre valori:
- [married] (sì/no) per indicare se il contribuente è sposato;
- [children]: il numero di figli del contribuente;
- [salary]: lo stipendio annuale del contribuente;
- In [2], il server web restituisce una stringa JSON che fornisce l'importo dell'imposta dovuta insieme alle sue varie componenti;
L'architettura dell'applicazione è la seguente:

- il browser [1] interroga il server [2]. Lo script [server_01] implementa il livello [web] [2] del server;
- i livelli [3-8] sono quelli già utilizzati nella |versione 5| dell'applicazione di calcolo delle imposte. Li riutilizziamo così come sono;
- Il livello [business] [3] è definito |qui|;
- il livello [DAO] [4] è definito |qui|;
L'applicazione web [server_01] è configurata utilizzando tre script:
- [config], che configura l'intera applicazione;
- [config_database], che configura l'accesso al database. Lavoreremo con i DBMS MySQL e PostgreSQL;
- [config_layers], che configura i livelli dell'applicazione;
Lo script [config] è il seguente:
- La funzione [configure] accetta come argomento un dizionario [config] (riga 1) e lo restituisce come risultato (riga 54) dopo averne arricchito il contenuto. Si sarebbe potuto far notare già da tempo che non era necessario restituire il risultato [config]. Infatti, [config] è un riferimento a un dizionario che il codice chiamante condivide con il codice chiamato. Il codice chiamante possiede quindi già questo riferimento (riga 1) e non c'è bisogno di restituirlo nuovamente (riga 54). Pertanto, scrivendo:
config=[module].configure(config) (1)
è ridondante. È sufficiente scrivere:
[module].configure(config) (2)
Ciononostante, ho mantenuto lo stile di scrittura (1) perché ho ritenuto che potesse illustrare meglio il fatto che il codice chiamato modifica il dizionario [config].
- riga 1: il dizionario [config] ricevuto dalla funzione [configure] ha una chiave ‘sgbd’ il cui valore è tratto dalla lista [‘mysql’, ‘pgres’]. [mysql] significa che il database utilizzato è gestito da MySQL, mentre ‘pgres’ significa che il database utilizzato è gestito da PostgreSQL;
- Righe 4–27: elenchiamo tutte le directory contenenti gli elementi necessari per l’applicazione web. Faranno parte del Python Path dell’applicazione (righe 30–31);
- righe 33–40: solo determinati utenti potranno accedere all’applicazione. Qui abbiamo un elenco con un unico utente;
- righe 43–46: lo script [config_database] crea la configurazione per il database in uso;
- riga 46: la configurazione creata dallo script [config_database] è un dizionario che memorizziamo nella configurazione generale associata alla chiave ‘database’;
- righe 48–51: lo script [config_layers] istanzia i livelli dell'applicazione web. Restituisce un dizionario che viene memorizzato nella configurazione generale sotto la chiave ‘layers’;
Lo script [config_database] è quello già utilizzato nella |versione 5|. Lo riportiamo qui a titolo di riferimento:
Lo script [config_layers] configura i livelli del server web. Riutilizziamo uno |script| che abbiamo già visto in precedenza:
- Riga 6: Il livello [dao] è implementato utilizzando un database;
- [ImpotsDaoWithAdminDataInDatabase] è stato definito |qui|;
- [BusinessTaxes] è stato definito |qui|;
Lo script principale [server_01] è il seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | |
- righe 1–10: recuperare il parametro che indica quale DBMS utilizzare;
- righe 12–14: con queste informazioni, possiamo configurare l'applicazione. In particolare, viene costruito il Python Path;
- righe 16–23: con il nuovo Python Path, importiamo i moduli necessari;
- righe 25–31: recupero dei dati dall'autorità fiscale per calcolare l'imposta;
- righe 33–34: istanziamento dell'applicazione Flask;
- Riga 38: l'applicazione Flask serve solo l'URL [/]. Si aspetta un URL formattato come segue: [/ ?married=xx&children=yy&salary=zz], dove:
- xx: sì / no;
- yy: numero di figli;
- zz: stipendio annuo;
- righe 40–89: verifichiamo la validità dei parametri dell'URL;
- riga 41: accumuleremo i messaggi di errore nell'elenco [errors];
- riga 43: ricorderete che i parametri dell'URL si trovano in [request.args] (vedi |qui|):
- l'oggetto [request] è l'oggetto Flask importato alla riga 20;
- l'oggetto [request.args] si comporta come un dizionario;
- righe 43–44: verifichiamo che ci siano esattamente tre parametri (né meno, né di più);
- righe 46–49: controlliamo che il parametro [married] sia presente nell'URL;
- righe 50–54: se è presente, controlliamo che il suo valore in minuscolo, privato degli spazi iniziali e finali, sia “yes” o “no”;
- righe 56–59: controlliamo che il parametro [children] sia presente nell'URL;
- righe 60–66: se presente, controlliamo che il suo valore sia un numero intero positivo;
- riga 66: ricordiamo che i parametri URL e i loro valori sono stringhe. Il valore del parametro [children] viene convertito in un ‘int’;
- righe 68–78: per il parametro [salary], eseguiamo gli stessi controlli effettuati per il parametro [children];
- righe 81–83: verifichiamo che nell’URL non siano presenti parametri diversi da [‘married’, ‘children’, ‘salary’];
- righe 85–89: se, dopo tutti questi controlli, l'elenco [errors] non è vuoto, inviamo questo elenco di errori al client come stringa JSON insieme al codice di stato [400 Bad Request];
Poiché in seguito avremo spesso bisogno di inviare una stringa JSON in risposta al client, le poche righe necessarie a questo scopo sono state inserite nel modulo [myutils.py] che abbiamo già utilizzato:

Lo script [myutils.py] diventa il seguente:
- Riga 16: La funzione [json_response] richiede due parametri:
- [response]: il dizionario contenente la stringa JSON da inviare al client web;
- [status_code]: il codice di stato HTTP della risposta;
- riga 18: impostiamo il corpo JSON della risposta;
- riga 20: aggiungiamo l'intestazione HTTP che indica al client web che riceverà JSON;
- riga 22: inviamo la risposta HTTP al codice chiamante. Spetta al codice chiamante inviarla al client web;
Il file [__init__.py] cambia come segue:
from .myutils import set_syspath, json_response
La nuova versione di [myutils] viene installata tra i moduli a livello di macchina utilizzando il comando [pip install .] in un terminale PyCharm:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install .
Processing c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\packages
Using legacy setup.py install for myutils, since package 'wheel' is not installed.
Installing collected packages: myutils
Attempting uninstall: myutils
Found existing installation: myutils 0.1
Uninstalling myutils-0.1:
Successfully uninstalled myutils-0.1
Running setup.py install for myutils ... done
Successfully installed myutils-0.1
- Riga 1: Per inserire questo comando devi trovarti nella cartella [packages];
Il codice dello script [server_01] prosegue come segue:
- riga 10: a questo punto, i parametri previsti nell'URL sono presenti e corretti;
- riga 10: creiamo l'oggetto [TaxPayer] che modella il contribuente;
- riga 11: chiediamo al livello [business] di calcolare l'imposta. Si noti che gli elementi calcolati dal livello [business] vengono inseriti nell'oggetto [taxpayer] passato come parametro;
- riga 13: la risposta viene inviata al client web come stringa JSON. Si tratta della stringa JSON di un dizionario. Associato alla chiave [result], inseriamo il dizionario dell'oggetto [taxpayer]. Non abbiamo potuto inserire l'oggetto [taxpayer] stesso perché non è serializzabile in JSON;
Creiamo due configurazioni di esecuzione, una per MySQL e l'altra per PostgreSQL:

Ecco alcuni esempi di esecuzione (avete avviato l'applicazione [server_01] e il DBMS, quindi avete richiesto l'URLhttp://localhost:5000/ utilizzando un browser):


Ecco un esempio della richiesta nella console di Postman:

GET /?mari%C3%A9=xx&enfants=yy&salaire=zz HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: e4c5df8c-4bd6-4250-b789-b7b164db4eff
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 134
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Fri, 17 Jul 2020 06:15:44 GMT
{"réponse": {"erreurs": ["paramètre marié [xx] invalide", "paramètre enfants [yy] invalide", "paramètre salaire [zz] invalide"]}}
- riga 1: è stato richiesto un URL errato;
- riga 10: il server risponde con lo stato 400 BAD REQUEST;
23.2.2. Versione 2

La versione 2 del server isola l'elaborazione degli URL nel modulo [index_controller] [5]:
- riga 9: la funzione [execute] riceve due parametri:
- [request]: la richiesta HTTP del client;
- [config]: il dizionario di configurazione dell'applicazione;
Lo script [server_02] è il seguente:
- righe 36–41: gestione della route /;
- riga 39: utilizzo della funzione [IndexController.execute];
Ora useremo questa tecnica: ogni route sarà gestita dal proprio modulo.
I risultati dell'esecuzione sono gli stessi della versione 1.
23.2.3. Versione 3
La versione 3 introduce il concetto di autenticazione.
Lo script [server_03] diventa il seguente:
- riga 21: importare un gestore di autenticazione. Esistono vari tipi di autenticazione per un server web. Quello che stiamo utilizzando qui si chiama [HTTP Basic]. Ogni tipo di autenticazione segue uno specifico dialogo client/server;
- riga 33: crea un'istanza del gestore di autenticazione;
- riga 37: l'annotazione [@auth.verify_password] indica la funzione da eseguire quando il gestore di autenticazione vuole verificare il nome utente e la password inviati dal client secondo il protocollo [HTTP Basic];
- riga 55: l'annotazione [@auth.login_required] contrassegna una route per la quale il client web deve essere autenticato. Se il client web non ha ancora inviato le proprie credenziali, il server web le richiederà automaticamente utilizzando il protocollo HTTP Basic;
È necessario installare il modulo [flask_httpauth]:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-servers\01\flask>pip install flask_httpauth
Collecting flask_httpauth
Downloading Flask_HTTPAuth-4.1.0-py2.py3-none-any.whl (5.8 kB)
Requirement already satisfied: Flask in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from flask_httpauth) (1.1.2)
Requirement already satisfied: itsdangerous>=0.24 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask->flask_httpauth) (1.1.0)
Requirement already satisfied: click>=5.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask->flask_httpauth) (7.1.2)
Requirement already satisfied: Jinja2>=2.10.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask->flask_httpauth) (2.11.2)
Requirement already satisfied: Werkzeug>=0.15 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask->flask_httpauth) (1.0.1)
Requirement already satisfied: MarkupSafe>=0.23 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Jinja2>=2.10.1->Flask->flask_httpauth) (1.1.1
)
Installing collected packages: flask-httpauth
Successfully installed flask-httpauth-4.1.0
Vediamo cosa succede nella console di Postman. Tu:
- crea una configurazione di esecuzione;
- avvia l'applicazione web;
- avvia il database che preferisci;
- richiedi l'URL [/] con Postman;
Il dialogo client/server nella console di Postman è il seguente:
- Riga 10: Il server risponde che non siamo autorizzati ad accedere all'URL [/];
- Riga 13: Ci indica quale protocollo di autenticazione utilizzare, in questo caso il protocollo di autenticazione Basic;
È possibile configurare Postman per inviare le credenziali utente secondo il protocollo di autenticazione Basic:

- nei campi [6-7] inseriamo le credenziali presenti nello script [config]:
config['users'] = [
{
"login": "admin",
"password": "admin"
}
]
Il dialogo client/server nella console di Postman diventa il seguente:
GET / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 5ce20822-e87c-4eef-a2f4-b9eaec38d881
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 203
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Fri, 17 Jul 2020 07:20:01 GMT
{"réponse": {"erreurs": ["Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]", "paramètre [marié] manquant", "paramètre [enfants] manquant", "paramètre [salaire] manquant"]}}
- Riga 2: il client Postman invia le credenziali utente [admin / admin] in forma crittografata;
- riga 17: il server risponde correttamente. Segnala degli errori perché i parametri [sposato, figli, stipendio] non sono stati inviati (riga 1), ma non segnala un errore di autenticazione;
Ora richiediamo l'URL / utilizzando un browser (Firefox qui sotto):

- Come con Postman, Firefox ha ricevuto la risposta HTTP dal server con le seguenti intestazioni HTTP:
Firefox, come altri browser, non interrompe la finestra di dialogo quando riceve queste intestazioni. Chiede all’utente le credenziali richieste dal server. Nell’esempio sopra riportato, basta digitare admin / admin per ricevere la risposta del server:

23.3. Il client web del server di calcolo delle imposte
23.3.1. Introduzione
Nella sezione precedente, il client web per il server di calcolo delle imposte era un browser. In questa sezione, il client web sarà uno script da console. L'architettura diventa la seguente:

- il client web è costituito dai livelli [1-2];
- il server web è costituito dai livelli [3-9]. Come menzionato nella sezione precedente;
dobbiamo quindi scrivere i livelli [1-2].
Il livello [dao] [2] deve essere in grado di comunicare con il server web [3]. Ora che comprendiamo il protocollo HTTP, potremmo scrivere, utilizzando ad esempio il modulo [pycurl] che abbiamo già studiato, uno script che comunichi con il server web [3]. Tuttavia, esistono moduli specializzati nella comunicazione client/server HTTP. Ne useremo uno, il modulo [requests]:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-servers\01\flask>pip install requests
Collecting requests
Downloading requests-2.24.0-py2.py3-none-any.whl (61 kB)
|| 61 kB 137 kB/s
Collecting idna<3,>=2.5
Downloading idna-2.10-py2.py3-none-any.whl (58 kB)
|| 58 kB 692 kB/s
Collecting chardet<4,>=3.0.2
Downloading chardet-3.0.4-py2.py3-none-any.whl (133 kB)
|| 133 kB 1.3 MB/s
Collecting urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1
Downloading urllib3-1.25.9-py2.py3-none-any.whl (126 kB)
|| 126 kB 1.1 MB/s
Collecting certifi>=2017.4.17
Downloading certifi-2020.6.20-py2.py3-none-any.whl (156 kB)
|| 156 kB 1.1 MB/s
Installing collected packages: idna, chardet, urllib3, certifi, requests
Successfully installed certifi-2020.6.20 chardet-3.0.4 idna-2.10 requests-2.24.0 urllib3-1.25.9
La struttura delle directory per gli script del client web è la seguente:

Lo script implementerà l'applicazione per il calcolo delle imposte in modalità batch descritta nella |versione 1|. L'ultima versione di questa applicazione è la |versione 5|. Ecco un promemoria di come funziona:
- i contribuenti per i quali verrà calcolata l'imposta sono elencati nel file di testo [taxpayersdata.txt]:
- I risultati vengono salvati in due file:
- Il file di testo [errors.txt] elenca gli errori rilevati nel file del contribuente:
Analyse du fichier C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-clients\01\main/../data/input/taxpayersdata.txt
Ligne 15, not enough values to unpack (expected 4, got 2)
Ligne 17, MyException[1, L'identifiant d'une entité <class 'TaxPayer.TaxPayer'> doit être un entier >=0]
- (continua)
- Il file JSON [results.json] contiene i risultati del calcolo delle imposte per i vari contribuenti:
[
{
"id": 0,
"marié": "oui",
"enfants": 2,
"salaire": 55555,
"impôt": 2814,
"surcôte": 0,
"taux": 0.14,
"décôte": 0,
"réduction": 0
},
{
"id": 1,
"marié": "oui",
"enfants": 2,
"salaire": 50000,
"impôt": 1384,
"surcôte": 0,
"taux": 0.14,
"décôte": 384,
"réduction": 347
},
…
]
23.3.2. Configurazione del client web

La configurazione viene eseguita utilizzando due script:
- [config], che gestisce tutte le configurazioni al di fuori dei livelli dell'architettura;
- [config_layers], che gestisce la configurazione dei livelli dell'architettura;
Lo script [config] è il seguente:
- riga 1: la funzione [configure] accetta come parametro il dizionario da riempire con le informazioni di configurazione. Questo dizionario può essere già precompilato o vuoto. In questo caso, sarà vuoto;
- righe 40–42: i percorsi assoluti dei tre file di testo gestiti dal livello [dao];
- righe 43-50: associate alla chiave [server], le informazioni che il livello [dao] deve conoscere sul server web con cui deve comunicare:
- riga 44: l'URL del servizio web;
- riga 45: la chiave [authBasic] è impostata su True se l'accesso all'URL richiede l'autenticazione Basic;
- righe 46–49: le credenziali dell'utente che effettuerà l'autenticazione se richiesta;
- righe 56–57: istanziamo i livelli — in questo caso, il singolo livello [dao] — e inseriamo i riferimenti ai livelli in [config] sotto la chiave [layers];
Lo script [config_layers] è il seguente:
- Riga 1: la funzione [configure] riceve il dizionario che configura l'applicazione;
- righe 4–6: viene istanziato il livello [dao]. Alla riga 6, gli passiamo la configurazione dell'applicazione, dove troverà le informazioni di cui ha bisogno;
- righe 8–11: viene restituito un dizionario contenente il riferimento al livello [dao];
23.3.3. Lo script principale [main]
Lo script principale [main] è una variante di quello della |versione 5|:
- righe 2-3: l'applicazione è configurata;
- riga 13: il livello [dao] fornisce l'elenco dei contribuenti per i quali devono essere calcolate le imposte;
- riga 21: il livello [dao] calcola l'imposta per ciascuno di essi;
- riga 23: i risultati vengono salvati in un file JSON;
23.3.4. Implementazione del livello [dao]

Rivediamo l'architettura client/server utilizzata:

- in [2, 6], vediamo che il livello [dao] ha due ruoli:
- accede al file system sia per leggere i dati dei contribuenti sia per scrivere i risultati dei calcoli fiscali. Abbiamo già una classe |AbstractImpôtsDao| in grado di farlo. È in uso dalla |versione 4|;
- comunica con il server web [3];
Nella |versione 5|, lo script principale [main] [1] comunicava direttamente con il livello [business] [4]. Preferiremmo non modificare questo script. Per ottenere questo risultato, faremo in modo che il livello [DAO] [2] implementi l'interfaccia del livello [business] [4]. In questo modo, lo script principale [main] sembrerà comunicare direttamente con il livello [business] [4] e potrà ignorare completamente il fatto che si trovi su un'altra macchina.
Una definizione della classe che implementa il livello [DAO] [2] potrebbe essere la seguente:
class ImpôtsDaoWithHttpClient(AbstractImpôtsDao, InterfaceImpôtsMétier):
- La classe [TaxDaoWithHttpClient]:
- eredita dalla classe [AbstractTaxDao], il che le permette di gestire la comunicazione con il file system [6];
- implementa l'interfaccia [InterfaceImpôtsMétier] in modo da non dover modificare lo script principale [main] della |versione 5|;
Il codice completo della classe [TaxDaoWithHttpClient] è il seguente:
- righe 21–23: la classe [AbstractTaxDao] (riga 12) ha un metodo astratto [get_admindata]. Siamo tenuti a implementarlo anche se non lo utilizziamo (admindata è gestito dal server, non dal client);
- riga 26: il metodo [calculate_tax] appartiene all'interfaccia [InterfaceImpôtsMétier] (riga 12). Dobbiamo implementarlo;
- riga 15: il costruttore riceve il dizionario di configurazione dell'applicazione come unico parametro;
- righe 16–17: la classe padre [AbstractTaxDao] viene inizializzata passandole, anche in questo caso, la configurazione dell'applicazione. Qui troverà i nomi dei tre file di testo che deve gestire;
- righe 18–19: le informazioni relative al server web di calcolo delle imposte sono memorizzate localmente all'interno della classe;
- riga 26: il metodo [calculate_tax] riceve come parametro un oggetto di tipo |Taxpayer|. Per rispettare la firma del metodo [InterfaceImpôtsMétier.calculate_tax], riceve anche un parametro [admindata], che dovrebbe incapsulare i dati dell'amministrazione fiscale. Sul lato client, non disponiamo di questi dati. Questo parametro rimarrà sempre [None]. Questa soluzione alternativa suggerisce che la classe [ImpôtsMétier] fosse inizialmente mal progettata:
- la firma di [calculate_tax] avrebbe dovuto essere semplicemente:
def calculate_tax(self, taxpayer: TaxPayer)
e il parametro [admindata: AdminData] avrebbe dovuto essere passato al costruttore della classe;
- riga 27: il codice del metodo [calculate_tax] non è stato racchiuso in un blocco try / catch / finally. Ciò significa che eventuali eccezioni non verranno gestite e saranno propagate al codice chiamante, in questo caso lo script [main]. Questo script intercetta tutte le eccezioni propagate dal livello [dao];
- Riga 28: il calcolo dell'imposta viene eseguito sul lato server. Dovremo quindi comunicare con esso. Lo facciamo utilizzando il modulo [requests] importato alla riga 2;
- righe 31–43: per inviare una richiesta GET al server web, utilizziamo il metodo [requests.get]:
- righe 33–34: il primo parametro del metodo è l'URL da contattare;
- righe 35–40: gli altri due parametri sono parametri denominati il cui ordine non ha importanza;
- righe 35-36: il valore del parametro denominato [params] deve essere un dizionario contenente le informazioni da includere nell'URL nella forma [/url?param1=value1¶m2=value2&…];
- riga 29: il dizionario contenente i tre parametri [married, children, salary] che il server web si aspetta. Non dobbiamo preoccuparci della codifica (chiamata urlencoded) a cui questi parametri devono essere sottoposti. [requests] se ne occupa;
- righe 37–40: il parametro denominato [auth] è una tupla di due elementi (login, password). Rappresenta le credenziali per l'autenticazione Basic;
- righe 44–45: queste due righe hanno solo scopo didattico (le commenteremo una volta completato il debug):
- [response] rappresenta la risposta HTTP del server;
- [response.text] rappresenta il testo del documento contenuto in questa risposta. Durante il debug, è utile verificare cosa ci ha inviato il server;
- riga 47: [response.status_code] è il codice di stato HTTP della risposta ricevuta. Il nostro server ne invia solo tre:
- 200 OK
- 400 RICHIESTA NON VALIDA
- 500 ERRORE INTERNO DEL SERVER
- riga 49: il nostro server invia sempre JSON, anche in caso di errore. La funzione [response.json()] crea un dizionario dalla stringa JSON ricevuta. Esaminiamo le due possibili forme della stringa JSON:
{"réponse": {"erreurs": ["Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]", "paramètre [marié] manquant", "paramètre [enfants] manquant", "paramètre [salaire] manquant"]}}
{"réponse": {"result": {"id": 0, "marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}}}
- righe 51–53: se il codice di stato non è 200, viene generata un'eccezione con i messaggi di errore inclusi nella risposta;
- riga 56: recupera il dizionario generato dal calcolo delle imposte e lo utilizza per aggiornare il parametro di input [contribuente];
23.3.5. Esecuzione
Per eseguire il client:
- avviare il server [server_03] con il DBMS di propria scelta;
- eseguire lo script [main] del client;
I risultati si trovano nella cartella [data/output]. Sono gli stessi della versione 5.
23.4. Test del livello [dao]
Torniamo all'architettura dell'applicazione client/server:

- nel codice client, ci siamo assicurati che il livello [dao] [1] fornisca la stessa interfaccia del livello [business] [3]. Utilizzeremo quindi la classe di test |TestDaoMétier|, che abbiamo studiato in precedenza, per testare il livello [business] [3];
La classe di test verrà eseguita nel seguente ambiente:

- La configurazione [2] è identica alla configurazione [1], che abbiamo appena esaminato;
La classe di test [TestHttpClientDao] è la seguente:
Questa classe è simile a quella già studiata nella versione 4 dell'applicazione.
- righe 40-41: configurazione dell'ambiente di test;
- riga 44: recuperiamo un riferimento al livello [DAO];
- righe 47-48: eseguiamo i test;
Per eseguire i test, creiamo una |configurazione di esecuzione|:

- Creiamo una configurazione di esecuzione per uno script da console, non per un UnitTest;
Quando si esegue questa configurazione, si ottengono i seguenti risultati:
Tutti gli 11 test sono stati superati.