Uso avanzato di Requests

Questo documento illustra alcune delle caratteristiche avanzate di Requests.

Oggetti Session

L’oggetto Session vi consente di tenere traccia di alcuni parametri tra una richiesta e le successive. Esso memorizza anche i cookie tra le diverse richieste effettuate con esso.

Un oggetto Session ha tutti i metodi dell’API canonica di Requests.

Proviamo a memorizzare dei cookie tra le richieste:

s = requests.Session()

s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get("http://httpbin.org/cookies")

print(r.text)
# '{"cookies": {"sessioncookie": "123456789"}}'

Le sessioni possono essere usate anche per fornire dati di default ai metodi di richiesta. Basta passare i dati alle property di un oggetto Session:

s = requests.Session()
s.auth = ('user', 'pass')
s.headers.update({'x-test': 'true'})

# sia 'x-test' che 'x-test2' sono settati
s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})

I dizionari che passate a un metodo di richiesta saranno incorporati nei valori specifici della sessione che avete impostato. I parametri a livello di metodo sovrascrivono i corrispondenti parametri a livello di sessione.

Tutti i valori contenuti all’interno di una sessione sono direttamente disponibili. Per saperne di più leggete la documentazione dell’API delle sessioni.

Oggetti Request e Response

Quando si invocano requests.get() e metodi di richiesta simili accadono due cose importanti. Primo, state costruendo un oggetto Request che verrà inviato ad un server per ottenere un qualche genere di risorsa. Secondo, un oggetto Response è generato quando requests riceve una risposta dal server. L’oggetto Response contiene tutte le informazioni ritornate dal server e contiene anche l’oggetto Request creato in origine. Questa è una semplice richiesta che ottiene informazioni molti importanti dai server di Wikipedia:

>>> r = requests.get('http://en.wikipedia.org/wiki/Monty_Python')

Se volessimo accedere agli header che il server ci ha inviato, useremmo:

