sparqlfun

LinkML based SPARQL template library and execution engine

  • modularized core library of SPARQL templates
    • generic templates using common vocabs (rdf, owl, skos, ...)
    • OBO and biology specific, e.g. Ubergraph
    • coming soon: uniprot, wikidata, etc
  • Fully FAIR description of templates
    • Each template has a URI (e.g.: https://linkml.io/sparqlfun/PairwiseCommonDescendant)
    • Each template parameter has a URI (e.g. https://linkml.io/sparqlfun/subject)
    • Full metadata including descriptions of each
    • Templates described in YAML, RDF, SHACL, ShEx, ...
  • Rich expressive language for moedeling templates
    • uses LinkML as base language
  • optional python bindings / object model using LinkML
  • supports both SELECT and CONSTRUCT
  • optional export to TSV, JSON, YAML, RDF
  • extensive endpoint metadata

This is currently alpha software, interfaces and organization may change

Browse the default templates

Note: currently not all metadata from the yaml is shown in the generated docs

Command Line

Use the sparqlfun:PairwiseCommonSubClassAncestor template

sparqlfun query -e ubergraph -T PairwiseCommonSubClassAncestor node1=GO:0046220 node2=GO:0008295

results:

results:
- node1: GO:0046220
  node2: GO:0008295
  predicate1: rdfs:subClassOf
  predicate2: rdfs:subClassOf
  ancestor: GO:0009987
- node1: GO:0046220
  node2: GO:0008295
  predicate1: rdfs:subClassOf
  predicate2: rdfs:subClassOf
  ancestor: GO:0044237
- node1: GO:0046220
  node2: GO:0008295
  predicate1: rdfs:subClassOf
  predicate2: rdfs:subClassOf
  ancestor: GO:0044271
...

Local RDF files

If you specify the -f / --format option then -e is assumed to be a path to a file on disk:

sparqlfun query -e go.owl.ttl -f ttl -T PairwiseCommonSubClassAncestor node1=GO:0046220 node2=GO:0008295

List all templates

sparqlfun endpoints

Python

from sparqlfun import SparqlEngine
from sparqlfun.model import PairwiseCommonSubClassAncestor

se = SparqlEngine(endpoint='ubergraph')
se.bind_prefixes(GO='http://purl.obolibrary.org/obo/GO_')
for apair in se.query(PairwiseCommonSubClassAncestor(node1='GO:0046220', node2='GO:0008295')).results:
        print(f'ROW={apair.node1} <-> {apair.node2} ANCESTOR = {apair.ancestor}')

For more examples, see [tests/] in GitHub

Browsing the templates

  • source is in sparqlfun/schema
    • add new templates here
  • Browse the generated markdown on the site
    • markdown is auto-created from the yaml schema

You can also list templates here:

sparqlfun templates

or for detailed view:

sparqlfun templates --detail

How it works

Basics

Templates are defined as YAML files following the LinkML schema.

A yaml file with a single template might look like this:

schema:
   id: http://example.org/my-vocab/templates
prefixes:
   my: http://example.org/my-vocab/
classes:
  my template:
    slots:
      - my_var1
      - my_var2
    annotations:
      sparql.select: |-
        SELECT  * WHERE { ... ?my_var1 ... ?my_var2}

slots:
  my_var1:
    description: about my var 1
  my_var2:
    description: about my var 2

This defines a template MyTemplate with two slots/parameters, and an arbitrarily complex SPARQL select query.

The YAML file is broken into blocks that minimally include 3 sections:

  • schema metadata, including prefix declarations
  • your templates, which are in the classes section
  • your parameters/variables, which are in the slots section

Note that the definitions of the slots go in a different section from the classes/templates. You are encouraged to "reuse" slots across templates. However, you can use an attribute declaration as a shortcut if you don't want to reuse.

The above can be used in queries:

sparqlfun -e ubergraph -T MyTemplate my_var2=MY_VAL

You can ground any or all of your vars on the command line (if you ground all then your SELECT is effectively an ASK query).

However, the features go beyond other templating systems, and leverage the fact that LinkML is a fully-fledged rich modeling language with bindings to JSON-Schema, SHACL, ShEx, etc.

For example, you will get markdown documentation describing your templates. This markdown documentation will be even richer if you annotate your schemas with metadata such as

  • descriptions
  • ranges for slots
  • mappings and URIs for your templates and slots

This brings a number of tangible benefits:

  • your templates can be strongly typed
  • templates can be compiled to multiple other forms
  • templates are turned into Python dataclasses, giving an optional ORM-like layer, IDE suppport, etc
  • in future applications will be able to use template metadata
    • documentation on each slot
    • pickers for fields such as dates, enums, etc
    • e.g. if a template slot has a range of class MyClass, applications could provide autocomplete

Template Inheritance

Templates can be inherited, facilitating reuse and composition patterns

To illustrate consider a simple "base" template to query a triple:

classes:
  triple:
    aliases:
      - statement
    description: >-
      Represents an RDF triple
    slots:
      - subject
      - predicate
      - object
    class_uri: rdf:Statement
    in_subset:
      - base table
    annotations:
      sparql.select: SELECT  * WHERE { ?subject ?predicate ?object}

This is arguable not a particularly useful template in isolation - you may as well query directly with sparql (nevertheless it can be useful to have templates for even this simple pattern, to faciliate generation of APIs etc)

New templates can use this as a base class, and inherit from it, which means that slots will be inherited, eliminating some boilerplate and the need to redefine them

classes:
  quad:
    is_a: triple
  slots:
     - graph  ## s/p/o slots inherited from triple
  annotations:
    sparql.select: SELECT  * WHERE {GRAPH ?graph { ?subject ?predicate ?object}}
````

Inerhitance allows even more powerful features using the LinkML
`classification_rules` construct. Let's say we want to represent type
triples as children of generic triples:


```yaml
rdf type triple:
    is_a: triple
    description: >-
      A triple that indicates the asserted type of the subject entity
    slot_usage:
      object:
        description: >-
          The entity type
        range: class node
    classification_rules:
      - is_a: triple
        slot_conditions:
          predicate:
            equals_string: rdf:type

Note we don't need to specify a SPARQL template here - the template is autogenerated from the classification rule.

Use of inheritance is a matter of choice. You may find it simpler to have some level of redundancy and repeat information in similar templates. Note you will still get a decent amount of reuse via a common vocabulary of slots

SPARQL CONSTRUCT and nested/inlined objects

Example CONSTRUCT query:

obo class:
    is_a: class node
    class_uri: owl:Class
    slots:
      - definition
      - exact_synonyms
    annotations:
      sparql.construct: |-
        CONSTRUCT {
          ?id a owl:Class ;
              IAO:0000115 ?definition ;
              oboInOwl:hasExactSynonym ?exact_snonyms
        }
        WHERE {
          ?id a owl:Class .
          OPTIONAL { ?id IAO:0000115 ?definition } .
          OPTIONAL { ?id oboInOwl:hasExactSynonym ?exact_snonyms } .
        }

...

slots:
  definition:
    slot_uri: IAO:0000115
  exact_synonyms:
    slot_uri: oboInOwl:hasExactSynonym
    multivalued: true

We can then query this as follows:

sparqlfun -e ontobee -T OboClass id=GO:0000023

The results will be nested following the LinkML specification for the model

{
  "results": [
    {
      "id": "GO:0000023",
      "definition": "The chemical reactions and pathways involving the disaccharide maltose (4-O-alpha-D-glucopyranosyl-D-glucopyranose), an intermediate in the catabolism of glycogen and starch.",
      "exact_synonyms": [
        "malt sugar metabolic process",
        "malt sugar metabolism",
        "maltose metabolism"
      ]
    }
  ],
  "@type": "ResultSet"
}

(note: templates are also compiled to JSON-Schema, which can be used for additional validation)

You can also get the turtle as returned by the triplestore using -f ttl:

@prefix ns1: <http://www.geneontology.org/formats/oboInOwl#> .
@prefix ns2: <http://purl.obolibrary.org/obo/> .
@prefix ns3: <https://w3id.org/sparqlfun/> .

ns2:GO_0000023 a <http://www.w3.org/2002/07/owl#Class> ;
    ns2:IAO_0000115 "The chemical reactions and pathways involving the disaccharide maltose (4-O-alpha-D-glucopyranosyl-D-glucopyranose), an intermediate in the catabolism of glycogen and starch." ;
    ns1:hasExactSynonym "malt sugar metabolic process",
        "malt sugar metabolism",
        "maltose metabolism" .

[] a ns3:ResultSet ;
    ns3:results ns2:GO_0000023 .

With -t tsv the linkml csv dumper will attempt to flatten the nested structure to TSV as closely as possible, e.g. using pipe internal seperators for multivalued

Multiple Values

Parameters can be passed as lists, which will be translated to VALUES statements

sparqlfun -e ontobee -T OboClass id=GO:0000023,GO:0000024

Modularity

LinkML allows importing so templates can be modularized using imports

NOTE In future this repo may be split up, with the bio/obo specific features migrating to a new repo.

Use of Jinja commands

You can incorporate additional logic via Jinja2 templating instructions:

obo class filtered:
    is_a: class node
    class_uri: owl:Class
    slots:
      - definition
      - exact_synonyms
    annotations:
      sparql.construct: |-
        CONSTRUCT {
          ?id a owl:Class ;
              IAO:0000115 ?definition ;
              oboInOwl:hasExactSynonym ?exact_snonyms
        }
        WHERE {
          ?id a owl:Class .
          OPTIONAL { ?id IAO:0000115 ?definition } .
          OPTIONAL { ?id oboInOwl:hasExactSynonym ?exact_snonyms } .
          {% if query_has_subclass_ancestor %}
          ?id rdfs:subClassOf ?query_has_subclass_ancestor
          {% endif %}
        }

slots:
  query_has_subclass_ancestor:
    range: class node
    description: transitive is_a parent
    in_subset:
       - ubergraph  ## requires relation-graph closure

Supported Endpoints

This framework can be used with any SPARQL endpoint. However, the current pre-defined templates are geared towards the combination of OBO-style ontologies together with storage patterns employed in triplestores such as ubergraph and ontobee.

In particular, ubergraph uses the relation-graph inference tool to pre-compute inferred direct triples from TBox existential axioms, allowing for simple and powerful queries over inferred ontologies

See the config files in sparqlfun/config for a list of all pre-defined endpoints

Example:

endpoints:
   ubergraph:
      url: https://stars-app.renci.org/ubergraph/sparql
      example_queries:
         - query_template: PairwiseCommonSubClassAncestor
           bindings:
              node1: GO:0046220
              node2: GO:0008295

See config_schema.yaml for the schema for endpoints

Note there is a rich metadata model that is intended to facilitate applications and automated testing. It should be possible to automatically determine which templates are compatible with which endpoints based on provided metadata.

Adding your own templates

Currently this library is easiest to use if you are working with the existing pre-defined templates (PRs are welcome)

However, you can use the framework with your own templates for your own triple data. THIS IS NOT YET WELL-SUPPORTED There are a couple of steps involved, in future this should be easier.

First you need to create your own yaml file. This needs to conform to the LinkML metamodel - we recommend just copying an existing template to do this. Some of this may seem like unnecessary boilerplate at this stage, but it will come in useful later.

Next you need to compile the template:

gen-python my_template.yaml > my_template.py

This requires linkml (this library uses linkml as a developer dependency)

You will need to pass BOTH of these as arguments to sparqlfun (-m and -S)

TODO:

  • add a dependency to the full linkml framework
  • allow dynamic compilation of templates

See also

This was inspired by and designed as a replacement for the powerful but arcane sparqlprog system.

TODO: list other SPARQL template frameworks

TODOs

  • Better Document
    • framework
    • templates
    • How-tos for use with Python, SHACL, ...
    • exemplar notebooks
  • Unify with SQL/rdftab functionality in semantic-sql
  • Cypher bindings
  • Split into bio-specific
  • Expose more ubergraph awesomeness
  • FastAPI/serverless endpoint
  • Expose more validation
  • Integrate visualization / obographviz
  • compilation to other frameworks, e.g. grlc
  • Chaining
    • inject output from one into another and merge results, e.g. to get labels
    • similar to wikidata services
  • UI/yasgui integration
  • generation from dosdp (use dosdp-query algorithm)
  • Templates for
    • uniprot
    • gocams
    • wikidata