Python의 데이터 모델링

참고: 새로운 애플리케이션을 빌드하는 개발자는 NDB 클라이언트 라이브러리를 사용하는 것이 좋습니다. NDB 클라이언트 라이브러리는 이 클라이언트 라이브러리와 비교할 때 Memcache API를 통한 자동 항목 캐싱과 같은 여러 이점이 있습니다. 현재 이전 DB 클라이언트 라이브러리를 사용 중인 경우 DB에서 NDB로의 마이그레이션 가이드를 참조하세요.

개요

Datastore 항목에는 키와 속성 집합이 있습니다. 애플리케이션은 Datastore API를 사용하여 데이터 모델을 정의하고 이러한 모델의 인스턴스를 만들어서 항목으로 저장합니다. 모델은 API에 의해 생성되는 항목에 공통된 구조를 제공하며 속성 값 유효성 검사를 위한 규칙을 정의할 수 있습니다.

Model 클래스

Model 클래스

애플리케이션은 모델을 통해 사용하는 데이터의 종류를 설명합니다. 모델은 Model 클래스에서 상속되는 Python 클래스입니다. 모델 클래스는 새로운 Datastore 항목의 Kind와 Kind가 취해야 하는 속성을 정의합니다. Kind 이름은 db.Model에서 상속되는 인스턴스화된 클래스에서 정의됩니다.

모델 속성은 모델 클래스의 클래스 특성을 사용하여 정의됩니다. 각 클래스 특성은 Property 클래스의 서브클래스 인스턴스이며 일반적으로 제공된 속성 클래스 중 하나입니다. 속성 인스턴스에는 인스턴스가 유효하기 위해 속성이 필요한지 여부, 또는 값이 제공되지 않을 경우 인스턴스에 사용할 기본값과 같은 속성의 구성이 저장됩니다.

from google.appengine.ext import db

class Pet(db.Model):
    name = db.StringProperty(required=True)
    type = db.StringProperty(required=True, choices=set(["cat", "dog", "bird"]))
    birthdate = db.DateProperty()
    weight_in_pounds = db.IntegerProperty()
    spayed_or_neutered = db.BooleanProperty()

정의된 항목 종류 중 하나의 항목은 API에서 해당 모델 클래스의 인스턴스로 표현됩니다. 애플리케이션은 클래스의 생성자를 호출하여 새 항목을 만들 수 있습니다. 애플리케이션은 인스턴스의 특성을 사용하여 항목의 속성에 액세스하고 이를 조작합니다. 모델 인스턴스 생성자는 속성의 초기 값을 키워드 인수로 받습니다.

from google.appengine.api import users

pet = Pet(name="Fluffy",
          type="cat")
pet.weight_in_pounds = 24

참고: 모델 클래스의 특성은 그 값이 Property 인스턴스인 모델 속성의 구성입니다. 모델 인스턴스의 특성은 그 값이 Property 클래스에 의해 수락되는 유형인 실제 속성 값입니다.

Model 클래스는 Property 인스턴스를 사용하여 모델 인스턴스 특성에 할당된 값의 유효성을 검사합니다. 속성 값 유효성 검사는 모델 인스턴스가 처음 생성될 때, 그리고 인스턴스 특성에 새 값이 할당될 때 수행됩니다. 이로써 속성이 잘못된 값을 갖지 않도록 보장됩니다.

유효성 검사는 인스턴스가 생성될 때 수행되므로 필수 항목으로 구성되는 속성은 생성자에서 초기화되어야 합니다. 이 예시에서는 nametype이 필수 값이므로 초기 값이 생성자에 지정됩니다. weight_in_pounds는 모델에서 필요하지 않으므로 할당되지 않은 상태로 시작된 후 나중에 값이 할당됩니다.

생성자를 사용하여 만들어지는 모델의 인스턴스는 처음 '배치'되기 전까지는 Datastore에 존재하지 않습니다.

참고: 모든 Python 클래스 특성이 그렇듯이 모델 속성 구성은 스크립트 또는 모듈을 처음 가져올 때 초기화됩니다. App Engine은 가져온 모듈을 요청 사이에 캐시하므로 모듈 구성이 한 사용자의 요청 중 초기화되고 다른 사용자의 요청 중 재사용될 수 있습니다. 기본값과 같은 모델 속성 구성을 요청 또는 현재 사용자에 특정한 데이터로 초기화하지 마세요. 자세한 내용은 앱 캐싱을 참조하세요.

Expando 클래스

