La classe Property
è progettata per essere sottoclasse.
Tuttavia, di solito è più semplice creare una sottoclasse
di una sottoclasse Property
esistente.
Tutti gli attributi Property
speciali, anche quelli considerati "pubblici", hanno nomi che iniziano con un trattino basso.
Questo perché StructuredProperty
utilizza lo spazio dei nomi dell'attributo non trattino basso per fare riferimento ai nomi Property
nidificati; ciò è essenziale per specificare le query sulle
proprietà secondarie.
La classe Property
e le sue sottoclassi predefinite consentono
di creare sottoclassi utilizzando le API di convalida e conversione componibili (o impilabili). Ciò richiede alcune definizioni terminologiche:
- Un valore utente è un valore che viene impostato e utilizzato dal codice dell'applicazione utilizzando gli attributi standard dell'entità.
- Un valore di base è un valore che verrebbe serializzato e deserializzato da Datastore.
Una sottoclasse Property
che implementa una trasformazione specifica tra i valori utente e i valori serializzabili deve implementare due metodi: _to_base_type()
e _from_base_type()
.
che non devono chiamare il loro metodo super()
.
Ecco cosa si intende per API componibili (o sovrapponibili).
L'API supporta classi stack con conversioni della base utenti sempre più sofisticate: la conversione da utente a base passa da più sofisticata a meno sofisticata, mentre la conversione base-utente da meno sofisticata a più sofisticata. Ad esempio, visualizza la relazione tra
BlobProperty
, TextProperty
e StringProperty
.
Ad esempio, TextProperty
eredita da BlobProperty
; il suo codice è piuttosto semplice perché eredita la maggior parte del comportamento di cui ha bisogno.
Oltre a _to_base_type()
e
_from_base_type()
, il
metodo _validate()
è anche un'API componibile.
L'API di convalida fa distinzione tra i valori utente lax e strict. L'insieme di valori lax è un soprainsieme dell'insieme di valori rigidi. Il metodo _validate()
accetta un valore lax e, se necessario, lo converte in un valore fisso. Ciò significa che quando si imposta il
valore della proprietà, vengono accettati valori lax, mentre quando si ottiene il
valore della proprietà vengono restituiti solo i valori rigidi. Se non è necessaria
alcuna conversione, _validate()
potrebbe restituire None. Se l'argomento non rientra nell'insieme di valori lax accettati, _validate()
deve generare un'eccezione, preferibilmente TypeError
o datastore_errors.BadValueError
.
_validate()
, _to_base_type()
e _from_base_type()
non devono gestire:
None
: non verranno richiamate conNone
(e se restituisce None, significa che il valore non ha bisogno di conversione).- Valori ripetuti: l'infrastruttura si occupa di chiamare
_from_base_type()
o_to_base_type()
per ogni elemento dell'elenco in un valore ripetuto. - Distinguere i valori utente dai valori di base: l'infrastruttura gestisce questa situazione chiamando le API componibili.
- Confronti: le operazioni di confronto chiamano
_to_base_type()
sull'operando. - Distinguere tra valori utente e valori base: l'infrastruttura garantisce che
_from_base_type()
verrà chiamato con un valore di base (senza wrapping) e che_to_base_type()
verrà chiamato con un valore utente.
Ad esempio, supponiamo che tu debba archiviare numeri interi molto lunghi.
Lo standard IntegerProperty
supporta solo numeri interi
a 64 bit (con segno).
La proprietà potrebbe memorizzare un numero intero più lungo come stringa; sarebbe utile che la classe della proprietà gestisca la conversione.
Un'applicazione che utilizza la classe di proprietà potrebbe avere il seguente aspetto:
...
...
...
...
La procedura è semplice e immediata. Dimostra inoltre l'utilizzo di alcune opzioni di proprietà standard (valore predefinito, ripetuto); in qualità di autore di LongIntegerProperty
, sarai felice di sapere che non devi scrivere alcun "boilerplate" per farle funzionare. È più facile definire una sottoclasse di un'altra proprietà, ad esempio:
Quando imposti un valore della proprietà su un'entità, ad esempio ent.abc = 42
, viene chiamato il metodo _validate()
e (se non viene generata un'eccezione) il valore viene archiviato nell'entità. Quando scrivi l'entità in Datastore,
viene chiamato il metodo _to_base_type()
, convertendo il
valore nella stringa. Questo valore viene serializzato dalla classe base,
StringProperty
.
La catena inversa di eventi si verifica quando l'entità viene letta da Datastore. Le classi StringProperty
e Property
insieme si occupano degli altri dettagli, ad esempio la serializzazione e la deserializzazione della stringa, l'impostazione del valore predefinito e la gestione dei valori delle proprietà ripetute.
In questo esempio, supportare le disuguaglianze (ovvero query che usano <, <=, >, >=) richiede più lavoro. La seguente implementazione di esempio impone una dimensione massima per il numero intero e archivia i valori come stringhe di lunghezza fissa:
Questa funzione può essere usata allo stesso modo di LongIntegerProperty
,
ad eccezione del fatto che devi passare il numero di bit al costruttore della proprietà,
ad esempio BoundedLongIntegerProperty(1024)
.
Puoi creare una sottoclasse di altri tipi di proprietà in modo simile.
Questo approccio funziona anche per l'archiviazione di dati strutturati.
Supponi di avere una classe Python FuzzyDate
che rappresenta un intervallo di date, che utilizza i campi first
e last
per archiviare l'inizio e la fine dell'intervallo di date:
...
Puoi creare un elemento FuzzyDateProperty
che deriva da
StructuredProperty
. Sfortunatamente, quest'ultima non funziona con le semplici classi Python precedenti; richiede una sottoclasse Model
.
Quindi definisci una sottoclasse del modello come rappresentazione intermedia;
Quindi, costruisci una sottoclasse StructuredProperty
che imposti come hardcoded l'argomento modelclass in FuzzyDateModel
e definisca i metodi _to_base_type()
e
_from_base_type()
per convertire tra FuzzyDate
e
FuzzyDateModel
:
Un'applicazione può utilizzare questa classe nel seguente modo:
...
Supponi di voler accettare oggetti date
semplici in aggiunta agli oggetti FuzzyDate
come valori per FuzzyDateProperty
. Per farlo, modifica il metodo _validate()
come segue:
Puoi optare per la sottoclasse FuzzyDateProperty
come segue
(supponendo che FuzzyDateProperty._validate()
sia come mostrato sopra).
Quando assegni un valore a un campo MaybeFuzzyDateProperty
, vengono richiamati sia MaybeFuzzyDateProperty._validate()
sia FuzzyDateProperty._validate()
in questo ordine.
Lo stesso vale per _to_base_type()
e
_from_base_type()
: i metodi nella
superclasse e nella sottoclasse sono implicitamente combinati.
Non usare super
per controllare il comportamento ereditato per questa operazione.
Per questi tre metodi,
l'interazione è minima e super
non fa quello che vuoi.)