הפניה למאפיין של ישות

‫Firestore במצב Datastore ‏ (Datastore) תומך במגוון סוגי נתונים של ערכי מאפיינים. לדוגמה:

  • מספרים שלמים
  • מספרים בשיטת נקודה צפה
  • מחרוזות
  • תאריכים
  • נתונים בינאריים

רשימה מלאה של הסוגים זמינה במאמר מאפיינים וסוגי ערכים.

מאפיינים וסוגי ערכים

ערכי הנתונים שמשויכים לישות מורכבים ממאפיין אחד או יותר. לכל מאפיין יש שם וערך אחד או יותר. למאפיין יכולים להיות ערכים מכמה סוגים, ולשתי ישויות יכולים להיות ערכים מסוגים שונים לאותו מאפיין. אפשר להוסיף מאפיינים לאינדקס או להסיר אותם מהאינדקס (שאילתות שממיינות או מסננות לפי מאפיין P יתעלמו מישויות שבהן המאפיין P לא מופיע באינדקס). לישות יכולים להיות לכל היותר 20,000 מאפיינים שעברו אינדוקס.

סוגי הערכים הנתמכים:

כששאילתה כוללת נכס עם ערכים מסוגים מעורבים, Datastore משתמש בסדר דטרמיניסטי שמבוסס על הייצוגים הפנימיים:

  1. ערכי Null
  2. מספרים עם נקודה קבועה
    • מספרים שלמים
    • תאריכים ושעות
  3. ערכים בוליאניים
  4. רצפים של בייטים
    • מחרוזת Unicode
    • מפתחות Blobstore
    • מפתחות של Datastore

מכיוון ש מחרוזות טקסט ארוכות ומחרוזות ארוכות של בייטים לא עוברות אינדוקס, לא מוגדר להן סדר.

סוגי נכסים

‫NDB תומך בסוגי המאפיינים הבאים:

סוג הנכס תיאור
IntegerProperty מספר שלם חתום ב-64 ביט
FloatProperty מספר בשיטת נקודה צפה עם דיוק כפול
BooleanProperty בוליאני
StringProperty מחרוזת Unicode; עד 1,500 בייט, באינדקס
TextProperty מחרוזת Unicode; אורך בלתי מוגבל, לא נוסף לאינדקס
BlobProperty מחרוזת בייטים לא מפוענחת:
אם מגדירים את indexed=True, עד 1,500 בייטים, באינדקס;
אם indexed הוא False (ברירת המחדל), אורך בלתי מוגבל, לא באינדקס.
ארגומנט אופציונלי של מילת מפתח: compressed.
DateTimeProperty תאריך ושעה (ראו מאפייני תאריך ושעה)
DateProperty תאריך (ראו מאפייני תאריך ושעה)
TimeProperty שעה (ראו מאפייני תאריך ושעה)
GeoPtProperty מיקום גיאוגרפי. זהו אובייקט ndb.GeoPt. לאובייקט יש מאפיינים lat ו-lon, שניהם מסוג float. אפשר ליצור אותו עם שני ערכים מסוג float כמו ndb.GeoPt(52.37, 4.88) או עם מחרוזת ndb.GeoPt("52.37, 4.88"). (זו למעשה אותה כיתה כמו db.GeoPt)
KeyProperty מפתח במאגר הנתונים
ארגומנט אופציונלי של מילת מפתח: kind=kind, כדי לדרוש שהמפתחות שמוקצים למאפיין הזה תמיד יהיו מהסוג שצוין. יכול להיות מחרוזת או מחלקה משנית של Model.
BlobKeyProperty מפתח Blobstore
מקביל ל-BlobReferenceProperty ב-db API הישן, אבל ערך המאפיין הוא BlobKey ולא BlobInfo. אפשר ליצור BlobInfo ממנו באמצעות BlobInfo(blobkey)
UserProperty אובייקט משתמש.
StructuredProperty כולל סוג אחד של מודל בתוך סוג אחר, לפי ערך (ראו מאפיינים מובנים)
LocalStructuredProperty בדומה ל-StructuredProperty, אבל הייצוג בדיסק הוא blob אטום ולא מתבצע אינדוקס (ראו מאפיינים מובְנים).
ארגומנט אופציונלי של מילת מפתח: compressed.
JsonProperty הערך הוא אובייקט Python (כמו רשימה, מילון או מחרוזת) שאפשר לבצע לו סריאליזציה באמצעות המודול json של Python. ‏Datastore מאחסן את הסריאליזציה של ה-JSON כ-blob. לא מתווסף לאינדקס כברירת מחדל.
ארגומנט אופציונלי של מילת מפתח: compressed.
PickleProperty הערך הוא אובייקט Python (כמו רשימה, מילון או מחרוזת) שאפשר לבצע לו סריאליזציה באמצעות פרוטוקול ה-pickle של Python. מאגר הנתונים מאחסן את הסריאליזציה של ה-pickle כ-blob. לא מתווסף לאינדקס כברירת מחדל.
ארגומנט אופציונלי של מילת מפתח: compressed.
GenericProperty ערך כללי
בשימוש בעיקר במחלקה Expando, אבל אפשר להשתמש בו גם באופן מפורש. הסוג יכול להיות כל אחד מהסוגים הבאים: int, ‏ long, ‏ float, ‏ bool, ‏ str, ‏ unicode, ‏ datetime, ‏ Key, ‏ BlobKey, ‏ GeoPt, ‏ User, ‏ None.
ComputedProperty ערך שמחושב ממאפיינים אחרים על ידי פונקציה בהגדרת המשתמש (UDF). (ראו נכסים מחושבים).

