This notebook creates the assertion templates themselves (not the policies). Once published, these templates can be used in Nanodash or programmatically to create ODRL policy and access grant nanopublications.
Two templates are created:
ODRL Access Policy — for data providers to define access conditions
ODRL Access Grant — for recording who was granted access (audit trail)
⚙️ SECTION 1: SETUP¶
from pathlib import Path
from datetime import datetime, timezone
from rdflib import Dataset, Namespace, URIRef, Literal
from rdflib.namespace import RDF, RDFS, XSD, FOAF
NP = Namespace("http://www.nanopub.org/nschema#")
DCT = Namespace("http://purl.org/dc/terms/")
NT = Namespace("https://w3id.org/np/o/ntemplate/")
NPX = Namespace("http://purl.org/nanopub/x/")
PROV = Namespace("http://www.w3.org/ns/prov#")
ORCID = Namespace("https://orcid.org/")
ODRL = Namespace("http://www.w3.org/ns/odrl/2/")
AUTHOR_ORCID = "0000-0002-1784-2920"
AUTHOR_NAME = "Anne Fouilloux"
OUTPUT_DIR = "output/"
Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
print("✓ Setup complete")✓ Setup complete
🔨 SECTION 2: ODRL Access Policy Template¶
def create_odrl_policy_template():
"""Create the ODRL Access Policy assertion template nanopub."""
TEMP_NP = Namespace("http://purl.org/nanopub/temp/np/")
DPV = Namespace("https://w3id.org/dpv#")
this_np = URIRef("http://purl.org/nanopub/temp/np/")
head_graph = URIRef("http://purl.org/nanopub/temp/np/Head")
assertion_graph = URIRef("http://purl.org/nanopub/temp/np/assertion")
provenance_graph = URIRef("http://purl.org/nanopub/temp/np/provenance")
pubinfo_graph = URIRef("http://purl.org/nanopub/temp/np/pubinfo")
author_uri = ORCID[AUTHOR_ORCID]
ds = Dataset()
ds.bind("this", "http://purl.org/nanopub/temp/np/")
ds.bind("sub", TEMP_NP)
ds.bind("np", NP)
ds.bind("dct", DCT)
ds.bind("nt", NT)
ds.bind("npx", NPX)
ds.bind("xsd", XSD)
ds.bind("rdfs", RDFS)
ds.bind("orcid", ORCID)
ds.bind("prov", PROV)
ds.bind("foaf", FOAF)
ds.bind("rdf", RDF)
ds.bind("odrl", ODRL)
ds.bind("dpv", DPV)
# HEAD
head = ds.graph(head_graph)
head.add((this_np, RDF.type, NP.Nanopublication))
head.add((this_np, NP.hasAssertion, assertion_graph))
head.add((this_np, NP.hasProvenance, provenance_graph))
head.add((this_np, NP.hasPublicationInfo, pubinfo_graph))
# ASSERTION — the template definition
a = ds.graph(assertion_graph)
# -- Predicate labels (for Nanodash display) --
a.add((RDF.type, RDFS.label, Literal("is a")))
a.add((ODRL.target, RDFS.label, Literal("applies to dataset")))
a.add((ODRL.action, RDFS.label, Literal("allows/prohibits action")))
a.add((ODRL.permission, RDFS.label, Literal("has permission")))
a.add((ODRL.prohibition, RDFS.label, Literal("has prohibition")))
a.add((ODRL.duty, RDFS.label, Literal("has duty")))
a.add((ODRL.constraint, RDFS.label, Literal("with constraint")))
# -- Template metadata --
a.add((TEMP_NP.assertion, RDF.type, NT.AssertionTemplate))
a.add((TEMP_NP.assertion, RDFS.label, Literal("ODRL Access Policy for FAIR Data")))
a.add((TEMP_NP.assertion, DCT.description, Literal(
"Defines an ODRL policy for controlling access to a FAIR dataset. "
"Specifies permitted actions (with purpose constraints), "
"prohibited actions, and attribution duties. "
"Use this template to publish machine-readable access conditions "
"for private research data."
)))
a.add((TEMP_NP.assertion, NT.hasTag, Literal("FAIR2Adapt")))
a.add((TEMP_NP.assertion, NT.hasTag, Literal("ODRL")))
a.add((TEMP_NP.assertion, NT.hasTag, Literal("Access Control")))
a.add((TEMP_NP.assertion, NT.hasNanopubLabelPattern,
Literal("ODRL policy: ${datasetUri}")))
a.add((TEMP_NP.assertion, NT.hasTargetNanopubType, ODRL.Policy))
# -- Statements the template contains --
a.add((TEMP_NP.assertion, NT.hasStatement, TEMP_NP.st1))
a.add((TEMP_NP.assertion, NT.hasStatement, TEMP_NP.st2))
a.add((TEMP_NP.assertion, NT.hasStatement, TEMP_NP.st3))
a.add((TEMP_NP.assertion, NT.hasStatement, TEMP_NP.st4))
a.add((TEMP_NP.assertion, NT.hasStatement, TEMP_NP.st5))
a.add((TEMP_NP.assertion, NT.hasStatement, TEMP_NP.st6))
# -- st1: policy rdf:type odrl:Offer --
a.add((TEMP_NP.st1, RDF.subject, TEMP_NP.policyUri))
a.add((TEMP_NP.st1, RDF.predicate, RDF.type))
a.add((TEMP_NP.st1, RDF.object, TEMP_NP.policyType))
# -- st2: policy odrl:target dataset --
a.add((TEMP_NP.st2, RDF.subject, TEMP_NP.policyUri))
a.add((TEMP_NP.st2, RDF.predicate, ODRL.target))
a.add((TEMP_NP.st2, RDF.object, TEMP_NP.datasetUri))
# -- st3: policy odrl:permission action (repeatable) --
a.add((TEMP_NP.st3, RDF.type, NT.RepeatableStatement))
a.add((TEMP_NP.st3, RDF.subject, TEMP_NP.policyUri))
a.add((TEMP_NP.st3, RDF.predicate, ODRL.permission))
a.add((TEMP_NP.st3, RDF.object, TEMP_NP.permittedAction))
# -- st4: policy odrl:constraint purposeConstraint (optional) --
a.add((TEMP_NP.st4, RDF.type, NT.OptionalStatement))
a.add((TEMP_NP.st4, RDF.subject, TEMP_NP.policyUri))
a.add((TEMP_NP.st4, RDF.predicate, ODRL.constraint))
a.add((TEMP_NP.st4, RDF.object, TEMP_NP.purposeConstraint))
# -- st5: policy odrl:prohibition action (optional, repeatable) --
a.add((TEMP_NP.st5, RDF.type, NT.OptionalStatement))
a.add((TEMP_NP.st5, RDF.type, NT.RepeatableStatement))
a.add((TEMP_NP.st5, RDF.subject, TEMP_NP.policyUri))
a.add((TEMP_NP.st5, RDF.predicate, ODRL.prohibition))
a.add((TEMP_NP.st5, RDF.object, TEMP_NP.prohibitedAction))
# -- st6: policy odrl:duty attributionParty (optional) --
a.add((TEMP_NP.st6, RDF.type, NT.OptionalStatement))
a.add((TEMP_NP.st6, RDF.subject, TEMP_NP.policyUri))
a.add((TEMP_NP.st6, RDF.predicate, ODRL.duty))
a.add((TEMP_NP.st6, RDF.object, TEMP_NP.attributionParty))
# -- Placeholder: policyUri --
a.add((TEMP_NP.policyUri, RDF.type, NT.UriPlaceholder))
a.add((TEMP_NP.policyUri, RDF.type, NT.IntroducedResource))
a.add((TEMP_NP.policyUri, RDFS.label, Literal("URI of the policy")))
a.add((TEMP_NP.policyUri, NT.hasPrefix, Literal("https://fair2adapt.eu/policy/")))
a.add((TEMP_NP.policyUri, NT.hasPrefixLabel, Literal("fair2adapt-policy")))
# -- Placeholder: policyType (restricted choice) --
a.add((TEMP_NP.policyType, RDF.type, NT.RestrictedChoicePlaceholder))
a.add((TEMP_NP.policyType, RDFS.label, Literal("Type of ODRL policy")))
a.add((TEMP_NP.policyType, NT.possibleValue, ODRL.Offer))
a.add((TEMP_NP.policyType, NT.possibleValue, ODRL.Set))
a.add((ODRL.Offer, RDFS.label, Literal("Offer - data available under conditions")))
a.add((ODRL.Set, RDFS.label, Literal("Set - general policy statement")))
# -- Placeholder: datasetUri --
a.add((TEMP_NP.datasetUri, RDF.type, NT.UriPlaceholder))
a.add((TEMP_NP.datasetUri, RDFS.label, Literal("URI of the dataset this policy applies to")))
a.add((TEMP_NP.datasetUri, NT.hasPrefix, Literal("https://fair2adapt.eu/data/")))
a.add((TEMP_NP.datasetUri, NT.hasPrefixLabel, Literal("fair2adapt-data")))
# -- Placeholder: permittedAction (restricted choice) --
a.add((TEMP_NP.permittedAction, RDF.type, NT.RestrictedChoicePlaceholder))
a.add((TEMP_NP.permittedAction, RDFS.label, Literal("Permitted action")))
a.add((TEMP_NP.permittedAction, NT.possibleValue, ODRL.use))
a.add((TEMP_NP.permittedAction, NT.possibleValue, ODRL.reproduce))
a.add((TEMP_NP.permittedAction, NT.possibleValue, ODRL.distribute))
a.add((TEMP_NP.permittedAction, NT.possibleValue, ODRL.derive))
a.add((TEMP_NP.permittedAction, NT.possibleValue, ODRL.present))
a.add((TEMP_NP.permittedAction, NT.possibleValue, ODRL.modify))
a.add((ODRL.use, RDFS.label, Literal("Use")))
a.add((ODRL.reproduce, RDFS.label, Literal("Reproduce")))
a.add((ODRL.distribute, RDFS.label, Literal("Distribute")))
a.add((ODRL.derive, RDFS.label, Literal("Derive")))
a.add((ODRL.present, RDFS.label, Literal("Present")))
a.add((ODRL.modify, RDFS.label, Literal("Modify")))
# -- Placeholder: purposeConstraint (restricted choice, DPV vocabulary) --
a.add((TEMP_NP.purposeConstraint, RDF.type, NT.RestrictedChoicePlaceholder))
a.add((TEMP_NP.purposeConstraint, RDFS.label, Literal("Required purpose for access")))
a.add((TEMP_NP.purposeConstraint, NT.possibleValue, DPV.AcademicResearch))
a.add((TEMP_NP.purposeConstraint, NT.possibleValue, DPV.ScientificResearch))
a.add((TEMP_NP.purposeConstraint, NT.possibleValue, DPV.NonCommercialResearch))
a.add((TEMP_NP.purposeConstraint, NT.possibleValue, DPV.PublicBenefit))
a.add((DPV.AcademicResearch, RDFS.label, Literal("Academic Research")))
a.add((DPV.ScientificResearch, RDFS.label, Literal("Scientific Research")))
a.add((DPV.NonCommercialResearch, RDFS.label, Literal("Non-Commercial Research")))
a.add((DPV.PublicBenefit, RDFS.label, Literal("Public Benefit")))
# -- Placeholder: prohibitedAction (restricted choice) --
a.add((TEMP_NP.prohibitedAction, RDF.type, NT.RestrictedChoicePlaceholder))
a.add((TEMP_NP.prohibitedAction, RDFS.label, Literal("Prohibited action")))
a.add((TEMP_NP.prohibitedAction, NT.possibleValue, ODRL.distribute))
a.add((TEMP_NP.prohibitedAction, NT.possibleValue, ODRL.commercialize))
a.add((TEMP_NP.prohibitedAction, NT.possibleValue, ODRL.sell))
a.add((TEMP_NP.prohibitedAction, NT.possibleValue, ODRL.modify))
a.add((ODRL.commercialize, RDFS.label, Literal("Commercialize")))
a.add((ODRL.sell, RDFS.label, Literal("Sell")))
# -- Placeholder: attributionParty --
a.add((TEMP_NP.attributionParty, RDF.type, NT.UriPlaceholder))
a.add((TEMP_NP.attributionParty, RDFS.label,
Literal("URI of party to attribute (e.g. https://fair2adapt-eosc.eu)")))
# PROVENANCE
provenance = ds.graph(provenance_graph)
provenance.add((assertion_graph, PROV.wasAttributedTo, author_uri))
# PUBINFO
pubinfo = ds.graph(pubinfo_graph)
pubinfo.add((author_uri, FOAF.name, Literal(AUTHOR_NAME)))
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")
pubinfo.add((this_np, DCT.created, Literal(now, datatype=XSD.dateTime)))
pubinfo.add((this_np, DCT.creator, author_uri))
pubinfo.add((this_np, DCT.license, URIRef("https://creativecommons.org/licenses/by/4.0/")))
pubinfo.add((this_np, NPX.wasCreatedAt, URIRef("https://fair2adapt-eosc.eu")))
pubinfo.add((this_np, RDFS.label, Literal("Assertion template: ODRL Access Policy for FAIR Data")))
pubinfo.add((this_np, NPX.hasNanopubType, NT.AssertionTemplate))
return ds
ds_policy = create_odrl_policy_template()
policy_template_file = Path(OUTPUT_DIR) / "odrl_policy_template.trig"
ds_policy.serialize(destination=str(policy_template_file), format='trig')
print(f"✓ Generated: {policy_template_file}")✓ Generated: output/odrl_policy_template.trig
# Preview
print("=" * 80)
with open(policy_template_file, 'r') as f:
print(f.read())================================================================================
@prefix dct: <http://purl.org/dc/terms/> .
@prefix dpv: <https://w3id.org/dpv#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix np: <http://www.nanopub.org/nschema#> .
@prefix npx: <http://purl.org/nanopub/x/> .
@prefix nt: <https://w3id.org/np/o/ntemplate/> .
@prefix odrl: <http://www.w3.org/ns/odrl/2/> .
@prefix orcid: <https://orcid.org/> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sub: <http://purl.org/nanopub/temp/np/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
sub:Head {
sub: a np:Nanopublication ;
np:hasAssertion sub:assertion ;
np:hasProvenance sub:provenance ;
np:hasPublicationInfo sub:pubinfo .
}
sub:assertion {
odrl:action rdfs:label "allows/prohibits action" .
sub:assertion a nt:AssertionTemplate ;
rdfs:label "ODRL Access Policy for FAIR Data" ;
dct:description "Defines an ODRL policy for controlling access to a FAIR dataset. Specifies permitted actions (with purpose constraints), prohibited actions, and attribution duties. Use this template to publish machine-readable access conditions for private research data." ;
nt:hasNanopubLabelPattern "ODRL policy: ${datasetUri}" ;
nt:hasStatement sub:st1,
sub:st2,
sub:st3,
sub:st4,
sub:st5,
sub:st6 ;
nt:hasTag "Access Control",
"FAIR2Adapt",
"ODRL" ;
nt:hasTargetNanopubType odrl:Policy .
sub:attributionParty a nt:UriPlaceholder ;
rdfs:label "URI of party to attribute (e.g. https://fair2adapt-eosc.eu)" .
sub:datasetUri a nt:UriPlaceholder ;
rdfs:label "URI of the dataset this policy applies to" ;
nt:hasPrefix "https://fair2adapt.eu/data/" ;
nt:hasPrefixLabel "fair2adapt-data" .
sub:permittedAction a nt:RestrictedChoicePlaceholder ;
rdfs:label "Permitted action" ;
nt:possibleValue odrl:derive,
odrl:distribute,
odrl:modify,
odrl:present,
odrl:reproduce,
odrl:use .
sub:policyType a nt:RestrictedChoicePlaceholder ;
rdfs:label "Type of ODRL policy" ;
nt:possibleValue odrl:Offer,
odrl:Set .
sub:prohibitedAction a nt:RestrictedChoicePlaceholder ;
rdfs:label "Prohibited action" ;
nt:possibleValue odrl:commercialize,
odrl:distribute,
odrl:modify,
odrl:sell .
sub:purposeConstraint a nt:RestrictedChoicePlaceholder ;
rdfs:label "Required purpose for access" ;
nt:possibleValue dpv:AcademicResearch,
dpv:NonCommercialResearch,
dpv:PublicBenefit,
dpv:ScientificResearch .
sub:st1 rdf:object sub:policyType ;
rdf:predicate rdf:type ;
rdf:subject sub:policyUri .
sub:st2 rdf:object sub:datasetUri ;
rdf:predicate odrl:target ;
rdf:subject sub:policyUri .
sub:st3 a nt:RepeatableStatement ;
rdf:object sub:permittedAction ;
rdf:predicate odrl:permission ;
rdf:subject sub:policyUri .
sub:st4 a nt:OptionalStatement ;
rdf:object sub:purposeConstraint ;
rdf:predicate odrl:constraint ;
rdf:subject sub:policyUri .
sub:st5 a nt:OptionalStatement,
nt:RepeatableStatement ;
rdf:object sub:prohibitedAction ;
rdf:predicate odrl:prohibition ;
rdf:subject sub:policyUri .
sub:st6 a nt:OptionalStatement ;
rdf:object sub:attributionParty ;
rdf:predicate odrl:duty ;
rdf:subject sub:policyUri .
rdf:type rdfs:label "is a" .
odrl:Offer rdfs:label "Offer - data available under conditions" .
odrl:Set rdfs:label "Set - general policy statement" .
odrl:commercialize rdfs:label "Commercialize" .
odrl:constraint rdfs:label "with constraint" .
odrl:derive rdfs:label "Derive" .
odrl:duty rdfs:label "has duty" .
odrl:permission rdfs:label "has permission" .
odrl:present rdfs:label "Present" .
odrl:prohibition rdfs:label "has prohibition" .
odrl:reproduce rdfs:label "Reproduce" .
odrl:sell rdfs:label "Sell" .
odrl:target rdfs:label "applies to dataset" .
odrl:use rdfs:label "Use" .
dpv:AcademicResearch rdfs:label "Academic Research" .
dpv:NonCommercialResearch rdfs:label "Non-Commercial Research" .
dpv:PublicBenefit rdfs:label "Public Benefit" .
dpv:ScientificResearch rdfs:label "Scientific Research" .
odrl:distribute rdfs:label "Distribute" .
odrl:modify rdfs:label "Modify" .
sub:policyUri a nt:IntroducedResource,
nt:UriPlaceholder ;
rdfs:label "URI of the policy" ;
nt:hasPrefix "https://fair2adapt.eu/policy/" ;
nt:hasPrefixLabel "fair2adapt-policy" .
}
sub:provenance {
sub:assertion prov:wasAttributedTo orcid:0000-0002-1784-2920 .
}
sub:pubinfo {
sub: rdfs:label "Assertion template: ODRL Access Policy for FAIR Data" ;
dct:created "2026-03-29T12:27:15+00:00"^^xsd:dateTime ;
dct:creator orcid:0000-0002-1784-2920 ;
dct:license <https://creativecommons.org/licenses/by/4.0/> ;
npx:hasNanopubType nt:AssertionTemplate ;
npx:wasCreatedAt <https://fair2adapt-eosc.eu> .
orcid:0000-0002-1784-2920 foaf:name "Anne Fouilloux" .
}
🔨 SECTION 3: ODRL Access Grant Template¶
def create_odrl_grant_template():
"""Create the ODRL Access Grant assertion template nanopub."""
TEMP_NP = Namespace("http://purl.org/nanopub/temp/np/")
FAIR = Namespace("https://fair2adapt.eu/ns/")
this_np = URIRef("http://purl.org/nanopub/temp/np/")
head_graph = URIRef("http://purl.org/nanopub/temp/np/Head")
assertion_graph = URIRef("http://purl.org/nanopub/temp/np/assertion")
provenance_graph = URIRef("http://purl.org/nanopub/temp/np/provenance")
pubinfo_graph = URIRef("http://purl.org/nanopub/temp/np/pubinfo")
author_uri = ORCID[AUTHOR_ORCID]
ds = Dataset()
ds.bind("this", "http://purl.org/nanopub/temp/np/")
ds.bind("sub", TEMP_NP)
ds.bind("np", NP)
ds.bind("dct", DCT)
ds.bind("nt", NT)
ds.bind("npx", NPX)
ds.bind("xsd", XSD)
ds.bind("rdfs", RDFS)
ds.bind("orcid", ORCID)
ds.bind("prov", PROV)
ds.bind("foaf", FOAF)
ds.bind("rdf", RDF)
ds.bind("odrl", ODRL)
ds.bind("fair", FAIR)
# HEAD
head = ds.graph(head_graph)
head.add((this_np, RDF.type, NP.Nanopublication))
head.add((this_np, NP.hasAssertion, assertion_graph))
head.add((this_np, NP.hasProvenance, provenance_graph))
head.add((this_np, NP.hasPublicationInfo, pubinfo_graph))
# ASSERTION
a = ds.graph(assertion_graph)
# Predicate labels
a.add((RDF.type, RDFS.label, Literal("is a")))
a.add((ODRL.target, RDFS.label, Literal("grants access to dataset")))
a.add((ODRL.assignee, RDFS.label, Literal("is granted to")))
a.add((ODRL.action, RDFS.label, Literal("for action")))
a.add((FAIR.underPolicy, RDFS.label, Literal("under ODRL policy")))
a.add((PROV.generatedAtTime, RDFS.label, Literal("granted at time")))
# Template metadata
a.add((TEMP_NP.assertion, RDF.type, NT.AssertionTemplate))
a.add((TEMP_NP.assertion, RDFS.label, Literal("ODRL Access Grant for FAIR Data")))
a.add((TEMP_NP.assertion, DCT.description, Literal(
"Records that a specific DID was granted access to a dataset "
"under an ODRL policy. Serves as an immutable audit trail "
"for data access in the FAIR2Adapt project."
)))
a.add((TEMP_NP.assertion, NT.hasTag, Literal("FAIR2Adapt")))
a.add((TEMP_NP.assertion, NT.hasTag, Literal("ODRL")))
a.add((TEMP_NP.assertion, NT.hasTag, Literal("Access Grant")))
a.add((TEMP_NP.assertion, NT.hasNanopubLabelPattern,
Literal("Access grant: ${datasetUri} → ${assigneeDid}")))
a.add((TEMP_NP.assertion, NT.hasTargetNanopubType, ODRL.Agreement))
# Statements
a.add((TEMP_NP.assertion, NT.hasStatement, TEMP_NP.st1))
a.add((TEMP_NP.assertion, NT.hasStatement, TEMP_NP.st2))
a.add((TEMP_NP.assertion, NT.hasStatement, TEMP_NP.st3))
a.add((TEMP_NP.assertion, NT.hasStatement, TEMP_NP.st4))
a.add((TEMP_NP.assertion, NT.hasStatement, TEMP_NP.st5))
a.add((TEMP_NP.assertion, NT.hasStatement, TEMP_NP.st6))
# st1: grant rdf:type odrl:Agreement
a.add((TEMP_NP.st1, RDF.subject, TEMP_NP.grantUri))
a.add((TEMP_NP.st1, RDF.predicate, RDF.type))
a.add((TEMP_NP.st1, RDF.object, ODRL.Agreement))
# st2: grant odrl:target dataset
a.add((TEMP_NP.st2, RDF.subject, TEMP_NP.grantUri))
a.add((TEMP_NP.st2, RDF.predicate, ODRL.target))
a.add((TEMP_NP.st2, RDF.object, TEMP_NP.datasetUri))
# st3: grant odrl:assignee did
a.add((TEMP_NP.st3, RDF.subject, TEMP_NP.grantUri))
a.add((TEMP_NP.st3, RDF.predicate, ODRL.assignee))
a.add((TEMP_NP.st3, RDF.object, TEMP_NP.assigneeDid))
# st4: grant odrl:action (repeatable)
a.add((TEMP_NP.st4, RDF.type, NT.RepeatableStatement))
a.add((TEMP_NP.st4, RDF.subject, TEMP_NP.grantUri))
a.add((TEMP_NP.st4, RDF.predicate, ODRL.action))
a.add((TEMP_NP.st4, RDF.object, TEMP_NP.grantedAction))
# st5: grant fair:underPolicy policyNanopub
a.add((TEMP_NP.st5, RDF.subject, TEMP_NP.grantUri))
a.add((TEMP_NP.st5, RDF.predicate, FAIR.underPolicy))
a.add((TEMP_NP.st5, RDF.object, TEMP_NP.policyNanopubUri))
# st6: grant prov:generatedAtTime timestamp
a.add((TEMP_NP.st6, RDF.subject, TEMP_NP.grantUri))
a.add((TEMP_NP.st6, RDF.predicate, PROV.generatedAtTime))
a.add((TEMP_NP.st6, RDF.object, TEMP_NP.grantTimestamp))
# Placeholder: grantUri
a.add((TEMP_NP.grantUri, RDF.type, NT.UriPlaceholder))
a.add((TEMP_NP.grantUri, RDF.type, NT.IntroducedResource))
a.add((TEMP_NP.grantUri, RDFS.label, Literal("Grant identifier")))
# Placeholder: datasetUri
a.add((TEMP_NP.datasetUri, RDF.type, NT.UriPlaceholder))
a.add((TEMP_NP.datasetUri, RDFS.label, Literal("URI of the dataset")))
a.add((TEMP_NP.datasetUri, NT.hasPrefix, Literal("https://fair2adapt.eu/data/")))
a.add((TEMP_NP.datasetUri, NT.hasPrefixLabel, Literal("fair2adapt-data")))
# Placeholder: assigneeDid
a.add((TEMP_NP.assigneeDid, RDF.type, NT.UriPlaceholder))
a.add((TEMP_NP.assigneeDid, RDFS.label,
Literal("DID of the requester (e.g. did:web:researcher.example.org)")))
# Placeholder: grantedAction (restricted choice)
a.add((TEMP_NP.grantedAction, RDF.type, NT.RestrictedChoicePlaceholder))
a.add((TEMP_NP.grantedAction, RDFS.label, Literal("Granted action")))
a.add((TEMP_NP.grantedAction, NT.possibleValue, ODRL.use))
a.add((TEMP_NP.grantedAction, NT.possibleValue, ODRL.reproduce))
a.add((TEMP_NP.grantedAction, NT.possibleValue, ODRL.distribute))
# Placeholder: policyNanopubUri
a.add((TEMP_NP.policyNanopubUri, RDF.type, NT.UriPlaceholder))
a.add((TEMP_NP.policyNanopubUri, RDFS.label,
Literal("Nanopub URI of the ODRL policy this grant is under")))
a.add((TEMP_NP.policyNanopubUri, NT.hasPrefix, Literal("https://w3id.org/np/")))
a.add((TEMP_NP.policyNanopubUri, NT.hasPrefixLabel, Literal("nanopub")))
# Placeholder: grantTimestamp (literal)
a.add((TEMP_NP.grantTimestamp, RDF.type, NT.LiteralPlaceholder))
a.add((TEMP_NP.grantTimestamp, RDFS.label, Literal("Timestamp of access grant")))
a.add((TEMP_NP.grantTimestamp, NT.hasDatatype, XSD.dateTime))
# PROVENANCE
provenance = ds.graph(provenance_graph)
provenance.add((assertion_graph, PROV.wasAttributedTo, author_uri))
# PUBINFO
pubinfo = ds.graph(pubinfo_graph)
pubinfo.add((author_uri, FOAF.name, Literal(AUTHOR_NAME)))
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")
pubinfo.add((this_np, DCT.created, Literal(now, datatype=XSD.dateTime)))
pubinfo.add((this_np, DCT.creator, author_uri))
pubinfo.add((this_np, DCT.license, URIRef("https://creativecommons.org/licenses/by/4.0/")))
pubinfo.add((this_np, NPX.wasCreatedAt, URIRef("https://fair2adapt-eosc.eu")))
pubinfo.add((this_np, RDFS.label, Literal("Assertion template: ODRL Access Grant for FAIR Data")))
pubinfo.add((this_np, NPX.hasNanopubType, NT.AssertionTemplate))
return ds
ds_grant = create_odrl_grant_template()
grant_template_file = Path(OUTPUT_DIR) / "odrl_grant_template.trig"
ds_grant.serialize(destination=str(grant_template_file), format='trig')
print(f"✓ Generated: {grant_template_file}")✓ Generated: output/odrl_grant_template.trig
# Preview
print("=" * 80)
with open(grant_template_file, 'r') as f:
print(f.read())================================================================================
@prefix dct: <http://purl.org/dc/terms/> .
@prefix fair: <https://fair2adapt.eu/ns/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix np: <http://www.nanopub.org/nschema#> .
@prefix npx: <http://purl.org/nanopub/x/> .
@prefix nt: <https://w3id.org/np/o/ntemplate/> .
@prefix odrl: <http://www.w3.org/ns/odrl/2/> .
@prefix orcid: <https://orcid.org/> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sub: <http://purl.org/nanopub/temp/np/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
sub:Head {
sub: a np:Nanopublication ;
np:hasAssertion sub:assertion ;
np:hasProvenance sub:provenance ;
np:hasPublicationInfo sub:pubinfo .
}
sub:assertion {
sub:assertion a nt:AssertionTemplate ;
rdfs:label "ODRL Access Grant for FAIR Data" ;
dct:description "Records that a specific DID was granted access to a dataset under an ODRL policy. Serves as an immutable audit trail for data access in the FAIR2Adapt project." ;
nt:hasNanopubLabelPattern "Access grant: ${datasetUri} → ${assigneeDid}" ;
nt:hasStatement sub:st1,
sub:st2,
sub:st3,
sub:st4,
sub:st5,
sub:st6 ;
nt:hasTag "Access Grant",
"FAIR2Adapt",
"ODRL" ;
nt:hasTargetNanopubType odrl:Agreement .
sub:assigneeDid a nt:UriPlaceholder ;
rdfs:label "DID of the requester (e.g. did:web:researcher.example.org)" .
sub:datasetUri a nt:UriPlaceholder ;
rdfs:label "URI of the dataset" ;
nt:hasPrefix "https://fair2adapt.eu/data/" ;
nt:hasPrefixLabel "fair2adapt-data" .
sub:grantTimestamp a nt:LiteralPlaceholder ;
rdfs:label "Timestamp of access grant" ;
nt:hasDatatype xsd:dateTime .
sub:grantedAction a nt:RestrictedChoicePlaceholder ;
rdfs:label "Granted action" ;
nt:possibleValue odrl:distribute,
odrl:reproduce,
odrl:use .
sub:policyNanopubUri a nt:UriPlaceholder ;
rdfs:label "Nanopub URI of the ODRL policy this grant is under" ;
nt:hasPrefix "https://w3id.org/np/" ;
nt:hasPrefixLabel "nanopub" .
sub:st1 rdf:object odrl:Agreement ;
rdf:predicate rdf:type ;
rdf:subject sub:grantUri .
sub:st2 rdf:object sub:datasetUri ;
rdf:predicate odrl:target ;
rdf:subject sub:grantUri .
sub:st3 rdf:object sub:assigneeDid ;
rdf:predicate odrl:assignee ;
rdf:subject sub:grantUri .
sub:st4 a nt:RepeatableStatement ;
rdf:object sub:grantedAction ;
rdf:predicate odrl:action ;
rdf:subject sub:grantUri .
sub:st5 rdf:object sub:policyNanopubUri ;
rdf:predicate fair:underPolicy ;
rdf:subject sub:grantUri .
sub:st6 rdf:object sub:grantTimestamp ;
rdf:predicate prov:generatedAtTime ;
rdf:subject sub:grantUri .
rdf:type rdfs:label "is a" .
odrl:action rdfs:label "for action" .
odrl:assignee rdfs:label "is granted to" .
odrl:target rdfs:label "grants access to dataset" .
prov:generatedAtTime rdfs:label "granted at time" .
fair:underPolicy rdfs:label "under ODRL policy" .
sub:grantUri a nt:IntroducedResource,
nt:UriPlaceholder ;
rdfs:label "Grant identifier" .
}
sub:provenance {
sub:assertion prov:wasAttributedTo orcid:0000-0002-1784-2920 .
}
sub:pubinfo {
sub: rdfs:label "Assertion template: ODRL Access Grant for FAIR Data" ;
dct:created "2026-03-29T12:27:17+00:00"^^xsd:dateTime ;
dct:creator orcid:0000-0002-1784-2920 ;
dct:license <https://creativecommons.org/licenses/by/4.0/> ;
npx:hasNanopubType nt:AssertionTemplate ;
npx:wasCreatedAt <https://fair2adapt-eosc.eu> .
orcid:0000-0002-1784-2920 foaf:name "Anne Fouilloux" .
}
🚀 SECTION 4: SIGN & PUBLISH¶
PUBLISH = True # Set to True when ready
USE_TEST_SERVER = False # Set to False for production
PROFILE_PATH = "/Users/annef/Documents/ScienceLive/ai-profile/profile.yml"if PUBLISH:
from nanopub import Nanopub, NanopubConf, load_profile
profile = load_profile(PROFILE_PATH)
print(f"Loaded profile: {profile.name}")
conf = NanopubConf(profile=profile, use_test_server=USE_TEST_SERVER)
template_files = [policy_template_file, grant_template_file]
published = {}
for trig_file in template_files:
np_obj = Nanopub(rdf=trig_file, conf=conf)
np_obj.sign()
signed_path = trig_file.with_suffix('.signed.trig')
np_obj.store(signed_path)
print(f"✓ Signed: {signed_path}")
np_obj.publish()
published[trig_file.stem] = np_obj.source_uri
print(f"✓ Published: {np_obj.source_uri}")
print("\n" + "=" * 80)
print("Template URIs to use in create_odrl_policy_nanopub.ipynb:")
print("=" * 80)
for name, uri in published.items():
print(f" {name}: {uri}")
else:
print("Publishing disabled. Set PUBLISH = True when ready.")
print("\nGenerated files:")
print(f" {policy_template_file}")
print(f" {grant_template_file}")Loaded profile: claude-ai-agent
✓ Signed: output/odrl_policy_template.signed.trig
✓ Published: https://w3id.org/np/RAfmYWze87u4ofAm6xhGfobp9KX_aTbItFrVfxhtQ8m0c
✓ Signed: output/odrl_grant_template.signed.trig
✓ Published: https://w3id.org/np/RAd1X8liGs-fjmbkrN514sL3CueyOVOjX3Bax63br6HYM
================================================================================
Template URIs to use in create_odrl_policy_nanopub.ipynb:
================================================================================
odrl_policy_template: https://w3id.org/np/RAfmYWze87u4ofAm6xhGfobp9KX_aTbItFrVfxhtQ8m0c
odrl_grant_template: https://w3id.org/np/RAd1X8liGs-fjmbkrN514sL3CueyOVOjX3Bax63br6HYM