snapquery 0.0.1__tar.gz → 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {snapquery-0.0.1 → snapquery-0.0.2}/.project +1 -0
- {snapquery-0.0.1 → snapquery-0.0.2}/PKG-INFO +2 -2
- {snapquery-0.0.1 → snapquery-0.0.2}/pyproject.toml +1 -1
- snapquery-0.0.2/snapquery/__init__.py +1 -0
- snapquery-0.0.2/snapquery/snapquery_cmd.py +90 -0
- snapquery-0.0.2/snapquery/snapquery_core.py +283 -0
- snapquery-0.0.2/snapquery/snapquery_webserver.py +106 -0
- {snapquery-0.0.1 → snapquery-0.0.2}/tests/test_endpoints.py +8 -7
- snapquery-0.0.2/tests/test_namedqueries.py +37 -0
- snapquery-0.0.2/tests/test_restful_api.py +34 -0
- snapquery-0.0.1/snapquery/__init__.py +0 -1
- snapquery-0.0.1/snapquery/snapquery_cmd.py +0 -43
- snapquery-0.0.1/snapquery/snapquery_core.py +0 -5
- snapquery-0.0.1/snapquery/snapquery_webserver.py +0 -51
- {snapquery-0.0.1 → snapquery-0.0.2}/.github/workflows/build.yml +0 -0
- {snapquery-0.0.1 → snapquery-0.0.2}/.github/workflows/upload-to-pypi.yml +0 -0
- {snapquery-0.0.1 → snapquery-0.0.2}/.gitignore +0 -0
- {snapquery-0.0.1 → snapquery-0.0.2}/.pydevproject +0 -0
- {snapquery-0.0.1 → snapquery-0.0.2}/LICENSE +0 -0
- {snapquery-0.0.1 → snapquery-0.0.2}/README.md +0 -0
- {snapquery-0.0.1 → snapquery-0.0.2}/scripts/blackisort +0 -0
- {snapquery-0.0.1 → snapquery-0.0.2}/scripts/install +0 -0
- {snapquery-0.0.1 → snapquery-0.0.2}/scripts/test +0 -0
- {snapquery-0.0.1 → snapquery-0.0.2}/snapquery/version.py +0 -0
- {snapquery-0.0.1 → snapquery-0.0.2}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: snapquery
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: snapquery: Introduce Named Queries and Named Query Middleware to wikidata
|
|
5
5
|
Project-URL: Home, https://github.com/WolfgangFahl/snapquery
|
|
6
6
|
Project-URL: Documentation, https://wiki.bitplan.com/index.php/snapquery
|
|
@@ -25,7 +25,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
25
25
|
Classifier: Topic :: Software Development :: User Interfaces
|
|
26
26
|
Requires-Python: >=3.9
|
|
27
27
|
Requires-Dist: ngwidgets>=0.14.2
|
|
28
|
-
Requires-Dist: pylodstorage>=0.10.
|
|
28
|
+
Requires-Dist: pylodstorage>=0.10.4
|
|
29
29
|
Provides-Extra: test
|
|
30
30
|
Requires-Dist: green; extra == 'test'
|
|
31
31
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.2"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Created on 2024-05-03
|
|
3
|
+
|
|
4
|
+
@author: wf
|
|
5
|
+
"""
|
|
6
|
+
import sys
|
|
7
|
+
from argparse import ArgumentParser
|
|
8
|
+
|
|
9
|
+
from lodstorage.query import EndpointManager, Format, Query
|
|
10
|
+
from ngwidgets.cmd import WebserverCmd
|
|
11
|
+
|
|
12
|
+
from snapquery.snapquery_core import NamedQueryManager
|
|
13
|
+
from snapquery.snapquery_webserver import SnapQueryWebServer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SnapQueryCmd(WebserverCmd):
|
|
17
|
+
"""
|
|
18
|
+
Command line for diagrams server
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def getArgParser(self, description: str, version_msg) -> ArgumentParser:
|
|
22
|
+
"""
|
|
23
|
+
override the default argparser call
|
|
24
|
+
"""
|
|
25
|
+
parser = super().getArgParser(description, version_msg)
|
|
26
|
+
# see https://github.com/WolfgangFahl/pyLoDStorage/blob/master/lodstorage/querymain.py
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
"-ep",
|
|
29
|
+
"--endpointPath",
|
|
30
|
+
default=None,
|
|
31
|
+
help="path to yaml file to configure endpoints to use for queries",
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"-en",
|
|
35
|
+
"--endpointName",
|
|
36
|
+
default="wikidata",
|
|
37
|
+
help=f"Name of the endpoint to use for queries - use --listEndpoints to list available endpoints",
|
|
38
|
+
)
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"-le",
|
|
41
|
+
"--listEndpoints",
|
|
42
|
+
action="store_true",
|
|
43
|
+
help="show the list of available endpoints",
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument("-qn", "--queryName", help="run a named query")
|
|
46
|
+
parser.add_argument("-f", "--format", type=Format, choices=list(Format))
|
|
47
|
+
return parser
|
|
48
|
+
|
|
49
|
+
def handle_args(self) -> bool:
|
|
50
|
+
"""
|
|
51
|
+
handle the command line args
|
|
52
|
+
"""
|
|
53
|
+
# Call the superclass handle_args to maintain base class behavior
|
|
54
|
+
handled = super().handle_args()
|
|
55
|
+
endpoints = EndpointManager.getEndpoints(self.args.endpointPath, lang="sparql")
|
|
56
|
+
# Check if listing of endpoints is requested
|
|
57
|
+
if self.args.listEndpoints:
|
|
58
|
+
# List endpoints
|
|
59
|
+
for endpoint in endpoints.values():
|
|
60
|
+
print(endpoint)
|
|
61
|
+
handled = True # Operation handled
|
|
62
|
+
elif self.args.queryName is not None:
|
|
63
|
+
nqm = NamedQueryManager()
|
|
64
|
+
name = self.args.queryName
|
|
65
|
+
endpoint_name = self.args.endpointName
|
|
66
|
+
r_format = self.args.format
|
|
67
|
+
sparql_query = nqm.get_sparql(name=name, endpoint_name=endpoint_name)
|
|
68
|
+
qlod = nqm.query(endpoint_name=endpoint_name, name=name)
|
|
69
|
+
query = Query(name=name, query=sparql_query, lang="sparql")
|
|
70
|
+
nqm.format_result(qlod, query, r_format, endpoint_name=endpoint_name)
|
|
71
|
+
return handled
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def main(argv: list = None):
|
|
75
|
+
"""
|
|
76
|
+
main call
|
|
77
|
+
"""
|
|
78
|
+
cmd = SnapQueryCmd(
|
|
79
|
+
config=SnapQueryWebServer.get_config(),
|
|
80
|
+
webserver_cls=SnapQueryWebServer,
|
|
81
|
+
)
|
|
82
|
+
exit_code = cmd.cmd_main(argv)
|
|
83
|
+
return exit_code
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
DEBUG = 0
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
if DEBUG:
|
|
89
|
+
sys.argv.append("-d")
|
|
90
|
+
sys.exit(main())
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Created on 2024-05-03
|
|
3
|
+
|
|
4
|
+
@author: wf
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from dataclasses import field
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from lodstorage.csv import CSV
|
|
13
|
+
from lodstorage.query import Endpoint, EndpointManager, Format, Query
|
|
14
|
+
from lodstorage.sparql import SPARQL
|
|
15
|
+
from lodstorage.sql import SQLDB, EntityInfo
|
|
16
|
+
from lodstorage.yamlable import lod_storable
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@lod_storable
|
|
20
|
+
class NamedQuery:
|
|
21
|
+
"""
|
|
22
|
+
A named query that encapsulates the details and SPARQL query for a specific purpose.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
namespace (str): The namespace of the query, which helps in categorizing the query.
|
|
26
|
+
name (str): The unique name or identifier of the query within its namespace.
|
|
27
|
+
title (str): A brief one-line title that describes the query.
|
|
28
|
+
description (str): A detailed multiline description of what the query does and the data it accesses.
|
|
29
|
+
sparql (str): The SPARQL query string. This might be hidden in future to encapsulate query details.
|
|
30
|
+
query_id (str): A unique identifier for the query, generated from namespace and name, used as a primary key.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
query_id: str = field(init=False)
|
|
34
|
+
|
|
35
|
+
# namespace
|
|
36
|
+
namespace: str
|
|
37
|
+
# name/id
|
|
38
|
+
name: str
|
|
39
|
+
# one line title
|
|
40
|
+
title: str
|
|
41
|
+
# multiline description
|
|
42
|
+
description: str
|
|
43
|
+
# sparql query (to be hidden later)
|
|
44
|
+
sparql: str
|
|
45
|
+
|
|
46
|
+
def __post_init__(self):
|
|
47
|
+
"""
|
|
48
|
+
Post-initialization processing to construct a unique identifier for the query
|
|
49
|
+
based on its namespace and name.
|
|
50
|
+
"""
|
|
51
|
+
self.query_id = f"{self.namespace}.{self.name}"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class NamedQueryManager:
|
|
55
|
+
"""
|
|
56
|
+
Manages the storage, retrieval, and execution of named SPARQL queries.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, db_path: str = None, debug: bool = False):
|
|
60
|
+
"""
|
|
61
|
+
Initializes the NamedQueryManager with a specific database path and a debug mode.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
db_path (Optional[str]): The file path to the SQLite database. If None, the default cache path is used.
|
|
65
|
+
debug (bool): If True, enables debug mode which may provide additional logging and error reporting.
|
|
66
|
+
|
|
67
|
+
Attributes:
|
|
68
|
+
debug (bool): Stores the debug state.
|
|
69
|
+
sql_db (SQLDB): An instance of SQLDB to manage the SQLite database interactions.
|
|
70
|
+
endpoints (dict): A dictionary of SPARQL endpoints configured for use.
|
|
71
|
+
"""
|
|
72
|
+
if db_path is None:
|
|
73
|
+
db_path = NamedQueryManager.get_cache_path()
|
|
74
|
+
self.debug = debug
|
|
75
|
+
self.sql_db = SQLDB(dbname=db_path,check_same_thread=False, debug=debug)
|
|
76
|
+
self.endpoints = EndpointManager.getEndpoints(lang="sparql")
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def get_cache_path(cls) -> str:
|
|
80
|
+
home = str(Path.home())
|
|
81
|
+
cache_dir = f"{home}/.solutions/snapquery/storage"
|
|
82
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
83
|
+
cache_path = f"{cache_dir}/named_queries.db"
|
|
84
|
+
return cache_path
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def from_samples(
|
|
88
|
+
cls, db_path: Optional[str] = None, debug: bool = False
|
|
89
|
+
) -> "NamedQueryManager":
|
|
90
|
+
"""
|
|
91
|
+
Creates and returns an instance of NamedQueryManager, optionally initializing it from sample data.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
db_path (Optional[str]): Path to the database file. If None, the default path is used.
|
|
95
|
+
debug (bool): If True, enables debug mode which may provide additional logging.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
NamedQueryManager: An instance of the manager initialized with the database at `db_path`.
|
|
99
|
+
"""
|
|
100
|
+
if db_path is None:
|
|
101
|
+
db_path = cls.get_cache_path()
|
|
102
|
+
|
|
103
|
+
nqm = NamedQueryManager(debug=debug)
|
|
104
|
+
path_obj = Path(db_path)
|
|
105
|
+
if not path_obj.exists() or path_obj.stat().st_size == 0:
|
|
106
|
+
sample_queries = cls.get_samples()
|
|
107
|
+
list_of_records = []
|
|
108
|
+
for _query_name, named_query in sample_queries.items():
|
|
109
|
+
record = {
|
|
110
|
+
"namespace": named_query.namespace,
|
|
111
|
+
"name": named_query.name,
|
|
112
|
+
"title": named_query.title,
|
|
113
|
+
"description": named_query.description,
|
|
114
|
+
"sparql": named_query.sparql,
|
|
115
|
+
}
|
|
116
|
+
list_of_records.append(record)
|
|
117
|
+
entityInfo = EntityInfo(
|
|
118
|
+
list_of_records, name="NamedQuery", primaryKey="query_id"
|
|
119
|
+
)
|
|
120
|
+
nqm.sql_db.createTable(list_of_records, "NamedQuery", withDrop=True)
|
|
121
|
+
nqm.sql_db.store(list_of_records, entityInfo)
|
|
122
|
+
return nqm
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def get_samples(cls) -> dict[str, NamedQuery]:
|
|
126
|
+
"""
|
|
127
|
+
get samples
|
|
128
|
+
"""
|
|
129
|
+
samples = {
|
|
130
|
+
"wikidata-examples.cats": NamedQuery(
|
|
131
|
+
namespace="wikidata-examples",
|
|
132
|
+
name="cats",
|
|
133
|
+
title="Cats on Wikidata",
|
|
134
|
+
description="This query retrieves all items classified under 'house cat' (Q146) on Wikidata.",
|
|
135
|
+
sparql="""
|
|
136
|
+
SELECT ?item ?itemLabel
|
|
137
|
+
WHERE {
|
|
138
|
+
?item wdt:P31 wd:Q146. # Must be a cat
|
|
139
|
+
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
|
|
140
|
+
}
|
|
141
|
+
""",
|
|
142
|
+
),
|
|
143
|
+
"wikidata-examples.horses": NamedQuery(
|
|
144
|
+
namespace="wikidata-examples",
|
|
145
|
+
name="horses",
|
|
146
|
+
title="Horses on Wikidata",
|
|
147
|
+
description="This query retrieves information about horses, including parents, gender, and approximate birth and death years.",
|
|
148
|
+
sparql="""
|
|
149
|
+
SELECT DISTINCT ?horse ?horseLabel ?mother ?motherLabel ?father ?fatherLabel
|
|
150
|
+
(year(?birthdate) as ?birthyear) (year(?deathdate) as ?deathyear) ?genderLabel
|
|
151
|
+
WHERE {
|
|
152
|
+
?horse wdt:P31/wdt:P279* wd:Q726 . # Instance and subclasses of horse (Q726)
|
|
153
|
+
OPTIONAL{?horse wdt:P25 ?mother .} # Mother
|
|
154
|
+
OPTIONAL{?horse wdt:P22 ?father .} # Father
|
|
155
|
+
OPTIONAL{?horse wdt:P569 ?birthdate .} # Birth date
|
|
156
|
+
OPTIONAL{?horse wdt:P570 ?deathdate .} # Death date
|
|
157
|
+
OPTIONAL{?horse wdt:P21 ?gender .} # Gender
|
|
158
|
+
SERVICE wikibase:label {
|
|
159
|
+
bd:serviceParam wikibase:language "[AUTO_LANGUAGE],fr,ar,be,bg,bn,ca,cs,da,de,el,en,es,et,fa,fi,he,hi,hu,hy,id,it,ja,jv,ko,nb,nl,eo,pa,pl,pt,ro,ru,sh,sk,sr,sv,sw,te,th,tr,uk,yue,vec,vi,zh"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
ORDER BY ?horse
|
|
163
|
+
""",
|
|
164
|
+
),
|
|
165
|
+
}
|
|
166
|
+
return samples
|
|
167
|
+
|
|
168
|
+
def get_sparql(
|
|
169
|
+
self,
|
|
170
|
+
name: str,
|
|
171
|
+
namespace: str = "wikidata-examples",
|
|
172
|
+
endpoint_name: str = "wikidata",
|
|
173
|
+
) -> str:
|
|
174
|
+
sql_query = f"""SELECT
|
|
175
|
+
sparql
|
|
176
|
+
FROM
|
|
177
|
+
NamedQuery
|
|
178
|
+
WHERE
|
|
179
|
+
name = ? AND namespace = ?"""
|
|
180
|
+
query_records = self.sql_db.query(sql_query, (name, namespace))
|
|
181
|
+
|
|
182
|
+
if not query_records:
|
|
183
|
+
msg = f"NamedQuery not found for the specified name '{name}' and namespace '{namespace}'."
|
|
184
|
+
raise ValueError(msg)
|
|
185
|
+
|
|
186
|
+
query_count = len(query_records)
|
|
187
|
+
if query_count != 1:
|
|
188
|
+
msg = f"multiple entries ({query_count}) for name '{name}' and namespace '{namespace}'"
|
|
189
|
+
raise ValueError(msg)
|
|
190
|
+
|
|
191
|
+
sparql_query = query_records[0]["sparql"]
|
|
192
|
+
return sparql_query
|
|
193
|
+
|
|
194
|
+
def query(
|
|
195
|
+
self,
|
|
196
|
+
name: str,
|
|
197
|
+
namespace: str = "wikidata-examples",
|
|
198
|
+
endpoint_name: str = "wikidata",
|
|
199
|
+
):
|
|
200
|
+
"""
|
|
201
|
+
Execute a named SPARQL query using a specified endpoint and return the results.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
name (str): The name of the named query to execute.
|
|
205
|
+
namespace (str): The namespace of the named query, default is 'wikidata-examples'.
|
|
206
|
+
endpoint_name (str): The name of the endpoint to send the SPARQL query to, default is 'wikidata'.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Dict[str, Any]: The results of the SPARQL query in JSON format.
|
|
210
|
+
|
|
211
|
+
Raises:
|
|
212
|
+
ValueError: If no named query matches the given name and namespace.
|
|
213
|
+
Exception: If the SPARQL query execution fails or the endpoint returns an error.
|
|
214
|
+
"""
|
|
215
|
+
sparql_query = self.get_sparql(name, namespace, endpoint_name)
|
|
216
|
+
|
|
217
|
+
if not endpoint_name in self.endpoints:
|
|
218
|
+
msg = f"Invalid endpoint {endpoint_name}"
|
|
219
|
+
ValueError(msg)
|
|
220
|
+
endpoint = self.endpoints.get(endpoint_name)
|
|
221
|
+
sparql = SPARQL(endpoint.endpoint)
|
|
222
|
+
lod = sparql.queryAsListOfDicts(sparql_query)
|
|
223
|
+
return lod
|
|
224
|
+
|
|
225
|
+
def format_result(
|
|
226
|
+
self,
|
|
227
|
+
qlod: List[Dict[str, Any]],
|
|
228
|
+
query: Query,
|
|
229
|
+
r_format_str: str,
|
|
230
|
+
endpoint_name: str = "wikidata",
|
|
231
|
+
) -> Optional[str]:
|
|
232
|
+
"""
|
|
233
|
+
Formats the query results based on the specified format and prints them.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
qlod (List[Dict[str, Any]]): The list of dictionaries that represent the query results.
|
|
237
|
+
query (Query): The query object which contains details like the endpoint and the database.
|
|
238
|
+
r_format_str: The format in which to print the results.
|
|
239
|
+
endpoint_name (str): The name of the endpoint to be used, defaults to "wikidata".
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Optional[str]: The formatted string representation of the query results, or None if printed directly.
|
|
243
|
+
"""
|
|
244
|
+
# Retrieve endpoint configuration using the provided endpoint name
|
|
245
|
+
endpointConf = self.endpoints.get(endpoint_name, Endpoint.getDefault())
|
|
246
|
+
query.tryItUrl = endpointConf.website
|
|
247
|
+
query.database = endpointConf.database
|
|
248
|
+
r_format = Format[r_format_str]
|
|
249
|
+
|
|
250
|
+
if r_format == Format.csv:
|
|
251
|
+
csv_output = CSV.toCSV(qlod)
|
|
252
|
+
return csv_output
|
|
253
|
+
elif r_format in [Format.latex, Format.github, Format.mediawiki, Format.html]:
|
|
254
|
+
doc = query.documentQueryResult(
|
|
255
|
+
qlod, tablefmt=str(r_format), floatfmt=".0f"
|
|
256
|
+
)
|
|
257
|
+
return doc.asText()
|
|
258
|
+
elif r_format == Format.json:
|
|
259
|
+
return json.dumps(qlod, indent=2, sort_keys=True, default=str)
|
|
260
|
+
return None # In case no format is matched or needed
|
|
261
|
+
|
|
262
|
+
def print_result(
|
|
263
|
+
self,
|
|
264
|
+
qlod: List[Dict[str, Any]],
|
|
265
|
+
query: Query,
|
|
266
|
+
r_format_str: str,
|
|
267
|
+
endpoint_name: str = "wikidata",
|
|
268
|
+
) -> None:
|
|
269
|
+
"""
|
|
270
|
+
Prints the formatted results of a query.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
qlod (List[Dict[str, Any]]): The list of dictionaries that represent the query results.
|
|
274
|
+
query (Query): The query object which contains details such as the endpoint and the database.
|
|
275
|
+
r_format_str: The format in which to print the results.
|
|
276
|
+
endpoint_name (str): The name of the endpoint to be used, defaults to "wikidata".
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
None
|
|
280
|
+
"""
|
|
281
|
+
result_str = self.format_result(qlod, query, r_format_str, endpoint_name)
|
|
282
|
+
if result_str is not None:
|
|
283
|
+
print(result_str)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Created on 2024-05-03
|
|
3
|
+
@author: wf
|
|
4
|
+
"""
|
|
5
|
+
from fastapi import Request, Header,HTTPException
|
|
6
|
+
from fastapi.responses import HTMLResponse
|
|
7
|
+
from lodstorage.query import Query
|
|
8
|
+
from ngwidgets.input_webserver import InputWebserver, InputWebSolution, WebserverConfig
|
|
9
|
+
from nicegui import app
|
|
10
|
+
from nicegui.client import Client
|
|
11
|
+
|
|
12
|
+
from snapquery.snapquery_core import NamedQueryManager
|
|
13
|
+
from snapquery.version import Version
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SnapQueryWebServer(InputWebserver):
|
|
17
|
+
"""
|
|
18
|
+
server to supply named Queries
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def get_config(cls) -> WebserverConfig:
|
|
23
|
+
"""
|
|
24
|
+
get the configuration for this Webserver
|
|
25
|
+
"""
|
|
26
|
+
copy_right = ""
|
|
27
|
+
config = WebserverConfig(
|
|
28
|
+
short_name="snapquery",
|
|
29
|
+
copy_right=copy_right,
|
|
30
|
+
version=Version(),
|
|
31
|
+
default_port=9862,
|
|
32
|
+
)
|
|
33
|
+
server_config = WebserverConfig.get(config)
|
|
34
|
+
server_config.solution_class = SnapQuerySolution
|
|
35
|
+
return server_config
|
|
36
|
+
|
|
37
|
+
def __init__(self):
|
|
38
|
+
"""Constructs all the necessary attributes for the WebServer object."""
|
|
39
|
+
InputWebserver.__init__(self, config=SnapQueryWebServer.get_config())
|
|
40
|
+
self.nqm = NamedQueryManager.from_samples()
|
|
41
|
+
|
|
42
|
+
@app.get("/query/{namespace}/{name}", response_class=HTMLResponse)
|
|
43
|
+
def query(namespace: str, name: str):
|
|
44
|
+
"""
|
|
45
|
+
Executes a SPARQL query by name within a specified namespace, formats the results, and returns them as an HTML response.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
namespace (str): The namespace identifying the group or category of the query.
|
|
49
|
+
name (str): The specific name of the query to be executed.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
HTMLResponse: The HTML formatted response containing the results of the query execution.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
HTTPException: If the query cannot be found or fails to execute.
|
|
56
|
+
"""
|
|
57
|
+
content=self.query(namespace,name)
|
|
58
|
+
if not content:
|
|
59
|
+
raise HTTPException(status_code=500, detail="Could not create result")
|
|
60
|
+
|
|
61
|
+
# Return the content as an HTML response
|
|
62
|
+
return HTMLResponse(content)
|
|
63
|
+
|
|
64
|
+
def query(self,namespace:str,name:str):
|
|
65
|
+
endpoint_name = "wikidata"
|
|
66
|
+
# content negotiation
|
|
67
|
+
# Determine response format by extension in the name or Accept header
|
|
68
|
+
if '.' in name:
|
|
69
|
+
r_format = name.split('.')[-1]
|
|
70
|
+
name = name[:name.rfind('.')]
|
|
71
|
+
else:
|
|
72
|
+
r_format = "html"
|
|
73
|
+
|
|
74
|
+
# Retrieve the SPARQL query string using the namespace and name.
|
|
75
|
+
try:
|
|
76
|
+
sparql_query = self.nqm.get_sparql(name, namespace, endpoint_name)
|
|
77
|
+
qlod = self.nqm.query(
|
|
78
|
+
name=name, namespace=namespace, endpoint_name=endpoint_name
|
|
79
|
+
)
|
|
80
|
+
query = Query(name=name, query=sparql_query, lang="sparql")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
# Handling specific exceptions can be more detailed based on what nqm.get_sparql and nqm.query can raise
|
|
83
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
84
|
+
|
|
85
|
+
# Format the results and generate HTML content
|
|
86
|
+
content = self.nqm.format_result(
|
|
87
|
+
qlod, query, r_format, endpoint_name=endpoint_name
|
|
88
|
+
)
|
|
89
|
+
return content
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class SnapQuerySolution(InputWebSolution):
|
|
93
|
+
"""
|
|
94
|
+
the Snap Query solution
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(self, webserver: SnapQueryWebServer, client: Client):
|
|
98
|
+
"""
|
|
99
|
+
Initialize the solution
|
|
100
|
+
|
|
101
|
+
Calls the constructor of the base solution
|
|
102
|
+
Args:
|
|
103
|
+
webserver (SnapQueryWebServer): The webserver instance associated with this context.
|
|
104
|
+
client (Client): The client instance this context is associated with.
|
|
105
|
+
"""
|
|
106
|
+
super().__init__(webserver, client) # Call to the superclass constructor
|
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
2
|
Created on 2024-05-03
|
|
3
3
|
|
|
4
4
|
@author: wf
|
|
5
|
-
|
|
6
|
-
from ngwidgets.basetest import Basetest
|
|
5
|
+
"""
|
|
7
6
|
from lodstorage.query import EndpointManager
|
|
7
|
+
from ngwidgets.basetest import Basetest
|
|
8
|
+
|
|
8
9
|
|
|
9
10
|
class TestEndpoints(Basetest):
|
|
10
11
|
"""
|
|
11
12
|
test endpoint handling according to https://github.com/WolfgangFahl/snapquery/issues/1
|
|
12
13
|
"""
|
|
13
|
-
|
|
14
|
+
|
|
14
15
|
def setUp(self, debug=False, profile=True):
|
|
15
16
|
Basetest.setUp(self, debug=debug, profile=profile)
|
|
16
|
-
|
|
17
|
+
|
|
17
18
|
def testEndpoints(self):
|
|
18
19
|
"""
|
|
19
20
|
test the endpoint handling
|
|
20
21
|
"""
|
|
21
|
-
em=EndpointManager()
|
|
22
|
-
ep_names=em.getEndpointNames()
|
|
22
|
+
em = EndpointManager()
|
|
23
|
+
ep_names = em.getEndpointNames()
|
|
23
24
|
self.assertTrue("wikidata" in ep_names)
|
|
24
25
|
pass
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Created on 2024-05-03
|
|
3
|
+
|
|
4
|
+
@author: wf
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
from ngwidgets.basetest import Basetest
|
|
9
|
+
|
|
10
|
+
from snapquery.snapquery_core import NamedQueryManager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestNamedQueryManager(Basetest):
|
|
14
|
+
"""
|
|
15
|
+
test the named query Manage
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def setUp(self, debug=False, profile=True):
|
|
19
|
+
Basetest.setUp(self, debug=debug, profile=profile)
|
|
20
|
+
|
|
21
|
+
def testNamedQueries(self):
|
|
22
|
+
"""
|
|
23
|
+
test getting a named query manager
|
|
24
|
+
"""
|
|
25
|
+
db_path = "/tmp/named_queries.db"
|
|
26
|
+
nqm = NamedQueryManager.from_samples(db_path=db_path)
|
|
27
|
+
for name, ex_count in [("x-invalid", -1), ("cats", 223)]:
|
|
28
|
+
try:
|
|
29
|
+
lod = nqm.query(name)
|
|
30
|
+
if self.debug:
|
|
31
|
+
print(f"{name}:")
|
|
32
|
+
print(json.dumps(lod, default=str, indent=2))
|
|
33
|
+
self.assertEqual(ex_count, len(lod))
|
|
34
|
+
except Exception as ex:
|
|
35
|
+
if self.debug:
|
|
36
|
+
print(f"{name}:Exception {str(ex)}")
|
|
37
|
+
self.assertEqual(-1, ex_count)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Created on 2024-05-03
|
|
3
|
+
|
|
4
|
+
@author: wf
|
|
5
|
+
"""
|
|
6
|
+
from snapquery.snapquery_webserver import SnapQueryWebServer
|
|
7
|
+
from snapquery.snapquery_cmd import SnapQueryCmd
|
|
8
|
+
from ngwidgets.webserver_test import WebserverTest
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestRestFullApi(WebserverTest):
|
|
12
|
+
"""
|
|
13
|
+
test endpoint handling according to https://github.com/WolfgangFahl/snapquery/issues/1
|
|
14
|
+
"""
|
|
15
|
+
def setUp(self, debug=True, profile=True):
|
|
16
|
+
server_class = SnapQueryWebServer
|
|
17
|
+
cmd_class = SnapQueryCmd
|
|
18
|
+
WebserverTest.setUp(self, server_class, cmd_class, debug=debug, profile=profile)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def testDemoWebserver(self):
|
|
22
|
+
"""
|
|
23
|
+
test API docs access
|
|
24
|
+
"""
|
|
25
|
+
# self.debug=True
|
|
26
|
+
html = self.getHtml("/docs")
|
|
27
|
+
self.assertTrue("Swagger" in html)
|
|
28
|
+
|
|
29
|
+
def testQueryApi(self):
|
|
30
|
+
for r_format in ["mediawiki","github","latex","html"]:
|
|
31
|
+
path=f"/query/wikidata-examples/cats.{r_format}"
|
|
32
|
+
html=self.getHtml(path)
|
|
33
|
+
if self.debug:
|
|
34
|
+
print(html)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.1"
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Created on 2024-05-03
|
|
3
|
-
|
|
4
|
-
@author: wf
|
|
5
|
-
"""
|
|
6
|
-
import sys
|
|
7
|
-
from argparse import ArgumentParser
|
|
8
|
-
|
|
9
|
-
from ngwidgets.cmd import WebserverCmd
|
|
10
|
-
|
|
11
|
-
from snapquery.snapquery_webserver import SnapQueryWebServer
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class SnapQueryCmd(WebserverCmd):
|
|
15
|
-
"""
|
|
16
|
-
Command line for diagrams server
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
def getArgParser(self, description: str, version_msg) -> ArgumentParser:
|
|
20
|
-
"""
|
|
21
|
-
override the default argparser call
|
|
22
|
-
"""
|
|
23
|
-
parser = super().getArgParser(description, version_msg)
|
|
24
|
-
return parser
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def main(argv: list = None):
|
|
28
|
-
"""
|
|
29
|
-
main call
|
|
30
|
-
"""
|
|
31
|
-
cmd = SnapQueryCmd(
|
|
32
|
-
config=SnapQueryWebServer.get_config(),
|
|
33
|
-
webserver_cls=SnapQueryWebServer,
|
|
34
|
-
)
|
|
35
|
-
exit_code = cmd.cmd_main(argv)
|
|
36
|
-
return exit_code
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
DEBUG = 0
|
|
40
|
-
if __name__ == "__main__":
|
|
41
|
-
if DEBUG:
|
|
42
|
-
sys.argv.append("-d")
|
|
43
|
-
sys.exit(main())
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Created on 2024-05-03
|
|
3
|
-
@author: wf
|
|
4
|
-
"""
|
|
5
|
-
from ngwidgets.input_webserver import InputWebserver, InputWebSolution, WebserverConfig
|
|
6
|
-
from nicegui.client import Client
|
|
7
|
-
|
|
8
|
-
from snapquery.version import Version
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class SnapQueryWebServer(InputWebserver):
|
|
12
|
-
"""
|
|
13
|
-
server to supply named Queries
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
@classmethod
|
|
17
|
-
def get_config(cls) -> WebserverConfig:
|
|
18
|
-
"""
|
|
19
|
-
get the configuration for this Webserver
|
|
20
|
-
"""
|
|
21
|
-
copy_right = ""
|
|
22
|
-
config = WebserverConfig(
|
|
23
|
-
short_name="snapquery",
|
|
24
|
-
copy_right=copy_right,
|
|
25
|
-
version=Version(),
|
|
26
|
-
default_port=9862,
|
|
27
|
-
)
|
|
28
|
-
server_config = WebserverConfig.get(config)
|
|
29
|
-
server_config.solution_class = SnapQuerySolution
|
|
30
|
-
return server_config
|
|
31
|
-
|
|
32
|
-
def __init__(self):
|
|
33
|
-
"""Constructs all the necessary attributes for the WebServer object."""
|
|
34
|
-
InputWebserver.__init__(self, config=SnapQueryWebServer.get_config())
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class SnapQuerySolution(InputWebSolution):
|
|
38
|
-
"""
|
|
39
|
-
the Snap Query solution
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
def __init__(self, webserver: SnapQueryWebServer, client: Client):
|
|
43
|
-
"""
|
|
44
|
-
Initialize the solution
|
|
45
|
-
|
|
46
|
-
Calls the constructor of the base solution
|
|
47
|
-
Args:
|
|
48
|
-
webserver (SnapQueryWebServer): The webserver instance associated with this context.
|
|
49
|
-
client (Client): The client instance this context is associated with.
|
|
50
|
-
"""
|
|
51
|
-
super().__init__(webserver, client) # Call to the superclass constructor
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|