Vai al contenuto

I modelli

Tutti i modelli sono dataclass congelate: non si modificano dopo la costruzione, si hashano e si confrontano per valore. Nessuno di questi va costruito a mano nell'uso normale: arrivano dalle risposte del servizio.

Quale oggetto arriva da quale chiamata

Chiamata Restituisce Contiene
dettaglio DettaglioAtto il testo di un atto o articolo in una finestra di vigenza
cronologia iteratore di DettaglioAtto una versione per volta, dalla più vecchia
ricerca, ricerca_avanzata EsitoRicerca una pagina di AttoTrovato, più totale e faccette
ricerca_completa iteratore di AttoTrovato gli atti di tutte le pagine, uno per volta
atti_aggiornati iteratore di AttoTrovato gli atti modificati nel periodo
start_export Export il lavoro in corso, con il suo token
Export.download Corpus un AttoStorico per atto, con tutte le versioni
denominazioni e le altre tipologiche tupla di Tipologica i codici che i criteri accettano
collections tupla di Collection gli archivi già confezionati
classDiagram
    direction LR
    class DettaglioAtto {
        +str titolo
        +str testo
        +tuple~Comma~ commi
        +str note_aggiornamento
        +bool possibile_troncamento
        +str permalink
    }
    class EstremiAtto {
        +str denominazione
        +date data
        +str numero
        +str citazione
    }
    class PubblicazioneGazzetta {
        +date data
        +int numero
        +str codice_redazionale
    }
    class FinestraVigenza {
        +date inizio
        +date fine
        +bool aperta
        +contiene(giorno) bool
    }
    class Comma {
        +str numero
        +str testo
    }
    class EsitoRicerca {
        +int totale
        +int pagina
        +bool ultima_pagina
    }
    class AttoTrovato {
        +str titolo
        +bool ha_urn
        +Urn urn
    }
    class Faccette {
        +tuple per_anno
        +tuple per_tipo
        +tuple per_emettitore
    }

    DettaglioAtto *-- "1" EstremiAtto : estremi
    DettaglioAtto *-- "1" PubblicazioneGazzetta : gazzetta
    DettaglioAtto *-- "1" FinestraVigenza : finestra
    DettaglioAtto *-- "0..n" Comma : commi
    EsitoRicerca *-- "0..n" AttoTrovato : atti
    EsitoRicerca *-- "1" Faccette : faccette
    AttoTrovato *-- "1" EstremiAtto : estremi
    AttoTrovato *-- "1" PubblicazioneGazzetta : gazzetta

Dall'esportazione arriva invece un albero, dove Partizione contiene sé stessa:

classDiagram
    direction LR
    class Corpus {
        +tuple~AttoStorico~ atti
        +save(path)
        +from_zip(path)$ Corpus
    }
    class AttoStorico {
        +Urn urn
        +bool abrogato
        +date pubblicato_il
        +alla_data(giorno) VersioneAtto
        +originale VersioneAtto
        +vigente VersioneAtto
    }
    class VersioneAtto {
        +date vigente_dal
        +bool originale
        +articoli() Iterator
    }
    class Partizione {
        +str tipo
        +str numero
        +str rubrica
        +str testo
    }
    class Aggiornamento {
        +date data
        +str testo
    }

    Corpus *-- "1..n" AttoStorico : atti
    AttoStorico *-- "1..n" VersioneAtto : versioni
    AttoStorico *-- "0..n" Aggiornamento : aggiornamenti
    VersioneAtto *-- "0..n" Partizione : articolato
    VersioneAtto *-- "0..n" Partizione : annessi
    Partizione *-- "0..n" Partizione : figli

I due percorsi producono modelli diversi perché le due risposte del servizio sono strutturalmente diverse: DettaglioAtto porta testo e commi di una versione, AttoStorico porta l'albero dell'articolato di tutte. Il confronto sta in com'è fatto il servizio.

Il testo di un atto o di un articolo

DettaglioAtto dataclass

DettaglioAtto(
    estremi: EstremiAtto,
    gazzetta: PubblicazioneGazzetta,
    titolo: str,
    sottotitolo: str | None,
    testo_html: str,
    finestra: FinestraVigenza | None,
)

Il testo di un atto o di un articolo a un punto nel tempo.

Porta il testo già separato dalle note redazionali, i commi numerati, la finestra di vigenza in cui quel testo è valido, le coordinate di Gazzetta e il permalink alla pagina pubblica. Le proprietà che leggono l'HTML del servizio lo fanno alla prima richiesta e tengono il risultato.