לחלק מהמאפיינים האלה יש ארגומנט אופציונלי של מילת מפתח, compressed. אם הנכס כולל compressed=True, הנתונים שלו דחוסים באמצעות gzip בדיסק. הוא תופס פחות מקום אבל צריך CPU כדי לבצע קידוד/פענוח בפעולות כתיבה וקריאה.

הדחיסה והפריסה הן "עצלניות"; ערך מאפיין דחוס ייפרס רק בפעם הראשונה שתיגשו אליו. אם קוראים ישות שמכילה ערך מאפיין דחוס וכותבים אותה בחזרה בלי לגשת למאפיין הדחוס, היא לא תעבור דחיסה וגם לא פתיחת דחיסה. גם המטמון בהקשר משתתף בסכימת הטעינה העצלנית הזו, אבל memcache תמיד מאחסן את הערך הדחוס של מאפיינים דחוסים.

בגלל הזמן הנוסף של מעבד המחשב שנדרש לדחיסה, בדרך כלל הכי טוב להשתמש בנכסים דחוסים רק אם הנתונים גדולים מדי ולא יכולים להיכנס בלי דחיסה. חשוב לזכור שדחיסה מבוססת-gzip בדרך כלל לא יעילה לתמונות ולנתוני מדיה אחרים, כי הפורמטים האלה כבר דחוסים באמצעות אלגוריתם דחיסה ספציפי למדיה (למשל, JPEG לתמונות).

אפשרויות הנכס

רוב סוגי הנכסים תומכים בחלק מהארגומנטים הרגילים. הראשון הוא ארגומנט מיקום אופציונלי שמציין את שם Datastore של הנכס. אפשר להשתמש בזה כדי לתת לנכס שם אחר במאגר הנתונים מאשר השם שמופיע מנקודת המבט של האפליקציה. שימוש נפוץ בזה הוא לצמצום הנפח ב-Datastore, כך ש-Datastore ישתמש בשמות מקוצרים של מאפיינים, בזמן שהקוד שלכם ישתמש בשמות ארוכים יותר ובעלי משמעות רבה יותר. לדוגמה,

class Employee(ndb.Model):
    full_name = ndb.StringProperty('n')
    retirement_age = ndb.IntegerProperty('r')

האפשרות הזו שימושית במיוחד למאפיינים חוזרים שבהם צפויים ערכים רבים לכל ישות.

בנוסף, רוב סוגי הנכסים תומכים בארגומנטים הבאים של מילות מפתח:

