4. Richieste di Rete con URLRequest

Nella lezione precedente abbiamo utilizzato la libreria Kivy per creare il layout dell’applicazione Wikipedia Reader e abbiamo utilizzato il linguaggio KV per aggiungere dei widget, tra cui il tasto che permetterà di mostrare un articolo casuale.

In questa lezione vediamo come connetterci a Wikipedia utilizzando UrlRequest, una classe di Kivy che consente di effettuare richieste HTTP asincrone: ciò significa che la richiesta di rete verrà effettuata in background, senza bloccare l'interfaccia utente. Per connetterci a Wikipedia comunicheremo con l’API di MediaWiki, che consente di accedere ai dati presenti nel sito.

Potete trovare il codice di WIkipedia Reader sul repo Github.


Come effettuare richieste di rete con UrlRequest

Per predisporci ad effettuare connessioni di rete importiamo la classe UrlRequest di Kivy e aggiungiamo i nuovi metodi get_data e set_textarea alla classe WikiReaderApp:

from kivy.network.urlrequest import UrlRequest


class WikiReaderApp(MDApp):

	# ...

    def get_data(self, request, response):
        pass

    def set_textarea(self, request, response):
        pass

Modifichiamo la stringa multiriga KV cambiando il testo in modo che descriva la funzione del pulsante che intendiamo implementare:

KV = """
Screen:
    # ...
        MDRaisedButton:
            id: mdbu
            text: "CERCA ARTICOLO CASUALE"
            size_hint_x: 1
            on_press: app.random_search_button()
"""

Facciamo in modo che il metodo random_search_button venga richiamato alla pressione del tasto MDRaisedButton e definiamo l’endpoint dell’API di MediaWiki che ci permette di reperire l'estratto di un articolo casuale di Wikipedia in lingua italiana. Modifichiamo il testo mostrato dall’etichetta MDLabel alla pressione del pulsante in modo che dica all’utente che l’articolo è in caricamento:

class WikiReaderApp(MDApp):

	# ...

    def random_search_button(self):
        endpoint = "https://it.wikipedia.org/w/api.php?action=query&list=random&rnlimit=1&rnnamespace=0&format=json"
        self.root.ids["mdlab"].text = "Caricamento in corso..."

Quando viene effettuata una richiesta verso l’endpoint si ottiene in risposta un json che contiene come valore della chiave query i dettagli dell’articolo, tra cui il titolo e l’id.

{"batchcomplete":"","continue":{"rncontinue":"0.751258403068|0.75125901284|3329104|0","continue":"-||"},"query":{"random":[{"id":6677755,"ns":0,"title":"Bruguiera sexangula"}]}}

Potete ottenere il contenuto del json inserendo il link nella barra di ricerca del browser o mostrandolo da terminale tramite la funzione print.


Come autenticare la connessione HTTP tramite il modulo certifi

Per fare in modo che la richiesta venga effettuata definiamo rs_request (dove rs sta per random search) e assegniamolo alla classe UrlRequest, alla quale passiamo tre parametri:

  • L’endpoint
  • Il metodo on_success che se la richiesta di rete ha successo chiama il metodo get_data (che a breve modificheremo)
  • Il metodo ca_file, utilizzato per specificare un file di certificato SSL da utilizzare per autenticare la connessione HTTPS con il server di destinazione.

Per specificare il certificato dobbiamo importare il modulo certifi. Se non è già presente nel nostro ambiente virtuale possiamo installarlo con pip:

pip install certifi

Il modulo certifi viene utilizzato per fornire un'implementazione SSL affidabile per le richieste HTTP effettuate dalle applicazioni Python, garantendo così la sicurezza delle comunicazioni tra il client e il server. Assegniamo al parametro ca_file il metodo where di certifi per ottenere il percorso del file di certificati SSL di sistema che verrà utilizzato per autenticare le connessioni HTTPS.

import certifi

# ...

class WikiReaderApp(MDApp):

    # ...

    def random_search_button(self):
        # ...
        self.rs_request = UrlRequest(endpoint,
                                     on_success=self.get_data,
                                     ca_file=certifi.where())

In questo modo alla pressione del pulsante “Cerca articolo casuale” verrà chiamato il metodo random_search_button che aggiornerà il contenuto dell’area testuale con la scritta “Caricamento in corso…”, effettuerà una richiesta di rete all’endpoint e chiamerà la callback function get_data se la richiesta ha successo. Andiamo quindi a modificare get_data.


Come analizzare ed estrarre i dati ricevuti come risposta alle richieste di rete

