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