snapquery 0.2.3__tar.gz → 0.2.5__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.
Files changed (144) hide show
  1. {snapquery-0.2.3 → snapquery-0.2.5}/PKG-INFO +5 -5
  2. {snapquery-0.2.3 → snapquery-0.2.5}/pyproject.toml +4 -4
  3. snapquery-0.2.5/snapquery/__init__.py +1 -0
  4. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/github_access.py +25 -10
  5. snapquery-0.2.5/snapquery/qlever.py +187 -0
  6. snapquery-0.2.5/snapquery/query_parameterizer.py +246 -0
  7. snapquery-0.2.5/snapquery/query_set_cmd.py +435 -0
  8. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/query_set_tool.py +11 -15
  9. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/endpoints.yaml +19 -2
  10. snapquery-0.2.5/snapquery/samples/query-sets/yaml/hartig-eqs-2025-12-16.yaml +3033 -0
  11. snapquery-0.2.5/snapquery/samples/query-sets/yaml/qlever-issues-2025-12-05.yaml +6219 -0
  12. snapquery-0.2.5/snapquery/samples/query-sets/yaml/scholia-2025-12-04.yaml +9324 -0
  13. snapquery-0.2.5/snapquery/samples/query-sets/yaml/scholia-2025-12-16.yaml +9324 -0
  14. snapquery-0.2.5/snapquery/samples/query-sets/yaml/scholia-qlever-2025-12-16.yaml +14907 -0
  15. snapquery-0.2.5/snapquery/samples/query-sets/yaml/sib-2025-12-04.yaml +24822 -0
  16. snapquery-0.2.5/snapquery/samples/query-sets/yaml/wikidata-examples-2025-12-04.yaml +8715 -0
  17. snapquery-0.2.5/snapquery/samples/query-sets/yaml/wikidata-short-urls-2025-12-06.yaml +1541 -0
  18. snapquery-0.2.5/snapquery/scholia.py +161 -0
  19. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/sib_sparql_examples.py +13 -7
  20. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/snapquery_cmd.py +11 -3
  21. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/snapquery_webserver.py +8 -2
  22. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/sparql_analyzer.py +1 -0
  23. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/version.py +1 -1
  24. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/wd_page_query_extractor.py +41 -15
  25. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/wd_short_url.py +13 -10
  26. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_cmdline.py +4 -2
  27. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_endpoints.py +3 -3
  28. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_qlever.py +18 -36
  29. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_query_set_tool.py +4 -3
  30. snapquery-0.2.5/tests/test_scholia.py +123 -0
  31. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_sib_examples.py +10 -6
  32. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_sparql_analyzer.py +1 -1
  33. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_wd_query_parsing.py +3 -3
  34. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_wd_short_url.py +4 -1
  35. snapquery-0.2.3/snapquery/__init__.py +0 -1
  36. snapquery-0.2.3/snapquery/qlever.py +0 -111
  37. snapquery-0.2.3/snapquery/query_set_cmd.py +0 -168
  38. snapquery-0.2.3/snapquery/scholia.py +0 -107
  39. snapquery-0.2.3/tests/test_scholia.py +0 -57
  40. {snapquery-0.2.3 → snapquery-0.2.5}/.github/workflows/build.yml +0 -0
  41. {snapquery-0.2.3 → snapquery-0.2.5}/.github/workflows/upload-to-pypi.yml +0 -0
  42. {snapquery-0.2.3 → snapquery-0.2.5}/.gitignore +0 -0
  43. {snapquery-0.2.3 → snapquery-0.2.5}/.project +0 -0
  44. {snapquery-0.2.3 → snapquery-0.2.5}/.pydevproject +0 -0
  45. {snapquery-0.2.3 → snapquery-0.2.5}/CITATION.cff +0 -0
  46. {snapquery-0.2.3 → snapquery-0.2.5}/LICENSE +0 -0
  47. {snapquery-0.2.3 → snapquery-0.2.5}/README.md +0 -0
  48. {snapquery-0.2.3 → snapquery-0.2.5}/docs/index.md +0 -0
  49. {snapquery-0.2.3 → snapquery-0.2.5}/mkdocs.yml +0 -0
  50. {snapquery-0.2.3 → snapquery-0.2.5}/scripts/blackisort +0 -0
  51. {snapquery-0.2.3 → snapquery-0.2.5}/scripts/doc +0 -0
  52. {snapquery-0.2.3 → snapquery-0.2.5}/scripts/fixdomain +0 -0
  53. {snapquery-0.2.3 → snapquery-0.2.5}/scripts/install +0 -0
  54. {snapquery-0.2.3 → snapquery-0.2.5}/scripts/qlv +0 -0
  55. {snapquery-0.2.3 → snapquery-0.2.5}/scripts/release +0 -0
  56. {snapquery-0.2.3 → snapquery-0.2.5}/scripts/restore_db +0 -0
  57. {snapquery-0.2.3 → snapquery-0.2.5}/scripts/test +0 -0
  58. {snapquery-0.2.3 → snapquery-0.2.5}/scripts/test_queries +0 -0
  59. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/authorization.py +0 -0
  60. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/basequeryview.py +0 -0
  61. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/ceurws.py +0 -0
  62. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/dblp.py +0 -0
  63. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/error_filter.py +0 -0
  64. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/execution.py +0 -0
  65. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/graph.py +0 -0
  66. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/models/__init__.py +0 -0
  67. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/models/person.py +0 -0
  68. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/models/sparql_components.py +0 -0
  69. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/mwlogin.py +0 -0
  70. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/namespace_stats_view.py +0 -0
  71. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/orcid.py +0 -0
  72. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/params_view.py +0 -0
  73. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/person_selector.py +0 -0
  74. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/pid.py +0 -0
  75. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/pid_lookup.py +0 -0
  76. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/prefix_merger.py +0 -0
  77. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/query_annotate.py +0 -0
  78. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/query_selector.py +0 -0
  79. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/query_set_tool_view.py +0 -0
  80. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/resources/sparql_language.json +0 -0
  81. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/WikiProject_LSEThesisProject.wiki +0 -0
  82. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/ceur-ws.json +0 -0
  83. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/dblp.json +0 -0
  84. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/endpoints_optional.yaml +0 -0
  85. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/federated-query-examples.json +0 -0
  86. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/graphs.yaml +0 -0
  87. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/meta_query.yaml +0 -0
  88. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/orkg.json +0 -0
  89. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/qlever.json +0 -0
  90. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/qlever_performance.json +0 -0
  91. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/query-sets/yaml/ceur-ws.yaml +0 -0
  92. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/query-sets/yaml/dblp.yaml +0 -0
  93. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/query-sets/yaml/federated-query-examples.yaml +0 -0
  94. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/query-sets/yaml/orkg.yaml +0 -0
  95. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/query-sets/yaml/qlever.yaml +0 -0
  96. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/query-sets/yaml/qlever_performance.yaml +0 -0
  97. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/query-sets/yaml/scholia.yaml +0 -0
  98. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/query-sets/yaml/snapquery.yaml +0 -0
  99. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/query-sets/yaml/wikidata-WikidataThesisToolkit.yaml +0 -0
  100. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/query-sets/yaml/wikidata-WikidataThesisToolkitAlt.yaml +0 -0
  101. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/query-sets/yaml/wikidata-examples.yaml +0 -0
  102. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/query-sets/yaml/wikidata-short-urls.yaml +0 -0
  103. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/query_stats.yaml +0 -0
  104. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/scholia.json +0 -0
  105. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/snapquery.json +0 -0
  106. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/wikidata-WikidataThesisToolkit.json +0 -0
  107. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/wikidata-WikidataThesisToolkitAlt.json +0 -0
  108. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/wikidata-examples.json +0 -0
  109. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/samples/wikidata-short-urls.json +0 -0
  110. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/snapquery_core.py +0 -0
  111. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/snapquery_view.py +0 -0
  112. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery/stats_view.py +0 -0
  113. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery.puml +0 -0
  114. {snapquery-0.2.3 → snapquery-0.2.5}/snapquery.yaml +0 -0
  115. {snapquery-0.2.3 → snapquery-0.2.5}/tests/__init__.py +0 -0
  116. {snapquery-0.2.3 → snapquery-0.2.5}/tests/action_stats.py +0 -0
  117. {snapquery-0.2.3 → snapquery-0.2.5}/tests/query_extractors/__init__.py +0 -0
  118. {snapquery-0.2.3 → snapquery-0.2.5}/tests/query_extractors/sparql_standard_queries.py +0 -0
  119. {snapquery-0.2.3 → snapquery-0.2.5}/tests/resources/error_messages/qlever/ask_unsupported.txt +0 -0
  120. {snapquery-0.2.3 → snapquery-0.2.5}/tests/resources/error_messages/qlever/invalid_literal.txt +0 -0
  121. {snapquery-0.2.3 → snapquery-0.2.5}/tests/resources/error_messages/qlever/invalid_regex_func.txt +0 -0
  122. {snapquery-0.2.3 → snapquery-0.2.5}/tests/resources/error_messages/qlever/memory_limit.txt +0 -0
  123. {snapquery-0.2.3 → snapquery-0.2.5}/tests/resources/error_messages/qlever/timeout.txt +0 -0
  124. {snapquery-0.2.3 → snapquery-0.2.5}/tests/resources/error_messages/triplydb/parser_error.txt +0 -0
  125. {snapquery-0.2.3 → snapquery-0.2.5}/tests/resources/error_messages/wdqs/duplicate_prefix.txt +0 -0
  126. {snapquery-0.2.3 → snapquery-0.2.5}/tests/resources/error_messages/wdqs/invalid_iri_declaration.txt +0 -0
  127. {snapquery-0.2.3 → snapquery-0.2.5}/tests/resources/error_messages/wdqs/invalid_sparql_syntax.txt +0 -0
  128. {snapquery-0.2.3 → snapquery-0.2.5}/tests/resources/error_messages/wdqs/timeout.txt +0 -0
  129. {snapquery-0.2.3 → snapquery-0.2.5}/tests/resources/error_messages/wdqs/undefined_prefix.txt +0 -0
  130. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_authorization.py +0 -0
  131. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_ceurws.py +0 -0
  132. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_error_filter.py +0 -0
  133. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_graph.py +0 -0
  134. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_namedqueries.py +0 -0
  135. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_oauth.py +0 -0
  136. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_orcid.py +0 -0
  137. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_pids.py +0 -0
  138. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_prefix_merger.py +0 -0
  139. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_query_annotate.py +0 -0
  140. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_query_execution.py +0 -0
  141. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_query_name.py +0 -0
  142. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_restful_api.py +0 -0
  143. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_scholia_q.py +0 -0
  144. {snapquery-0.2.3 → snapquery-0.2.5}/tests/test_sparql_components.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: snapquery
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: Just Query wikidata and other SPARQL endpoints by query name - a frontend to Introduce Named Queries and Named Query Middleware to wikidata and other SPARQL endpoints
5
5
  Project-URL: Home, https://github.com/WolfgangFahl/snapquery
