linked-data-python 0.6.1__tar.gz → 0.6.3__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.
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/PKG-INFO +1 -1
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/lsp/islanddoc.py +19 -11
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/pygments_lexer.py +20 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/runtime.py +24 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/transpiler/core.py +84 -1
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/linked_data_python.egg-info/PKG-INFO +1 -1
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/linked_data_python.egg-info/SOURCES.txt +1 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/pyproject.toml +1 -1
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_current_graph.py +43 -0
- linked_data_python-0.6.3/tests/test_lexing.py +53 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_prefix_scoping.py +47 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_sparql_island.py +48 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/LICENSE.md +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/README.md +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/__init__.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/__main__.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/backend.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/build.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/console.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/debug.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/formatter.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/importer.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/lsp/__init__.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/lsp/__main__.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/lsp/backend.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/lsp/hover.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/lsp/rpc.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/lsp/server.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/lsp/translate.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/sparql.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/transpiler/__init__.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/transpiler/errors.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/ldpy/transpiler/linemap.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/linked_data_python.egg-info/dependency_links.txt +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/linked_data_python.egg-info/entry_points.txt +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/linked_data_python.egg-info/requires.txt +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/linked_data_python.egg-info/top_level.txt +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/setup.cfg +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_bench.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_bindings.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_build.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_call_suffix.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_charsets.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_coercion.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_console.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_debug.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_debug_stepping.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_disambiguation.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_docs.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_docstrings.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_examples.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_formatter.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_generator.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_graphs.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_hover_parts.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_identity.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_import_hook.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_islanddoc.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_islands_terms.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_linemap.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_lsp_rpc.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_lsp_server.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_lsp_translate.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_match_island.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_micropython_subset.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_prefix_import.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_pygments_lexer.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_scope_modifiers.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_semantics.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_sourcemap_v3.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_sparql_expr.py +0 -0
- {linked_data_python-0.6.1 → linked_data_python-0.6.3}/tests/test_target_micropython.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linked-data-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
4
4
|
Summary: Python extended with Semantic Web primitives: IRIs, RDF literals and Turtle-notation graphs as expressions, transpiled by island parsing.
|
|
5
5
|
Author-email: Maxime Lefrançois <maxime.lefrancois@emse.fr>
|
|
6
6
|
License: MIT
|
|
@@ -45,11 +45,13 @@ ISLANDS = {
|
|
|
45
45
|
# -- declarations -------------------------------------------------------
|
|
46
46
|
|
|
47
47
|
"prefix": IslandDoc(
|
|
48
|
-
"(declaration) @prefix ex: <IRI> .",
|
|
48
|
+
"(declaration) @prefix ex: <IRI> [as EX] .",
|
|
49
49
|
"Binds `ex:` to a namespace IRI for the rest of the enclosing block. "
|
|
50
|
-
"A prefix is lexical:
|
|
51
|
-
"
|
|
52
|
-
"
|
|
50
|
+
"A prefix is lexical: `ex:` on its own is never a value. When the "
|
|
51
|
+
"code needs the Namespace as an OBJECT — to `bind()` it on a graph, "
|
|
52
|
+
"to export it, to put it in a registry — `as EX` binds that object "
|
|
53
|
+
"to a Python name too. Declaring the prefix again in a deeper block "
|
|
54
|
+
"shadows it, the way a Python name would.",
|
|
53
55
|
_DECLS, "prefix-bind-a-prefix"),
|
|
54
56
|
|
|
55
57
|
"base": IslandDoc(
|
|
@@ -69,7 +71,9 @@ ISLANDS = {
|
|
|
69
71
|
"(declaration) @graph EXPR | @graph as NAME -> Graph",
|
|
70
72
|
"Designates the current graph for the block — the one `+{ }`, `-{ }` "
|
|
71
73
|
"and a receiver-less `m{ }` act on. `as NAME` creates a fresh graph "
|
|
72
|
-
"and binds it to NAME; `global` and `nonlocal` widen the scope."
|
|
74
|
+
"and binds it to NAME; `global` and `nonlocal` widen the scope. "
|
|
75
|
+
"Either way the graph inherits the block's prefixes, so it "
|
|
76
|
+
"serialises with them.",
|
|
73
77
|
_CURRENT, "graph-designate-or-create"),
|
|
74
78
|
|
|
75
79
|
"bindings-decl": IslandDoc(
|
|
@@ -134,7 +138,9 @@ ISLANDS = {
|
|
|
134
138
|
"A SPARQL query or update, parsed when the file is transpiled rather "
|
|
135
139
|
"than at run time. `{expr}` in term position becomes an initial "
|
|
136
140
|
"binding — never string pasting, so nothing here can be injected. "
|
|
137
|
-
"
|
|
141
|
+
"Calling it on a graph gives a LAZY query; `.execute()` is what "
|
|
142
|
+
"forces it — for an update, and for any query whose rdflib `Result` "
|
|
143
|
+
"you then call a method on.",
|
|
138
144
|
_QUERY, "s-a-sparql-query"),
|
|
139
145
|
|
|
140
146
|
# -- terms --------------------------------------------------------------
|
|
@@ -169,10 +175,10 @@ ISLANDS = {
|
|
|
169
175
|
|
|
170
176
|
"firi": IslandDoc(
|
|
171
177
|
"(term) f<...{expr}...> -> URIRef",
|
|
172
|
-
"A formatted IRI: the braces interpolate as in an f-string, the "
|
|
173
|
-
"result is
|
|
174
|
-
"
|
|
175
|
-
"
|
|
178
|
+
"A formatted IRI: the braces interpolate as in an f-string, and the "
|
|
179
|
+
"result is resolved against `@base` if it is relative. It does NOT "
|
|
180
|
+
"percent-encode — the shape of the IRI is yours. To mint one from "
|
|
181
|
+
"data that may hold a space or a slash, use `e<...>`, which encodes.",
|
|
176
182
|
_TERMS, "formatted-iris-f"),
|
|
177
183
|
|
|
178
184
|
"fnode": IslandDoc(
|
|
@@ -202,7 +208,9 @@ ISLANDS = {
|
|
|
202
208
|
"eiri": IslandDoc(
|
|
203
209
|
"(expression) e<...{?var}...> -> Expr",
|
|
204
210
|
"A deferred IRI: `f<...>`'s interpolation, except that the holes are "
|
|
205
|
-
"SPARQL expressions re-evaluated for each set of bindings
|
|
211
|
+
"SPARQL expressions re-evaluated for each set of bindings — and that "
|
|
212
|
+
"it percent-encodes what it interpolates. This is the form for "
|
|
213
|
+
"minting an IRI from a data column.",
|
|
206
214
|
_SPARQL_EXPR, "deferred-iris-e"),
|
|
207
215
|
}
|
|
208
216
|
|
|
@@ -254,6 +254,10 @@ _NO_CURRENT_GRAPH = re.compile(r"sans graphe courant")
|
|
|
254
254
|
#: input from looping.
|
|
255
255
|
_MAX_SYNTHETIC = 24
|
|
256
256
|
|
|
257
|
+
#: `as EX` closing a prefix declaration (record ldpy/027).
|
|
258
|
+
_PREFIX_AS_RE = re.compile(
|
|
259
|
+
r"(>)(\s+)(as)(\s+)([A-Za-z_]\w*)(?=\s*\.)")
|
|
260
|
+
|
|
257
261
|
#: Namespace of the synthetic prefixes. Never resolved — the map is thrown
|
|
258
262
|
#: away after the tokens are read — but it has to be a legal IRI.
|
|
259
263
|
_SYNTHETIC_NS = "urn:x-ldpy-highlight:"
|
|
@@ -400,10 +404,26 @@ class LdpyLexer(Lexer):
|
|
|
400
404
|
|
|
401
405
|
# -- the ldpy-specific regions of an island body -------------------------
|
|
402
406
|
|
|
407
|
+
def _emit_prefix_as(self, text, m):
|
|
408
|
+
"""The `as EX` of a prefix declaration: a keyword and a Python name."""
|
|
409
|
+
def emit(offset):
|
|
410
|
+
yield offset + m.start(3), Keyword, m.group(3)
|
|
411
|
+
yield offset + m.end(3), Text, m.group(4)
|
|
412
|
+
yield offset + m.start(5), Name.Variable, m.group(5)
|
|
413
|
+
return emit
|
|
414
|
+
|
|
403
415
|
def _regions(self, text, flavour, is_query=False):
|
|
404
416
|
"""Locate what no Turtle or SPARQL lexer can read: interpolations and
|
|
405
417
|
nested islands. Each becomes a (start, end, emit) region."""
|
|
406
418
|
regions = []
|
|
419
|
+
if flavour == "turtle":
|
|
420
|
+
# `@prefix ex: <IRI> as EX .` (record ldpy/027): Turtle's own
|
|
421
|
+
# lexer knows the directive but not the clause, and would mark it
|
|
422
|
+
# Error. Mask it and emit it ourselves.
|
|
423
|
+
m = _PREFIX_AS_RE.search(text)
|
|
424
|
+
if m:
|
|
425
|
+
regions.append((m.start(3), m.end(5),
|
|
426
|
+
self._emit_prefix_as(text, m)))
|
|
407
427
|
i, n = 0, len(text)
|
|
408
428
|
while i < n:
|
|
409
429
|
c = text[i]
|
|
@@ -708,6 +708,30 @@ def new_graph(namespaces, base, identifier=None):
|
|
|
708
708
|
return g
|
|
709
709
|
|
|
710
710
|
|
|
711
|
+
def designate(namespaces, graph):
|
|
712
|
+
"""'@graph EXPR' — the designated graph inherits the block's prefixes.
|
|
713
|
+
|
|
714
|
+
A graph CREATED by '@graph as g' already serialises with the prefixes in
|
|
715
|
+
scope (:func:`new_graph`); a graph merely DESIGNATED did not, so the same
|
|
716
|
+
block writing the same prefixed names into two graphs produced two
|
|
717
|
+
different serialisations. That was an inconsistency rather than a
|
|
718
|
+
decision, and record ldpy/027 settles it: both inherit.
|
|
719
|
+
|
|
720
|
+
The bindings are added to the graph's OWN manager, never by swapping it
|
|
721
|
+
for the shared cached one — a designated graph belongs to the caller and
|
|
722
|
+
may already carry bindings that matter. Anything that is not a graph, or
|
|
723
|
+
a backend without a namespace manager, passes through untouched: the
|
|
724
|
+
declaration's job is to designate, and it must not fail on the shape of
|
|
725
|
+
what it is given.
|
|
726
|
+
"""
|
|
727
|
+
if namespaces:
|
|
728
|
+
try:
|
|
729
|
+
_backend.bind_namespaces(graph, namespaces)
|
|
730
|
+
except (AttributeError, TypeError):
|
|
731
|
+
pass # not a graph, or no binding support
|
|
732
|
+
return graph
|
|
733
|
+
|
|
734
|
+
|
|
711
735
|
def add_to(graph, *triples, bindings=None):
|
|
712
736
|
"""'+{ ... }': instantiate and add to the current graph (record ldpy/014).
|
|
713
737
|
A triple with a term still unbound is dropped — one cannot write an
|
|
@@ -592,6 +592,40 @@ class Transpiler:
|
|
|
592
592
|
# chaînes et littéraux RDF
|
|
593
593
|
# ------------------------------------------------------------------
|
|
594
594
|
|
|
595
|
+
# Island sigils that open with a brace or a chevron. A hole beginning
|
|
596
|
+
# with one of these is an island the scanner will never reach.
|
|
597
|
+
_HOLE_ISLANDS = ("_:{", "g{", "m{", "s{", "e{", "e<", "f{", "f<", "?{")
|
|
598
|
+
|
|
599
|
+
def _island_in_fstring_hole(self, tok_start, end):
|
|
600
|
+
"""The island opener written inside an f-string hole, or None.
|
|
601
|
+
|
|
602
|
+
The scanner does not descend into f-string interpolation holes: an
|
|
603
|
+
island written there would be copied verbatim into the generated
|
|
604
|
+
Python, which then fails with a message from CPython's f-string
|
|
605
|
+
parser that names nothing (record ldpy/012). Refusing it here, by
|
|
606
|
+
name, costs one scan of the token and turns a baffling failure into
|
|
607
|
+
an actionable one.
|
|
608
|
+
"""
|
|
609
|
+
tok = self.text[tok_start:end]
|
|
610
|
+
if "f" not in tok[:3].lower() or "{" not in tok:
|
|
611
|
+
return None
|
|
612
|
+
i, n = 0, len(tok)
|
|
613
|
+
while i < n:
|
|
614
|
+
if tok[i] != "{":
|
|
615
|
+
i += 1
|
|
616
|
+
continue
|
|
617
|
+
if tok[i + 1:i + 2] == "{": # '{{' is an escaped brace
|
|
618
|
+
i += 2
|
|
619
|
+
continue
|
|
620
|
+
j = i + 1
|
|
621
|
+
while j < n and tok[j] in " \t":
|
|
622
|
+
j += 1
|
|
623
|
+
for opener in self._HOLE_ISLANDS:
|
|
624
|
+
if tok.startswith(opener, j):
|
|
625
|
+
return opener
|
|
626
|
+
i += 1
|
|
627
|
+
return None
|
|
628
|
+
|
|
595
629
|
def _handle_string(self, start, prefix_start=None):
|
|
596
630
|
"""start : indice du guillemet. prefix_start : indice du préfixe de
|
|
597
631
|
chaîne (r/b/f/u) déjà repéré, sinon None."""
|
|
@@ -607,6 +641,12 @@ class Transpiler:
|
|
|
607
641
|
if suffix is None and t[end:end + 2] == "^^":
|
|
608
642
|
suffix = ("datatype", None, end + 2)
|
|
609
643
|
if suffix is None:
|
|
644
|
+
opener = self._island_in_fstring_hole(tok_start, end)
|
|
645
|
+
if opener is not None:
|
|
646
|
+
self._error(
|
|
647
|
+
"îlot '%s' dans un trou d'f-string : le scanner n'y "
|
|
648
|
+
"descend pas. Sortez la lecture dans une variable "
|
|
649
|
+
"au-dessus de l'f-string." % opener)
|
|
610
650
|
self._copy(end - self.i)
|
|
611
651
|
self.operand = False
|
|
612
652
|
self.stmt_start = False
|
|
@@ -1280,6 +1320,7 @@ class Transpiler:
|
|
|
1280
1320
|
if end is None:
|
|
1281
1321
|
return False
|
|
1282
1322
|
k = self._skip_ws_ahead(end)
|
|
1323
|
+
as_name, k = self._prefix_as_clause(k)
|
|
1283
1324
|
if k >= self.n or t[k] != ".":
|
|
1284
1325
|
return False
|
|
1285
1326
|
# consommation effective
|
|
@@ -1308,9 +1349,40 @@ class Transpiler:
|
|
|
1308
1349
|
scope_mode)
|
|
1309
1350
|
gen = "__namespaces__[%r] = %s.Namespace(%r)" % (
|
|
1310
1351
|
prefix, RUNTIME_ALIAS, resolved)
|
|
1352
|
+
if as_name:
|
|
1353
|
+
# `as EX` binds the Namespace OBJECT to a Python name, for the
|
|
1354
|
+
# code that needs it to survive the block (record ldpy/027).
|
|
1355
|
+
gen = "__namespaces__[%r] = %s = %s.Namespace(%r)" % (
|
|
1356
|
+
prefix, as_name, RUNTIME_ALIAS, resolved)
|
|
1357
|
+
if scope_mode:
|
|
1358
|
+
gen = "%s %s; %s" % (scope_mode, as_name, gen)
|
|
1311
1359
|
self._end_island(kind, mark, gen)
|
|
1312
1360
|
return True
|
|
1313
1361
|
|
|
1362
|
+
def _prefix_as_clause(self, k):
|
|
1363
|
+
"""`as NAME` before a @prefix declaration's closing '.', or nothing.
|
|
1364
|
+
|
|
1365
|
+
`@prefix` is lexical and yields no Python value, which the corpus
|
|
1366
|
+
study showed to be its most attested limit: code that must keep the
|
|
1367
|
+
`Namespace` as an OBJECT — to `g.bind()` it, to export it, to put it
|
|
1368
|
+
in a registry — had no translation at all (record ldpy/027). `as EX`
|
|
1369
|
+
binds that object, without making a bare `ex:` a value (records
|
|
1370
|
+
ldpy/002 and 004 refuse that, and still do).
|
|
1371
|
+
|
|
1372
|
+
Returns `(name, index after the clause)`; `(None, k)` when absent.
|
|
1373
|
+
"""
|
|
1374
|
+
t = self.text
|
|
1375
|
+
if t[k:k + 2] != "as":
|
|
1376
|
+
return None, k
|
|
1377
|
+
if k + 2 < self.n and _name_char(t[k + 2]):
|
|
1378
|
+
return None, k # `assets`, not `as`
|
|
1379
|
+
j = self._skip_ws_ahead(k + 2)
|
|
1380
|
+
m = _PYNAME_RE.match(t, j)
|
|
1381
|
+
if not m or j == k + 2:
|
|
1382
|
+
self._error("nom Python attendu après 'as' dans la "
|
|
1383
|
+
"déclaration @prefix")
|
|
1384
|
+
return m.group(0), self._skip_ws_ahead(m.end())
|
|
1385
|
+
|
|
1314
1386
|
def _prefix_firi_decl(self, j, prefix, scope_mode=None):
|
|
1315
1387
|
"""Consomme `@prefix p: f<...> .` (self.i sur '@', j sur 'f').
|
|
1316
1388
|
Sans interpolation : déclaration statique ordinaire. Avec : préfixe
|
|
@@ -1539,7 +1611,10 @@ class Transpiler:
|
|
|
1539
1611
|
self._error("expression attendue après '@graph'")
|
|
1540
1612
|
if "\n" in expr:
|
|
1541
1613
|
self._error("l'expression de '@graph' tient sur sa ligne")
|
|
1542
|
-
|
|
1614
|
+
# The designated graph inherits the block's prefixes, exactly
|
|
1615
|
+
# as a created one does (record ldpy/027).
|
|
1616
|
+
rhs = "%s.designate(__namespaces__, (%s))" % (
|
|
1617
|
+
RUNTIME_ALIAS, expr)
|
|
1543
1618
|
self._graph_ws_inline()
|
|
1544
1619
|
if self._peek() not in "\n\r#;" and self._peek() != "":
|
|
1545
1620
|
self._error("fin de ligne attendue après la déclaration @graph")
|
|
@@ -2124,6 +2199,14 @@ class Transpiler:
|
|
|
2124
2199
|
elif c in "\"'":
|
|
2125
2200
|
end = self._string_end(self.i)
|
|
2126
2201
|
pieces.append(self._take(end - self.i))
|
|
2202
|
+
elif c == "<" and self._iriref_end(self.i) is not None:
|
|
2203
|
+
# A full IRI is a token, not punctuation: its '#' is part of
|
|
2204
|
+
# the IRI, not a comment. Without this the fragment of
|
|
2205
|
+
# <http://example.org/ns#X> — the most ordinary shape an IRI
|
|
2206
|
+
# takes in RDF — swallowed the rest of the line, closing
|
|
2207
|
+
# brace included. `<` that does NOT open an IRI is SPARQL's
|
|
2208
|
+
# less-than, and falls through to the single-character case.
|
|
2209
|
+
pieces.append(self._take(self._iriref_end(self.i) - self.i))
|
|
2127
2210
|
elif c == "#":
|
|
2128
2211
|
j = t.find("\n", self.i)
|
|
2129
2212
|
pieces.append(self._take((j if j != -1 else self.n) - self.i))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linked-data-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
4
4
|
Summary: Python extended with Semantic Web primitives: IRIs, RDF literals and Turtle-notation graphs as expressions, transpiled by island parsing.
|
|
5
5
|
Author-email: Maxime Lefrançois <maxime.lefrancois@emse.fr>
|
|
6
6
|
License: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "linked-data-python"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.3"
|
|
8
8
|
description = "Python extended with Semantic Web primitives: IRIs, RDF literals and Turtle-notation graphs as expressions, transpiled by island parsing."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -288,3 +288,46 @@ def test_compound_suite_does_not_capture_python(run):
|
|
|
288
288
|
"""))
|
|
289
289
|
assert g["n"] == 5 and g["d"] == {"a": 1}
|
|
290
290
|
assert g["f"]("x") == "x" and g["keys"] == {"b"}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# --------------------------------------------------------------- record 027
|
|
294
|
+
# A graph CREATED by `@graph as g` already carried the block's prefixes; a
|
|
295
|
+
# graph merely DESIGNATED carried none, so the same block writing the same
|
|
296
|
+
# prefixed names into two graphs serialised them differently. The corpus
|
|
297
|
+
# study met this as nineteen hand-written `g.bind()` calls in one function.
|
|
298
|
+
|
|
299
|
+
def test_a_designated_graph_inherits_the_block_prefixes(run):
|
|
300
|
+
ns, _ = run("from rdflib import Graph\n"
|
|
301
|
+
"@prefix ex: <http://example.org/ns#> .\n"
|
|
302
|
+
"mine = Graph()\n"
|
|
303
|
+
"@graph mine\n"
|
|
304
|
+
"+{ ex:a ex:p 1 }\n")
|
|
305
|
+
assert ("ex", URIRef("http://example.org/ns#")) in list(
|
|
306
|
+
ns["mine"].namespaces())
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def test_created_and_designated_serialise_alike(run):
|
|
310
|
+
"""The point of the fix: two graphs the same block fills the same way
|
|
311
|
+
must not differ in their serialisation."""
|
|
312
|
+
ns, _ = run("from rdflib import Graph\n"
|
|
313
|
+
"@prefix ex: <http://example.org/ns#> .\n"
|
|
314
|
+
"@graph as made\n"
|
|
315
|
+
"+{ ex:a ex:p 1 }\n"
|
|
316
|
+
"given = Graph()\n"
|
|
317
|
+
"@graph given\n"
|
|
318
|
+
"+{ ex:a ex:p 1 }\n")
|
|
319
|
+
assert "@prefix ex:" in ns["made"].serialize(format="turtle")
|
|
320
|
+
assert "@prefix ex:" in ns["given"].serialize(format="turtle")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def test_designating_keeps_bindings_the_caller_already_had(run):
|
|
324
|
+
"""A designated graph belongs to its caller: the block's prefixes are
|
|
325
|
+
ADDED to its own manager, never swapped for a shared one."""
|
|
326
|
+
ns, _ = run("from rdflib import Graph, Namespace\n"
|
|
327
|
+
"@prefix ex: <http://example.org/ns#> .\n"
|
|
328
|
+
"mine = Graph()\n"
|
|
329
|
+
'mine.bind("mine", Namespace("http://mine.example/"))\n'
|
|
330
|
+
"@graph mine\n"
|
|
331
|
+
"+{ ex:a ex:p 1 }\n")
|
|
332
|
+
got = {p for p, _ in ns["mine"].namespaces()}
|
|
333
|
+
assert "ex" in got and "mine" in got
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Lexical guards on the boundary between Python and the islands.
|
|
2
|
+
|
|
3
|
+
What is pinned here is not what the transpiler produces but what it REFUSES,
|
|
4
|
+
and how: a construct the scanner cannot see must fail by name, not by leaving
|
|
5
|
+
CPython to complain about something else.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from ldpy.transpiler import transpile, LdpySyntaxError
|
|
11
|
+
|
|
12
|
+
# CPython then rejected with a message naming nothing. Found by the corpus
|
|
13
|
+
# study; refused by name since 0.5.1.
|
|
14
|
+
|
|
15
|
+
def test_an_island_in_an_fstring_hole_is_refused_by_name():
|
|
16
|
+
src = ('@prefix ex: <http://example.org/> .\n'
|
|
17
|
+
'@graph as g\n'
|
|
18
|
+
't = f"{m{ ?s ex:p ?o }.first()}"\n')
|
|
19
|
+
with pytest.raises(LdpySyntaxError) as e:
|
|
20
|
+
transpile(src, "t.ldpy")
|
|
21
|
+
assert "f-string" in e.value.msg and "m{" in e.value.msg
|
|
22
|
+
assert e.value.line == 2 # 0-based: the third line
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.mark.parametrize("hole", ["g{ ex:a ex:b 1 }", "s{ SELECT ?s WHERE {} }",
|
|
26
|
+
"e{ ?a }", "f<a/{x}>", "_:{1}", "?{1}"])
|
|
27
|
+
def test_every_brace_island_is_caught_in_a_hole(hole):
|
|
28
|
+
src = ('@prefix ex: <http://example.org/> .\n'
|
|
29
|
+
'@graph as g\n'
|
|
30
|
+
't = f"{%s}"\n' % hole)
|
|
31
|
+
with pytest.raises(LdpySyntaxError):
|
|
32
|
+
transpile(src, "t.ldpy")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_an_ordinary_fstring_is_untouched():
|
|
36
|
+
"""The guard must not cost a plain f-string, nor an escaped brace, nor a
|
|
37
|
+
dict display inside a hole."""
|
|
38
|
+
src = ('xs = [1, 2]\n'
|
|
39
|
+
'a = f"{len(xs)} results"\n'
|
|
40
|
+
'b = f"{{literal braces}}"\n'
|
|
41
|
+
'c = f"{ {k: v for k, v in []} }"\n'
|
|
42
|
+
'd = f"{xs!r:>10}"\n')
|
|
43
|
+
code = transpile(src, "t.ldpy").code
|
|
44
|
+
assert 'f"{len(xs)} results"' in code
|
|
45
|
+
assert 'f"{{literal braces}}"' in code
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_an_island_outside_a_hole_still_works():
|
|
49
|
+
"""A comprehension over an island is fine — only the hole is blind."""
|
|
50
|
+
src = ('@prefix ex: <http://example.org/> .\n'
|
|
51
|
+
'@graph as g\n'
|
|
52
|
+
'xs = [s for s in m{ ?s ex:p ?o }]\n')
|
|
53
|
+
assert "_ldpy_.match(" in transpile(src, "t.ldpy").code
|
|
@@ -300,3 +300,50 @@ def test_never_declared_prefix_gives_no_warning():
|
|
|
300
300
|
r = transpile("d = {foo:bar}\nx = arr[i:j]\n")
|
|
301
301
|
assert r.warnings == []
|
|
302
302
|
assert r.code == "d = {foo:bar}\nx = arr[i:j]\n"
|
|
303
|
+
|
|
304
|
+
# --------------------------------------------------------------- record 027
|
|
305
|
+
# `@prefix` is lexical and yields no Python value, which the corpus study
|
|
306
|
+
# showed to be its most attested limit: code that must keep the Namespace as
|
|
307
|
+
# an OBJECT — to `g.bind()` it, to export it, to put it in a registry — had
|
|
308
|
+
# no translation at all. `as EX` binds that object. A bare `ex:` is still
|
|
309
|
+
# never a value (records ldpy/002 and 004).
|
|
310
|
+
|
|
311
|
+
def test_as_binds_the_namespace_object(run):
|
|
312
|
+
ns, _ = run("@prefix ex: <http://example.org/ns#> as EX .\n"
|
|
313
|
+
"iri = EX.Thing\n"
|
|
314
|
+
"prefixed = ex:Thing\n")
|
|
315
|
+
assert str(ns["EX"]) == "http://example.org/ns#"
|
|
316
|
+
assert ns["iri"] == ns["prefixed"]
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def test_as_serves_the_three_forms_the_corpus_needed(run):
|
|
320
|
+
ns, _ = run("from rdflib import Graph\n"
|
|
321
|
+
"@prefix ex: <http://example.org/ns#> as EX .\n"
|
|
322
|
+
"g = Graph()\n"
|
|
323
|
+
"g.bind('ex', EX)\n"
|
|
324
|
+
"registry = {'ex': EX}\n"
|
|
325
|
+
"exported = EX\n")
|
|
326
|
+
assert ("ex", URIRef("http://example.org/ns#")) in list(
|
|
327
|
+
ns["g"].namespaces())
|
|
328
|
+
assert ns["registry"]["ex"] is ns["exported"]
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def test_the_prefix_still_works_without_as(run):
|
|
332
|
+
ns, _ = run("@prefix ex: <http://example.org/ns#> .\n"
|
|
333
|
+
"t = ex:Thing\n")
|
|
334
|
+
assert str(ns["t"]) == "http://example.org/ns#Thing"
|
|
335
|
+
assert "EX" not in ns
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def test_as_needs_a_name():
|
|
339
|
+
with pytest.raises(LdpySyntaxError) as e:
|
|
340
|
+
transpile("@prefix ex: <http://example.org/> as .\n", "t.ldpy")
|
|
341
|
+
assert "as" in e.value.msg
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def test_a_prefix_named_as_something_is_not_an_as_clause(run):
|
|
345
|
+
"""`as` must be a whole word: a declaration is not broken by an IRI
|
|
346
|
+
followed by nothing, nor by a name that merely starts with 'as'."""
|
|
347
|
+
ns, _ = run("@prefix ex: <http://example.org/ns#> as assets .\n"
|
|
348
|
+
"t = ex:Thing\n")
|
|
349
|
+
assert str(ns["assets"]) == "http://example.org/ns#"
|
|
@@ -159,3 +159,51 @@ def test_update_par_execute_public(run):
|
|
|
159
159
|
g, _ = run(DATA + "s{ INSERT { ?s ex:seen 1 } WHERE { ?s a ex:Sensor } }"
|
|
160
160
|
".execute()\n")
|
|
161
161
|
assert len(g["g"]) > 2
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# --------------------------------------------------------------- record 012
|
|
165
|
+
# A '#' inside a full IRI was read as the start of a comment, so the rest of
|
|
166
|
+
# the line — closing brace included — was swallowed. The most ordinary shape
|
|
167
|
+
# an IRI takes in RDF, and only `s{ }` had the defect: `m{ }` and `g{ }`
|
|
168
|
+
# parse IRIs as terms and never saw it. Found by the corpus study.
|
|
169
|
+
|
|
170
|
+
def test_a_hash_inside_a_full_iri_is_not_a_comment():
|
|
171
|
+
src = ("@graph as g\n"
|
|
172
|
+
"q = s{ SELECT ?s WHERE { ?s <http://example.org/ns#X> ?o } }\n")
|
|
173
|
+
code = transpile(src, "t.ldpy").code
|
|
174
|
+
assert "http://example.org/ns#X" in code
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_the_same_iri_in_the_other_islands_never_broke():
|
|
178
|
+
"""The regression guard is on `s{ }`, but pin the siblings too: the fix
|
|
179
|
+
must not have been needed there, and must not change them."""
|
|
180
|
+
src = ("@graph as g\n"
|
|
181
|
+
"m1 = m{ ?s <http://example.org/ns#X> ?o }\n"
|
|
182
|
+
"g1 = g{ <http://example.org/ns#A> <http://example.org/ns#p> 1 }\n")
|
|
183
|
+
code = transpile(src, "t.ldpy").code
|
|
184
|
+
assert code.count("http://example.org/ns#") == 3
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_a_real_comment_is_still_a_comment():
|
|
188
|
+
src = ("@prefix ex: <http://example.org/ns#> .\n"
|
|
189
|
+
"@graph as g\n"
|
|
190
|
+
"q = s{ SELECT ?s WHERE { ?s ex:v ?v } # a real comment\n"
|
|
191
|
+
" }\n")
|
|
192
|
+
assert "a real comment" in transpile(src, "t.ldpy").code
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_less_than_is_still_an_operator():
|
|
196
|
+
"""`<` that does not open an IRI is SPARQL's less-than. The fix keys on
|
|
197
|
+
`_iriref_end`, so it must leave FILTER(?v < 5) alone."""
|
|
198
|
+
src = ("@prefix ex: <http://example.org/ns#> .\n"
|
|
199
|
+
"@graph as g\n"
|
|
200
|
+
"q = s{ SELECT ?s WHERE { ?s ex:v ?v . FILTER(?v < 5 && ?v > 1) } }\n")
|
|
201
|
+
code = transpile(src, "t.ldpy").code
|
|
202
|
+
assert "?v < 5 && ?v > 1" in code
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_a_hash_inside_a_string_is_not_a_comment_either():
|
|
206
|
+
src = ("@prefix ex: <http://example.org/ns#> .\n"
|
|
207
|
+
"@graph as g\n"
|
|
208
|
+
'q = s{ SELECT ?s WHERE { ?s ex:v "a#b" } }\n')
|
|
209
|
+
assert 'a#b' in transpile(src, "t.ldpy").code
|
|
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
|
|
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
|
{linked_data_python-0.6.1 → linked_data_python-0.6.3}/linked_data_python.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{linked_data_python-0.6.1 → linked_data_python-0.6.3}/linked_data_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{linked_data_python-0.6.1 → linked_data_python-0.6.3}/linked_data_python.egg-info/top_level.txt
RENAMED
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|