Il client sincrono¶
Normattiva è la classe da cui passa tutto: tiene aperta la connessione HTTP
verso l'API, il limitatore di richieste e la politica dei retry, ed espone un
metodo per ogni endpoint. Si costruisce una volta e si riusa, meglio se dentro
un with, che la chiude a fine blocco.
from normattiva import Normattiva
with Normattiva() as normattiva:
atto = normattiva.dettaglio("urn:nir:stato:legge:1990-08-07;241~art2")
stateDiagram-v2
direction LR
[*] --> Aperto : Normattiva(...)
Aperto --> Aperto : dettaglio(), ricerca(), start_export(), ...
Aperto --> Chiuso : close(), o uscita dal blocco with
Chiuso --> [*]
note right of Chiuso
closed vale True.
Un http_client passato da fuori
non viene chiuso: lo chiude chi lo ha aperto.
end note
Per l'uso asincrono c'è AsyncNormattiva, che rispecchia
questa classe metodo per metodo e firma per firma.
Normattiva
¶
Normattiva(
*,
user_agent: str | None = None,
timeout: float = 30.0,
retries: int = 2,
requests_per_second: float = 2.0,
base_url: str = PRODUZIONE,
http_client: Client | None = None,
sleep: Callable[[float], None] = sleep,
clock: Callable[[], float] = monotonic,
)
Il client sincrono verso il servizio open data di Normattiva.
Tiene il pool di connessioni HTTP, l'autolimitazione delle richieste e la politica dei tentativi, ed espone un metodo per ogni endpoint dell'API. Va costruito una volta e riusato: l'autolimitazione conta le richieste di un client, quindi con un client per chiamata non limita più niente.
I metodi che costano una richiesta restituiscono un modello; quelli che possono costarne molte sono iteratori, e il nome lo indica.
Ogni metodo che tocca la rete può sollevare ConnectionError se il
servizio non è raggiungibile, UnexpectedResponseError se la risposta non ha
la forma che la libreria sa leggere, e RequestBlockedError se lo strato
di protezione respinge la forma della richiesta. Le sezioni Raises dei
singoli metodi elencano solo le eccezioni specifiche di ciascuno.
Parametri:
| Nome | Tipo | Descrizione | Predefinito |
|---|---|---|---|
user_agent
|
str | None
|
come il client si presenta al servizio. Il predefinito nomina la libreria e il suo repository; indicare il proprio servizio e un recapito è una cortesia verso chi riceve il traffico. |
None
|
timeout
|
float
|
quanti secondi attendere una singola risposta. Le esportazioni lente vogliono un valore più alto. |
30.0
|
retries
|
int
|
quanti ritentativi dopo il primo tentativo, per ogni
richiesta. Con |
2
|
requests_per_second
|
float
|
il tetto che il client si impone. Il servizio non
pubblica quote: due al secondo è una scelta prudente di questa
libreria, non un limite imposto da Normattiva. Con |
2.0
|
base_url
|
str
|
la radice dell'API. Si cambia per puntare a un doppio del servizio nei test. |
PRODUZIONE
|
http_client
|
Client | None
|
un client |
None
|
sleep
|
Callable[[float], None]
|
la funzione che attende fra un tentativo e l'altro; nei test la si sostituisce per non attendere davvero. |
sleep
|
clock
|
Callable[[], float]
|
la sorgente di tempo dell'autolimitazione, sostituibile per lo stesso motivo. |
monotonic
|
Esempi:
>>> with Normattiva() as normattiva:
... atto = normattiva.dettaglio("urn:nir:stato:legge:1990-08-07;241~art2")
Codice sorgente in src/normattiva/client.py
close
¶
Rilascia il pool di connessioni, se è stato creato da questo client.
Un client HTTP passato dall'esterno resta aperto: chiuderlo spetta a chi lo ha creato.
dettaglio
¶
dettaglio(
atto: Urn | str | AttoTrovato,
*,
vigenza: Vigenza = None,
se_troncato: SeTroncato = "segnala",
) -> DettaglioAtto
Legge il testo di un atto o di un suo articolo.
atto è un URN, oppure un AttoTrovato uscito da una ricerca. Per i
dodici tipi di atto su trenta la cui forma URN non è verificata la
libreria passa dalle coordinate di Gazzetta, che il servizio accetta
allo stesso modo. Quel percorso però non supporta le date: chiedere una
vigenza per un atto raggiungibile solo così solleva un errore, il
parametro non viene ignorato in silenzio.
Senza vigenza il servizio restituisce il testo vigente oggi, e nella
risposta nulla dichiara a quale data corrisponde: indicarla è l'unico
modo di saperlo. Quando si indica una data, la finestra restituita
viene verificata contro di essa.
Con se_troncato="solleva" un articolo che sembra tagliato solleva
TruncationError invece di limitarsi a segnalarlo in una
proprietà.
Parametri:
| Nome | Tipo | Descrizione | Predefinito |
|---|---|---|---|
atto
|
Urn | str | AttoTrovato
|
un URN, la sua forma testuale, oppure un |
obbligatorio |
vigenza
|
Vigenza
|
il giorno a cui leggere il testo, |
None
|
se_troncato
|
SeTroncato
|
|
'segnala'
|
Restituisce:
| Tipo | Descrizione |
|---|---|
DettaglioAtto
|
Il testo richiesto, con la finestra di vigenza in cui è valido. |
Esempi:
Il testo dell'articolo 19 della legge 241 com'era nel 2000, con la finestra in cui quel testo è stato in vigore::
atto = normattiva.dettaglio(
"urn:nir:stato:legge:1990-08-07;241~art19",
vigenza=date(2000, 1, 1),
)
print(atto.finestra) # 1994-01-01 → 2005-03-07
Solleva:
| Tipo | Descrizione |
|---|---|
NotFoundError
|
nessun atto risponde a quelle coordinate. |
AmbiguityError
|
l'URN corrisponde a più atti pubblicati; i candidati sono elencati nell'eccezione. |
NotYetInForceError
|
l'articolo non esisteva alla data richiesta. |
ValidityMismatchError
|
il servizio ha risposto con una versione che non copre la data richiesta. |
TruncationError
|
solo con |
InvalidUrnError
|
l'URN è malformato, oppure la forma URN di quel tipo di atto non è verificata. |
InvalidArgumentError
|
la vigenza è indicata due volte, oppure è chiesta per un atto raggiungibile solo dalle coordinate di Gazzetta. |
Codice sorgente in src/normattiva/client.py
dettaglio_da_gazzetta
¶
dettaglio_da_gazzetta(
codice_redazionale: str,
data: date,
*,
se_troncato: SeTroncato = "segnala",
) -> DettaglioAtto
Legge un atto a partire dalle sue coordinate di Gazzetta.
È il percorso per gli atti la cui forma URN non è verificata: il codice redazionale e la data di Gazzetta arrivano da una ricerca, e il servizio risponde anche per un decreto-legge luogotenenziale del 1917. Questo percorso però non supporta la multivigenza: restituisce sempre il testo vigente oggi, e per una data serve un URN.
Parametri:
| Nome | Tipo | Descrizione | Predefinito |
|---|---|---|---|
codice_redazionale
|
str
|
l'identificativo di Gazzetta dell'atto, come
arriva da |
obbligatorio |
data
|
date
|
la data di pubblicazione in Gazzetta. |
obbligatorio |
se_troncato
|
SeTroncato
|
come per |
'segnala'
|
Restituisce:
| Tipo | Descrizione |
|---|---|
DettaglioAtto
|
Il testo dell'atto, sempre nella versione vigente oggi. |
Solleva:
| Tipo | Descrizione |
|---|---|
NotFoundError
|
nessun atto per quelle coordinate di Gazzetta. |
InvalidArgumentError
|
il codice redazionale è vuoto. |
Codice sorgente in src/normattiva/client.py
cronologia
¶
cronologia(
urn: Urn | str, *, massimo: int | None = None
) -> Iterator[DettaglioAtto]
Percorre tutte le versioni di un articolo, dall'originale a quella in vigore.
Costa una richiesta per versione: le finestre di vigenza sono contigue,
quindi ogni versione viene chiesta al giorno successivo alla fine della
precedente. Con massimo l'iterazione si ferma dopo quel numero di
versioni; senza, arriva all'ultima. Senza massimo la catena si ferma
comunque dopo cinquecento passi: nessun articolo italiano ha
cinquecento versioni, quindi una catena così lunga indica finestre non
contigue.
Parametri:
| Nome | Tipo | Descrizione | Predefinito |
|---|---|---|---|
urn
|
Urn | str
|
l'articolo di cui percorrere le versioni. |
obbligatorio |
massimo
|
int | None
|
quante versioni al più produrre. Senza, si arriva in fondo. |
None
|
Produce:
| Tipo | Descrizione |
|---|---|
DettaglioAtto
|
Una versione per volta, dalla più vecchia alla più recente. |
Solleva:
| Tipo | Descrizione |
|---|---|
UnexpectedResponseError
|
la catena non si chiude entro cinquecento passi, cioè le finestre di vigenza non sono contigue. |
Codice sorgente in src/normattiva/client.py
ricerca
¶
ricerca(
testo: str,
*,
pagina: int = 1,
per_pagina: int = 20,
sort: Sort | str = NEWEST,
tipo: str | None = None,
anno: int | None = None,
emettitore: str | None = None,
) -> EsitoRicerca
Cerca nel testo pieno del corpus.
Le parole vengono combinate in AND dal servizio; non c'è modo di
chiedere un OR. tipo, anno ed emettitore sono le faccette che la
risposta stessa propone, non le coordinate dell'atto: per quelle c'è
ricerca_avanzata.
Parametri:
| Nome | Tipo | Descrizione | Predefinito |
|---|---|---|---|
testo
|
str
|
le parole da cercare, combinate in AND dal servizio. |
obbligatorio |
pagina
|
int
|
quale pagina di risultati, a partire da 1. |
1
|
per_pagina
|
int
|
quanti risultati per pagina. |
20
|
sort
|
Sort | str
|
|
NEWEST
|
tipo
|
str | None
|
codice della faccetta per tipo di atto, come |
None
|
anno
|
int | None
|
faccetta per anno di provvedimento. |
None
|
emettitore
|
str | None
|
faccetta per amministrazione emanante. |
None
|
Restituisce:
| Tipo | Descrizione |
|---|---|
EsitoRicerca
|
Una pagina di risultati, con il totale e le faccette per restringere. |
Codice sorgente in src/normattiva/client.py
ricerca_avanzata
¶
ricerca_avanzata(
*,
denominazione: str | None = None,
anno: int | None = None,
numero: int | str | None = None,
giorno: int | None = None,
mese: int | None = None,
titolo: str | None = None,
testo: str | None = None,
vigente_al: date | None = None,
classe: ClasseProvvedimento | int | None = None,
emanazione: Intervallo | None = None,
pubblicazione: Intervallo | None = None,
sort: Sort | str = NEWEST,
tipo: str | None = None,
emettitore: str | None = None,
pagina: int = 1,
per_pagina: int = 20,
) -> EsitoRicerca
Cerca per coordinate invece che per parole.
Senza nessuna coordinata il servizio risponde con l'intero corpus: è una richiesta ammessa, e la libreria non la rifiuta.
Parametri:
| Nome | Tipo | Descrizione | Predefinito |
|---|---|---|---|
denominazione
|
str | None
|
il tipo di atto come lo scrive il dizionario, per
esempio |
None
|
anno
|
int | None
|
anno di emanazione. |
None
|
numero
|
int | str | None
|
numero del provvedimento. |
None
|
giorno
|
int | None
|
giorno di emanazione. |
None
|
mese
|
int | None
|
mese di emanazione. |
None
|
titolo
|
str | None
|
parole da cercare nel titolo. |
None
|
testo
|
str | None
|
parole da cercare nel testo. |
None
|
vigente_al
|
date | None
|
tiene solo gli atti in vigore in quel giorno. |
None
|
classe
|
ClasseProvvedimento | int | None
|
la classe redazionale dell'atto (senza aggiornamenti, aggiornato, abrogato). |
None
|
emanazione
|
Intervallo | None
|
intervallo di emanazione, come coppia |
None
|
pubblicazione
|
Intervallo | None
|
intervallo di pubblicazione in Gazzetta, come sopra. |
None
|
sort
|
Sort | str
|
|
NEWEST
|
tipo
|
str | None
|
faccetta per tipo di atto. |
None
|
emettitore
|
str | None
|
faccetta per amministrazione emanante. |
None
|
pagina
|
int
|
quale pagina di risultati, a partire da 1. |
1
|
per_pagina
|
int
|
quanti risultati per pagina. |
20
|
Restituisce:
| Tipo | Descrizione |
|---|---|
EsitoRicerca
|
Una pagina di risultati, nella stessa forma che rende |
Codice sorgente in src/normattiva/client.py
ricerca_completa
¶
ricerca_completa(
testo: str,
*,
massimo: int | None = None,
per_pagina: int = 50,
sort: Sort | str = NEWEST,
tipo: str | None = None,
anno: int | None = None,
emettitore: str | None = None,
) -> Iterator[AttoTrovato]
Scorre tutti gli atti che una ricerca trova, una pagina per volta.
L'iteratore è pigro: ogni pagina viene chiesta solo quando serve,
quindi consumare dieci risultati costa una richiesta sola. Con
massimo ci si ferma dopo quel numero di atti; per sapere quanti ce
n'erano in tutto basta una ricerca e il suo totale.
Quel totale è anche la condizione di uscita: il numero di pagina
restituito dal servizio non è affidabile, e usarlo come condizione
potrebbe rileggere la prima pagina all'infinito.
Parametri:
| Nome | Tipo | Descrizione | Predefinito |
|---|---|---|---|
testo
|
str
|
le parole da cercare. |
obbligatorio |
massimo
|
int | None
|
quanti atti al più produrre. Senza, si arriva in fondo. |
None
|
per_pagina
|
int
|
quanti risultati chiedere per richiesta. |
50
|
sort
|
Sort | str
|
|
NEWEST
|
tipo
|
str | None
|
faccetta per tipo di atto. |
None
|
anno
|
int | None
|
faccetta per anno di provvedimento. |
None
|
emettitore
|
str | None
|
faccetta per amministrazione emanante. |
None
|
Produce:
| Tipo | Descrizione |
|---|---|
AttoTrovato
|
Un atto per volta, nell'ordine in cui il servizio li rende. |
Codice sorgente in src/normattiva/client.py
atti_aggiornati
¶
atti_aggiornati(
dal: date, al: date
) -> Iterator[AttoTrovato]
Elenca gli atti modificati fra due date.
Il flusso contiene solo le modifiche: un atto pubblicato dentro la finestra ma mai modificato dopo non compare. Le finestre più lunghe di un anno vengono spezzate, perché il servizio le rifiuta.
Parametri:
| Nome | Tipo | Descrizione | Predefinito |
|---|---|---|---|
dal
|
date
|
primo giorno della finestra, compreso. |
obbligatorio |
al
|
date
|
ultimo giorno della finestra, compreso. |
obbligatorio |
Produce:
| Tipo | Descrizione |
|---|---|
AttoTrovato
|
Un atto modificato per volta, finestra dopo finestra. |
Solleva:
| Tipo | Descrizione |
|---|---|
RuleViolationError
|
|
Codice sorgente in src/normattiva/client.py
denominazioni
¶
denominazioni(
*, reload: bool = False
) -> tuple[Tipologica, ...]
Elenca i tipi di atto che il corpus contiene, e tiene il risultato in memoria.
Codice sorgente in src/normattiva/client.py
classi_provvedimento
¶
classi_provvedimento(
*, reload: bool = False
) -> tuple[Tipologica, ...]
Elenca le classi redazionali a cui un atto può appartenere.
Codice sorgente in src/normattiva/client.py
export_formats
¶
export_formats(
*, reload: bool = False
) -> tuple[Tipologica, ...]
Elenca i formati in cui si può chiedere un'esportazione.
Codice sorgente in src/normattiva/client.py
ricerche_predefinite
¶
ricerche_predefinite() -> tuple[RicercaPredefinita, ...]
Elenca le ricerche predefinite che il servizio propone.
collections
¶
collections() -> tuple[Collection, ...]
Elenca gli archivi già confezionati che il servizio mette a disposizione.
download_collection
¶
download_collection(
name: str,
*,
format: Format | str = JSON,
mode: ExportMode | str = VIGENTE,
) -> Corpus
Scarica un archivio già confezionato e legge gli atti che contiene.
Solleva:
| Tipo | Descrizione |
|---|---|
InvalidArgumentError
|
il formato chiesto non viene letto in
modelli; usare |
Codice sorgente in src/normattiva/client.py
save_collection
¶
save_collection(
name: str,
path: str | Path,
*,
format: Format | str = JSON,
mode: ExportMode | str = VIGENTE,
) -> Path
Scarica su disco un archivio già confezionato, in qualunque formato sia.
Codice sorgente in src/normattiva/client.py
start_export
¶
start_export(
*,
format: Format | str = JSON,
mode: ExportMode | str = MULTIVIGENTE,
massimo_atti: int | None = 100,
escludi_testo: str | None = None,
escludi_titolo: str | None = None,
denominazione: str | None = None,
anno: int | None = None,
numero: int | str | None = None,
giorno: int | None = None,
mese: int | None = None,
titolo: str | None = None,
testo: str | None = None,
vigente_al: date | None = None,
classe: ClasseProvvedimento | int | None = None,
emanazione: Intervallo | None = None,
pubblicazione: Intervallo | None = None,
) -> Export
Chiede l'esportazione completa degli atti che una ricerca trova.
Le coordinate sono le stesse di ricerca_avanzata. Prima di avviare
l'esportazione gli atti vengono contati con una ricerca sincrona,
perché un'esportazione costa minuti di lavoro al servizio e, una volta
partita, non si annulla. Con massimo_atti=None si parte senza
conteggio.
escludi_testo ed escludi_titolo escludono dal risultato gli atti
che contengono quelle parole, e sono l'unico filtro che l'esportazione
supporta e la ricerca no: il conteggio preventivo non ne tiene conto,
quindi può contare più atti di quanti ne arriveranno.
Parametri:
| Nome | Tipo | Descrizione | Predefinito |
|---|---|---|---|
format
|
Format | str
|
in che formato produrre l'archivio. Solo |
JSON
|
mode
|
ExportMode | str
|
quante versioni includere nell'archivio. |
MULTIVIGENTE
|
massimo_atti
|
int | None
|
il tetto oltre il quale l'esportazione non parte.
|
100
|
escludi_testo
|
str | None
|
esclude gli atti che contengono questa parola. |
None
|
escludi_titolo
|
str | None
|
esclude gli atti il cui titolo la contiene. |
None
|
denominazione
|
str | None
|
come in |
None
|
anno
|
int | None
|
anno di emanazione. |
None
|
numero
|
int | str | None
|
numero del provvedimento. |
None
|
giorno
|
int | None
|
giorno di emanazione. |
None
|
mese
|
int | None
|
mese di emanazione. |
None
|
titolo
|
str | None
|
parole da cercare nel titolo. |
None
|
testo
|
str | None
|
parole da cercare nel testo. |
None
|
vigente_al
|
date | None
|
tiene solo gli atti in vigore in quel giorno. |
None
|
classe
|
ClasseProvvedimento | int | None
|
la classe redazionale dell'atto (senza aggiornamenti, aggiornato, abrogato). |
None
|
emanazione
|
Intervallo | None
|
intervallo di emanazione, come coppia |
None
|
pubblicazione
|
Intervallo | None
|
intervallo di pubblicazione in Gazzetta. |
None
|
Restituisce:
| Tipo | Descrizione |
|---|---|
Export
|
L'esportazione appena avviata, da attendere e poi scaricare. |
Solleva:
| Tipo | Descrizione |
|---|---|
TooManyResultsError
|
i criteri selezionano più atti di
|
ConnectionError
|
il conteggio preventivo non è riuscito. Il messaggio indica come procedere senza. |
Codice sorgente in src/normattiva/client.py
863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 | |
export_from_token
¶
Riprende un'esportazione già in corso, dal suo token.