Introduction
Database wrapper implementation. This wrapper can be used to have easy access to roundup's hyper database without having to know too much knowledge about the class structures and (multi) links.
This document contains a few examples. In those cases you will see objects like 'db', 'dbw', 'cl', 'clw' and 'nodew'. These objects are:
- db
- hyperdb.Database
- cl
hyperdb.Class (or FileClass or IssueClass)
- dbw
- dbwrapper.DBWrapper
- clw
dbwrapper.ClassWrapper
- nodew
dbwrapper.NodeClass
- lw
dbwrapper.ListWrapper
Features
Addressing
Addressing a specific node in a class is easier with this wrapper. It almost has the looks and feels of the wrapper used in the TAL templates. Also with this wrapper you don't really need to use 'get' methods to get the data. Just address the data as if they are member of a tree with the database object as root. The class objects as limbs and the class properties as leaves. What's left is how to address the right node. Well that can be done with indexing the class (sorry but I don't know were to put that in a tree). As index you can either use the node id as integer or string, but you may also use a key value (if the class has a key column). Here are some examples:
- dbw.issue[1].title dbw.issue['1'].title Both these examples will return the title of issue 1. - dbw.priority['urgent'].name This example will return 'urgent'. Not really meaningful, but it's only meant as an example. - dbw.status['unread'].id Will return the nodeid of status node 'unread'.
In general the benefit of this wrapper is that you don't really have to know any of the node ids. All 'set' or indexing methods will resolve key values into id's. Even so will all 'get' methods return the key values and not the id's when it comes to linked items.
Searching
The wrapper is capable of handling regular expression searches. It will not only return you the found nodes, but it also will return the found matches per node.
Usage
To use the wrapper you need to place it somewhere. In our case we placed it in a sub-folder of roundup's core code which remains in the 'site-packages' folder of the Python library tree. In this /lib/site-packages/roundup we created a folder named 'extensions'. This is about conform the standard that Richard uses for additional stuff in tracker templates (we look at the wrapper as additional stuff for the roundup core code). In other words the complete path to the wrapper will be: '/lib/site-packages/roundup/extensions/dbwrapper.py'
It will be easiest if the wrapper can be imported as sub package. This will require a 'init.py' file in that directory to. The content of that file will only be this line: __all__ = [ 'dbwrapper' ]
What's left is importing the wrapper in your Python sources (e.g. detectors and action handlers). You do that by adding the line:
from roundup.extensions import dbwrapper
to all sources in which you want to use the wrapper.
There's only one comment left. As you can see, the wrapper has a lot of documentation strings inside. Therefor we suggest to run all scripts with the '-OO' option passed to Python's interpreter. This will prevent that your scripts will load these documentation strings too. If you're not sure, then please read section **6.1.2 Compiled Python files** of the tutorial in the Python documentation.
Detectors
The wrapper can easily be used in a detector without having to change anything to roundup's core code. This is what you do in an auditor:
And this in a reactor:
As you can see, they are both the same. In both cases 'dbw' will be the root object of the tree. 'clw' will be a wrapper around hyperdb object 'cl'. In most cases you don't really need that one, but it is needed to create the node wrapper 'nodew'.
In case of an auditor, 'nodeid' can be 'None' if the auditor was fired by a 'create' operation. In that case 'nodew' will be a phantom node which doesn't have any properties (except 'id' which will be 'None'). A test like 'nodew is None' will return True on phantom nodes. It will return False if the node isn't a phantom node but a real living one (auditor fired by 'set' operation).
Action handlers
The wrapper is almost used the same way in an action handler as it is used in a detector. This is what you do:
Below you find some more examples and of course the source code for the wrapper.
Best regards,
Marlon van den Berg
PS: The wrapper has been tested with 0.7.12 and 1.1.0.
Examples
Here are a few more examples what dbwrapper can do for you:
1. Reading a linked property::
- - With roundup's hyperdb:
- unread_id = db.status.lookup('unread') db.status.get(unread_id, 'order')
- dbw.status['unread'].order
2. Changing a linked property::
- - With roundup's hyperdb:
- unread_id = db.status.lookup('unread') db.issue.set('1', status=unread_id)
- dbw.issue[1].status = 'unread'
3. Adding a link to a multi linked property::
- - With roundup's hyperdb:
- topics = db.issue.get('1', 'topic') keyword_id = db.keyword.lookup('DBWrapper') topics.append(keyword_id) db.issue.set('1', topic=topics)
- dbw.issue[1].topic += 'DBWrapper'
- or:
- dbw.issue[1].topic += ['DBWrapper']
4. Removing a link from a multi linked property::
- - With roundup's hyperdb:
- topics = db.issue.get('1', 'topic') keyword_id = db.keyword.lookup('DBWrapper') topics.remove(keyword_id) db.issue.set('1', topic=topics)
- dbw.issue[1].topic -= 'DBWrapper'
- or:
- dbw.issue[1].topic -= ['DBWrapper']
5. Duplicating an issue with dbwrapper::
- - With dbwrapper:
- issue = db.issue[1].properties issue['title'] = 'Copy of %s'%issue['title'] db.issue += issue
6. Retiring a node::
- - With roundup's hyperdb:
- nodeid = db.status.lookup('chatting') db.status.retire(nodeid)
- del dbw.status['chatting']
7. Filtering::
- - With roundup's hyperdb:
- nodeids = [ db.status.lookup(name) for name in ['chatting', 'unread'] ] db.filter(None, {'status':nodeids})
- dbw.filter(None, {'status':['chatting', 'unread']})
8. Regular expression search::
- - With dbwrapper:
- To lookup all issues of which the title starts witch 'roundup':
- dbw.issue.re.search('^roundup', dbwrapper.IGNORECASE)
- To lookup all issues of which the title starts witch 'roundup':
9. Combination RE search::
- - With dbwrapper:
- To lookup all issues of which the title starts witch 'roundup' and have status 'unread':
- lw = dbw.issue.filter(None, {'status':'unread'}) lw.re.search('^roundup', dbwrapper.IGNORECASE)
- lw = dbw.issue.re.search('^roundup', dbwrapper.IGNORECASE) lw.issue.filter(None, {'status':'unread'})
- To lookup all issues of which the title starts witch 'roundup' and have status 'unread':
10. More search possibilities::
- - With dbwrapper:
- Lookup all issues that have at least one message attached which starts with the word 'roundup':
- flags = dbwrapper.IGNORECASE + dbwrapper.MULTILINE lw = dbw.msg.re.search('\Aroundup', flags, column='content') dbw.issue.find(messages=lw)
- Lookup all issues that have at least one message attached which starts with the word 'roundup':
11. Iteration::
- - With dbwrapper:
- The next code will step through all nodes in all classes and print the content on the screen. Please don't try this on your 10,000 issue tracker. If you do, well I guess you will be spending your day next to the coffee machine.
- for clw in dbw:
- print clw.classname for nodew in clw:
- print " %s"%nodew.plain() for property in nodew:
- print " | %s"%property
- print " %s"%nodew.plain() for property in nodew:
- print clw.classname for nodew in clw:
- for clw in dbw:
- The next code will step through all nodes in all classes and print the content on the screen. Please don't try this on your 10,000 issue tracker. If you do, well I guess you will be spending your day next to the coffee machine.
The Source
- And finally the source code
1 """\
2 Introduction
3 ============
4 Database wrapper implementation.
5 This wrapper can be used to have easy access to RoundUp's
6 hyper database without having to know too much knowledge
7 about the class structures and (multi) links.
8
9 This document contains a few examples. In those cases
10 you will see objects like 'db', 'dbw', 'cl', 'clw'
11 and 'nodew'. These object are:
12 db : hyperdb.Database
13 cl : hyperdb.Class (or FileClass or IssueClass)
14 dbw : dbwrapper.DBWrapper
15 clw : dbwrapper.ClassWrapper
16 nodew : dbwrapper.NodeClass
17 lw : dbwrapper.ListWrapper
18
19
20 Features
21 ========
22
23 Addressing
24 ----------
25 Addressing a specific node in a class is easier with this
26 wrapper. It almost has the looks and feels of the wrapper
27 used in the TAL templates. Also with this wrapper you don't
28 really need to use 'get' methods to get the data. Just address
29 the data as if they are member of a tree with the database
30 object as root. The class objects as limbs and the class
31 properties as leaves. What's left is how to address the right
32 node. Well that can be done with indexing the class (sorry
33 but I don't know were to put that in a tree). As index you
34 can either use the node id as integer or string, but you
35 may also use a key value (if the class has a key column).
36 Here are some examples:
37 - dbw.issue[1].title
38 dbw.issue['1'].title
39 Both these examples will return the title of issue 1.
40 - dbw.priority['urgent'].name
41 This example will return 'urgent'. Not really
42 meaningful, but it's only meant as an example.
43 - dbw.status['unread'].id
44 Will return the nodeid of status node 'unread'.
45
46 In general the benefit of this wrapper is that you don't
47 really have to know any of the node ids. All 'set' or indexing
48 methods will resolve key values into id's. Even so will all
49 'get' methods return the key values and not the id's when it
50 comes to linked items.
51
52 Searching
53 ---------
54 The wrapper is capable of handling regular expression searches.
55 It will not only return you the found nodes, but it also will
56 return the found matches per node.
57
58
59 Usage
60 =====
61 To use the wrapper you need to place it somewhere. In our case
62 we placed it in a sub-folder of RoundUp's core code which remains
63 in the 'site-packages' folder of the Python library tree.
64 In this /lib/site-packages/roundup we created a folder named
65 'extensions'. This is about conform the standard that Richard uses
66 for additional stuff in tracker templates (we look at the wrapper
67 as additional stuff for the RoundUp core code). In other words
68 the complete path to the wrapper will be:
69 /lib/site-packages/roundup/extensions/dbwrapper.py
70 It will be easiest if the wrapper can be imported as sub package.
71 This will require a '__init__.py' file in that directory to. The
72 content of that file will only be this line:
73 __all__ = [ 'dbwrapper' ]
74
75 What's left is importing the wrapper in your Python sources (e.g.
76 detectors and action handlers). You do that by adding the line:
77 from roundup.extensions import dbwrapper
78 to all sources in which you want to use the wrapper.
79
80 There's only one comment left. As you can see, the wrapper has a
81 lot of documentation strings inside. Therefor we suggest to run
82 all scripts with the '-OO' option passed to Python's interpreter.
83 This will prevent that your scripts will load these documentation
84 strings too. If you're not sure, then please read section
85 '6.1.2 "Compiled" Python files' of the tutorial in the Python
86 documentation.
87
88 Detectors
89 ---------
90 The wrapper can easily be used in a detector without having to
91 change anything to RoundUp's core code.
92 This is what you do in an auditor:
93 def auditor(db, cl, nodeid, newvalues):
94 dbw = dbwrapper.DBWrapper(db)
95 clw = dbwrapper.ClassWrapper(dbw, cl)
96 nodew = dbwrapper.NodeWrapper(clw, nodeid)
97
98 And this in a reactor:
99 def reactor(db, cl, nodeid, oldvalues):
100 dbw = dbwrapper.DBWrapper(db)
101 clw = dbwrapper.ClassWrapper(dbw, cl)
102 nodew = dbwrapper.NodeWrapper(clw, nodeid)
103
104 As you can see, they are both the same.
105 In both cases 'dbw' will be the root object of the tree.
106 'clw' will be a wrapper around hyperdb object 'cl'. In most cases
107 you don't really need that one, but it is needed to create the
108 node wrapper 'nodew'.
109
110 In case of an auditor, 'nodeid' can be 'None' if the auditor was
111 fired by a 'create' operation. In that case 'nodew' will be a
112 phantom node which doesn't have any properties (except 'id' which
113 will be 'None'). A test like:
114 nodew is None
115 will return True on phantom nodes. It will return False if the node
116 isn't a phantom node but a real living one (auditor fired by 'set'
117 operation).
118
119 Action handlers
120 ---------------
121 The wrapper is almost used the same way in an action handler as it
122 is used in a detector.
123 This is what you do:
124 def handler(self):
125 dbw = dbwrapper.DBWrapper(self.db)
126 clw = dbwrapper.ClassWrapper(dbw, self.cl)
127 nodew = dbwrapper.NodeWrapper(clw, self.nodeid)
128
129
130 Examples
131 ========
132 Here are a few more examples what dbwrapper can do for you:
133 1. Reading a linked property:
134 - With RoundUp's hyperdb:
135 unread_id = db.status.lookup('unread')
136 db.status.get(unread_id, 'order')
137
138 - With dbwrapper:
139 dbw.status['unread'].order
140
141 2. Changing a linked property:
142 - With RoundUp's hyperdb:
143 unread_id = db.status.lookup('unread')
144 db.issue.set('1', status=unread_id)
145
146 - With dbwrapper:
147 dbw.issue[1].status = 'unread'
148
149 3. Adding a link to a multi linked property:
150 - With RoundUp's hyperdb:
151 topics = db.issue.get('1', 'topic')
152 keyword_id = db.keyword.lookup('DBWrapper')
153 topics.append(keyword_id)
154 db.issue.set('1', topic=topics)
155
156 - With dbwrapper:
157 dbw.issue[1].topic += 'DBWrapper'
158 or:
159 dbw.issue[1].topic += ['DBWrapper']
160
161 4. Removing a link from a multi linked property:
162 - With RoundUp's hyperdb:
163 topics = db.issue.get('1', 'topic')
164 keyword_id = db.keyword.lookup('DBWrapper')
165 topics.remove(keyword_id)
166 db.issue.set('1', topic=topics)
167
168 - With dbwrapper:
169 dbw.issue[1].topic -= 'DBWrapper'
170 or:
171 dbw.issue[1].topic -= ['DBWrapper']
172
173 5. Duplicating an issue with dbwrapper:
174 - With dbwrapper:
175 issue = db.issue[1].properties
176 issue['title'] = 'Copy of %s'%issue['title']
177 db.issue += issue
178
179 6. Retiring a node:
180 - With RoundUp's hyperdb:
181 nodeid = db.status.lookup('chatting')
182 db.status.retire(nodeid)
183
184 - With dbwrapper:
185 del dbw.status['chatting']
186
187 7. Filtering:
188 - With RoundUp's hyperdb:
189 nodeids = [ db.status.lookup(name) for name in ['chatting', 'unread'] ]
190 db.filter(None, {'status':nodeids})
191
192 - With dbwrapper:
193 dbw.filter(None, {'status':['chatting', 'unread']})
194
195 8. Regular expression search:
196 - With dbwrapper:
197 To lookup all issues of which the title starts witch 'RoundUp':
198 dbw.issue.re.search('^RoundUp', dbwrapper.IGNORECASE)
199
200 9. Combination RE search:
201 - With dbwrapper:
202 To lookup all issues of which the title starts witch 'RoundUp'
203 and have status 'unread':
204 lw = dbw.issue.filter(None, {'status':'unread'})
205 lw.re.search('^RoundUp', dbwrapper.IGNORECASE)
206 The next will have the same result but takes longer:
207 lw = dbw.issue.re.search('^RoundUp', dbwrapper.IGNORECASE)
208 lw.issue.filter(None, {'status':'unread'})
209
210 10. More search possibilities:
211 - With dbwrapper:
212 Lookup all issues that have at least one message attached which
213 starts with the word 'RoundUp':
214 flags = dbwrapper.IGNORECASE + dbwrapper.MULTILINE
215 lw = dbw.msg.re.search('\ARoundUp', flags, column='content')
216 dbw.issue.find(messages=lw)
217
218 11. Iteration:
219 - With dbwrapper:
220 The next code will step trough all nodes in all classes and print
221 the content on the screen. Please don't try this on your 10,000
222 issue tracker. If you do, well I guess you will be spending your
223 day next to the coffee machine.
224 for clw in dbw:
225 print clw.classname
226 for nodew in clw:
227 print " %s"%nodew.plain()
228 for property in nodew:
229 print " | %s"%property
230 """
231
232 __docformat__ = 'restructuredtext'
233
234 import types, os
235
236 from re import IGNORECASE, DOTALL, LOCALE, MULTILINE, UNICODE, VERBOSE
237 from re import I, S, L, M, U, X
238
239 from roundup import instance, hyperdb, date
240
241
242 isHyperDB = lambda object: isinstance(object, hyperdb.Database)
243 isHyperDBClass = lambda object: isinstance(object, hyperdb.Class)
244 isDB = lambda object: isinstance(object, DBWrapper)
245 isClass = lambda object: isinstance(object, ClassWrapper)
246 isNode = lambda object: isinstance(object, NodeWrapper)
247
248
249
250 def to_hyperdb(*nodes):
251 """\
252 Converts a list (or single node) of dbwrapper nodes to a list of
253 hyperdb compliant nodes.
254 """
255
256 result = []
257
258 if nodes:
259 if len(nodes) == 1 \
260 and (isinstance(nodes[0], types.ListType) \
261 or isinstance(nodes[0], types.TupleType)):
262 nodes = nodes[0]
263
264 for node in nodes:
265 if isinstance(node, types.TupleType) \
266 and len(node) == 2:
267 # regular expression list
268 node = node[0]
269
270 if not isNode(node):
271 raise ValueError, \
272 "Node '%s' isn't an instance of 'NodeWrapper'."%node
273
274 result.append(str(node._id))
275
276 return result
277
278
279
280 class DBWrapper:
281 """\
282 A wrapper around RoundUp's hyperdb.Database.
283
284 Initialization
285 ==============
286 There are three methods to initialize the DBWrapper:
287 1. dbw = dbwrapper.DBWrapper(<tracker home>)
288 In this case the wrapper will open the database
289 belonging to <tracker_home>. On destruction it
290 will always close the database.
291 This form is useful in separate scripts which
292 need to access the RoundUp database.
293
294 2. dbw = dbwrapper.DBWrapper()
295 The wrapper will open the database for you if
296 the environment has a variable TRACKER_HOME
297 which points to a valid tracker home folder.
298 On destruction the database will be closed.
299 This form is useful in separate scripts which
300 need to access the RoundUp database.
301
302 3. dbw = dbwrapper.DBWrapper(<hyperdb.Dtabase object>)
303 The wrapper will not open the database, but will
304 use an already opened database. The database
305 object is passed as parameter to the wrapper and
306 should be an opened hyperdb.Database instance.
307 The wrapper will never close the database.
308 This form is useful in detectors and action
309 handlers.
310
311 Usage
312 =====
313 You have full access to the database as soon as the
314 DBWrapper is initialized. To access a class, just access
315 it as being a member of the wrapper (same as with hyperdb).
316 Syntax:
317 <DBWrapper>.<class name>
318 The returned object will be an initialized ClassWrapper
319 object.
320
321 Examples:
322 dbw.issue
323 dbw.status
324
325 Transparency
326 ============
327 Members defined in hyperdb.Database, but not
328 defined in this 'DBWrapper' class can still be
329 used as if they are member of this class.
330 """
331
332 def __init__(self, arg=None, *user):
333 """\
334 Initiate a wrapper around RoundUp's hyperdb.Database.
335
336 "arg" is used to specify the database.
337 It can have three different types:
338 1 - an instance of an open RoundUp hyperdb.Database.
339 This is the most common usage if the wrapper is
340 used within any detector or action handler.
341 2 - a valid tracker home path.
342 This allows us to use the wrapper in any external
343 script without having to open a tracker instance
344 and the database before using the wrapper.
345 If used like this, the destructor of this class
346 will close the database to prevent pending open
347 database links.
348 3 - not specified.
349 This is almost similar with type 2 except that
350 environment variable TRACKER_HOME will be used
351 to open the database.
352
353 "user" has only meaning if 'arg' is of type 2 or 3.
354 In those cases the database will be opened as being 'user'.
355 Default it opens the database as 'admin'.
356 """
357
358 if isHyperDB(arg):
359 # arg is a roundup database instance
360 # no need to open the database
361 self._instance = None
362 self._db = arg
363
364 else:
365 if isinstance(arg, types.StringType):
366 # arg is a string instance
367 # we assume it is a valid tracker_home location
368 # and we will use it to open the database
369 tracker_home = arg
370
371 elif arg is None:
372 # arg is None -> no argument was passed
373 # user TRACKER_HOME environment variable
374 # to open the database
375 tracker_home = os.getenv('TRACKER_HOME')
376
377 else:
378 # and what now?
379 raise TypeError, \
380 "No 'hyperdb' to access"
381
382 if not user:
383 user = ('admin',)
384
385 self._instance = instance.open(tracker_home)
386 self._db = self._instance.open(user[0])
387
388 # create storage for classes
389 self._classes = {}
390 for classname in self._db.getclasses():
391 self._classes[classname] = {}
392
393 def __del__(self):
394 """\
395 Closes the RoundUp database if it was opened by the wrapper
396 it selves (initiated with an 'arg' of type 2 or 3)
397 """
398
399 # restore possible turned off detectors
400 for classname in self._db.getclasses():
401 # restore auditors
402 if self._classes[classname].has_key('auditors'):
403 self._db.getclass(classname).auditors = \
404 self._classes[classname]['auditors']
405
406 # restore reactors
407 if self._classes[classname].has_key('reactors'):
408 self._db.getclass(classname).reactors = \
409 self._classes[classname]['reactors']
410
411 # make sure we close the database if we did open it ourselves
412 if not self._instance is None:
413 self._db.close()
414
415 def __repr__(self):
416 """\
417 Slightly more useful representation
418 """
419
420 return '''<dbwrapper.DBWrapper '%s'>'''%self._db
421
422 def __str__(self):
423 """\
424 Slightly more useful representation
425 """
426
427 return self.__repr__()
428
429 def __getattr__(self, attr):
430 """\
431 Grant access to ClassWrapper objects of all
432 classes like they are member of this class.
433
434 It also creates transparency to hyperdb.Database
435 members if they aren't defined in the DBWrapper
436 class.
437 """
438
439 # If 'attr' is a member, then access that member
440 if self.__dict__.has_key(attr):
441 value = self.__dict__[attr]
442
443 # If 'attr' is a hyperdatabse class, wrap it in
444 # a 'ClassWrapper'
445 elif attr in self._db.getclasses():
446 value = ClassWrapper(self, attr)
447
448 # If 'attr' is a member of class 'Database', return
449 # the address of that member to create the transparency
450 else:
451 value = getattr(self._db, attr)
452
453 return value
454
455 def __setattr__(self, attr, value):
456 """\
457 Prevent that attributes which refer to ClassWrapper
458 objects are overwritten by a wrong assign statement.
459 """
460
461 # If 'attr' is a member or member to be,
462 # then access that member
463 if attr in ['_instance', '_db', '_classes'] \
464 or self.__dict__.has_key(attr):
465 self.__dict__[attr] = value
466
467 # If 'attr' is a hyperdatabse class, wrap it in
468 # a 'ClassWrapper'
469 elif attr in self._db.getclasses():
470 # Check if it isn't the result of 'ClassWrapper.__iadd__'
471 if not isClass(value) or value._cl.classname != attr:
472 raise ValueError, \
473 "'%s' can't be assigned any value"%attr
474
475 # If 'attr' is a member of class 'Database', return
476 # the address of that member to create the transparency
477 else:
478 setattr(self._db, attr, value)
479
480 def __iter__(self):
481 """\
482 Create iteration of all classes as ClassWrapper's.
483 """
484
485 return iter( [ClassWrapper(self, classname) \
486 for classname in self._db.getclasses()] )
487
488 def getclass(self, classname):
489 """\
490 Get the ClassWrapper object representing a particular class.
491 """
492
493 return self.__getattr__(classname)
494
495
496
497 class ClassWrapper:
498 """\
499 A wrapper around a RoundUp hyperdb.Class.
500
501 Introduction
502 ============
503 In most cases you don't need to initialize a
504 ClassWrapper because the DBWrapper will do that
505 for you where needed. But sometimes it can be
506 more convenient to initialize one yourself.
507
508 Initialization
509 ==============
510 There are three methods to initialize a ClassWrapper:
511 1. clw = dbwrapper.ClassWrapper(dbw, <class name>)
512 A wrapper will be created for class <class name>.
513
514 2. clw = dbwrapper.ClassWrapper(dbw, <hyperdb.Class>)
515 A wrapper will be created for the hyperdb.Class object.
516
517 3. clw = dbwrapper.ClassWrapper(dbw, <ClassWrapper>)
518 A wrapper will be created for the ClassWrapper object.
519 This won't make much sense because the wrapper was already there.
520
521 Usage
522 =====
523 The wrapper behaves like a dictionary, meaning you can access the
524 nodes by indexing them as dictionary items.
525 Syntax:
526 <DBWrapper>.<class name>[<key>|<nodeid>|<NodeWrapper>]
527 As index you can either use the node id as integer or string,
528 a key value (if the class has a key column) or an instance of
529 NodeWrapper.
530 The returned value is a NodeWrapper object.
531
532 Examples:
533 dbw.status['chatting']
534 dbw.issue[1]
535 dbw.priority['3']
536 dbw.msg[nodew]
537
538 Some of the methods in this class do have a "key"
539 parameter. In all these cases this parameter can
540 have one of the next types:
541 1 - an integer
542 The 'key' will be treated as the id of the node
543 and used for the indexing.
544 2 - a string
545 The 'key' will be treated as key argument of the
546 node and used to lookup the node id.
547 If the lookup action raises a KeyError and 'key'
548 only contains digits, the integer value of 'key'
549 will be used as the node id.
550 In both cases the node id is used for the indexing.
551 3 - an instance of NodeWrapper
552 The node id will be obtained from the NodeWrapper
553 class and used for the indexing.
554
555 Transparency
556 ============
557 Members defined in the hyperdb.Class, but not
558 defined in this 'ClassWrapper' class can still
559 be used as if they are member of this class.
560 """
561
562 class RegExpClass:
563 """\
564 A class with regular expression methods that will filter nodes
565 from hyperdb.Class. The methods in this class are one to one
566 with the regular expression methods in python module 're'.
567 """
568
569 def __init__(self, owner):
570 """\
571 Initialize class
572
573 "owner" must be of type 'ClassWrapper'
574 """
575
576 import re
577
578 if not isClass(owner):
579 raise TypeError, \
580 "Owner object isn't an instance of 'ClassWrapper'"
581
582 self._clw = owner
583 self.__re = re
584
585 def __repr__(self):
586 """\
587 Slightly more useful representation
588 """
589
590 return '''<dbwrapper.ClassWrapper.RegExpClass '%s'>'''% \
591 self._clw._cl
592
593 def __str__(self):
594 """\
595 Slightly more useful representation
596 """
597
598 return self.__repr__()
599
600 def __inner_re(self, re_func, column, nodelist):
601 """\
602 Handles the main reg. expression search.
603
604 "re_func" must contain one of the search methods
605 from standard python module 're'.
606
607 "column" can be used to perform a search on an
608 other column than the column returned by
609 ClassWrapper.getlabel().
610
611 The return value is a list object containing tuples
612 pairs.
613
614 The first index of each tuple contains a NodeWrapper
615 object to a node matching the search.
616
617 The second index of the tuples holds the result object
618 of the corresponding regular expression method passed
619 in "re_func".
620
621 "nodelist" can be used to limit the search within the list
622 of nodes. 'nodelist' may be a list of ids or any list created
623 by any of the search methods in this module.
624 """
625
626 if not column:
627 column = self._clw.getlabel()
628
629 if nodelist is None:
630 list = self._clw._cl.list()
631 else:
632 list = ListWrapper(self._clw, nodelist).getnodeids()
633
634 result = []
635 for nodeid in list:
636 value = self._clw._cl.get(nodeid, column)
637
638 if value is None:
639 value = ''
640
641 res = re_func(value)
642
643 if res:
644 result.append( (NodeWrapper(self._clw, nodeid, False), res) )
645
646 return ListWrapper(self._clw, result)
647
648 def match(self, pattern, flags=0, column=None, nodelist=None):
649 """\
650 Find all nodes in the class that have a result when
651 applying the "pattern" at the start of the string in
652 "column", returning a list object with tuples pairs.
653
654 The first index of each tuple contains a
655 dbwraper.NodeWrapper object of a node that matches
656 with "pattern".
657
658 The second index in the tuples holds the match object.
659
660 "flags" may have the same flags as used/defined in
661 python module 're'. These flags are also available
662 as members of module 'dbwrapper'.
663 (e.g. dbwrapper.IGNORECASE)
664
665 "column" can be used to perform a search on an
666 other column than the column returned by
667 ClassWrapper.getlabel().
668
669 "nodelist" can be used to limit the search within the list
670 of nodes. 'nodelist' may be a list of ids or any list created
671 by any of the search methods in this module.
672 """
673
674 reg = self.__re.compile(pattern, flags)
675
676 return self.__inner_re(reg.match, column, nodelist)
677
678 def search(self, pattern, flags=0, column=None, nodelist=None):
679 """\
680 Find all nodes in the class that have a result when
681 scanning through the string in "column" looking for a
682 match to the "pattern", returning a list object with
683 tuples pairs.
684
685 The first index of each tuple contains a
686 dbwraper.NodeWrapper object of a node that matches
687 with "pattern".
688
689 The second index in the tuples holds the search object.
690
691 "flags" may have the same flags as used/defined in
692 python module 're'. These flags are also available
693 as members of module 'dbwrapper'.
694 (e.g. dbwrapper.IGNORECASE)
695
696 "column" can be used to perform a search on an
697 other column than the column returned by
698 ClassWrapper.getlabel().
699
700 "nodelist" can be used to limit the search within the list
701 of nodes. 'nodelist' may be a list of ids or any list created
702 by any of the search methods in this module.
703 """
704
705 reg = self.__re.compile(pattern, flags)
706
707 return self.__inner_re(reg.search, column, nodelist)
708
709 def findall(self, pattern, flags=0, column=None, nodelist=None):
710 """\
711 Return all nodes in class that have "pattern" matches
712 in the strings in "column". Returned is a list of tuple
713 pairs.
714
715 The first tuple index contains a NodeWrapper object of
716 a matching node.
717
718 The second tuple index contains a list of all
719 non-overlapping matches in the node's "column" string.
720
721 If one or more groups are present in the "pattern", the
722 second tuple index will contain a list of groups; this
723 will be a list of tuples if the "pattern" has more than
724 one group.
725
726 "flags" may have the same flags as used/defined in
727 python module 're'. These flags are also available
728 as members of module 'dbwrapper'.
729 (e.g. dbwrapper.IGNORECASE)
730
731 "column" can be used to perform a search on an
732 other column than the column returned by
733 ClassWrapper.getlabel().
734
735 "nodelist" can be used to limit the search within the list
736 of nodes. 'nodelist' may be a list of ids or any list created
737 by any of the search methods in this module.
738 """
739
740 reg = self.__re.compile(pattern, flags)
741
742 return self.__inner_re(reg.findall, column, nodelist)
743
744
745 def __init__(self, parent, cl):
746 """\
747 Initiate a wrapper around a hyperdb.Class.
748
749 "parent" is the DBWrapper that owns this ClassWrapper.
750
751 "cl" is used to specify the class.
752 It can have three different types:
753 1 - an instance of a hyperdb.Class.
754 2 - an instance of a ClassWrapper.
755 3 - the class name.
756 """
757
758 if not isDB(parent):
759 raise TypeError, \
760 "Parent object isn't an instance of 'DBWrapper'"
761
762 self._dbw = parent
763 self._db = parent._db
764
765 if isHyperDBClass(cl):
766 self._cl = cl
767 elif isClass(cl):
768 self._cl = cl._cl
769 else:
770 self._cl = self._db.getclass(cl)
771
772 self.re = self.RegExpClass(self)
773
774 self.__lastid = None
775
776 def __repr__(self):
777 """\
778 Slightly more useful representation
779 """
780
781 return '''<dbwrapper.ClassWrapper '%s'>'''%self._cl
782
783 def __str__(self):
784 """\
785 Slightly more useful representation
786 """
787
788 return self.__repr__()
789
790 def __call__(self, *nodeids):
791 """\
792 Converts a list (or single id) of hyperdb node ids to a list of
793 dbwrapper compliant nodes.
794 """
795
796 result = ListWrapper(self)
797
798 if nodeids:
799 if len(nodeids) == 1 \
800 and (isinstance(nodeids[0], types.ListType) \
801 or isinstance(nodeids[0], types.TupleType)):
802 nodeids = nodeids[0]
803
804 for nodeid in nodeids:
805 result.append(NodeWrapper(self, int(nodeid)))
806
807 return result
808
809 def __getattr__(self, attr):
810 """\
811 Create transparency to hyperdb.Class members
812 if they aren't defined in the ClassWrapper
813 class.
814 """
815
816 if self.__dict__.has_key(attr):
817 value = self.__dict__[attr]
818 else:
819 value = getattr(self._cl, attr)
820
821 return value
822
823 def __len__(self):
824 """\
825 Number of nodes in this class (excluding retired nodes).
826 """
827
828 return len(self._cl.list())
829
830 def __contains__(self, key):
831 """\
832 Determine if the class has a given node.
833 """
834
835 return self.has_key(key)
836
837 def __getitem__(self, key):
838 """\
839 Grant get access to all class nodes by indexing them as
840 dictionary items and wrapping them in a NodeWrapper.
841 """
842
843 return NodeWrapper(self, key, False)
844
845 def __setitem__(self, key, columns):
846 """\
847 Grant set access to all class nodes by indexing them as
848 dictionary items and wrapping them in a NodeWrapper.
849
850 "columns" must be a dictionary containing class properties
851 as the keys. The values in the dictionary are used as
852 values for the properties.
853 """
854
855 if isinstance(columns, types.DictType):
856 node = NodeWrapper(self, key, False)
857
858 for key in columns.keys():
859 setattr(node, key, columns[key])
860
861 else:
862 raise TypeError, \
863 "Item can only be assigned a dictionary"
864
865 def __delitem__(self, key):
866 """\
867 Retire a class node by using the python 'del' command and
868 indexing the node as dictionary item.
869 """
870
871 self._cl.retire( str(self.getid(key)) )
872
873 def __iadd__(self, columns):
874 """\
875 Add a new node to the class.
876
877 "columns" must be a dictionary containing class properties
878 as the keys. The values in the dictionary are used as
879 values for the properties.
880 """
881
882 if isinstance(columns, types.DictType):
883 self.create(**columns)
884 else:
885 raise TypeError, \
886 "Item can only be assigned a dictionary"
887
888 return self
889
890 def __iter__(self):
891 """\
892 Create iteration of all nodes as NodeWrapper's.
893 """
894
895 return iter(self.list())
896
897 def getid(self, key):
898 """\
899 Get the node id of the node that matches the key.
900
901 "key" can have three different types:
902 1 - an integer
903 The 'key' will be treated as the id of the node
904 and used for the indexing.
905 2 - a string
906 The 'key' will be treated as key argument of the
907 node and used to lookup the node id.
908 If the lookup action raises a KeyError and 'key'
909 only contains digits, the integer value of 'key'
910 will be used as the node id.
911 In both cases the node id is used for the indexing.
912 3 - an instance of NodeWrapper
913 The node id will be obtained from the NodeWrapper
914 class and used for the indexing.
915 """
916
917 if isinstance(key, types.IntType):
918 nodeid = key
919
920 elif isNode(key):
921 nodeid = key._id
922
923 else:
924 try:
925 nodeid = self._cl.lookup(key)
926
927 except (KeyError, TypeError):
928 if isinstance(key, types.StringType) \
929 and key.isdigit() \
930 and self._cl.hasnode(key):
931 nodeid = int(key)
932
933 else:
934 raise IndexError, \
935 "Class '%s' doesn't have a node that can be \
936 indexed with '%s'"%(self._cl.classname, key)
937
938 else:
939 nodeid = int(nodeid)
940
941 return nodeid
942
943 def auditors(self, enable):
944 """\
945 Enable/Disable auditors for this class.
946 """
947
948 p = self._dbw._classes[self._cl.classname]
949
950 if enable:
951 if p.has_key('auditors'):
952 self._cl.auditors = p['auditors']
953 del p['auditors']
954 else:
955 if not p.has_key('auditors'):
956 p['auditors'] = dict(self._cl.auditors)
957 self._cl.auditors = {
958 'create': [],
959 'set': [],
960 'retire': [],
961 'restore': []
962 }
963
964 def reactors(self, enable):
965 """\
966 Enable/Disable reactors for this class.
967 """
968
969 p = self._dbw._classes[self._cl.classname]
970
971 if enable:
972 if p.has_key('reactors'):
973 self._cl.reactors = p['reactors']
974 del p['reactors']
975 else:
976 if not p.has_key('reactors'):
977 p['reactors'] = dict(self._cl.reactors)
978 self._cl.reactors = {
979 'create': [],
980 'set': [],
981 'retire': [],
982 'restore': []
983 }
984
985 def setlabel(self, label=None):
986 """\
987 Set an alternative label to be used as label property.
988
989 If label is None, the result of hyperdb 'labelprop()' will be
990 used as label.
991 """
992
993 if not label is None:
994 props = self._cl.getprops()
995
996 if label in props.keys():
997 if isinstance(props[label], hyperdb.String):
998 self._dbw._classes[self._cl.classname]['label'] = label
999 else:
1000 raise TypeError, \
1001 "'label' should be a string type column"
1002
1003 else:
1004 raise AttributeError, \
1005 "Class '%s' doesn't have a property '%s'"% \
1006 (self._cl.classname, label)
1007
1008 else:
1009 del self._dbw._classes[self._cl.classname]['label']
1010
1011 def getlabel(self):
1012 """\
1013 Get the label to be used as label property.
1014
1015 If an alternative label is set, it will be returned.
1016 """
1017
1018 p = self._dbw._classes[self._cl.classname]
1019
1020 return p.get('label', self._cl.labelprop())
1021
1022 def create(self, **propvalues):
1023 """\
1024 Create a new node of this class and return its id.
1025
1026 The keyword arguments in 'propvalues' map property names to values.
1027
1028 The values of arguments must be acceptable for the types of their
1029 corresponding properties or a TypeError is raised.
1030
1031 If this class has a key property, it must be present and its value
1032 must not collide with other key strings or a ValueError is raised.
1033
1034 Any other properties on this class that are missing from the
1035 'propvalues' dictionary are set to None.
1036
1037 If an id in a link or multilink property does not refer to a valid
1038 node, an IndexError is raised. Valid are node ids (as integer or
1039 as string), key values (for links to classes that have a key column)
1040 or a NodeWrapper object.
1041 """
1042
1043 props = self._cl.getprops()
1044
1045 creator = {}
1046 for column, value in propvalues.items():
1047 if props.has_key(column):
1048 if isinstance(props[column], hyperdb.Link):
1049 cl = self._db.getclass(props[column].classname)
1050 wrapper = ClassWrapper(self._dbw, cl)
1051 creator[column] = str(wrapper.getid(value))
1052
1053 elif isinstance(props[column], hyperdb.Multilink):
1054 cl = self._db.getclass(props[column].classname)
1055 wrapper = ClassWrapper(self._dbw, cl)
1056
1057 if not isinstance(value, types.ListType) \
1058 and not isinstance(value, types.TupleType):
1059 value = [value]
1060
1061 creator[column] = [ str(wrapper.getid(key)) for key in value ]
1062
1063 elif isinstance(props[column], hyperdb.Interval) \
1064 and not isinstance(value, date.Interval):
1065 creator[column] = date.Interval(value)
1066
1067 elif isinstance(props[column], hyperdb.Date) \
1068 and not isinstance(value, date.Date):
1069 creator[column] = date.Date(value)
1070
1071 else:
1072 creator[column] = value
1073
1074 else:
1075 creator[column] = value
1076
1077 self.__lastid = self._cl.create(**creator)
1078
1079 return self.__lastid
1080
1081 def get(self, key, propname):
1082 """\
1083 Get the value of a property on an existing node of this class.
1084
1085 'propname' must be the name of a property of this class or a
1086 KeyError is raised.
1087 """
1088
1089 node = self.__getitem__(key)
1090
1091 return getattr(node, propname)
1092
1093 def set(self, key, **propvalues):
1094 """\
1095 Modify a property on an existing node of this class.
1096
1097 Each key in 'propvalues' must be the name of a property of this
1098 class or a KeyError is raised.
1099
1100 All values in 'propvalues' must be acceptable types for their
1101 corresponding properties or a TypeError is raised.
1102
1103 If the value of the key property is set, it must not collide with
1104 other key strings or a ValueError is raised.
1105
1106 If the value of a Link or Multilink property contains an invalid
1107 node id, a ValueError is raised.
1108 """
1109
1110 self.__setitem__(key, propvalues)
1111
1112 def list(self):
1113 """\
1114 Return a list of NodeWrappers of the active nodes in this class.
1115 """
1116
1117 id_list = [ int(nodeid) for nodeid in self._cl.list() ]
1118 id_list.sort()
1119
1120 return ListWrapper(self,
1121 [ NodeWrapper(self, int(nodeid), False) for nodeid in id_list ] )
1122
1123 def filter(self, search_matches, filterspec, sort=(None,None),
1124 group=(None,None), nodelist=None):
1125 """\
1126 Return a list of the NodeWrappers of the active nodes in this class
1127 that match the 'filter' spec, sorted by the group spec and then the
1128 sort spec.
1129
1130 "filterspec" is {propname: value(s)}
1131 'value(s)' may be any node id as integer or string or a key value
1132 (for classes that have a key column).
1133
1134 "sort" and "group" are (dir, prop) where dir is '+', '-' or None
1135 and prop is a prop name or None
1136
1137 "search_matches" is {nodeid: marker}
1138
1139 "nodelist" can be used to limit the search within the list
1140 of nodes. 'nodelist' may be a list of ids or any list created
1141 by any of the search methods in this module.
1142
1143 The filter must match all properties specified - but if the
1144 property value to match is a list, any one of the values in the
1145 list may match for that property to match.
1146 """
1147
1148 props = self._cl.getprops()
1149
1150 for column in filterspec.keys():
1151 if isinstance(props[column], hyperdb.Link):
1152 cl = self._db.getclass(props[column].classname)
1153 parent = ClassWrapper(self._dbw, cl)
1154 node = NodeWrapper(parent, filterspec[column])
1155 filterspec[column] = str(node._id)
1156
1157 elif isinstance(props[column], hyperdb.Multilink):
1158 cl = self._db.getclass(props[column].classname)
1159 parent = ClassWrapper(self._dbw, cl)
1160 list = ListWrapper(parent, filterspec[column])
1161 filterspec[column] = list.getnodeids()
1162
1163 if not nodelist is None:
1164 filterspec['id'] = ListWrapper(self, nodelist).getnodeids()
1165
1166 return ListWrapper(self,
1167 [ NodeWrapper(self, int(nodeid), False) for nodeid in self._cl.filter(search_matches, filterspec, sort, group) ])
1168
1169 def find(self, **propspec):
1170 """\
1171 Get the NodeWrappers of items in this class which link to the given
1172 items.
1173
1174 'propspec' consists of keyword args propname=itemid or
1175 propname=key
1176 'propname' must be the name of a property in this class, or a
1177 KeyError is raised. That property must be a Link or
1178 Multilink property, or a TypeError is raised.
1179
1180 "propspec" can also be passed a list with results from anu of the
1181 search methods in this wrapper (also regular expression search
1182 results can be passed 1-to-1).
1183
1184 Any item in this class whose 'propname' property links to any of the
1185 itemids will be returned. Used by the full text indexing, which knows
1186 that "foo" occurs in msg1, msg3 and file7, so we have hits on these
1187 issues:
1188 db.issue.find(messages=[1,3], files=[7])
1189 """
1190
1191 for column, values in propspec.items():
1192 if isinstance(values, ListWrapper):
1193 values.simple()
1194
1195 elif not isinstance(values, types.ListType) \
1196 and not isinstance(values, types.TupleType):
1197 values = [values]
1198
1199 links = {}
1200 for key in values:
1201 links[str(self.getid(key))] = 1
1202
1203 propspec[column] = dict(links)
1204
1205 id_list = [ int(nodeid) for nodeid in self._cl.find(**propspec) ]
1206 id_list.sort()
1207
1208 return ListWrapper(self,
1209 [ NodeWrapper(self, nodeid, False) for nodeid in id_list ])
1210
1211 def newid(self):
1212 """\
1213 Generate a new id for this class.
1214 """
1215
1216 return self._db.newid(self._cl.classname)
1217
1218 def newnodeid(self):
1219 """\
1220 Return the id of the last created node.
1221 """
1222
1223 return self.__lastid
1224
1225 def getnodeids(self, retired=None):
1226 """\
1227 Retrieve all NodeWrappers of the nodes for a particular Class.
1228
1229 if 'retired' is True, all retired nodes will be returned.
1230 """
1231
1232 id_list = [ int(nodeid) for nodeid in self._cl.getnodeids(retired) ]
1233 id_list.sort()
1234
1235 return id_list
1236
1237 def hasnode(self, key):
1238 """\
1239 Determine if the class has a given node (also true on retired nodes).
1240 """
1241
1242 try:
1243 nodeid = self.getid(key)
1244
1245 except:
1246 label = self._cl.getkey()
1247
1248 if label \
1249 and isinstance(key, types.StringType):
1250 value = None
1251
1252 for nodeid in self._cl.getnodeids(True):
1253 value = self._cl.get(nodeid, label)
1254
1255 if value == key:
1256 break
1257
1258 if not value is None \
1259 and value == key:
1260 result = True
1261 else:
1262 result = False
1263
1264 else:
1265 result = False
1266
1267 else:
1268 result = self._cl.hasnode( str(nodeid) )
1269
1270 return result
1271
1272 def lookup(self, keyvalue):
1273 """\
1274 Locate a particular node by its key property and return it in a
1275 NodeClass object.
1276
1277 If this class has no key property, a TypeError is raised. If the
1278 'keyvalue' matches one of the values for the key property among
1279 the nodes in this class, the matching node's id is returned;
1280 otherwise a KeyError is raised.
1281 """
1282
1283 return NodeWrapper(self, int(self._cl.lookup(keyvalue)), False)
1284
1285 def search(self, value, column=None, nodelist=None):
1286 """\
1287 Lookup all nodes that have a 100% match with 'value' and
1288 return them in a list.
1289
1290 "value" is the pattern to look for.
1291
1292 "column" can be set to any string property. A TypeError is
1293 raised if 'column' isn't a string property. The label column
1294 will be used if 'column' is omitted.
1295
1296 "nodelist" can be used to limit the search within the list
1297 of nodes. 'nodelist' may be a list of ids or any list created
1298 by any of the search methods in this module.
1299 """
1300
1301 if not isinstance(value, types.StringType):
1302 raise TypeError, \
1303 "'value' should be a string"
1304
1305 if not column:
1306 column = self.getlabel()
1307
1308 props = self._cl.getprops()
1309 if not isinstance(props[column], hyperdb.String):
1310 raise TypeError, \
1311 "'column' should be a string type column"
1312
1313 if nodelist is None:
1314 list = self._cl.filter(None, {column:value})
1315 else:
1316 list = ListWrapper(self, nodelist).getnodeids()
1317
1318 list.sort()
1319
1320 value = value.lower()
1321 result = []
1322 for nodeid in list:
1323 if self._cl.get(nodeid, column).lower() == value:
1324 result.append( NodeWrapper(self, int(nodeid), False) )
1325
1326 return ListWrapper(self, result)
1327
1328 def is_retired(self, key):
1329 """\
1330 Return true if the node is retired.
1331 """
1332
1333 return self._cl.is_retired( str(self.getid(key)) )
1334
1335 def safeget(self, key, propname, default=None):
1336 """\
1337 Safely get the value of a property on an existing node of this class.
1338
1339 Return 'default' if the node doesn't exist.
1340 """
1341
1342 value = getattr(NodeWrapper(self, key), propname, default)
1343
1344 if value is None \
1345 or (isinstance(value, types.ListType) and not value):
1346 value = default
1347
1348 return value
1349
1350 def has_key(self, key):
1351 """\
1352 Determine if the class has a given node, but then in
1353 a dictionary known method (false on retired nodes).
1354 """
1355
1356 try:
1357 nodeid = self.getid(key)
1358
1359 except (KeyError, TypeError, IndexError):
1360 result = False
1361
1362 else:
1363 result = str(nodeid) in self._cl.list()
1364
1365 return result
1366
1367 def keys(self):
1368 """\
1369 Get a list with all key values of this class. If no key is defined
1370 for this class, a list of ids will be returned.
1371 """
1372
1373 key = self._cl.getkey()
1374
1375 if key:
1376 keys = [ self._cl.get(nodeid, key) for nodeid in self._cl.list() ]
1377 else:
1378 keys = [ int(nodeid) for nodeid in self._cl.list() ]
1379
1380 return keys
1381
1382 def items(self):
1383 """\
1384 Get a list with tuples containing all keys with there NodeWrapper kept
1385 as pairs.
1386 """
1387
1388 return [ (key, NodeWrapper(self, key, False)) for key in self.keys() ]
1389
1390 def pop(self, key):
1391 """\
1392 Get node 'key' and remove (retire) it from this class.
1393 """
1394
1395 value = self.__getitem__(key)
1396 self.__delitem__(key)
1397
1398 return value
1399
1400 def resurrect(self, key):
1401 """\
1402 Bring any deleted (retired) node back alive.
1403 """
1404
1405 retireds = self._cl.getnodeids(retired=1)
1406
1407 if isinstance(key, types.IntType):
1408 nodeid = str(key)
1409 else:
1410 prop = self._cl.getkey()
1411 nodeid = None
1412 for retired_id in retireds:
1413 value = self._cl.get(retired_id, prop)
1414
1415 if value == key:
1416 nodeid = retired_id
1417 break;
1418
1419 if nodeid and nodeid in retireds:
1420 self._cl.restore(nodeid)
1421 else:
1422 raise KeyError, \
1423 "No retired node '%s' in class '%s'"%(key, self._cl.classname)
1424
1425
1426
1427 class NodeWrapper:
1428 """\
1429 A wrapper around one specific node in a
1430 RoundUp hyperdb.Class.
1431
1432 Introduction
1433 ============
1434 In most cases you don't need to initialize a
1435 NodeWrapper because the ClassWrapper will do that
1436 for you where needed. But sometimes it can be
1437 more convenient to initialize one yourself.
1438
1439 Initialization
1440 ==============
1441 There are three methods to initialize a NodeWrapper:
1442 1. nodew = dbwrapper.NodeWrapper(clw, <nodeid>)
1443 A wrapper will be created for node <nodeid>.
1444 <nodeid> may be either an integer or a string.
1445
1446 2. nodew = dbwrapper.NodeWrapper(clw, <key>)
1447 For classes that have a key column, a key value can be
1448 used to create a wrapper for the node indexed by <key>.
1449
1450 3. nodew = dbwrapper.NodeWrapper(clw, <NodeWrapper>)
1451 A wrapper will be created for the NodeWrapper object.
1452 This won't make much sense because the wrapper was already there.
1453
1454 4. nodew = dbwrapper.NodeWrapper(clw, None)
1455 This is a phantom object and can be used to test against None.
1456 This might be convenient in auditors where there isn't a node
1457 if they are fired by the create event.
1458
1459 Usage
1460 =====
1461 All node properties can be accessed as if they are members of this
1462 wrapper object.
1463 The syntax is:
1464 <NodeWrapper>.<property>
1465 The returned type depends on the requested property. In most cases
1466 a simple type is returned (like string and integer), but in case of
1467 a linked property, a NodeWrapper object will be returned for the
1468 node to which the property is linked. In case of multi linked
1469 properties, a ListWrapper object will be returned containing
1470 NodeWrapper objects for all nodes to which the property is linked.
1471
1472 Examples:
1473 nodew.title
1474 nodew.name
1475 nodew.id (read only)
1476 nodew.nodeid (read only) (same as str(nodew.id))
1477
1478 It is also possible to set a property by accessing them as wrapper
1479 members.
1480 The syntax is:
1481 <NodeWrapper>.<property> = <value>
1482 The assigned value depends on the property type. In case of the
1483 simple types (String, Number, Boolean) value will be of the
1484 corresponding Python type (string, integer, boolean).
1485 In case of a linked type, the value can have more than one type:
1486 1. node id as integer
1487 2. node id as string
1488 3. key value (for classes that have a key column)
1489 4. NodeWrapper instance
1490 In case of multi linked type, value must be a list (either Python's
1491 list object or dbwrapper's ListWrapper object) containing node
1492 specifications like mentioned for link type properties (see above).
1493
1494 Examples:
1495 nodew.title = 'My new title'
1496 nodew.status = 'chatting'
1497 nodew.topic = ['Keyword 1', 'Keyword 2', 3, '4']
1498
1499 In case of multi linked properties, new links to other nodes can be
1500 created by simply adding them to the property. Even so can links be
1501 removed by subtracting them from the property.
1502
1503 Examples:
1504 Add:
1505 nodew.topic += 'Keyword 3'
1506 nodew.topic += ['Keyword 4', 'Keyword 5']
1507 Remove:
1508 nodew.topic -= 'Keyword 3'
1509 nodew.topic -= ['Keyword 4', 'Keyword 5']
1510 """
1511
1512 def __init__(self, parent, key, readonly=True):
1513 """\
1514 Initiate a wrapper around a hyperdb.Class for one
1515 specific node.
1516
1517 "parent" is the ClassWrapper that owns this NodeWrapper.
1518
1519 "key" identifies the node.
1520 It can have one of the next types:
1521 1 - an integer
1522 The 'key' will be treated as the id of the node
1523 and used for the indexing.
1524 2 - a string
1525 The 'key' will be treated as key argument of the
1526 node and used to lookup the node id.
1527 If the lookup action raises a KeyError and 'key'
1528 only contains digits, the integer value of 'key'
1529 will be used as the node id.
1530 In both cases the node id is used for the indexing.
1531 3 - an instance of NodeWrapper
1532 The node id will be obtained from the NodeWrapper
1533 class and used for the indexing.
1534 4 - None type
1535 This is a special option implemented to make it
1536 easy usable in detectors.
1537
1538 "readonly" can be set to False to allow set operations
1539 on any of the properties of this node.
1540 """
1541
1542 if not isClass(parent):
1543 raise TypeError, \
1544 "Parent object isn't an instance of 'ClassWrapper'"
1545
1546 self._clw = parent
1547 self._db = parent._db
1548 self._cl = parent._cl
1549 self._readonly = readonly
1550
1551 if key is None:
1552 self._id = None
1553 else:
1554 self._id = parent.getid(key)
1555
1556 if not self._cl.hasnode(str(self._id)):
1557 raise IndexError, \
1558 "Class '%s' doesn't have a node with id '%s'"% \
1559 (self._cl.classname, self._id)
1560
1561 def __repr__(self):
1562 """\
1563 Return a resolved property.
1564 """
1565
1566 import re
1567
1568 if self._id is None:
1569 value = "<dbwrapper.NodeWrapper 'Phantom'>"
1570
1571 else:
1572 value = self.plain()
1573
1574 if isinstance(value, types.StringType):
1575 value = "'%s'"%re.sub("'", "\\'", value)
1576
1577 return str(value)
1578
1579 def __str__(self):
1580 """\
1581 Slightly more useful representation
1582 """
1583
1584 if self._id is None:
1585 value = "<dbwrapper.NodeWrapper 'Phantom'>"
1586
1587 else:
1588 value = str(self.plain())
1589
1590 return value
1591
1592 def __getattr__(self, attr):
1593 """\
1594 Grant easy get access to all properties of this node.
1595 """
1596
1597 # is it a NoneClass member?
1598 if self.__dict__.has_key(attr):
1599 value = self.__dict__[attr]
1600
1601 elif attr == 'id':
1602 value = self._id
1603
1604 elif attr == 'nodeid':
1605 if self._id is None:
1606 value = None
1607 else:
1608 value = str(self._id)
1609
1610 elif attr in ['properties', 'values']:
1611 if self._id is None:
1612 value = None
1613
1614 else:
1615 internals = ('id', 'actor', 'activity', 'creator', 'creation')
1616
1617 props = self._cl.getprops()
1618
1619 value = {}
1620 for prop in props.keys():
1621 if not prop in internals:
1622 data = self._cl.get(str(self._id), prop)
1623
1624 if not data is None \
1625 and (not isinstance(data, types.ListType) or data):
1626 if attr == 'properties':
1627 if isinstance(props[prop], hyperdb.Link):
1628 if data:
1629 cl = self._db.getclass(props[prop].classname)
1630 key = cl.getkey()
1631
1632 if key:
1633 value[prop] = cl.get(data, key)
1634 else:
1635 value[prop] = int(data)
1636
1637 elif isinstance(props[prop], hyperdb.Multilink):
1638 cl = self._db.getclass(props[prop].classname)
1639 key = cl.getkey()
1640
1641 if key:
1642 value[prop] = [ cl.get(nodeid, key) for nodeid in data ]
1643 else:
1644 value[prop] = [ int(nodeid) for nodeid in data ]
1645
1646 elif not isinstance(props[prop], hyperdb.String):
1647 value[prop] = str(data)
1648
1649 else:
1650 value[prop] = data
1651
1652 else:
1653 if isinstance(props[prop], hyperdb.Link):
1654 if data:
1655 value[prop] = int(data)
1656
1657 elif isinstance(props[prop], hyperdb.Multilink):
1658 value[prop] = [ int(nodeid) for nodeid in data ]
1659
1660 else:
1661 value[prop] = data
1662
1663 else:
1664 if self._id is None:
1665 raise ValueError, \
1666 "Phantom nodes do not have an attribute '%s'"%attr
1667
1668 else:
1669 props = self._cl.getprops()
1670
1671 # is it a class property?
1672 if props.has_key(attr):
1673 if isinstance(props[attr], hyperdb.Link):
1674 elementid = self._cl.get(str(self._id), attr)
1675
1676 if elementid:
1677 cl = self._db.getclass(props[attr].classname)
1678 parent = ClassWrapper(self._clw._dbw, cl)
1679 value = NodeWrapper(parent, elementid)
1680 else:
1681 value = None
1682
1683 elif isinstance(props[attr], hyperdb.Multilink):
1684 elements = self._cl.get(str(self._id), attr)
1685 cl = self._db.getclass(props[attr].classname)
1686 parent = ClassWrapper(self._clw._dbw, cl)
1687
1688 value = ListWrapper(parent, True, elements)
1689
1690 else:
1691 if attr == 'id':
1692 value = int(self._cl.get(str(self._id), attr))
1693 else:
1694 value = self._cl.get(str(self._id), attr)
1695
1696 # I don't know you
1697 else:
1698 raise AttributeError, \
1699 "'%s' is not a member of object 'NodeWrapper'"%attr
1700
1701 return value
1702
1703 def __setattr__(self, attr, value):
1704 """\
1705 Grant easy set access to all properties of this node.
1706
1707 If "read only" than all set operations will raise exception
1708 "ValueError".
1709 """
1710
1711 # test if we are a member of NodeClass or if we will become
1712 # a member of NodeClass
1713 if attr in ['_clw', '_db', '_cl', '_id', '_readonly'] \
1714 or self.__dict__.has_key(attr):
1715 self.__dict__[attr] = value
1716
1717 elif attr in ['properties', 'values']:
1718 raise ValueError, \
1719 "'properties' can't be assigned any value"
1720
1721 else:
1722 if self._id is None:
1723 raise ValueError, \
1724 "Phantom nodes do not have an attribute '%s'"%attr
1725
1726 else:
1727 props = self._cl.getprops()
1728
1729 # is it a class property?
1730 if props.has_key(attr):
1731 if not self._readonly:
1732 if isinstance(props[attr], hyperdb.Link):
1733 cl = self._db.getclass(props[attr].classname)
1734 parent = ClassWrapper(self._clw._dbw, cl)
1735
1736 value = str(parent.getid(value))
1737
1738 elif isinstance(props[attr], hyperdb.Multilink):
1739 cl = self._db.getclass(props[attr].classname)
1740 parent = ClassWrapper(self._clw._dbw, cl)
1741
1742 if isinstance(value, types.StringType):
1743 value = [value]
1744
1745 value = ListWrapper(parent, value)
1746
1747 value = [ str(nodeid) for nodeid in value.getnodeids() ]
1748
1749 elif isinstance(props[attr], hyperdb.Interval) \
1750 and not isinstance(value, date.Interval):
1751 value = date.Interval(value)
1752
1753 elif isinstance(props[attr], hyperdb.Date) \
1754 and not isinstance(value, date.Date):
1755 value = date.Date(value)
1756
1757 self._cl.set(str(self._id), **{attr:value})
1758
1759 else:
1760 raise ValueError, \
1761 "This node is marked as readonly and can't \
1762 be assigned and value"
1763
1764 # not a member or class property
1765 else:
1766 raise AttributeError, \
1767 "'%s' is not a member of object 'NodeWrapper'"%attr
1768
1769 def __iter__(self):
1770 """\
1771 Create iteration of all properties in this node. The iteration contains
1772 tuples with the property name on the first index and the value on the
1773 second index. For linked properties an instance to a NodeWrapper is
1774 used and not the value.
1775 """
1776
1777 return iter( [(prop, self.__getattr__(prop)) for prop in self._cl.getprops()] )
1778
1779 def __hash__(self):
1780 """\
1781 Node identifier.
1782 """
1783
1784 return self._id
1785
1786 def __eq__(self, other):
1787 """\
1788 Are these nodes equal?
1789 """
1790
1791 same_class = isinstance(other, NodeWrapper) \
1792 and self._cl.classname == other._cl.classname
1793
1794 if not other is None:
1795 other = self._clw.getid(other)
1796
1797 return same_class and (other == self._id)
1798
1799 def __ne__(self, other):
1800 """\
1801 Are these nodes different?
1802 """
1803
1804 return not self.__eq__(other)
1805
1806 def __nonzero__(self):
1807 """\
1808 Truth value testing.
1809 """
1810
1811 return not self._id is None
1812
1813 def plain(self, property=None):
1814 """\
1815 Return the property in a nice way. Links are resolved.
1816 """
1817
1818 if not property is None:
1819 value = self.__getattr__(property)
1820
1821 if isinstance(value, types.ListType):
1822 list = []
1823 for one in value:
1824 label = one._clw.getlabel()
1825
1826 if isNode(one):
1827 list.append( getattr(one, label) )
1828 else:
1829 list.append(one)
1830 value = ', '.join( [str(item) for item in list] )
1831
1832 elif isinstance(value, types.NoneType):
1833 value = ''
1834
1835 elif isNode(value):
1836 label = value._clw.getlabel()
1837 value = getattr(value, label)
1838 else:
1839 label = self._clw.getlabel()
1840 value = self.__getattr__(label)
1841
1842 if value is None:
1843 value = ''
1844
1845 return value
1846
1847 def get(self, propname):
1848 """\
1849 Get the value of a property on an existing node of this class.
1850
1851 'propname' must be the name of a property of this class or a
1852 KeyError is raised.
1853 """
1854
1855 if self._id is None:
1856 raise NotImplementedError, \
1857 "This method is not implemented for phantom nodes"
1858
1859 else:
1860 return self._clw.get(self._id, propname)
1861
1862 def set(self, **propvalues):
1863 """\
1864 Modify a property on an existing node of this class.
1865
1866 Each key in 'propvalues' must be the name of a property of this
1867 class or a KeyError is raised.
1868
1869 All values in 'propvalues' must be acceptable types for their
1870 corresponding properties or a TypeError is raised.
1871
1872 If the value of the key property is set, it must not collide with
1873 other key strings or a ValueError is raised.
1874
1875 If the value of a Link or Multilink property contains an invalid
1876 node id, a ValueError is raised.
1877 """
1878
1879 if self._id is None:
1880 raise NotImplementedError, \
1881 "This method is not implemented for phantom nodes"
1882
1883 else:
1884 self._clw.set(self._id, propvalues)
1885
1886 def is_retired(self):
1887 """\
1888 Return true if the node is retired.
1889 """
1890
1891 if self._id is None:
1892 raise NotImplementedError, \
1893 "This method is not implemented for phantom nodes"
1894
1895 else:
1896 return self._clw.is_retired(self._id)
1897
1898 def safeget(self, propname, default=None):
1899 """\
1900 Safely get the value of a property on an existing node of this class.
1901
1902 Return 'default' if the node doesn't exist.
1903 """
1904
1905 if self._id is None:
1906 raise NotImplementedError, \
1907 "This method is not implemented for phantom nodes"
1908
1909 else:
1910 return self._clw.safeget(self._id, propname, default)
1911
1912 def pop(self):
1913 """\
1914 Get node 'key' and remove (retire) it from this class.
1915 """
1916
1917 if self._id is None:
1918 raise NotImplementedError, \
1919 "This method is not implemented for phantom nodes"
1920
1921 else:
1922 return self._clw.pop(self._id)
1923
1924 def resurrect(self):
1925 """\
1926 Bring any deleted (retired) node back alive.
1927 """
1928
1929 if self._id is None:
1930 raise NotImplementedError, \
1931 "This method is not implemented for phantom nodes"
1932
1933 else:
1934 self._clw.resurrect(self._id)
1935
1936
1937
1938 class ListWrapper(types.ListType):
1939 """\
1940 List object to give easier access to NodeWrapper's.
1941
1942 Introduction
1943 ============
1944 This wrapper almost fully supports all available operations for
1945 Python's list object. The difference is that it is meant to be used
1946 in ClassWrapper's and NodeWrapper's.
1947
1948 Initialization
1949 ==============
1950 The wrapper can be initialized as follows:
1951 1. lw = dbwrapper.ClassWrapper(<ClassWrapper>)
1952 This is the simplest form. The wrapper will be created to hold
1953 only NodeWrapper objects from class <ClassWrapper>.
1954 The list will be empty.
1955
1956 2. lw = dbwrapper.ClassWrapper(<ClassWrapper>, [<key>|<nodeid>|<NodeWrapper>])
1957 With this form you will fill the list with initial data.
1958 The data must be nodes in <ClassWrapper> and may be addressed
1959 by either key (in case the class has a key column), or by node id
1960 (as integer or string), or by a NodeWrapper object.
1961
1962 For both forms an additional parameter can be set to tell the dbwrapper
1963 that all NodeWrapper classes in the ListClass are either read-only or not.
1964 Default they are set to not read-only, meaning all nodes in the list
1965 may be changed.
1966
1967 Several methods in the ClassWrapper and the NodeWrapper do return
1968 ListWrapper objects. In general these methods are all the search
1969 methods in the ClassWrapper and the 'get' method in the NodeWrapper.
1970
1971 Default the list won't accept duplicate nodes. If you try to add a node
1972 which is already present, a ValueError will be raised. This behavior
1973 can be omitted by setting ListWrapper member 'unique' to False.
1974 If False, then duplicate nodes are accepted. Switching member
1975 'unique' back to True while there are duplicate nodes will raise
1976 a ValueError too.
1977 """
1978
1979 class RegExpClass:
1980 """\
1981 A class with regular expression methods that will filter nodes
1982 from hyperdb.Class. The methods in this class are one to one
1983 with the regular expression methods in python module 're'.
1984 """
1985
1986 def __init__(self, owner):
1987 """\
1988 Initialize class
1989
1990 "owner" must be of type ListWrapper
1991 """
1992
1993 if not isinstance(owner, ListWrapper):
1994 raise TypeError, \
1995 "Owner object isn't an instance of 'ListWrapper'"
1996
1997 self._lw = owner
1998
1999 def __repr__(self):
2000 """\
2001 Slightly more useful representation
2002 """
2003
2004 return '''<dbwrapper.ListWrapper.RegExpClass '%s'>'''% \
2005 self._lw._clw._cl
2006
2007 def __str__(self):
2008 """\
2009 Slightly more useful representation
2010 """
2011
2012 return self.__repr__()
2013
2014 def match(self, pattern, flags=0, column=None):
2015 """\
2016 Find all nodes in the class that have a result when
2017 applying the "pattern" at the start of the string in
2018 "column", returning a list object with tuples pairs.
2019
2020 The first index of each tuple contains a
2021 dbwraper.NodeWrapper object of a node that matches
2022 with "pattern".
2023
2024 The second index in the tuples holds the match object.
2025
2026 "flags" may have the same flags as used/defined in
2027 python module 're'. These flags are also available
2028 as members of module 'dbwrapper'.
2029 (e.g. dbwrapper.IGNORECASE)
2030
2031 "column" can be used to perform a search on an
2032 other column than the column returned by
2033 ClassWrapper.getlabel().
2034 """
2035
2036 return self._lw._clw.re.match(pattern, flags, column, nodelist=self._lw)
2037
2038 def search(self, pattern, flags=0, column=None):
2039 """\
2040 Find all nodes in the class that have a result when
2041 scanning through the string in "column" looking for a
2042 match to the "pattern", returning a list object with
2043 tuples pairs.
2044
2045 The first index of each tuple contains a
2046 dbwraper.NodeWrapper object of a node that matches
2047 with "pattern".
2048
2049 The second index in the tuples holds the search object.
2050
2051 "flags" may have the same flags as used/defined in
2052 python module 're'. These flags are also available
2053 as members of module 'dbwrapper'.
2054 (e.g. dbwrapper.IGNORECASE)
2055
2056 "column" can be used to perform a search on an
2057 other column than the column returned by
2058 ClassWrapper.getlabel().
2059 """
2060
2061 return self._lw._clw.re.search(pattern, flags, column, nodelist=self._lw)
2062
2063 def findall(self, pattern, flags=0, column=None):
2064 """\
2065 Return all nodes in class that have "pattern" matches
2066 in the strings in "column". Returned is a list of tuple
2067 pairs.
2068
2069 The first tuple index contains a NodeWrapper object of
2070 a matching node.
2071
2072 The second tuple index contains a list of all
2073 non-overlapping matches in the node's "column" string.
2074
2075 If one or more groups are present in the "pattern", the
2076 second tuple index will contain a list of groups; this
2077 will be a list of tuples if the "pattern" has more than
2078 one group.
2079
2080 "flags" may have the same flags as used/defined in
2081 python module 're'. These flags are also available
2082 as members of module 'dbwrapper'.
2083 (e.g. dbwrapper.IGNORECASE)
2084
2085 "column" can be used to perform a search on an
2086 other column than the column returned by
2087 ClassWrapper.getlabel().
2088 """
2089
2090 return self._lw._clw.re.findall(pattern, flags, column, nodelist=self._lw)
2091
2092
2093 def __init__(self, owner, readonly=False, *list):
2094 """\
2095 Create a list object for dbwrapper.
2096
2097 "owner" must be a 'ClassWrapper' object. Only nodes present
2098 in 'ClassWrapper' will be allowed in the list.
2099
2100 "readonly" can be set to prevent that properties of the nodes
2101 in the list can be changed. The list content can always be
2102 changed.
2103
2104 "list" can be a list of any node specification like key,
2105 nodeid or NodeClass object.
2106 """
2107
2108 if not isClass(owner):
2109 raise TypeError, \
2110 "owner object isn't an instance of 'ClassWrapper'"
2111
2112 self.__list = []
2113 self._clw = owner
2114 self.__regexp = False
2115 self.re = self.RegExpClass(self)
2116 self.unique = True
2117
2118 if isinstance(readonly, types.ListType) \
2119 or isinstance(readonly, types.TupleType):
2120 self._readonly = False
2121 list = readonly
2122 else:
2123 self._readonly = readonly
2124
2125 if list:
2126 if len(list) == 1 \
2127 and isinstance(list[0], types.ListType):
2128 list = list[0]
2129
2130 self.__iadd__(list)
2131
2132 def __repr__(self):
2133 """\
2134 Slightly more useful representation
2135 """
2136
2137 return str(self.__list)
2138
2139 def __str__(self):
2140 """\
2141 Slightly more useful representation
2142 """
2143
2144 return self.__repr__()
2145
2146 def __getitem__(self, index):
2147 """\
2148 Retrieve an item at position 'index'.
2149 """
2150
2151 return self.__list[index]
2152
2153 def __setitem__(self, index, value):
2154 """\
2155 Assign 'value' to the item at position 'index'.
2156 """
2157
2158 self.__inner_set(index, value)
2159
2160 def __delitem__(self, index):
2161 """\
2162 Delete the item at position 'index'.
2163 """
2164
2165 del self.__list[index]
2166
2167 def __setattr__(self, attr, value):
2168 """\
2169 Set any attribute.
2170
2171 This method prevents that member 'unique' can be set to True
2172 while the content in the list is not unique.
2173 """
2174
2175 if attr == 'unique' \
2176 and value \
2177 and self.__dict__.has_key(attr) \
2178 and not self.unique:
2179 for index, node1 in enumerate(self.__list):
2180 if self.__regexp:
2181 node1 = node1[0]
2182
2183 for node2 in self.__list[index+1:]:
2184 if self.__regexp:
2185 node2 = node2[0]
2186
2187 if node1 == node2:
2188 raise ValueError, \
2189 "Can't set list to unique. Node '%s' has more \
2190 than one instances in list."%node1
2191
2192 self.__dict__[attr] = value
2193
2194 def __len__(self):
2195 """\
2196 Number of items in list.
2197 """
2198
2199 return len(self.__list)
2200
2201 def __isub__(self, other):
2202 """\
2203 Remove items from list.
2204 """
2205
2206 if not isinstance(other, types.ListType) \
2207 and not isinstance(other, types.TupleType):
2208 other = [ other ]
2209
2210 for node in other:
2211 self.remove(node)
2212
2213 return self
2214
2215 def __iadd__(self, other):
2216 """\
2217 Add items to list.
2218 """
2219
2220 if not isinstance(other, types.ListType) \
2221 and not isinstance(other, types.TupleType):
2222 other = [ other ]
2223
2224 for node in other:
2225 self.append(node)
2226
2227 return self
2228
2229 def __iter__(self):
2230 """\
2231 Step trough the list.
2232 """
2233
2234 return iter(self.__list)
2235
2236 def __getslice__(self, start, stop):
2237 """\
2238 Get a piece of the list.
2239 """
2240
2241 return self.__list[start:stop]
2242
2243 def __setslice__(self, start, stop, sequence):
2244 """\
2245 Set a piece of the list.
2246 """
2247
2248 if isinstance(sequence, types.ListType) \
2249 or isinstance(sequence, types.TupleType):
2250 for index in range(start, stop):
2251 if index < len(sequence):
2252 self.__setitem__(index, sequence[index])
2253 else:
2254 del self.__list[stop-1]
2255 else:
2256 del self.__list[start:stop]
2257 self.insert(start, sequence)
2258
2259 def __delslice__(self, start, stop):
2260 """\
2261 Delete a piece of the list.
2262 """
2263
2264 del self.__list[start:stop]
2265
2266 def __contains__(self, value):
2267 """\
2268 Determine if the list has a given node.
2269 """
2270
2271 try:
2272 index = self.index(value)
2273
2274 except:
2275 found = False
2276
2277 else:
2278 found = (index > -1)
2279
2280 return found
2281
2282 def __inner_set(self, index, value):
2283 """\
2284 Handles the 'set' operation of all data changing methods.
2285 """
2286
2287 regexp = False
2288
2289 if isinstance(value, types.TupleType):
2290 if len(value) == 2:
2291 # regulare expression list
2292 node = value[0]
2293 regexp = True
2294 else:
2295 node = None
2296 else:
2297 node = value
2298
2299 if not isNode(node):
2300 try:
2301 node = NodeWrapper(self._clw, node, self._readonly)
2302
2303 except:
2304 raise TypeError, \
2305 "Value isn't an instance of 'NodeWrapper'"
2306
2307 if node._cl.classname != self._clw._cl.classname:
2308 raise TypeError, \
2309 "Node '%s' isn't member of class '%s'"% \
2310 (node, self._clw._cl.classname)
2311
2312 if self.unique:
2313 try:
2314 list_index = self.index(node)
2315
2316 except ValueError:
2317 pass
2318
2319 else:
2320 if list_index != index:
2321 raise ValueError, \
2322 "Node '%s' already present in unique list."%node
2323
2324 if len(self.__list) == 0 \
2325 or (len(self.__list) == 1 and self.__list[index] == None):
2326 self.__regexp = regexp
2327
2328 if self.__regexp:
2329 if regexp:
2330 self.__list[index] = (node, value[1])
2331 else:
2332 self.__list[index] = (node, None)
2333 else:
2334 if regexp:
2335 self.__list[index] = node
2336 else:
2337 self.__list[index] = node
2338
2339 def append(self, value):
2340 """\
2341 Append a node to the list.
2342 """
2343
2344 self.insert(len(self.__list), value)
2345
2346 def insert(self, index, value):
2347 """\
2348 Insert a node at position 'index'.
2349 """
2350
2351 self.__list.insert(index, None)
2352
2353 try:
2354 self.__inner_set(index, value)
2355
2356 except:
2357 del self.__list[index]
2358 raise
2359
2360 def extend(self, other):
2361 """\
2362 Extend the list with the content of an other list.
2363 """
2364
2365 self.__iadd__(other)
2366
2367 def remove(self, value):
2368 """\
2369 Remove the first node in the list that matches value.
2370 """
2371
2372 index = self.index(value)
2373
2374 del self.__list[index]
2375
2376 def count(self, value):
2377 """\
2378 Return the number of occurrences of value.
2379 """
2380
2381 value = NodeWrapper(self._clw, value)
2382
2383 if not isNode(value):
2384 raise TypeError, \
2385 "Value isn't an instance of 'NodeWrapper'"
2386
2387 occurrens = 0
2388 for index, node in enumerate(self.__list):
2389 if self.__regexp:
2390 # regulare expression list
2391 node = node[0]
2392
2393 if node == value:
2394 occurrens += 1
2395
2396 return occurrens
2397
2398 def pop(self, index):
2399 """\
2400 Return the node at position 'index' and remove it from the list.
2401 """
2402
2403 value = self.__list[index]
2404 del self.__list[index]
2405 return value
2406
2407 def sort(self):
2408 """\
2409 Sort all nodes in the list. The sorting will be done on column
2410 'order'. If the class has no column 'order', then the node id
2411 is used.
2412 """
2413
2414 def inner_sort(self, prop, index1, index2):
2415 if self.__regexp:
2416 # regulare expression list
2417 node1 = self.__list[index1][0]
2418 node2 = self.__list[index2][0]
2419 else:
2420 node1 = self.__list[index1]
2421 node2 = self.__list[index2]
2422
2423 value1 = int(getattr(node1, prop))
2424 value2 = int(getattr(node2, prop))
2425
2426 if value1 > value2:
2427 temp = self.__list[index2]
2428 self.__list[index2] = self.__list[index1]
2429 self.__list[index1] = temp
2430
2431 return True
2432
2433 else:
2434 return False
2435
2436 if self.__list:
2437 props = self._clw._cl.getprops()
2438
2439 if props.has_key('order'):
2440 prop = 'order'
2441 else:
2442 prop = 'id'
2443
2444 swap = True
2445 while swap:
2446 swap = False
2447
2448 for index in range(0, len(self.__list)-1):
2449 swap = swap or inner_sort(self, prop, index, index+1)
2450
2451 def reverse(self):
2452 """\
2453 Reverse all nodes in the list.
2454 """
2455
2456 self.__list.reverse()
2457
2458 def index(self, value, start=None, stop=None):
2459 """\
2460 Return the index position of the first node that matches value.
2461 'start' and 'stop' can be used to search trough a slice of the list.
2462 """
2463
2464 try:
2465 value = NodeWrapper(self._clw, value)
2466
2467 except IndexError:
2468 raise ValueError, \
2469 "index(%s): %s not in list"%(value, value)
2470
2471 if not isNode(value):
2472 raise TypeError, \
2473 "Value isn't an instance of 'NodeWrapper'"
2474
2475 if start is None:
2476 start = 0
2477
2478 if stop is None:
2479 stop = len(self.__list)
2480
2481 index = 0
2482 for index, node in enumerate(self.__list[start:stop]):
2483 if self.__regexp \
2484 and isinstance(node, types.TupleType):
2485 # regulare expression list
2486 node = node[0]
2487
2488 if value == node:
2489 return index
2490
2491 raise ValueError, \
2492 "index(%s): %s not in list"%(value, value)
2493
2494 def get(self, key):
2495 """\
2496 Return a node indexed by 'key'.
2497
2498 "key" identifies the node.
2499 It can have one of the next types:
2500 1 - an integer
2501 The 'key' will be treated as the id of the node
2502 and used for the indexing.
2503 2 - a string
2504 The 'key' will be treated as key argument of the
2505 node and used to lookup the node id.
2506 If the lookup action raises a KeyError and 'key'
2507 only contains digits, the integer value of 'key'
2508 will be used as the node id.
2509 In both cases the node id is used for the indexing.
2510 3 - an instance of NodeWrapper
2511 The node id will be obtained from the NodeWrapper
2512 class and used for the indexing.
2513 4 - None type
2514 This is a special option implemented to make it
2515 easy usable in detectors.
2516 """
2517
2518 index = self.index(key)
2519
2520 return self.__list[index]
2521
2522 def getnodeids(self):
2523 """\
2524 Return a (normal) list containing all node ids (as integer) of all
2525 nodes in this list class.
2526 """
2527
2528 ids = []
2529 for node in self.__list:
2530 if self.__regexp:
2531 # regulare expression list
2532 node = node[0]
2533
2534 ids.append(node._id)
2535
2536 return ids
2537
2538 def filter(self, search_matches, filterspec, sort=(None,None), group=(None,None)):
2539 """\
2540 Return a list of the NodeWrappers of the active nodes in this class
2541 that match the 'filter' spec, sorted by the group spec and then the
2542 sort spec.
2543
2544 "filterspec" is {propname: value(s)}
2545
2546 "sort" and "group" are (dir, prop) where dir is '+', '-' or None
2547 and prop is a prop name or None
2548
2549 "search_matches" is {nodeid: marker}
2550
2551 The filter must match all properties specified - but if the
2552 property value to match is a list, any one of the values in the
2553 list may match for that property to match.
2554 """
2555
2556 return self._clw.filter(search_matches, filterspec, sort, group, nodelist=self)
2557
2558 def search(self, value, column=None):
2559 """\
2560 Lookup all nodes that have a 100% match with 'value' and
2561 return them in a list.
2562
2563 "value" is the pattern to look for.
2564
2565 "column" can be set to any string property. A TypeError is
2566 raised if 'column' isn't a string property. The label column
2567 will be used if 'column' is omitted.
2568 """
2569
2570 return self._clw.search(value, column, nodelist=self)
2571
2572 def simple(self):
2573 """\
2574 Converts the list content to the simple format. This method can be
2575 used to get rid of the regular expression object when a list was
2576 created by a regular expression search.
2577 """
2578
2579 if self.__regexp:
2580 for index, node in enumerate(self.__list):
2581 self.__list[index] = node[0]
2582
2583 self.__regexp = False