Model 클래스를 사용하여 정의된 모델은 클래스의 모든 인스턴스가 가져야 하는 고정된 속성 집합을 설정합니다(기본값 사용 가능). 이는 데이터 객체를 모델링하기 위한 유용한 방법이지만 Datastore에는 지정된 종류의 모든 항목이 동일한 속성 집합을 가져야 한다는 요건은 없습니다.

항목의 속성이 같은 종류의 다른 항목의 속성과 꼭 동일할 필요가 없는 것이 유용한 경우도 있습니다. 그러한 항목은 Datastore API에 'expando' 모델로 표현됩니다. expando 모델 클래스는 Expando 슈퍼클래스를 서브클래스로 갖습니다. expando 모델 인스턴스의 특성에 할당되는 모든 값은 특성의 이름을 사용하여 Datastore 항목의 속성이 됩니다. 이러한 속성을 동적 속성이라고 합니다. 클래스 특성의 Property 클래스 인스턴스를 사용하여 정의되는 속성은 고정 속성입니다.

expando 모델은 고정 및 동적 속성을 모두 가질 수 있습니다. 모델 클래스는 고정 속성의 Property 구성 객체를 사용하여 클래스 특성을 설정합니다. 애플리케이션은 값을 할당할 때 동적 속성을 만듭니다.

class Person(db.Expando):
    first_name = db.StringProperty()
    last_name = db.StringProperty()
    hobbies = db.StringListProperty()

p = Person(first_name="Albert", last_name="Johnson")
p.hobbies = ["chess", "travel"]

p.chess_elo_rating = 1350

p.travel_countries_visited = ["Spain", "Italy", "USA", "Brazil"]
p.travel_trip_count = 13

동적 속성에는 모델 속성 정의가 없으므로 동적 속성은 유효성 검사가 되지 않습니다. 모든 동적 속성은 None을 포함하여 Datastore 기본 유형의 값을 가질 수 있습니다. 같은 종류의 두 항목은 같은 동적 속성에 서로 다른 값 유형을 가질 수 있으며 한 항목이 설정한 속성을 다른 항목에서는 설정하지 않은 채로 둘 수 있습니다.

고정 속성과 달리 동적 속성은 없어도 됩니다. 값이 None인 동적 속성은 존재하지 않는 동적 속성과 다릅니다. expando 모델 인스턴스에 속성의 특성이 없는 경우 대응하는 데이터 항목에는 해당 속성이 없습니다. 특성을 삭제하여 동적 속성을 삭제할 수 있습니다.

이름이 밑줄(_)로 시작하는 특성은 Datastore 항목에 저장되지 않습니다. 이로써 항목과 함께 저장된 데이터에 영향을 미치지 않으면서 임시 내부 용도로 모델 인스턴스에 값을 저장할 수 있습니다.

참고: 정적 속성은 Expando, Model인지 여부, 또는 밑줄(_)로 시작하는지 여부와 관계없이 항상 Datastore 항목에 저장됩니다.

del p.chess_elo_rating

필터에 동적 속성을 사용하는 쿼리는 속성 값의 유형이 쿼리에 사용된 값의 유형과 동일한 항목만 반환합니다. 마찬가지로, 쿼리는 해당 속성이 설정된 항목만 반환합니다.

p1 = Person()
p1.favorite = 42
p1.put()

p2 = Person()
p2.favorite = "blue"
p2.put()

p3 = Person()
p3.put()

people = db.GqlQuery("SELECT * FROM Person WHERE favorite < :1", 50)
# people has p1, but not p2 or p3

people = db.GqlQuery("SELECT * FROM Person WHERE favorite > :1", 50)
# people has no results

참고: 위 예에서는 항목 그룹 간에 쿼리를 사용하며 이로 인해 비활성 결과가 반환될 수 있습니다. strong consistency를 가진 결과에는 항목 그룹 내 상위 쿼리를 사용하세요.

Expando 클래스는 Model 클래스의 서브클래스이며 모든 메서드를 상속합니다.

PolyModel 클래스

Python API에는 클래스의 계층구조를 정의하고 지정된 클래스 또는 모든 서브클래스의 항목을 반환할 수 있는 쿼리를 수행하도록 해주는 데이터 모델링을 위한 다른 클래스가 포함되어 있습니다. 이러한 모델과 쿼리는 한 클래스의 인스턴스가 상위 클래스 쿼리의 결과가 될 수 있도록 하므로 '다형성'이라고 합니다.

다음 예시에서는 PersonCompany 서브클래스가 있는 Contact 클래스를 정의합니다.

from google.appengine.ext import db
from google.appengine.ext.db import polymodel

