{"id":19710736,"url":"https://github.com/vemonet/rdflib-endpoint","last_synced_at":"2026-02-17T18:09:18.843Z","repository":{"id":57460360,"uuid":"366127349","full_name":"vemonet/rdflib-endpoint","owner":"vemonet","description":"💫 Deploy SPARQL endpoints from RDFLib Graphs to serve RDF files, machine learning models, or any other logic implemented in Python","archived":false,"fork":false,"pushed_at":"2025-03-17T15:25:52.000Z","size":10006,"stargazers_count":86,"open_issues_count":1,"forks_count":19,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-30T17:08:09.096Z","etag":null,"topics":["fastapi","oxigraph","python","rdf","rdflib","sparql","sparql-endpoints"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/rdflib-endpoint","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vemonet.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-10T17:39:31.000Z","updated_at":"2025-03-22T19:09:55.000Z","dependencies_parsed_at":"2023-02-12T12:01:57.916Z","dependency_job_id":"236f4bea-4811-4cf5-9fbd-0b409618a939","html_url":"https://github.com/vemonet/rdflib-endpoint","commit_stats":{"total_commits":271,"total_committers":4,"mean_commits":67.75,"dds":0.05535055350553508,"last_synced_commit":"84b5c16f779d2d73394ed57642b1c029e7321041"},"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vemonet%2Frdflib-endpoint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vemonet%2Frdflib-endpoint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vemonet%2Frdflib-endpoint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vemonet%2Frdflib-endpoint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vemonet","download_url":"https://codeload.github.com/vemonet/rdflib-endpoint/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247526753,"owners_count":20953143,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fastapi","oxigraph","python","rdf","rdflib","sparql","sparql-endpoints"],"created_at":"2024-11-11T22:08:17.541Z","updated_at":"2026-02-17T18:09:18.830Z","avatar_url":"https://github.com/vemonet.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# 💫 SPARQL endpoint for RDFLib\n\n[![PyPI - Version](https://img.shields.io/pypi/v/rdflib-endpoint.svg?logo=pypi\u0026label=PyPI\u0026logoColor=silver)](https://pypi.org/project/rdflib-endpoint/)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/rdflib-endpoint.svg?logo=python\u0026label=Python\u0026logoColor=silver)](https://pypi.org/project/rdflib-endpoint/)\n\n[![Test package](https://github.com/vemonet/rdflib-endpoint/actions/workflows/test.yml/badge.svg)](https://github.com/vemonet/rdflib-endpoint/actions/workflows/test.yml)\n[![Publish package](https://github.com/vemonet/rdflib-endpoint/actions/workflows/release.yml/badge.svg)](https://github.com/vemonet/rdflib-endpoint/actions/workflows/release.yml)\n[![Coverage Status](https://coveralls.io/repos/github/vemonet/rdflib-endpoint/badge.svg?branch=main)](https://coveralls.io/github/vemonet/rdflib-endpoint?branch=main)\n\n[![license](https://img.shields.io/pypi/l/rdflib-endpoint.svg?color=%2334D058)](https://github.com/vemonet/rdflib-endpoint/blob/main/LICENSE.txt)\n[![types - Mypy](https://img.shields.io/badge/types-mypy-blue.svg)](https://github.com/python/mypy)\n\n\u003c/div\u003e\n\n`rdflib-endpoint` enables to:\n\n- **deploy RDFLib `Graph`** and `Dataset` as SPARQL endpoints,\n- **define custom SPARQL functions** implemented in python that can be queried in a federated fashion using SPARQL `SERVICE` from another endpoint,\n- **serve local RDF files** in one command.\n\n\u003e Feel free to create an [issue](/issues), or send a pull request if you are facing issues or would like to see a feature implemented.\n\n## 📦️ Installation\n\nThis package requires Python \u003e=3.8, install it  from [PyPI](https://pypi.org/project/rdflib-endpoint/) with:\n\n```shell\npip install \"rdflib-endpoint[cli,oxigraph]\"\n# Or install with uv\nuv tool install rdflib-endpoint --with \"rdflib-endpoint[cli,oxigraph]\"\n# Or run directly with uvx\nuvx --with \"rdflib-endpoint[cli,oxigraph]\" rdflib-endpoint\n```\n\nOptional extras:\n\n| Extra      | Adds                                                     |\n| ---------- | -------------------------------------------------------- |\n| `web`      | `uvicorn` (not included in default dependencies)         |\n| `cli`      | CLI commands and `uvicorn`                               |\n| `oxigraph` | [Oxigraph](https://github.com/oxigraph/oxigraph) backend |\n\n## ⌨️ Use the CLI\n\n`rdflib-endpoint` can be used from the command line interface to perform basic utility tasks, such as serving or converting RDF files locally.\n\n**Serve RDF files**, with YASGUI available on http://localhost:8000:\n\n```bash\nrdflib-endpoint serve *.ttl *.jsonld *.nq\n```\n\nUse [oxigraph](https://github.com/oxigraph/oxigraph) as backend, it supports some functions that are not supported by the RDFLib query engine, such as `COALESCE`:\n\n```bash\nrdflib-endpoint serve --store Oxigraph \"*.ttl\" \"*.jsonld\" \"*.nq\"\n```\n\n**Convert and merge RDF files** from multiple formats to a specific format:\n\n```bash\nrdflib-endpoint convert \"*.ttl\" \"*.jsonld\" \"*.nq\" --output \"merged.trig\"\n```\n\n## ✨ Deploy your SPARQL endpoint\n\n`rdflib-endpoint` enables you to easily define and deploy SPARQL endpoints based on RDFLib `Graph` and `Dataset`. Additionally it provides helpers to defines custom functions in the endpoint.\n\n\u003e [!TIP]\n\u003e\n\u003e Checkout the [`example`](https://github.com/vemonet/rdflib-endpoint/tree/main/example) folder for a complete working app example with custom functions to get started, including a docker deployment.\n\n### ⚡️ Deploy as a standalone API\n\nCreate and run a standalone SPARQL endpoint using `SparqlEndpoint`, e.g. in a `main.py` file:\n\n```python\nfrom rdflib import Dataset\nfrom rdflib_endpoint import SparqlEndpoint\n\nds = Dataset()\n\napp = SparqlEndpoint(\n    graph=ds,\n    path=\"/\",\n    # CORS enabled by default to enable querying it from client JavaScript\n    cors_enabled=True,\n    # Metadata used for the SPARQL service description and Swagger UI:\n    title=\"SPARQL endpoint for RDFLib graph\",\n    description=\"A SPARQL endpoint to serve any other logic implemented in Python. \\n[Source code](https://github.com/vemonet/rdflib-endpoint)\",\n    version=\"0.1.0\",\n    public_url='https://127.0.0.1:8000/',\n)\n```\n\nStart the server on http://localhost:8000:\n\n```sh\nuv run uvicorn main:app --reload\n```\n\n### 🛣️ Embedding in an existing app\n\nInstead of a full app, you can mount the endpoint as a router. `SparqlRouter` constructor takes the same arguments as `SparqlEndpoint`, apart from `enable_cors` which is defined at the API level.\n\n```python\nfrom fastapi import FastAPI\nfrom rdflib import Dataset\nfrom rdflib_endpoint import SparqlRouter\n\nds = Dataset()\nsparql_router = SparqlRouter(graph=ds, path=\"/\")\n\napp = FastAPI()\napp.include_router(sparql_router)\n```\n\n\u003e [!TIP]\n\u003e\n\u003e To deploy this route in a **Flask** app checkout how it has been done in the [curies mapping service](https://github.com/biopragmatics/curies/blob/main/src/curies/mapping_service/api.py) of the [Bioregistry](https://bioregistry.io/).\n\n### 🧩 Custom SPARQL Functions using decorators\n\n`DatasetExt` extends RDFLib `Dataset` with four decorator helpers to register python-based SPARQL evaluation functions.\n\n| Decorator             | Triggered by                                        | Typical use                         |\n| --------------------- | --------------------------------------------------- | ----------------------------------- |\n| `@type_function`      | A triple pattern with subject typed by the function | Structured multi-field results      |\n| `@predicate_function` | A predicate in the given namespace                  | Fill object values via python logic |\n| `@extension_function` | `BIND(func:myFunc(...))`                            | Scalar or multi-binding functions   |\n| `@graph_function`     | `BIND(func:funcGraph(...) AS ?g)`                   | Return a temporary graph            |\n\nKey behaviors:\n\n- Types, predicates and functions IRIs are generated from the provided namespace concatenated to their python counterpart following SPARQL naming conventions (classes in PascalCase, predicates and functions in camelCase). Default namespace is `urn:sparql-function:`\n- Return a list to emit multiple result rows\n- Return dataclasses to populate multiple variables.\n- Python defaults handle missing input values.\n- Add sparql codeblocks with a query example in the function docstring, these will be extracted and added as YASGUI queries tabs when deployed through the `SparqlEndpoint` or `SparqlRouter`\n\n\u003e [!CAUTION]\n\u003e\n\u003e For now RDFLib uses a global variable to define custom evaluations, that means if you declare 2 datasets in the same process the functions defined on one dataset will be also used on another dataset.\n\n\u003e [!WARNING]\n\u003e\n\u003e Oxigraph does not support custom functions, so it can be only used to deploy graphs without custom functions.\n\n#### `type_function` · Typed triple-pattern functions\n\nRegister a triple-pattern function, ideal for complex functions as all inputs/outputs are explicit in the SPARQL query. The function is selected when a subject is typed with the function name in PascalCase in the given namespace. The decorated function receives arguments extracted from input predicates derived from the arguments names, and returns either a single result or a list of results.\n\n```python\nfrom dataclasses import dataclass\nfrom rdflib_endpoint import DatasetExt\n\nds = DatasetExt()\n\n@dataclass\nclass SplitterResult:\n    splitted: str\n    index: int\n\n@ds.type_function()\ndef string_splitter(\n    split_string: str,\n    separator: str = \" \",\n) -\u003e list[SplitterResult]:\n    \"\"\"Split a string and return each part with their index.\"\"\"\n    return [SplitterResult(splitted=part, index=idx) for idx, part in enumerate(split_string.split(separator))]\n```\n\nExample query:\n\n```SPARQL\nPREFIX func: \u003curn:sparql-function:\u003e\nSELECT ?input ?part ?idx\nWHERE {\n    VALUES ?input { \"hello world\" \"cheese is good\" }\n    [] a func:StringSplitter ;\n        func:splitString ?input ;\n        func:separator \" \" ;\n        func:splitted ?part ;\n        func:index ?idx .\n}\n```\n\n#### `predicate_function` · Predicate evaluation\n\nRegister a predicate function, ideal when the input is a simple IRI. The function is selected when the predicate is the function name in camelCase in the given namespace. The decorated function receives the subject IRI as input and returns the object values.\n\n```python\nimport bioregistry\nfrom rdflib import DC, OWL, URIRef\nfrom rdflib_endpoint import DatasetExt\n\nds = DatasetExt()\nconv = bioregistry.get_converter()\n\n@ds.predicate_function(namespace=DC._NS)\ndef identifier(input_iri: URIRef) -\u003e URIRef:\n    \"\"\"Get the standardized IRI for a given input IRI.\n\n    ```sparql\n    PREFIX dc: \u003chttp://purl.org/dc/elements/1.1/\u003e\n    SELECT ?id WHERE {\n        \u003chttps://identifiers.org/CHEBI/1\u003e dc:identifier ?id .\n    }\n    ```\n    \"\"\"\n    return URIRef(conv.standardize_uri(input_iri))\n\n@ds.predicate_function(namespace=OWL._NS)\ndef same_as(input_iri: URIRef) -\u003e list[URIRef]:\n    \"\"\"Get all alternative IRIs for a given IRI using the Bioregistry.\"\"\"\n    prefix, identifier = conv.compress(input_iri).split(\":\", 1)\n    return [URIRef(iri) for iri in bioregistry.get_providers(prefix, identifier).values()]\n```\n\nExample queries:\n\n```sparql\nPREFIX dc: \u003chttp://purl.org/dc/elements/1.1/\u003e\nSELECT ?id WHERE {\n    \u003chttps://identifiers.org/CHEBI/1\u003e dc:identifier ?id .\n}\n```\n\n```sparql\nPREFIX owl: \u003chttp://www.w3.org/2002/07/owl#\u003e\nSELECT ?sameAs WHERE {\n    \u003chttps://identifiers.org/CHEBI/1\u003e owl:sameAs ?sameAs .\n}\n```\n\n#### `extension_function` · Standard SPARQL extension functions\n\nRegister a SPARQL extension function usable with `BIND(\u003cnamespace+name\u003e(...) AS ?var)`. The Python function receives evaluated args, returning a list emits multiple bound values.\n\n```python\nfrom dataclasses import dataclass\nfrom rdflib_endpoint import DatasetExt\n\nds = DatasetExt()\n\n@ds.extension_function()\ndef split(input_str: str, separator: str = \",\") -\u003e list[str]:\n    \"\"\"Split a string and return each part.\"\"\"\n    return input_str.split(separator)\n```\n\nExample query:\n\n```sparql\nPREFIX func: \u003curn:sparql-function:\u003e\nSELECT ?input ?part WHERE {\n    VALUES ?input { \"hello world\" \"cheese is good\" }\n    BIND(func:split(?input, \" \") AS ?part)\n}\n```\n\nUse a dataclass to **populate multiple variables**, the first field of the dataclass will be returned in the bound variable, other fields will populate variables derived from the base bound variable concatenated with the fields in PascalCase:\n\n```python\nfrom dataclasses import dataclass\nfrom rdflib_endpoint import DatasetExt\n\nds = DatasetExt()\n\n@dataclass\nclass SplitResult:\n    value: str\n    index: int\n\n@ds.extension_function()\ndef split_index(input_str: str, separator: str = \",\") -\u003e list[SplitResult]:\n    \"\"\"Split a string and return each part with their index.\"\"\"\n    return [SplitResult(value=part, index=idx) for idx, part in enumerate(input_str.split(separator))]\n```\n\nExample query:\n\n```sparql\nPREFIX func: \u003curn:sparql-function:\u003e\nSELECT ?input ?part ?partIndex WHERE {\n    VALUES ?input { \"hello world\" \"cheese is good\" }\n    BIND(func:splitIndex(?input, \" \") AS ?part)\n}\n```\n\n#### `graph_function` · Return temporary graph\n\nRegister a function that returns an `rdflib.Graph`. Use it in SPARQL as `BIND(\u003cnamespace+name\u003e(...) AS ?g)` and then query the temporary graph with `GRAPH ?g { ... }`. Returned graphs are added to the dataset for the duration of the query and cleaned up afterwards.\n\n```python\nfrom rdflib import Graph, Literal, Namespace, URIRef\nfrom rdflib_endpoint import DatasetExt\n\nds = DatasetExt(default_union=True)\n\n@ds.graph_function()\ndef split_graph(input_str: str, separator: str = \",\") -\u003e Graph:\n    g = Graph()\n    for part in input_str.split(separator):\n        g.add((URIRef(\"http://splitted\"), URIRef[\"http://part\"], Literal(part)))\n    return g\n```\n\nExample query:\n\n```sparql\nPREFIX func: \u003curn:sparql-function:\u003e\nSELECT DISTINCT * WHERE {\n    VALUES ?input { \"hello world\" \"cheese is good\" }\n    BIND(func:splitGraph(?input, \" \") AS ?g)\n    GRAPH ?g {\n        ?s ?p ?o .\n    }\n}\n```\n\n### 📝 Define custom SPARQL functions (legacy API)\n\nAlternatively you can manually implement evaluation extension functions by passing a `functions={...}` dict to `SparqlEndpoint` or `SparqlRouter`.\n\n````python\nimport rdflib\nfrom rdflib import Dataset\nfrom rdflib.plugins.sparql.evalutils import _eval\nfrom rdflib.plugins.sparql.parserutils import CompValue\nfrom rdflib.plugins.sparql.sparql import QueryContext\nfrom rdflib_endpoint import SparqlEndpoint\n\ndef custom_concat(query_results, ctx: QueryContext, part: CompValue, eval_part):\n    \"\"\"Concat 2 strings in the 2 senses and return the length as additional Length variable\n    \"\"\"\n    # Retrieve the 2 input arguments\n    argument1 = str(_eval(part.expr.expr[0], eval_part.forget(ctx, _except=part.expr._vars)))\n    argument2 = str(_eval(part.expr.expr[1], eval_part.forget(ctx, _except=part.expr._vars)))\n    evaluation = []\n    scores = []\n    # Prepare the 2 result string, 1 for eval, 1 for scores\n    evaluation.append(argument1 + argument2)\n    evaluation.append(argument2 + argument1)\n    scores.append(len(argument1 + argument2))\n    scores.append(len(argument2 + argument1))\n    # Append the results for our custom function\n    for i, result in enumerate(evaluation):\n        query_results.append(eval_part.merge({\n            part.var: rdflib.Literal(result),\n            # With an additional custom var for the length\n            rdflib.term.Variable(part.var + 'Length'): rdflib.Literal(scores[i])\n        }))\n    return query_results, ctx, part, eval_part\n\napp = SparqlEndpoint(\n    graph=Dataset(default_union=True),\n    # Register the functions:\n    functions={\n        'urn:sparql-function:custom_concat': custom_concat\n    },\n    # Example queries used to populate YASGUI tabs\n    example_queries={\n        \"Custom function\": {\n            \"query\": \"\"\"PREFIX myfunctions: \u003curn:sparql-function:\u003e\nSELECT ?concat ?concatLength WHERE {\n    BIND(myfunctions:custom_concat(\"First\", \"last\") AS ?concat)\n}\"\"\",\n        },\n    },\n)\n````\n\n### ✒️ Or directly define the custom evaluation\n\nFor full control, override the evaluation process entirely using `custom_eval`. Refer to the [RDFLib documentation](https://rdflib.readthedocs.io/en/stable/apidocs/examples.custom_eval/) for more details.\n\n```python\nimport rdflib\nfrom rdflib.plugins.sparql.evaluate import evalBGP\nfrom rdflib.plugins.sparql.parserutils import CompValue\nfrom rdflib.plugins.sparql.sparql import QueryContext\nfrom rdflib.namespace import FOAF, RDF, RDFS\n\ndef custom_eval(ctx: QueryContext, part: CompValue):\n    \"\"\"Rewrite triple patterns to get super-classes\"\"\"\n    if part.name == \"BGP\":\n        # rewrite triples\n        triples = []\n        for t in part.triples:\n            if t[1] == RDF.type:\n                bnode = rdflib.BNode()\n                triples.append((t[0], t[1], bnode))\n                triples.append((bnode, RDFS.subClassOf, t[2]))\n            else:\n                triples.append(t)\n        # delegate to normal evalBGP\n        return evalBGP(ctx, triples)\n    raise NotImplementedError()\n\napp = SparqlEndpoint(\n    graph=g,\n    custom_eval=custom_eval\n)\n```\n\n## 📂 Projects using rdflib-endpoint\n\nHere are some projects using `rdflib-endpoint` to deploy custom SPARQL endpoints with python:\n\n* [The Bioregistry](https://bioregistry.io/), an open source, community curated registry, meta-registry, and compact identifier resolver.\n* [proycon/codemeta-server](https://github.com/proycon/codemeta-server), server for codemeta, in memory triple store, SPARQL endpoint and simple web-based visualisation for end-user.\n* [AKSW/sparql-file](https://github.com/AKSW/sparql-file), serve a RDF file as an RDFLib Graph through a SPARQL endpoint.\n\n## 🛠️ Contributing\n\nTo run the project in development and make a contribution checkout the [contributing page](https://github.com/vemonet/rdflib-endpoint/blob/main/CONTRIBUTING.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvemonet%2Frdflib-endpoint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvemonet%2Frdflib-endpoint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvemonet%2Frdflib-endpoint/lists"}