ארגומנט סוג ברירת מחדל תיאור
indexed bool בדרך כלל True האם לכלול את הנכס באינדקסים של Datastore. אם הערך הוא False, אי אפשר לשלוח שאילתות לגבי הערכים, אבל פעולות הכתיבה מהירות יותר. לא כל סוגי הנכסים תומכים באינדוקס, ולכן הגדרת indexed ל-True נכשלת עבורם.
עלויות פעולות הכתיבה בנכסים לא מפונדקסים נמוכות יותר מאשר בנכסים מפונדקסים.
repeated bool False ערך המאפיין הוא רשימת Python שמכילה ערכים מהסוג הבסיסי (ראו מאפיינים חוזרים).
אי אפשר לשלב אותו עם required=True או default=True.
required bool False חובה לציין ערך למאפיין.
default סוג הבסיס של הנכס ללא ערך ברירת המחדל של המאפיין אם לא צוין ערך מפורש.
choices רשימת הערכים של סוג הבסיס None רשימה אופציונלית של ערכים מותרים.
validator תפקיד None פונקציה אופציונלית לאימות הערך ואולי להמרת הערך. הפונקציה תופעל עם הארגומנטים (prop, value) והיא צריכה להחזיר את הערך (אחרי המרה אפשרית) או להעלות חריגה. קריאה חוזרת לפונקציה על ערך שנכפה לא אמורה לשנות את הערך עוד יותר. (לדוגמה, החזרת value.strip() או value.lower() היא בסדר, אבל לא value + '$'). יכולה גם להחזיר None, שפירושו 'ללא שינוי'. אפשר לעיין גם במאמר בנושא כתיבת מחלקות משנה של מאפיינים
verbose_name מחרוזת None תווית HTML אופציונלית לשימוש במסגרות של טפסים באינטרנט כמו jinja2.

מאפיינים חוזרים

כל נכס עם repeated=True הופך לנכס חוזר. המאפיין מקבל רשימה של ערכים מהסוג הבסיסי, ולא ערך יחיד. לדוגמה, הערך של מאפיין שמוגדר עם IntegerProperty(repeated=True) הוא רשימה של מספרים שלמים.

יכול להיות שמאגר הנתונים יראה כמה ערכים למאפיין כזה. המערכת יוצרת רשומת אינדקס נפרדת לכל ערך. ההגדרה הזו משפיעה על הסמנטיקה של השאילתה. דוגמה מופיעה במאמר בנושא שאילתות לגבי מאפיינים חוזרים.

בדוגמה הזו נעשה שימוש במאפיין שחוזר על עצמו:

class Article(ndb.Model):
    title = ndb.StringProperty()
    stars = ndb.IntegerProperty()
    tags = ndb.StringProperty(repeated=True)
...
article = Article(
    title='Python versus Ruby',
    stars=3,
    tags=['python', 'ruby'])
article.put()

כך נוצרת ישות Datastore עם התוכן הבא:

assert article.title == 'Python versus Ruby'
assert article.stars == 3
assert sorted(article.tags) == sorted(['python', 'ruby'])

כשמבצעים שאילתה לגבי המאפיין tags, הישות הזו תענה לשאילתה לגבי 'python' או לגבי 'ruby'.

כשמעדכנים מאפיין חוזר, אפשר להקצות לו רשימה חדשה או לשנות את הרשימה הקיימת במקום. כשמקצים רשימה חדשה, המערכת מאמתת מייד את הסוגים של הפריטים ברשימה. סוגי פריטים לא תקינים (לדוגמה, הקצאת [1, 2] ל-art.tags למעלה) מעלים חריגה. כשמשנים את הרשימה, השינוי לא מאומת באופן מיידי. במקום זאת, הערך יאומת כשכותבים את הישות ל-Datastore.

‫Datastore שומר על הסדר של הפריטים ברשימה במאפיין חוזר, כך שאפשר להקצות משמעות מסוימת לסדר שלהם.

מאפייני תאריך ושעה

