המחלקות Property מיועדות להיות מחלקות משנה.
עם זאת, בדרך כלל קל יותר ליצור מחלקת משנה של מחלקת משנה קיימת.Property
כל המאפיינים המיוחדים Property, כולל אלה שנחשבים 'ציבוריים', מתחילים בקו תחתון.
הסיבה לכך היא ש-StructuredProperty
משתמש במרחב שמות של מאפיינים ללא קו תחתון כדי להתייחס לשמות של Property בתוך מאפיינים; זה חיוני לציון שאילתות על מאפייני משנה.
המחלקות Property והמחלקות המשניות המוגדרות מראש שלה מאפשרות יצירת מחלקות משניות באמצעות ממשקי API של אימות והמרה שניתנים להרכבה (או להערמה). כדי להבין את המושגים האלה, צריך להגדיר כמה מונחים:
- ערך משתמש הוא ערך שמוגדר ומתבצעת אליו גישה על ידי קוד האפליקציה באמצעות מאפיינים רגילים בישות.
- ערך בסיס הוא ערך שניתן לבצע לו סריאליזציה אל מאגר הנתונים ולבטל את הסריאליזציה ממנו.
מחלקת משנה Property שמטמיעה טרנספורמציה ספציפית בין ערכי משתמשים לבין ערכים שניתנים לסריאליזציה צריכה להטמיע שתי שיטות, _to_base_type() ו-_from_base_type().
הן לא צריכות להפעיל את ה-method super() שלהן.
זו המשמעות של ממשקי API שניתנים להרכבה (או להערמה).
ה-API תומך ביצירת מקבצים של מחלקות עם המרות מתוחכמות יותר ויותר של בסיס המשתמשים: ההמרה ממשתמש לבסיס הופכת ממתוחכמת יותר לפחות מתוחכמת, בעוד שההמרה מבסיס למשתמש הופכת מפחות מתוחכמת למתוחכמת יותר. לדוגמה, אפשר לראות את הקשר בין BlobProperty, TextProperty ו-StringProperty.
לדוגמה, TextProperty יורש מ-BlobProperty; הקוד שלו די פשוט כי הוא יורש את רוב ההתנהגות שהוא צריך.
בנוסף ל-_to_base_type() ול-_from_base_type(), גם השיטה _validate() היא API שאפשר להרכיב ממנו פתרונות.
ממשק ה-API של האימות מבחין בין ערכי משתמש גמישים לבין ערכי משתמש מחמירים. קבוצת הערכים המותרים היא קבוצת-על של קבוצת הערכים המחמירים. השיטה _validate() מקבלת ערך לא מדויק, ואם צריך היא ממירה אותו לערך מדויק. כלומר, כשמגדירים את ערך המאפיין, מתקבלים ערכים לא מחמירים, אבל כשמקבלים את ערך המאפיין, מוחזרים רק ערכים מחמירים. אם לא נדרשת המרה, הפונקציה _validate() עשויה להחזיר None. אם הארגומנט
לא נמצא בקבוצת הערכים המותרים של lax,
הפונקציה _validate() צריכה להחזיר
חריגה, רצוי TypeError או
datastore_errors.BadValueError.
הדגלים _validate(), _to_base_type() ו-_from_base_type() לא צריכים לטפל ב:
-
None: הפונקציות האלה לא יופעלו עםNone(ואם הן מחזירות None, זה אומר שלא צריך להמיר את הערך). - ערכים חוזרים: התשתית דואגת להפעיל את הפונקציה
_from_base_type()או את הפונקציה_to_base_type()לכל פריט ברשימה של ערך חוזר. - הבחנה בין ערכי משתמשים לבין ערכי בסיס: התשתית מטפלת בזה באמצעות קריאה לממשקי ה-API הניתנים להרכבה.
- השוואות: פעולות ההשוואה קוראות ל-
_to_base_type()על האופרנד שלהן. - הבחנה בין ערכי משתמש לערכי בסיס: התשתית מבטיחה שהפונקציה
_from_base_type()תיקרא עם ערך בסיס (לא עטוף), ושהפונקציה_to_base_type()תיקרא עם ערך משתמש.
לדוגמה, נניח שאתם צריכים לאחסן מספרים שלמים ארוכים מאוד.
התקן IntegerProperty תומך רק במספרים שלמים (עם סימן) של 64 ביט.
יכול להיות שבנכס שלכם מאוחסן מספר שלם ארוך כמחרוזת. מומלץ שהמחלקה של הנכס תטפל בהמרה.
אפליקציה שמשתמשת במחלקת הנכסים שלכם יכולה להיראות כך:
...
...
...
...
זה נראה פשוט וברור. בנוסף, מוצגות דוגמאות לשימוש באפשרויות סטנדרטיות של מאפיינים (ברירת מחדל, חוזר); בתור המחבר של LongIntegerProperty, תשמח לשמוע שלא צריך לכתוב קוד boilerplate כדי שהן יפעלו. קל יותר להגדיר מחלקת משנה של מאפיין אחר, לדוגמה:
כשמגדירים ערך של מאפיין בישות, למשל:
ent.abc = 42, השיטה _validate()
מופעלת, ואם לא נוצר חריג, הערך מאוחסן בישות. כשכותבים את הישות ל-Datastore, השיטה _to_base_type() נקראת, והערך מומר למחרוזת. לאחר מכן הערך הזה עובר סריאליזציה על ידי מחלקת הבסיס, StringProperty.
השרשרת ההפוכה של האירועים מתרחשת כשקוראים את הישות בחזרה מ-Datastore. המחלקות StringProperty ו-Property מטפלות יחד בפרטים האחרים, כמו סדרת הנתונים של המחרוזת, הגדרת ברירת המחדל וטיפול בערכי מאפיינים חוזרים.
בדוגמה הזו, כדי לתמוך באי-שוויונים (כלומר, בשאילתות שמשתמשות בסימנים <, <=, >, >=) נדרשת עבודה נוספת. בדוגמה הבאה להטמעה מוגדר גודל מקסימלי למספר שלם, והערכים מאוחסנים כמחרוזות באורך קבוע:
אפשר להשתמש בו באותו אופן כמו ב-LongIntegerProperty
אלא שצריך להעביר את מספר הביטים לבונה המאפיינים, למשל BoundedLongIntegerProperty(1024).
אפשר ליצור מחלקת משנה של סוגי נכסים אחרים בדרכים דומות.
הגישה הזו מתאימה גם לאחסון נתונים מובנים.
נניח שיש לכם מחלקת Python FuzzyDate שמייצגת טווח תאריכים. המחלקה משתמשת בשדות first ו-last כדי לאחסן את תאריך ההתחלה ותאריך הסיום של טווח התאריכים:
...
אפשר ליצור FuzzyDateProperty שמבוסס על StructuredProperty. לצערנו, האפשרות השנייה לא פועלת עם מחלקות Python רגילות, אלא עם מחלקת משנה של Model.
לכן מגדירים מחלקה משנית של Model כייצוג ביניים.
בשלב הבא, יוצרים מחלקת משנה של StructuredProperty
שמקודדת את הארגומנט modelclass כ-FuzzyDateModel, ומגדירים את השיטות _to_base_type() ו-_from_base_type()
כדי להמיר בין FuzzyDate ל-FuzzyDateModel:
אפליקציה יכולה להשתמש במחלקה הזו באופן הבא:
...
נניח שאתם רוצים לקבל אובייקטים פשוטים של date בנוסף לאובייקטים של FuzzyDate כערכים של FuzzyDateProperty. כדי לעשות זאת, משנים את שיטת _validate()
ההגדרה באופן הבא:
במקום זאת, אפשר ליצור מחלקת משנה של FuzzyDateProperty באופן הבא (בהנחה ש-FuzzyDateProperty._validate()
הוא כמו שמוצג למעלה).
כשמקצים ערך לשדה MaybeFuzzyDateProperty, מופעלים גם MaybeFuzzyDateProperty._validate() וגם FuzzyDateProperty._validate(), בסדר הזה.
אותו עיקרון חל על _to_base_type() ו-_from_base_type(): ה-methods במחלקת האב ובמחלקה המשנית משולבים באופן מרומז.
(אל תשתמשו ב-super כדי לשלוט בהתנהגות שעוברת בירושה.
בשלוש השיטות האלה,
האינטראקציה עדינה וsuper לא עושה את מה שאתם רוצים).