Facciamo in modo che il metodo che accetti come parametri request e response, assegnamo il contenuto della chiave query del json che abbiamo ottenuto come risposta dalla API di MediaWIki (che corrisponde alla lista contenuta come valore della chiave random) alla variabile random_article e assegnamo il titolo dell’articolo alla variabile random_title. Assegniamo alla variabile endpoint l’URL fornito dalla API in risposta alla richiesta a cui aggiungiamo in coda il titolo dell’articolo utilizzando il metodo replace per sostituire gli spazi con il codice escape %20.

Aggiungiamo una nuova richiesta di rete alla nuova variabile data_request come abbiamo fatto con random_search_button ma assegnando a on_success la funzione set_textarea che mostrerà nell’area testuale il contenuto dell’articolo.

class WikiReaderApp(MDApp):
    # ...

    def get_data(self, request, response):
        random_article = response["query"]["random"][0]
        random_title = random_article["title"]
        endpoint = f"https://it.wikipedia.org/w/api.php?prop=extracts&explaintext&exintro&format=json&action=query&titles={random_title.replace(' ', '%20')}"
        self.data_request = UrlRequest(endpoint,
                                       on_success=self.set_textarea,
                                       ca_file=certifi.where())

Il json che rappresenta il nostro endpoint in questo caso sarà simile al seguente:

{"batchcomplete":"","query":{"pages":{"1143446":{"pageid":1143446,"ns":0,"title":"Aleph","extract":"L'aleph \u00e8 la prima lettera dell'alfabeto fenicio e la prima lettera dell'alfabeto ebraico."}}}}

Andiamo quindi a modificare il metodo set_textarea in modo che estragga dal json le informazioni che ci servono. Facciamo in modo che il metodo accetti request e response come parametri e assegnamo a page_info i valori della chiave pages. Tramite le funzioni iter e next accediamo al primo elemento di pages per ottenere l’id e tramite esso accediamo al titolo e all’estratto principale del testo dell’articolo: adesso possiamo mostrare i dati ottenuti nell’area testuale all’interno dell’etichetta MDLabel riferendoci ad essa tramite l’id che abbiano definito nella stringa KV durante la lezione precedente:

class WikiReaderApp(MDApp):

    # ...

    def set_textarea(self, request, response):
        page_info = response["query"]["pages"]
        page_id = next(iter(page_info))
        page_title = page_info[page_id]["title"]
        page_extract = page_info[page_id]["extract"]
        self.root.ids["mdlab"].text = f"{page_title}\n\n{page_extract}"

Se adesso eseguiamo il codice potremmo leggere un estratto dell’articolo:

kivy_lezione4

A questo punto il nostro codice dovrebbe essere così:

import certifi
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.network.urlrequest import UrlRequest


KV = """
Screen:
    GridLayout:
        rows: 2
        ScrollView:
            MDLabel:
                id: mdlab
                text: "Benvenuti su Wikipedia Reader!"
                size_hint_y: None
                height: self.texture_size[1]
                text_size: self.width, None
        MDRaisedButton:
            id: mdbu
            text: "CERCA ARTICOLO CASUALE"
            size_hint_x: 1
            on_press: app.random_search_button()
"""


class WikiReaderApp(MDApp):

    def build(self):
        self.title = "WikipediaReader"
        self.theme_cls.primary_palette = "Teal"
        self.theme_cls.primary_hue = "400"
        return Builder.load_string(KV)

    def random_search_button(self):
        endpoint = "https://it.wikipedia.org/w/api.php?action=query&list=random&rnlimit=1&rnnamespace=0&format=json"
        self.root.ids["mdlab"].text = "Caricamento in corso..."
        self.rs_request = UrlRequest(endpoint,
                                     on_success=self.get_data,
                                     ca_file=certifi.where())

    def get_data(self, request, response):
        random_article = response["query"]["random"][0]
        random_title = random_article["title"]
        endpoint = f"https://it.wikipedia.org/w/api.php?prop=extracts&explaintext&exintro&format=json&action=query&titles={random_title.replace(' ', '%20')}"
        self.data_request = UrlRequest(endpoint,
                                       on_success=self.set_textarea,
                                       ca_file=certifi.where())

    def set_textarea(self, request, response):
        page_info = response["query"]["pages"]
        page_id = next(iter(page_info))
        page_title = page_info[page_id]["title"]
        page_extract = page_info[page_id]["extract"]
        self.root.ids["mdlab"].text = f"{page_title}\n\n{page_extract}"



WikiReaderApp().run()

Nella prossima lezione aggiungeremo al layout della nostra app un nuovo tasto e un nuovo widget in cui l’utente potrà inserire il titolo di un articolo specifico da visualizzare nella schermata.