>>> r.headers
{'content-length': '56170', 'x-content-type-options': 'nosniff', 'x-cache':
'HIT from cp1006.eqiad.wmnet, MISS from cp1010.eqiad.wmnet', 'content-encoding':
'gzip', 'age': '3080', 'content-language': 'en', 'vary': 'Accept-Encoding,Cookie',
'server': 'Apache', 'last-modified': 'Wed, 13 Jun 2012 01:33:50 GMT',
'connection': 'close', 'cache-control': 'private, s-maxage=0, max-age=0,
must-revalidate', 'date': 'Thu, 14 Jun 2012 12:59:39 GMT', 'content-type':
'text/html; charset=UTF-8', 'x-cache-lookup': 'HIT from cp1006.eqiad.wmnet:3128,
MISS from cp1010.eqiad.wmnet:80'}

Mentre se volessimo accedere agli header che abbiamo inviato noi al server dovremmo accedere prima alla request e quindi agli header della richiesta:

>>> r.request.headers
{'Accept-Encoding': 'identity, deflate, compress, gzip',
'Accept': '*/*', 'User-Agent': 'python-requests/1.2.0'}

Richieste preparate

Ogni volta che ricevete un oggetto Response dalla chiamata all’API o a Session, l’attributo request contiene la PreparedRequest che è stata utilizzata.

In alcuni casi potreste dover manipolare il corpo o gli headers (e a dire il vero qualsiasi altra cosa) di una richiesta prima di inviarla. Un modo semplice per farlo è il seguente:

from requests import Request, Session

s = Session()
req = Request('GET', url,
    data=data,
    headers=header
)
prepped = req.prepare()

# fate qualcosa con prepped.body
# fate qualcosa con prepped.headers

resp = s.send(prepped,
    stream=stream,
    verify=verify,
    proxies=proxies,
    cert=cert,
    timeout=timeout
)

print(resp.status_code)

Visto che non dovete fare nulla di speciale con l’oggetto Request, lo preparate da subito e modificate l’oggetto PreparedRequest. A questo punto inviate questo oggetto insieme gli altri parametri che avreste passato ai metodi requests.* o Session.*.

Tuttavia, il codice qui sopra perde i vantaggi di avere un oggetto Session in Requests. Nello specifico, lo stato a livello di Session, come ad esempio i cookie, non viene riportato sulla vostra richiesta. Per ottenere una PreparedRequest contenente quello stato, sostituite la chiamata a Request.prepare() con la chiamata a Session.prepare_request(), in questo modo:

from requests import Request, Session

s = Session()
req = Request('GET',  url,
    data=data
    headers=headers
)

prepped = s.prepare_request(req)

# fate qualcosa con prepped.body
# fate qualcosa con prepped.headers

resp = s.send(prepped,
    stream=stream,
    verify=verify,
    proxies=proxies,
    cert=cert,
    timeout=timeout
)

print(resp.status_code)

Verifica dei certificati SSL

Requests può verificare i certificati SSL per le richieste HTTPS, esattamente come i browser. Per verificare il certificato SSL di un host potete usare l’argomento verify:

>>> requests.get('https://kennethreitz.com', verify=True)
requests.exceptions.SSLError: hostname 'kennethreitz.com' doesn't match either of '*.herokuapp.com', 'herokuapp.com'

Non ho impostato SSL su quel dominio, per cui la richiesta fallisce. Ottimo. Github tuttavia ha SSL:

>>> requests.get('https://github.com', verify=True)
<Response [200]>

Potete specificare per verify il path ad un file CA_BUNDLE contenente certificati di una Certification Authority di fiducia. La lista di bundle delle CA di fiducia può essere anche secificata attraverso la variabile di ambiente REQUESTS_CA_BUNDLE.

Requests può anche ignorare la verifica dei certificati SSL se impostata verify a False.

>>> requests.get('https://kennethreitz.com', verify=False)
<Response [200]>

Di default, verify è True. L’opzione verify si applica solo a certi host.

Potete anche specificare un certificato locale da usare come verifica client side, sia sotto forma di file locale (contenente la chiave privata e il certificato) che di tupla contenente i path ad entrambi i file:

>>> requests.get('https://kennethreitz.com', cert=('/path/server.crt', '/path/key'))
<Response [200]>

Se specificate un path errato o un certificato invalido:

>>> requests.get('https://kennethreitz.com', cert='/wrong_path/server.pem')
SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib

Workflow di lettura del corpo delle risposte

Di default, quando lanciate una richiesta il corpo della risposta è scaricato immediatamente. Potete modificare questo comportamento e deferire il download del corpo della risposta fino a quando non leggete il valore dell’attributo Response.content utilizzando il parametro

stream:

tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'
r = requests.get(tarball_url, stream=True)

A questo punto solo gli header sono stati scaricati per la risposta e la connessione rimane aperta, dunque lasciando il recupero del contenuto alla nostra volontà:

if int(r.headers['content-length']) < TOO_LONG:
  content = r.content
  ...

Potete ancora di più controllare il workflow usando i metodi Response.iter_content e Response.iter_lines. In alternativa, potete leggere il corpo della risposta dalla sottostante urllib3 urllib3.HTTPResponse disponibile invocando Response.raw.

Se impostate stream a True quando inviate una richiesta, Requests non può liberare la connessione in modo che ritorni nel pool fino a che non fruite dei dati o non chiamate Response.close. Questo comportamento può portare ad un uso inefficiente delle connessioni. Se vi capita sovente di leggere solo parzialmente il corpo delle richieste (o di non leggerlo per nulla) quando stream=True, dovreste prendere in considerazione l’uso di contextlib.closing (documentato qui), in questo modo:

from contextlib import closing

with closing(requests.get('http://httpbin.org/get', stream=True)) as r:
    # Usate la risposta

Keep-Alive

Grandi notizie — grazie a urllib3, il keep-alive delle connessioni è al 100% automatico all’interno di una sessione! Ogni richiesta che inviate all’interno di una sessione userà automaticamente la stessa connessione!

Notate che le connessioni sono rilasciate e mess nel pool per il riuso solo quando tutti i dati del corpo delle richieste sono stati letti; dunque accertatevi o di impostare stream a False o di leggere il content dei vostri oggetti Response.

Upload in streaming

Requests supporta gli upload in streaming, il che consente di inviare grossi flussi di dati o grandi file senza doverli leggere in memoria. Per lanciare un upload in streaming, vi basta fornire un file-like object come corpo della richiesta:

with open('massive-body', 'rb') as f:
    requests.post('http://some.url/streamed', data=f)

Richieste Chunk-Encoded

Requests supporta anche il trasferimento in chunks sia per le richieste in uscita che per le risposte in ingresso. Per inviare una richiesta chunk-encoded, dovete semplicemente fornire un generatore (o qualsiasi iteratore di lunghezza indefinita) come corpo della richiesta:

def gen():
    yield 'hi'
    yield 'there'

requests.post('http://some.url/chunked', data=gen())

POST-are più file Multipart-Encoded

Potete inviare più file in una singola richiesta. Ad esempio, immaginate di voler uploadare dei file immagine da un form HTML con un campo file multiplo di nome ‘images’:

<input type=”file” name=”images” multiple=”true” required=”true”/>

Per fare lo stesso con Requests vi basta inserire i file in una lista di tuple nella forma (nome_campo_del_form, tupla_con_info_sul_file):

>>> url = 'http://httpbin.org/post'
>>> multiple_files = [('images', ('foo.png', open('foo.png', 'rb'), 'image/png')),
                      ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))]
