Roundup Tracker

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:

   1     def auditor(db, cl, nodeid, newvalues):
   2         dbw   = dbwrapper.DBWrapper(db)
   3         clw   = dbwrapper.ClassWrapper(dbw, cl)
   4         nodew = dbwrapper.NodeWrapper(clw, nodeid)

And this in a reactor:

   1     def reactor(db, cl, nodeid, oldvalues):
   2         dbw   = dbwrapper.DBWrapper(db)
   3         clw   = dbwrapper.ClassWrapper(dbw, cl)
   4         nodew = dbwrapper.NodeWrapper(clw, nodeid)

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:

   1     def handler(self):
   2         dbw   = dbwrapper.DBWrapper(self.db)
   3         clw   = dbwrapper.ClassWrapper(dbw, self.cl)
   4         nodew = dbwrapper.NodeWrapper(clw, self.nodeid)

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::

2. Changing a linked property::

3. Adding a link to a multi linked property::

4. Removing a link from a multi linked property::

5. Duplicating an issue with dbwrapper::

6. Retiring a node::

7. Filtering::

8. Regular expression search::

9. Combination RE search::

10. More search possibilities::

11. Iteration::


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