testo property

testo: str

Il solo testo, senza le note redazionali di aggiornamento.

commi property

commi: tuple[Comma, ...]

I commi numerati, quando l'articolo è marcato come tale.

note_aggiornamento property

note_aggiornamento: str | None

Le note redazionali sulle modifiche a questo testo, se presenti.

preambolo property

preambolo: str | None

La formula introduttiva, quando la risposta la include.

commi_presenti property

commi_presenti: int | None

Quanti commi sono arrivati, o None se il testo non ne ha.

ultimo_comma_numerato property

ultimo_comma_numerato: int | None

L'etichetta dell'ultimo comma numerato, o None se non ce n'è nessuno.

Non coincide con commi_presenti: un articolo può avere commi con etichette non numeriche, e sono le etichette a indicare dove il testo si ferma.

possibile_troncamento property

possibile_troncamento: bool

Indica se questo testo sembra troncato.

Il servizio conserva gli articoli lunghi a blocchi di cento commi e ne restituisce solo il primo, senza segnalarlo. Un articolo il cui ultimo comma numerato cade esattamente su un multiplo di cento è quindi sospetto. Un articolo che finisce davvero lì resta indistinguibile da uno troncato: per questo il valore esprime un sospetto, non una certezza.

urn property

urn: Urn

L'URN dell'atto a cui questo testo appartiene.

permalink: str

Il link pubblico di Normattiva, per verificare sulla fonte.

attribuzione property

attribuzione: str

La riga di attribuzione richiesta dalla licenza.

Comma dataclass

Comma(numero: str, testo: str)

Un comma numerato di un articolo.

Le coordinate di un atto

EstremiAtto dataclass

EstremiAtto(
    denominazione: str,
    data: date,
    numero: str | None = None,
    codice_tipo: str | None = None,
)

Gli estremi che identificano un provvedimento: tipo, data e numero.

ha_urn property

ha_urn: bool

Indica se per questo tipo di atto si sa comporre l'URN.

Da verificare prima di leggere urn scorrendo risultati di ricerca: dodici tipi di atto su trenta, quasi tutti storici, non hanno una forma NIR verificata, e per quelli urn solleva un'eccezione invece di indovinare.

urn property

urn: Urn

L'URN che identifica questo atto.

Solleva InvalidUrnError per i tipi di atto la cui forma URN non è stata verificata: un URN inventato otterrebbe un 404 dal servizio, e chi lo riceve non avrebbe modo di capire che il difetto è nell'URN. ha_urn permette di verificarlo in anticipo, senza sollevare.

Solleva:

Tipo Descrizione
InvalidUrnError

la forma URN di questo tipo di atto non è verificata.

citazione property

citazione: str

L'atto nella forma in cui si cita nella pratica giuridica italiana.

PubblicazioneGazzetta dataclass

PubblicazioneGazzetta(
    data: date,
    numero: int | None = None,
    codice_redazionale: str | None = None,
    supplemento: str | None = None,
    numero_supplemento: int | None = None,
)

Dove e quando un atto è stato pubblicato in Gazzetta Ufficiale.

numero è opzionale perché il servizio non lo fornisce ovunque: gli atti aggiornanti citati dentro un'esportazione hanno la data di Gazzetta ma non il numero, e in quel caso il campo resta None.

in_supplemento property

in_supplemento: bool

Se l'atto è uscito in un supplemento e non nella Gazzetta ordinaria.

FinestraVigenza dataclass

FinestraVigenza(inizio: date, fine: date | None = None)

Intervallo di tempo in cui una versione di un testo è stata in vigore.

aperta property

aperta: bool

Se questa è la versione tuttora in vigore.

contiene

contiene(giorno: date) -> bool

Indica se il giorno indicato cade dentro questa finestra.

Una finestra aperta, cioè senza fine, contiene ogni giorno a partire dal suo inizio.

Codice sorgente in src/normattiva/modelli.py
def contiene(self, giorno: date) -> bool:
    """Indica se il giorno indicato cade dentro questa finestra.

    Una finestra aperta, cioè senza fine, contiene ogni giorno a partire
    dal suo inizio.
    """
    return self.inizio <= giorno and (self.fine is None or giorno <= self.fine)

I risultati di una ricerca

EsitoRicerca dataclass

