55
52
provides the additional data and behaviour you'd normally implement in a
56
53
subclass. Following is the design from above converted to use the pattern.
59
>>> from storm.locals import Storm, Store, Int, Unicode, Reference
61
>>> person_info_types = {}
63
>>> def register_person_info_type(info_type, info_class):
64
... existing_info_class = person_info_types.get(info_type)
65
... if existing_info_class is not None:
66
... raise RuntimeError("%r has the same info_type of %r" %
67
... (info_class, existing_info_class))
68
... person_info_types[info_type] = info_class
69
... info_class.info_type = info_type
72
>>> class Person(Storm):
74
... __storm_table__ = "person"
76
... id = Int(allow_none=False, primary=True)
77
... name = Unicode(allow_none=False)
78
... info_type = Int(allow_none=False)
81
... def __init__(self, store, name, info_class, **kwargs):
83
... self.info_type = info_class.info_type
85
... self._info = info_class(self, **kwargs)
89
... if self._info is not None:
91
... assert self.id is not None
92
... info_class = person_info_types[self.info_type]
93
... if not hasattr(info_class, "__storm_table__"):
94
... info = info_class.__new__(info_class)
95
... info.person = self
97
... info = Store.of(self).get(info_class, self.id)
102
>>> class PersonInfo(object):
104
... def __init__(self, person):
105
... self.person = person
108
>>> class StoredPersonInfo(PersonInfo):
110
... person_id = Int(allow_none=False, primary=True)
111
... person = Reference(person_id, Person.id)
114
>>> class SecretAgent(StoredPersonInfo):
116
... __storm_table__ = "secret_agent"
118
... passcode = Unicode(allow_none=False)
120
... def __init__(self, person, passcode=None):
121
... super(SecretAgent, self).__init__(person)
122
... self.passcode = passcode
125
>>> class Teacher(StoredPersonInfo):
127
... __storm_table__ = "teacher"
129
... school = Unicode(allow_none=False)
131
... def __init__(self, person, school=None):
132
... super(Teacher, self).__init__(person)
133
... self.school = school
137
The pattern works by having a base class, `Person`, keep a reference to an
138
info class, `PersonInfo`. Info classes need to be registered so that `Person`
139
can discover them and load them when necessary. Note that info types have the
140
same ID as their parent object. This isn't strictly necessary, but it makes
141
certain things easy, such as being able to look up info objects directly by ID
142
when given a person object. `Person` objects are required to be in a store to
143
ensure that an ID is available and can used by the info class.
146
==== Registering info classes ====
57
>>> from storm.locals import Storm, Store, Int, Unicode, Reference
59
>>> person_info_types = {}
61
>>> def register_person_info_type(info_type, info_class):
62
... existing_info_class = person_info_types.get(info_type)
63
... if existing_info_class is not None:
64
... raise RuntimeError("%r has the same info_type of %r" %
65
... (info_class, existing_info_class))
66
... person_info_types[info_type] = info_class
67
... info_class.info_type = info_type
70
>>> class Person(Storm):
72
... __storm_table__ = "person"
74
... id = Int(allow_none=False, primary=True)
75
... name = Unicode(allow_none=False)
76
... info_type = Int(allow_none=False)
79
... def __init__(self, store, name, info_class, **kwargs):
81
... self.info_type = info_class.info_type
83
... self._info = info_class(self, **kwargs)
87
... if self._info is not None:
89
... assert self.id is not None
90
... info_class = person_info_types[self.info_type]
91
... if not hasattr(info_class, "__storm_table__"):
92
... info = info_class.__new__(info_class)
93
... info.person = self
95
... info = Store.of(self).get(info_class, self.id)
100
>>> class PersonInfo(object):
102
... def __init__(self, person):
103
... self.person = person
106
>>> class StoredPersonInfo(PersonInfo):
108
... person_id = Int(allow_none=False, primary=True)
109
... person = Reference(person_id, Person.id)
112
>>> class SecretAgent(StoredPersonInfo):
114
... __storm_table__ = "secret_agent"
116
... passcode = Unicode(allow_none=False)
118
... def __init__(self, person, passcode=None):
119
... super(SecretAgent, self).__init__(person)
120
... self.passcode = passcode
123
>>> class Teacher(StoredPersonInfo):
125
... __storm_table__ = "teacher"
127
... school = Unicode(allow_none=False)
129
... def __init__(self, person, school=None):
130
... super(Teacher, self).__init__(person)
131
... self.school = school
133
The pattern works by having a base class, ``Person``, keep a reference to an
134
info class, ``PersonInfo``. Info classes need to be registered so that
135
``Person`` can discover them and load them when necessary. Note that info
136
types have the same ID as their parent object. This isn't strictly
137
necessary, but it makes certain things easy, such as being able to look up
138
info objects directly by ID when given a person object. ``Person`` objects
139
are required to be in a store to ensure that an ID is available and can used
143
Registering info classes
144
------------------------
148
146
Let's register our info classes. Each class must be registered with a unique
149
147
info type key. This key is stored in the database, so be sure to use a stable
153
>>> register_person_info_type(1, SecretAgent)
154
>>> register_person_info_type(2, Teacher)
152
>>> register_person_info_type(1, SecretAgent)
153
>>> register_person_info_type(2, Teacher)
158
155
Let's create a database to store person objects before we continue.
161
>>> from storm.locals import create_database
163
>>> database = create_database("sqlite:")
164
>>> store = Store(database)
165
>>> result = store.execute("""
166
... CREATE TABLE person (
167
... id INTEGER PRIMARY KEY,
168
... info_type INTEGER NOT NULL,
169
... name TEXT NOT NULL)
171
>>> result = store.execute("""
172
... CREATE TABLE secret_agent (
173
... person_id INTEGER PRIMARY KEY,
174
... passcode TEXT NOT NULL)
176
>>> result = store.execute("""
177
... CREATE TABLE teacher (
178
... person_id INTEGER PRIMARY KEY,
179
... school TEXT NOT NULL)
185
==== Creating info classes ====
159
>>> from storm.locals import create_database
161
>>> database = create_database("sqlite:")
162
>>> store = Store(database)
163
>>> result = store.execute("""
164
... CREATE TABLE person (
165
... id INTEGER PRIMARY KEY,
166
... info_type INTEGER NOT NULL,
167
... name TEXT NOT NULL)
169
>>> result = store.execute("""
170
... CREATE TABLE secret_agent (
171
... person_id INTEGER PRIMARY KEY,
172
... passcode TEXT NOT NULL)
174
>>> result = store.execute("""
175
... CREATE TABLE teacher (
176
... person_id INTEGER PRIMARY KEY,
177
... school TEXT NOT NULL)
181
Creating info classes
182
---------------------
187
184
We can easily create person objects now.
190
>>> secret_agent = Person(store, u"Dick Tracy",
191
... SecretAgent, passcode=u"secret!")
192
>>> teacher = Person(store, u"Mrs. Cohen",
193
... Teacher, school=u"Cameron Elementary School")
188
>>> secret_agent = Person(store, u"Dick Tracy",
189
... SecretAgent, passcode=u"secret!")
190
>>> teacher = Person(store, u"Mrs. Cohen",
191
... Teacher, school=u"Cameron Elementary School")
198
194
And we can easily find them again.
205
>>> [type(person.info) for person in store.find(Person).order_by(Person.name)]
206
[<class '...SecretAgent'>, <class '...Teacher'>]
211
==== Retrieving info classes ====
213
Now that we have our basic hierarchy in place we're going to want to retrieve
214
objects by info type. Let's implement a function to make finding `Person`s
218
>>> def get_persons(store, info_classes=None):
221
... info_types = [info_class.info_type for info_class in info_classes]
222
... where = [Person.info_type.is_in(info_types)]
223
... result = store.find(Person, *where)
224
... result.order_by(Person.name)
227
>>> secret_agent = get_persons(store, info_classes=[SecretAgent]).one()
228
>>> print(secret_agent.name)
230
>>> print(secret_agent.info.passcode)
233
>>> teacher = get_persons(store, info_classes=[Teacher]).one()
234
>>> print(teacher.name)
236
>>> print(teacher.info.school)
237
Cameron Elementary School
242
Great, we can easily find different kinds of `Person`s.
245
==== In-memory info objects ====
202
>>> [type(person.info)
203
... for person in store.find(Person).order_by(Person.name)]
204
[<class '...SecretAgent'>, <class '...Teacher'>]
207
Retrieving info classes
208
-----------------------
210
Now that we have our basic hierarchy in place we're going to want to
211
retrieve objects by info type. Let's implement a function to make finding
212
``Person``\ s easier.
216
>>> def get_persons(store, info_classes=None):
220
... info_class.info_type for info_class in info_classes]
221
... where = [Person.info_type.is_in(info_types)]
222
... result = store.find(Person, *where)
223
... result.order_by(Person.name)
226
>>> secret_agent = get_persons(store, info_classes=[SecretAgent]).one()
227
>>> print(secret_agent.name)
229
>>> print(secret_agent.info.passcode)
232
>>> teacher = get_persons(store, info_classes=[Teacher]).one()
233
>>> print(teacher.name)
235
>>> print(teacher.info.school)
236
Cameron Elementary School
238
Great, we can easily find different kinds of ``Person``\ s.
241
In-memory info objects
242
----------------------
247
244
This design also allows for in-memory info objects. Let's add one to our
251
>>> class Ghost(PersonInfo):
255
>>> register_person_info_type(3, Ghost)
249
>>> class Ghost(PersonInfo):
253
>>> register_person_info_type(3, Ghost)
259
255
We create and load in-memory objects the same way we do stored ones.
262
>>> ghost = Person(store, u"Casper", Ghost)
267
>>> ghost = get_persons(store, info_classes=[Ghost]).one()
268
>>> print(ghost.name)
270
>>> print(ghost.info.friendly)
259
>>> ghost = Person(store, u"Casper", Ghost)
264
>>> ghost = get_persons(store, info_classes=[Ghost]).one()
265
>>> print(ghost.name)
267
>>> print(ghost.info.friendly)
276
270
This pattern is very handy when using Storm with code that would naturally be
277
271
implemented using inheritance.
282
274
>>> Person._storm_property_registry.clear()
284
## vim:ts=4:sw=4:et:ft=moin1_5