-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPKG-INFO
541 lines (395 loc) · 19.3 KB
/
PKG-INFO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
Metadata-Version: 1.0
Name: megrok.rdb
Version: 0.12
Summary: SQLAlchemy based RDB support for Grok.
Home-page: UNKNOWN
Author: Grok Team
Author-email: [email protected]
License: ZPL
Description: ==========
megrok.rdb
==========
Introduction
------------
The ``megrok.rdb`` package adds powerful relational database support
to Grok, based on the powerful SQLAlchemy_ library. It makes available
a new ``megrok.rdb.Model`` and ``megrok.rdb.Container`` which behave
much like ones in core Grok, but are instead backed by a relational
database.
.. _SQLAlchemy: http://www.sqlalchemy.org
In this document we will show you how to use ``megrok.rdb``.
Declarative models
------------------
``megrok.rdb`` uses SQLAlchemy's ORM system, in particular its
declarative extension, almost directly. ``megrok.rdb`` just supplies a
few special base classes and directives to make things easier, and a few
other conveniences that help with integration with Grok.
We first import the SQLAlchemy bits we'll need later::
>>> from sqlalchemy import Column, ForeignKey
>>> from sqlalchemy.types import Integer, String
>>> from sqlalchemy.orm import relation
SQLAlchemy groups database schema information into a unit called
``MetaData``. The schema can be reflected from the database schema, or
can be created from a schema defined in Python. With ``megrok.rdb`` we
typically do the latter, from within the content classes that they are
mapped to using the ORM. We need to have some metadata to associate
our content classes with.
Let's set up the metadata object::
>>> from megrok import rdb
>>> metadata = rdb.MetaData()
Now we'll set up a few content classes. We'll have a very simple
structure where a (university) department has zero or more courses
associated with it. First we'll define a container that can contain
courses::
>>> class Courses(rdb.Container):
... pass
That's all. If the ``rdb.key`` directive is not used the key in the
container will be defined as the (possibly automatically assigned)
primary key in the database.
FIXME a hack to make things work in doctests. In some particular setup
this hack wasn't needed anymore, but I am unable at this time to
reestablish this combination of packages::
>>> __file__ = 'foo'
Now we can set up the ``Department`` class. This has the ``courses``
relation that links to its courses::
>>> class Department(rdb.Model):
... rdb.metadata(metadata)
...
... id = Column('id', Integer, primary_key=True)
... name = Column('name', String(50))
...
... courses = relation('Course',
... backref='department',
... collection_class=Courses)
This is very similar to the way you'd use
``sqlalchemy.ext.declarative``, but there are a few differences::
* we inherit from ``rdb.Model`` to make this behave like a Grok model.
* We don't need to use ``__tablename__`` to set up the table name. By
default the table name will be the class name, lowercased, but you
can override this by using the ``rdb.tablename`` directive.
* we need to make explicit the metadata object that is used. We do
this in the tests, though in Grok applications it's enough to use
the ``rdb.metadata`` directive on a module-level to have all rdb
classes automatically associated with that metadata object.
* we mark that the ``courses`` relation uses the ``Courses`` container
class we have defined before. This is a normal SQLAlchemy feature,
it's just we have to use it if we want to use Grok-style containers.
We finish up our database definition by defining the ``Course``
class::
>>> class Course(rdb.Model):
... rdb.metadata(metadata)
...
... id = Column('id', Integer, primary_key=True)
... department_id = Column('department_id', Integer,
... ForeignKey('department.id'))
... name = Column('name', String(50))
We see here that ``Course`` links back to the department it is in,
using a foreign key.
Configuration
-------------
We need to actually grok these objects to have them fully set
up. Normally grok takes care of this automatically, but in this case
we'll need to do it manually.
First we grok this package's grokkers::
>>> import grokcore.component.testing
>>> grokcore.component.testing.grok('megrok.rdb.meta')
Now we can grok the components::
>>> from grokcore.component.testing import grok_component
>>> grok_component('Courses', Courses)
True
>>> grok_component('Department', Department)
True
>>> grok_component('Course', Course)
True
Once we have our metadata and object relational map defined, we need
to have a database to actually put these in. While it is possible to
set up a different database per Grok application, here we will use a
single global database::
>>> TEST_DSN = 'sqlite:///:memory:'
>>> from z3c.saconfig import EngineFactory
>>> from z3c.saconfig.interfaces import IEngineFactory
>>> engine_factory = EngineFactory(TEST_DSN)
We need to supply the engine factory as a utility. Grok can do this
automatically for you using the module-level ``grok.global_utility``
directive, like this::
grok.global_utility(engine_factory, provides=IEngineFactory, direct=True)
In the tests we'll use the component architecture directly::
>>> from zope import component
>>> component.provideUtility(engine_factory, provides=IEngineFactory)
Now that we've set up an engine, we can set up the SQLAlchemy session
utility::
>>> from z3c.saconfig import GloballyScopedSession
>>> from z3c.saconfig.interfaces import IScopedSession
>>> scoped_session = GloballyScopedSession()
With Grok, we'd register it like this::
grok.global_utility(scoped_session, provides=IScopedSession, direct=True)
But again we'll just register it directly for the tests::
>>> component.provideUtility(scoped_session, provides=IScopedSession)
We now need to create the tables we defined in our database. We can do this
only when the engine is first created, so we set up a handler for it::
>>> from z3c.saconfig.interfaces import IEngineCreatedEvent
>>> @component.adapter(IEngineCreatedEvent)
... def engine_created(event):
... rdb.setupDatabase(metadata)
>>> component.provideHandler(engine_created)
Using the database
------------------
Now all that is out the way, we can use the ``rdb.Session`` object to make
a connection to the database.
>>> session = rdb.Session()
Let's now create a database structure. We have a department of philosophy::
>>> philosophy = Department(name="Philosophy")
We need to manually add it to the database, as we haven't defined a
particular ``departments`` container in our database::
>>> session.add(philosophy)
The philosophy department has a number of courses::
>>> logic = Course(name="Logic")
>>> ethics = Course(name="Ethics")
>>> metaphysics = Course(name="Metaphysics")
>>> session.add_all([logic, ethics, metaphysics])
We'll add them to the philosophy department's courses container. Since
we want to leave it up to the database what the key will be, we will
use the special ``set`` method that ``rdb.Container`` objects have to
add the objects::
>>> philosophy.courses.set(logic)
>>> philosophy.courses.set(ethics)
>>> philosophy.courses.set(metaphysics)
We can now verify that the courses are there::
>>> for key, value in sorted(philosophy.courses.items()):
... print key, value.name, value.department.name
1 Logic Philosophy
2 Ethics Philosophy
3 Metaphysics Philosophy
As you can see, the automatically generated primary key is also used
as the container key now.
The keys to the container are always integer, even if we're dealing with
a primary key::
>>> philosophy.courses['1'].name
'Logic'
>>> philosophy.courses.get('1').name
'Logic'
Custom key with ``rdb.key``
---------------------------
Let's now set up a different attribute to use as the container key.
We will use the ``name`` attribute of the course.
We'll set up the data model again, this time with a ``rdb.key`` on the
``Courses`` class::
>>> metadata = rdb.MetaData()
>>> class Courses(rdb.Container):
... rdb.key('name')
>>> class Department(rdb.Model):
... rdb.metadata(metadata)
...
... id = Column('id', Integer, primary_key=True)
... name = Column('name', String(50))
...
... courses = relation('Course',
... backref='department',
... collection_class=Courses)
>>> class Course(rdb.Model):
... rdb.metadata(metadata)
...
... id = Column('id', Integer, primary_key=True)
... department_id = Column('department_id', Integer,
... ForeignKey('department.id'))
... name = Column('name', String(50))
We grok these new classes::
>>> grok_component('Courses', Courses)
True
>>> grok_component('Department', Department)
True
>>> grok_component('Course', Course)
True
We don't need to change the engine, as the underlying relational
database has remained the same. Let's set up another faculty with some
departments::
>>> physics = Department(name="Physics")
>>> session.add(physics)
>>> quantum = Course(name="Quantum Mechanics")
>>> relativity = Course(name="Relativity")
>>> high_energy = Course(name="High Energy")
>>> session.add_all([quantum, relativity, high_energy])
We'll now add these departments to the physics faculty::
>>> physics.courses.set(quantum)
>>> physics.courses.set(relativity)
>>> physics.courses.set(high_energy)
We can now verify that the courses are there, with the names as the keys::
>>> for key, value in sorted(physics.courses.items()):
... print key, value.name, value.department.name
High Energy High Energy Physics
Quantum Mechanics Quantum Mechanics Physics
Relativity Relativity Physics
Custom query container
----------------------
Sometimes we want to expose objects as a (read-only) container based
on a query, not a relation. This is useful when constructing an
application and you need a "starting point", a root object that
launches into SQLAlchemy-mapped object that itself is not directly
managed by SQLAlchemy.
We can construct such a special container by subclassing from ``rdb.QueryContainer`` and implementing
the special ``query`` method::
>>> class MyQueryContainer(rdb.QueryContainer):
... def query(self):
... return session.query(Department)
>>> qc = MyQueryContainer()
Let's try some common read-only container operations, such as
``__getitem__``::
>>> qc['1'].name
u'Philosophy'
>>> qc['2'].name
'Physics'
FIXME Why the unicode difference between u'Philosophy' and 'Physics'?
``__getitem__`` with a ``KeyError``::
>>> qc['3']
Traceback (most recent call last):
...
KeyError: '3'
``get``::
>>> qc.get('1').name
u'Philosophy'
>>> qc.get('3') is None
True
>>> qc.get('3', 'foo')
'foo'
``__contains__``::
>>> '1' in qc
True
>>> '3' in qc
False
``has_key``::
>>> qc.has_key('1')
True
>>> qc.has_key('3')
False
``len``::
>>> len(qc)
2
``values``::
>>> sorted([v.name for v in qc.values()])
[u'Philosophy', 'Physics']
The parents of all the values are the query container::
>>> [v.__parent__ is qc for v in qc.values()]
[True, True]
>>> sorted([v.__name__ for v in qc.values()])
[u'1', u'2']
``keys``::
>>> sorted([key for key in qc.keys()])
[u'1', u'2']
``items``::
>>> sorted([(key, value.name) for (key, value) in qc.items()])
[(u'1', u'Philosophy'), (u'2', 'Physics')]
>>> [value.__parent__ is qc for (key, value) in qc.items()]
[True, True]
>>> sorted([value.__name__ for (key, value) in qc.items()])
[u'1', u'2']
``__iter__``::
>>> result = []
>>> for key in qc:
... result.append(key)
>>> sorted(result)
[u'1', u'2']
Converting results of QueryContainer
------------------------------------
Sometimes it's useful to convert (or modify) the output of the query
to something else before they appear in the container. You can implement
the ``convert`` method to do so. It takes the individual value resulting
from the value and should return the converted value::
>>> class ConvertingQueryContainer(rdb.QueryContainer):
... def query(self):
... return session.query(Department)
... def convert(self, value):
... return SpecialDepartment(value.id)
>>> class SpecialDepartment(object):
... def __init__(self, id):
... self.id = id
>>> qc = ConvertingQueryContainer()
Let's now check that all values are ``SpecialDepartment``::
>>> isinstance(qc['1'], SpecialDepartment)
True
>>> isinstance(qc['2'], SpecialDepartment)
True
KeyError still works::
>>> qc['3']
Traceback (most recent call last):
...
KeyError: '3'
``get``::
>>> isinstance(qc.get('1'), SpecialDepartment)
True
>>> qc.get('3') is None
True
>>> qc.get('3', 'foo')
'foo'
``values``::
>>> [isinstance(v, SpecialDepartment) for v in qc.values()]
[True, True]
The parents of all the values are the query container::
>>> [v.__parent__ is qc for v in qc.values()]
[True, True]
>>> sorted([v.__name__ for v in qc.values()])
[u'1', u'2']
``items``::
>>> sorted([(key, isinstance(value, SpecialDepartment)) for (key, value) in qc.items()])
[(u'1', True), (u'2', True)]
>>> [value.__parent__ is qc for (key, value) in qc.items()]
[True, True]
>>> sorted([value.__name__ for (key, value) in qc.items()])
[u'1', u'2']
Customizing QueryContainer further
----------------------------------
Sometimes it's useful to define a custom keyfunc and custom method to
retrieve the key from the database - these usually are implemented
together::
>>> class KeyfuncQueryContainer(rdb.QueryContainer):
... def query(self):
... return session.query(Department)
... def keyfunc(self, value):
... return 'd' + unicode(value.id)
... def dbget(self, key):
... if not key.startswith('d'):
... return None
... return self.query().get(key[1:])
>>> qc = KeyfuncQueryContainer()
>>> qc.keys()
[u'd1', u'd2']
>>> qc[u'd1'].id
1
=======
CHANGES
=======
0.12 (2011-02-02)
-----------------
- Update dependencies and imports to work with Grok 1.2 and 1.3.
0.11 (2010-02-22)
-----------------
- Added a LICENSE.txt file.
- Added setupDatabaseSkipCreate. This allows setting up the database
without trying to create any tables, just reflection.
0.10 (2009-09-18)
-----------------
- Added to SQLAlchemy to zope.schema adapters so that most of the types in
sqlalchemy.types are covered.
- Import schema_from_model into ``megrok.rdb`` package namespace.
- Update buildout to use Grok 1.0b2 versions.
- Added a test that demonstrates a common initialization pattern using
`rdb.setupDatabase`` in a ``IEngineCreatedEvent`` subscriber.
0.9.1 (2009-08-14)
------------------
- megrok.rdb 0.9 accidentally had zip_safe set to True, which resulted
in a dud release as its ZCML wouldn't be loaded. Set zip_safe to
False.
0.9 (2009-08-14)
----------------
- Initial public release.
Keywords: rdb relational sqlalchemy grok database
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Zope3
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Zope Public License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Database