Changeset 571 for bbox/rdfobj

Show
Ignore:
Timestamp:
11/27/06 18:03:14 (2 years ago)
Author:
zool
Message:

wrote a lot of acceptance tests for bbox. moved spatial indexing out of core.
providing a lookup table for feedparser shorthand for RSS namespaces that map to functions.


Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • bbox/rdfobj/__init__.py

    Revision 551 Revision 571
    1import RDF, re 1import RDF, re 
    2from time import time 2from time import time 
    3from random import randint 3from random import randint 
    4from xml.sax.saxutils import escape 4from xml.sax.saxutils import escape 
    5import warnings  5 from warnings import warn 
       6 import __builtin__ 
    6 7 
    7is_uri = re.compile('^\w+:[^ ]+$') 8is_uri = re.compile('^\w+:[^ ]+$') 
    8uri_split = re.compile('^(.+[#/])([^/#]+)$') 9uri_split = re.compile('^(.+[#/])([^/#]+)$') 
    9 10 
    10Schema_Namespaces = { 11Schema_Namespaces = { 
    11    "rdf"  : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 12    "rdf"  : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 
    12    "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", 13    "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", 
    13    "owl"  : "http://www.w3.org/2002/07/owl#" 14    "owl"  : "http://www.w3.org/2002/07/owl#" 
    14} 15} 
    15 16 
    16class Model: 17class Model: 
    17    """rdfobj.Model provides access to an RDF model (stored, at present, in 18    """rdfobj.Model provides access to an RDF model (stored, at present, in 
    18    Redland). The constructor expects the name of a file and an optional dict of 19    Redland). The constructor expects the name of a file and an optional dict of 
    19    qualified name to namespace URI mapping.""" 20    qualified name to namespace URI mapping.""" 
    20 21 
    21    def __init__ (self, file, 22    def __init__ (self, file,qnames = None, context = None, db = None, module_ns = "rdfobj.modules", **storage_opts): 
    22        qnames = None, context = None, db = None, 23        """__init__() loads a Redland Storage object using the provided 
    23        module_ns = "rdfobj.modules", **storage_opts): 24        storage_opts and some sensible defaults, and initializes 
    24 25        its model from the storage. 
    25        """__init__() loads a Redland Storage object using the provided 26        """ 
    26        storage_opts and some sensible defaults, and initializes 27 
    27        its model from the storage.""" 28        storage_opts["contexts"] = "'yes'" 
    28         29        storage_opts["write"]    = "'yes'" 
    29        storage_opts["contexts"] = "'yes'" 30        storage_opts["new"]      = "'no'" 
    30        storage_opts["write"]    = "'yes'" 31         
    31        storage_opts["new"]      = "'no'" 32        opt_string = "" 
    32         33        opt_string = str.join(",",[ "%s=%s" % x for x in storage_opts.iteritems() ]) 
    33        opt_string = "" 34 
    34        opt_string = str.join(",", 35        if db: # a BDB store has a different interface to FileStorage. sheesh. 
    35                        [ "%s=%s" % x for x in storage_opts.iteritems() ]) 36                opt_string = "hash-type='bdb',"+opt_string 
    36 37                self.storage = RDF.HashStorage( file, options = opt_string ) 
    37        if db: # a BDB store has a different interface to FileStorage. sheesh. 38        else: 
    38            opt_string = "hash-type='bdb',"+opt_string 39            self.storage = RDF.FileStorage( file, options_string = opt_string ) 
    39            self.storage = RDF.HashStorage( file, options = opt_string ) 40 
    40        else: 41        self.model = RDF.Model( self.storage ) 
    41            self.storage = RDF.FileStorage( file, options_string = opt_string ) 42        self.ns        = {} 
    42 43        self.context   = context 
    43        self.model = RDF.Model( self.storage ) 44        self.module_ns = module_ns 
    44        self.ns        = {} 45     
    45        self.context   = context 46        """Next, __init__() creates its internal qname map, first from the 
    46        self.module_ns = module_ns 47        user provided dict, and then subsequently from all the xmlns:qname 
    47 48        predicates it finds in the model.""" 
    48        """Next, __init__() creates its internal qname map, first from the 49 
    49        user provided dict, and then subsequently from all the xmlns:qname 50        self.alias( Schema_Namespaces ) 
    50        predicates it finds in the model.""" 51 
    51 52        if qnames is not None: 
    52        self.alias( Schema_Namespaces ) 53            self.alias( qnames ) 
    53 54 
    54        if qnames is not None: 55        canonical_qname = RDF.Statement( None, RDF.Uri("http://www.w3.org/2000/xmlns/qname"), None  )  
    55            self.alias( qnames ) 56 
    56 57        for ns in self.model.find_statements( canonical_qname ): 
    57        canonical_qname = RDF.Statement( None,  58            self.alias( {str(ns.object): str(ns.subject.uri)} ) 
    58            RDF.Uri("http://www.w3.org/2000/xmlns/qname"), None  )    
    59   
    60        for ns in self.model.find_statements( canonical_qname ):   
    61            self.alias( {str(ns.object): str(ns.subject.uri)} )   
    62 59 
    63    def sync (self): # helper method, mostly for console mode 60    def sync (self): # helper method, mostly for console mode 
    64       self.model.sync() 61        self.model.sync() 
    65 62 
    66    def __del__ (self): 63    def __del__ (self): 
    67       self.sync() 64        self.sync() 
    68 65 
    69    def alias (self, dict): 66    def alias (self, dict): 
    70        """The alias method takes a dict mapping qname to URI, and populates 67        """The alias method takes a dict mapping qname to URI, and populates the model's internal qname map. We also populate the rdfobj's internal package globals to make it easier to look up rdf:type URIs later on.""" 
    71        the model's internal qname map. We also populate the rdfobj's internal 68 
    72        package globals to make it easier to look up rdf:type URIs later on.""" 69        for qname, uri in dict.iteritems(): 
    73 70            if not self.ns.has_key(qname): 
    74        for qname, uri in dict.iteritems(): 71                globals()[qname] = self.ns[qname] = Namespace(uri) 
    75            if not self.ns.has_key(qname): 72         
    76                globals()[qname] = self.ns[qname] = Namespace(uri) 73        self.qname = {} 
    77 74        uris = [] 
    78        self.qname = {} 75 
    79        uris = [] 76        for qname, ns in self.ns.iteritems(): 
    80        for qname, ns in self.ns.iteritems(): 77            uri = str(ns.uri()) 
    81            uri = str(ns.uri()) 78            self.qname[uri] = qname 
    82            self.qname[uri] = qname 79            uris.append(uri) 
    83            uris.append(uri) 80 
    84 81        pattern = "^(" + "|".join(uris) + ")" 
    85        pattern = "^(" + "|".join(uris) + ")" 82        self.match_ns_pattern = pattern # for debugging 
    86        self.match_ns_pattern = pattern # for debugging 83        self.match_ns = re.compile(pattern) 
    87        self.match_ns = re.compile(pattern)   
    88 84 
    89    def __iter__ (self): 85    def __iter__ (self): 
    90       ids = self.model.find_statements(RDF.Statement( None, rdf.type )) 86        ids = self.model.find_statements(RDF.Statement( None, rdf.type )) 
    91        return Iterator(self, ids) 87        return Iterator(self, ids) 
    92 88 
    93    def __getitem__ (self, key): 89    def __getitem__ (self, key): 
    94       key = self.node_ify(key) 90        key = self.node_ify(key) 
    95       return self.fetch(key) 91        return self.fetch(key) 
    96 92 
    97    def load ( self, uri ): 93    def load ( self, uri ): 
    98        """load() loads an RDF/XML document at given URI into the model."""  94          
    99          95         """load() loads an RDF/XML document at given URI into the model. 
    100        self.model.load(RDF.Uri(uri))  96         At this stage it checks for new namespaces in the model and sticks 
       97         them into the package global, so we don't have to keep reloading. 
       98         """ 
       99         self.model.load(RDF.Uri(uri)) 
       100  
       101         canonical_qname = RDF.Statement( None, RDF.Uri("http://www.w3.org/2000/xmlns/qname"), None  ) 
       102  
       103         for ns in self.model.find_statements( canonical_qname ): 
       104             self.alias( {str(ns.object): str(ns.subject.uri)} ) 
       105   
       106      
    101 107 
    102    def fetch (self, uri, context = None): 108    def fetch (self, uri, context = None): 
    103        """First, fetch() ensures that the URI it is trying to fetch is  109         """First, fetch() ensures that the URI it is trying to fetch is 
    104        encapsulated in a Redland object."""  110             encapsulated in a Redland object. 
    105          111  
    106        if type(uri) is not RDF.Node:  112         fetch() treats the URI as the URI of the desired object 
    107            uri = RDF.Uri(uri)  113         and instantiate that object. 
    108  114         """ 
    109        if RDF.Statement(uri, None, None) in self.model:  115          
    110            """fetch() treats the URI as the URI of the desired object  116         if type(uri) is not RDF.Node: 
    111            and instantiate that object."""  117             uri = RDF.Uri(uri) 
    112          118  
    113            return self.instantiate(uri)  119         if RDF.Statement(uri, None, None) in self.model: 
    114        else:  120          
    115            return None  121             return self.instantiate(uri) 
       122         else: 
       123             return None 
    116 124 
    117    def search (self, uri, object = None, context = None): 125    def search (self, uri, object = None, context = None): 
    118        """search() accepts a predicate uri and an optional object uri or 126        """search() accepts a predicate uri and an optional object uri or 
    119        literal. An optional context may be provided as well."""    127        literal. An optional context may be provided as well."""    
    120 128 
    121        """First, search() ensures that the URI it is trying to fetch is 129        """First, search() ensures that the URI it is trying to fetch is 
    122        encapsulated in a Redland object.""" 130        encapsulated in a Redland object.""" 
    123 131 
    124        if uri is not None and type(uri) is not RDF.Node: 132        if uri is not None and type(uri) is not RDF.Node: 
    125            uri = RDF.Uri(uri) 133            uri = RDF.Uri(uri) 
    126 134 
    127        """With the optional object uri supplied, search() calls fetch()  135        """With the optional object uri supplied, search() calls fetch()  
    128        with the predicate and object (and optional context) supplied.""" 136        with the predicate and object (and optional context) supplied.""" 
    129          137          
    130        if object is not None: 138        if object is not None: 
    131            ids = self.model.get_sources_context( uri,self.node_ify(object) ) 139            ids = self.model.get_sources_context( uri,self.node_ify(object) ) 
    132            return Iterator(self, ids, context) 140            return Iterator(self, ids, context) 
    133          141          
    134        else:   142        else:   
    135            """Otherwise it find all statements matching that predicate.""" 143            """Otherwise it find all statements matching that predicate.""" 
    136            stats = self.model.find_statements_context( 144            stats = self.model.find_statements_context( 
    137                                 RDF.Statement(None,uri,None) ) 145                                 RDF.Statement(None,uri,None) ) 
    138            return Iterator(self, stats, context) 146            return Iterator(self, stats, context) 
    139 147 
    140    def node_ify(self,object): 148    def node_ify(self,object): 
    141        if type(object) == str: 149        if type(object) == str: 
    142            s = is_uri.search(object) 150            s = is_uri.search(object) 
    143            if s is not None: 151            if s is not None: 
    144                return RDF.Uri(object) 152                return RDF.Uri(object) 
    145            else: 153            else: 
    146                return RDF.Node(object) 154                return RDF.Node(object) 
    147        elif isinstance(object, Object): 155        elif isinstance(object, Object): 
    148            return object.uri() 156            return object.uri() 
    149        return object 157        return object 
    150 158 
    151    def instantiate (self, uri, context = None): 159    def instantiate (self, uri, context = None): 
    152        """instantiate() handles the details of creating an honest-to-goodness 160        """instantiate() handles the details of creating an honest-to-goodness 
    153        Python object from a set of database statements, which involves a lot of 161        Python object from a set of database statements, which involves a lot of 
    154        monkey work.""" 162        monkey work.""" 
    155         163         
    156        obj = None 164        obj = None 
    157         165         
    158        """We can be passed an RDF.Node representing a URI, or an RDF.Uri, or a 166        """We can be passed an RDF.Node representing a URI, or an RDF.Uri, or a 
    159        string which we convert to an RDF.Uri""" 167        string which we convert to an RDF.Uri""" 
    160         168         
    161        if type(uri) is not RDF.Node: 169        if type(uri) is not RDF.Node: 
    162            uri = RDF.Uri(uri) 170            uri = RDF.Uri(uri) 
    163 171 
    164        """instantiate() checks the database to see if the object represented 172        """instantiate() checks the database to see if the object represented 
    165        by the URI has an rdf:type, so that it can search for the matching 173        by the URI has an rdf:type, so that it can search for the matching 
    166        code module.""" 174        code module.""" 
    167 175 
    168        objtype = self.model.get_target(uri, rdf.type) 176        objtype = self.model.get_target(uri, rdf.type) 
    169 177 
    170        if objtype is not None: 178        if objtype is not None: 
    171            """If so, we look at our qname map to reverse engineer 179            """If so, we look at our qname map to reverse engineer 
    172            the qualified name of the rdf:type of the object.""" 180            the qualified name of the rdf:type of the object.""" 
    173            module_name  = "" 181            module_name  = "" 
    174            target_class = "" 182            target_class = "" 
    175            match = uri_split.match( str(objtype.uri) ) 183            match = uri_split.match( str(objtype.uri) ) 
    176            if match: 184            if match: 
    177                prefix       = match.group(1) 185                prefix       = match.group(1) 
    178                target_class = match.group(2) 186                target_class = match.group(2) 
    179                if self.qname.has_key(prefix): 187                if self.qname.has_key(prefix): 
    180                    module_name = str(self.qname[prefix]) 188                    module_name = str(self.qname[prefix]) 
    181 189 
    182            if module_name: 190            if module_name: 
    183                try: 191                try: 
    184                    """Attempt to load the code module matching the rdf:type's  192                    """Attempt to load the code module matching the rdf:type's  
    185                    qname and instantiate an object from that module, if it 193                    qname and instantiate an object from that module, if it 
    186                    exists.""" 194                    exists.""" 
    187 195 
    188                    ### this only gets the top-level module, alas 196                    ### this only gets the top-level module, alas 
    189                    module_name = self.module_ns + "." + module_name 197                    module_name = self.module_ns + "." + module_name 
    190                    module = __import__(module_name) 198                    module = __import__(module_name) 
    191                 199                 
    192                    ### as recommended by  200                    ### as recommended by  
    193                    ### http://docs.python.org/lib/built-in-funcs.html 201                    ### http://docs.python.org/lib/built-in-funcs.html 
    194                    components = module_name.split('.') 202                    components = module_name.split('.') 
    195                    for comp in components[1:]: 203                    for comp in components[1:]: 
    196                        module = getattr(module, comp) 204                        module = getattr(module, comp) 
    197 205 
    198                    ### now that we've gotten the module object, get the 206                    ### now that we've gotten the module object, get the 
    199                    ### class out. 207                    ### class out. 
    200                    module = getattr(module, target_class)                   208                    module = getattr(module, target_class)                   
    201 209 
    202                    ### this instantiates the object itself. 210                    ### this instantiates the object itself. 
    203                    obj = module(self, uri, context) 211                    obj = module(self, uri, context) 
      212 
    204                except (ImportError, AttributeError): 213                except (ImportError, AttributeError): 
    205                    pass 214                    pass 
    206                except: 215                except: 
    207                    raise 216                    raise 
    208 217 
    209        """If the object doesn't have an rdf:type or there isn't a code module 218        """If the object doesn't have an rdf:type or there isn't a code module 
    210        matching that rdf:type's qname, then instantiate a bare rdfobj.Object 219        matching that rdf:type's qname, then instantiate a bare rdfobj.Object 
    211        from the database using the given URI.""" 220        from the database using the given URI.""" 
    212 221 
    213        if obj is None: 222        if obj is None: 
    214            obj = Object(self, uri, context) 223            obj = Object(self, uri, context) 
    215             224             
    216        return obj 225        return obj 
    217 226 
    218    def create (self, type, uri = None, context = None): 227    def create (self, type, uri = None, context = None): 
    219        """create() makes a new object with the specified type and (optional) 228        """create() makes a new object with the specified type and (optional) 
    220        URI. If no URI is provided, a bnode ID is generated.""" 229        URI. If no URI is provided, a bnode ID is generated.""" 
    221 230 
    222        if uri is None: # then it's a blank node 231        if uri is None: # then it's a blank node 
    223            uri = RDF.Uri("_id:%08x%04x" % (time(), randint(0, 1 << 16))) 232            uri = RDF.Uri("_id:%08x%04x" % (time(), randint(0, 1 << 16))) 
    224        else: 233        else: 
    225            uri = RDF.Uri(uri) 234            uri = RDF.Uri(uri) 
    226 235 
    227        """First, we seed the object's rdf:type, and then we do the usual 236        """First, we seed the object's rdf:type, and then we do the usual 
    228        instantiation via instantiate().""" 237        instantiation via instantiate().""" 
    229        if self.context: context = self.context 238        if self.context: context = self.context 
    230        rdf_type = RDF.Statement( uri, rdf.type, RDF.Uri(type) ) 239        rdf_type = RDF.Statement( uri, rdf.type, RDF.Uri(type) ) 
    231        self.model.append( rdf_type, context ) 240        self.model.append( rdf_type, context ) 
    232        return self.instantiate( uri, context )  241        return self.instantiate( uri, context )  
    233 242 
    234    def remove (self, uri): 243    def remove (self, uri): 
    235        removeable = [] 244        removeable = [] 
    236        node = self.node_ify(uri) 245        node = self.node_ify(uri) 
    237        model = self.model 246        model = self.model 
    238        context = None 247        context = None 
    239        #if self.context: context = self.context 248        #if self.context: context = self.context 
    240        repudiate_source = RDF.Statement( node, None, None ) 249        repudiate_source = RDF.Statement( node, None, None ) 
    241        for fact in model.find_statements( repudiate_source ):#, context ): 250        for fact in model.find_statements( repudiate_source ):#, context ): 
    242            removeable.append(fact) 251            removeable.append(fact) 
    243 252 
    244        repudiate_target = RDF.Statement( None, None, node ) 253        repudiate_target = RDF.Statement( None, None, node ) 
    245        for fact in model.find_statements( repudiate_target):#, context ): 254        for fact in model.find_statements( repudiate_target):#, context ): 
    246            removeable.append(fact) 255            removeable.append(fact) 
    247 256 
    248        for fact in removeable: 257        for fact in removeable: 
    249            del model[fact]#, context] 258            del model[fact]#, context] 
    250 259 
    251    def _abbreviate_uri (self, format, match): 260    def _abbreviate_uri (self, format, match): 
    252        prefix = match.group(1) 261        prefix = match.group(1) 
    253        if self.qname.has_key(prefix): 262        if self.qname.has_key(prefix): 
    254            return format % self.qname[prefix] 263            return format % self.qname[prefix] 
    255        else: 264        else: 
    256            return prefix 265            return prefix 
    257 266 
    258    def abbreviate (self, uri, as_xml_ns = False, as_xml_entity = False): 267    def abbreviate (self, uri, as_xml_ns = False, as_xml_entity = False): 
    259        repl = None 268        repl = None 
    260        if as_xml_entity: 269        if as_xml_entity: 
    261            repl = "&%s;" 270            repl = "&%s;" 
    262        elif as_xml_ns: 271        elif as_xml_ns: 
    263            repl = "%s:" 272            repl = "%s:" 
    264        else: 273        else: 
    265            repl = "%s." 274            repl = "%s." 
    266        callback = lambda match: self._abbreviate_uri(repl, match) 275        callback = lambda match: self._abbreviate_uri(repl, match) 
    267        return re.sub( self.match_ns, callback, str(uri) ); 276        return re.sub( self.match_ns, callback, str(uri) ); 
    268 277 
    269    def dump (self, iter = None): 278    def dump (self, iter = None): 
    270        if iter is None: 279        if iter is None: 
    271            iter = self 280            iter = self 
    272         281         
    273        entities = [""] 282        entities = [""] 
    274        xmlns = [""] 283        xmlns = [""] 
    275        for qname, ns in self.ns.iteritems(): 284        for qname, ns in self.ns.iteritems(): 
    276            entities.append( '<!ENTITY %s "%s" >' %  (qname, ns) ) 285            entities.append( '<!ENTITY %s "%s" >' %  (qname, ns) ) 
    277            xmlns.append( 'xmlns:%s="%s"' % (qname, ns) ) 286            xmlns.append( 'xmlns:%s="%s"' % (qname, ns) ) 
    278                 287                 
    279        out = '<?xml version="1.0"?>\n<!DOCTYPE rdf:RDF [' 288        out = '<?xml version="1.0"?>\n<!DOCTYPE rdf:RDF [' 
    280        out += "\n    ".join(entities) + "\n]>\n" 289        out += "\n    ".join(entities) + "\n]>\n" 
    281        out += '<rdf:RDF xmlns="' + \ 290        out += '<rdf:RDF xmlns="' + \ 
    282                Schema_Namespaces["rdf"] + '"' + \ 291                Schema_Namespaces["rdf"] + '"' + \ 
    283                '\n    '.join(xmlns) + '>\n' 292                '\n    '.join(xmlns) + '>\n' 
    284 293 
    285        for obj in iter: 294        for obj in iter: 
    286            out += obj.dump() 295            out += obj.dump() 
    287 296 
    288        out += "</rdf:RDF>\n" 297        out += "</rdf:RDF>\n" 
    289        return out 298        return out 
    290 299 
    291    def data(self): 300    def data(self): 
    292        return self.__dict__['__factory'] 301        return self.__dict__['__factory'] 
    293 302 
    294class Iterator: 303class Iterator: 
    295    """This is typically a query result from the database. It gives you a 304    """This is typically a query result from the database. It gives you a 
    296    sequence of nodes matching a query, one by one.""" 305    sequence of nodes matching a query, one by one.""" 
    297     306     
    298    def __init__ (self, model, nodes, context = None): 307    def __init__ (self, model, nodes, context = None): 
    299         308         
    300        """__init__ is supplied a model, a Redland iterator or sequence, 309        """__init__ is supplied a model, a Redland iterator or sequence, 
    301        and an optional context. This object is really a wrapper around the 310        and an optional context. This object is really a wrapper around the 
    302        Redland iterator.""" 311        Redland iterator.""" 
    303 312 
    304        self.model = model 313        self.model = model 
    305        self.context = context 314        self.context = context 
    306        if type(nodes) is RDF.Stream: 315        if type(nodes) is RDF.Stream: 
    307            self.nodes = RDF.StreamIter(nodes) 316            self.nodes = RDF.StreamIter(nodes) 
    308        else: 317        else: 
    309            self.nodes = nodes 318            self.nodes = nodes 
    310 319 
    311    def next_node (self): 320    def next_node (self): 
    312        """next_node() jumps the hoops between RDF.Iterator and RDF.Sequence.""" 321        """next_node() jumps the hoops between RDF.Iterator and RDF.Sequence.""" 
    313        node = context = None 322        node = context = None 
    314 323 
    315        node = self.nodes.next() 324        node = self.nodes.next() 
    316 325 
    317        if type(node) is not tuple: 326        if type(node) is not tuple: 
    318            return (node, None) 327            return (node, None) 
    319        else: 328        else: 
    320            return node 329            return node 
    321             330             
    322    def __iter__ (self): 331    def __iter__ (self): 
    323        return self 332        return self 
    324 333 
    325    def next (self): 334    def next (self): 
    326        while True: 335        while True: 
    327            node, context = self.next_node() 336            node, context = self.next_node() 
    328         337         
    329            """Items are filtered until they match a given context, if one is 338            """Items are filtered until they match a given context, if one is 
    330            provided.""" 339            provided.""" 
    331            if self.context is not None and context != self.context: 340            if self.context is not None and context != self.context: 
    332                continue 341                continue 
    333                 342                 
    334            if node is None: 343            if node is None: 
    335                raise StopIteration 344                raise StopIteration 
    336 345 
    337            """If our list contains a sequence of statements, we only want the 346            """If our list contains a sequence of statements, we only want the 
    338            subject uri of each statement.""" 347            subject uri of each statement.""" 
    339 348 
    340            if type(node) is RDF.Statement: 349            if type(node) is RDF.Statement: 
    341                node = node.subject 350                node = node.subject 
    342             351             
    343            """We instantiate an object from the statements which contain this 352            """We instantiate an object from the statements which contain this 
    344            subject uri.""" 353            subject uri.""" 
    345         354         
    346            obj = self.model.instantiate(node) 355            obj = self.model.instantiate(node) 
    347            return obj 356            return obj 
    348 357 
    349    def first (self): 358    def first (self): 
    350        object = None 359        object = None 
    351        try: 360        try: 
    352            object = self.next() 361            object = self.next() 
    353        except StopIteration: 362        except StopIteration: 
    354            object = None 363            object = None 
    355        return object 364        return object 
    356 365 
    357    def list (self): 366    def list (self): 
    358        return [object for object in self] 367        return [object for object in self] 
    359 368 
    360    def filter (self, prop, value = None): 369    def filter (self, prop, value = None): 
    361        return Filter(self, prop, value) 370        return Filter(self, prop, value) 
    362 371 
    363class Filter (Iterator): 372class Filter (Iterator): 
    364    def __init__ (self, iter, prop, value = None): 373    def __init__ (self, iter, prop, value = None): 
    365        self.prop  = prop 374        self.prop  = prop 
    366        self.value = value 375        self.value = value 
    367        self.iter  = iter 376        self.iter  = iter 
    368 377 
    369    def next (self): 378    def next (self): 
    370        while True: # StopIteration breaks this loop 379        while True: # StopIteration breaks this loop 
    371            obj = self.iter.next() 380            obj = self.iter.next() 
    372            if self.value is None: 381            if self.value is None: 
    373                if obj[self.prop] is not None: 382                if obj[self.prop] is not None: 
    374                    return obj 383                    return obj 
    375            elif self.prop is None: 384            elif self.prop is None: 
    376                for st in obj: 385                for st in obj: 
    377                    if str(st.object) == self.value: 386                    if str(st.object) == self.value: 
    378                        return obj 387                        return obj 
    379            elif str(obj[self.prop]) == str(self.value): 388            elif str(obj[self.prop]) == str(self.value): 
    380                return obj 389                return obj 
    381 390 
    382class Namespace: 391class Namespace: 
    383    """The rdfobj.Namespace class provides RDF namespace magic.""" 392    """The rdfobj.Namespace class provides RDF namespace magic.""" 
    384     393     
    385    def __init__ (self, uri): 394    def __init__ (self, uri): 
    386        """__init__() takes the URI prefix of the RDF namespace and creates 395        """__init__() takes the URI prefix of the RDF namespace and creates 
    387        a uri() method on the Namespace object that returns that URI prefix.""" 396        a uri() method on the Namespace object that returns that URI prefix.""" 
    388 397 
    389        self.uri = lambda x = None: uri 398        self.uri = lambda x = None: uri 
    390 399 
    391    def __getitem__ (self, property): 400    def __getitem__ (self, property): 
    392        """Any properties fetched from the Namespace object return a full RDF 401        """Any properties fetched from the Namespace object return a full RDF 
    393        URI for any requested property.""" 402        URI for any requested property.""" 
    394     403     
    395        return RDF.Uri(self.uri() + str(property)) 404        return RDF.Uri(self.uri() + str(property)) 
    396 405 
    397    def __getattr__ (self, property): 406    def __getattr__ (self, property): 
    398        """Any properties fetched from the Namespace object return a full RDF 407        """Any properties fetched from the Namespace object return a full RDF 
    399        URI for any requested property.""" 408        URI for any requested property.""" 
    400     409     
    401        return self.__getitem__(property) 410        return self.__getitem__(property) 
    402     411     
    403    def __str__ (self): 412    def __str__ (self): 
    404        """Namespace objects also conveniently stringify to their URI prefix.""" 413        """Namespace objects also conveniently stringify to their URI prefix.""" 
    405         414         
    406        return str(self.uri()) 415        return str(self.uri()) 
    407 416 
    408class Object (dict): 417class Object (dict): 
    409    """The rdfobj.Object class provides a working base class for all RDF 418    """The rdfobj.Object class provides a working base class for all RDF 
    410    objects. rdfobj.Object inherits from dict.""" 419    objects. rdfobj.Object inherits from dict.""" 
    411     420     
    412    def __init__ (self, model, uri, context = None): 421    def __init__ (self, model, uri, context = None): 
    413        """__init__() takes a reference to the object's rdfobj.Model, and 422        """__init__() takes a reference to the object's rdfobj.Model, and 
    414        to its own RDF.Node or URI.""" 423        to its own RDF.Node or URI.""" 
    415         424         
    416        dict.__init__(self) 425        dict.__init__(self) 
    417        self.__dict__['__context'] = context 426        self.__dict__['__context'] = context 
    418        self.__dict__['__factory'] = model 427        self.__dict__['__factory'] = model 
    419        self.__dict__['__node'] = uri 428        self.__dict__['__node'] = uri 
    420 429 
    421    def __to_uri (self, key): 430    def __to_uri (self, key): 
    422        factory = self.__dict__['__factory'] 431        factory = self.__dict__['__factory'] 
    423        if type(key) is str and key.find("_") != -1: 432        if type(key) is str and key.find("_") != -1: 
    424            """If the attribute name contains an underscore, extract the qname 433            """If the attribute name contains an underscore, extract the qname 
    425            and property, and look up the qname in the parent model's qname map.""" 434            and property, and look up the qname in the parent model's qname map.""" 
    426 435 
    427            (qname, property) = key.split("_", 1) 436            (qname, property) = key.split("_", 1) 
    428 437 
    429            if factory.ns.has_key(qname): 438            if factory.ns.has_key(qname): 
    430                """If the qname is present, formulate the URI of the RDF property 439                """If the qname is present, formulate the URI of the RDF property 
    431                and return the value from the internal dict, if present, otherwise, 440                and return the value from the internal dict, if present, otherwise, 
    432                return None.""" 441                return None.""" 
    433 442 
    434                key = str( factory.ns[qname] ) + property 443                key = str( factory.ns[qname] ) + property 
    435 444 
    436        if type(key) is not RDF.Node: 445        if type(key) is not RDF.Node: 
    437            key = factory.node_ify(key) 446            key = factory.node_ify(key) 
    438             447             
    439        return key 448        return key 
    440 449 
    441    def __getattr__ (self, key): 450    def __getattr__ (self, key): 
    442        """Attributes on rdfobj.Object are either built-in or take the form 451        """Attributes on rdfobj.Object are either built-in or take the form 
    443        {qname}_{property}.""" 452        {qname}_{property}.""" 
    444   453   
    445        if self.__dict__.has_key(key): 454        if self.__dict__.has_key(key): 
    446            """If the attribute name is built-in, 455            """If the attribute name is built-in, 
    447            then return the underlying Python attribute.""" 456            then return the underlying Python attribute.""" 
    448            return self.__dict__[key] 457            return self.__dict__[key] 
    449        elif isinstance(key, RDF.Uri) or key.find("_") > 0: 458        elif isinstance(key, RDF.Uri) or key.find("_") > 0: 
    450            return self[key] 459            return self[key] 
    451        else: 460        else: 
    452            raise AttributeError, \ 461            raise AttributeError, \ 
    453                "rdfobj.Object instance has no attribute '%s'" % key 462                "rdfobj.Object instance has no attribute '%s'" % key 
    454 463 
    455    def __setattr__ (self, key, value): 464    def __setattr__ (self, key, value): 
    456        """The same rules apply when setting an attribute on an rdfobj.Object.""" 465        """The same rules apply when setting an attribute on an rdfobj.Object.""" 
    457        if key.find("_") <= 0 or self.__dict__.has_key(key): 466        if key.find("_") <= 0 or self.__dict__.has_key(key): 
    458            """If the attribute name starts with an underscore, or doesn't 467            """If the attribute name starts with an underscore, or doesn't 
    459            contain one at all, or is built-in, then set the underlying Python 468            contain one at all, or is built-in, then set the underlying Python 
    460            attribute.""" 469            attribute.""" 
    461            self.__dict__[key] = value  470            self.__dict__[key] = value  
    462        else: 471        else: 
    463            self[key] = value 472            self[key] = value 
    464 473 
    465        return value 474        return value 
    466 475 
    467    def __iter__ (self): 476    def __iter__ (self): 
    468        """When used as an iterator, rdfobj.Object returns an iterator of 477        """When used as an iterator, rdfobj.Object returns an iterator of 
    469        statements about that object.""" 478        statements about that object.""" 
    470 479 
    471        context = self.__dict__["__context"] 480        context = self.__dict__["__context"] 
    472        model   = self.__dict__["__factory"].model 481        model   = self.__dict__["__factory"].model 
    473        node    = self.__dict__["__node"] 482        node    = self.__dict__["__node"] 
    474 483 
    475        o = model.find_statements(RDF.Statement(node, None, None), context) 484        o = model.find_statements(RDF.Statement(node, None, None), context) 
    476        return RDF.StreamIter(o) 485        return RDF.StreamIter(o) 
    477 486 
    478    def __getitem__ (self, key): 487    def __getitem__ (self, key): 
    479        """rdfobj.Object can be used as a dict. Keys are expected to be full 488        """rdfobj.Object can be used as a dict. Keys are expected to be full 
    480        RDF predicate URIs.""" 489        RDF predicate URIs.""" 
    481        values = self.list(key) 490        values = self.list(key) 
    482        if values: 491        if values: 
    483            return values[0] 492            return values[0] 
    484        else: 493        else: 
    485            return None 494            return None 
    486     495     
    487    def list (self, key): 496    def list (self, key): 
    488        factory = self.__dict__["__factory"] 497        factory = self.__dict__["__factory"] 
    489        context = self.__dict__["__context"] 498        context = self.__dict__["__context"] 
    490        node    = self.__dict__["__node"] 499        node    = self.__dict__["__node"] 
    491 500 
    492        key = self.__to_uri(key)  501        key = self.__to_uri(key)  
    493            502            
    494        """If the rdfobj.Object dict contains a URI for that key, 503        """If the rdfobj.Object dict contains a URI for that key, 
    495        then the rdfobj.Object corresponding to that URI is returned.""" 504        then the rdfobj.Object corresponding to that URI is returned.""" 
    496        values = [] 505        values = [] 
    497        for val, ctxt in factory.model.get_targets_context( node, key ): 506        for val, ctxt in factory.model.get_targets_context( node, key ): 
    498            if context is None or context == ctxt: 507            if context is None or context == ctxt: 
    499                if not val.is_literal(): 508                if not val.is_literal(): 
    500                    # print "### FETCH %s (%s)" % (val, type(val)) 509                    # print "### FETCH %s (%s)" % (val, type(val)) 
    501                    val = factory.fetch( val, context ) 510                    val = factory.fetch( val, context ) 
    502                else: 511                else: 
    503                    val = str(val) 512                    val = str(val) 
    504                values.append(val) 513                values.append(val) 
    505        return values 514        return values 
    506     515     
    507    def __setitem__ (self, key, values): 516    def __setitem__ (self, key, values): 
    508        """Setting items in the rdfobj.Object dict works in a similar fashion. 517        """Setting items in the rdfobj.Object dict works in a similar fashion. 
    509        URIs are stored in place of objects (due to object stringification).""" 518        URIs are stored in place of objects (due to object stringification).""" 
    510         519         
    511        context = self.__dict__["__context"]     520        context = self.__dict__["__context"]     
    512        node    = self.__dict__["__node"]        521        node    = self.__dict__["__node"]        
    513        factory = self.__dict__["__factory"] 522        factory = self.__dict__["__factory"] 
    514        model   = factory.model 523        model   = factory.model 
    515 524 
    516        if key is None: 525        if key is None: 
    517            raise ValueError, "rdfobj.Object keys must not be None" 526            raise ValueError, "rdfobj.Object keys must not be None" 
    518        key = self.__to_uri(key)  527        key = self.__to_uri(key)  
    519 528 
    520        # First remove old value 529        # First remove old value 
    521        # Setting a value of None has the same effect as del... 530        # Setting a value of None has the same effect as del... 
    522        del self[key] 531        del self[key] 
    523        if values is None: return None 532        if values is None: return None 
    524 533 
    525        if not type(values) is list: 534        if not type(values) is list: 
    526            values = [values] 535            values = [values] 
    527 536 
    528        facts = [] 537        facts = [] 
    529        for val in values: 538        for val in values: 
    530            if type(val) is Object: 539            if type(val) is Object: 
    531                val = val.uri()   540                val = val.uri()   
    532            else: 541            else: 
    533                val = factory.node_ify(val) 542                val = factory.node_ify(val) 
    534            fact = RDF.Statement( node, key, val ) 543            fact = RDF.Statement( node, key, val ) 
    535            model.append( fact, context ) 544            model.append( fact, context ) 
    536            facts.append( fact ) 545            facts.append( fact ) 
    537 546 
    538        return facts 547        return facts 
    539 548 
    540    def __delitem__ (self, key): 549    def __delitem__ (self, key): 
    541        """Items can be removed from the rdfobj.Object as you'd expect. The 550        """Items can be removed from the rdfobj.Object as you'd expect. The 
    542        process is two-staged to keep Redland from segfaulting.""" 551        process is two-staged to keep Redland from segfaulting.""" 
    543 552 
    544        context = self.__dict__["__context"]     553        context = self.__dict__["__context"]     
    545        model   = self.__dict__["__factory"].model 554        model   = self.__dict__["__factory"].model 
    546        node    = self.__dict__["__node"]        555        node    = self.__dict__["__node"]        
    547         556         
    548        key = self.__to_uri(key) 557        key = self.__to_uri(key) 
    549 558 
    550        removeable = [] 559        removeable = [] 
    551        repudiate = RDF.Statement( node, key, None ) 560        repudiate = RDF.Statement( node, key, None ) 
    552        for fact in model.find_statements( repudiate, context ): 561        for fact in model.find_statements( repudiate, context ): 
    553            removeable.append(fact) 562            removeable.append(fact) 
    554 563 
    555        for fact in removeable: 564        for fact in removeable: 
    556            del model[fact, context] 565            del model[fact, context] 
    557 566 
    558    def __repr__ (self): 567    def __repr__ (self): 
    559        """rdfobj.Objects stringify as their URI.""" 568        """rdfobj.Objects stringify as their URI.""" 
    560        return str(self.uri()) 569        return str(self.uri()) 
    561 570 
    562    def uri (self): 571    def uri (self): 
    563        """The object URI can be found out from the uri() method.""" 572        """The object URI can be found out from the uri() method.""" 
    564        node = self.__dict__["__node"] 573        node = self.__dict__["__node"] 
    565        if type(node) is RDF.Uri: 574        if type(node) is RDF.Uri: 
    566            return node 575            return node 
    567        elif node.is_resource(): 576        elif node.is_resource(): 
    568            return node.uri 577            return node.uri