>>> r = requests.post(url, files=multiple_files)
>>> r.text
{
  ...
  'files': {'images': 'data:image/png;base64,iVBORw ....'}
  'Content-Type': 'multipart/form-data; boundary=3131623adb2043caaeb5538cc7aa0b3a',
  ...
}

Hook per gli eventi

Requests ha un sistema di hook che potete usare per manipolare le fasi del processo di richiesta o gestire eventi di segnalazione.

Hook disponibili:

response:
La risposta generata a partire da una richiesta.

Potete impostare una funzione callback ad ogni singola richiesta, passando un dizionario {hook_name: callback_function} al parametro hooks della richiesta:

hooks=dict(response=print_url)

La funzione callback_function riceverà come primo argomento la richiesta.

def print_url(r, *args, **kwargs):
    print(r.url)

Se accade un errore durante l’esecuzione della vostra callback, viene sollevato un warning. Se la callback ritorna un valore, il contratto implicito è usare questo valore per rimpiazzare i dati che sono stati passati come argomento. Se la callback non ritorna nulla, nessuna azione è intrapresa.

Printiamo a runtime alcuni metodi di richiesta:

>>> requests.get('http://httpbin.org', hooks=dict(response=print_url))
http://httpbin.org
<Response [200]>

Autenticazione custom

Requests vi permette di specificare un meccanismo di autenticazione custom.

Ogni callable che passerete all’argomento auth di un metodo di richiesta avrà la possibilità di modificare la richiesta prima che questa venga inviata.

Le implementazioni del meccanismo di autenticazione devono essere sottoclassi di requests.auth.AuthBase, e sono semplici da realizzare. Requests mette a disposizione in requests.auth due schemi di autenticazione di uso comune: HTTPBasicAuth e HTTPDigestAuth.

Mettiamoci nell’ipotetico caso di avere un web service che risponde solamente se l’header X-Pizza contiene una username. Abbastanza improbabile, ma andiamo avanti.

from requests.auth import AuthBase

class PizzaAuth(AuthBase):
    """Aggiunge l'Autenticazione HTTP Pizza a questa istanza di Request."""
    def __init__(self, username):
        # setup any auth-related data here
        self.username = username

    def __call__(self, r):
        # modifico e ritorno la richiesta
        r.headers['X-Pizza'] = self.username
        return r

A questo punto possiamo fare richieste usando la nostra classe Pizza Auth:

>>> requests.get('http://pizzabin.org/admin', auth=PizzaAuth('kenneth'))
<Response [200]>

Richieste in streaming

Con requests.Response.iter_lines() potete iterare in maniera semplice su API in streaming come l’API Streaming di Twitter. Basta impostare stream a True e iterare sulla risposta con iter_lines():

import json
import requests

r = requests.get('http://httpbin.org/stream/20', stream=True)

for line in r.iter_lines():

    # printa le nuove righe in streaming
    if line:
        print(json.loads(line))

..caveat:

:class:`~requests.Response.iter_lines()` non è un metodo rientrante.
Invocarlo più volte provoca la perdita di parte dei dati ricevuti. Nel caso
in cui serva invocarlo da più punti del vostro codice, usate piuttosto
l'iteratore che risulta dalla sua invocazione::

    lines = r.iter_lines()
    # Memorizza la prima riga per dopo o skippala
    first_line = next(lines)
    for line in lines:
        print(line)

Proxy

Se dovete utilizzare un proxy, potete configurare ogni singolo metodo di richiesta con l’argomento proxies:

import requests

proxies = {
  "http": "http://10.10.1.10:3128",
  "https": "http://10.10.1.10:1080",
}

requests.get("http://example.org", proxies=proxies)

