diff --git a/examples/README.md b/examples/README.md index 8d6b854..5030521 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,496 +1,17 @@ # RDFProxy Examples -To execute the examples, the `examples` dependency group needs to be installed: +## Running examples + +First, install the examples dependency group: ```shell poetry install --with examples ``` -## Basic FastAPI/RDFProxy example - -Run the `basic_fastapi_example` with Poetry by executing: +Then, run `fastapi` on an example file ```shell -poetry run fastapi dev basic_fastapi_example.py +poetry run fastapi dev ``` -### Routes - -- http://localhost:8000/ - - `SPARQLModelAdapter.query()` maps the flat SPARQL result sets to the nested `ComplexModel` model and returns a list of `ComplexModel` instances which are then served over the FastAPI route. - -
- JSON result - - ```json - [ - { - "p":"p value 1", - "q":{ - "a":"a value 1", - "b":{ - "x":1, - "y":2 - } - } - }, - { - "p":"p value 2", - "q":{ - "a":"a value 2", - "b":{ - "x":1, - "y":22 - } - } - }, - { - "p":"p value 3", - "q":{ - "a":"a value 3", - "b":{ - "x":1, - "y":222 - } - } - }, - { - "p":"p value 4", - "q":{ - "a":"a value 4", - "b":{ - "x":2, - "y":3 - } - } - }, - { - "p":"p value 5", - "q":{ - "a":"a value 5", - "b":{ - "x":2, - "y":33 - } - } - }, - { - "p":"p value 6", - "q":{ - "a":"a value 6", - "b":{ - "x":3, - "y":4 - } - } - }, - { - "p":"p value 7", - "q":{ - "a":"a value 7", - "b":{ - "x":4, - "y":5 - } - } - } - ] - ``` -
- -- http://localhost:8000/group/?group_by=x - - `SPARQModelAdapter.query` with the `group_by` parameter groups the result model instances according to a SPARQL binding. In this case results are grouped by the "x" binding. - -
- JSON result - - ```json - { - "1":[ - { - "p":"p value 1", - "q":{ - "a":"a value 1", - "b":{ - "x":1, - "y":2 - } - } - }, - { - "p":"p value 2", - "q":{ - "a":"a value 2", - "b":{ - "x":1, - "y":22 - } - } - }, - { - "p":"p value 3", - "q":{ - "a":"a value 3", - "b":{ - "x":1, - "y":222 - } - } - } - ], - "2":[ - { - "p":"p value 4", - "q":{ - "a":"a value 4", - "b":{ - "x":2, - "y":3 - } - } - }, - { - "p":"p value 5", - "q":{ - "a":"a value 5", - "b":{ - "x":2, - "y":33 - } - } - } - ], - "3":[ - { - "p":"p value 6", - "q":{ - "a":"a value 6", - "b":{ - "x":3, - "y":4 - } - } - } - ], - "4":[ - { - "p":"p value 7", - "q":{ - "a":"a value 7", - "b":{ - "x":4, - "y":5 - } - } - } - ] - } - ``` -
- -- http://localhost:8000/paginate/?page=1&size=2 - - Supplying `SPARQLModelAdapter.query` with arguments for the `page` and `size` parameters returns an `rdfproxy.Page` object (a generic model instance holding the actual results along the `items` field and also pagination metadata). - -
- JSON result - - ```json - { - "items":[ - { - "p":"p value 1", - "q":{ - "a":"a value 1", - "b":{ - "x":1, - "y":2 - } - } - }, - { - "p":"p value 2", - "q":{ - "a":"a value 2", - "b":{ - "x":1, - "y":22 - } - } - } - ], - "page":1, - "size":2, - "total":7, - "pages":4 - } - ``` -
- - -- http://localhost:8000/grouped_paginate/?page=1&size=2&group_by=x - - `SPARQModelAdapter.query` with arguments for `page`/`size` parameters as well as the `group_by` parameter returns an `rdfproxy.Page` object where the `items` field points to a model grouping. - -
- JSON result - - ```json - { - "items":{ - "2":[ - { - "p":"p value 4", - "q":{ - "a":"a value 4", - "b":{ - "x":2, - "y":3 - } - } - }, - { - "p":"p value 5", - "q":{ - "a":"a value 5", - "b":{ - "x":2, - "y":33 - } - } - } - ], - "1":[ - { - "p":"p value 1", - "q":{ - "a":"a value 1", - "b":{ - "x":1, - "y":2 - } - } - }, - { - "p":"p value 2", - "q":{ - "a":"a value 2", - "b":{ - "x":1, - "y":22 - } - } - }, - { - "p":"p value 3", - "q":{ - "a":"a value 3", - "b":{ - "x":1, - "y":222 - } - } - } - ] - }, - "page":1, - "size":2, - "total":4, - "pages":2 - } - ``` -
- - - - -## Basic Wikidata example - -This example shows how to map SPARQL query result rows to a simpel Pydantic model and serve the results over FastAPI routes. - -The example query - -```sparql -SELECT ?name ?title -WHERE { - wd:Q44336 wdt:P1559 ?name . - wd:Q44336 wdt:P800 ?work . - ?work wdt:P1476 ?title . - SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } -} -``` - -returns the following result rows - -| name | title | -|-----------------|----------------| -| Thomas Bernhard | Der Untergeher | -| Thomas Bernhard | Auslöschung | -| Thomas Bernhard | Korrektur | -| Thomas Bernhard | Holzfällen | -| Thomas Bernhard | Heldenplatz | - -which shall be mapped to the following simple model: - -```python -from rdfproxy import SPARQLBinding -from pydantic import BaseModel - - -class Work(BaseModel): - name: Annotated[str, SPARQLBinding("title")] - -class Person(BaseModel): - name: str - work: Work -``` - -### Routes - -- http://localhost:8000/ - - ```json - [ - { - "name":"Thomas Bernhard", - "work":{ - "name":"Der Untergeher" - } - }, - { - "name":"Thomas Bernhard", - "work":{ - "name":"Auslöschung" - } - }, - { - "name":"Thomas Bernhard", - "work":{ - "name":"Korrektur" - } - }, - { - "name":"Thomas Bernhard", - "work":{ - "name":"Holzfällen" - } - }, - { - "name":"Thomas Bernhard", - "work":{ - "name":"Heldenplatz" - } - } - ] - ``` - -- http://localhost:8000/paginate/?page=1&size=2 - - ```json - { - "items":[ - { - "name":"Thomas Bernhard", - "work":{ - "name":"Der Untergeher" - } - }, - { - "name":"Thomas Bernhard", - "work":{ - "name":"Auslöschung" - } - } - ], - "page":1, - "size":2, - "total":5, - "pages":3 - } - ``` - -- http://localhost:8000/group/?group_by=name - - ```json - { - "Thomas Bernhard":[ - { - "name":"Thomas Bernhard", - "work":{ - "name":"Der Untergeher" - } - }, - { - "name":"Thomas Bernhard", - "work":{ - "name":"Auslöschung" - } - }, - { - "name":"Thomas Bernhard", - "work":{ - "name":"Korrektur" - } - }, - { - "name":"Thomas Bernhard", - "work":{ - "name":"Holzfällen" - } - }, - { - "name":"Thomas Bernhard", - "work":{ - "name":"Heldenplatz" - } - } - ] - } - ``` - -- http://localhost:8000/grouped_paginate/?page=1&size=2&group_by=name - - ```json - { - "items":{ - "Thomas Bernhard":[ - { - "name":"Thomas Bernhard", - "work":{ - "name":"Der Untergeher" - } - }, - { - "name":"Thomas Bernhard", - "work":{ - "name":"Auslöschung" - } - }, - { - "name":"Thomas Bernhard", - "work":{ - "name":"Korrektur" - } - }, - { - "name":"Thomas Bernhard", - "work":{ - "name":"Holzfällen" - } - }, - { - "name":"Thomas Bernhard", - "work":{ - "name":"Heldenplatz" - } - } - ] - }, - "page":1, - "size":2, - "total":1, - "pages":1 - } - ``` +This will run the FastAPI application on `http://127.0.0.1:8000`. diff --git a/examples/basic_fastapi_example.py b/examples/basic_fastapi_example.py deleted file mode 100644 index f2b17ae..0000000 --- a/examples/basic_fastapi_example.py +++ /dev/null @@ -1,66 +0,0 @@ -"""Basic example for using rdfproxy.SPARQLModelAdapter with FastAPI.""" - -from fastapi import FastAPI -from pydantic import BaseModel -from rdfproxy import Page, SPARQLModelAdapter - - -class SimpleModel(BaseModel): - x: int - y: int - - -class NestedModel(BaseModel): - a: str - b: SimpleModel - - -class ComplexModel(BaseModel): - p: str - q: NestedModel - - -query = """ -select ?x ?y ?a ?p -where { - values (?x ?y ?a ?p) { - (1 2 "a value 1" "p value 1") - (1 22 "a value 2" "p value 2") - (1 222 "a value 3" "p value 3") - (2 3 "a value 4" "p value 4") - (2 33 "a value 5" "p value 5") - (3 4 "a value 6" "p value 6") - (4 5 "a value 7" "p value 7") - } -} -""" - - -adapter = SPARQLModelAdapter( - target="https://query.wikidata.org/bigdata/namespace/wdq/sparql", - query=query, - model=ComplexModel, -) - - -app = FastAPI() - - -@app.get("/") -def base() -> list[ComplexModel]: - return adapter.query() - - -@app.get("/group/") -def group(group_by: str) -> dict[str, list[ComplexModel]]: - return adapter.query(group_by=group_by) - - -@app.get("/paginate/") -def paginate(page: int, size: int) -> Page[ComplexModel]: - return adapter.query(page=page, size=size) - - -@app.get("/grouped_paginate/") -def grouped_paginate(page: int, size: int, group_by: str) -> Page[ComplexModel]: - return adapter.query(page=page, size=size, group_by=group_by) diff --git a/examples/full_static_fastapi_example.py b/examples/full_static_fastapi_example.py new file mode 100644 index 0000000..4d3b8b4 --- /dev/null +++ b/examples/full_static_fastapi_example.py @@ -0,0 +1,52 @@ +"""RDFProxy-based FastAPI route example: Static query targeting Wikidata with grouped and nested models.""" + +from typing import Annotated + +from fastapi import FastAPI +from pydantic import BaseModel, ConfigDict +from rdfproxy import Page, SPARQLBinding, SPARQLModelAdapter + + +query = """ +select * +where { + values (?gnd ?authorName ?educatedAt ?workName ?work ?viaf) { + (119359464 'Schindel' UNDEF 'Gebürtig' UNDEF) + (115612815 'Geiger' 'University of Vienna' 'Der alte König in seinem Exil' 299260555) + (115612815 'Geiger' 'University of Vienna' 'Der alte König in seinem Exil' 6762154387354230970008) + (115612815 'Geiger' 'University of Vienna' 'Unter der Drachenwand' 2277151717053313900002) + (1136992030 'Edelbauer' 'University of Vienna' 'Das flüssige Land' UNDEF) + (1136992030 'Edelbauer' 'University of Applied Arts Vienna' 'Das flüssige Land' UNDEF) + } +} +""" + + +class Work(BaseModel): + model_config = ConfigDict(group_by="workName") + + name: Annotated[str, SPARQLBinding("workName")] + viafs: Annotated[list[str], SPARQLBinding("viaf")] + + +class Author(BaseModel): + model_config = ConfigDict(group_by="authorName") + + gnd: str + surname: Annotated[str, SPARQLBinding("authorName")] + works: list[Work] + education: Annotated[list[str], SPARQLBinding("educatedAt")] + + +adapter = SPARQLModelAdapter( + target="https://query.wikidata.org/bigdata/namespace/wdq/sparql", + query=query, + model=Author, +) + +app = FastAPI() + + +@app.get("/") +def base_route(page: int = 1, size: int = 100) -> Page[Author]: + return adapter.query(page=page, size=size) diff --git a/examples/releven_person_fastapi_example.py b/examples/releven_person_fastapi_example.py index ef8f8a3..80f61e6 100644 --- a/examples/releven_person_fastapi_example.py +++ b/examples/releven_person_fastapi_example.py @@ -1,3 +1,5 @@ +"""RDFProxy-based FastAPI route example: CRM query targeting Releven GraphDB with simple ungrouped Person model.""" + from fastapi import FastAPI from pydantic import BaseModel from rdfproxy import Page, SPARQLModelAdapter @@ -13,8 +15,8 @@ ?person a crm:E21_Person . { - # id - ?person ^crm:P140_assigned_attribute_to ?e13_id . + # id + ?person ^crm:P140_assigned_attribute_to ?e13_id . ?e13_id a crm:E15_Identifier_Assignment ; crm:P37_assigned [ a crm:E42_Identifier ; @@ -25,7 +27,7 @@ } { - # appellation + # appellation ?person ^crm:P140_assigned_attribute_to ?e13_appellation . ?e13_appellation a star:E13_crm_P1 ; @@ -37,12 +39,13 @@ { # note - ?person ^crm:P140_assigned_attribute_to ?e13_note . + ?person ^crm:P140_assigned_attribute_to ?e13_note . ?e13_note a star:E13_crm_P3 ; crm:P141_assigned ?note . } } + """ @@ -63,20 +66,5 @@ class R11PersonModel(BaseModel): @app.get("/") -def base() -> list[R11PersonModel]: - return adapter.query() - - -@app.get("/group/") -def group(group_by: str) -> dict[str, list[R11PersonModel]]: - return adapter.query(group_by=group_by) - - -@app.get("/paginate/") -def paginate(page: int, size: int) -> Page[R11PersonModel]: +def base(page=1, size=100) -> Page[R11PersonModel]: return adapter.query(page=page, size=size) - - -@app.get("/grouped_paginate/") -def grouped_paginate(page: int, size: int, group_by: str) -> Page[R11PersonModel]: - return adapter.query(page=page, size=size, group_by=group_by) diff --git a/examples/wikidata_grouped_person_fastapi_example.py b/examples/wikidata_grouped_person_fastapi_example.py new file mode 100644 index 0000000..c6a8868 --- /dev/null +++ b/examples/wikidata_grouped_person_fastapi_example.py @@ -0,0 +1,44 @@ +"""RDFProxy-based FastAPI route example: Wikidata query with simple grouped model.""" + +from typing import Annotated + +from fastapi import FastAPI +from pydantic import BaseModel, ConfigDict +from rdfproxy import Page, SPARQLBinding, SPARQLModelAdapter + + +query = """ +SELECT ?name ?title +WHERE { + wd:Q44336 wdt:P1559 ?name . + wd:Q44336 wdt:P800 ?work . + ?work wdt:P1476 ?title . + SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } +} +""" + + +class Work(BaseModel): + name: Annotated[str, SPARQLBinding("title")] + + +class Person(BaseModel): + model_config = ConfigDict(group_by="name") + + name: str + work: list[Work] + + +adapter = SPARQLModelAdapter( + target="https://query.wikidata.org/bigdata/namespace/wdq/sparql", + query=query, + model=Person, +) + + +app = FastAPI() + + +@app.get("/") +def base(page: int = 1, size: int = 100) -> Page[Person]: + return adapter.query(page=page, size=size) diff --git a/examples/wikidata_person_fastapi_example.py b/examples/wikidata_ungrouped_person_fastapi_example.py similarity index 58% rename from examples/wikidata_person_fastapi_example.py rename to examples/wikidata_ungrouped_person_fastapi_example.py index 0c2b7e4..8387f9d 100644 --- a/examples/wikidata_person_fastapi_example.py +++ b/examples/wikidata_ungrouped_person_fastapi_example.py @@ -1,4 +1,4 @@ -"""rdfproxy.SPARQLModelAdapter + FastAPI example targeting Wikidata.""" +"""RDFProxy-based FastAPI route example: Wikidata query with simple ungrouped model.""" from typing import Annotated @@ -7,15 +7,6 @@ from rdfproxy import Page, SPARQLBinding, SPARQLModelAdapter -class Work(BaseModel): - name: Annotated[str, SPARQLBinding("title")] - - -class Person(BaseModel): - name: str - work: Work - - query = """ SELECT ?name ?title WHERE { @@ -26,6 +17,16 @@ class Person(BaseModel): } """ + +class Work(BaseModel): + name: Annotated[str, SPARQLBinding("title")] + + +class Person(BaseModel): + name: str + work: Work + + adapter = SPARQLModelAdapter( target="https://query.wikidata.org/bigdata/namespace/wdq/sparql", query=query, @@ -37,20 +38,5 @@ class Person(BaseModel): @app.get("/") -def base() -> list[Person]: - return adapter.query() - - -@app.get("/group/") -def group(group_by: str) -> dict[str, list[Person]]: - return adapter.query(group_by=group_by) - - -@app.get("/paginate/") -def paginate(page: int, size: int) -> Page[Person]: +def base(page: int = 1, size: int = 100) -> Page[Person]: return adapter.query(page=page, size=size) - - -@app.get("/grouped_paginate/") -def grouped_paginate(page: int, size: int, group_by: str) -> Page[Person]: - return adapter.query(page=page, size=size, group_by=group_by)