Graph2SPARQL è un server GraphQL che traduce dinamicamente query GraphQL in query SPARQL, permettendo di interrogare qualsiasi RDF store configurato tramite un file YAML.
L’obiettivo del progetto è fornire un’interfaccia di interrogazione GraphQL per accedere a knowledge base strutturate in RDF (come DBpedia, Wikidata o dataset custom), senza richiedere all’utente di conoscere la sintassi SPARQL, e mascherare la complessità di SPARQL dietro una sintassi GraphQL più semplice.
- Endpoint GraphQL unico per interrogare dataset RDF.
- Traduzione automatica delle query GraphQL in SPARQL.
- Configurazione dinamica via YAML: supporto a più endpoint SPARQL semplicemente modificando il file
sparql_config.yaml. - Supporto ai principali costrutti SPARQL:
FILTER,OPTIONAL,FILTER NOT EXISTS,FILTER EXISTS,UNIONORDER BY,GROUP BY,HAVING,LIMIT,OFFSET
- Funzioni di aggregazione supportate:
COUNT,SUM,AVG,MIN,MAX
- Navigazione lungo path RDF arbitrari, anche su più salti (
?film dbo:director ?d . ?d dbo:birthPlace ?city). - Personalizzazione completa della sintassi SPARQL, operatori, prefissi, variabili e output tramite YAML.
Assicurati di avere installato:
- Python 3.9 o superiore
- Le seguenti dipendenze Python:
uvicornfastapiariadnepyyamlSPARQLWrapper
Installa tutto con:
pip install -r requirements.txtSe non specifichi un endpoint, il sistema userà quello definito come default in sparql_config.yaml.
python server.pyGraphQL Playground sarà disponibile su: http://127.0.0.1:8000/graphql
Puoi usare un qualsiasi SPARQL endpoint (es. Wikidata):
python server.py --endpoint "https://query.wikidata.org/sparql"Il server userà questo endpoint temporaneamente, senza modificare il file YAML. In questo caso occorrerà procedere con la modifica del file di configurazione sparql_config.yaml per far si che il tutto funzioni correttamente con il nuovo endpoint SPARQL.
Per maggiori dettagli sul file di configurazione consulta la sezione File di configurazione sparql_config.yaml, mentre per il cambio RDF Store consulta la sezione Cambio RDF store a runtime.
Di seguito una panoramica della struttura del progetto:
Graph2SPARQL/
├── server.py # Entry point: avvia il server e gestisce l'endpoint SPARQL
├── requirements.txt # Dipendenze Python del progetto
├── app/
│ ├── graphql/
│ │ └── schema.py # Definizione dello schema GraphQL
│ ├── services/
│ │ ├── entity_search.py # Logica per searchEntity
│ │ ├── aggregate.py # Logica per aggregateEntities
│ │ └── compare.py # Logica per compareEntities
│ │ └── query_executor.py # Esecuzione delle query SPARQL
│ ├── utils/
│ │ ├── config_loader.py # Caricamento e parsing del file YAML
│ │ └── uri_utils.py # Utility per nomi variabili SPARQL e gestione URI
│ └── config/
│ └── sparql_config.yaml # Configurazione dell’endpoint, sintassi e mapping
Il file sparql_config.yaml (in app/config/) consente di personalizzare completamente il comportamento del sistema per adattarsi a qualsiasi RDF store, senza modificare il codice Python.
default:
endpoint: "https://dbpedia.org/sparql"
rdf_type_property: "a"
prefixes:
rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
rdfs: "http://www.w3.org/2000/01/rdf-schema#"
dbo: "http://dbpedia.org/ontology/"
dbp: "http://dbpedia.org/property/"default: nome del profilo di configurazione.endpoint: URL dell’endpoint SPARQL.rdf_type_property: indica quale predicato usare per rappresentare il tipo di un'entità RDF.prefixes: dizionario dei prefissi da usare nelle query.
Template per costruire query SPARQL dinamiche usate da searchEntity.
query_syntax:
select_format: |
SELECT {modifiers}{select_vars} WHERE {{
{main_pattern}
{optional_patterns}
{union_blocks}
{filter_conditions}
}}
property_format: "{subject} <{property}> {object} ."
optional_format: "OPTIONAL {{ {pattern} }}"
union_format: "{{ {block} }}"
filter_format: "FILTER ({condition})"
group_by: "GROUP BY {group_by_fields}"
having: "HAVING({having_conditions})"
order_by: "ORDER BY {order_by_conditions}"
limit: "LIMIT {limit}"
offset: "OFFSET {offset}"| Chiave | Descrizione |
|---|---|
select_format |
Template dell'intera query SELECT; usa segnaposti per pattern e filtri |
property_format |
Triple di base nella forma soggetto - predicato - oggetto |
optional_format |
Template SPARQL per blocchi opzionali (OPTIONAL { ... }) |
union_format |
Template SPARQL per blocchi UNION |
filter_format |
Template SPARQL per i filtri (FILTER (...)) |
group_by |
Template per clausole GROUP BY con variabili |
having |
Template per blocchi HAVING, usati con aggregazioni |
order_by |
Template per clausole ORDER BY |
limit |
Limita il numero di risultati (LIMIT n) |
offset |
Offset per la paginazione (OFFSET n) |
options:
select_vars: ["?s"]
main_patterns:
- "?s a <{class_uri}> ."
optional_patterns: []
filter_conditions: []
group_by_fields: []
having_conditions: []
order_by_conditions: []| Chiave | Descrizione |
|---|---|
select_vars |
Variabili di default nel SELECT |
main_patterns |
Triple SPARQL principali (usa {class_uri} come segnaposto) |
optional_patterns |
Triple opzionali fisse |
filter_conditions |
Filtri SPARQL fissi |
group_by_fields |
Variabili di grouping fisse |
having_conditions |
Condizioni fisse per blocco HAVING |
order_by_conditions |
Ordinamenti predefiniti |
operator_map:
"=": "{var} = {value}"
"!=": "{var} != {value}"
">": "{var} > {value}"
"<": "{var} < {value}"
">=": "{var} >= {value}"
"<=": "{var} <= {value}"
REGEX: "REGEX({var}, {value}, \"i\")"
LANG_EQUALS: "LANG({var}) = {value}"| Operatore | Descrizione |
|---|---|
= |
Uguaglianza tra variabile e valore |
!= |
Disuguaglianza tra variabile e valore |
> |
Maggiore di |
< |
Minore di |
>= |
Maggiore o uguale a |
<= |
Minore o uguale a |
REGEX |
Confronto testuale con espressione regolare (insensibile al caso) |
LANG_EQUALS |
Confronto tra lingua della variabile e valore (es. "en") |
type_casting:
date: "^^xsd:date"Serve per specificare il tipo SPARQL da usare per valori come date o numeri.
| Chiave | Descrizione |
|---|---|
date |
Specifica che un valore deve essere trattato come xsd:date, utile per confronti con operatori come >, < |
aggregation_syntax:
select_format: |
SELECT {group_var} {aggregations} WHERE {{
{main_pattern}
}} GROUP BY {group_var}
aggregation_format: "({function}({variable}) AS ?{alias})"
group_by: "GROUP BY {group_var}"
having: "HAVING({having_conditions})"
order_by: "ORDER BY {order_by_conditions}"
limit: "LIMIT {limit}"
offset: "OFFSET {offset}"
property_format: "{subject} <{property}> {object} ."| Chiave | Descrizione |
|---|---|
select_format |
Template principale della query aggregate |
aggregation_format |
Formato per ogni aggregazione (es. SUM(?budget)) |
group_by |
Clausola GROUP BY con variabili specificate |
having |
Condizioni sul risultato aggregato |
order_by |
Ordinamento dei risultati aggregati |
limit |
Numero massimo di risultati |
offset |
Offset per la paginazione |
property_format |
Formato delle triple SPARQL |
compare_syntax:
select_format: |
SELECT {select_vars} WHERE {{
{subject_blocks}
{path_blocks}
{filter_conditions}
}}
subject_format: "{var} a <{class_uri}> ."
property_format: "{subject} <{property}> {object} ."
filter_format: "FILTER ({condition})"| Chiave | Descrizione |
|---|---|
select_format |
Struttura della query SPARQL per il confronto tra entità |
subject_format |
Definizione del tipo RDF per ogni soggetto |
property_format |
Formato delle triple di relazione tra entità |
filter_format |
Formato delle condizioni di filtro tra variabili |
Per orientarsi nel grafo RDF, Graph2SPARQL espone due metodi utili:
Restituisce tutte le classi RDF presenti nel dataset interrogato, rilevate tramite la proprietà rdf:type.
È anche possibile filtrare i risultati specificando una stringa namespaceFilter, che restituisce solo le classi il cui URI contiene quella stringa (case-insensitive).
namespaceFilter(opzionale): stringa per filtrare solo le classi che contengono il termine specificato (case-insensitive)
SELECT DISTINCT ?class WHERE {
?s a ?class .
}query {
availableClasses(namespaceFilter: "film")
}[
"http://dbpedia.org/ontology/Film",
"http://schema.org/Film",
...
]Restituisce tutte le proprietà RDF direttamente collegate a una certa classe RDF, sotto forma di lista.
className(obbligatorio): URI della classe RDF di cui si vogliono esplorare le proprietà.
SELECT DISTINCT ?property WHERE {
?s a <className> ;
?property ?o .
}query {
availableProperties(className: "http://dbpedia.org/ontology/Film") {
className
fields
}
}{
"className": "http://dbpedia.org/ontology/Film",
"fields": [
"http://dbpedia.org/ontology/budget",
"http://dbpedia.org/ontology/director",
"http://dbpedia.org/ontology/releaseDate",
...
]
}Questi metodi sono utili per esplorare il dataset RDF e permettono di costruire query corrette in searchEntity, aggregateEntities o compareEntities senza dover conoscere a priori tutte le classi e proprietà esistenti.
Permette di interrogare una classe RDF specificando proprietà, filtri, path, aggregazioni e altro.
| Parametro | Tipo | Descrizione |
|---|---|---|
className |
String | URI della classe RDF da interrogare (es. dbo:Film) |
selectFields |
[String] | URI delle proprietà RDF da includere nella SELECT |
filters |
[Object] | Filtro su proprietà dirette o su path multipli |
optionalFilters |
[Object] | Proprietà opzionali (SPARQL OPTIONAL) |
notExistsFilters, existsFilters |
[Object] | Pattern con FILTER NOT EXISTS e FILTER EXISTS |
distinct |
Boolean | Aggiunge DISTINCT alla SELECT |
limit, offset |
Int | Paginazione |
orderBy |
[Object] | Ordinamento dei risultati |
groupBy, having |
[String] | Supporto ad aggregazioni e condizioni su aggregati |
query {
searchEntity(
className: "http://dbpedia.org/ontology/Film"
filters: [
{
path: [
{ property: "http://dbpedia.org/property/writer" },
{ property: "http://dbpedia.org/property/deathDate" }
]
}
]
optionalFilters: [
{ property: "http://dbpedia.org/ontology/budget" }
]
selectFields: [
"http://dbpedia.org/ontology/budget"
]
)
}SELECT ?s ?var_budget WHERE {
?s a <http://dbpedia.org/ontology/Film> .
?s <http://dbpedia.org/property/writer> ?var_step_0_writer .
?var_step_0_writer <http://dbpedia.org/property/deathDate> ?var_step_1_deathDate .
OPTIONAL { ?s <http://dbpedia.org/ontology/budget> ?var_budget . }
}[
{
"film": "http://dbpedia.org/resource/Cabin_in_the_Sky_(film)",
"budget": "679000.0"
},
...
]Permette di eseguire aggregazioni SPARQL (COUNT, SUM, AVG, ...) su una proprietà, raggruppando per un’altra.
className(opzionale) – Se presente, filtra le entità RDF da aggregare (es. solo entità di tipodbo:Film)groupBy(obbligatorio) – URI della proprietà su cui eseguire il raggruppamento (es.dbo:country,rdf:type)aggregation(obbligatorio) – Oggetto che definisce il tipo di aggregazione:function: Uno traCOUNT,SUM,AVG,MIN,MAXon: URI della proprietà da aggregare (oppure"*"perCOUNT(*))alias: Nome della variabile risultante (es.filmCount,totalBudget)
having(opzionale) – Condizioni da applicare al risultato aggregato (es.SUM(?budget) > 1000000)orderBy(opzionale) – Array di oggetti{ property, order }dove:property: nome della variabile aggregata (es."filmCount")order:"ASC"o"DESC"
limit,offset(opzionali) – Controllano paginazione e offset nei risultati
query {
aggregateEntities(
className: "http://dbpedia.org/ontology/Film"
groupBy: "http://dbpedia.org/ontology/country"
aggregation: {
function: "COUNT"
alias: "filmCount"
on: "*"
}
orderBy: [{ property: "filmCount", order: "DESC" }]
limit: 10
)
}SELECT ?country (COUNT(*) AS ?filmCount) WHERE {
?s a <http://dbpedia.org/ontology/Film> .
?s <http://dbpedia.org/ontology/country> ?country .
}
GROUP BY ?country
ORDER BY DESC(?filmCount)
LIMIT 10[
{
"country": "http://dbpedia.org/resource/India",
"filmCount": "5915"
},
{
"country": "http://dbpedia.org/resource/United_States",
"filmCount": "1834"
},
{
"country": "http://dbpedia.org/resource/Japan",
"filmCount": "759"
},
...
]Confronta entità RDF multiple (es. due film o due persone) che condividono uno o più percorsi/proprietà. Permette di specificare condizioni tra le entità (es. disuguaglianze), confronti tra attori, registi o altre entità collegate.
subjects: array di entità, ognuna conclassName: URI della classe RDFalias: nome personalizzato da usare nelle query
paths: array di oggetti che descrivono i percorsi tra le entitàfrom: alias della risorsa di partenzapath: array di proprietà RDF (es.["dbo:starring"])alias: variabile SPARQL risultante (es.actor1)
filters: array di condizioni tra le entità, ad esempio:?film1 != ?film2?actor1 != ?actor2
selectVars: variabili da includere nella clausolaSELECT(es.["?film1", "?film2"])limit,offset(opzionali) – Controllano paginazione e offset nei risultati
query {
compareEntities(
subjects: [
{ alias: "film1", className: "http://dbpedia.org/ontology/Film" }
{ alias: "film2", className: "http://dbpedia.org/ontology/Film" }
]
paths: [
{ from: "film1", path: ["http://dbpedia.org/ontology/starring"], alias: "actor1" }
{ from: "film2", path: ["http://dbpedia.org/ontology/starring"], alias: "actor2" }
]
filters: [
"?film1 != ?film2",
"?actor1 != ?actor2"
]
selectVars: ["?film1", "?film2"]
limit: 10
)
}SELECT ?film1 ?film2 WHERE {
?film1 a <http://dbpedia.org/ontology/Film> .
?film2 a <http://dbpedia.org/ontology/Film> .
?film1 <http://dbpedia.org/ontology/starring> ?actor1 .
?film2 <http://dbpedia.org/ontology/starring> ?actor2 .
FILTER (?film1 != ?film2)
FILTER (?actor1 != ?actor2)
}[
{
"film1": "http://dbpedia.org/resource/Cab_Calloway's_Jitterbug_Party",
"film2": "http://dbpedia.org/resource/Ca-bau-kan"
},
{
"film1": "http://dbpedia.org/resource/Cab_Number_13",
"film2": "http://dbpedia.org/resource/Ca-bau-kan"
},
...
]
ℹ️ Nota: Per una raccolta di query di esempio (sia GraphQL che SPARQL), consulta il file Sample_Query.pdf.
Il sistema effettua una trasformazione automatica tra query GraphQL e SPARQL secondo regole predefinite.
Di seguito una tabella di corrispondenza tra i costrutti GraphQL supportati e i costrutti SPARQL generati.
| Costrutto GraphQL | Equivalente SPARQL |
|---|---|
className |
?s a <classURI> |
selectFields |
?s <property> ?var per ciascuna proprietà |
filters.path |
Catena di triple con variabili intermedie |
filters.value |
FILTER (?var = "valore") |
optionalFilters |
OPTIONAL { ?s <property> ?var } |
notExistsFilters |
FILTER NOT EXISTS { ... } |
existsFilters |
FILTER EXISTS { ... } |
distinct: true |
SELECT DISTINCT |
limit, offset |
LIMIT n, OFFSET m |
orderBy |
ORDER BY ASC(?var) o DESC(?var) |
groupBy, having |
GROUP BY, HAVING (...) |
aggregation.function |
SUM, COUNT, AVG, MIN, MAX |
compareEntities.subjects |
?alias a <class> |
compareEntities.sharedPaths |
Triple comuni su più entità |
compareEntities.filters |
FILTER (?var1 != ?var2) tra entità |
Tutti i nomi variabili (come ?var_step_0_writer) vengono generati automaticamente a partire dall'URI della proprietà.
Graph2SPARQL consente di collegarsi a qualsiasi SPARQL endpoint dinamicamente, senza modificare il codice Python.
- Apri il file
app/config/sparql_config.yaml - Modifica tutto ciò che è necessario sotto
default:, ad esempio per Wikidata:
default:
endpoint: "https://query.wikidata.org/sparql"
rdf_type_property: "wdt:P31"
prefixes:
wd: "http://www.wikidata.org/entity/"
wdt: "http://www.wikidata.org/prop/direct/"
rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
rdfs: "http://www.w3.org/2000/01/rdf-schema#"
query_syntax:
select_format: "SELECT {modifiers}{select_vars} WHERE {{ {main_pattern} {optional_patterns} {union_blocks} {filter_conditions} }}"
property_format: "{subject} <{property}> {object} ."
optional_format: "OPTIONAL {{ {pattern} }}"
union_format: "{{ {block} }}"
filter_format: "FILTER ({condition})"
order_by: "ORDER BY {order_by_conditions}"
group_by: "GROUP BY {group_by_fields}"
having: "HAVING({having_conditions})"
limit: "LIMIT {limit}"
offset: "OFFSET {offset}"
options:
select_vars: ["?s"]
main_patterns:
- "?s wdt:P31 <{class_uri}> ."
optional_patterns: []
filter_conditions: []
group_by_fields: []
having_conditions: []
order_by_conditions: []
filter_template: 'FILTER ({condition})'
operator_map:
"=": '{var} = {value}'
"!=": '{var} != {value}'
">": '{var} > {value}'
"<": '{var} < {value}'
">=": '{var} >= {value}'
"<=": '{var} <= {value}'
REGEX: 'REGEX({var}, {value}, "i")'
LANG_EQUALS: 'LANG({var}) = {value}'
aggregation_syntax:
select_format: "SELECT {group_var} {aggregations} WHERE {{ {main_pattern} }} GROUP BY {group_var}"
aggregation_format: "({function}({variable}) AS ?{alias})"
group_by: "GROUP BY {group_var}"
having: "HAVING({having_conditions})"
order_by: "ORDER BY {order_by_conditions}"
limit: "LIMIT {limit}"
offset: "OFFSET {offset}"
property_format: "{subject} <{property}> {object} ."
compare_syntax:
select_format: "SELECT {select_vars} WHERE {{ {subject_blocks} {path_blocks} {filter_conditions} }}"
subject_format: "{var} a <{class_uri}> ."
property_format: "{subject} <{property}> {object} ."
filter_format: "FILTER ({condition})"
limit: "LIMIT {limit}"
offset: "OFFSET {offset}"- Avvia il server specificando questo endpoint:
python server.py --endpoint "https://query.wikidata.org/sparql"query {
searchEntity(
className: "http://www.wikidata.org/entity/Q5" # Q5 = Person
selectFields: [
"http://www.wikidata.org/prop/direct/P569" # birth date
]
limit: 5
)
}SELECT ?s ?var_P569 WHERE {
?s wdt:P31 <http://www.wikidata.org/entity/Q5> .
?s <http://www.wikidata.org/prop/direct/P569> ?var_P569 .
}
LIMIT 5query{
aggregateEntities(
className: "http://www.wikidata.org/entity/Q5" # Persone
groupBy: "http://www.wikidata.org/prop/direct/P21" # genere
aggregation: {
function: "COUNT"
alias: "numPeople"
on: "*"
}
limit: 5
)
}SELECT ?P21 (COUNT(*) AS ?numPeople) WHERE {
?s wdt:P31 <http://www.wikidata.org/entity/Q5> .
?s <http://www.wikidata.org/prop/direct/P21> ?P21 .
}
GROUP BY ?P21
LIMIT 5