EsitoRicerca(
    atti: tuple[AttoTrovato, ...],
    totale: int,
    pagina: int = 1,
    pagine: int = 1,
    faccette: Faccette = Faccette(),
)

Una pagina di risultati di ricerca.

Non definisce __len__: non sarebbe chiaro se conta i risultati di questa pagina o quelli dell'intera ricerca, e un esito con la pagina vuota ma cinquemila atti in totale risulterebbe falso dentro un if. Si iterano gli atti di questa pagina, e si legge totale per il conteggio complessivo.

ultima_pagina property

ultima_pagina: bool

Indica se non ci sono altre pagine da chiedere.

AttoTrovato dataclass

AttoTrovato(
    estremi: EstremiAtto,
    gazzetta: PubblicazioneGazzetta,
    titolo: str,
    descrizione: str | None = None,
    ultima_modifica: date | None = None,
    atti_modificanti: tuple[str, ...] = (),
    evidenziazioni: tuple[Evidenziazione, ...] = (),
)

Un atto come restituito da una ricerca.

atti_modificanti class-attribute instance-attribute

atti_modificanti: tuple[str, ...] = ()

I codici redazionali degli ultimi atti che hanno modificato questo, come 26G00129.

Non sono URN né titoli: sono gli identificativi di Gazzetta degli atti modificanti, e per risalire all'atto serve anche la loro data, che il servizio qui non fornisce. Osservati il 2026-08-24 nel flusso degli atti aggiornati.

ha_urn property

ha_urn: bool

Indica se per questo atto si sa comporre l'URN: da verificare prima di leggerlo.

urn property

urn: Urn

L'URN con cui chiedere il testo di questo atto.

Solleva InvalidUrnError per i tipi di atto storici la cui forma NIR non è verificata: scorrendo i risultati conviene filtrare su ha_urn.

citazione property

citazione: str

L'atto nella forma in cui si cita nella pratica giuridica italiana.

Evidenziazione dataclass

Evidenziazione(
    articolo: str | None, frammenti: tuple[str, ...] = ()
)

Il punto in cui un termine di ricerca è stato trovato dentro un atto.

Faccette dataclass

Faccette(
    per_anno: tuple[Faccetta, ...] = (),
    per_tipo: tuple[Faccetta, ...] = (),
    per_emettitore: tuple[Faccetta, ...] = (),
)

Le tre faccette che la ricerca restituisce.

Faccetta dataclass

Faccetta(
    codice: str,
    conteggio: int,
    descrizione: str | None = None,
)

Un valore di una faccetta di ricerca, con quanti atti lo portano.

L'atto intero, dall'esportazione

AttoStorico dataclass

AttoStorico(
    urn: Urn,
    estremi: EstremiAtto,
    versioni: tuple[VersioneAtto, ...],
    eli: str | None = None,
    gazzetta: PubblicazioneGazzetta | None = None,
    abrogato: bool = False,
    aggiornamenti: tuple[Aggiornamento, ...] = (),
)

Un atto intero con tutte le versioni incluse nell'esportazione.

pubblicato_il property

pubblicato_il: date

La data da cui l'atto esiste: la data di Gazzetta, o in mancanza quella di emanazione.

originale property

originale: VersioneAtto | None

La versione originale dell'atto, se inclusa nell'export.

vigente property

vigente: VersioneAtto | None

La versione più recente inclusa nell'export.

Per un atto mai modificato è l'originale: non esiste un testo più recente di quello di pubblicazione.

attribuzione property

attribuzione: str

La riga di attribuzione richiesta dalla licenza.

alla_data

alla_data(giorno: date) -> VersioneAtto

Restituisce la versione dell'atto in vigore nel giorno indicato.

Prima della prima modifica vale il testo originale, che nell'export non ha una data di inizio: vale la data di pubblicazione dell'atto. Un atto mai modificato ha solo quella versione, valida senza limite di tempo.

Parametri:

Nome Tipo Descrizione Predefinito
giorno date

il giorno di cui si vuole il testo.

obbligatorio

Restituisce:

Tipo Descrizione
VersioneAtto

La versione in vigore quel giorno, con il suo articolato.

Solleva:

Tipo Descrizione
VersionNotFoundError

nessuna versione copre quel giorno, tipicamente perché è anteriore alla pubblicazione.

