root / rdfalchemy / trunk / rdfalchemy / descriptors.py

Revision 136, 14.8 kB (checked in by phil, 6 months ago)

RDFAlchemy added descriptors for locale and some tests

Line 
1#!/usr/bin/env python
2# encoding: utf-8
3"""
4descriptors.py
5
6Created by Philip Cooper on 2008-02-03.
7Copyright (c) 2008 Openvest. All rights reserved.
8"""
9
10from rdflib import URIRef, BNode, Namespace
11from rdflib.Identifier import Identifier 
12from rdfalchemy import rdfSubject, Literal 
13from copy import copy
14
15import logging
16
17__all__=["rdfSingle","rdfMultiple","rdfList","rdfContainer","owlTransitive"]
18
19#console = logging.StreamHandler()
20#formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
21#console.setFormatter(formatter)
22log=logging.getLogger(__name__)
23#log.setLevel(logging.DEBUG)
24#log.addHandler(console)
25
26RDF = Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#") # need the RDF['_%d'%i] ability
27
28# helper function, might be somewhere in rdflib I need to look for it there
29def getList(sub, pred=None, db=None):
30    """Attempts to return a list from sub (subject that is)
31    passed in if it is a Collection or a Container (Bag,Seq or Alt)"""
32    if not db:
33        if isinstance(sub,rdfSubject):
34            db=sub.db
35        else:
36            db=rdfSubject.db
37    if isinstance(sub,rdfSubject):
38        sub = sub.resUri
39    if pred:
40        base = db.value(sub, pred, any=True)
41    else:
42        # if there was no predicate assume a base node was passed in
43        base=sub
44    if type(base) != BNode:
45        # Doesn't look like a list or a collection, just return multiple values (or an error?)
46        val=[o for o in db.objects(sub, pred)]
47        return val
48    members=[]
49    first = db.value(base, RDF.first)
50    # OK let's work at returning a list if there is an RDF.first
51    if first:
52        while first:
53            members.append(first)
54            base = db.value(base, RDF.rest)
55            first = db.value(base, RDF.first)
56        return members
57    # OK let's work at returning a Collection (Seq,Bag or Alt) if was no RDF.first
58    else:
59        i=1       
60        first=db.value(base, RDF._1)
61        if not first:
62            raise AttributeError, "Not a list, or collection but another type of BNode"
63        while first:
64            members.append(first)
65            i += 1
66            first=db.value(base, RDF['_%d'%i])
67        return members
68       
69def value2object(value):
70    """suitable for a triple takes a value and returns a Literal, URIRef or BNode
71    suitable for a triple"""
72    if isinstance(value, rdfSubject):
73        return value.resUri
74    elif isinstance(value, Identifier):
75        return value
76    else:
77        return Literal(value)
78
79
80##################################################################################
81# define a series of descriptors
82# each one will map an attribute of a class (derived from rdfObjet) to a predicate
83##################################################################################
84
85
86class rdfAbstract(object):
87    """Abstract base class for descriptors
88    Descriptors are to map class instance variables to predicates
89    optional cacheName is where to store items
90    range_type is the rdf:type of the range of this predicate"""
91    def __init__(self, pred, cacheName=None, range_type=None):
92        self.pred = pred
93        self.name = cacheName or pred
94        self.range_type = range_type
95   
96    @property
97    def range_class(self):
98        """return the class that this descriptor is mapped to through the range_type"""
99        if self.range_type:
100            try:
101                return self._mappedClass
102            except AttributeError:
103                log.warn("Descriptor %s has range of: %s but not yet mapped"%(self, self.range_type))
104                return rdfSubject
105        else:
106            return rdfSubject
107           
108    def __delete__(self, obj):
109        """deletes or removes from the database triples with:
110          obj.resUri as subject and self.pred as predicate
111          if the object of that triple is a Literal that stop
112          if the object of that triple is a BNode
113          then cascade the delete if that BNode has no further references to it
114          i.e. it is not the object in any other triples.
115        """ 
116        # be done ala getList above
117        log.debug("DELETE with descriptor for %s on %s"%(self.pred, obj.n3()))       
118        # first drop the cached value
119        if obj.__dict__.has_key(self.name):
120            del obj.__dict__[self.name]
121        # next, drop the triples
122        obj.__delitem__(self.pred)         
123
124
125
126                   
127class rdfSingle(rdfAbstract):
128    '''This is a Discriptor
129    Takes a the URI of the predicate at initialization
130    Expects to return a single item
131    on Assignment will set that value to the
132    ONLY triple with that subject,predicate pair'''
133    def __init__(self, pred, cacheName=None, range_type=None):
134        super(rdfSingle, self).__init__(pred, cacheName, range_type)
135       
136    def __get__(self, obj, cls):
137        if obj is None:
138            return self
139        if self.name in obj.__dict__:
140            return obj.__dict__[self.name]
141        log.debug("Geting with descriptor %s for %s"%(self.pred,obj.n3()))
142        val=obj.__getitem__(self.pred)       
143        if isinstance(val, (rdfSubject, BNode, URIRef)):
144            val = self.range_class(val)
145        obj.__dict__[self.name]= val
146        return val
147   
148    def __set__(self, obj, value):
149        log.debug("SET with descriptor value %s of type %s"%(value,type(value)))
150        #setattr(obj, self.name, value)  #this recurses indefinatly
151        if isinstance(value,(list,tuple,set)):
152            raise AttributeError("to set an rdfSingle you must pass in a single value")
153        obj.__dict__[self.name]= value
154        o =value2object(value)
155        obj.db.set((obj.resUri, self.pred, o))
156       
157   
158class rdfMultiple(rdfAbstract):
159    '''This is a Discriptor   
160       Expects to return a list of values (could be a list of one)'''
161    def __init__(self, pred, cacheName=None, range_type=None):
162        super(rdfMultiple, self).__init__(pred, cacheName, range_type)
163       
164    def __get__(self, obj, cls):
165        if obj is None:
166            return self
167        if self.name in obj.__dict__:
168            return obj.__dict__[self.name]
169        val=[o for o in obj.db.objects(obj.resUri, self.pred)]
170        log.debug("Geting with descriptor %s for %s"%(self.pred,obj.n3()))
171        # check to see if this is a Container or Collection
172        # if so, return collection as a list
173        if len(val) == 1 \
174           and (obj.db.value(o,RDF.first) or obj.db.value(o,RDF._1)): 
175                  val=getList(obj, self.pred)
176        val=[(isinstance(v, (BNode,URIRef)) and self.range_class(v) or v.toPython()) for v in val]
177        obj.__dict__[self.name]= val
178        return val
179
180    def __set__(self, obj, newvals):
181        log.debug("SET with descriptor value %s of type %s"%(newvals,type(newvals)))
182        if not isinstance(newvals, (list,tuple)):
183            raise AttributeError("to set a rdfMultiple you must pass in a list (it can be a list of one)")
184        try:
185            oldvals = obj.__dict__[self.name]
186        except KeyError:
187            oldvals = []
188            obj.__dict__[self.name] = oldvals
189        for value in oldvals:
190            if value not in newvals:
191                obj.db.remove((obj.resUri,self.pred, value2object(value)))
192                log.debug("removing: %s, %s, %s"%(obj.n3(),self.pred,value))
193        for value in newvals:
194            if value not in oldvals:
195                obj.db.add((obj.resUri, self.pred, value2object(value)))
196                log.debug("adding: %s, %s, %s"%(obj.n3(),self.pred,value))
197        obj.__dict__[self.name] = copy(newvals)
198       
199class rdfBest(rdfSingle):
200    '''This is a Discriptor  that returns one value that is the
201    "best" result out of possible multiple matches
202   
203    returns a single value or None
204   
205    It is the responsibility of the select_fun to return a default
206    like choices[0] if no "Best" is found'''
207   
208    def __init__(self, pred, select_fun = None, cacheName=None, range_type=None ):
209        if select_fun:
210            self.select_fun = select_fun
211        super(rdfMultiple, self).__init__(pred, range_type)
212       
213    def __get__(self, obj, cls):
214        if obj is None:
215            return self
216        if self.name in obj.__dict__:
217            return obj.__dict__[self.name]
218        log.debug("Geting with descriptor %s for %s"%(self.pred,obj.n3()))
219        vals=[o for o in obj.db.objects(obj.resUri, self.pred)]
220        if vals:
221            val = self.select_fun(vals)
222            val = isinstance(val, (BNode,URIRef)) and self.range_class(val) or val.toPython()
223        else:
224            val = None
225        obj.__dict__[self.name]= val
226        return val
227
228class rdfLocale(rdfBest):
229    '''This is like rdfBest with a predefined select_fun to select
230    from multiple choices like labels or comments and select the one
231    with the correct locale'''   
232    def __init__(self, pred, lang, cacheName=None):
233        self.lang = lang
234        cacheNameLang = cacheName or ("%s@%s" % (pred, lang))
235        super(rdfBest, self).__init__(pred,cacheName = cacheNameLang)
236
237    def select_fun(self, choices):
238        for x in choices:
239            if isinstance(x,Literal) and x.language==self.lang:
240                return x
241        return choices[0]
242
243class rdfList(rdfMultiple):
244    '''This is a Discriptor   
245       Expects to return a list of values (could be a list of one)
246       `__set__` will set the predicate as a RDF List'''
247       
248    def __init__(self, pred, range_type=None):
249        super(rdfMultiple, self).__init__(pred, range_type)
250       
251    def __get__(self, obj, cls):
252        if obj is None:
253            return self 
254        if self.name in obj.__dict__:
255            return obj.__dict__[self.name]       
256        #log.debug("Geting %s for %s"%(obj.db.qname(self.pred),obj.db.qname(obj.resUri)))
257        log.debug("Geting %s for %s"%(self.pred,obj.n3()))
258        base = obj.db.value(obj.resUri,self.pred)
259        if not base or base==RDF.nil:
260            return []
261        members=[]
262        first = obj.db.value(base, RDF.first)
263        # OK let's work at returning a list if there is an RDF.first
264        if not first:
265            raise AttributeError, ("expected node [%s] to be a list but it's not" %base.n3())
266        while first:
267            members.append(first)
268            base = obj.db.value(base, RDF.rest)
269            first = obj.db.value(base, RDF.first)
270
271        val=[((isinstance(v,BNode) or isinstance(v,URIRef)) and self.range_class(v) or v.toPython()) for v in members]
272        obj.__dict__[self.name] = val
273        return val
274       
275    def __set__(self, obj, newvals):
276        log.debug("SET with descriptor value %s of type %s"%(newvals,type(newvals)))
277        if not isinstance(newvals, (list,tuple)):
278            raise AttributeError("to set a rdfList you must pass in a list (it can be a list of one)")
279        try:
280            oldvals = obj.__dict__[self.name]
281        except KeyError:
282            oldvals = []
283            obj.__dict__[self.name] = oldvals
284        oldhead = obj.db.value(obj.resUri,self.pred)
285##          # This is a stack style where retrevial is oppisite of how it starts out
286##         newnode = RDF.nil
287##         for value in newvals:
288##             almostnewnode = newnode
289##             newnode = BNode()           
290##             obj.db.add((newnode, RDF.first, value2object(value)))
291##             obj.db.add((newnode, RDF.rest, almostnewnode))       
292        if not newvals:
293            newhead = RDF.nil
294        else:
295            newhead = BNode()
296            newtail = newhead
297            oldtail = None
298            for value in newvals:
299                if oldtail:
300                    obj.db.add((oldtail, RDF.rest, newtail))
301                obj.db.add((newtail, RDF.first, value2object(value)))
302                oldtail = newtail
303                newtail = BNode()
304            obj.db.add((oldtail, RDF.rest, RDF.nil))
305        obj.db.set((obj.resUri, self.pred, newhead))
306        if oldhead:
307            rdfSubject(oldhead)._remove(db=obj.db)
308        obj.__dict__[self.name] = copy(newvals)
309
310
311       
312class rdfContainer(rdfMultiple):
313    '''This is a Discriptor   
314       Expects to return a list of values (could be a list of one)
315       
316       container_type in `__init__` should be one of
317
318               * rdf:Seq
319               * rdf:Bag
320               * rdf:Alt
321               
322       `__set__` will set the predicate as a RDF Container type (defaults to rdf:Seq)'''
323
324    def __init__(self, pred,  range_type=None, container_type="http://www.w3.org/1999/02/22-rdf-syntax-ns#Seq"):
325        super(rdfMultiple, self).__init__(pred,  range_type)
326        self.container_type = container_type
327       
328       
329    def __get__(self, obj, cls):
330        if obj is None:
331            return self
332        if self.name in obj.__dict__:
333            return obj.__dict__[self.name]               
334        #log.debug("Geting %s for %s"%(obj.db.qname(self.pred),obj.db.qname(obj.resUri)))
335        log.debug("Geting %s for %s"%(self.pred,obj.n3()))
336        base = obj.db.value(obj.resUri, self.pred)
337        if not base:
338            return []
339        members=[]
340        i=1       
341        first=obj.db.value(base, RDF._1)
342        if not first:
343            raise AttributeError, ("expected node [%s] to be a list but it's not" % base.n3())
344        while first:
345            members.append(first)
346            i += 1
347            first=obj.db.value(base, RDF['_%d'%i])
348
349        val=[(isinstance(v,(BNode,URIRef)) and self.range_class(v) or v.toPython()) for v in members]
350        obj.__dict__[self.name] = val
351        return val
352       
353    def __set__(self, obj, newvals):
354        log.debug("SET with descriptor value %s of type %s"%(newvals,type(newvals)))
355        if not isinstance(newvals, (list,tuple)):
356            raise AttributeError("to set a rdfList you must pass in a list (it can be a list of one)")
357        seq = obj.db.value(obj.resUri, self.pred)
358        if not seq:
359            seq = BNode()
360            obj.db.add((obj.resUri, self.pred, seq))
361            obj.db.add((seq, RDF.type, RDF.Seq))
362        for s,p,o in obj.db.triples((seq, None, None)):
363            if p.startswith(RDF['_']):
364                obj.db.remove((s,p,o))
365                if isinstance(o, BNode) and o not in newvals:
366                    rdfSubject(o)._remove(db=obj.db)
367        for i in range(len(newvals)):
368            obj.db.add((seq, RDF['_%i'%(i+1)], value2object(newvals[i])))
369        obj.__dict__[self.name] = copy(newvals)
370       
371#################################################################################