uuid - Identificatori Universalmente Univoci

Scopo: Implementa gli Identificatori Universalmente Univoci (Universally Unique Identifiers) come descritti in RFC 4122

RFC 4122 definisce un sistema per creare Identificatori Universalmente Univoci (UUID) per risorse in modo che non richieda mantenerne traccia tramite la gestione di un archivio centralizzato. I valori UUID sono lunghi 128 bit e come recita la guida di riferimento, "possono garantire l'univocità attraverso lo spazio e il tempo". Sono utili per generare identificatori per documenti, host, applicazioni client, e altre situazioni nelle quali sia necessario un valore univoco. La RFC è in particolare orientata verso la creazione di uno spazio dei nomi chiamato "Uniform Resource Name" e copre tre algoritmi principali:

  • Uso degli indirizzi IEEE 802 MAC come fonte di univocità
  • Uso dei numeri pseudo-casuali
  • Uso di stringhe note in combinazione con hashing crittografico

In tutti i casi il valore di origine viene combinato con il clock di sistema e una sequenza di valori di clock usati per mantenere univocità nel caso che il clock sia messo indietro.

UUID 1 - Indirizzi IEEE 802 MAC

Nella versione UUID 1 valori sono calcolati usando l'indirizzo MAC dell'host. Il modulo uuid usa getnode() per recuperare il valore MAC del sistema corrente:

# uuid_getnode.py

import uuid

print(hex(uuid.getnode()))

Se un sistema ha più di una scheda di rete, quindi più di un MAC può essere ritornato uno qualsiasi dei valori.

$ python3 uuid_getnode.py

0xf46d0462e951

Per generare un UUID per un dato host, identificato dal suo indirizzo MAC, si usa la funzione uuid1(). L'identificatore di nodo è opzionale, si lasci il campo vuoto per usare il valore restituito da getnode().

# uuid_uuid1.py

import uuid

u = uuid.uuid1()

print(u)
print(type(u))
print('bytes   :', repr(u.bytes))
print('hex     :', u.hex)
print('int     :', u.int)
print('urn     :', u.urn)
print('variant :', u.variant)
print('version :', u.version)
print('fields  :', u.fields)
print('  time_low            : ', u.time_low)
print('  time_mid            : ', u.time_mid)
print('  time_hi_version     : ', u.time_hi_version)
print('  clock_seq_hi_variant: ', u.clock_seq_hi_variant)
print('  clock_seq_low       : ', u.clock_seq_low)
print('  node                : ', u.node)
print('  time                : ', u.time)
print('  clock_seq           : ', u.clock_seq)

I componenti dell'oggetto UUID restituito possono essere indirizzati tramite degli attributi di istanza a sola lettura. Alcuni attributi, come hex, int ed urn sono diverse rappresentazioni del valore UUID.

$ python3 uuid_uuid1.py
0dca8244-f9f7-11e6-8f16-f46d0462e951
<class 'uuid.UUID'>
bytes   : b'\r\xca\x82D\xf9\xf7\x11\xe6\x8f\x16\xf4m\x04b\xe9Q'
hex     : 0dca8244f9f711e68f16f46d0462e951
int     : 18331450088751923872094499947564689745
urn     : urn:uuid:0dca8244-f9f7-11e6-8f16-f46d0462e951
variant : specified in RFC 4122
version : 1
fields  : (231375428, 63991, 4582, 143, 22, 268749062203729)
  time_low            :  231375428
  time_mid            :  63991
  time_hi_version     :  4582
  clock_seq_hi_variant:  143
  clock_seq_low       :  22
  node                :  268749062203729
  time                :  137071678164992580
  clock_seq           :  3862

A causa della componente temporale, ogni chiamata ad uuid1() restituisce un nuovo valore.

# uuid_uuid1_repeat.py

import uuid

for i in range(3):
    print(uuid.uuid1())

Nell'output cambia solo la componente temporale (all'inizio della stringa).

$ python3 uuid_uuid1_repeat.py

65275ca6-f9f7-11e6-8f16-f46d0462e951
65275ca7-f9f7-11e6-8f16-f46d0462e951
65275ca8-f9f7-11e6-8f16-f46d0462e951

Visto ogni computer ha il proprio indirizzo MAC, quando si esegue il programma di esempio su sistemi differenti, i valori saranno completamente diversi. Questo esempio passa esplicitamente l'identificatore di nodo per simulare l'esecuzione su host differenti.

# uuid_uuid1_othermac.py

import uuid

for node in [0x1ec200d9e0, 0x1e5274040e]:
    print(uuid.uuid1(node), hex(node))

Oltre a diversi valori temporali, cambia anche l'identificatore di nodo alla fine dell'UUID.

$ python3 uuid_uuid1_othermac.py

ded265e8-f9f7-11e6-8e61-001ec200d9e0 0x1ec200d9e0
ded26912-f9f7-11e6-ada2-001e5274040e 0x1e5274040e

UUID 3 e 5 - Valori Basati sui Nomi

E' anche utile, in certi contesti, creare valori UUID da nomi invece che da valori casuali o basati sul tempo. Le versioni 3 e 5 delle specifiche UUID usano valori hash crittografici (MD5 oppure SHA-1) per combinare valori di origine specifici dello spazio dei nomi con "nomi". Ci sono diversi spazi dei nomi ben noti, identificati da valori UUID predefiniti, per lavorare con DNS, URL, ISO OID ed X.500 Distinguished Names. Si possono anche definire nuovi spazi dei nomi specifici per una applicazione generando e salvando i valori UUID.

