Skip to content

Commit 9c064ac

Browse files
author
Nicholas Car
authored
Merge pull request #997 from kushagr08/master
Fix #280: Added container.py for adding container class and seq, alt and bag as it's subclasses
2 parents 9103720 + bc213dc commit 9c064ac

File tree

7 files changed

+380
-12
lines changed

7 files changed

+380
-12
lines changed

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def find_version(filename):
154154
# The name of an image file (within the static path) to use as favicon of the
155155
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
156156
# pixels large.
157-
html_favicon = "_static/logo-rdflib.ico"
157+
html_favicon = "_static/RDFlib.ico"
158158

159159
# Add any paths that contain custom static files (such as style sheets) here,
160160
# relative to this directory. They are copied after the builtin static files,

docs/intro_to_creating_rdf.rst

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,24 +128,46 @@ When removing, it is possible to leave parts of the triple unspecified (i.e. pas
128128

129129
.. code-block:: python
130130
131-
g.remove((bob, None, None)) # remove all triples about bob
131+
g.remove((bob, None, None)) # remove all triples about bob
132132
133133
134134
An example
135135
----------
136136

137137
LiveJournal produces FOAF data for their users, but they seem to use
138-
``foaf:member_name`` for a person's full name. To align with data from
139-
other sources, it would be nice to have ``foaf:name`` act as a synonym
140-
for ``foaf:member_name`` (a poor man's one-way
141-
``owl:equivalentProperty``):
138+
``foaf:member_name`` for a person's full name but ``foaf:member_name``
139+
isn't in FOAF's namespace and perhaps they should have used ``foaf:name``
140+
141+
To retrieve some LiveJournal data, add a ``foaf:name`` for every
142+
``foaf:member_name`` and then remove the ``foaf:member_name`` values to
143+
ensure the data actually aligns with other FOAF data, we could do this:
142144

143145
.. code-block:: python
144146
147+
from rdflib import Graph
145148
from rdflib.namespace import FOAF
146-
g.parse("http://danbri.livejournal.com/data/foaf")
149+
150+
g = Graph()
151+
# get the data
152+
g.parse("http://danbri.livejournal.com/data/foaf")
153+
154+
# for every foaf:member_name, add foaf:name and remove foaf:member_name
147155
for s, p, o in g.triples((None, FOAF['member_name'], None)):
148156
g.add((s, FOAF['name'], o))
157+
g.remove((s, FOAF['member_name'], o))
158+
159+
.. note:: Since rdflib 5.0.0, using ``foaf:member_name`` is somewhat prevented in RDFlib since FOAF is declared
160+
as a :meth:`~rdflib.namespace.ClosedNamespace` class instance that has a closed set of members and
161+
``foaf:member_name`` isn't one of them! If LiveJournal used RDFlib 5.0.0, an error would have been raised for
162+
``foaf:member_name`` when the triple was created.
163+
164+
165+
Creating Containers & Collections
166+
---------------------------------
167+
There are two convenience classes for RDF Containers & Collections which you can use instead of declaring each
168+
triple of a Containers or a Collections individually:
169+
170+
* :meth:`~rdflib.container.Container` (also ``Bag``, ``Seq`` & ``Alt``) and
171+
* :meth:`~rdflib.collection.Collection`
149172

150-
Note that since rdflib 5.0.0, using ``foaf:member_name`` is somewhat prevented in rdflib since FOAF is declared as a :meth:`~rdflib.namespace.ClosedNamespace`
151-
class instance that has a closed set of members and ``foaf:member_name`` isnt one of them!
173+
See their documentation for how.

docs/rdf_terms.rst

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ matching nodes by term-patterns probably will only be terms and not nodes.
2020
BNodes
2121
======
2222

23-
In RDF, a blank node (also called BNode) is a node in an RDF graph representing a resource for which a URI or literal is not given. The resource represented by a blank node is also called an anonymous resource. By RDF standard a blank node can only be used as subject or object in an RDF triple, although in some syntaxes like Notation 3 [1] it is acceptable to use a blank node as a predicate. If a blank node has a node ID (not all blank nodes are labelled in all RDF serializations), it is limited in scope to a serialization of a particular RDF graph, i.e. the node p1 in the subsequent example does not represent the same node as a node named p1 in any other graph --`wikipedia`__
23+
In RDF, a blank node (also called BNode) is a node in an RDF graph representing a resource for which a URI or literal is not given. The resource represented by a blank node is also called an anonymous resource. By RDF standard a blank node can only be used as subject or object in an RDF triple, although in some syntaxes like Notation 3 [1] it is acceptable to use a blank node as a predicate. If a blank node has a node ID (not all blank nodes are labelled in all RDF serializations), it is limited in scope to a serialization of a particular RDF graph, i.e. the node p1 in the subsequent example does not represent the same node as a node named p1 in any other graph --`wikipedia`__
2424

2525

2626
.. __: http://en.wikipedia.org/wiki/Blank_node
@@ -40,7 +40,7 @@ BNodes
4040
URIRefs
4141
=======
4242

43-
A URI reference within an RDF graph is a Unicode string that does not contain any control characters ( #x00 - #x1F, #x7F-#x9F) and would produce a valid URI character sequence representing an absolute URI with optional fragment identifier -- `W3 RDF Concepts`__
43+
A URI reference within an RDF graph is a Unicode string that does not contain any control characters ( #x00 - #x1F, #x7F-#x9F) and would produce a valid URI character sequence representing an absolute URI with optional fragment identifier -- `W3 RDF Concepts`__
4444

4545
.. __: http://www.w3.org/TR/rdf-concepts/#section-Graph-URIref
4646

@@ -159,4 +159,3 @@ All this happens automatically when creating ``Literal`` objects by passing Pyth
159159

160160
You can add custom data-types with :func:`rdflib.term.bind`, see also :mod:`examples.custom_datatype`
161161

162-

rdflib/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,5 @@
196196
assert query
197197

198198
from rdflib import util
199+
200+
from .container import *

rdflib/container.py

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
from rdflib.namespace import RDF
2+
from rdflib.term import BNode
3+
from rdflib import URIRef
4+
from random import randint
5+
6+
__all__ = ["Container", "Bag", "Seq", "Alt", "NoElementException"]
7+
8+
9+
class Container(object):
10+
"""A class for constructing RDF containers, as per https://www.w3.org/TR/rdf11-mt/#rdf-containers
11+
12+
Basic usage, creating a ``Bag`` and adding to it::
13+
14+
>>> from rdflib import Graph, BNode, Literal, Bag
15+
>>> g = Graph()
16+
>>> b = Bag(g, BNode(), [Literal("One"), Literal("Two"), Literal("Three")])
17+
>>> print(g.serialize(format="turtle").decode())
18+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
19+
<BLANKLINE>
20+
[] a rdf:Bag ;
21+
rdf:_1 "One" ;
22+
rdf:_2 "Two" ;
23+
rdf:_3 "Three" .
24+
<BLANKLINE>
25+
<BLANKLINE>
26+
27+
>>> # print out an item using an index reference
28+
>>> print(b[2])
29+
Two
30+
31+
>>> # add a new item
32+
>>> b.append(Literal("Hello"))
33+
>>> print(g.serialize(format="turtle").decode())
34+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
35+
<BLANKLINE>
36+
[] a rdf:Bag ;
37+
rdf:_1 "One" ;
38+
rdf:_2 "Two" ;
39+
rdf:_3 "Three" ;
40+
rdf:_4 "Hello" .
41+
<BLANKLINE>
42+
<BLANKLINE>
43+
44+
"""
45+
46+
def __init__(self, graph, uri, seq=[], rtype="Bag"):
47+
"""Creates a Container
48+
49+
:param graph: a Graph instance
50+
:param uri: URI or Blank Node of the Container
51+
:param seq: the elements of the Container
52+
:param rtype: the type of Container, one of "Bag", "Seq" or "Alt"
53+
"""
54+
55+
self.graph = graph
56+
self.uri = uri or BNode()
57+
self._len = 0
58+
self._rtype = rtype # rdf:Bag or rdf:Seq or rdf:Alt
59+
60+
self.append_multiple(seq)
61+
62+
# adding triple corresponding to container type
63+
self.graph.add((self.uri, RDF.type, RDF[self._rtype]))
64+
65+
def n3(self):
66+
67+
items = []
68+
for i in range(len(self)):
69+
70+
v = self[i + 1]
71+
items.append(v)
72+
73+
return "( %s )" % " ".join([a.n3() for a in items])
74+
75+
def _get_container(self):
76+
"""Returns the URI of the container"""
77+
78+
return self.uri
79+
80+
def __len__(self):
81+
"""Number of items in container"""
82+
83+
return self._len
84+
85+
def type_of_conatiner(self):
86+
return self._rtype
87+
88+
def index(self, item):
89+
"""Returns the 1-based numerical index of the item in the container"""
90+
91+
pred = self.graph.predicates(self.uri, item)
92+
if not pred:
93+
raise ValueError("%s is not in %s" % (item, "container"))
94+
LI_INDEX = URIRef(str(RDF) + "_")
95+
96+
i = None
97+
for p in pred:
98+
i = int(p.replace(LI_INDEX, ""))
99+
return i
100+
101+
def __getitem__(self, key):
102+
"""Returns item of the container at index key"""
103+
104+
c = self._get_container()
105+
106+
assert isinstance(key, int)
107+
elem_uri = str(RDF) + "_" + str(key)
108+
if key <= 0 or key > len(self):
109+
raise KeyError(key)
110+
v = self.graph.value(c, URIRef(elem_uri))
111+
if v:
112+
return v
113+
else:
114+
raise KeyError(key)
115+
116+
def __setitem__(self, key, value):
117+
"""Sets the item at index key or predicate rdf:_key of the container to value"""
118+
119+
assert isinstance(key, int)
120+
121+
c = self._get_container()
122+
elem_uri = str(RDF) + "_" + str(key)
123+
if key <= 0 or key > len(self):
124+
raise KeyError(key)
125+
126+
self.graph.set((c, URIRef(elem_uri), value))
127+
128+
def __delitem__(self, key):
129+
"""Removing the item with index key or predicate rdf:_key"""
130+
131+
assert isinstance(key, int)
132+
if key <= 0 or key > len(self):
133+
raise KeyError(key)
134+
135+
graph = self.graph
136+
container = self.uri
137+
elem_uri = str(RDF) + "_" + str(key)
138+
graph.remove((container, URIRef(elem_uri), None))
139+
for j in range(key + 1, len(self) + 1):
140+
elem_uri = str(RDF) + "_" + str(j)
141+
v = graph.value(container, URIRef(elem_uri))
142+
graph.remove((container, URIRef(elem_uri), v))
143+
elem_uri = str(RDF) + "_" + str(j - 1)
144+
graph.add((container, URIRef(elem_uri), v))
145+
146+
self._len -= 1
147+
148+
def items(self):
149+
"""Returns a list of all items in the container"""
150+
151+
l_ = []
152+
container = self.uri
153+
i = 1
154+
while True:
155+
elem_uri = str(RDF) + "_" + str(i)
156+
157+
if (container, URIRef(elem_uri), None) in self.graph:
158+
i += 1
159+
l_.append(self.graph.value(container, URIRef(elem_uri)))
160+
else:
161+
break
162+
return l_
163+
164+
def end(self): #
165+
166+
# find end index (1-based) of container
167+
168+
container = self.uri
169+
i = 1
170+
while True:
171+
elem_uri = str(RDF) + "_" + str(i)
172+
173+
if (container, URIRef(elem_uri), None) in self.graph:
174+
i += 1
175+
else:
176+
return i - 1
177+
178+
def append(self, item):
179+
"""Adding item to the end of the container"""
180+
181+
end = self.end()
182+
elem_uri = str(RDF) + "_" + str(end + 1)
183+
container = self.uri
184+
self.graph.add((container, URIRef(elem_uri), item))
185+
self._len += 1
186+
187+
def append_multiple(self, other):
188+
"""Adding multiple elements to the container to the end which are in python list other"""
189+
190+
end = self.end() # it should return the last index
191+
192+
container = self.uri
193+
for item in other:
194+
195+
end += 1
196+
self._len += 1
197+
elem_uri = str(RDF) + "_" + str(end)
198+
self.graph.add((container, URIRef(elem_uri), item))
199+
200+
def clear(self):
201+
"""Removing all elements from the container"""
202+
203+
container = self.uri
204+
graph = self.graph
205+
i = 1
206+
while True:
207+
elem_uri = str(RDF) + "_" + str(i)
208+
if (container, URIRef(elem_uri), None) in self.graph:
209+
graph.remove((container, URIRef(elem_uri), None))
210+
i += 1
211+
else:
212+
break
213+
self._len = 0
214+
215+
216+
class Bag(Container):
217+
"""Unordered container (no preference order of elements)"""
218+
219+
def __init__(self, graph, uri, seq=[]):
220+
Container.__init__(self, graph, uri, seq, "Bag")
221+
222+
223+
class Alt(Container):
224+
def __init__(self, graph, uri, seq=[]):
225+
Container.__init__(self, graph, uri, seq, "Alt")
226+
227+
def anyone(self):
228+
if len(self) == 0:
229+
raise NoElementException()
230+
else:
231+
p = randint(1, len(self))
232+
item = self.__getitem__(p)
233+
return item
234+
235+
236+
class Seq(Container):
237+
def __init__(self, graph, uri, seq=[]):
238+
Container.__init__(self, graph, uri, seq, "Seq")
239+
240+
def add_at_position(self, pos, item):
241+
assert isinstance(pos, int)
242+
if pos <= 0 or pos > len(self) + 1:
243+
raise ValueError("Invalid Position for inserting element in rdf:Seq")
244+
245+
if pos == len(self) + 1:
246+
self.append(item)
247+
else:
248+
for j in range(len(self), pos - 1, -1):
249+
container = self._get_container()
250+
elem_uri = str(RDF) + "_" + str(j)
251+
v = self.graph.value(container, URIRef(elem_uri))
252+
self.graph.remove((container, URIRef(elem_uri), v))
253+
elem_uri = str(RDF) + "_" + str(j + 1)
254+
self.graph.add((container, URIRef(elem_uri), v))
255+
elem_uri_pos = str(RDF) + "_" + str(pos)
256+
self.graph.add((container, URIRef(elem_uri_pos), item))
257+
self._len += 1
258+
259+
260+
class NoElementException(Exception):
261+
def __init__(self, message="rdf:Alt Container is empty"):
262+
self.message = message
263+
264+
def __str__(self):
265+
return self.message

requirements.dev.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
sphinx
2+
sphinxcontrib-apidoc
3+
black

0 commit comments

Comments
 (0)