class Contact(polymodel.PolyModel):
    phone_number = db.PhoneNumberProperty()
    address = db.PostalAddressProperty()

class Person(Contact):
    first_name = db.StringProperty()
    last_name = db.StringProperty()
    mobile_number = db.PhoneNumberProperty()

class Company(Contact):
    name = db.StringProperty()
    fax_number = db.PhoneNumberProperty()

이 모델은 모든 Person 항목과 모든 Company 항목이 phone_numberaddress 속성을 갖도록 하고 Contact 항목의 쿼리가 Person 또는 Company 항목을 반환할 수 있도록 합니다. Person 항목에만 mobile_number 속성이 있습니다.

서브클래스는 다른 모델 클래스와 같은 방법으로 인스턴스화할 수 있습니다.

p = Person(phone_number='1-206-555-9234',
           address='123 First Ave., Seattle, WA, 98101',
           first_name='Alfred',
           last_name='Smith',
           mobile_number='1-206-555-0117')
p.put()

c = Company(phone_number='1-503-555-9123',
            address='P.O. Box 98765, Salem, OR, 97301',
            name='Data Solutions, LLC',
            fax_number='1-503-555-6622')
c.put()

Contact 항목의 쿼리는 Contact, Person, Company의 인스턴스를 반환할 수 있습니다. 다음 코드는 위에서 생성된 두 항목의 정보를 반환합니다.

for contact in Contact.all():
    print 'Phone: %s\nAddress: %s\n\n' % (contact.phone_number,
                                          contact.address)

Company 항목의 쿼리는 Company의 인스턴스만 반환합니다.

for company in Company.all()
    # ...

현재로서는 다형성 모델을 Query 클래스 생성자에 직접 전달해서는 안 됩니다. 대신 위 예시와 같이 all() 메서드를 사용합니다.

다형성 모델을 사용하는 방법과 구현 방법에 대한 자세한 내용은 PolyModel 클래스를 참조하세요.

Property 클래스 및 유형

Datastore는 Unicode 문자열, 정수, 부동 소수점 수, 날짜, 항목 키, 바이트 문자열(blob), 다양한 GData 유형을 포함하여 항목 속성의 고정된 값 유형 집합을 지원합니다. 각각의 Datastore 값 유형에는 google.appengine.ext.db 모듈에서 제공하는 해당 Property 클래스가 있습니다.

유형 및 Property 클래스에서는 모든 지원되는 값 유형과 해당 Property 클래스에 대해 설명합니다. 아래에는 몇 가지 특수한 값 유형이 설명되어 있습니다.

문자열 및 blob

Datastore는 텍스트 저장을 위한 두 가지 값 유형으로 최대 길이 1,500바이트의 short 텍스트 문자열과 최대 길이 1MB의 long 텍스트 문자열을 지원합니다. short 문자열은 색인이 생성되며 쿼리 필터 조건 및 정렬 순서에서 사용 가능합니다. long 문자열은 색인이 생성되지 않으며 필터 조건 또는 정렬 순서에서 사용할 수 없습니다.

short 문자열 값은 unicode 값 또는 str 값일 수 있습니다. 값이 str이면 'ascii' 인코딩이 전제됩니다. str 값에 다른 인코딩을 지정하려면 str 및 인코딩 이름을 인수로 받는 unicode() 유형 생성자로 이 값을 unicode 값으로 변환하면 됩니다. short 문자열은 StringProperty 클래스를 사용하여 모델링할 수 있습니다.

class MyModel(db.Model):
    string = db.StringProperty()

obj = MyModel()

# Python Unicode literal syntax fully describes characters in a text string.
obj.string = u"kittens"

# unicode() converts a byte string to a Unicode string using the named codec.
obj.string = unicode("kittens", "latin-1")

# A byte string is assumed to be text encoded as ASCII (the 'ascii' codec).
obj.string = "kittens"

# Short string properties can be used in query filters.
results = db.GqlQuery("SELECT * FROM MyModel WHERE string = :1", u"kittens")

long 문자열 값은 db.Text 인스턴스로 표시됩니다. 생성자는 unicode 값 또는 str 값을 받고 선택적으로 str에 사용되는 인코딩의 이름을 받습니다. long 문자열은 TextProperty 클래스를 사용하여 모델링할 수 있습니다.

class MyModel(db.Model):
    text = db.TextProperty()

obj = MyModel()

# Text() can take a Unicode string.
obj.text = u"lots of kittens"

# Text() can take a byte string and the name of an encoding.
obj.text = db.Text("lots of kittens", "latin-1")

