Initial port to python3
[htsworkflow.git] / htsworkflow / util / rdfhelp.py
index d12b2c7e52792d0ca6674dbf7393b892501aebbd..76cde06e97d10593e9eef6d166df59e0329270d6 100644 (file)
@@ -2,11 +2,14 @@
 """
 import collections
 from datetime import datetime
-from urlparse import urlparse, urlunparse
-from urllib2 import urlopen
+from glob import glob
+from urllib.parse import urlparse, urlunparse
+from urllib.request import urlopen
 import logging
 import os
+import sys
 import types
+from pkg_resources import resource_listdir, resource_string
 
 import lxml.html
 import lxml.html.clean
@@ -14,27 +17,14 @@ import RDF
 
 logger = logging.getLogger(__name__)
 
-# standard ontology namespaces
-owlNS = RDF.NS('http://www.w3.org/2002/07/owl#')
-dublinCoreNS = RDF.NS("http://purl.org/dc/elements/1.1/")
-rdfNS = RDF.NS("http://www.w3.org/1999/02/22-rdf-syntax-ns#")
-rdfsNS = RDF.NS("http://www.w3.org/2000/01/rdf-schema#")
-xsdNS = RDF.NS("http://www.w3.org/2001/XMLSchema#")
-
-# internal ontologies
-submissionOntology = RDF.NS(
-    "http://jumpgate.caltech.edu/wiki/UcscSubmissionOntology#")
-dafTermOntology = RDF.NS("http://jumpgate.caltech.edu/wiki/UcscDaf#")
-libraryOntology = RDF.NS("http://jumpgate.caltech.edu/wiki/LibraryOntology#")
-inventoryOntology = RDF.NS(
-    "http://jumpgate.caltech.edu/wiki/InventoryOntology#")
-submissionLog = RDF.NS("http://jumpgate.caltech.edu/wiki/SubmissionsLog/")
-geoSoftNS = RDF.NS('http://www.ncbi.nlm.nih.gov/geo/info/soft2.html#')
+from htsworkflow.util.rdfns import *
+
+SCHEMAS_URL='http://jumpgate.caltech.edu/phony/schemas'
+INFERENCE_URL='http://jumpgate.caltech.edu/phony/inference'
 
 ISOFORMAT_MS = "%Y-%m-%dT%H:%M:%S.%f"
 ISOFORMAT_SHORT = "%Y-%m-%dT%H:%M:%S"
 
-
 def sparql_query(model, query_filename, output_format='text'):
     """Execute sparql query from file
     """
@@ -52,9 +42,9 @@ def display_query_results(results):
     """A very simple display of sparql query results showing name value pairs
     """
     for row in results:
-        for k, v in row.items()[::-1]:
-            print "{0}: {1}".format(k, v)
-        print
+        for k, v in list(row.items())[::-1]:
+            print("{0}: {1}".format(k, v))
+        print()
 
 def html_query_results(result_stream):
     from django.conf import settings
@@ -75,11 +65,11 @@ def html_query_results(result_stream):
     for row in result_stream:
         new_row = collections.OrderedDict()
         row_urls = []
-        for k,v in row.items():
+        for k,v in list(row.items()):
             new_row[k] = Simplified(v)
         results.append(new_row)
     context = Context({'results': results,})
-    print template.render(context)
+    print(template.render(context))
 
 def blankOrUri(value=None):
     """Return a blank node for None or a resource node for strings.
@@ -87,7 +77,7 @@ def blankOrUri(value=None):
     node = None
     if value is None:
         node = RDF.Node()
-    elif type(value) in types.StringTypes:
+    elif type(value) in str:
         node = RDF.Node(uri_string=value)
     elif isinstance(value, RDF.Node):
         node = value
@@ -95,21 +85,21 @@ def blankOrUri(value=None):
     return node
 
 
-def toTypedNode(value):
+def toTypedNode(value, language="en"):
     """Convert a python variable to a RDF Node with its closest xsd type
     """
-    if type(value) == types.BooleanType:
+    if type(value) == bool:
         value_type = xsdNS['boolean'].uri
         if value:
-            value = u'1'
+            value = '1'
         else:
-            value = u'0'
-    elif type(value) in (types.IntType, types.LongType):
+            value = '0'
+    elif type(value) in (int, int):
         value_type = xsdNS['decimal'].uri
-        value = unicode(value)
-    elif type(value) == types.FloatType:
+        value = str(value)
+    elif type(value) == float:
         value_type = xsdNS['float'].uri
-        value = unicode(value)
+        value = str(value)
     elif isinstance(value, datetime):
         value_type = xsdNS['dateTime'].uri
         if value.microsecond == 0:
@@ -118,20 +108,22 @@ def toTypedNode(value):
             value = value.strftime(ISOFORMAT_MS)
     else:
         value_type = None
-        value = unicode(value)
+        value = str(value)
 
     if value_type is not None:
         node = RDF.Node(literal=value, datatype=value_type)
     else:
-        node = RDF.Node(literal=unicode(value).encode('utf-8'))
+        node = RDF.Node(literal=str(value).encode('utf-8'), language=language)
     return node
 
 
 def fromTypedNode(node):
     """Convert a typed RDF Node to its closest python equivalent
     """
-    if node is None:
-        return None
+    if not isinstance(node, RDF.Node):
+        return node
+    if node.is_resource():
+        return node
 
     value_type = get_node_type(node)
     literal = node.literal_value['string']
@@ -155,7 +147,7 @@ def fromTypedNode(node):
     elif value_type in ('dateTime'):
         try:
             return datetime.strptime(literal, ISOFORMAT_MS)