Potete anche configurare i proxy attraverso le variabili di ambiente HTTP_PROXY e HTTPS_PROXY.

$ export HTTP_PROXY="http://10.10.1.10:3128"
$ export HTTPS_PROXY="http://10.10.1.10:1080"
$ python
>>> import requests
>>> requests.get("http://example.org")

Per usare la HTTP Basic Authentication con il vostro proxy, servitevi della sintassi http://user:password@host/

proxies = {
    "http": "http://user:[email protected]:3128/",
}

Notate che gli URL dei proxy devono includere lo schema.

Conformità

Requests è pensato per essere conforme con tutte le specifiche e le RFC rilevanti laddove tale conformità non causi difficoltà d’utilizzo per gli utenti. Questa attenzione alla specifica può portare ad un comportamento che potrebbe sembrare inusuale a coloro che non sono familiari con le specifiche stesse.

Encoding

Quando ricevete una risposta, Requests cerca di capire l’encoding da usare per decodificarla quando accedete l’attributo Response.text. Requests dapprima cercherà un encoding specifico negli header HTTP, e se non ne trova, allora si servirà chardet per cercare di indovindare l’encoding.

L’unica situazione in cui Requests non seguirà questa procedura è quando non è specificato un charset esplicito negli header HTTP e l’header Content-Type contiene text. In tale situazione, la RFC 2616 specifica che il charset di default deve essere ISO-8859-1. Requests si conforma alla specifica in questo caso. Se avete bisogno di un encoding diverso, potete settare manualmente la property Response.encoding o usare l’oggetto Response.content raw.

Verbi HTTP

Requests dà accesso a quasi tutto il range di verbi HTTP: GET, OPTIONS, HEAD, POST, PUT, PATCH e DELETE. Di seguito vengono illustrati esempi dettagliati di come usare questi verbi in Requests, usando l’API di GitHub.

Cominceremo con il verbo di più comune utilizzo: GET. HTTP GET è un metodo idempotente che ritorna una risorsa da un URL specifico. Dunque è il verbo che dovete usare quando cercate di ottenere dati da un indirizzo web. Un esempio d’uso potrebbe essere recuperare informazioni su una specifica commit da GitHub. Immaginate di volere la commit a050faf con Requests. La potremmo recuperare così:

>>> import requests
>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')

Dovremmo controllare se GitHub ha risposto correttamente. Se sì, wogliamo capire quale tipo di contenuto ha ritornato. Facciamo così:

>>> if r.status_code == requests.codes.ok:
...     print(r.headers['content-type'])
...
application/json; charset=utf-8

Dunque GitHub ritorna JSON. Grandioso, possiamo usare il metodo r.json per convertirlo in un oggetto Python.

>>> commit_data = r.json()
>>> print(commit_data.keys())
[u'committer', u'author', u'url', u'tree', u'sha', u'parents', u'message']
>>> print(commit_data[u'committer'])
{u'date': u'2012-05-10T11:10:50-07:00', u'email': u'[email protected]', u'name': u'Kenneth Reitz'}
>>> print(commit_data[u'message'])
makin' history

Finora tutto semplice. Ora investighiamo l’API di GitHub un po’ più nel dettaglio. Potremmo guardare la documentazione, ma sarebbe più divertente usare Requests. Possiamo servirci del verbo OPTIONS per vedere quali metodi HTTP sono consentiti sull’URL che abbiamo appena usato.

>>> verbs = requests.options(r.url)
>>> verbs.status_code
500

Cosa? Questo non ci serve! Sembra che GitHub, come molti provider di API, non implementi il metodo OPTIONS. E’ una cosa noiosa, ma OK, possiamo sempre annoiarci a leggere la documentazione. Se GitHub avesse implementato OPTIONS, avrebbe ritornato negli header i metodi consentiti. As esempio

>>> verbs = requests.options('http://a-good-website.com/api/cats')
>>> print(verbs.headers['allow'])
GET,HEAD,POST,OPTIONS

Se leggiamo la documentazione, notiamo che l’unico altro metodo consentito sulle commit è POST, che crea una nuova commit. Dato che stiamo usando il repo di Requests, dovremmo evitare di creare a mano delle nuovo commit. Usiamo invece le Issue di GitHub.

Il documento che state leggendo è stato aggiunto in risposta alla Issue #482. Dal momento che tale issue esiste già, la useremo come esempio. Cominciamo con il leggerla.