6
6
  Project-URL: Documentation, https://wiki.bitplan.com/index.php/snapquery
@@ -26,14 +26,14 @@ Classifier: Topic :: Software Development :: User Interfaces
26
26
  Requires-Python: >=3.10
27
27
  Requires-Dist: mwoauth>=0.4.0
28
28
  Requires-Dist: nameparser>=1.1.3
29
- Requires-Dist: ngwidgets>=0.30.0
29
+ Requires-Dist: ngwidgets>=0.30.3
30
30
  Requires-Dist: nicegui[plotly]==2.24.2
31
31
  Requires-Dist: pandas>=2.3.3
32
32
  Requires-Dist: py-3rdparty-mediawiki>=0.18.1
33
33
  Requires-Dist: py-ez-wikidata>=0.3.2
34
- Requires-Dist: pybasemkit>=0.1.3
35
- Requires-Dist: pylodstorage>=0.18.5
36
- Requires-Dist: pyopensourceprojects>=0.3.0
34
+ Requires-Dist: pybasemkit>=0.1.6
35
+ Requires-Dist: pylodstorage>=0.18.6
36
+ Requires-Dist: pyopensourceprojects>=0.4.1
37
37
  Requires-Dist: python-slugify>=8.0.4
38
38
  Requires-Dist: ratelimit>=2.2.1
