La classe Property
est conçue pour être sous-classée.
Cependant, il est normalement plus facile de sous-classer une sous-classe Property
existante.
Le nom de tous les attributs spéciaux associés à Property
, même ceux considérés comme "publics", commence par un trait de soulignement.
En effet, StructuredProperty
utilise l'espace de noms d'attribut non souligné pour faire référence aux noms Property
imbriqués. Cette caractéristique est essentielle pour spécifier des requêtes sur des sous-propriétés.
La classe Property
et ses sous-classes prédéfinies permettent un sous-classement à l'aide des API de validation et de conversion composables (ou empilables). Celles-ci nécessitent les définitions terminologiques suivantes :
- Une valeur utilisateur correspond à une valeur définie et accessible par le code d'application à l'aide d'attributs standards sur l'entité.
- Une valeur de base correspond à une valeur sérialisée vers et désérialisée depuis le datastore.
Une sous-classe Property
qui met en œuvre une transformation spécifique entre des valeurs utilisateur et des valeurs sérialisables doit utiliser deux méthodes, _to_base_type()
et _from_base_type()
.
Celles-ci ne doivent pas appeler leur méthode super()
.
C'est ce que l'on entend par des API composables (ou empilables).
L'API accepte les classes d'empilage et utilise une complexité accrue dans le cadre des conversions des valeurs utilisateur/de base : la conversion des valeurs utilisateur vers des valeurs de base devient de moins en moins complexe, tandis que la conversion des valeurs de base vers des valeurs utilisateur devient de plus en plus complexe. Par exemple, vous pouvez vous référer à la relation entre BlobProperty
, TextProperty
et StringProperty
.
En effet, TextProperty
hérite de BlobProperty
; son code est assez simple, car il hérite de la plupart des comportements dont il a besoin.
Outre _to_base_type()
et _from_base_type()
, la méthode _validate()
est également une API composable.
L'API de validation fait la distinction entre les valeurs utilisateur laxistes et strictes. L'ensemble de valeurs laxistes correspond à un sur-ensemble de l'ensemble de valeurs strictes. La méthode _validate()
utilise une valeur laxiste et la convertit en valeur stricte si nécessaire. Cela signifie que lors de la définition de la valeur de propriété, les valeurs laxistes sont acceptées, tandis que lors de l'obtention de la valeur de propriété, seules les valeurs strictes sont renvoyées. Si aucune conversion n'est nécessaire, la méthode _validate()
peut renvoyer la valeur "None". Si l'argument se situe en dehors de l'ensemble des valeurs laxistes acceptées, _validate()
doit générer une exception, de préférence TypeError
ou datastore_errors.BadValueError
.
Les éléments _validate()
, _to_base_type()
et _from_base_type()
n'ont pas besoin de gérer les éléments suivants :
None
: Ils ne seront pas appelés avecNone
(et s'ils renvoient "None", cela signifie que la valeur n'a pas besoin d'être convertie).- Valeurs répétées : L'infrastructure se charge d'appeler
_from_base_type()
ou_to_base_type()
pour chaque élément de liste dans une valeur répétée. - Distinction entre les valeurs utilisateur et les valeurs de base : l'infrastructure s'en charge en appelant les API composables.
- Comparaisons : Les opérations de comparaison appellent
_to_base_type()
leur opérande. - Distinction entre les valeurs utilisateur et les valeurs de base : l’infrastructure garantit que
_from_base_type()
est appelé avec une valeur de base (désencapsulée) et que_to_base_type()
est appelé avec une valeur utilisateur.
Par exemple, supposons que vous ayez besoin de stocker des entiers très longs.
L'élément standard IntegerProperty
n'accepte que les entiers de 64 bits signés.
La propriété peut stocker un entier plus long sous forme de chaîne ; il serait donc préférable que la classe de propriété gère la conversion.
Une application utilisant la classe de propriété peut ressembler à ceci :
...
...
...
...
Cette méthode semble simple et directe. Elle illustre également l'utilisation de certaines options de propriétés standards (par défaut, répétées) ; en tant qu'auteur de LongIntegerProperty
, vous n'avez pas besoin de rédiger de "code récurrent" pour les faire fonctionner. Il est plus facile de définir une sous-classe associée à une autre propriété, par exemple :
Lorsque vous définissez une valeur de propriété sur une entité, par exemple ent.abc = 42
, votre méthode _validate()
est appelée et, si elle ne génère pas d'exception, la valeur est stockée sur l'entité. Lorsque vous écrivez l'entité dans le datastore, la méthode _to_base_type()
est appelée et convertit la valeur en chaîne. Cette valeur est ensuite sérialisée par la classe de base, StringProperty
.
La chaîne d'événements inverse se produit lorsque l'entité est lue depuis le datastore. Les classes StringProperty
et Property
se chargent des autres détails, tels que la sérialisation et la désérialisation de la chaîne, la définition de la valeur par défaut et la gestion des valeurs de propriété répétées.
Dans cet exemple, la gestion des inégalités (requêtes utilisant <, <=, > et >=) nécessite davantage de travail. L'exemple de mise en œuvre suivant impose une taille maximale du nombre entier et stocke les valeurs sous forme de chaînes à longueur fixe :
Ceci peut être utilisé de la même manière que LongIntegerProperty
, sauf que vous devez transmettre le nombre de bits au constructeur de la propriété, par exemple BoundedLongIntegerProperty(1024)
.
Vous pouvez sous-classer d'autres types de propriétés de manière similaire.
Cette approche fonctionne également pour stocker des données structurées.
Supposons que vous ayez une classe Python FuzzyDate
qui représente une plage de dates. celle-ci utilise les champs first
et last
pour stocker le début et la fin de la plage de dates :
...
Vous pouvez créer une classe FuzzyDateProperty
dérivée de StructuredProperty
. Malheureusement, cette classe ne fonctionne pas avec les anciennes classes Python de base ; une sous-classe Model
doit lui être associée.
Définissez donc une sous-classe Model comme une représentation intermédiaire.
Créez ensuite une sous-classe de StructuredProperty
qui code en dur l'argument "modelclass" en FuzzyDateModel
et définit les méthodes _to_base_type()
et _from_base_type()
à convertir entre FuzzyDate
et FuzzyDateModel
:
Une application peut utiliser cette classe comme suit :
...
Supposons que vous souhaitiez accepter des objets date
de base en plus des objets FuzzyDate
en tant que valeurs pour FuzzyDateProperty
. Pour ce faire, modifiez la méthode _validate()
_validate() comme suit :
Vous pouvez plutôt sous-classer FuzzyDateProperty
comme suit (en supposant que FuzzyDateProperty._validate()
se présente comme indiqué ci-dessus).
Lorsque vous affectez une valeur à un champ MaybeFuzzyDateProperty
, MaybeFuzzyDateProperty._validate()
et FuzzyDateProperty._validate()
sont tous deux appelés dans cet ordre.
Il en va de même pour _to_base_type()
et _from_base_type()
: les méthodes incluses dans la superclasse et la sous-classe sont combinées de manière implicite.
(N'utilisez pas super
pour contrôler le comportement hérité à cette fin.
Dans le cas de ces trois méthodes, l'interaction est subtile et super
ne fait pas ce que vous voulez).