יש שלושה סוגים של מאפיינים שבהם אפשר לאחסן ערכים שקשורים לתאריך ולשעה:

  • DateProperty
  • TimeProperty
  • DateTimeProperty

הערכים של המאפיינים האלה שייכים לסיווגים המתאימים (date, ‏ time,‏ datetime) של מודול datetime Python הרגיל. הפורמט הכללי ביותר מבין השלושה הוא DateTimeProperty, שמציין גם תאריך בלוח השנה וגם שעה ביום. הפורמטים האחרים שימושיים מדי פעם למטרות מיוחדות שבהן נדרש רק תאריך (למשל תאריך לידה) או רק שעה (למשל שעת פגישה). מסיבות טכניות, DateProperty ו-TimeProperty הם תת-מחלקות של DateTimeProperty, אבל לא כדאי להסתמך על יחסי התורשה האלה (חשוב לציין שהם שונים מיחסי התורשה בין המחלקות הבסיסיות שמוגדרות על ידי מודול datetime עצמו).

לכל אחד מהמאפיינים האלה יש שתי אפשרויות נוספות של מילות מפתח בוליאניות:

אפשרות תיאור
auto_now_add הגדרת הנכס לתאריך ולשעה הנוכחיים כשיוצרים את הישות. אפשר לשנות את ברירת המחדל של המאפיין הזה באופן ידני. כשהישות מתעדכנת, המאפיין לא משתנה. כדי להגדיר את ההתנהגות הזו, משתמשים ב-auto_now.
auto_now הגדרת המאפיין לתאריך ולשעה הנוכחיים כשיוצרים את הישות ובכל פעם שהיא מתעדכנת.

אי אפשר לשלב את האפשרויות האלה עם repeated=True. ברירת המחדל של שניהם היא False. אם שניהם מוגדרים כ-True, המדיניות auto_now מקבלת עדיפות. אפשר לשנות את הערך של מאפיין באמצעות auto_now_add=True, אבל לא באמצעות auto_now=True. הערך האוטומטי לא נוצר עד שהישות נכתבת, כלומר האפשרויות האלה לא מספקות ערכי ברירת מחדל דינמיים. (הפרטים האלה שונים מאלה של ה-API הישן של מסד הנתונים).

מאפיינים מובנים

אפשר לבנות את המאפיינים של מודל. לדוגמה, אפשר להגדיר מחלקה של מודל Contact שמכילה רשימה של כתובות, שלכל אחת מהן יש מבנה פנימי. אפשר לעשות את זה באמצעות מאפיינים מובְנים (סוג StructuredProperty``). לדוגמה:

class Address(ndb.Model):
    type = ndb.StringProperty()  # E.g., 'home', 'work'
    street = ndb.StringProperty()
    city = ndb.StringProperty()

...

class Contact(ndb.Model):
    name = ndb.StringProperty()
    addresses = ndb.StructuredProperty(Address, repeated=True)

...

guido = Contact(
    name='Guido',
    addresses=[
        Address(
            type='home',
            city='Amsterdam'),
        Address(
            type='work',
            street='Spear St',
            city='SF')])

guido.put()

כך נוצרת ישות Datastore אחת עם המאפיינים הבאים:

assert guido.name == 'Guido'
addresses = guido.addresses
assert addresses[0].type == 'home'
assert addresses[1].type == 'work'
assert addresses[0].street is None
assert addresses[1].street == 'Spear St'
assert addresses[0].city == 'Amsterdam'
assert addresses[1].city == 'SF'

קריאה חוזרת של ישות כזו משחזרת בדיוק את הישות המקורית Contact. למרות שהמופעים של Address מוגדרים באמצעות אותו תחביר כמו עבור מחלקות מודלים, הם לא ישויות מלאות. אין להם מפתחות משלהם ב-Datastore. אי אפשר לאחזר אותם בנפרד מהיישות Contact שאליה הם שייכים. עם זאת, אפשר להריץ שאילתה באפליקציה כדי לקבל את הערכים של השדות הנפרדים שלה. מידע נוסף זמין במאמר בנושא סינון ערכים של מאפיינים מובנים. שימו לב ש-address.type,‏ address.street ו-address.city נחשבים למערכים מקבילים מנקודת המבט של Datastore, אבל ספריית NDB מסתירה את ההיבט הזה ובונה את הרשימה המתאימה של מופעי Address.

