הכיתה Property מיועדת להיות תת-כיתה.
עם זאת, בדרך כלל קל יותר ליצור מחלקת משנה של מחלקת משנה קיימת
Property.
כל המאפיינים המיוחדים Property, כולל אלה שנחשבים 'ציבוריים', מתחילים בקו תחתון.
הסיבה לכך היא שב-StructuredProperty נעשה שימוש במרחב השמות של המאפיינים ללא קו תחתון כדי להתייחס לשמות של Property מקוננים. זה חיוני לציון שאילתות במאפייני משנה.
המחלקות Property והמחלקות המשניות המוגדרות מראש שלה מאפשרות ליצור מחלקות משניות באמצעות ממשקי API של המרות ואימות שניתנים להרכבה (או להערמה). כדי להבין את המונחים האלה, צריך להגדיר אותם:
- ערך משתמש הוא ערך שמוגדר ומתבצעת אליו גישה על ידי קוד האפליקציה באמצעות מאפיינים רגילים בישות.
- ערך בסיס הוא ערך שניתן לסדר אותו בפורמט סדרתי ל-Datastore ולבטל את הסדר שלו מ-Datastore.
מחלקת משנה Property שמטמיעה טרנספורמציה ספציפית בין ערכי משתמשים לבין ערכים שניתנים לסריאליזציה צריכה להטמיע שתי שיטות, _to_base_type() ו-_from_base_type().
הן לא צריכות להפעיל את ה-method super() שלהן.
זו המשמעות של ממשקי API שניתנים להרכבה (או להוספה).
ה-API תומך בהוספת מחלקות עם המרות מתוחכמות יותר ויותר של בסיס המשתמשים: ההמרה 'משתמש לבסיס' הופכת ממתוחכמת יותר למתוחכמת פחות, ואילו ההמרה 'בסיס למשתמש' הופכת ממתוחכמת פחות למתוחכמת יותר. לדוגמה, אפשר לראות את הקשר בין BlobProperty, TextProperty ו-StringProperty.
לדוגמה, TextProperty יורש מ-
BlobProperty. הקוד שלו פשוט למדי כי הוא יורש את רוב ההתנהגות שהוא צריך.
בנוסף ל-_to_base_type() ול-_from_base_type(), גם ה-method _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, תשמח לשמוע שלא צריך לכתוב קוד מוכן מראש כדי שהן יפעלו. קל יותר להגדיר מחלקת משנה של נכס אחר, לדוגמה:
כשמגדירים ערך של מאפיין בישות, למשל:
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 לא עושה את מה שאתם רוצים).