11import abc
2+ from json import loads
3+ from pathlib import Path
4+ from typing import List , Union
5+ from urllib .parse import urljoin
26
7+ import rdflib .util
8+ import requests
39from franz .openrdf .repository import Repository
410from franz .openrdf .sail import AllegroGraphServer
11+ from requests .auth import HTTPBasicAuth
512
13+ from ted_sws import config
614from ted_sws .data_manager .adapters .sparql_endpoint import TripleStoreEndpointABC , SPARQLTripleStoreEndpoint
715
816
917class TripleStoreABC :
1018 @abc .abstractmethod
11- def add_data_to_repository (self , file_content : str , repository_name : str ):
19+ def create_repository (self , repository_name : str ):
20+ """
21+ Method to create a repository
22+ :param repository_name:
23+ :return:
24+ """
25+
26+ @abc .abstractmethod
27+ def delete_repository (self , repository_name : str ):
28+ """
29+ Method to delete a repository
30+ :param repository_name:
31+ :return:
32+ """
33+
34+ @abc .abstractmethod
35+ def list_repositories (self ) -> List [str ]:
36+ """
37+ Method to list all repositories
38+ :return:
39+ """
40+
41+ @abc .abstractmethod
42+ def add_data_to_repository (self , file_content : Union [str , bytes , bytearray ], mime_type : str , repository_name : str ):
1243 """
1344 Method to add triples from a string
1445 :param file_content:
1546 :param repository_name:
47+ :param mime_type:
1648 :return:
1749 """
1850
@@ -31,7 +63,7 @@ def get_sparql_triple_store_endpoint(self, repository_name: str = None) -> Tripl
3163 """
3264
3365
34- class AllegroGraphTripleStore (abc . ABC ):
66+ class AllegroGraphTripleStore (TripleStoreABC ):
3567 """
3668 This class is handling interactions with Allegro Graph triple store
3769 Note: If catalog name is not set, every operation will be executed at root level in the triple store
@@ -84,13 +116,15 @@ def list_repositories(self):
84116 """
85117 return self .allegro .openCatalog (name = self .catalog_name ).listRepositories ()
86118
87- def add_data_to_repository (self , file_content : str , repository_name : str = None ):
119+ def add_data_to_repository (self , file_content : Union [ str , bytes , bytearray ], mime_type : str , repository_name : str = None ):
88120 """
89121 Method to add triples from a string
90122 :param file_content:
91123 :param repository_name:
124+ :param mime_type:
92125 :return:
93126 """
127+
94128 repository = self ._get_repository (repository_name = repository_name )
95129 repository .getConnection ().addData (data = file_content )
96130
@@ -111,3 +145,121 @@ def get_sparql_triple_store_endpoint(self, repository_name: str = None) -> Tripl
111145 endpoint_url = f"{ self .host } /repositories/{ repository_name } "
112146 sparql_endpoint = SPARQLTripleStoreEndpoint (endpoint_url = endpoint_url )
113147 return sparql_endpoint
148+
149+
150+ class FusekiException (Exception ):
151+ """
152+ An exception when Fuseki server interaction has failed.
153+ """
154+
155+
156+ RDF_MIME_TYPES = {
157+ "turtle" : "text/turtle" ,
158+ "xml" : "application/rdf+xml" ,
159+ "json-ld" : "application/ld+json" ,
160+ "n3" : "text/n3" ,
161+ "nt" : "application/n-triples" ,
162+ "nquads" : "application/n-quads" ,
163+ "trig" : "application/trig" ,
164+ }
165+
166+
167+ class FusekiAdapter (TripleStoreABC ):
168+
169+ def __init__ (self , host : str = config .FUSEKI_ADMIN_HOST ,
170+ user : str = config .FUSEKI_ADMIN_USER ,
171+ password : str = config .FUSEKI_ADMIN_PASSWORD ):
172+
173+ self .host = host
174+ self .user = user
175+ self .password = password
176+
177+ def create_repository (self , repository_name : str ):
178+ """
179+ Create the dataset for the Fuseki store
180+ :param repository_name: The repository identifier. This should be short alphanumeric string uniquely
181+ identifying the repository
182+ :return: true if repository was created
183+ """
184+ if not repository_name :
185+ raise ValueError ('Repository name cannot be empty.' )
186+
187+ data = {
188+ 'dbType' : 'tdb' , # assuming that all repository are created persistent across restart
189+ 'dbName' : repository_name
190+ }
191+
192+ response = requests .post (urljoin (self .host , f"/$/datasets" ),
193+ auth = HTTPBasicAuth (self .user ,
194+ self .password ),
195+ data = data )
196+
197+ if response .status_code == 409 :
198+ raise FusekiException ('A repository with this name already exists.' )
199+
200+ def add_data_to_repository (self , file_content : Union [str , bytes , bytearray ], mime_type : str , repository_name : str ):
201+ url = urljoin (self .host , f"{ repository_name } /data" )
202+ headers = {
203+ "Content-Type" : mime_type ,
204+ }
205+ response = requests .post (url ,
206+ auth = HTTPBasicAuth (self .user , self .password ),
207+ data = file_content , headers = headers )
208+ if response .status_code != 200 :
209+ raise FusekiException (f'Server refused to load the content with code { response .status_code } .' )
210+
211+ def add_file_to_repository (self , file_path : Path , repository_name : str ):
212+ file_content = file_path .open ('rb' ).read ()
213+ file_format = rdflib .util .guess_format (str (file_path ))
214+ mime_type = RDF_MIME_TYPES [file_format ] if file_format in RDF_MIME_TYPES else "text/n3"
215+ self .add_data_to_repository (file_content = file_content , mime_type = mime_type , repository_name = repository_name )
216+
217+ def delete_repository (self , repository_name : str ):
218+ """
219+ Delete the repository from the Fuseki store
220+ :param repository_name: The repository identifier. This should be short alphanumeric string uniquely
221+ identifying the repository
222+ :return: true if repository was deleted
223+ """
224+ response = requests .delete (urljoin (self .host , f"/$/datasets/{ repository_name } " ),
225+ auth = HTTPBasicAuth (self .user ,
226+ self .password ))
227+
228+ if response .status_code == 404 :
229+ raise FusekiException ('The repository doesn\' t exist.' )
230+
231+ def list_repositories (self ) -> list :
232+ """
233+ Get the list of the repository names from the Fuseki store.
234+ :return: the list of the repository names
235+ :rtype: list
236+ """
237+ response = requests .get (urljoin (self .host , "/$/datasets" ),
238+ auth = HTTPBasicAuth (self .user ,
239+ self .password ))
240+
241+ if response .status_code != 200 :
242+ raise FusekiException (f"Fuseki server request ({ response .url } ) returned response { response .status_code } " )
243+
244+ return self ._select_repository_names_from_fuseki_response (response_text = response .text )
245+
246+ @staticmethod
247+ def _select_repository_names_from_fuseki_response (response_text ) -> list :
248+ """
249+ Helper method for digging for the list of repository.
250+ :param response_text:
251+ :return: list of the repository names
252+ """
253+ result = loads (response_text )
254+ return [d_item ['ds.name' ][1 :] for d_item in result ['datasets' ]]
255+
256+ def get_sparql_triple_store_endpoint (self , repository_name : str = None ) -> TripleStoreEndpointABC :
257+ """
258+ Helper method to create the url for querying.
259+ :param repository_name: The dataset identifier. This should be short alphanumeric string uniquely
260+ identifying the repository
261+ :return: the query url
262+ """
263+ endpoint_url = urljoin (self .host , repository_name + "/sparql" )
264+ sparql_endpoint = SPARQLTripleStoreEndpoint (endpoint_url = endpoint_url )
265+ return sparql_endpoint
0 commit comments