-        except ValueError, _:
+        except ValueError as _:
             return datetime.strptime(literal, ISOFORMAT_SHORT)
     return literal
 
@@ -245,10 +237,10 @@ def get_model(model_name=None, directory=None):
         directory = os.getcwd()
 
     if model_name is None:
-        storage = RDF.MemoryStorage()
+        storage = RDF.MemoryStorage(options_string="contexts='yes'")
         logger.info("Using RDF Memory model")
     else:
-        options = "hash-type='bdb',dir='{0}'".format(directory)
+        options = "contexts='yes',hash-type='bdb',dir='{0}'".format(directory)
         storage = RDF.HashStorage(model_name,
                       options=options)
         logger.info("Using {0} with options {1}".format(model_name, options))
@@ -257,7 +249,7 @@ def get_model(model_name=None, directory=None):
 
 
 def load_into_model(model, parser_name, path, ns=None):
-    if type(ns) in types.StringTypes:
+    if type(ns) in str:
         ns = RDF.Uri(ns)
 
     if isinstance(path, RDF.Node):
@@ -284,7 +276,7 @@ def load_into_model(model, parser_name, path, ns=None):
             retries -= 1
             statements = rdf_parser.parse_as_stream(url, ns)
             retries = 0
-        except RDF.RedlandError, e:
+        except RDF.RedlandError as e:
             errmsg = "RDF.RedlandError: {0} {1} tries remaining"
             logger.error(errmsg.format(str(e), retries))
 
@@ -300,16 +292,18 @@ def load_string_into_model(model, parser_name, data, ns=None):
     for s in rdf_parser.parse_string_as_stream(data, ns):
         conditionally_add_statement(model, s, ns)
 
+
 def fixup_namespace(ns):
     if ns is None:
         ns = RDF.Uri("http://localhost/")
-    elif type(ns) in types.StringTypes:
+    elif type(ns) in str:
         ns = RDF.Uri(ns)
     elif not(isinstance(ns, RDF.Uri)):
         errmsg = "Namespace should be string or uri not {0}"
         raise ValueError(errmsg.format(str(type(ns))))
     return ns
 
+
 def conditionally_add_statement(model, s, ns):
     imports = owlNS['imports']
     if s.predicate == imports:
@@ -322,6 +316,51 @@ def conditionally_add_statement(model, s, ns):
             s.object = sanitize_literal(s.object)
     model.add_statement(s)
 
+
+def add_default_schemas(model, schema_path=None):
+    """Add default schemas to a model
+    Looks for turtle files in either htsworkflow/util/schemas
+    or in the list of directories provided in schema_path
+    """
+
+    schemas = resource_listdir(__name__, 'schemas')
+    for s in schemas:
+        schema = resource_string(__name__,  'schemas/' + s)
+        namespace = 'file://localhost/htsworkflow/schemas/'+s
+        add_schema(model, schema, namespace)
+
+    if schema_path:
+        if type(schema_path) in str:
+            schema_path = [schema_path]
+
+        for path in schema_path:
+            for pathname in glob(os.path.join(path, '*.turtle')):
+                url = 'file://' + os.path.splitext(pathname)[0]
+                stream = open(pathname, 'r')
+                add_schema(model, stream, url)
+                stream.close()
+
+def add_schema(model, schema, url):
+    """Add a schema to a model.
+
+    Main difference from 'load_into_model' is it tags it with
+    a RDFlib context so I can remove them later.
+    """
+    parser = RDF.Parser(name='turtle')
+    context = RDF.Node(RDF.Uri(SCHEMAS_URL))
+    for s in parser.parse_string_as_stream(schema, url):
+        try:
+            model.append(s, context)
+        except RDF.RedlandError as e:
+            logger.error("%s with %s", str(e), str(s))
+
+
+def remove_schemas(model):
+    """Remove statements labeled with our schema context"""
+    context = RDF.Node(RDF.Uri(SCHEMAS_URL))
+    model.context_remove_statements(context)
+
+
 def sanitize_literal(node):
     """Clean up a literal string
     """
@@ -374,18 +413,33 @@ def get_serializer(name='turtle'):
     """
     writer = RDF.Serializer(name=name)
     # really standard stuff
-    writer.set_namespace('owl', owlNS._prefix)
     writer.set_namespace('rdf', rdfNS._prefix)
     writer.set_namespace('rdfs', rdfsNS._prefix)
+    writer.set_namespace('owl', owlNS._prefix)
+    writer.set_namespace('dc', dcNS._prefix)
+    writer.set_namespace('xml', xmlNS._prefix)
     writer.set_namespace('xsd', xsdNS._prefix)
+    writer.set_namespace('vs', vsNS._prefix)
+    writer.set_namespace('wot', wotNS._prefix)
 
     # should these be here, kind of specific to an application
-    writer.set_namespace('libraryOntology', libraryOntology._prefix)
+    writer.set_namespace('htswlib', libraryOntology._prefix)
     writer.set_namespace('ucscSubmission', submissionOntology._prefix)
     writer.set_namespace('ucscDaf', dafTermOntology._prefix)
+    writer.set_namespace('geoSoft', geoSoftNS._prefix)
+    writer.set_namespace('encode3', encode3NS._prefix)
     return writer
 
+def get_turtle_header():
+    """Return a turtle header with our typical namespaces
+    """
+    serializer = get_serializer()
+    empty = get_model()
+    return serializer.serialize_model_to_string(empty)
 
-def dump_model(model):
+def dump_model(model, destination=None):
+    if destination is None:
+        destination = sys.stdout
     serializer = get_serializer()
-    print serializer.serialize_model_to_string(model)
+    destination.write(serializer.serialize_model_to_string(model))
+    destination.write(os.linesep)