>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/issues/482')
>>> r.status_code
200
>>> issue = json.loads(r.text)
>>> print(issue[u'title'])
Feature any http verb in docs
>>> print(issue[u'comments'])
3

Grande, abbiamo tre commenti. Diamo un’occhiata all’ultimo.

>>> r = requests.get(r.url + u'/comments')
>>> r.status_code
200
>>> comments = r.json()
>>> print(comments[0].keys())
[u'body', u'url', u'created_at', u'updated_at', u'user', u'id']
>>> print(comments[2][u'body'])
Probably in the "advanced" section

Bè, non sembra la migliore delle sezioni! Postiamo un nuovo commento per dire all’autore del commento che è un cretino. Ma... chi è l’autore?

>>> print(comments[2][u'user'][u'login'])
kennethreitz

OK, diciamo a questo tizio di nome Kenneth che crediamo che l’esempio debba andare nella sezione quickstart. La documentazione dell’API di GitHub, il modo giusto è POST-are su un thread. Facciamolo

>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it!"})
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/482/comments"
>>> r = requests.post(url=url, data=body)
>>> r.status_code
404

Mmmm, strano. Probabilmente dobbiamo autenticarci. Sarà un’impresa, vero? No, sbagliato. Requests rende facile usare varie forme di autenticazione, inclusa la comunissima HTTP Basic Authentication.

>>> from requests.auth import HTTPBasicAuth
>>> auth = HTTPBasicAuth('[email protected]', 'not_a_real_password')
>>> r = requests.post(url=url, data=body, auth=auth)
>>> r.status_code
201
>>> content = r.json()
>>> print(content[u'body'])
Sounds great! I'll get right on it.

Fico! No, aspettate un attimo! Volevamo anche aggiungere che ci metteremo un po’ perchè dobbiamo dare da mangiare al nostro gatto. Se solo potessimo modificare quel commento! Con piacere, GitHub ci permette di usare un altro verbo HTTP, PATCH, per modificare quel commento. Facciamolo.

>>> print(content[u"id"])
5804413
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it once I feed my cat."})
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/comments/5804413"
>>> r = requests.patch(url=url, data=body, auth=auth)
>>> r.status_code
200

Eccellente. Ora, per continuare a torturare questo tizio di nome Kenneth, decidiamo di lasciarlo all’oscuro del fatto che stiamo lavorando sulla issue. Questo significa che vogliamo cancellare il commento usando il metodo DELETE, dal nome incredibilmente azzeccato. Buttiamo via il commento.

>>> r = requests.delete(url=url, auth=auth)
>>> r.status_code
204
>>> r.headers['status']
'204 No Content'

Perfetto. E’ sparito. L’ultima cosa che vorremmo sapere è quanto siamo vicini al numero limite di chiamate all’API. Vediamolo. GitHub invia questa informazione negli headers, dunque al posto di scaricare un’intera pagina manderemo una richiesta di tipo HEAD su di essa per recuperare solo gli headers.

>>> r = requests.head(url=url, auth=auth)
>>> print(r.headers)
...
'x-ratelimit-remaining': '4995'
'x-ratelimit-limit': '5000'
...

Eccellente. E’ il momento di scrivere un programma Python che abusi dell’API di GitHub in un sacco di modi divertenti, 4995 altre volte.

Adapter di Trasporto

Dalla versione v1.0.0, Requests ha adottato un design interno modulare. Uno dei motivi alla base di questo è l’implementazione di Adapter di Trasporto, in origine `descritti qui`_. Gli Adapter di Trasporto forniscono un meccanismo per definire i metodi di interazione con un servizio HTTP. Nello specifico, permettono di utilizzare una configurazione specifica per ogni servizio.

Requests contiene un singolo Adapter di Trasporto, HTTPAdapter. Questo adapter implementa l’interazione di default di Requests con HTTP e HTTPS servendosi della poderosa libreria urllib3 library. Ogni volta che una Session è inizializzata, un adapter è allegato all’oggetto Session per HTTP e un secondo adapter per HTTPS.

Requests consente agli utenti di creare e usare i propri Adapter di Trasporto per esporre funzionalità custom. Una volta creato, un Adapter di Trasporto può essere montato su un oggetto Session insieme all’indicazione di quali servizi web si dovrebbe applicare.

>>> s = requests.Session()
>>> s.mount('http://www.github.com', MyAdapter())