39
39
  Requires-Dist: requests
@@ -22,11 +22,11 @@ dependencies = [
22
22
  # https://pypi.org/project/pybasemkit/
23
23
  # https://github.com/WolfgangFahl/pybasemkit
24
24
  # Python base module kit: YAML/JSON I/O, structured logging, CLI tooling, shell execution, and remote pydevd debug support.
25
- "pybasemkit>=0.1.3",
25
+ "pybasemkit>=0.1.6",
26
26
  # https://github.com/WolfgangFahl/nicegui_widgets
27
- "ngwidgets>=0.30.0",
27
+ "ngwidgets>=0.30.3",
28
28
  # https://pypi.org/project/pyLodStorage/
29
- "pyLodStorage>=0.18.5",
29
+ "pyLodStorage>=0.18.6",
30
30
  # https://pypi.org/project/py-3rdparty-mediawiki/
31
31
  "py-3rdparty-mediawiki>=0.18.1",
32
32
  "wikitextparser",
@@ -51,7 +51,7 @@ dependencies = [
51
51
  # https://pypi.org/project/nameparser/
52
52
  "nameparser>=1.1.3",
53
53
  # https://github.com/WolfgangFahl/pyOpenSourceProjects
54
- "pyOpenSourceProjects>=0.3.0"
54
+ "pyOpenSourceProjects>=0.4.1"
55
55
  ]
56
56
 
57
57
  requires-python = ">=3.10"
@@ -0,0 +1 @@
1
+ __version__ = "0.2.5"
@@ -4,9 +4,10 @@ GitHub API client for accessing repository contents.
4
4
  Created on 2025-12-02
5
5
  @author: wf
6
6
  """
7
+
7
8
  import json
8
- from pathlib import Path
9
- from typing import List, Dict, Any, Optional, Union
9
+ from pathlib import Path
10
+ from typing import Any, Dict, List, Optional, Union
10
11
 
11
12
  import requests
12
13
 
@@ -16,28 +17,38 @@ class GitHub:
16
17
  A simple GitHub API client for accessing repository contents.
17
18
  """
18
19
 
19
- def __init__(self, owner: str, repo: str, token: Optional[str] = None, session: Optional[requests.Session] = None):
20
+ def __init__(
21
+ self,
22
+ owner: str,
23
+ repo: str,
24
+ branch: Optional[str] = None,
25
+ token: Optional[str] = None,
26
+ session: Optional[requests.Session] = None
27
+ ):
20
28
  """
21
29
  Initialize GitHub client.
22
30
 
23
31
  Args:
24
32
  owner: Repository owner (username or organization)
25
33
  repo: Repository name
34
+ branch: Optional specific branch or commit SHA (default: default repo branch)
26
35
  token: Optional GitHub API token for authentication
27
36
  session: Optional custom requests.Session
28
37
  """
29
38
  self.owner = owner
30
39
  self.repo = repo
31
- self.token = token
40
+ self.branch = branch
41
+
42
+
32
43
  self.base_url = f"https://api.github.com/repos/{owner}/{repo}"
33
- # Use provided token or read from file (compatible with GitHubApi)
44
+
45
+ # Use provided token or read from file
34
46
  self.token = token if token is not None else self._read_token()
35
47
 
36
48
  # Use custom session or create new one
37
49
  self.session = session or requests.Session()
38
50
 
39
51
 
40
-
41
52
  def _headers(self) -> Dict[str, str]:
42
53
  headers = {"Accept": "application/vnd.github.v3+json"}
43
54
  if self.token:
@@ -55,14 +66,13 @@ class GitHub:
55
66
  token_path = Path.home() / ".github" / "access_token.json"
56
67
  if token_path.exists():
57
68
  try:
58
- with open(token_path, 'r') as f:
69
+ with open(token_path, "r") as f:
59
70
  data = json.load(f)
60
71
  return data.get("access_token")
61
72
  except (json.JSONDecodeError, OSError):
62
73
  pass
63
74
  return None
64
75
 
65
-
66
76
  def get_contents(self, path: str = "") -> Union[List[Dict[str, Any]], Dict[str, Any]]:
67
77
  """
68
78
  Get contents of a directory or file from the repository.
@@ -74,10 +84,15 @@ class GitHub:
74
84
  List of dictionaries for directories, or content for files
75
85
  """
76
86
  url = f"{self.base_url}/contents/{path}"
77
- response = self.session.get(url, headers=self._headers(), timeout=30)
87
+ params = {}
88
+ if self.branch:
89
+ params["ref"] = self.branch
90
+
91
+ response = self.session.get(url, headers=self._headers(), params=params, timeout=30)
78
92
  response.raise_for_status()
79
93
  return response.json()
80
94
 
95
+
81
96
  def list_files_recursive(self, path: str = "", suffix: Optional[str] = None) -> List[Dict[str, Any]]:
82
97
  """
83
98
  Recursively list files under a given path. Optionally filter by file suffix.
@@ -116,4 +131,4 @@ class GitHub:
116
131
  """
117
132
  response = self.session.get(download_url, headers=self._headers(), timeout=30)
118
133
  response.raise_for_status()
119
- return response.text
134
+ return response.text
@@ -0,0 +1,187 @@
1
+ """
2
+ Created on 2024-06-20
3
+
4
+ @author: wf
5
+ """
6
+
7
+ import re
8
+ from typing import Dict, List
9
+
10
+ from bs4 import BeautifulSoup
11
+ from osprojects.osproject import OsProject, Ticket
12
+ from tqdm import tqdm
13
+
14
+ from snapquery.snapquery_core import NamedQuery, NamedQuerySet
15
+ from snapquery.wd_short_url import ShortUrl
16
+
17
+
18
+ class QLeverUrl(ShortUrl):
19
+ """
20
+ Handles operations related to QLever short URLs.
21
+ """
22
+
23
+ def __init__(self, short_url: str):
24
+ super().__init__(short_url, scheme="https", netloc="qlever.cs.uni-freiburg.de")
25
+ self.sparql = None
26
+
27
+ def read_query(self) -> str:
28
+ """
29
+ Read a query from a QLever short URL.
30
+ It follows redirects and parses the HTML to find the query in the textarea.
31
+
32
+ Returns:
33
+ str: The SPARQL query extracted from the short URL.
34
+ """
35
+ # Ensure we have the final URL (handles redirects)
36
+ self.fetch_final_url(self.short_url)
37
+
38
+ if self.url:
39
+ try:
40
+ # Assuming ShortUrl or its parent has a get_content or similar method.
41
+ # If not, specific request logic would go here.
42
+ # Using get_wikitext as per your snippet pattern, assuming it returns page content.
43
+ text = self.get_wikitext(self.url)
44
+ if text:
45
+ soup = BeautifulSoup(text, "html.parser")
46
+ query_element = soup.find("textarea", {"id": "query"})
47
+ if query_element and query_element.text:
48
+ self.sparql = query_element.text.strip()
49
+ except Exception as ex:
50
+ self.error = ex
51
+ return self.sparql
52
+
53
+
54
+ class QLever:
55
+ """
56
+ Handle https://github.com/ad-freiburg/qlever specifics
57
+ and interaction with QLever instances.
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ with_progress: bool = True,
63
+ debug: bool = False,
64
+ ):
65
+ """
66
+ constructor
67
+ """
68
+ self.url = "https://github.com/ad-freiburg/qlever"
69
+ self.with_progress = with_progress
70
+ self.debug = debug
71
+ # Regex pattern to find URLs starting with the specified prefix
72
+ self.wd_url_pattern = re.compile(r"https://(?:qlever\.cs\.uni-freiburg\.de|qlever\.dev)/wikidata/[A-Za-z0-9]+")
73
+ self.osproject = OsProject.fromUrl(self.url)
74
+
75
+ def wd_urls_for_ticket(self, ticket: Ticket) -> List[str]:
76
+ """
77
+ Extracts and returns all URLs from a ticket's body and comments
78
+ that match the QLever Wikidata pattern.
79
+ """
80
+ extracted_urls = []
81
+
82
+ # Extract URLs from the ticket body
83
+ if ticket.body:
84
+ found_urls = self.wd_url_pattern.findall(ticket.body)
85
+ extracted_urls.extend(found_urls)
86
+
87
+ # Fetch and extract URLs from comments
88
+ comments = self.osproject.getComments(ticket.number)
89
+ for comment in comments:
90
+ if "body" in comment:
91
+ found_urls = self.wd_url_pattern.findall(comment["body"])
92
+ extracted_urls.extend(found_urls)
93
+
94
+ # distinct list
95
+ ticket_urls = list(set(extracted_urls))
96
+ return ticket_urls
97
+
98
+ def get_issues_query_set(self, limit: int = None, progress: bool = False) -> NamedQuerySet:
99
+ """
100
+ Orchestrates the retrieval of tickets, extraction of URLs,
101
+ resolving of SPARQL queries, and creation of a NamedQuerySet.
102
+
103
+ Args:
104
+ limit (int): Max number of tickets to process.
105
+ progress (bool): Whether to show a CLI progress bar.
106
+
107
+ Returns:
108
+ NamedQuerySet: The populated query set.
109
+ """
110
+ # Get the full dictionary
111
+ tickets_map = self.osproject.getAllTickets(limit=limit)
112
+ items_iterable = tickets_map.items()
113
+
114
+ if progress:
115
+ iterator = tqdm(
116
+ items_iterable, total=len(tickets_map), desc="Scanning tickets for QLever URLs", unit="ticket"
117
+ )
118
+ else:
119
+ iterator = items_iterable
120
+
121
+ ticket_dict = {}
122
+
123
+ # 1. Scan tickets for URLs
124
+ for i, (t_id, ticket) in enumerate(iterator):
125
+ msg = f"fetching short urls for ticket {i}:#{t_id} {ticket.title}"
126
+ if self.debug:
127
+ print(msg)
128
+ urls = self.wd_urls_for_ticket(ticket)
129
+ if urls:
130
+ ticket_dict[ticket] = urls
131
+
132
+ # 2. Convert to NamedQuerySet
133
+ # (This resolves the short URLs to actual SPARQL)
134
+ nq_set = self.named_queries_for_tickets(ticket_dict)
135
+ return nq_set
136
+
137
+ def named_queries_for_tickets(self, ticket_dict: Dict[Ticket, List[str]]) -> NamedQuerySet:
138
+ """
139
+ Create named queries for each ticket's extracted URLs.
140
+
141
+ Args:
142
+ ticket_dict (dict): Dictionary mapping tickets to a list of URLs.
143
+
144
+ Returns:
145
+ NamedQuerySet: A set of named queries generated from the URLs.
146
+ """
147
+ named_query_set = NamedQuerySet(
148
+ domain="qlever.dev",
149
+ namespace="issues.wikidata",
150
+ target_graph_name="wikidata",
151
+ )
152
+
153
+ total_urls = sum(len(urls) for urls in ticket_dict.values())
154
+
155
+ # We might want progress here if there are many URLs to resolve
156
+ if self.with_progress and total_urls > 0:
157
+ pbar = tqdm(total=total_urls, desc="Resolving QLever URLs", unit="url")
158
+ else:
159
+ pbar = None
160
+
161
+ for ticket, urls in ticket_dict.items():
162
+ for i, url in enumerate(urls, 1):
163
+ # Resolve the Short URL
164
+ qlever_url = QLeverUrl(url)
165
+ sparql = qlever_url.read_query()
166
+
167
+ if sparql:
168
+ # Construct the NamedQuery
169
+ query = NamedQuery(
170
+ domain=named_query_set.domain,
171
+ name=f"Issue{ticket.number}_query{i}",
172
+ namespace=named_query_set.namespace,
173
+ url=url,
174
+ sparql=sparql,
175
+ title=f"QLever Issue #{ticket.number} Query {i}",
176
+ description=ticket.title,
177
+ comment=f"Source: Ticket {ticket.url}, QLever Link: {url}",
178
+ )
179
+ named_query_set.queries.append(query)
180
+
181
+ if pbar:
182
+ pbar.update(1)
183
+
184
+ if pbar:
185
+ pbar.close()
186
+
187
+ return named_query_set
@@ -0,0 +1,246 @@
1
+ """
2
+ Created on 2025-12-16
3
+
4
+ @author: wf
5
+ """
6
+ import os
7
+ import re
8
+ import textwrap
9
+ from typing import Optional, Dict, Any, List
10
+
11
+ from lodstorage.params import Param
12
+ from lodstorage.query import Query
13
+ from snapquery.snapquery_core import NamedQuerySet, NamedQuery
14
+
15
+
16
+ class QueryParameterizer:
17
+ """
18
+ Parameterizer for Scholia Query Sets.
19
+ Transforms Jinja2-based Scholia templates into standard
20
+ LoDStorage Parameterized Queries.
21
+ """
22
+
23
+ # Replacement for Scholia's label macro
24
+ LABEL_SERVICE = 'SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }'
25
+
26
+ # Defaults for specific Scholia Aspects
27
+ # derived from the prompt list
28
+ ASPECT_DEFAULTS = {
29
+ "author": "Q80", # Tim Berners-Lee
30
+ "authors": "Q80",
31
+ "award": "Q616568", # Turing Award
32
+ "catalogue": "Q2463660", # British Library catalogue
33
+ "chemical": "Q18216", # Aspirin
34
+ "cito": "Q1054942", # citation intent
35
+ "clinical": "Q12136", # Common Cold (disease context)
36
+ "complex": "Q417724", # Protein complex
37
+ "countries": "Q183", # Germany
38
+ "country": "Q183",
39
+ "dataset": "Q1172284", # Data set
40
+ "disease": "Q12136", # Common Cold
41
+ "event": "Q2020153", # WWW Conference
42
+ "gene": "Q7191", # p53
43
+ "index": "Q1", # Universe (generic fallback)
44
+ "language": "Q1860", # English
45
+ "lexeme": "Q151326", # Word
46
+ "license": "Q20007257", # CC BY 4.0
47
+ "location": "Q64", # Berlin
48
+ "ontology": "Q324254", # Ontology
49
+ "organization": "Q95", # Google
50
+ "organizations": "Q95",
51
+ "pathway": "Q26487", # Metabolic pathway
52
+ "podcast": "Q24634210", # The Ozymandias Project
53
+ "printer": "Q11226", # Printer (the device/person)
54
+ "project": "Q5562725", # WikiProject
55
+ "property": "P31", # Instance of
56
+ "protein": "Q11060", # Green fluorescent protein
57
+ "publisher": "Q203019", # Elsevier
58
+ "retraction": "Q45263695", # Retracted paper example
59
+ "series": "Q245209", # Book series
60
+ "software": "Q28865", # Python
61
+ "sponsor": "Q95", # Google
62
+ "taxon": "Q140", # Lion
63
+ "topic": "Q21198", # Computer Science
64
+ "topics": "Q21198",
65
+ "use": "Q1",
66
+ "uses": "Q1",
67
+ "venue": "Q180445", # Nature (Journal)
68
+ "venues": "Q180445",
69
+ "wikiproject": "Q5562725",
70
+ "work": "Q13442814", # Scientific Article
71
+ "works": "Q13442814"
72
+ }
73
+
74
+ def __init__(self, debug: bool = False):
75
+ self.debug = debug
76
+
77
+ def _infer_param_details(self, var_name: str, aspect: Optional[str] = None) -> Dict[str, Any]:
78
+ """
79
+ Heuristics to determine type and default value based on variable name
80
+ and the query aspect (category).
81
+
82
+ Args:
83
+ var_name: The variable name found in the jinja template (e.g. 'q', 'target').
84
+ aspect: The scholia aspect derived from the query name (e.g. 'venue', 'author').
85
+ """
86
+ # Global Default
87
+ details = {"type": "string", "default": "Q80"}
88
+
89
+ # 1. Identify Type based on variable name
90
+ if var_name in ['q', 'target', 'item', 'person', 'work', 'venue', 'gene', 'disease']:
91
+ details["type"] = "WikidataItem"
92
+
93
+ # 2. Assign Default Value based on Aspect
94
+ if aspect and aspect in self.ASPECT_DEFAULTS:
95
+ details["default"] = self.ASPECT_DEFAULTS[aspect]
96
+
97
+ # 3. Fallback defaults if aspect is missing or generic
98
+ elif var_name == 'person':
99
+ details["default"] = "Q80" # Tim Berners-Lee
100
+ elif var_name == 'work':
101
+ details["default"] = "Q13442814"
102
+ else:
103
+ details["default"] = "Q80"
104
+
105
+ elif var_name in ['p', 'property']:
106
+ details["type"] = "WikidataProperty"
107
+ details["default"] = "P31"
108
+ elif var_name in ['limit']:
109
+ details["type"] = "int"
110
+ details["default"] = 10
111
+ elif var_name in ['lang', 'language']:
112
+ details["type"] = "string"
113
+ details["default"] = "en"
114
+
115
+ return details
116
+
117
+ def get_queries_by_aspect(self,query_list:List[Query])->Dict[str,List[Query]]:
118
+ """
119
+ get queries by aspect
120
+ """
121
+ queries_by_aspect = {}
122
+ for query in query_list:
123
+ aspect=self.get_aspect(query.name)
124
+ if aspect in self.ASPECT_DEFAULTS:
125
+ if aspect not in queries_by_aspect:
126
+ queries_by_aspect[aspect] = []
127
+ queries_by_aspect[aspect].append(query)
128
+ return queries_by_aspect
129
+
130
+ def store_query_list(self,query_list:List[Query],tag:str,path:str="/tmp/queries"):
131
+ """
132
+ Store query list sorted by aspect into separate YAML files.
133
+
134
+ Args:
135
+ query_list (List[Query]): List of Query objects with aspect metadata.
136
+ tag (str): Tag to include in filename.
137
+ path (str): Directory path where YAML files will be stored.
138
+ """
139
+ queries_by_aspect=self.get_queries_by_aspect(query_list)
140
+ # Create directory if it doesn't exist
141
+ os.makedirs(path, exist_ok=True)
142
+
143
+ # Store each aspect's queries in a separate YAML file
144
+ for aspect, queries in sorted(queries_by_aspect.items()):
145
+ filename = f"{tag}_{aspect}_queries.yaml"
146
+ filepath = os.path.join(path, filename)
147
+
148
+ with open(filepath, "w", encoding="utf-8") as f:
149
+ for query in queries:
150
+ # work around https://github.com/yaml/pyyaml/issues/411
151
+ if query.sparql:
152
+ clean_sparql = textwrap.dedent(query.sparql).strip()
153
+ yaml_str=query.to_yaml()
154
+ f.write(yaml_str)
155
+ pass
156
+
157
+
158
+ def parameterize_query_set(self, query_set: NamedQuerySet) -> List[Query]:
159
+ """
160
+ Parameterize the given query set to produce a list of LoDStorage queries.
161
+
162
+ Args:
163
+ query_set (NamedQuerySet): The set of named queries to transform.
164
+
165
+ Returns:
166
+ List[Query]: A list of transformed LoDStorage Query objects.
167
+ """
168
+ query_list = []
169
+ for nq in query_set.queries:
170
+ query = self.parameterize_query(nq)
171
+ query_list.append(query)
172
+ return query_list
173
+
174
+ def get_aspect(self,name:str)->str:
175
+ aspect = None
176
+ if name:
177
+ # Split by hyphen first, as that is the standard separator in the example
178
+ parts = re.split(r'[-_]', name)
179
+ if parts:
180
+ aspect = parts[0].lower()
181
+ return aspect
182
+
183
+ def parameterize_query(self, nq: NamedQuery) -> Query:
184
+ """
185
+ Parameterize a single named query.
186
+
187
+ 1. Identifies the "Aspect" from the query name (e.g. 'venue-...' -> 'venue').
188
+ 2. Cleans Jinja2 specific imports.
189
+ 3. Replaces Scholia Macros with standard SPARQL.
190
+ 4. Detects variables and creates Param objects using aspect-specific defaults.
191
+
192
+ Args:
193
+ nq (NamedQuery): The named query input.
194
+
195
+ Returns:
196
+ Query: The transformed LoDStorage Query object.
197
+ """
198
+ # 0. Determine Aspect
199
+ # Naming convention: {aspect}-{subname} or {aspect}_{subname}
200
+ # e.g., "venue-curation_missing-author-item" -> "venue"
201
+ aspect=self.get_aspect(nq.name)
202
+
203
+ raw_sparql = nq.sparql
204
+
205
+ # 1. Remove Jinja imports
206
+ # e.g., {% import 'sparql-helpers.sparql' as sparql_helpers -%}
207
+ txt = re.sub(r'\{%\s*import\s*.*?%\}', '', raw_sparql, flags=re.DOTALL)
208
+
209
+ # 2. Replace Label/Description Macros
210
+ # Matches: {{ sparql_helpers.labels(["?mol"], languages) }}
211
+ txt = re.sub(
212
+ r"\{\{\s*sparql_helpers\.(labels|descriptions).*?\}\}",
213
+ self.LABEL_SERVICE,
214
+ txt,
215
+ flags=re.DOTALL
216
+ )
217
+
218
+ # 3. Detect Variables to build Param list
219
+ found_vars = set(re.findall(r"\{\{\s*([a-zA-Z0-9_]+)\s*\}\}", txt))
220
+ found_vars.discard('sparql_helpers')
221
+
222
+ # Create lodstorage Query object
223
+ clean_sparql = txt.strip()
224
+
225
+ query_obj = Query(
226
+ name=nq.name,
227
+ query=clean_sparql,
228
+ title=nq.title,
229
+ description=nq.description,
230
+ lang='sparql'
231
+ )
232
+
233
+ # 4. Attach Parameters with Aspect context
234
+ param_list = []
235
+ for var in sorted(found_vars):
236
+ details = self._infer_param_details(var, aspect=aspect)
237
+ p = Param(
238
+ name=var,
239
+ type=details["type"],
240
+ default_value=details["default"]
241
+ )
242
+ param_list.append(p)
243
+
244
+ query_obj.param_list = param_list
245
+
246
+ return query_obj