1
2 """This module contains a system for creating rdf query classes in a mainly
3 declarative manner. These are built by subclassing ``RdfQuery`` and creating
4 class attributes by using ``Selector``:s. There are several selectors provided
5 in this module to cover all regular cases of data acquisition.
6 """
7
8 from itertools import chain
9 from types import ModuleType
10 import warnings
11 from rdflib import RDF, RDFS, Namespace, URIRef, BNode, Literal
12 from rdflib import ConjunctiveGraph
13 try: import simplejson
14 except ImportError: simplejson = None
15 from oort.util.code import contract
16
17
18
19
20 THIS_QUERY = object()
21
22 MODULE_SEP = ':'
23
24
25
26
27
29 __slots__ = ('predicate', '_namespace', 'filters',
30 '_subQueryMarker', '_finalSubQuery',
31 '_name', '_queryClass')
32
33 - def __init__(self, predBase=None, subQuery=None):
43
44 @contract.state_change
55
56
57
58
59
75
76 - def __get__(self, rdfQueryInstance, rdfQueryOwnerClass=None):
87
88 @contract.default_method
96
99 returnList = isinstance(rawresults, list)
100 if not rawresults:
101 if returnList: return []
102 else: return None
103
104 subQuery = self.get_sub_query()
105 if not subQuery:
106 return rawresults
107 else:
108
109
110 run_query = query_or_cached(subQuery, rdfQueryInstance._execCache)
111 if returnList:
112 return [run_query(graph, lang, uri) for uri in rawresults]
113 else:
114 return run_query(graph, lang, rawresults)
115
116 @contract.template_method
117 - def select(self, graph, lang, subject):
118 raise NotImplementedError
119 return None or []
120
121 @contract.default_method
124
128
131
134
138
139 - def __set__(self, rdfQueryInstance, value):
156
157 @contract.default_method
163
164
166 __slots__ = ('selectArgs', 'result', 'hasRun')
167 - def __init__(self, graph, lang, subject):
171
172
199
200
202 __metaclass__ = _rdf_query_meta
203
204 RDF_TYPE = RDFS.Resource
205
206
207
208 - def __init__(self, graph, lang, subject, execCache=None):
209 self._graph = graph
210 self._subject = subject
211 self._lang = lang
212 self._preparedSelects = self._make_prepare_selects()
213 self._execCache = execCache
214
216 prepareds = {}
217 graph, lang, subject = self._graph, self._lang, self._subject
218 for name, selector in self._selectors.items():
219 if not subject:
220
221
222
223
224
225 continue
226 prep = PreparedSelect(graph, lang, subject)
227 prepareds[name] = prep
228 return prepareds
229
231 return str(self._subject)
232
234 if isinstance(other, RdfQuery):
235 return self._subject == other._subject
236 else:
237 return self._subject == other
238
239 @classmethod
241 def bound_query(graph, _lang, _subject):
242 return cls(graph, lang or _lang, subject)
243 bound_query.query = cls
244 bound_query.__name__ = cls.__name__
245 return bound_query
246
247 @classmethod
249 graph = ConjunctiveGraph()
250 query = cls(graph, lang, subject)
251 for k, v in data.items():
252 setattr(query, k, v)
253 return query
254
255 @classmethod
256 - def find_by(cls, graph, lang, execCache=None, **kwargs):
262
263 @property
266
269
271 subject = self._subject or BNode()
272 if not subject: return
273
274 lgraph = lgraph or ConjunctiveGraph()
275
276 for t in self._graph.objects(subject, RDF.type):
277 lgraph.add((subject, RDF.type, t))
278
279 for selector in self._selectors.values():
280 value = selector.__get__(self)
281 if not value:
282 continue
283 selector.back_to_graph(lgraph, subject, value)
284
285
286 for t in lgraph:
287 if None in t: lgraph.remove(t)
288 return lgraph
289
291 return self.to_graph().serialize(format='pretty-xml')
292
293 - def to_dict(self, keepSubject=False):
317
323
324 - def to_json(self, keepSubject=False):
325 if simplejson:
326 return simplejson.dumps(self.to_dict(keepSubject))
327 else:
328 raise NotImplementedError
329
330
331
332
333
334
335
336
337 from weakref import WeakValueDictionary
338
340 """
341 This is a query execution cache which reuses results for the same query,
342 subject and language, avoiding multiple instances of the same query when
343 given the same subject and lang.
344 """
346 self.cache = WeakValueDictionary()
347 - def __call__(self, query, graph, lang, subject):
356
357
359 if execCache:
360 def run_query(graph, lang, uri):
361 return execCache(rdfQuery, graph, lang, uri)
362 return run_query
363 else:
364 return rdfQuery
365
366
371
372
373
374
375
380 return filter(self.func, items)
381
382
384 - def __init__(self, obj=None, reverse=False, ignoreCase=False):
385 if callable(obj):
386 self.attr = None
387 self.func = obj
388 else:
389 self.attr = obj
390 self.func = None
391 self.reverse = reverse
392 self.ignoreCase = ignoreCase
394 copy = items[:]
395 copy.sort(self.sort)
396 if self.reverse:
397 copy.reverse()
398 return copy
399 - def sort(self, r1, r2):
400 attr = self.attr
401 func = self.func
402 if attr:
403 v1, v2 = getattr(r1, attr, r1), getattr(r2, attr, r2)
404 elif func:
405 v1, v2 = func(r1), func(r2)
406 else:
407 v1, v2 = r1, r2
408 if self.ignoreCase:
409 v1, v2 = v1.lower(), v2.lower()
410 return cmp(v1, v2)
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
439
440
444
449
450
451 -class one(UnarySelector):
452 - def select(self, graph, lang, subject):
454
455
456 -class each(EachSelector):
457 - def select(self, graph, lang, subject):
458 return list(graph.objects(subject, self.predicate))
459
460
462 - def select(self, graph, lang, subject):
464
467
468
470 - def select(self, graph, lang, subject):
471 return list(graph.subjects(self.predicate, subject))
472
476
477
479 - def __init__(self, predBase=None, subQuery=None, multiple=False):
482
483 - def select(self, graph, lang, subject):
484 if self.multiple:
485 allItems = [graph.items(res)
486 for res in graph.objects(subject, self.predicate)]
487 return list(chain(*allItems))
488 else:
489 return list(graph.items(
490 graph.value(subject, self.predicate, None, any=True)
491 ))
492
494 if not values:
495 graph.add((subject, self.predicate, RDF.nil))
496 return
497 bnode = BNode()
498 graph.add((subject, self.predicate, bnode))
499 for value in values:
500 back_from_value(graph, bnode, RDF.first, value)
501 newBnode = BNode()
502 graph.add((bnode, RDF.rest, newBnode))
503 bnode = newBnode
504 graph.add((bnode, RDF.rest, RDF.nil))
505
506
512
513 -class localized(TypeLocalized, UnarySelector):
514 - def select(self, graph, lang, subject):
515 first = None
516 for value in graph.objects(subject, self.predicate):
517 if not first: first = value
518 if getattr(value, 'language', None) == lang:
519 return value
520 return first
521
522
523
524
525 try:
526 from oort.util._genshifilters import language_filtered_xml
527 except ImportError, e:
528 warnings.warn("Could not import _genshifilters. Error was: %r. The selector 'localized_xml' will not be available." % e)
529 else:
531 """This selector removes any elements with an xml:lang other than the
532 current language. It also supports the never standardized 'rdf-wrapper' in
533 XML Literals, who are removed from the output.
534
535 Important! This is currently tied to the Genshi Templating System, and may
536 not work as expected in all cases."""
537
538 - def select(self, graph, lang, subject):
541
543 if isinstance(value, basestring):
544 value = Literal(value, datatype=RDF.XMLLiteral)
545 return value
546
548 - def select(self, graph, lang, subject):
549 valueDict = {}
550 for value in graph.objects(subject, self.predicate):
551 valueDict[value.language] = value
552 return valueDict
553
557
558
560 - def select(self, graph, lang, subject):
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
579 "Use as decorator for methods of an RdfQuery subclass to convert them to selectors."
584 return self.func(rdfQueryInstance, *selectArgs)
585 @classmethod
587 def decorator(func):
588 sel = cls(func)
589 for fltr in filters:
590 sel.add_filter(fltr)
591 return sel
592 return decorator
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609 -class QueryContext(object):
610 """
611 A query context, used to provide a managed context for query execution.
612
613 Initalized with:
614
615 - graph
616 - language or getter for language
617 - a set of queries or a modules containing queries
618 Accessible as attributes on the context or via view_for using RDF_TYPE
619
620 """
621
622 - def __init__(self, graph, langobj, queries=None, query_modules=None):
623 self._graph = graph
624 self._execCache = ExecCache()
625
626 if callable(langobj):
627 get_lang = langobj
628 else:
629 def get_lang(): return langobj
630 self._get_lang = get_lang
631
632 self._querydict = querydict = {}
633 if queries:
634 for query in queries:
635 querydict[query.__name__] = query
636 if query_modules:
637 for module in query_modules:
638 for name, obj in module.__dict__.items():
639 if isinstance(obj, type) and issubclass(obj, RdfQuery):
640 querydict[name] = obj
641
642 self._queryTypeMap = {}
643 for query in querydict.values():
644 self._queryTypeMap[query.RDF_TYPE] = query
645
646 - def __getattr__(self, name):
647 try:
648 query = self._querydict[name]
649 return self._prepared_query(query)
650 except KeyError:
651 raise AttributeError("%s has no attribute '%s'" % (self, name))
652
653 - def view_for(self, uriref):
654 for typeref in self._graph.objects(uriref, RDF.type):
655 query = self._queryTypeMap.get(typeref)
656 if query:
657 return self._prepared_query(query)(uriref)
658 raise KeyError("%s has no query for type '%s'" % (self, uriref))
659
660 - def _prepared_query(self, query):
661 return self.PreparedQuery(self, query)
662
663 - class PreparedQuery(object):
664 __slots__ = ('query', 'context')
665
666 - def __init__(self, context, query):
667 self.context = context
668 self.query = query
669
670 - def __call__(self, subject):
671 cx = self.context
672 return cx._execCache(self.query, cx._graph, cx._get_lang(), subject)
673
674 - def find_all(self):
675 cx = self.context
676 for subject in cx._graph.subjects(RDF.type, self.query.RDF_TYPE):
677 yield cx.view_for(subject)
678
679 - def find_by(self, **kwargs):
680 cx = self.context
681 return self.query.find_by(cx._graph, cx._get_lang(),
682 execCache=cx._execCache, **kwargs)
683