Questa chiamata registra un’istanza specifica di un Adapter di Trasporto ad un prefisso. Una volta montato, ogni richiesta HTTP fatta usando la sessione il cui URL inizia con il prefisso specificato si servirà dell’Adapter specificato.

Molti dei dettagli implementativi di un Adapter di Trasporto sono oltre lo scopo di questa documentazione, ma date un’occhiata al prossimo esempio per un semplice caso d’uso con SSL. Se volete andare ancora oltre, dovreste creare una sottoclasse di requests.adapters.BaseAdapter.

Esempio: usare una versione specifica di SSL

Il team di Requests ha fatto la scelta specifica di usare qualsiasi versione di SSL si trovi di default nella sottostante libreria (urllib3). Ci potrebbero essere volte in cui dovrete connettervi ad un servizio web che usa una versione di SSL non compatibile con la vostra di default.

In questo caso potete usare gli Adapter di Trasporto riutilizzando la maggior parte dell’implementazione di un HTTPAdapter e aggiungendo un parametro ssl_version che viene passato ad urllib3. Creiamo un Adapter che dice alla libreria di usare SSLv3:

import ssl

from requests.adapters import HTTPAdapter
from requests.packages.urllib3.poolmanager import PoolManager


class Ssl3HttpAdapter(HTTPAdapter):
    """"Transport adapter" that allows us to use SSLv3."""

    def init_poolmanager(self, connections, maxsize, block=False):
        self.poolmanager = PoolManager(num_pools=connections,
                                       maxsize=maxsize,
                                       block=block,
                                       ssl_version=ssl.PROTOCOL_SSLv3)

Bloccante o Non-Bloccante?

Quando usa l’Adapter di Trasporto di default, Requests non provvede alcun tipo di IO non-bloccante. La property Response.content si bloccherà fincho a che l’intera risposta non è stata scaricata. Se vi serve pià granularità, le potenzialità di streaming della libreria (si veda Richieste in streaming) vi consentiranno di recuperare piccoli pezzi della risposta uno dopo l’altro. Tuttavia, queste chiamate sono sempre bloccanti.

Se intendete usare un paradigma di IO non-bloccante, ci sono molti progetti che combinano Requests con uno dei framework asincroni per Python. Due esempi d’eccellenza sono grequests e requests-futures.

Timeout

La maggior parte delle richieste a server esterni dovrebbero contenere un timeout nel caso in cui il server non risponda con le tempistiche che ci si attende. Senza un timeout il vostro codice potrebbe rimanere in attesa per minuti o anche più.

Il timeout di connessione è il numero di secondi che Requests attenderà che il vostro client stabilisca una connessione alla macchina remota (corrisponde alla chiamata connect()) sul socket. E’ buona pratica settare i timeout di connessione poco oltre un multiplo di 3, che è la finestra di default di TCP per la ritrasmissione dei pacchetti.

Quando il client è connesso al server e ha inviato la richiesta HTTP, il timeout di lettura è il numero di secondi che il client attenderà che il server invii una rispostsa (nello specifico, è il numero di secondi che il client attende tra ogni byte inviato dal server. Nel 99.9% dei casi, questo è il tempo che passa prima che il server invii il primo byte).

Se specificate un valore singolo per il timeout, ad esempio così:

r = requests.get('https://github.com', timeout=5)

Il valore di timeout sarà usato per entrambi i timeout di connect e read. Specificate una tupla se volete impostare i valori separatamente:

r = requests.get('https://github.com', timeout=(3.05, 27))

Se il server remoto è molto lento, potete istruire Requests di attendere indefinitamente per la risposta, passando None come valore di timeout e munendovi di una tazza di caffè.

r = requests.get('https://github.com', timeout=None)

Certificati delle CA

Di default Requests contiene una raccolta di certificati root delle Certification Authority che considera fidate, provenienti dal Mozilla trust store. Tuttavia questi certificati sono aggiornati solo ad ogni nuova versione di Requests. Ciò significa che se utilizzate per molto tempo una specifica versione di Requests, i certificati possono divenire molto obsoleti.

Dalla versione 2.4.0 in poi, Requests cerca di utilizzare i certificati da certifi se questo è presente sul sistema. Ciò consente agli utenti di aggiornare i loro certificati di fiducia senza dover modificare il codice che gira sui loro sistemi.

A beneficio della sicurezza, vi raccomandiamo di aggiornare certifi frequentemente!