Codice sorgente in src/normattiva/modelli.py
def alla_data(self, giorno: date) -> VersioneAtto:
    """Restituisce la versione dell'atto in vigore nel giorno indicato.

    Prima della prima modifica vale il testo originale, che nell'export non
    ha una data di inizio: vale la data di pubblicazione dell'atto. Un atto
    mai modificato ha solo quella versione, valida senza limite di tempo.

    Args:
        giorno: il giorno di cui si vuole il testo.

    Returns:
        La versione in vigore quel giorno, con il suo articolato.

    Raises:
        VersionNotFoundError: nessuna versione copre quel giorno,
            tipicamente perché è anteriore alla pubblicazione.
    """
    datate = [
        (v.vigente_dal, v)
        for v in self.versioni
        if v.vigente_dal is not None and v.vigente_dal <= giorno
    ]
    if datate:
        return max(datate, key=lambda coppia: coppia[0])[1]
    originale = self.originale
    if originale is not None and giorno >= self.pubblicato_il:
        return originale
    raise VersionNotFoundError(giorno)

VersioneAtto dataclass

VersioneAtto(
    vigente_dal: date | None,
    articolato: tuple[Partizione, ...] = (),
    annessi: tuple[Partizione, ...] = (),
)

Una versione dell'atto, in vigore da una certa data in poi.

originale property

originale: bool

Indica se questa è la versione originale, come pubblicata la prima volta.

articoli

articoli() -> Iterator[Partizione]

Itera gli articoli di questa versione, in ordine.

Scende solo nell'articolato: gli allegati stanno in annessi, che è un ramo separato dell'atto.

Produce:

Tipo Descrizione
Partizione

Un articolo per volta, in ordine di lettura.

Codice sorgente in src/normattiva/modelli.py
def articoli(self) -> Iterator[Partizione]:
    """Itera gli articoli di questa versione, in ordine.

    Scende solo nell'articolato: gli allegati stanno in `annessi`, che è un
    ramo separato dell'atto.

    Yields:
        Un articolo per volta, in ordine di lettura.
    """
    for nodo in self.articolato:
        yield from nodo.articoli()

Partizione dataclass

Partizione(
    tipo: str | None,
    numero: str,
    testo: str,
    rubrica: str | None = None,
    finestre: tuple[FinestraVigenza, ...] = (),
    figli: tuple[Partizione, ...] = (),
)

Un nodo della struttura di un atto: un capo, un articolo, un allegato.

tipo contiene il nome NIR del nodo così come lo dichiara il servizio; per gli articoli vale la costante ARTICOLO.

articoli

articoli() -> Iterator[Partizione]

Itera gli articoli a partire da questo nodo, incluso il nodo stesso.

Produce:

Tipo Descrizione
Partizione

Ogni nodo il cui tipo è ARTICOLO, in ordine di lettura.

Codice sorgente in src/normattiva/modelli.py
def articoli(self) -> Iterator[Partizione]:
    """Itera gli articoli a partire da questo nodo, incluso il nodo stesso.

    Yields:
        Ogni nodo il cui `tipo` è `ARTICOLO`, in ordine di lettura.
    """
    if self.tipo == ARTICOLO:
        yield self
    for figlio in self.figli:
        yield from figlio.articoli()

Aggiornamento dataclass

Aggiornamento(
    data: date,
    testo: str,
    riferimenti: tuple[RiferimentoAggiornamento, ...] = (),
)

Una modifica subita dall'atto, come descritta dal servizio.

RiferimentoAggiornamento dataclass

RiferimentoAggiornamento(
    gazzetta: PubblicazioneGazzetta,
    articolo: str | None = None,
)

L'articolo che ha introdotto una modifica.

I dizionari del servizio

Tipologica dataclass

Tipologica(codice: str, descrizione: str)

Una voce di uno dei dizionari (tipologiche) del servizio.

Collection dataclass

Collection(
    name: str,
    format: str,
    total_atti: int,
    description: str | None = None,
    created_at: date | None = None,
)

Un archivio già confezionato messo a disposizione dal servizio.

RicercaPredefinita dataclass

RicercaPredefinita(
    nome: str, parametri: tuple[tuple[str, str], ...] = ()
)

Una ricerca predefinita suggerita dal servizio.

Le enumerazioni

Format

Bases: str, Enum

Formati in cui il servizio può produrre un'esportazione.

ExportMode

Bases: str, Enum

Quali versioni di un atto deve includere un'esportazione.

ClasseProvvedimento

Bases: IntEnum

Stato redazionale di un atto: mai aggiornato, aggiornato o abrogato.