# If no encoding is specified, a byte string is assumed to be ASCII text.
obj.text = "lots of kittens"

# Text properties can store large values.
obj.text = db.Text(open("a_tale_of_two_cities.txt").read(), "utf-8")

Datastore는 텍스트가 아닌 바이트 문자열에 두 가지 비슷한 유형인 db.ByteStringdb.Blob을 지원합니다. 이러한 값은 원시 바이트의 문자열이며 인코딩된 텍스트(예: UTF-8)로 취급되지 않습니다.

db.StringProperty 값과 마찬가지로 db.ByteString 값 색인이 생성됩니다. db.TextProperty 속성처럼 db.ByteString 값은 1500바이트로 제한됩니다. ByteString 인스턴스는 short 바이트 문자열을 나타내며 str 값을 생성자의 인수로 받습니다. 바이트 문자열은 ByteStringProperty 클래스를 사용하여 모델링됩니다.

db.Text와 같이 db.Blob 값은 최대 1MB까지 될 수 있지만 색인이 생성되지 않으며 쿼리 필터 또는 정렬 순서에서 사용될 수 없습니다. db.Blob 클래스는 str 값을 생성자의 인수로 받습니다. 또는 직접 값을 할당할 수 있습니다. blob은 BlobProperty 클래스를 사용하여 모델링됩니다.

class MyModel(db.Model):
    blob = db.BlobProperty()

obj = MyModel()

obj.blob = open("image.png").read()

목록

속성에는 Datastore API에서 Python list로 표현되는 값이 여러 개 있을 수 있습니다. 목록에는 Datastore가 지원하는 모든 값 유형의 값이 포함될 수 있습니다. 하나의 목록 속성이 여러 유형의 값을 가질 수도 있습니다.

순서는 일반적으로 보존되므로 쿼리 및 get()에서 항목이 반환될 때 목록 속성 값의 순서는 정렬된 시점의 순서와 동일합니다. 한 가지 예외로 BlobText 값은 목록의 끝으로 이동됩니다. 하지만 두 값의 상호 상대적인 순서는 유지됩니다.

ListProperty 클래스는 목록을 모델링하며 목록의 모든 값이 지정된 유형이 되도록 강제합니다. 편의를 위해 라이브러리는 ListProperty(basestring)과 비슷한 StringListProperty도 제공합니다.

class MyModel(db.Model):
    numbers = db.ListProperty(long)

obj = MyModel()
obj.numbers = [2, 4, 6, 8, 10]

obj.numbers = ["hello"]  # ERROR: MyModel.numbers must be a list of longs.

목록 속성에 대한 필터가 있는 쿼리는 목록의 각 값을 개별적으로 테스트합니다. 항목은 목록의 일부 값이 그 속성의 모든 필터를 통과하는 경우에만 쿼리를 일치시킵니다. 자세한 내용은 Datastore 쿼리 페이지를 참조하세요.

# Get all entities where numbers contains a 6.
results = db.GqlQuery("SELECT * FROM MyModel WHERE numbers = 6")

# Get all entities where numbers contains at least one element less than 10.
results = db.GqlQuery("SELECT * FROM MyModel WHERE numbers < 10")

쿼리 필터는 목록 멤버에서만 작동합니다. 쿼리 필터에서 두 목록의 유사성을 테스트하는 방법은 없습니다.

내부적으로 Datastore는 목록 속성 값을 속성의 여러 값으로 표현합니다. 목록 속성 값이 빈 목록인 경우 Datastore에 속성의 표현이 없습니다. Datastore API는 정적 속성(ListProperty 있음)과 동적 속성에서 이 상황을 다르게 처리합니다.

  • 정적 ListProperty에는 빈 목록을 값으로 할당할 수 있습니다. 속성은 Datastore에 존재하지 않지만 모델 인스턴스는 값이 빈 목록인 것처럼 동작합니다. 정적 ListProperty는 None 값을 가질 수 없습니다.
  • list 값이 있는 동적 속성에는 빈 목록 값을 할당할 수 없습니다. 그러나 None 값을 가질 수 있으며 del을 사용하여 삭제할 수 있습니다.

ListProperty 모델은 목록에 추가된 값의 유형이 올바른지 테스트하고 올바르지 않을 경우 BadValueError를 발생시킵니다. 이 테스트는 이전에 저장된 항목을 가져와 모델에 로드하는 경우에도 수행되며 또한 실패할 가능성이 있습니다. str 값은 저장 전에 unicode 값(ASCII 텍스트)으로 변환되므로 ListProperty(str)strunicode 값을 모두 받는 Python 데이터 유형인 ListProperty(basestring)로 취급됩니다. 이 용도로 StringListProperty()를 사용할 수도 있습니다.

