├── MANIFEST.in ├── dist └── sparta-0.82.tar.gz ├── CHANGELOG ├── examples ├── rss_schema.xml ├── rss.py ├── example.py └── example-out.txt ├── setup.py ├── README.rst └── sparta.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include CHANGELOG 3 | include examples/* 4 | -------------------------------------------------------------------------------- /dist/sparta-0.82.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnot/sparta/HEAD/dist/sparta-0.82.tar.gz -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 2 | * Sparta 0.9 (not released) 3 | - update for rdflib 2.4 4 | - fix _id typo 5 | -------------------------------------------------------------------------------- /examples/rss_schema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | setup(name='sparta', 6 | version = '0.9-pre', 7 | py_modules = ['sparta'], 8 | author = 'Mark Nottingham', 9 | author_email = 'mnot@mnot.net', 10 | url = 'http://gitub.com/mnot/sparta', 11 | description = 'Simple API for RDF', 12 | long_description = """Sparta is an Python API for RDF that is designed 13 | to help easily learn and navigate the Semantic Web programmatically. 14 | Unlike other RDF interfaces, which are generally triple-based, Sparta 15 | binds RDF nodes to Python objects and RDF arcs to attributes of those 16 | Python objects.""", 17 | license = 'MIT', 18 | classifiers = [ 19 | 'License :: OSI Approved :: MIT License', 20 | 'Operating System :: OS Independent', 21 | 'Programming Language :: Python', 22 | 'Topic :: Software Development :: Libraries :: Python Modules', 23 | ], 24 | ) 25 | -------------------------------------------------------------------------------- /examples/rss.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from rdflib.Graph import Graph 4 | from rdflib.URIRef import URIRef as URI 5 | from sparta import ThingFactory 6 | import textwrap 7 | indent = textwrap.TextWrapper(initial_indent=" ", subsequent_indent=" ").fill 8 | 9 | def main(rss_url, blog_uri): 10 | store, schema_store = Graph(), Graph() 11 | store.parse(rss_url) 12 | store.bind('rss', 'http://purl.org/rss/1.0/') 13 | schema_store.parse('file:rss_schema.xml') 14 | Thing = ThingFactory(store, schema_store) 15 | 16 | blog = Thing(URI(blog_uri)) 17 | for item in blog.rss_items: 18 | print "*", item.rss_title 19 | print indent(item.rss_description) 20 | 21 | 22 | if __name__ == '__main__': 23 | import sys 24 | try: 25 | main(sys.argv[1], sys.argv[2]) 26 | except IndexError: 27 | sys.stderr.write("Usage: %s [RSS feed URL] [Blog URI]\n" % sys.argv[0]) 28 | sys.exit(1) 29 | -------------------------------------------------------------------------------- /examples/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from sparta import ThingFactory 4 | from rdflib.Graph import Graph 5 | 6 | store = Graph() 7 | store.bind("contact", "http://www.example.com/contact#") 8 | store.bind("person", "http://www.example.com/person#") 9 | store.bind("xs", "http://www.w3.org/2001/XMLSchema#") 10 | store.bind("rdfs", "http://www.w3.org/2000/01/rdf-schema#") 11 | store.bind("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#") 12 | store.bind("owl", "http://www.w3.org/2002/07/owl#") 13 | 14 | Thing = ThingFactory(store) 15 | Thing.addAlias("special", "http://www.example.com/my-unmappable-stuff#special-thing") 16 | 17 | ### these should be loaded externally... 18 | Thing("person_employment_history", 19 | rdfs_range=[Thing("rdf_List")], 20 | rdf_type=[Thing("owl_FunctionalProperty")] 21 | ) 22 | Thing("person_age", rdfs_range=[Thing("xs_int")]) 23 | Thing("person_picture", rdfs_range=[Thing("xs_base64Binary")]) 24 | Thing("contact_phone", rdf_type=[Thing("owl_FunctionalProperty")]) 25 | Thing("person_wife", rdf_type=[Thing("owl_FunctionalProperty")]) 26 | Thing("person_name", rdf_type=[Thing("owl_FunctionalProperty")]) 27 | Thing("contact_www", rdf_type=[Thing("owl_FunctionalProperty")]) 28 | Thing("person_age", rdf_type=[Thing("owl_FunctionalProperty")]) 29 | 30 | bob = Thing("person_bob") 31 | Person = Thing("person_Person") 32 | bob.rdf_type.add(Person) 33 | bob.contact_phone = "555-1212" 34 | bob.person_name = "Bob" 35 | bob.contact_address.add(Thing(None, 36 | contact_street = ["314159 There Street"], 37 | contact_city = ["EveryVille"], 38 | contact_state = ["NA"], 39 | contact_zip = ["12345"], 40 | )) 41 | 42 | bob.person_childname.add("joe") 43 | bob.person_childname.add("jim") 44 | bob.person_childname.add("bob") 45 | if "jim" in bob.person_childname: 46 | print "Yes, Bob has a child named jim." 47 | if "george" not in bob.person_childname: 48 | print "But he doesn't have one named george." 49 | bob.person_childname.remove("jim") 50 | if "jim" not in bob.person_childname: 51 | print "And in fact, jim isn't any more." 52 | bob.person_childname.discard("nonexistant") 53 | 54 | bob.person_employment_history = ["7-11", "Wal-Mart", "Goldman Sachs"] 55 | 56 | bob.special.add("testing...") 57 | print "This should be 1:", len(bob.special) 58 | 59 | mary = Thing("person_mary", 60 | person_name="Mary", 61 | person_hair=["blue"], 62 | person_age=23 63 | ) 64 | 65 | bob.person_wife = mary 66 | 67 | mary.person_childname = bob.person_childname 68 | print "Mary has", len(mary.person_childname), "kids." 69 | 70 | mary.contact_phone = "123-4567" 71 | bob.person_wife.contact_www = "http://www.example.org/~mary" 72 | 73 | print "Bob's phone is", bob.contact_phone 74 | # print "Bob's zip code is", bob.contact_address.contact_zip 75 | print "Bob's wife is", bob.person_wife.person_name 76 | print "Mary's phone is", mary.contact_phone 77 | print "Bob's wife's phone is", bob.person_wife.contact_phone 78 | print "Mary's web site is", mary.contact_www 79 | print "their", len(bob.person_childname), "kids' names are:" 80 | for kid in bob.person_childname: print " ", kid 81 | print "Bob has worked at:", bob.person_employment_history 82 | 83 | print "Mary's age is", mary.person_age 84 | bob.person_age = bob.person_wife.person_age + 4 85 | print "Bob's age is", bob.person_age, type(bob.person_age) 86 | 87 | f = open('/dev/random') 88 | bob.person_picture.add(f.read(25)) 89 | f.close() 90 | 91 | print "Bob's properties:", ", ".join(map(str, bob.properties())) 92 | print "Bob has a wife?", hasattr(bob, "person_wife") 93 | print "Her name?", getattr(bob, "person_wife").person_name 94 | 95 | print 96 | print store.serialize(format="xml") 97 | -------------------------------------------------------------------------------- /examples/example-out.txt: -------------------------------------------------------------------------------- 1 | Yes, Bob has a child named jim. 2 | But he doesn't have one named george. 3 | And in fact, jim isn't any more. 4 | Mary has 2 kids. 5 | Bob's phone is 555-1212 6 | Bob's wife is Mary 7 | Mary's phone is 123-4567 8 | Bob's wife's phone is 123-4567 9 | Mary's web site is http://www.example.org/~mary 10 | their 2 kids' names are: 11 | bob 12 | joe 13 | Bob has worked at: [u'7-11', u'Wal-Mart', u'Goldman Sachs'] 14 | Mary's age is 23 15 | Bob's age is 27 16 | Bob's properties: person_name, rdf_type, person_age, person_picture, contact_phone, contact_address, person_wife, person_employment_history, person_childname, person_childname 17 | Bob has a wife? True 18 | Her name? Mary 19 | 20 | 21 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Wal-Mart 42 | 43 | 44 | 45 | 46 | 47 | Bob 48 | 49 | 27 50 | 9YhWIicFtjEh4Uc9iFmEzhFT5xH9vkA6xg== 51 | 555-1212 52 | 53 | 54 | 55 | bob 56 | joe 57 | 58 | 59 | Mary 60 | blue 61 | 23 62 | 123-4567 63 | http://www.example.org/~mary 64 | bob 65 | joe 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Goldman Sachs 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 7-11 84 | 85 | 86 | EveryVille 87 | 12345 88 | 314159 There Street 89 | NA 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | Sparta 3 | ====== 4 | 5 | A Simple API for RDF by Mark Nottingham, 6 | 7 | Sparts is a simple, resource-centric API for RDF graphs, built on top of 8 | rdflib_. 9 | 10 | Installation 11 | ------------ 12 | 13 | Sparta requires rdflib_ 2.4+. 14 | 15 | To install:: 16 | 17 | python setup.py install 18 | 19 | For installation help:: 20 | 21 | python setup.py install --help 22 | 23 | In examples/:: 24 | 25 | example.py: Example of use 26 | example-out.txt: ouput of example script 27 | rss.py: example RSS 1.0 feed parser 28 | rss_schema.xml: partial RDF Schema/OWL for RSS 1.0 example 29 | 30 | For more information, see sparta.py and . 31 | 32 | Getting Started 33 | --------------- 34 | 35 | The easiest way to get started is to play with it; take a look at the example 36 | files above. You can also take a look through the preliminary documentation below. 37 | 38 | Sparta is a wrapper around an rdflib_ Graph. To use it, 39 | you must first instantiate one or more Graphs, make any necessary prefix mappings, 40 | and then instantiate a ThingFactory. 41 | 42 | Prefix bindings allow URIs to be referred to with a short name. 43 | For example, if "http://www.example.com/foo#" is mapped to the prefix "foo", 44 | then the URI "http://www.example.com/foo#bar" can be referred to in Sparta 45 | with the name "foo_bar". 46 | 47 | Prefix bindings are made by calling the normal bind(prefix, 48 | URI) method on the rdflib graph. You can also bind a complete URI to a 49 | string with the addAlias(alias, URI) method on the ThingFactory 50 | (this accommodates URIs that are awkward or impossible to map using 51 | prefixes). 52 | 53 | To be instantiated, a ThingFactory requires one argument; the 54 | Graph (or store) that is to be used. Optionally, you can also give a 55 | schema_store argument, which points to a separate store that contains the 56 | schema hints used to help Sparta map RDF into Python datatypes and objects. If 57 | this is not specified, the primary store will be used. 58 | 59 | This is a common idiom for setting up Sparta:: 60 | 61 |
    from rdflib.Graph import Graph
 62 |     store = Graph()
 63 |     store.parse([URI])
 64 |     store.bind([prefix], [URI])
 65 |     Thing = ThingFactory(store)
66 | 67 | Working with Nodes 68 | ------------------ 69 | 70 | Once you've bound any prefixes you need and set up the store, 71 | you're ready to work with RDF data. 72 | 73 | An RDF node is represented as a Python object in Sparta, whose properties 74 | correspond to RDF arcs. To start working with a node, you must instantiate it 75 | with its identity; there are three ways to do this. 76 | 77 | 1. Thing("prefix_localname") - Refers to the URI indicated using the 78 | prefix mapping, as described above. 79 | 2. Thing(URIRef('http://www.example.com/foo#bar')) - Refers to the 80 | URI specified. 81 | 3. Thing(None) - creates a bNode_ (blank, or anonymous RDF node). 82 | 83 | Accessing and Manipulating Data 84 | ------------------------------- 85 | 86 | A node's properties can be accessed and changed by name, 87 | using the prefix mapping as explained above. For example:: 88 | 89 | print foo.rdf_type 90 | 91 | will print the 'rdf_type' property of the 'foo' node. 92 | 93 | There are two ways to access a property's values, depending on what Sparta 94 | knows about it through the schema store. If it is an 95 | owl:FunctionalProperty_, or if the subject is subclassed to restrict that 96 | property with either a owl:maxCardinality_ or a owl:cardinality_ of "1", the 97 | property can be accessed as a normal, singular value; that is, it can be 98 | accessed as explained above, assigned with the '=' operator, deleted with 99 | 'del', and so forth. 100 | 101 | Otherwise, the property's value is assumed to have a cardinality greater 102 | than one, and implements a subset of the Python set() interface. For 103 | example, you can add to the set with the add method, like this:: 104 | 105 | foo.children.add("bob") 106 | 107 | test for membership with the in operator, and so forth. See the PropertySet 108 | class for the exact methods implemented. 109 | 110 | Datatyping 111 | ---------- 112 | 113 | An RDF predicate with one of the following as its 114 | rdfs:range_ (according to the schema store) will be mapped to these 115 | Python datatypes: 116 | 117 | * rdf:List - list 118 | * rdf:Seq - list 119 | * xs:string, xs:normalizedString, xs:token, xs:language - unicode 120 | * xs:boolean - bool 121 | * xs:decimal, xs:float, xs:double - float 122 | * xs:integer, xs:long, xs:unsignedLong, xs:unsignedInt - long 123 | * xs:nonPositiveInteger, xs:nonNegativeInteger, xs:positiveInteger, 124 | xs:negativeInteger, xs:int, xs:short, xs:byte, xs:unsignedShort, 125 | xs:unsignedByte - int 126 | * xs:anyURI - str 127 | * xs:base64Binary - (decoded base64) 128 | 129 | 130 | .. _rdflib: http://rdflib.net/ 131 | .. _bnode: http://www.w3.org/TR/rdf-primer/#structuredproperties 132 | .. _cardinality: http://www.w3.org/TR/owl-ref/#cardinality 133 | .. _maxCardinality: http://www.w3.org/TR/owl-ref/#maxCardinality-def 134 | .. _FunctionalProperty: http://www.w3.org/TR/owl-ref/#FunctionalProperty-def 135 | .. _range: http://www.w3.org/TR/rdf-schema/#ch_range -------------------------------------------------------------------------------- /sparta.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | sparta.py - a Simple API for RDF 5 | 6 | Sparta is a simple API for RDF that binds RDF nodes to Python 7 | objects and RDF arcs to attributes of those Python objects. As 8 | such, it can be considered a "data binding" from RDF to Python. 9 | 10 | Requires rdflib version 2.3.1+. 11 | """ 12 | 13 | __license__ = """ 14 | Copyright (c) 2001-2006 Mark Nottingham 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy 17 | of this software and associated documentation files (the "Software"), to deal 18 | in the Software without restriction, including without limitation the rights 19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | copies of the Software, and to permit persons to whom the Software is 21 | furnished to do so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in all 24 | copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | SOFTWARE. 33 | """ 34 | 35 | __version__ = "0.9-pre" 36 | 37 | import base64 38 | import rdflib # http://rdflib.net/ 39 | from rdflib.Identifier import Identifier as ID 40 | from rdflib.URIRef import URIRef as URI 41 | from rdflib.BNode import BNode 42 | from rdflib.Literal import Literal 43 | from rdflib import RDF, RDFS 44 | 45 | RDF_SEQi = "http://www.w3.org/1999/02/22-rdf-syntax-ns#_%s" 46 | MAX_CARD = URI("http://www.w3.org/2002/07/owl#maxCardinality") 47 | CARD = URI("http://www.w3.org/2002/07/owl#cardinality") 48 | RESTRICTION = URI("http://www.w3.org/2002/07/owl#Restriction") 49 | FUNC_PROP = URI("http://www.w3.org/2002/07/owl#FunctionalProperty") 50 | ON_PROP = URI("http://www.w3.org/2002/07/owl#onProperty") 51 | ONE = Literal("1") 52 | 53 | 54 | 55 | class ThingFactory: 56 | """ 57 | Fed a store, return a factory that can be used to instantiate 58 | Things into that world. 59 | """ 60 | def __init__(self, store, schema_store=None, alias_map=None): 61 | """ 62 | store - rdflib.Graph.Graph instance 63 | schema_store - rdflib.Graph.Graph instance; defaults to store 64 | """ 65 | self.store = store 66 | self.schema_store = schema_store or self.store 67 | self.alias_map = alias_map or {} 68 | 69 | def __call__(self, ident, **props): 70 | """ 71 | ident - either: 72 | a) None (creates a new BNode) 73 | b) rdflib.URIRef.URIRef instance 74 | c) str in the form prefix_localname 75 | props - dict of properties and values, to be added. If the value is a list, its 76 | contents will be added to a ResourceSet. 77 | 78 | returns Thing instance 79 | """ 80 | return Thing(self.store, self.schema_store, self.alias_map, ident, props) 81 | 82 | def addAlias(self, alias, uri): 83 | """ 84 | Add an alias for an pythonic name to a URI, which overrides the 85 | default prefix_localname syntax for properties and object names. Intended to 86 | be used for URIs which are unmappable. 87 | 88 | E.g., 89 | .addAlias("foobar", "http://example.com/my-unmappable-types#blah-type") 90 | will map the .foobar property to the provided URI. 91 | """ 92 | self.alias_map[alias] = uri 93 | 94 | class Thing: 95 | """ An RDF resource, as uniquely identified by a URI. Properties 96 | of the resource are avaiable as attributes; for example: 97 | .prefix_localname is the property in the namespace mapped 98 | to the "prefix" prefix, with the localname "localname". 99 | 100 | A "python literal datatype" is a datatype that maps to a Literal type; 101 | e.g., int, float, bool. 102 | 103 | A "python data representation" is one of: 104 | a) a python literal datatype 105 | b) a self.__class__ instance 106 | c) a list containing a and/or b 107 | """ 108 | 109 | def __init__(self, store, schema_store, alias_map, ident, props=None): 110 | """ 111 | store - rdflib.Graph.Graph 112 | schema_store - rdflib.Graph.Graph 113 | ident - either: 114 | a) None (creates a new BNode) 115 | b) rdflib.URIRef.URIRef instance 116 | c) str in the form prefix_localname 117 | props - dict of properties and values, to be added. If the value is a list, its 118 | contents will be added to a ResourceSet. 119 | """ 120 | self._store = store 121 | self._schema_store = schema_store 122 | self._alias_map = alias_map 123 | if ident is None: 124 | self._id = BNode() 125 | elif isinstance(ident, ID): 126 | self._id = ident 127 | else: 128 | self._id = self._AttrToURI(ident) 129 | if props is not None: 130 | for attr, obj in props.items(): 131 | if type(obj) is type([]): 132 | [self.__getattr__(attr).add(o) for o in obj] 133 | else: 134 | self.__setattr__(attr, obj) 135 | 136 | def __getattr__(self, attr): 137 | """ 138 | attr - either: 139 | a) str starting with _ (normal attribute access) 140 | b) str that is a URI 141 | c) str in the form prefix_localname 142 | 143 | returns a python data representation or a ResourceSet instance 144 | """ 145 | if attr[0] == '_': 146 | raise AttributeError 147 | else: 148 | if ":" in attr: 149 | pred = URI(attr) 150 | else: 151 | try: 152 | pred = self._AttrToURI(attr) 153 | except ValueError: 154 | raise AttributeError 155 | if self._isUniqueObject(pred): 156 | try: 157 | obj = self._store.triples((self._id, pred, None)).next()[2] 158 | except StopIteration: 159 | raise AttributeError 160 | return self._rdfToPython(pred, obj) 161 | else: 162 | return ResourceSet(self, pred) 163 | 164 | def __setattr__(self, attr, obj): 165 | """ 166 | attr - either: 167 | a) str starting with _ (normal attribute setting) 168 | b) str that is a URI 169 | c) str in the form prefix_localname 170 | obj - a python data representation or a ResourceSet instance 171 | """ 172 | if attr[0] == '_': 173 | self.__dict__[attr] = obj 174 | else: 175 | if ":" in attr: 176 | pred = URI(attr) 177 | else: 178 | try: 179 | pred = self._AttrToURI(attr) 180 | except ValueError: 181 | raise AttributeError 182 | if self._isUniqueObject(pred): 183 | self._store.remove((self._id, pred, None)) 184 | self._store.add((self._id, pred, self._pythonToRdf(pred, obj))) 185 | elif isinstance(obj, ResourceSet) or type(obj) is type(set()): 186 | ResourceSet(self, pred, obj.copy()) 187 | else: 188 | raise TypeError 189 | 190 | def __delattr__(self, attr): 191 | """ 192 | attr - either: 193 | a) str starting with _ (normal attribute deletion) 194 | b) str that is a URI 195 | c) str in the form prefix_localname 196 | """ 197 | if attr[0] == '_': 198 | del self.__dict__[attr] 199 | else: 200 | if ":" in attr: 201 | self._store.remove((self._id, URI(attr), None)) 202 | else: 203 | try: 204 | self._store.remove((self._id, self._AttrToURI(attr), None)) 205 | except ValueError: 206 | raise AttributeError 207 | 208 | def _rdfToPython(self, pred, obj): 209 | """ 210 | Given a RDF predicate and object, return the equivalent Python object. 211 | 212 | pred - rdflib.URIRef.URIRef instance 213 | obj - rdflib.Identifier.Identifier instance 214 | 215 | returns a python data representation 216 | """ 217 | obj_types = self._getObjectTypes(pred, obj) 218 | if isinstance(obj, Literal): # typed literals 219 | return self._literalToPython(obj, obj_types) 220 | elif RDF.List in obj_types: 221 | return self._listToPython(obj) 222 | elif RDF.Seq in obj_types: 223 | l, i = [], 1 224 | while True: 225 | counter = URI(RDF_SEQi % i) 226 | try: 227 | item = self._store.triples((obj, counter, None)).next()[2] 228 | except StopIteration: 229 | return l 230 | l.append(self._rdfToPython(counter, item)) 231 | i += 1 232 | elif isinstance(obj, ID): 233 | return self.__class__(self._store, self._schema_store, self._alias_map, obj) 234 | else: 235 | raise ValueError 236 | 237 | def _pythonToRdf(self, pred, obj): 238 | """ 239 | Given a Python predicate and object, return the equivalent RDF object. 240 | 241 | pred - rdflib.URIRef.URIRef instance 242 | obj - a python data representation 243 | 244 | returns rdflib.Identifier.Identifier instance 245 | """ 246 | obj_types = self._getObjectTypes(pred, obj) 247 | if RDF.List in obj_types: 248 | blank = BNode() 249 | self._pythonToList(blank, obj) ### this actually stores things... 250 | return blank 251 | elif RDF.Seq in obj_types: ### so will this 252 | blank = BNode() 253 | i = 1 254 | for item in obj: 255 | counter = URI(RDF_SEQi % i) 256 | self._store.add((blank, counter, self._pythonToRdf(counter, item))) 257 | i += 1 258 | return blank 259 | elif isinstance(obj, self.__class__): 260 | if obj._store is not self._store: 261 | obj.copyTo(self._store) ### and this... 262 | return obj._id 263 | else: 264 | return self._pythonToLiteral(obj, obj_types) 265 | 266 | def _literalToPython(self, obj, obj_types): 267 | """ 268 | obj - rdflib.Literal.Literal instance 269 | obj_types - iterator yielding rdflib.URIRef.URIRef instances 270 | 271 | returns a python literal datatype 272 | """ 273 | for obj_type in obj_types: 274 | try: 275 | return SchemaToPython[str(obj_type)][0](obj) 276 | except KeyError: 277 | pass 278 | return SchemaToPythonDefault[0](obj) 279 | 280 | def _pythonToLiteral(self, obj, obj_types): 281 | """ 282 | obj - a python literal datatype 283 | obj_types - iterator yielding rdflib.URIRef.URIRef instances 284 | 285 | returns rdflib.Literal.Literal instance 286 | """ 287 | for obj_type in obj_types: 288 | try: 289 | return Literal(SchemaToPython[str(obj_type)][1](obj)) 290 | except KeyError: 291 | pass 292 | return Literal(SchemaToPythonDefault[1](obj)) 293 | 294 | def _listToPython(self, subj): 295 | """ 296 | Given a RDF list, return the equivalent Python list. 297 | 298 | subj - rdflib.Identifier.Identifier instance 299 | 300 | returns list of python data representations 301 | """ 302 | try: 303 | first = self._store.triples((subj, RDF.first, None)).next()[2] 304 | except StopIteration: 305 | return [] 306 | try: 307 | rest = self._store.triples((subj, RDF.rest, None)).next()[2] 308 | except StopIteration: 309 | return ValueError 310 | return [self._rdfToPython(RDF.first, first)] + self._listToPython(rest) ### type first? 311 | 312 | def _pythonToList(self, subj, members): 313 | """ 314 | Given a Python list, store the eqivalent RDF list. 315 | 316 | subj - rdflib.Identifier.Identifier instance 317 | members - list of python data representations 318 | """ 319 | first = self._pythonToRdf(RDF.first, members[0]) 320 | self._store.add((subj, RDF.first, first)) 321 | if len(members) > 1: 322 | blank = BNode() 323 | self._store.add((subj, RDF.rest, blank)) 324 | self._pythonToList(blank, members[1:]) 325 | else: 326 | self._store.add((subj, RDF.rest, RDF.nil)) 327 | 328 | def _AttrToURI(self, attr): 329 | """ 330 | Given an attribute, return a URIRef. 331 | 332 | attr - str in the form prefix_localname 333 | 334 | returns rdflib.URIRef.URIRef instance 335 | """ 336 | if self._alias_map.has_key(attr): 337 | return URI(self._alias_map[attr]) 338 | else: 339 | prefix, localname = attr.split("_", 1) 340 | return URI("".join([self._store.namespace_manager.store.namespace(prefix), localname])) 341 | 342 | def _URIToAttr(self, uri): 343 | """ 344 | Given a URI, return an attribute. 345 | 346 | uri - str that is a URI 347 | 348 | returns str in the form prefix_localname. Not the most efficient thing around. 349 | """ 350 | for alias, alias_uri in self._alias_map.items(): 351 | if uri == alias_uri: 352 | return alias 353 | for ns_prefix, ns_uri in self._store.namespace_manager.namespaces(): 354 | if ns_uri == uri[:len(ns_uri)]: 355 | return "_".join([ns_prefix, uri[len(ns_uri):]]) 356 | raise ValueError 357 | 358 | def _getObjectTypes(self, pred, obj): 359 | """ 360 | Given a predicate and an object, return a list of the object's types. 361 | 362 | pred - rdflib.URIRef.URIRef instance 363 | obj - rdflib.Identifier.Identifier instance 364 | 365 | returns list containing rdflib.Identifier.Identifier instances 366 | """ 367 | obj_types = [o for (s, p, o) in self._schema_store.triples( 368 | (pred, RDFS.range, None))] 369 | if isinstance(obj, URI): 370 | obj_types += [o for (s, p, o) in self._store.triples((obj, RDF.type, None))] 371 | return obj_types 372 | 373 | def _isUniqueObject(self, pred): 374 | """ 375 | Given a predicate, figure out if the object has a cardinality greater than one. 376 | 377 | pred - rdflib.URIRef.URIRef instance 378 | 379 | returns bool 380 | """ 381 | # pred rdf:type owl:FunctionalProperty - True 382 | if (pred, RDF.type, FUNC_PROP) in self._schema_store: 383 | return True 384 | # subj rdf:type [ rdfs:subClassOf [ a owl:Restriction; owl:onProperty pred; owl:maxCardinality "1" ]] - True 385 | # subj rdf:type [ rdfs:subClassOf [ a owl:Restriction; owl:onProperty pred; owl:cardinality "1" ]] - True 386 | subj_types = [o for (s, p, o) in self._store.triples((self._id, RDF.type, None))] 387 | for type in subj_types: 388 | superclasses = [o for (s, p, o) in \ 389 | self._schema_store.triples((type, RDFS.subClassOf, None))] 390 | for superclass in superclasses: 391 | if ( 392 | (superclass, RDF.type, RESTRICTION) in self._schema_store and 393 | (superclass, ON_PROP, pred) in self._schema_store 394 | ) and \ 395 | ( 396 | (superclass, MAX_CARD, ONE) in self._schema_store or 397 | (superclass, CARD, ONE) in self._schema_store 398 | ): return True 399 | return False 400 | 401 | def __repr__(self): 402 | return self._id 403 | 404 | def __str__(self): 405 | try: 406 | return self._URIToAttr(self._id) 407 | except ValueError: 408 | return str(self._id) 409 | 410 | def __eq__(self, other): 411 | if isinstance(other, self.__class__): 412 | return self._id == other._id 413 | elif isinstance(other, ID): 414 | return self._id == other 415 | 416 | def __ne__(self, other): 417 | if self is other: return False 418 | else: return True 419 | 420 | def properties(self): 421 | """ 422 | List unique properties. 423 | 424 | returns list containing self.__class__ instances 425 | """ 426 | return [self.__class__(self._store, self._schema_store, self._alias_map, p) 427 | for (s,p,o) in self._store.triples((self._id, None, None))] 428 | 429 | def copyTo(self, store): 430 | """ 431 | Recursively copy statements to the given store. 432 | 433 | store - rdflib.Store.Store 434 | """ 435 | for (s, p, o) in self._store.triples((self._id, None, None)): 436 | store.add((s, p, o)) 437 | if isinstance(o, (URI, BNode)): 438 | self.__class__(self._store, self._schema_store, self._alias_map, o).copyTo(store) 439 | 440 | 441 | class ResourceSet: 442 | """ 443 | A set interface to the object(s) of a non-unique RDF predicate. Interface is a subset 444 | (har, har) of set().copy() returns a set. 445 | """ 446 | def __init__(self, subject, predicate, iterable=None): 447 | """ 448 | subject - rdflib.Identifier.Identifier instance 449 | predicate - rdflib.URIRef.URIRef instance 450 | iterable - 451 | """ 452 | self._subject = subject 453 | self._predicate = predicate 454 | self._store = subject._store 455 | if iterable is not None: 456 | for obj in iterable: 457 | self.add(obj) 458 | def __len__(self): 459 | return len(list( 460 | self._store.triples((self._subject._id, self._predicate, None)))) 461 | def __contains__(self, obj): 462 | if isinstance(obj, self._subject.__class__): 463 | obj = obj._id 464 | else: ### doesn't use pythonToRdf because that might store it 465 | obj_types = self._subject._getObjectTypes(self._predicate, obj) 466 | obj = self._subject._pythonToLiteral(obj, obj_types) 467 | return (self._subject._id, self._predicate, obj) in self._store 468 | def __iter__(self): 469 | for (s, p, o) in \ 470 | self._store.triples((self._subject._id, self._predicate, None)): 471 | yield self._subject._pythonToRdf(self._predicate, o) 472 | def copy(self): 473 | return set(self) 474 | def add(self, obj): 475 | self._store.add((self._subject._id, self._predicate, 476 | self._subject._pythonToRdf(self._predicate, obj))) 477 | def remove(self, obj): 478 | if not obj in self: 479 | raise KeyError 480 | self.discard(obj) 481 | def discard(self, obj): 482 | if isinstance(obj, self._subject.__class__): 483 | obj = obj._id 484 | else: ### doesn't use pythonToRdf because that might store it 485 | obj_types = self._subject._getObjectTypes(self._predicate, obj) 486 | obj = self._subject._pythonToLiteral(obj, obj_types) 487 | self._store.remove((self._subject._id, self._predicate, obj)) 488 | def clear(self): 489 | self._store.remove((self._subject, self._predicate, None)) 490 | 491 | 492 | SchemaToPythonDefault = (unicode, unicode) 493 | SchemaToPython = { # (schema->python, python->schema) Does not validate. 494 | 'http://www.w3.org/2001/XMLSchema#string': (unicode, unicode), 495 | 'http://www.w3.org/2001/XMLSchema#normalizedString': (unicode, unicode), 496 | 'http://www.w3.org/2001/XMLSchema#token': (unicode, unicode), 497 | 'http://www.w3.org/2001/XMLSchema#language': (unicode, unicode), 498 | 'http://www.w3.org/2001/XMLSchema#boolean': (bool, lambda i:unicode(i).lower()), 499 | 'http://www.w3.org/2001/XMLSchema#decimal': (float, unicode), 500 | 'http://www.w3.org/2001/XMLSchema#integer': (long, unicode), 501 | 'http://www.w3.org/2001/XMLSchema#nonPositiveInteger': (int, unicode), 502 | 'http://www.w3.org/2001/XMLSchema#long': (long, unicode), 503 | 'http://www.w3.org/2001/XMLSchema#nonNegativeInteger': (int, unicode), 504 | 'http://www.w3.org/2001/XMLSchema#negativeInteger': (int, unicode), 505 | 'http://www.w3.org/2001/XMLSchema#int': (int, unicode), 506 | 'http://www.w3.org/2001/XMLSchema#unsignedLong': (long, unicode), 507 | 'http://www.w3.org/2001/XMLSchema#positiveInteger': (int, unicode), 508 | 'http://www.w3.org/2001/XMLSchema#short': (int, unicode), 509 | 'http://www.w3.org/2001/XMLSchema#unsignedInt': (long, unicode), 510 | 'http://www.w3.org/2001/XMLSchema#byte': (int, unicode), 511 | 'http://www.w3.org/2001/XMLSchema#unsignedShort': (int, unicode), 512 | 'http://www.w3.org/2001/XMLSchema#unsignedByte': (int, unicode), 513 | 'http://www.w3.org/2001/XMLSchema#float': (float, unicode), 514 | 'http://www.w3.org/2001/XMLSchema#double': (float, unicode), # doesn't do the whole range 515 | # duration 516 | # dateTime 517 | # time 518 | # date 519 | # gYearMonth 520 | # gYear 521 | # gMonthDay 522 | # gDay 523 | # gMonth 524 | # hexBinary 525 | 'http://www.w3.org/2001/XMLSchema#base64Binary': (base64.decodestring, lambda i:base64.encodestring(i)[:-1]), 526 | 'http://www.w3.org/2001/XMLSchema#anyURI': (str, str), 527 | } 528 | 529 | 530 | if __name__ == '__main__': 531 | # use: "python -i sparta.py [URI for RDF file]+" 532 | from rdflib.TripleStore import TripleStore 533 | import sys 534 | mystore = TripleStore() 535 | for arg in sys.argv[1:]: 536 | mystore.parse(arg) 537 | thing = ThingFactory(mystore) 538 | --------------------------------------------------------------------------------