Sort

Bases: str, Enum

L'ordinamento dei risultati di una ricerca.

NEWEST mette per primi gli atti più recenti, OLDEST i più antichi.

Le costanti

ATTRIBUZIONE module-attribute

ATTRIBUZIONE = "Fonte: Normattiva (https://www.normattiva.it), Istituto Poligrafico e Zecca dello Stato, in licenza CC BY 4.0. Testo non autentico e gratuito: l'unico testo ufficiale è quello pubblicato sulla Gazzetta Ufficiale a mezzo stampa."

La riga di attribuzione che la licenza dei dati richiede.

L'avviso legale del portale richiede tre menzioni: «La riproduzione dei testi forniti nel formato elettronico è consentita purché venga menzionata la fonte, il carattere non autentico e gratuito». La riga le contiene tutte e tre, e tests/test_licenza.py lo verifica, perché è facile accorciare un'attribuzione senza accorgersi di aver perso una menzione.

DENOMINAZIONI_URN module-attribute

DENOMINAZIONI_URN = {
    "COSTITUZIONE": "costituzione",
    "DECRETO": "decreto",
    "DECRETO DEL CAPO PROVVISORIO DELLO STATO": "decreto.del.capo.provvisorio.dello.status",
    "DECRETO DEL PRESIDENTE DEL CONSIGLIO DEI MINISTRI": "decreto.del.presidente.del.consiglio.dei.ministri",
    "DECRETO DEL PRESIDENTE DELLA REPUBBLICA": "decreto.del.presidente.della.repubblica",
    "DECRETO LEGISLATIVO": "decreto.legislativo",
    "DECRETO LEGISLATIVO LUOGOTENENZIALE": "decreto.legislativo.luogotenenziale",
    "DECRETO LEGISLATIVO PRESIDENZIALE": "decreto.legislativo.presidenziale",
    "DECRETO LUOGOTENENZIALE": "decreto.luogotenenziale",
    "DECRETO MINISTERIALE": "decreto.ministeriale",
    "DECRETO PRESIDENZIALE": "decreto.presidenziale",
    "DECRETO-LEGGE": "decreto.legge",
    "DELIBERAZIONE": "deliberazione",
    "LEGGE": "legge",
    "LEGGE COSTITUZIONALE": "legge.costituzionale",
    "ORDINANZA": "ordinanza",
    "REGIO DECRETO": "regio.decreto",
    "REGIO DECRETO LEGISLATIVO": "regio.decreto.legislativo",
}

Forma di ogni tipo di atto dentro un URN, verificata contro il servizio.

Sono le diciotto denominazioni su trenta per cui un URN così composto risponde davvero. Per le altre, quasi tutte tipologie storiche come «DECRETO DEL DUCE» o «REGOLAMENTO», la forma NIR non è nota: indovinarla porta a un 404 che sembra un difetto dell'atto e invece è un URN composto male. EstremiAtto.ha_urn permette di verificarlo in anticipo.

ABBREVIAZIONI module-attribute

ABBREVIAZIONI = {
    "COSTITUZIONE": "Cost.",
    "LEGGE": "L.",
    "LEGGE COSTITUZIONALE": "L. cost.",
    "DECRETO-LEGGE": "D.L.",
    "DECRETO LEGISLATIVO": "D.Lgs.",
    "DECRETO DEL PRESIDENTE DELLA REPUBBLICA": "D.P.R.",
    "DECRETO DEL PRESIDENTE DEL CONSIGLIO DEI MINISTRI": "D.P.C.M.",
    "DECRETO MINISTERIALE": "D.M.",
    "REGIO DECRETO": "R.D.",
    "REGIO DECRETO-LEGGE": "R.D.L.",
    "REGIO DECRETO LEGISLATIVO": "R.D.Lgs.",
}

Abbreviazione di ogni tipo di atto nelle citazioni.

Non coincide con DENOMINAZIONI_URN, e la differenza è voluta: abbreviare è una convenzione editoriale applicabile anche a un atto che questa libreria non sa indirizzare, come il regio decreto-legge, mentre comporre un URN richiede di conoscere la forma esatta che il servizio accetta.

Un tipo che qui non compare si cita per esteso, quindi EstremiAtto.citazione risponde per tutti.

ARTICOLO module-attribute

ARTICOLO = 'articolo'

Valore del campo tipo di Partizione per i nodi di tipo articolo.