La clase Property
está diseñada para subclasificarse.
Sin embargo, por lo general, es más fácil subclasificar una subclase Property
existente.
Todos los atributos de Property
especiales, incluso aquellos considerados “públicos”, tienen nombres que comienzan con un guion bajo.
Esto se debe a que StructuredProperty
utiliza el espacio de nombres de atributos sin guion bajo para referirse a nombres de Property
anidados; esto es esencial para especificar consultas sobre subpropiedades.
La clase Property
y sus subclases predefinidas permiten crear subclases con las API de conversión y validación acoplables (o apilables). Estas requieren algunas definiciones de terminología:
- Un valor de usuario es un valor como el código de la aplicación que se establecerá y accederá mediante los atributos estándar de la entidad.
- Un valor base es un valor que se serializará y deserializará desde el almacén de datos.
Una subclase Property
que implementa una transformación específica entre valores de usuario y valores serializables debe implementar dos métodos, _to_base_type()
y _from_base_type()
.
Estos no deben llamar al método super()
.
Esto es lo que se entiende por API acoplables (o apilables).
La API admite clases de apilado con conversiones de usuario cada vez más sofisticadas: la conversión de usuario a base va de más sofisticada a menos sofisticada, mientras que la conversión de base a usuario va de menos sofisticada a más sofisticada. Por ejemplo, mira la relación entre BlobProperty
, TextProperty
y StringProperty
.
Por ejemplo, TextProperty
se hereda de BlobProperty
; su código es bastante simple porque hereda la mayor parte del comportamiento que necesita.
Además de _to_base_type()
y _from_base_type()
, el método _validate()
también es una API acoplable.
La API de validación distingue entre valores de usuario estrictos y laxos. El conjunto de valores laxos es un superconjunto del conjunto de valores estrictos. El método _validate()
toma un valor laxo y, si es necesario, lo convierte en un valor estricto. Esto significa que, cuando se establece el valor de la propiedad, se aceptan valores laxos, mientras que, cuando se obtiene el valor de la propiedad, solo se mostrarán los valores estrictos. Si no se necesita conversión, _validate()
puede mostrar None. Si el argumento está fuera del conjunto de valores laxos aceptados, _validate()
debe generar una excepción, preferentemente TypeError
o datastore_errors.BadValueError
.
_validate()
, _to_base_type()
y _from_base_type()
no necesitan controlar lo siguiente:
None
: no se llamarán conNone
(y si muestran Ninguna, esto significa que el valor no necesita conversión).- Valores repetidos: La infraestructura se encarga de llamar a
_from_base_type()
o_to_base_type()
para cada elemento de la lista en un valor repetido. - Distinguir los valores de usuario de los valores básicos: la infraestructura controla esto llamando a las API compuestas.
- Comparaciones: Las operaciones de comparación llaman a
_to_base_type()
en su operando. - Distinción entre los valores de usuario y base: la infraestructura garantiza que
_from_base_type()
se llamará con un valor de base (sin envolver), y que a_to_base_type()
se llamará con un valor de usuario.
Por ejemplo, imagina que necesitas almacenar enteros realmente largos.
IntegerProperty
estándar solo admite enteros de 64 bits (firmados).
Tu propiedad puede almacenar un número entero tan largo como una string; sería bueno que la clase de propiedad controlara la conversión.
Una aplicación que use tu clase de propiedad podría presentar este aspecto
...
...
...
...
Esto parece simple y directo. También demuestra el uso de algunas opciones de propiedad estándar (predeterminado, repetido) como autor de LongIntegerProperty
, te alegrará saber que no tienes que escribir ningún “código estándar” para que funcionen. Es más fácil definir una subclase de otra propiedad, por ejemplo:
Cuando configuras un valor de propiedad en una entidad, p. ej., ent.abc = 42
, se llama a tu método _validate()
y (si no genera una excepción) el valor se almacena en la entidad. Cuando escribes la entidad en Datastore, se llama a tu método _to_base_type()
y se convierte el valor en la string. Entonces ese valor se serializa por la clase base, StringProperty
.
La cadena inversa de eventos ocurre cuando la entidad se lee desde Datastore. Las clases StringProperty
y Property
se ocupan de los demás detalles, como la serialización y deserialización de la string, la configuración predeterminada y el control de valores de propiedad repetidos.
En este ejemplo, admitir desigualdades (es decir, consultas que utilizan <, <=, >, >=) requiere más trabajo. La siguiente implementación de ejemplo impone un tamaño máximo de número entero y almacena valores como strings de longitud fija:
Se puede usar de la misma manera que LongIntegerProperty
, con la excepción de que debes pasar la cantidad de bits al constructor de propiedades, p. ej., BoundedLongIntegerProperty(1024)
.
Puedes subclasificar otros tipos de propiedades de manera similar.
Este enfoque también funciona para almacenar datos estructurados.
Supongamos que tienes una clase de Python FuzzyDate
que representa un período, esta usa los campos first
y last
para almacenar el principio y el final del período:
...
Puedes crear una FuzzyDateProperty
que derive de StructuredProperty
. Desafortunadamente, esta última no funciona con clases antiguas de Python y necesita una subclase Model
.
Por lo tanto, define una subclase de modelo como una representación intermedia;
A continuación, crea una subclase de StructuredProperty
que codifique el argumento de clase de modelo para que sea FuzzyDateModel
y define los métodos _to_base_type()
y _from_base_type()
para convertir entre FuzzyDate
y FuzzyDateModel
:
Una aplicación podría usar esta clase así:
...
Supongamos que deseas aceptar objetos date
sin formato además de los objetos FuzzyDate
como valores para FuzzyDateProperty
. Para hacerlo, modifica el método _validate()
de la siguiente manera:
En su lugar, puedes subclasificar FuzzyDateProperty
de la siguiente manera (suponiendo que FuzzyDateProperty._validate()
es como se muestra arriba).
Cuando asignas un valor a un campo MaybeFuzzyDateProperty
, se invocan MaybeFuzzyDateProperty._validate()
y FuzzyDateProperty._validate()
, en ese orden.
Lo mismo se aplica a _to_base_type()
y _from_base_type()
: los métodos en superclase y subclase se combinan implícitamente.
(No uses super
para controlar el comportamiento heredado para esto.
En cuanto a estos tres métodos, la interacción es sutil y super
no lleva a cabo lo que quieres).