אפשר לציין את אפשרויות הנכס הרגילות עבור נכסים מובנים (חוץ מ-indexed). שם מאגר הנתונים הוא הארגומנט המיקומי השני במקרה הזה (הראשון הוא מחלקת המודל שמשמשת להגדרת מבנה המשנה).

אם לא צריך לשלוח שאילתה לגבי מאפיינים פנימיים של מבנה משנה, אפשר להשתמש במאפיין מקומי מובנה (LocalStructuredProperty) במקום זאת. אם מחליפים את StructuredProperty ב-LocalStructuredProperty בדוגמה שלמעלה, ההתנהגות של קוד Python נשארת זהה, אבל Datastore רואה רק blob אטום לכל כתובת. הישות guido שנוצרה בדוגמה תאוחסן באופן הבא:

name = 'Guido'
address = <opaque blob for {'type': 'home', 'city': 'Amsterdam'}>
address = <opaque blob for {'type': 'work', 'city': 'SF',
                              'street': 'Spear St'}>

הישות תוקרא בצורה נכונה. מאחר שהמאפיינים מסוג זה תמיד לא נכללים באינדקס, אי אפשר לשלוח שאילתות לגבי ערכי כתובות.

נכסים מחושבים

מאפיינים מחושבים (ComputedProperty) הם מאפיינים לקריאה בלבד, שהערך שלהם מחושב מתוך ערכים של מאפיינים אחרים באמצעות פונקציה שסופקה על ידי האפליקציה. הערה: מאפיין מחושב תומך רק בסוגים שנתמכים על ידי מאפיינים גנריים. הערך המחושב נכתב ל-Datastore כדי שאפשר יהיה לשלוח אליו שאילתות ולהציג אותו בכלי לצפייה ב-Datastore, אבל המערכת מתעלמת מהערך המאוחסן כשהישות נקראת בחזרה מ-Datastore. במקום זאת, הערך מחושב מחדש על ידי קריאה לפונקציה בכל פעם שהערך מתבקש. לדוגמה:

class SomeEntity(ndb.Model):
    name = ndb.StringProperty()
    name_lower = ndb.ComputedProperty(lambda self: self.name.lower())

...

entity = SomeEntity(name='Nick')
entity.put()

הפעולה הזו מאחסנת ישות עם ערכי המאפיינים הבאים:

assert entity.name == 'Nick'
assert entity.name_lower == 'nick'

אם נשנה את השם ל-'Nickie' ונבקש את הערך של name_lower, הפונקציה תחזיר 'nickie':

entity.name = 'Nick'
assert entity.name_lower == 'nick'
entity.name = 'Nickie'
assert entity.name_lower == 'nickie'

מאפייני הודעות של Google Protocol RPC

ספריית Google Protocol RPC משתמשת באובייקטים של Message לנתונים מובְנים. האובייקטים האלה יכולים לייצג בקשות RPC, תגובות או דברים אחרים. ‫NDB מספק API לאחסון אובייקטים של Google Protocol RPC‏ Messageכמאפייני ישות. נניח שאתם מגדירים מחלקת משנה Message:

from protorpc import messages
...
class Note(messages.Message):
    text = messages.StringField(1, required=True)
    when = messages.IntegerField(2)

אפשר לאחסן אובייקטים של Note ב-Datastore כערכי מאפיינים של ישויות באמצעות msgprop API של NDB.

from google.appengine.ext import ndb
from google.appengine.ext.ndb import msgprop
...
class NoteStore(ndb.Model):
    note = msgprop.MessageProperty(Note, indexed_fields=['when'])
    name = ndb.StringProperty()
...
my_note = Note(text='Excellent note', when=50)

ns = NoteStore(note=my_note, name='excellent')
key = ns.put()

