The Most Secure Graph Database Available
- Access control: It is possible to instruct AllegroGraph to prevent an user from accessing triples with certain attributes.
- Sharding: Attributes can be used to ensure that related triples are always placed in the same shard when AllegroGraph acts as a distributed triple store.
Managing attribute definitions
from franz.openrdf.connect import ag_connect
conn = ag_connect('python-tutorial', create=True, clear=True)
AttributeDefinition
objects. Each definition has a name, which must be unique, and a few optional properties (that can also be passed as constructor arguments):
allowed_values
: a list of strings. If this property is set then only the values from this list can be used for the defined attribute.ordered
: a boolean. If true then attribute value comparisons will use the ordering defined byallowed_values
. The default is false.minimum_number
,maximum_number
: integers that can be used to constrain the cardinality of an attribute. By default there are no limits.
setAttributeDefinition()
method of the connection object.from franz.openrdf.repository.attributes import AttributeDefinition
# A simple attribute with no constraints governing the set
# of legal values or the number of values that can be
# associated with a triple.
tag = AttributeDefinition(name='tag')
# An attribute with a limited set of legal values.
# Every bit of data can come from multiple sources.
# We encode this information in triple attributes,
# since it refers to the tripe as a whole. Another
# way of achieving this would be to use triple ids
# or RDF reification.
source = AttributeDefinition(
name='source',
allowed_values=['sonar', 'radar', 'esm', 'visual'])
# Security level - notice that the values are ordered
# and each triple *must* have exactly one value for
# this attribute. We will use this to prevent some
# users from accessing classified data.
level = AttributeDefinition(
name='level',
allowed_values=['low', 'medium', 'high'],
ordered=True,
minimum_number=1,
maximum_number=1)
# An attribute like this could be used for sharding.
# That would ensure that data related to a particular
# contact is never partitioned across multiple shards.
# Note that this attribute is required, since without
# it an attribute-sharded triple store would not know
# what to do with a triple.
contact = AttributeDefinition(
name='contact',
minimum_number=1,
maximum_number=1)
# So far we have created definition objects, but we
# have not yet sent those definitions to the server.
# Let's do this now.
conn.setAttributeDefinition(tag)
conn.setAttributeDefinition(source)
conn.setAttributeDefinition(level)
conn.setAttributeDefinition(contact)
# This line is not strictly necessary, because our
# connection operates in autocommit mode.
# However, it is important to note that attribute
# definitions have to be committed before they can
# be used by other sessions.
conn.commit()
getAttributeDefinitions()
method:for attr in conn.getAttributeDefinitions():
print('Name: {0}'.format(attr.name))
if attr.allowed_values:
print('Allowed values: {0}'.format(
', '.join(attr.allowed_values)))
print('Ordered: {0}'.format(
'Y' if attr.ordered else 'N'))
print('Min count: {0}'.format(attr.minimum_number))
print('Max count: {0}'.format(attr.maximum_number))
print()
Name: tag
Min count: 0
Max count: 1152921504606846975
Name: source
Allowed values: sonar, radar, esm, visual
Min count: 0
Max count: 1152921504606846975
Ordered: N
Name: level
Allowed values: low, medium, high
Ordered: Y
Min count: 1
Max count: 1
Name: contact
Min count: 1
Max count: 1
deleteAttributeDefinition()
:conn.deleteAttributeDefinition('tag')
defs = conn.getAttributeDefinitions()
print(', '.join(sorted(a.name for a in defs)))
contact, level, source
Adding triples with attributes
addTriple()
is used it is possible to pass attributes in a keyword parameter, as shown below:ex = conn.namespace('ex://')
conn.addTriple(ex.S1, ex.cls, ex.Udaloy, attributes={
'source': 'sonar',
'level': 'low',
'contact': 'S1'
})
addStatement()
method works in similar way. Note that it is not possible to include attributes in the Statement
object itself.from franz.openrdf.model import Statement
s = Statement(ex.M1, ex.cls, ex.Zumwalt)
conn.addStatement(s, attributes={
'source': ['sonar', 'esm'],
'level': 'medium',
'contact': 'M1'
})
addTriples()
one can add a fifth element to each tuple to represent attributes. Let us illustrate this by adding an aircraft to our dataset.conn.addTriples(
[(ex.R1, ex.cls, ex['Ka-27'], None,
{'source': 'radar',
'level': 'low',
'contact': 'R1'}),
(ex.R1, ex.altitude, 200, None,
{'source': 'radar',
'level': 'medium',
'contact': 'R1'})])
attributes
keyword parameter. This provides default values, but is completely ignored for all tuples that already contain attributes (the dictionaries are not merged). In the example below we add a triple representing an aircraft carrier and a few more triples that specify its position. Notice that the first triple has a lower security level and multiple sources. The common ‘contact’ attribute could be used to ensure that all this data will remain on a single shard.conn.addTriples(
[(ex.M2, ex.cls, ex.Kuznetsov, None, {
'source': ['sonar', 'radar', 'visual'],
'contact': 'M2',
'level': 'low',
}),
(ex.M2, ex.position, ex.pos343),
(ex.pos343, ex.x, 430.0),
(ex.pos343, ex.y, 240.0)],
attributes={
'contact': 'M2',
'source': 'radar',
'level': 'medium'
})
addFile()
and addData()
(illustrated below):from franz.openrdf.rio.rdfformat import RDFFormat
conn.addData('''
<ex://S2> <ex://cls> <ex://Alpha> \
{"source": "sonar", "level": "medium", "contact": "S2"} .
<ex://S2> <ex://depth> "300" \
{"source": "sonar", "level": "medium", "contact": "S2"} .
<ex://S2> <ex://speed_kn> "15.0" \
{"source": "sonar", "level": "medium", "contact": "S2"} .
''', rdf_format=RDFFormat.NQX)
from franz.openrdf.rio.rdfformat import RDFFormat
conn.addData('''
<ex://V1> <ex://cls> <ex://Walrus> ;
<ex://altitude> 100 ;
<ex://speed_kn> 12.0e+8 .
<ex://V2> <ex://cls> <ex://Walrus> ;
<ex://altitude> 200 ;
<ex://speed_kn> 12.0e+8 .
<ex://V3> <ex://cls> <ex://Walrus> ;
<ex://altitude> 300;
<ex://speed_kn> 12.0e+8 .
<ex://V4> <ex://cls> <ex://Walrus> ;
<ex://altitude> 400 ;
<ex://speed_kn> 12.0e+8 .
<ex://V5> <ex://cls> <ex://Walrus> ;
<ex://altitude> 500 ;
<ex://speed_kn> 12.0e+8 .
<ex://V6> <ex://cls> <ex://Walrus> ;
<ex://altitude> 600 ;
<ex://speed_kn> 12.0e+8 .
''', attributes={
'source': 'visual',
'level': 'high',
'contact': 'a therapist'})
Retrieving attribute values
import json
r = conn.executeTupleQuery('''
PREFIX attr: <http://franz.com/ns/allegrograph/6.2.0/>
SELECT ?s ?p ?o ?a {
?s ?p ?o .
?a attr:attributes (?s ?p ?o) .
} ORDER BY ?s ?p ?o''')
with r:
for row in r:
print(row['s'], row['p'], row['o'])
print(json.dumps(json.loads(row['a'].label),
sort_keys=True,
indent=4))
<ex://M1> <ex://cls> <ex://Zumwalt>
{
"contact": "M1",
"level": "medium",
"source": [
"esm",
"sonar"
]
}
<ex://M2> <ex://cls> <ex://Kuznetsov>
{
"contact": "M2",
"level": "low",
"source": [
"visual",
"radar",
"sonar"
]
}
<ex://M2> <ex://position> <ex://pos343>
{
"contact": "M2",
"level": "medium",
"source": "radar"
}
<ex://R1> <ex://altitude> "200"^^...
{
"contact": "R1",
"level": "medium",
"source": "radar"
}
<ex://R1> <ex://cls> <ex://Ka-27>
{
"contact": "R1",
"level": "low",
"source": "radar"
}
<ex://S1> <ex://cls> <ex://Udaloy>
{
"contact": "S1",
"level": "low",
"source": "sonar"
}
<ex://S2> <ex://cls> <ex://Alpha>
{
"contact": "S2",
"level": "medium",
"source": "sonar"
}
<ex://S2> <ex://depth> "300"
{
"contact": "S2",
"level": "medium",
"source": "sonar"
}
<ex://S2> <ex://speed_kn> "15.0"
{
"contact": "S2",
"level": "medium",
"source": "sonar"
}
<ex://V1> <ex://altitude> "100"^^...
{
"contact": "a therapist",
"level": "high",
"source": "visual"
}
<ex://V1> <ex://cls> <ex://Walrus>
{
"contact": "a therapist",
"level": "high",
"source": "visual"
}
<ex://V1> <ex://speed_kn> "1.2E9"^^...
{
"contact": "a therapist",
"level": "high",
"source": "visual"
}
...
<ex://pos343> <ex://x> "4.3E2"^^...
{
"contact": "M2",
"level": "medium",
"source": "radar"
}
<ex://pos343> <ex://y> "2.4E2"^^...
{
"contact": "M2",
"level": "medium",
"source": "radar"
}
Attribute filters
setAttributeFilter()
. The values passed to this method must be either strings (the syntax is described in the documentation of static attribute filters) or filter objects.
- a string or a list of strings: represents a constant set of values.
- TripleAttribute.name: represents the value of the name attribute associated with the currently inspected triple.
- UserAttribute.name: represents the value of the name attribute associated with current query. User attributes will be discussed in more detail later.
franz.openrdf.repository.attributes
package:Syntax | Meaning |
---|---|
Empty(x) |
True if the specified attribute set is empty. |
Overlap(x, y) |
True if there is at least one matching value between the two attribute sets. |
Subset(x, y) , x << y |
True if every element of x can be found in y |
Superset(x, y) , x >> y |
True if every element of y can be found in x |
Equal(x, y) , x == y |
True if x and y have exactly the same contents. |
Lt(x, y) , x < y |
True if both sets are singletons, at least one of the sets refers to a triple or user attribute, the attribute is ordered and the value of the single element of x occurs before the single value of y in the lowed_values list of the attribute. |
Le(x, y) , x <= y |
True if y < x is false. |
Eq(x, y) |
True if both x < y and y < x are false. Note that using the == Python operator translates toEqauls, not Eq. |
Ge(x, y) , x >= y |
True if x < y is false. |
Gt(x, y) , x > y |
True if y < x. |
UserAttribute
or TripleAttribute
reference – if both arguments are strings or lists of strings the default Python semantics for each operator are used. The prefix syntax always produces filters.Syntax | Meaning |
---|---|
Not(x) , ~x |
Negates the meaning of the filter. |
And(x, y, ...) , x & y |
True if all subfilters are true. |
Or(x, y, ...) , x | y |
True if at least one subfilter is true. |
Using filters and user attributes
from franz.openrdf.repository.attributes import *
conn.setAttributeFilter(TripleAttribute.source >> 'sonar')
conn.executeTupleQuery(
'select ?class { ?s <ex://cls> ?class } order by ?class',
output=True)
------------------
| class |
==================
| ex://Alpha |
| ex://Kuznetsov |
| ex://Udaloy |
| ex://Zumwalt |
------------------
conn.setUserAttributes({'level': 'low'})
conn.setAttributeFilter(
TripleAttribute.level <= UserAttribute.level)
conn.executeTupleQuery(
'select ?class { ?s <ex://cls> ?class } order by ?class',
output=True)
------------------
| class |
==================
| ex://Ka-27 |
| ex://Kuznetsov |
| ex://Udaloy |
------------------
clearAttributeFilter()
method.conn.clearAttributeFilter()
temporaryUserAttributes()
method, which returns a context manager. The example below illustrates its use. It also shows how to use getUserAttributes()
to inspect user attributes.with conn.temporaryUserAttributes({'level': 'high'}):
print('User attributes inside the block:')
for k, v in conn.getUserAttributes().items():
print('{0}: {1}'.format(k, v))
print()
print('User attributes outside the block:')
for k, v in conn.getUserAttributes().items():
print('{0}: {1}'.format(k, v))
User attributes inside the block:
level: high
User attributes outside the block:
level: low »