비 텍스트 바이트 문자열 저장에는 db.Blob 값을 사용합니다. blob 문자열의 바이트는 저장 및 검색될 때 보존됩니다. blob의 목록인 속성을 ListProperty(db.Blob)로 선언할 수 있습니다.

목록 속성은 독특한 방식으로 정렬 순서와 상호작용할 수 있습니다. 자세한 내용은 Datastore 쿼리 페이지를 참조하세요.

참조

속성 값은 다른 항목의 키를 포함할 수 있습니다. 값은 Key 인스턴스입니다.

ReferenceProperty 클래스는 키 값을 모델링하며 모든 값이 지정된 종류의 항목을 참조하도록 강제합니다. 편의를 위해 라이브러리는 이 속성이 있는 항목과 동일한 종류를 참조하는 ReferenceProperty에 상응하는 SelfReferenceProperty도 제공합니다.

모델 인스턴스를 ReferenceProperty 속성에 할당하면 자동으로 키가 값으로 사용됩니다.

class FirstModel(db.Model):
    prop = db.IntegerProperty()

class SecondModel(db.Model):
    reference = db.ReferenceProperty(FirstModel)

obj1 = FirstModel()
obj1.prop = 42
obj1.put()

obj2 = SecondModel()

# A reference value is the key of another entity.
obj2.reference = obj1.key()

# Assigning a model instance to a property uses the entity's key as the value.
obj2.reference = obj1
obj2.put()

ReferenceProperty 속성 값은 참조된 항목의 모델 인스턴스와 같은 방식으로 사용될 수 있습니다. 참조된 항목이 메모리에 없는 경우 속성을 인스턴스로 사용하면 자동으로 Datastore에서 항목을 가져옵니다. ReferenceProperty는 키도 저장하지만 이 속성 사용은 관련된 항목의 로드를 유발합니다.

obj2.reference.prop = 999
obj2.reference.put()

results = db.GqlQuery("SELECT * FROM SecondModel")
another_obj = results.fetch(1)[0]
v = another_obj.reference.prop

키가 존재하지 않는 항목을 가리키는 경우 속성 액세스는 오류를 일으킵니다. 참조가 유효하지 않을 수 있음을 예상하는 애플리케이션의 경우 try/except 블록을 사용하여 객체의 존재 여부를 테스트할 수 있습니다.

try:
  obj1 = obj2.reference
except db.ReferencePropertyResolveError:
  # Referenced entity was deleted or never existed.

ReferenceProperty에는 역참조라는 또 다른 유용한 기능이 있습니다. 모델이 다른 모델에 대한 ReferenceProperty를 가진 경우 참조된 각 항목은 항목을 참조하는 첫 모델의 모든 항목을 반환하는 Query를 값으로 가진 속성을 받습니다.

# To fetch and iterate over every SecondModel entity that refers to the
# FirstModel instance obj1:
for obj in obj1.secondmodel_set:
    # ...

역참조 속성의 이름은 기본적으로 modelname_set로 설정되며(소문자로 된 모델 클래스의 이름 끝에 '_set'를 추가한 형태) ReferenceProperty 생성자의 collection_name 인수를 통해 조정될 수 있습니다.

동일한 모델 클래스를 참조하는 여러 개의 ReferenceProperty 값이 있는 경우 역참조 속성의 기본 생성은 오류를 일으킵니다.

class FirstModel(db.Model):
    prop = db.IntegerProperty()

# This class raises a DuplicatePropertyError with the message
# "Class Firstmodel already has property secondmodel_set"
class SecondModel(db.Model):
    reference_one = db.ReferenceProperty(FirstModel)
    reference_two = db.ReferenceProperty(FirstModel)

이 오류를 방지하려면 collection_name 인수를 명시적으로 설정해야 합니다.

class FirstModel(db.Model):
    prop = db.IntegerProperty()

# This class runs fine
class SecondModel(db.Model):
    reference_one = db.ReferenceProperty(FirstModel,
        collection_name="secondmodel_reference_one_set")
    reference_two = db.ReferenceProperty(FirstModel,
        collection_name="secondmodel_reference_two_set")

모델 인스턴스, 유형 확인 및 역참조의 자동 참조 및 참조 해제는 ReferenceProperty 모델 속성 클래스를 사용해서만 가능합니다. Expando 동적 속성의 값 또는 ListProperty 값으로 저장된 값에는 이러한 특징이 없습니다.