new_notes = NoteStore.query(NoteStore.note.when >= 10).fetch()

אם רוצים לשלוח שאילתה לשמות של שדות, צריך ליצור להם אינדקס. אפשר לציין רשימה של שמות שדות שיעברו אינדוקס באמצעות הפרמטר indexed_fields עד MessageProperty.

MessageProperty תומך בהרבה אפשרויות של נכסים, אבל לא בכולן. הוא תומך ב:

  • name
  • repeated
  • required
  • default
  • choices
  • validator
  • verbose_name

מאפייני ההודעה לא תומכים באפשרות המאפיין indexed, ולכן אי אפשר ליצור אינדקס של ערכי Message. (אפשר ליצור אינדקס לשדות של הודעה כמו שמתואר למעלה).

אפשר גם להשתמש בהודעות מוטמעות (באמצעות MessageField):

class Notebook(messages.Message):
    notes = messages.MessageField(Note, 1, repeated=True)
...
class SignedStorableNotebook(ndb.Model):
    author = ndb.StringProperty()
    nb = msgprop.MessageProperty(
        Notebook, indexed_fields=['notes.text', 'notes.when'])

MessageProperty כולל אפשרות מאפיין מיוחדת, protocol, שמציינת איך אובייקט ההודעה עובר סריאליזציה למאגר נתונים. הערכים הם שמות פרוטוקולים כפי שמשמשים במחלקה protorpc.remote.Protocols. הפרוטוקולים הנתמכים הם protobuf ו-protojson. ברירת המחדל היא protobuf.

בנוסף, msgprop מגדיר את EnumProperty, סוג מאפיין שאפשר להשתמש בו כדי לאחסן ערך protorpc.messages.Enum בישות. דוגמה:

class Color(messages.Enum):
    RED = 620
    GREEN = 495
    BLUE = 450
...
class Part(ndb.Model):
    name = ndb.StringProperty()
    color = msgprop.EnumProperty(Color, required=True)
...
p1 = Part(name='foo', color=Color.RED)
print p1.color  # prints "RED"

הערך מאוחסן ב-EnumProperty כמספר שלם. למעשה, EnumProperty הוא מחלקת משנה של IntegerProperty. המשמעות היא שאפשר לשנות את השם של ערכי enum בלי לשנות ישויות שכבר נשמרו, אבל אי אפשר לשנות את המספר שלהם.

‫EnumProperty תומך באפשרויות המאפיין הבאות:

  • name
  • indexed
  • repeated
  • required
  • default
  • choices
  • validator
  • verbose_name

מידע על מודלים של ישויות ב-NDB

מודל של ישות NDB יכול להגדיר מאפיינים. מאפייני ישות דומים קצת למשתני נתונים של מחלקות Python, שהם דרך מובנית להחזיק נתונים. הם גם דומים קצת לשדות בסכמת מסד נתונים.

בדרך כלל, אפליקציה מגדירה מודל נתונים על ידי הגדרת מחלקה שיורשת מ-Model עם כמה מאפיינים של מחלקת נכסים. לדוגמה:


from google.appengine.ext import ndb
...
class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()

כאן, username, userid ו-email הם מאפיינים של Account.

יש עוד כמה סוגים של נכסים. חלקם שימושיים להצגת תאריכים ושעות, ויש להם תכונות נוחות של עדכון אוטומטי.

אפליקציה יכולה להתאים את ההתנהגות של מאפיין על ידי ציון אפשרויות במאפיין. האפשרויות האלה יכולות להקל על האימות, להגדיר ערכי ברירת מחדל או לשנות את יצירת האינדקס של השאילתות.

למודל יכולות להיות תכונות מורכבות יותר. מאפיינים חוזרים הם כמו רשימות. מאפיינים מובְנים הם כמו אובייקטים. מאפיינים מחושבים לקריאה בלבד מוגדרים באמצעות פונקציות, ולכן קל להגדיר מאפיין במונחים של מאפיין אחד או יותר. מודלים של Expando יכולים להגדיר מאפיינים באופן דינמי.