sparql-cli 0.1.0__py3-none-any.whl
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.
- sparql/__init__.py +6 -0
- sparql/__main__.py +6 -0
- sparql/_version.py +1 -0
- sparql/cli/__init__.py +3 -0
- sparql/cli/commands/__init__.py +1 -0
- sparql/cli/commands/config.py +143 -0
- sparql/cli/commands/convenience.py +886 -0
- sparql/cli/commands/query.py +210 -0
- sparql/cli/main.py +160 -0
- sparql/cli/output.py +12 -0
- sparql/core/__init__.py +0 -0
- sparql/core/client.py +90 -0
- sparql/core/config.py +244 -0
- sparql/core/exceptions.py +34 -0
- sparql/core/exit_codes.py +17 -0
- sparql/core/logging.py +43 -0
- sparql/core/models.py +49 -0
- sparql/core/monitoring.py +37 -0
- sparql/core/prefixes.py +61 -0
- sparql/core/query_source.py +55 -0
- sparql/formatters/__init__.py +37 -0
- sparql/formatters/base.py +76 -0
- sparql/formatters/csv.py +89 -0
- sparql/formatters/json.py +100 -0
- sparql/formatters/table.py +72 -0
- sparql_cli-0.1.0.dist-info/METADATA +435 -0
- sparql_cli-0.1.0.dist-info/RECORD +30 -0
- sparql_cli-0.1.0.dist-info/WHEEL +4 -0
- sparql_cli-0.1.0.dist-info/entry_points.txt +2 -0
- sparql_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
"""Convenience commands for common SPARQL exploration tasks (Phase 5)."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from sparql._version import __version__
|
|
9
|
+
from sparql.cli.output import OutputFormat
|
|
10
|
+
from sparql.core.client import SPARQLClient
|
|
11
|
+
from sparql.core.config import AuthType, load_config, resolve_config
|
|
12
|
+
from sparql.core.exceptions import ConfigError, NetworkError
|
|
13
|
+
from sparql.core.exceptions import TimeoutError as SPARQLTimeoutError
|
|
14
|
+
from sparql.core.exit_codes import ExitCode
|
|
15
|
+
from sparql.core.logging import get_logger
|
|
16
|
+
from sparql.core.prefixes import PrefixResolver
|
|
17
|
+
from sparql.formatters import (
|
|
18
|
+
CSVFormatter,
|
|
19
|
+
Formatter,
|
|
20
|
+
JSONFormatter,
|
|
21
|
+
TableFormatter,
|
|
22
|
+
TSVFormatter,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _detect_default_format() -> OutputFormat:
|
|
27
|
+
if sys.stdout.isatty():
|
|
28
|
+
return OutputFormat.table
|
|
29
|
+
return OutputFormat.tsv
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _get_formatter(
|
|
33
|
+
output_format: OutputFormat,
|
|
34
|
+
prefix_resolver: PrefixResolver | None = None,
|
|
35
|
+
) -> Formatter:
|
|
36
|
+
if output_format == OutputFormat.json:
|
|
37
|
+
return JSONFormatter(jsonl=False, prefix_resolver=prefix_resolver)
|
|
38
|
+
if output_format == OutputFormat.jsonl:
|
|
39
|
+
return JSONFormatter(jsonl=True, prefix_resolver=prefix_resolver)
|
|
40
|
+
if output_format == OutputFormat.table:
|
|
41
|
+
return TableFormatter(max_width=60, prefix_resolver=prefix_resolver)
|
|
42
|
+
if output_format == OutputFormat.csv:
|
|
43
|
+
return CSVFormatter(skip_header=False, prefix_resolver=prefix_resolver)
|
|
44
|
+
if output_format == OutputFormat.tsv:
|
|
45
|
+
return TSVFormatter(skip_header=False, prefix_resolver=prefix_resolver)
|
|
46
|
+
# Default to JSON
|
|
47
|
+
return JSONFormatter(jsonl=False, prefix_resolver=prefix_resolver)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _get_global_options(
|
|
51
|
+
ctx: typer.Context | None,
|
|
52
|
+
) -> tuple[str | None, str | None, bool, str | None]:
|
|
53
|
+
if ctx and ctx.obj:
|
|
54
|
+
return (
|
|
55
|
+
ctx.obj.get("profile"),
|
|
56
|
+
ctx.obj.get("endpoint"),
|
|
57
|
+
ctx.obj.get("show_graphs", False),
|
|
58
|
+
ctx.obj.get("graph_filter"),
|
|
59
|
+
)
|
|
60
|
+
return None, None, False, None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _execute_convenience_query(
|
|
64
|
+
query: str,
|
|
65
|
+
endpoint: str | None,
|
|
66
|
+
profile: str | None,
|
|
67
|
+
timeout: float | None,
|
|
68
|
+
format: OutputFormat | None,
|
|
69
|
+
verbose: bool = False,
|
|
70
|
+
ctx: typer.Context | None = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Execute a SPARQL query and output formatted results.
|
|
73
|
+
|
|
74
|
+
Handles configuration resolution, client creation, execution, and error handling.
|
|
75
|
+
"""
|
|
76
|
+
logger = get_logger("convenience")
|
|
77
|
+
|
|
78
|
+
# Merge global options (command-specific takes precedence)
|
|
79
|
+
global_profile, global_endpoint, _, _ = _get_global_options(ctx)
|
|
80
|
+
profile = profile or global_profile
|
|
81
|
+
endpoint = endpoint or global_endpoint
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
config = load_config()
|
|
85
|
+
resolved = resolve_config(
|
|
86
|
+
config,
|
|
87
|
+
profile=profile,
|
|
88
|
+
cli_endpoint=endpoint,
|
|
89
|
+
cli_timeout=timeout,
|
|
90
|
+
)
|
|
91
|
+
except ConfigError as e:
|
|
92
|
+
typer.echo(f"Config error: {e}", err=True)
|
|
93
|
+
raise typer.Exit(ExitCode.CONFIG_ERROR) from e
|
|
94
|
+
|
|
95
|
+
if verbose:
|
|
96
|
+
typer.echo(f"Endpoint: {resolved.endpoint}", err=True)
|
|
97
|
+
typer.echo(f"Timeout: {resolved.timeout}s", err=True)
|
|
98
|
+
typer.echo("Query:", err=True)
|
|
99
|
+
typer.echo(query, err=True)
|
|
100
|
+
typer.echo("---", err=True)
|
|
101
|
+
|
|
102
|
+
logger.debug(
|
|
103
|
+
"endpoint.resolved",
|
|
104
|
+
endpoint=resolved.endpoint,
|
|
105
|
+
timeout=resolved.timeout,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
client = SPARQLClient(
|
|
109
|
+
endpoint_url=resolved.endpoint,
|
|
110
|
+
timeout=resolved.timeout,
|
|
111
|
+
user_agent=resolved.user_agent or f"sparql-cli/{__version__}",
|
|
112
|
+
username=resolved.username,
|
|
113
|
+
password=resolved.password,
|
|
114
|
+
digest_auth=resolved.auth_type == AuthType.DIGEST,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
output_format = format if format is not None else _detect_default_format()
|
|
118
|
+
|
|
119
|
+
# Create prefix resolver if prefixes configured
|
|
120
|
+
prefix_resolver = PrefixResolver(config.prefixes) if config.prefixes else None
|
|
121
|
+
formatter = _get_formatter(output_format, prefix_resolver)
|
|
122
|
+
|
|
123
|
+
logger.debug("query.execute", query=query, query_bytes=len(query))
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
start_time = time.perf_counter()
|
|
127
|
+
results = client.execute(query)
|
|
128
|
+
result_count = 0
|
|
129
|
+
for line in formatter.format(results):
|
|
130
|
+
typer.echo(line)
|
|
131
|
+
result_count += 1
|
|
132
|
+
elapsed = time.perf_counter() - start_time
|
|
133
|
+
logger.debug(
|
|
134
|
+
"query.complete", duration_s=round(elapsed, 3), results=result_count
|
|
135
|
+
)
|
|
136
|
+
except SPARQLTimeoutError as e:
|
|
137
|
+
typer.echo(f"Timeout: {e}", err=True)
|
|
138
|
+
raise typer.Exit(ExitCode.TIMEOUT) from e
|
|
139
|
+
except NetworkError as e:
|
|
140
|
+
typer.echo(f"Error: {e}", err=True)
|
|
141
|
+
raise typer.Exit(ExitCode.NETWORK_ERROR) from e
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def graphs(
|
|
145
|
+
ctx: typer.Context,
|
|
146
|
+
endpoint: str | None = typer.Option( # noqa: B008
|
|
147
|
+
None,
|
|
148
|
+
"--endpoint",
|
|
149
|
+
"-E",
|
|
150
|
+
help="SPARQL endpoint URL (overrides config)",
|
|
151
|
+
),
|
|
152
|
+
profile: str | None = typer.Option( # noqa: B008
|
|
153
|
+
None,
|
|
154
|
+
"--profile",
|
|
155
|
+
"-P",
|
|
156
|
+
help="Use named endpoint profile from config",
|
|
157
|
+
),
|
|
158
|
+
limit: int = typer.Option( # noqa: B008
|
|
159
|
+
100,
|
|
160
|
+
"--limit",
|
|
161
|
+
"-n",
|
|
162
|
+
help="Maximum number of results (default: 100)",
|
|
163
|
+
),
|
|
164
|
+
timeout: float | None = typer.Option( # noqa: B008
|
|
165
|
+
None,
|
|
166
|
+
"--timeout",
|
|
167
|
+
"-t",
|
|
168
|
+
help="Query timeout in seconds",
|
|
169
|
+
),
|
|
170
|
+
format: OutputFormat | None = typer.Option( # noqa: B008
|
|
171
|
+
None,
|
|
172
|
+
"--format",
|
|
173
|
+
"-f",
|
|
174
|
+
help="Output format (default: table for TTY, tsv for pipes)",
|
|
175
|
+
),
|
|
176
|
+
verbose: bool = typer.Option( # noqa: B008
|
|
177
|
+
False,
|
|
178
|
+
"--verbose",
|
|
179
|
+
"-v",
|
|
180
|
+
help="Show endpoint and query before execution",
|
|
181
|
+
),
|
|
182
|
+
) -> None:
|
|
183
|
+
"""List named graphs in the endpoint.
|
|
184
|
+
|
|
185
|
+
Shows all distinct named graphs that contain data.
|
|
186
|
+
"""
|
|
187
|
+
query = (
|
|
188
|
+
"SELECT DISTINCT ?graph WHERE { GRAPH ?graph { ?s ?p ?o } } "
|
|
189
|
+
f"LIMIT {limit}"
|
|
190
|
+
)
|
|
191
|
+
_execute_convenience_query(query, endpoint, profile, timeout, format, verbose, ctx)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def classes(
|
|
195
|
+
ctx: typer.Context,
|
|
196
|
+
endpoint: str | None = typer.Option( # noqa: B008
|
|
197
|
+
None,
|
|
198
|
+
"--endpoint",
|
|
199
|
+
"-E",
|
|
200
|
+
help="SPARQL endpoint URL (overrides config)",
|
|
201
|
+
),
|
|
202
|
+
profile: str | None = typer.Option( # noqa: B008
|
|
203
|
+
None,
|
|
204
|
+
"--profile",
|
|
205
|
+
"-P",
|
|
206
|
+
help="Use named endpoint profile from config",
|
|
207
|
+
),
|
|
208
|
+
limit: int = typer.Option( # noqa: B008
|
|
209
|
+
100,
|
|
210
|
+
"--limit",
|
|
211
|
+
"-n",
|
|
212
|
+
help="Maximum number of results (default: 100)",
|
|
213
|
+
),
|
|
214
|
+
timeout: float | None = typer.Option( # noqa: B008
|
|
215
|
+
None,
|
|
216
|
+
"--timeout",
|
|
217
|
+
"-t",
|
|
218
|
+
help="Query timeout in seconds",
|
|
219
|
+
),
|
|
220
|
+
format: OutputFormat | None = typer.Option( # noqa: B008
|
|
221
|
+
None,
|
|
222
|
+
"--format",
|
|
223
|
+
"-f",
|
|
224
|
+
help="Output format (default: table for TTY, tsv for pipes)",
|
|
225
|
+
),
|
|
226
|
+
labels: bool = typer.Option( # noqa: B008
|
|
227
|
+
False,
|
|
228
|
+
"--labels/--no-labels",
|
|
229
|
+
"-l",
|
|
230
|
+
help="Include rdfs:label for each class (slower, not all endpoints)",
|
|
231
|
+
),
|
|
232
|
+
show_graphs: bool = typer.Option( # noqa: B008
|
|
233
|
+
False,
|
|
234
|
+
"--graphs/--no-graphs",
|
|
235
|
+
"-g",
|
|
236
|
+
help="Show graph column in output",
|
|
237
|
+
),
|
|
238
|
+
graph_filter: str | None = typer.Option( # noqa: B008
|
|
239
|
+
None,
|
|
240
|
+
"--graph",
|
|
241
|
+
"-G",
|
|
242
|
+
help="Filter to specific named graph URI",
|
|
243
|
+
),
|
|
244
|
+
verbose: bool = typer.Option( # noqa: B008
|
|
245
|
+
False,
|
|
246
|
+
"--verbose",
|
|
247
|
+
"-v",
|
|
248
|
+
help="Show endpoint and query before execution",
|
|
249
|
+
),
|
|
250
|
+
) -> None:
|
|
251
|
+
"""List distinct RDF classes from endpoint.
|
|
252
|
+
|
|
253
|
+
Use --labels to also fetch rdfs:label for each class (if available).
|
|
254
|
+
Use -g to show which graph each class comes from.
|
|
255
|
+
Use -G <uri> to query only that named graph.
|
|
256
|
+
"""
|
|
257
|
+
# Merge global graph options (command-specific takes precedence)
|
|
258
|
+
_, _, global_show_graphs, global_graph_filter = _get_global_options(ctx)
|
|
259
|
+
show_graph = show_graphs or global_show_graphs
|
|
260
|
+
graph_uri = graph_filter or global_graph_filter
|
|
261
|
+
|
|
262
|
+
if labels:
|
|
263
|
+
# Use subquery: first find classes (limited), then lookup labels only for those
|
|
264
|
+
# Include PREFIX declaration for endpoints that require it (e.g., MarkLogic)
|
|
265
|
+
if show_graph:
|
|
266
|
+
query = f"""PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
267
|
+
SELECT ?graph ?class ?label WHERE {{
|
|
268
|
+
{{
|
|
269
|
+
SELECT DISTINCT ?graph ?class
|
|
270
|
+
WHERE {{ GRAPH ?graph {{ ?s a ?class }} }} LIMIT {limit}
|
|
271
|
+
}}
|
|
272
|
+
OPTIONAL {{
|
|
273
|
+
?class rdfs:label ?label .
|
|
274
|
+
FILTER(LANG(?label) = "en" || LANG(?label) = "")
|
|
275
|
+
}}
|
|
276
|
+
}}"""
|
|
277
|
+
elif graph_uri:
|
|
278
|
+
query = f"""PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
279
|
+
SELECT ?class ?label WHERE {{
|
|
280
|
+
{{
|
|
281
|
+
SELECT DISTINCT ?class
|
|
282
|
+
WHERE {{ GRAPH <{graph_uri}> {{ ?s a ?class }} }} LIMIT {limit}
|
|
283
|
+
}}
|
|
284
|
+
OPTIONAL {{
|
|
285
|
+
?class rdfs:label ?label .
|
|
286
|
+
FILTER(LANG(?label) = "en" || LANG(?label) = "")
|
|
287
|
+
}}
|
|
288
|
+
}}"""
|
|
289
|
+
else:
|
|
290
|
+
query = f"""PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
291
|
+
SELECT ?class ?label WHERE {{
|
|
292
|
+
{{
|
|
293
|
+
SELECT DISTINCT ?class WHERE {{ ?s a ?class }} LIMIT {limit}
|
|
294
|
+
}}
|
|
295
|
+
OPTIONAL {{
|
|
296
|
+
?class rdfs:label ?label .
|
|
297
|
+
FILTER(LANG(?label) = "en" || LANG(?label) = "")
|
|
298
|
+
}}
|
|
299
|
+
}}"""
|
|
300
|
+
else:
|
|
301
|
+
if show_graph:
|
|
302
|
+
query = (
|
|
303
|
+
"SELECT DISTINCT ?graph ?class "
|
|
304
|
+
f"WHERE {{ GRAPH ?graph {{ ?s a ?class }} }} LIMIT {limit}"
|
|
305
|
+
)
|
|
306
|
+
elif graph_uri:
|
|
307
|
+
query = (
|
|
308
|
+
"SELECT DISTINCT ?class "
|
|
309
|
+
f"WHERE {{ GRAPH <{graph_uri}> {{ ?s a ?class }} }} LIMIT {limit}"
|
|
310
|
+
)
|
|
311
|
+
else:
|
|
312
|
+
query = f"SELECT DISTINCT ?class WHERE {{ ?s a ?class }} LIMIT {limit}"
|
|
313
|
+
_execute_convenience_query(query, endpoint, profile, timeout, format, verbose, ctx)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def predicates(
|
|
317
|
+
ctx: typer.Context,
|
|
318
|
+
predicate_uri: str | None = typer.Argument( # noqa: B008
|
|
319
|
+
None,
|
|
320
|
+
help="Optional predicate URI to show values for (e.g., rdf:type)",
|
|
321
|
+
),
|
|
322
|
+
endpoint: str | None = typer.Option( # noqa: B008
|
|
323
|
+
None,
|
|
324
|
+
"--endpoint",
|
|
325
|
+
"-E",
|
|
326
|
+
help="SPARQL endpoint URL (overrides config)",
|
|
327
|
+
),
|
|
328
|
+
profile: str | None = typer.Option( # noqa: B008
|
|
329
|
+
None,
|
|
330
|
+
"--profile",
|
|
331
|
+
"-P",
|
|
332
|
+
help="Use named endpoint profile from config",
|
|
333
|
+
),
|
|
334
|
+
limit: int = typer.Option( # noqa: B008
|
|
335
|
+
100,
|
|
336
|
+
"--limit",
|
|
337
|
+
"-n",
|
|
338
|
+
help="Maximum number of results (default: 100)",
|
|
339
|
+
),
|
|
340
|
+
timeout: float | None = typer.Option( # noqa: B008
|
|
341
|
+
None,
|
|
342
|
+
"--timeout",
|
|
343
|
+
"-t",
|
|
344
|
+
help="Query timeout in seconds",
|
|
345
|
+
),
|
|
346
|
+
format: OutputFormat | None = typer.Option( # noqa: B008
|
|
347
|
+
None,
|
|
348
|
+
"--format",
|
|
349
|
+
"-f",
|
|
350
|
+
help="Output format (default: table for TTY, tsv for pipes)",
|
|
351
|
+
),
|
|
352
|
+
labels: bool = typer.Option( # noqa: B008
|
|
353
|
+
False,
|
|
354
|
+
"--labels/--no-labels",
|
|
355
|
+
"-l",
|
|
356
|
+
help="Include rdfs:label (slower, not all endpoints)",
|
|
357
|
+
),
|
|
358
|
+
values_only: bool = typer.Option( # noqa: B008
|
|
359
|
+
False,
|
|
360
|
+
"--values/--subjects",
|
|
361
|
+
help="Show only distinct values (objects), not subjects",
|
|
362
|
+
),
|
|
363
|
+
show_graphs: bool = typer.Option( # noqa: B008
|
|
364
|
+
False,
|
|
365
|
+
"--graphs/--no-graphs",
|
|
366
|
+
"-g",
|
|
367
|
+
help="Show graph column in output",
|
|
368
|
+
),
|
|
369
|
+
graph_filter: str | None = typer.Option( # noqa: B008
|
|
370
|
+
None,
|
|
371
|
+
"--graph",
|
|
372
|
+
"-G",
|
|
373
|
+
help="Filter to specific named graph URI",
|
|
374
|
+
),
|
|
375
|
+
verbose: bool = typer.Option( # noqa: B008
|
|
376
|
+
False,
|
|
377
|
+
"--verbose",
|
|
378
|
+
"-v",
|
|
379
|
+
help="Show endpoint and query before execution",
|
|
380
|
+
),
|
|
381
|
+
) -> None:
|
|
382
|
+
"""List predicates, or show usage of a specific predicate.
|
|
383
|
+
|
|
384
|
+
Without argument: lists all distinct predicates in the endpoint.
|
|
385
|
+
With PREDICATE_URI: shows subject-value pairs using that predicate.
|
|
386
|
+
|
|
387
|
+
Examples:
|
|
388
|
+
sparql predicates # list all predicates
|
|
389
|
+
sparql predicates rdf:type # show who has rdf:type and what values
|
|
390
|
+
sparql predicates rdf:type --values # show only distinct values
|
|
391
|
+
"""
|
|
392
|
+
# Merge global graph options (command-specific takes precedence)
|
|
393
|
+
_, _, global_show_graphs, global_graph_filter = _get_global_options(ctx)
|
|
394
|
+
show_graph = show_graphs or global_show_graphs
|
|
395
|
+
graph_uri = graph_filter or global_graph_filter
|
|
396
|
+
|
|
397
|
+
# Expand prefixed predicate URI if provided
|
|
398
|
+
config = load_config()
|
|
399
|
+
if predicate_uri and config.prefixes:
|
|
400
|
+
resolver = PrefixResolver(config.prefixes)
|
|
401
|
+
predicate_uri = resolver.expand(predicate_uri)
|
|
402
|
+
|
|
403
|
+
if predicate_uri:
|
|
404
|
+
# Show usage of specific predicate
|
|
405
|
+
query = _build_predicate_usage_query(
|
|
406
|
+
predicate_uri, limit, labels, values_only, show_graph, graph_uri
|
|
407
|
+
)
|
|
408
|
+
else:
|
|
409
|
+
# List all predicates
|
|
410
|
+
query = _build_predicate_list_query(limit, labels, show_graph, graph_uri)
|
|
411
|
+
|
|
412
|
+
_execute_convenience_query(query, endpoint, profile, timeout, format, verbose, ctx)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _build_predicate_list_query(
|
|
416
|
+
limit: int, labels: bool, show_graph: bool, graph_uri: str | None
|
|
417
|
+
) -> str:
|
|
418
|
+
if labels:
|
|
419
|
+
if show_graph:
|
|
420
|
+
return f"""PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
421
|
+
SELECT ?graph ?predicate ?label WHERE {{
|
|
422
|
+
{{
|
|
423
|
+
SELECT DISTINCT ?graph ?predicate
|
|
424
|
+
WHERE {{ GRAPH ?graph {{ ?s ?predicate ?o }} }} LIMIT {limit}
|
|
425
|
+
}}
|
|
426
|
+
OPTIONAL {{
|
|
427
|
+
?predicate rdfs:label ?label .
|
|
428
|
+
FILTER(LANG(?label) = "en" || LANG(?label) = "")
|
|
429
|
+
}}
|
|
430
|
+
}}"""
|
|
431
|
+
elif graph_uri:
|
|
432
|
+
return f"""PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
433
|
+
SELECT ?predicate ?label WHERE {{
|
|
434
|
+
{{
|
|
435
|
+
SELECT DISTINCT ?predicate
|
|
436
|
+
WHERE {{ GRAPH <{graph_uri}> {{ ?s ?predicate ?o }} }} LIMIT {limit}
|
|
437
|
+
}}
|
|
438
|
+
OPTIONAL {{
|
|
439
|
+
?predicate rdfs:label ?label .
|
|
440
|
+
FILTER(LANG(?label) = "en" || LANG(?label) = "")
|
|
441
|
+
}}
|
|
442
|
+
}}"""
|
|
443
|
+
else:
|
|
444
|
+
return f"""PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
445
|
+
SELECT ?predicate ?label WHERE {{
|
|
446
|
+
{{
|
|
447
|
+
SELECT DISTINCT ?predicate WHERE {{ ?s ?predicate ?o }} LIMIT {limit}
|
|
448
|
+
}}
|
|
449
|
+
OPTIONAL {{
|
|
450
|
+
?predicate rdfs:label ?label .
|
|
451
|
+
FILTER(LANG(?label) = "en" || LANG(?label) = "")
|
|
452
|
+
}}
|
|
453
|
+
}}"""
|
|
454
|
+
else:
|
|
455
|
+
if show_graph:
|
|
456
|
+
return (
|
|
457
|
+
"SELECT DISTINCT ?graph ?predicate "
|
|
458
|
+
f"WHERE {{ GRAPH ?graph {{ ?s ?predicate ?o }} }} LIMIT {limit}"
|
|
459
|
+
)
|
|
460
|
+
elif graph_uri:
|
|
461
|
+
return (
|
|
462
|
+
"SELECT DISTINCT ?predicate "
|
|
463
|
+
f"WHERE {{ GRAPH <{graph_uri}> {{ ?s ?predicate ?o }} }} LIMIT {limit}"
|
|
464
|
+
)
|
|
465
|
+
else:
|
|
466
|
+
return (
|
|
467
|
+
"SELECT DISTINCT ?predicate "
|
|
468
|
+
f"WHERE {{ ?s ?predicate ?o }} LIMIT {limit}"
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _build_predicate_usage_query(
|
|
473
|
+
predicate_uri: str,
|
|
474
|
+
limit: int,
|
|
475
|
+
labels: bool,
|
|
476
|
+
values_only: bool,
|
|
477
|
+
show_graph: bool,
|
|
478
|
+
graph_uri: str | None,
|
|
479
|
+
) -> str:
|
|
480
|
+
if values_only:
|
|
481
|
+
# Show only distinct values (objects)
|
|
482
|
+
if labels:
|
|
483
|
+
if show_graph:
|
|
484
|
+
return f"""PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
485
|
+
SELECT ?graph ?value ?label WHERE {{
|
|
486
|
+
{{
|
|
487
|
+
SELECT DISTINCT ?graph ?value
|
|
488
|
+
WHERE {{ GRAPH ?graph {{ ?s <{predicate_uri}> ?value }} }} LIMIT {limit}
|
|
489
|
+
}}
|
|
490
|
+
OPTIONAL {{
|
|
491
|
+
?value rdfs:label ?label .
|
|
492
|
+
FILTER(LANG(?label) = "en" || LANG(?label) = "")
|
|
493
|
+
}}
|
|
494
|
+
}}"""
|
|
495
|
+
elif graph_uri:
|
|
496
|
+
return f"""PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
497
|
+
SELECT ?value ?label WHERE {{
|
|
498
|
+
{{
|
|
499
|
+
SELECT DISTINCT ?value
|
|
500
|
+
WHERE {{ GRAPH <{graph_uri}> {{ ?s <{predicate_uri}> ?value }} }} LIMIT {limit}
|
|
501
|
+
}}
|
|
502
|
+
OPTIONAL {{
|
|
503
|
+
?value rdfs:label ?label .
|
|
504
|
+
FILTER(LANG(?label) = "en" || LANG(?label) = "")
|
|
505
|
+
}}
|
|
506
|
+
}}"""
|
|
507
|
+
else:
|
|
508
|
+
return f"""PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
509
|
+
SELECT ?value ?label WHERE {{
|
|
510
|
+
{{
|
|
511
|
+
SELECT DISTINCT ?value
|
|
512
|
+
WHERE {{ ?s <{predicate_uri}> ?value }} LIMIT {limit}
|
|
513
|
+
}}
|
|
514
|
+
OPTIONAL {{
|
|
515
|
+
?value rdfs:label ?label .
|
|
516
|
+
FILTER(LANG(?label) = "en" || LANG(?label) = "")
|
|
517
|
+
}}
|
|
518
|
+
}}"""
|
|
519
|
+
else:
|
|
520
|
+
if show_graph:
|
|
521
|
+
return (
|
|
522
|
+
"SELECT DISTINCT ?graph ?value "
|
|
523
|
+
f"WHERE {{ GRAPH ?graph {{ ?s <{predicate_uri}> ?value }} }} "
|
|
524
|
+
f"LIMIT {limit}"
|
|
525
|
+
)
|
|
526
|
+
elif graph_uri:
|
|
527
|
+
return (
|
|
528
|
+
"SELECT DISTINCT ?value "
|
|
529
|
+
f"WHERE {{ GRAPH <{graph_uri}> {{ ?s <{predicate_uri}> ?value }} }}"
|
|
530
|
+
f" LIMIT {limit}"
|
|
531
|
+
)
|
|
532
|
+
else:
|
|
533
|
+
return (
|
|
534
|
+
"SELECT DISTINCT ?value "
|
|
535
|
+
f"WHERE {{ ?s <{predicate_uri}> ?value }} LIMIT {limit}"
|
|
536
|
+
)
|
|
537
|
+
else:
|
|
538
|
+
# Show subject-value pairs
|
|
539
|
+
if show_graph:
|
|
540
|
+
return (
|
|
541
|
+
"SELECT DISTINCT ?graph ?subject ?value "
|
|
542
|
+
f"WHERE {{ GRAPH ?graph {{ ?subject <{predicate_uri}> ?value }} }} "
|
|
543
|
+
f"LIMIT {limit}"
|
|
544
|
+
)
|
|
545
|
+
elif graph_uri:
|
|
546
|
+
return (
|
|
547
|
+
"SELECT DISTINCT ?subject ?value WHERE "
|
|
548
|
+
f"{{ GRAPH <{graph_uri}> {{ ?subject <{predicate_uri}> ?value }} }}"
|
|
549
|
+
f" LIMIT {limit}"
|
|
550
|
+
)
|
|
551
|
+
else:
|
|
552
|
+
return (
|
|
553
|
+
"SELECT DISTINCT ?subject ?value "
|
|
554
|
+
f"WHERE {{ ?subject <{predicate_uri}> ?value }} LIMIT {limit}"
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def explore(
|
|
559
|
+
ctx: typer.Context,
|
|
560
|
+
uri: str = typer.Argument( # noqa: B008
|
|
561
|
+
...,
|
|
562
|
+
help="URI to explore (finds all triples where this URI appears)",
|
|
563
|
+
),
|
|
564
|
+
endpoint: str | None = typer.Option( # noqa: B008
|
|
565
|
+
None,
|
|
566
|
+
"--endpoint",
|
|
567
|
+
"-E",
|
|
568
|
+
help="SPARQL endpoint URL (overrides config)",
|
|
569
|
+
),
|
|
570
|
+
profile: str | None = typer.Option( # noqa: B008
|
|
571
|
+
None,
|
|
572
|
+
"--profile",
|
|
573
|
+
"-P",
|
|
574
|
+
help="Use named endpoint profile from config",
|
|
575
|
+
),
|
|
576
|
+
limit: int = typer.Option( # noqa: B008
|
|
577
|
+
100,
|
|
578
|
+
"--limit",
|
|
579
|
+
"-n",
|
|
580
|
+
help="Maximum number of results (default: 100)",
|
|
581
|
+
),
|
|
582
|
+
timeout: float | None = typer.Option( # noqa: B008
|
|
583
|
+
None,
|
|
584
|
+
"--timeout",
|
|
585
|
+
"-t",
|
|
586
|
+
help="Query timeout in seconds",
|
|
587
|
+
),
|
|
588
|
+
format: OutputFormat | None = typer.Option( # noqa: B008
|
|
589
|
+
None,
|
|
590
|
+
"--format",
|
|
591
|
+
"-f",
|
|
592
|
+
help="Output format (default: table for TTY, tsv for pipes)",
|
|
593
|
+
),
|
|
594
|
+
show_graphs: bool = typer.Option( # noqa: B008
|
|
595
|
+
False,
|
|
596
|
+
"--graphs/--no-graphs",
|
|
597
|
+
"-g",
|
|
598
|
+
help="Show graph column in output",
|
|
599
|
+
),
|
|
600
|
+
graph_filter: str | None = typer.Option( # noqa: B008
|
|
601
|
+
None,
|
|
602
|
+
"--graph",
|
|
603
|
+
"-G",
|
|
604
|
+
help="Filter to specific named graph URI",
|
|
605
|
+
),
|
|
606
|
+
verbose: bool = typer.Option( # noqa: B008
|
|
607
|
+
False,
|
|
608
|
+
"--verbose",
|
|
609
|
+
"-v",
|
|
610
|
+
help="Show endpoint and query before execution",
|
|
611
|
+
),
|
|
612
|
+
) -> None:
|
|
613
|
+
"""Explore triples where URI appears as subject, predicate, or object.
|
|
614
|
+
|
|
615
|
+
Finds all relationships involving the specified URI in any position.
|
|
616
|
+
|
|
617
|
+
URI can be a full URI or prefixed name (e.g., dbo:Person, wd:Q42).
|
|
618
|
+
Use -g to show which graph each triple comes from.
|
|
619
|
+
Use -G <uri> to query only that named graph.
|
|
620
|
+
"""
|
|
621
|
+
# Merge global graph options (command-specific takes precedence)
|
|
622
|
+
_, _, global_show_graphs, global_graph_filter = _get_global_options(ctx)
|
|
623
|
+
show_graph = show_graphs or global_show_graphs
|
|
624
|
+
graph_uri = graph_filter or global_graph_filter
|
|
625
|
+
|
|
626
|
+
# Expand prefixed names to full URIs
|
|
627
|
+
config = load_config()
|
|
628
|
+
if config.prefixes:
|
|
629
|
+
resolver = PrefixResolver(config.prefixes)
|
|
630
|
+
uri = resolver.expand(uri)
|
|
631
|
+
|
|
632
|
+
if show_graph:
|
|
633
|
+
query = f"""SELECT ?graph ?s ?p ?o WHERE {{
|
|
634
|
+
GRAPH ?graph {{
|
|
635
|
+
{{ <{uri}> ?p ?o . BIND(<{uri}> AS ?s) }}
|
|
636
|
+
UNION
|
|
637
|
+
{{ ?s <{uri}> ?o . BIND(<{uri}> AS ?p) }}
|
|
638
|
+
UNION
|
|
639
|
+
{{ ?s ?p <{uri}> . BIND(<{uri}> AS ?o) }}
|
|
640
|
+
}}
|
|
641
|
+
}} LIMIT {limit}"""
|
|
642
|
+
elif graph_uri:
|
|
643
|
+
query = f"""SELECT ?s ?p ?o WHERE {{
|
|
644
|
+
GRAPH <{graph_uri}> {{
|
|
645
|
+
{{ <{uri}> ?p ?o . BIND(<{uri}> AS ?s) }}
|
|
646
|
+
UNION
|
|
647
|
+
{{ ?s <{uri}> ?o . BIND(<{uri}> AS ?p) }}
|
|
648
|
+
UNION
|
|
649
|
+
{{ ?s ?p <{uri}> . BIND(<{uri}> AS ?o) }}
|
|
650
|
+
}}
|
|
651
|
+
}} LIMIT {limit}"""
|
|
652
|
+
else:
|
|
653
|
+
query = f"""SELECT ?s ?p ?o WHERE {{
|
|
654
|
+
{{ <{uri}> ?p ?o . BIND(<{uri}> AS ?s) }}
|
|
655
|
+
UNION
|
|
656
|
+
{{ ?s <{uri}> ?o . BIND(<{uri}> AS ?p) }}
|
|
657
|
+
UNION
|
|
658
|
+
{{ ?s ?p <{uri}> . BIND(<{uri}> AS ?o) }}
|
|
659
|
+
}} LIMIT {limit}"""
|
|
660
|
+
_execute_convenience_query(query, endpoint, profile, timeout, format, verbose, ctx)
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def objects(
|
|
664
|
+
ctx: typer.Context,
|
|
665
|
+
class_uri: str = typer.Argument( # noqa: B008
|
|
666
|
+
...,
|
|
667
|
+
help="Class URI to list instances of (e.g., dbo:Person, foaf:Agent)",
|
|
668
|
+
),
|
|
669
|
+
endpoint: str | None = typer.Option( # noqa: B008
|
|
670
|
+
None,
|
|
671
|
+
"--endpoint",
|
|
672
|
+
"-E",
|
|
673
|
+
help="SPARQL endpoint URL (overrides config)",
|
|
674
|
+
),
|
|
675
|
+
profile: str | None = typer.Option( # noqa: B008
|
|
676
|
+
None,
|
|
677
|
+
"--profile",
|
|
678
|
+
"-P",
|
|
679
|
+
help="Use named endpoint profile from config",
|
|
680
|
+
),
|
|
681
|
+
limit: int = typer.Option( # noqa: B008
|
|
682
|
+
100,
|
|
683
|
+
"--limit",
|
|
684
|
+
"-n",
|
|
685
|
+
help="Maximum number of results (default: 100)",
|
|
686
|
+
),
|
|
687
|
+
timeout: float | None = typer.Option( # noqa: B008
|
|
688
|
+
None,
|
|
689
|
+
"--timeout",
|
|
690
|
+
"-t",
|
|
691
|
+
help="Query timeout in seconds",
|
|
692
|
+
),
|
|
693
|
+
format: OutputFormat | None = typer.Option( # noqa: B008
|
|
694
|
+
None,
|
|
695
|
+
"--format",
|
|
696
|
+
"-f",
|
|
697
|
+
help="Output format (default: table for TTY, tsv for pipes)",
|
|
698
|
+
),
|
|
699
|
+
labels: bool = typer.Option( # noqa: B008
|
|
700
|
+
False,
|
|
701
|
+
"--labels/--no-labels",
|
|
702
|
+
"-l",
|
|
703
|
+
help="Include rdfs:label for each object (slower, not all endpoints)",
|
|
704
|
+
),
|
|
705
|
+
show_graphs: bool = typer.Option( # noqa: B008
|
|
706
|
+
False,
|
|
707
|
+
"--graphs/--no-graphs",
|
|
708
|
+
"-g",
|
|
709
|
+
help="Show graph column in output",
|
|
710
|
+
),
|
|
711
|
+
graph_filter: str | None = typer.Option( # noqa: B008
|
|
712
|
+
None,
|
|
713
|
+
"--graph",
|
|
714
|
+
"-G",
|
|
715
|
+
help="Filter to specific named graph URI",
|
|
716
|
+
),
|
|
717
|
+
verbose: bool = typer.Option( # noqa: B008
|
|
718
|
+
False,
|
|
719
|
+
"--verbose",
|
|
720
|
+
"-v",
|
|
721
|
+
help="Show endpoint and query before execution",
|
|
722
|
+
),
|
|
723
|
+
) -> None:
|
|
724
|
+
"""List instances (objects) of a given RDF class.
|
|
725
|
+
|
|
726
|
+
CLASS_URI can be a full URI or prefixed name (e.g., dbo:Person, foaf:Agent).
|
|
727
|
+
|
|
728
|
+
Use --labels to also fetch rdfs:label for each instance (if available).
|
|
729
|
+
Use -g to show which graph each instance comes from.
|
|
730
|
+
Use -G <uri> to query only that named graph.
|
|
731
|
+
"""
|
|
732
|
+
# Merge global graph options (command-specific takes precedence)
|
|
733
|
+
_, _, global_show_graphs, global_graph_filter = _get_global_options(ctx)
|
|
734
|
+
show_graph = show_graphs or global_show_graphs
|
|
735
|
+
graph_uri = graph_filter or global_graph_filter
|
|
736
|
+
|
|
737
|
+
# Expand prefixed names to full URIs
|
|
738
|
+
config = load_config()
|
|
739
|
+
if config.prefixes:
|
|
740
|
+
resolver = PrefixResolver(config.prefixes)
|
|
741
|
+
class_uri = resolver.expand(class_uri)
|
|
742
|
+
|
|
743
|
+
if labels:
|
|
744
|
+
if show_graph:
|
|
745
|
+
query = f"""PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
746
|
+
SELECT ?graph ?object ?label WHERE {{
|
|
747
|
+
{{
|
|
748
|
+
SELECT DISTINCT ?graph ?object
|
|
749
|
+
WHERE {{ GRAPH ?graph {{ ?object a <{class_uri}> }} }} LIMIT {limit}
|
|
750
|
+
}}
|
|
751
|
+
OPTIONAL {{
|
|
752
|
+
?object rdfs:label ?label .
|
|
753
|
+
FILTER(LANG(?label) = "en" || LANG(?label) = "")
|
|
754
|
+
}}
|
|
755
|
+
}}"""
|
|
756
|
+
elif graph_uri:
|
|
757
|
+
query = f"""PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
758
|
+
SELECT ?object ?label WHERE {{
|
|
759
|
+
{{
|
|
760
|
+
SELECT DISTINCT ?object
|
|
761
|
+
WHERE {{ GRAPH <{graph_uri}> {{ ?object a <{class_uri}> }} }} LIMIT {limit}
|
|
762
|
+
}}
|
|
763
|
+
OPTIONAL {{
|
|
764
|
+
?object rdfs:label ?label .
|
|
765
|
+
FILTER(LANG(?label) = "en" || LANG(?label) = "")
|
|
766
|
+
}}
|
|
767
|
+
}}"""
|
|
768
|
+
else:
|
|
769
|
+
query = f"""PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
770
|
+
SELECT ?object ?label WHERE {{
|
|
771
|
+
{{
|
|
772
|
+
SELECT DISTINCT ?object WHERE {{ ?object a <{class_uri}> }} LIMIT {limit}
|
|
773
|
+
}}
|
|
774
|
+
OPTIONAL {{
|
|
775
|
+
?object rdfs:label ?label .
|
|
776
|
+
FILTER(LANG(?label) = "en" || LANG(?label) = "")
|
|
777
|
+
}}
|
|
778
|
+
}}"""
|
|
779
|
+
else:
|
|
780
|
+
if show_graph:
|
|
781
|
+
query = (
|
|
782
|
+
"SELECT DISTINCT ?graph ?object "
|
|
783
|
+
f"WHERE {{ GRAPH ?graph {{ ?object a <{class_uri}> }} }} LIMIT {limit}"
|
|
784
|
+
)
|
|
785
|
+
elif graph_uri:
|
|
786
|
+
query = (
|
|
787
|
+
"SELECT DISTINCT ?object "
|
|
788
|
+
f"WHERE {{ GRAPH <{graph_uri}> {{ ?object a <{class_uri}> }} }} "
|
|
789
|
+
f"LIMIT {limit}"
|
|
790
|
+
)
|
|
791
|
+
else:
|
|
792
|
+
query = (
|
|
793
|
+
"SELECT DISTINCT ?object "
|
|
794
|
+
f"WHERE {{ ?object a <{class_uri}> }} LIMIT {limit}"
|
|
795
|
+
)
|
|
796
|
+
_execute_convenience_query(query, endpoint, profile, timeout, format, verbose, ctx)
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def object(
|
|
800
|
+
ctx: typer.Context,
|
|
801
|
+
uri: str = typer.Argument( # noqa: B008
|
|
802
|
+
...,
|
|
803
|
+
help="Instance URI to describe (e.g., dbr:Berlin, wd:Q42)",
|
|
804
|
+
),
|
|
805
|
+
endpoint: str | None = typer.Option( # noqa: B008
|
|
806
|
+
None,
|
|
807
|
+
"--endpoint",
|
|
808
|
+
"-E",
|
|
809
|
+
help="SPARQL endpoint URL (overrides config)",
|
|
810
|
+
),
|
|
811
|
+
profile: str | None = typer.Option( # noqa: B008
|
|
812
|
+
None,
|
|
813
|
+
"--profile",
|
|
814
|
+
"-P",
|
|
815
|
+
help="Use named endpoint profile from config",
|
|
816
|
+
),
|
|
817
|
+
limit: int = typer.Option( # noqa: B008
|
|
818
|
+
100,
|
|
819
|
+
"--limit",
|
|
820
|
+
"-n",
|
|
821
|
+
help="Maximum number of results (default: 100)",
|
|
822
|
+
),
|
|
823
|
+
timeout: float | None = typer.Option( # noqa: B008
|
|
824
|
+
None,
|
|
825
|
+
"--timeout",
|
|
826
|
+
"-t",
|
|
827
|
+
help="Query timeout in seconds",
|
|
828
|
+
),
|
|
829
|
+
format: OutputFormat | None = typer.Option( # noqa: B008
|
|
830
|
+
None,
|
|
831
|
+
"--format",
|
|
832
|
+
"-f",
|
|
833
|
+
help="Output format (default: table for TTY, tsv for pipes)",
|
|
834
|
+
),
|
|
835
|
+
show_graphs: bool = typer.Option( # noqa: B008
|
|
836
|
+
False,
|
|
837
|
+
"--graphs/--no-graphs",
|
|
838
|
+
"-g",
|
|
839
|
+
help="Show graph column in output",
|
|
840
|
+
),
|
|
841
|
+
graph_filter: str | None = typer.Option( # noqa: B008
|
|
842
|
+
None,
|
|
843
|
+
"--graph",
|
|
844
|
+
"-G",
|
|
845
|
+
help="Filter to specific named graph URI",
|
|
846
|
+
),
|
|
847
|
+
verbose: bool = typer.Option( # noqa: B008
|
|
848
|
+
False,
|
|
849
|
+
"--verbose",
|
|
850
|
+
"-v",
|
|
851
|
+
help="Show endpoint and query before execution",
|
|
852
|
+
),
|
|
853
|
+
) -> None:
|
|
854
|
+
"""Show all predicates and values for a specific instance.
|
|
855
|
+
|
|
856
|
+
URI can be a full URI or prefixed name (e.g., dbr:Berlin, wd:Q42).
|
|
857
|
+
|
|
858
|
+
Returns all properties (predicate-value pairs) of the given resource.
|
|
859
|
+
Use -g to see which named graph each triple comes from.
|
|
860
|
+
Use -G <uri> to query only that named graph.
|
|
861
|
+
"""
|
|
862
|
+
# Merge global graph options (command-specific takes precedence)
|
|
863
|
+
_, _, global_show_graphs, global_graph_filter = _get_global_options(ctx)
|
|
864
|
+
show_graph = show_graphs or global_show_graphs
|
|
865
|
+
graph_uri = graph_filter or global_graph_filter
|
|
866
|
+
|
|
867
|
+
# Expand prefixed names to full URIs
|
|
868
|
+
config = load_config()
|
|
869
|
+
if config.prefixes:
|
|
870
|
+
resolver = PrefixResolver(config.prefixes)
|
|
871
|
+
uri = resolver.expand(uri)
|
|
872
|
+
|
|
873
|
+
if show_graph:
|
|
874
|
+
query = f"""SELECT DISTINCT ?graph ?predicate ?value WHERE {{
|
|
875
|
+
GRAPH ?graph {{ <{uri}> ?predicate ?value }}
|
|
876
|
+
}} LIMIT {limit}"""
|
|
877
|
+
elif graph_uri:
|
|
878
|
+
query = f"""SELECT DISTINCT ?predicate ?value WHERE {{
|
|
879
|
+
GRAPH <{graph_uri}> {{ <{uri}> ?predicate ?value }}
|
|
880
|
+
}} LIMIT {limit}"""
|
|
881
|
+
else:
|
|
882
|
+
query = (
|
|
883
|
+
"SELECT DISTINCT ?predicate ?value "
|
|
884
|
+
f"WHERE {{ <{uri}> ?predicate ?value }} LIMIT {limit}"
|
|
885
|
+
)
|
|
886
|
+
_execute_convenience_query(query, endpoint, profile, timeout, format, verbose, ctx)
|