# uuid_uuid3_uuid5.py

import uuid

hostnames = ['www.doughellmann.com', 'blog.doughellmann.com']

for name in hostnames:
    print(name)
    print('  MD5   :', uuid.uuid3(uuid.NAMESPACE_DNS, name))
    print('  SHA-1 :', uuid.uuid5(uuid.NAMESPACE_DNS, name))

Per creare un UUID da un nome DNS, si passi uuid.NAMESPACE_DNS come argomento namespace per uuid3() oppure uuid5().

$ python3 uuid_uuid3_uuid5.py

www.doughellmann.com
  MD5   : bcd02e22-68f0-3046-a512-327cca9def8f
  SHA-1 : e3329b12-30b7-57c4-8117-c2cd34a87ce9
blog.doughellmann.com
  MD5   : 9bdabfce-dfd6-37ab-8a3f-7f7293bcf111
  SHA-1 : fa829736-7ef8-5239-9906-b4775a5abacb

Il valore UUID per un dato nome in uno spazio dei nomi è sempre lo stesso, non importa quando o dove esso viene calcolato.

# uuid_uuid3_repeat.py

import uuid

namespace_types = sorted(
    n
    for n in dir(uuid)
    if n.startswith('NAMESPACE_')
)
name = 'www.doughellmann.com'

for namespace_type in namespace_types:
    print(namespace_type)
    namespace_uuid = getattr(uuid, namespace_type)
    print(' ', uuid.uuid3(namespace_uuid, name))
    print(' ', uuid.uuid3(namespace_uuid, name))
    print()

I valori per lo stesso nome negli spazi dei nomi sono diversi.

$ python3 uuid_uuid3_repeat.py

NAMESPACE_DNS
  bcd02e22-68f0-3046-a512-327cca9def8f
  bcd02e22-68f0-3046-a512-327cca9def8f

NAMESPACE_OID
  e7043ac1-4382-3c45-8271-d5c083e41723
  e7043ac1-4382-3c45-8271-d5c083e41723

NAMESPACE_URL
  5d0fdaa9-eafd-365e-b4d7-652500dd1208
  5d0fdaa9-eafd-365e-b4d7-652500dd1208

NAMESPACE_X500
  4a54d6e7-ce68-37fb-b0ba-09acc87cabb7
  4a54d6e7-ce68-37fb-b0ba-09acc87cabb7

UUID 4 - Valori Casuali

Talvolta i valori UUID basati sull'host e sullo spazio dei nomi sono sono "diversi abbastanza". Ad esempio, in casi dove si voglia usare un UUID come chiave di hash, è desiderabile una sequenza di valori più casuale con più differenziazioni per evitare collisioni nella hash table. Il disporre di valori con meno cifre comuni rende inoltre più semplice trovarli nei file di registro. Per aggiungere una differenziazione maggiore nel proprio UUID, si usa uuid4() per generare i valori usando dati in input casuali.

# uuid_uuid4.py

import uuid

for i in range(3):
    print(uuid.uuid4())

La sorgente della casualità dipende da quali librerie C sono disponibili quando viene importato uuid. Se libuuid (oppure uuid.dll) possono essere caricate e contengono una funzione per generare valori casuali, essa viene usata. Altrimenti viene usato os.urandom() oppure il modulo random.

$ python3 uuid_uuid4.py

4a9122e8-dbd2-4706-bbab-6982073fe23e
2138606e-42bb-4f69-a325-b4595487a133
6a17d800-ba00-4691-9a36-f3be9e92b2fb

Lavorare con Oggetti UUID

Oltre a generare nuovi valori UUID, si possono anche elaborare stringhe in formati standard per creare oggetti UUID. Questo rende facile la gestione di operazioni di confronto e ordinamento.

# uuid_uuid_objects.py

import uuid


def show(msg, l):
    print(msg)
    for v in l:
        print(' ', v)
    print()

input_values = [
    'urn:uuid:f2f84497-b3bf-493a-bba9-7c68e6def80b',
    '{417a5ebb-01f7-4ed5-aeac-3d56cd5037b0}',
    '2115773a-5bf1-11dd-ab48-001ec200d9e0',
]

show('valori in input', input_values)

uuids = [uuid.UUID(s) for s in input_values]
show('convertiti in uuid', uuids)

uuids.sort()
show('ordinati', uuids)

Le parentesi graffe vengono rimosse dall'input, così come i trattini (-). Se la stringa ha un prefisso che contiene urn: e/o uuid: viene anch'esso rimosso. Il testo rimanente deve essere una stringa di 16 cifre esadecimali, che saranno interpretate come valore UUID.

$ python3 uuid_uuid_objects.py

valori in input
  urn:uuid:f2f84497-b3bf-493a-bba9-7c68e6def80b
  {417a5ebb-01f7-4ed5-aeac-3d56cd5037b0}
  2115773a-5bf1-11dd-ab48-001ec200d9e0

convertiti in uuid
  f2f84497-b3bf-493a-bba9-7c68e6def80b
  417a5ebb-01f7-4ed5-aeac-3d56cd5037b0
  2115773a-5bf1-11dd-ab48-001ec200d9e0

ordinati
  2115773a-5bf1-11dd-ab48-001ec200d9e0
  417a5ebb-01f7-4ed5-aeac-3d56cd5037b0
  f2f84497-b3bf-493a-bba9-7c68e6def80b

Vedere anche:

uuid
La documentazione della libreria standard per questo modulo
Note di portabilità per uuid
RFC 4122
Uno spazio dei nomi URN per Universally Unique IDentifiers (UUID)