resource.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. """
  2. The [`Resource`][rdflib.resource.Resource] class wraps a
  3. [`Graph`][rdflib.graph.Graph]
  4. and a resource reference (i.e. a [`URIRef`][rdflib.term.URIRef] or
  5. [`BNode`][rdflib.term.BNode]) to support a resource-oriented way of
  6. working with a graph.
  7. It contains methods directly corresponding to those methods of the Graph
  8. interface that relate to reading and writing data. The difference is that a
  9. Resource also binds a resource identifier, making it possible to work without
  10. tracking both the graph and a current subject. This makes for a "resource
  11. oriented" style, as compared to the triple orientation of the Graph API.
  12. Resulting generators are also wrapped so that any resource reference values
  13. ([`URIRef`][rdflib.term.URIRef] and [`BNode`][rdflib.term.BNode]) are in turn
  14. wrapped as Resources. (Note that this behaviour differs from the corresponding
  15. methods in [`Graph`][rdflib.graph.Graph], where no such conversion takes place.)
  16. ## Basic Usage Scenario
  17. Start by importing things we need and define some namespaces:
  18. ```python
  19. >>> from rdflib import *
  20. >>> FOAF = Namespace("http://xmlns.com/foaf/0.1/")
  21. >>> CV = Namespace("http://purl.org/captsolo/resume-rdf/0.2/cv#")
  22. ```
  23. Load some RDF data:
  24. ```python
  25. >>> graph = Graph().parse(format='n3', data='''
  26. ... @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
  27. ... @prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
  28. ... @prefix foaf: <http://xmlns.com/foaf/0.1/> .
  29. ... @prefix cv: <http://purl.org/captsolo/resume-rdf/0.2/cv#> .
  30. ...
  31. ... @base <http://example.org/> .
  32. ...
  33. ... </person/some1#self> a foaf:Person;
  34. ... rdfs:comment "Just a Python & RDF hacker."@en;
  35. ... foaf:depiction </images/person/some1.jpg>;
  36. ... foaf:homepage <http://example.net/>;
  37. ... foaf:name "Some Body" .
  38. ...
  39. ... </images/person/some1.jpg> a foaf:Image;
  40. ... rdfs:label "some 1"@en;
  41. ... rdfs:comment "Just an image"@en;
  42. ... foaf:thumbnail </images/person/some1-thumb.jpg> .
  43. ...
  44. ... </images/person/some1-thumb.jpg> a foaf:Image .
  45. ...
  46. ... [] a cv:CV;
  47. ... cv:aboutPerson </person/some1#self>;
  48. ... cv:hasWorkHistory [ cv:employedIn </#company>;
  49. ... cv:startDate "2009-09-04"^^xsd:date ] .
  50. ... ''')
  51. ```
  52. Create a Resource:
  53. ```python
  54. >>> person = Resource(
  55. ... graph, URIRef("http://example.org/person/some1#self"))
  56. ```
  57. Retrieve some basic facts:
  58. ```python
  59. >>> person.identifier
  60. rdflib.term.URIRef('http://example.org/person/some1#self')
  61. >>> person.value(FOAF.name)
  62. rdflib.term.Literal('Some Body')
  63. >>> person.value(RDFS.comment)
  64. rdflib.term.Literal('Just a Python & RDF hacker.', lang='en')
  65. ```
  66. Resources can be sliced (like graphs, but the subject is fixed):
  67. ```python
  68. >>> for name in person[FOAF.name]:
  69. ... print(name)
  70. Some Body
  71. >>> person[FOAF.name : Literal("Some Body")]
  72. True
  73. ```
  74. Resources as unicode are represented by their identifiers as unicode:
  75. ```python
  76. >>> %(unicode)s(person) #doctest: +SKIP
  77. 'Resource(http://example.org/person/some1#self'
  78. ```
  79. Resource references are also Resources, so you can easily get e.g. a qname
  80. for the type of a resource, like:
  81. ```python
  82. >>> person.value(RDF.type).qname()
  83. 'foaf:Person'
  84. ```
  85. Or for the predicates of a resource:
  86. ```python
  87. >>> sorted(
  88. ... p.qname() for p in person.predicates()
  89. ... ) #doctest: +NORMALIZE_WHITESPACE +SKIP
  90. ['foaf:depiction', 'foaf:homepage',
  91. 'foaf:name', 'rdf:type', 'rdfs:comment']
  92. ```
  93. Follow relations and get more data from their Resources as well:
  94. ```python
  95. >>> for pic in person.objects(FOAF.depiction):
  96. ... print(pic.identifier)
  97. ... print(pic.value(RDF.type).qname())
  98. ... print(pic.value(FOAF.thumbnail).identifier)
  99. http://example.org/images/person/some1.jpg
  100. foaf:Image
  101. http://example.org/images/person/some1-thumb.jpg
  102. ```
  103. ```python
  104. >>> for cv in person.subjects(CV.aboutPerson):
  105. ... work = list(cv.objects(CV.hasWorkHistory))[0]
  106. ... print(work.value(CV.employedIn).identifier)
  107. ... print(work.value(CV.startDate))
  108. http://example.org/#company
  109. 2009-09-04
  110. ```
  111. It's just as easy to work with the predicates of a resource:
  112. ```python
  113. >>> for s, p in person.subject_predicates():
  114. ... print(s.value(RDF.type).qname())
  115. ... print(p.qname())
  116. ... for s, o in p.subject_objects():
  117. ... print(s.value(RDF.type).qname())
  118. ... print(o.value(RDF.type).qname())
  119. cv:CV
  120. cv:aboutPerson
  121. cv:CV
  122. foaf:Person
  123. ```
  124. This is useful for e.g. inspection:
  125. ```python
  126. >>> thumb_ref = URIRef("http://example.org/images/person/some1-thumb.jpg")
  127. >>> thumb = Resource(graph, thumb_ref)
  128. >>> for p, o in thumb.predicate_objects():
  129. ... print(p.qname())
  130. ... print(o.qname())
  131. rdf:type
  132. foaf:Image
  133. ```
  134. ## Schema Example
  135. With this artificial schema data:
  136. ```python
  137. >>> graph = Graph().parse(format='n3', data='''
  138. ... @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
  139. ... @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
  140. ... @prefix owl: <http://www.w3.org/2002/07/owl#> .
  141. ... @prefix v: <http://example.org/def/v#> .
  142. ...
  143. ... v:Artifact a owl:Class .
  144. ...
  145. ... v:Document a owl:Class;
  146. ... rdfs:subClassOf v:Artifact .
  147. ...
  148. ... v:Paper a owl:Class;
  149. ... rdfs:subClassOf v:Document .
  150. ...
  151. ... v:Choice owl:oneOf (v:One v:Other) .
  152. ...
  153. ... v:Stuff a rdf:Seq; rdf:_1 v:One; rdf:_2 v:Other .
  154. ...
  155. ... ''')
  156. ```
  157. From this class:
  158. ```python
  159. >>> artifact = Resource(graph, URIRef("http://example.org/def/v#Artifact"))
  160. ```
  161. we can get at subclasses:
  162. ```python
  163. >>> subclasses = list(artifact.transitive_subjects(RDFS.subClassOf))
  164. >>> [c.qname() for c in subclasses]
  165. ['v:Artifact', 'v:Document', 'v:Paper']
  166. ```
  167. and superclasses from the last subclass:
  168. ```python
  169. >>> [c.qname() for c in subclasses[-1].transitive_objects(RDFS.subClassOf)]
  170. ['v:Paper', 'v:Document', 'v:Artifact']
  171. ```
  172. Get items from the Choice:
  173. ```python
  174. >>> choice = Resource(graph, URIRef("http://example.org/def/v#Choice"))
  175. >>> [it.qname() for it in choice.value(OWL.oneOf).items()]
  176. ['v:One', 'v:Other']
  177. ```
  178. On add, other resources are auto-unboxed:
  179. ```python
  180. >>> paper = Resource(graph, URIRef("http://example.org/def/v#Paper"))
  181. >>> paper.add(RDFS.subClassOf, artifact)
  182. >>> artifact in paper.objects(RDFS.subClassOf) # checks Resource instance
  183. True
  184. >>> (paper._identifier, RDFS.subClassOf, artifact._identifier) in graph
  185. True
  186. ```
  187. ## Technical Details
  188. Comparison is based on graph and identifier:
  189. ```python
  190. >>> g1 = Graph()
  191. >>> t1 = Resource(g1, URIRef("http://example.org/thing"))
  192. >>> t2 = Resource(g1, URIRef("http://example.org/thing"))
  193. >>> t3 = Resource(g1, URIRef("http://example.org/other"))
  194. >>> t4 = Resource(Graph(), URIRef("http://example.org/other"))
  195. >>> t1 is t2
  196. False
  197. >>> t1 == t2
  198. True
  199. >>> t1 != t2
  200. False
  201. >>> t1 == t3
  202. False
  203. >>> t1 != t3
  204. True
  205. >>> t3 != t4
  206. True
  207. >>> t3 < t1 and t1 > t3
  208. True
  209. >>> t1 >= t1 and t1 >= t3
  210. True
  211. >>> t1 <= t1 and t3 <= t1
  212. True
  213. >>> t1 < t1 or t1 < t3 or t3 > t1 or t3 > t3
  214. False
  215. ```
  216. Hash is computed from graph and identifier:
  217. ```python
  218. >>> g1 = Graph()
  219. >>> t1 = Resource(g1, URIRef("http://example.org/thing"))
  220. >>> hash(t1) == hash(Resource(g1, URIRef("http://example.org/thing")))
  221. True
  222. >>> hash(t1) == hash(Resource(Graph(), t1.identifier))
  223. False
  224. >>> hash(t1) == hash(Resource(Graph(), URIRef("http://example.org/thing")))
  225. False
  226. ```
  227. The Resource class is suitable as a base class for mapper toolkits. For
  228. example, consider this utility for accessing RDF properties via qname-like
  229. attributes:
  230. ```python
  231. >>> class Item(Resource):
  232. ...
  233. ... def __getattr__(self, p):
  234. ... return list(self.objects(self._to_ref(*p.split('_', 1))))
  235. ...
  236. ... def _to_ref(self, pfx, name):
  237. ... return URIRef(self._graph.store.namespace(pfx) + name)
  238. ```
  239. It works as follows:
  240. ```python
  241. >>> graph = Graph().parse(format='n3', data='''
  242. ... @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
  243. ... @prefix foaf: <http://xmlns.com/foaf/0.1/> .
  244. ...
  245. ... @base <http://example.org/> .
  246. ... </person/some1#self>
  247. ... foaf:name "Some Body";
  248. ... foaf:depiction </images/person/some1.jpg> .
  249. ... </images/person/some1.jpg> rdfs:comment "Just an image"@en .
  250. ... ''')
  251. >>> person = Item(graph, URIRef("http://example.org/person/some1#self"))
  252. >>> print(person.foaf_name[0])
  253. Some Body
  254. ```
  255. The mechanism for wrapping references as resources cooperates with subclasses.
  256. Therefore, accessing referenced resources automatically creates new `Item`
  257. objects:
  258. ```python
  259. >>> isinstance(person.foaf_depiction[0], Item)
  260. True
  261. >>> print(person.foaf_depiction[0].rdfs_comment[0])
  262. Just an image
  263. ```
  264. """
  265. from rdflib.namespace import RDF
  266. from rdflib.paths import Path
  267. from rdflib.term import BNode, Node, URIRef
  268. __all__ = ["Resource"]
  269. class Resource:
  270. """A Resource is a wrapper for a graph and a resource identifier."""
  271. def __init__(self, graph, subject):
  272. self._graph = graph
  273. self._identifier = subject
  274. @property
  275. def graph(self):
  276. return self._graph
  277. @property
  278. def identifier(self):
  279. return self._identifier
  280. def __hash__(self):
  281. return hash(Resource) ^ hash(self._graph) ^ hash(self._identifier)
  282. def __eq__(self, other):
  283. return (
  284. isinstance(other, Resource)
  285. and self._graph == other._graph
  286. and self._identifier == other._identifier
  287. )
  288. def __ne__(self, other):
  289. return not self == other
  290. def __lt__(self, other):
  291. if isinstance(other, Resource):
  292. return self._identifier < other._identifier
  293. else:
  294. return False
  295. def __gt__(self, other):
  296. return not (self < other or self == other)
  297. def __le__(self, other):
  298. return self < other or self == other
  299. def __ge__(self, other):
  300. return not self < other
  301. def __unicode__(self):
  302. return str(self._identifier)
  303. def add(self, p, o):
  304. if isinstance(o, Resource):
  305. o = o._identifier
  306. self._graph.add((self._identifier, p, o))
  307. def remove(self, p, o=None):
  308. if isinstance(o, Resource):
  309. o = o._identifier
  310. self._graph.remove((self._identifier, p, o))
  311. def set(self, p, o):
  312. if isinstance(o, Resource):
  313. o = o._identifier
  314. self._graph.set((self._identifier, p, o))
  315. def subjects(self, predicate=None): # rev
  316. return self._resources(self._graph.subjects(predicate, self._identifier))
  317. def predicates(self, o=None):
  318. if isinstance(o, Resource):
  319. o = o._identifier
  320. return self._resources(self._graph.predicates(self._identifier, o))
  321. def objects(self, predicate=None):
  322. return self._resources(self._graph.objects(self._identifier, predicate))
  323. def subject_predicates(self):
  324. return self._resource_pairs(self._graph.subject_predicates(self._identifier))
  325. def subject_objects(self):
  326. return self._resource_pairs(self._graph.subject_objects(self._identifier))
  327. def predicate_objects(self):
  328. return self._resource_pairs(self._graph.predicate_objects(self._identifier))
  329. def value(self, p=RDF.value, o=None, default=None, any=True):
  330. if isinstance(o, Resource):
  331. o = o._identifier
  332. return self._cast(self._graph.value(self._identifier, p, o, default, any))
  333. def items(self):
  334. return self._resources(self._graph.items(self._identifier))
  335. def transitive_objects(self, predicate, remember=None):
  336. return self._resources(
  337. self._graph.transitive_objects(self._identifier, predicate, remember)
  338. )
  339. def transitive_subjects(self, predicate, remember=None):
  340. return self._resources(
  341. self._graph.transitive_subjects(predicate, self._identifier, remember)
  342. )
  343. def qname(self):
  344. return self._graph.qname(self._identifier)
  345. def _resource_pairs(self, pairs):
  346. for s1, s2 in pairs:
  347. yield self._cast(s1), self._cast(s2)
  348. def _resource_triples(self, triples):
  349. for s, p, o in triples:
  350. yield self._cast(s), self._cast(p), self._cast(o)
  351. def _resources(self, nodes):
  352. for node in nodes:
  353. yield self._cast(node)
  354. def _cast(self, node):
  355. if isinstance(node, (BNode, URIRef)):
  356. return self._new(node)
  357. else:
  358. return node
  359. def __iter__(self):
  360. return self._resource_triples(
  361. self._graph.triples((self.identifier, None, None))
  362. )
  363. def __getitem__(self, item):
  364. if isinstance(item, slice):
  365. if item.step:
  366. raise TypeError(
  367. "Resources fix the subject for slicing, and can only be sliced by predicate/object. "
  368. )
  369. p, o = item.start, item.stop
  370. if isinstance(p, Resource):
  371. p = p._identifier
  372. if isinstance(o, Resource):
  373. o = o._identifier
  374. if p is None and o is None:
  375. return self.predicate_objects()
  376. elif p is None:
  377. return self.predicates(o)
  378. elif o is None:
  379. return self.objects(p)
  380. else:
  381. return (self.identifier, p, o) in self._graph
  382. elif isinstance(item, (Node, Path)):
  383. return self.objects(item)
  384. else:
  385. raise TypeError(
  386. "You can only index a resource by a single rdflib term, a slice of rdflib terms, not %s (%s)"
  387. % (item, type(item))
  388. )
  389. def __setitem__(self, item, value):
  390. self.set(item, value)
  391. def _new(self, subject):
  392. return type(self)(self._graph, subject)
  393. def __str__(self):
  394. return "Resource(%s)" % self._identifier
  395. def __repr__(self):
  396. return "Resource(%s,%s)" % (self._graph, self._identifier)