linked-data-python 0.6.1__py3-none-any.whl → 0.6.3__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.
ldpy/lsp/islanddoc.py CHANGED
@@ -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: it has no run-time object, and `ex:` on its own "
51
- "is never a value. Declaring it again in a deeper block shadows it, "
52
- "the way a Python name would.",
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
- "Call it on a graph to run it; `.execute()` runs an update.",
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 percent-encoded and then resolved against `@base`. "
174
- "Encoding first is what keeps a space or a slash in a value from "
175
- "changing the IRI's structure.",
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
 
ldpy/pygments_lexer.py CHANGED
@@ -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]
ldpy/runtime.py CHANGED
@@ -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
ldpy/transpiler/core.py CHANGED
@@ -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
- rhs = "(%s)" % expr
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.1
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
@@ -6,24 +6,24 @@ ldpy/console.py,sha256=vMSnFFoOIOcanw6_niNxqgrt-F4gzMOrD1GopkXru5s,4837
6
6
  ldpy/debug.py,sha256=JUuzeKc8ya4SG30AxaUVeihTEbzySo1cS0sU0s78Mv0,10493
7
7
  ldpy/formatter.py,sha256=VaDPECxhivsMe8jnX_fOL7BUyLEu5r4xREdr4Hav-0c,12695
8
8
  ldpy/importer.py,sha256=QXAhSujPN6rFMJSQuj0UsWyI4CajirKt1SOrykJ_fUw,3670
9
- ldpy/pygments_lexer.py,sha256=mcQ0u9nZjLnSWpqzcxd6bZjk69LfKSNTsf9A3rqfhhk,25320
10
- ldpy/runtime.py,sha256=vkk9HQvUpi6pET0S4EqH1sA3797eA89NeTzHz9dLT_w,39632
9
+ ldpy/pygments_lexer.py,sha256=fmqYytdnovqfXemuKE5lg4XG1iub-hui9otXERBWELw,26217
10
+ ldpy/runtime.py,sha256=jOFR8lg4tVOP-H_2-cO-Q5SEyW7iz1y0nmZ3QkSlThU,40736
11
11
  ldpy/sparql.py,sha256=yPe-ZR1tpY--nM6q5LYXyXP7zlkE4gqhIRYAKwfpTBg,14851
12
12
  ldpy/lsp/__init__.py,sha256=rK5GYOITi9PN09-WVlJ2aPKodW8dWTpgI_X6dFbP1zA,542
13
13
  ldpy/lsp/__main__.py,sha256=MIp4ktqaoWZCNIMfv14bSC9pqK86JBS5XVjd1Z3c24o,72
14
14
  ldpy/lsp/backend.py,sha256=8DnSGaBk1dsIMOBo7D1WQxxF3MZjJbYd78jp4ucWORY,5375
15
15
  ldpy/lsp/hover.py,sha256=zSJ0PpU3kTJDXcwI2zSDCbsKrT-eNTt2RlvmEPRoV-M,2407
16
- ldpy/lsp/islanddoc.py,sha256=vxxRKEcywBH31c8XgykEoC8fZfvbHok1NH-rPc-0Sy0,9540
16
+ ldpy/lsp/islanddoc.py,sha256=kyW9R38otc66OG2SxhzKKwb8AG--LAbDkV1tc2Xv5pg,10120
17
17
  ldpy/lsp/rpc.py,sha256=-TF_9U8dum23Pi4O1FZLogwoZgrGrt14J5BFB1OYVZw,3554
18
18
  ldpy/lsp/server.py,sha256=PAMMookGUn00vAW_-a5BGy3qkVe9PoUdfzAzpdlvoF8,16359
19
19
  ldpy/lsp/translate.py,sha256=HU9fBP_xWyYLih6QwQfSoyfF-MH-oInFcPX3o8Vc2GI,6116
20
20
  ldpy/transpiler/__init__.py,sha256=9mQx0e47ixz0iMqSbCBbtGiwJ7PLxTcsVSIp6KNTMG4,504
21
- ldpy/transpiler/core.py,sha256=LaZT8hE4jocIcTLyb2kc_mFtwu8QTSm0FhOMNetY_2k,114831
21
+ ldpy/transpiler/core.py,sha256=ySjfXkKKuKGFKwwlzgbm19f3wVWE1ecZL10pSy63xBo,118921
22
22
  ldpy/transpiler/errors.py,sha256=zEEwsNvC2Tawq8jIYls7hrbcWSvQtd7eo89k_SKp0k0,1142
23
23
  ldpy/transpiler/linemap.py,sha256=ecLlCUkD4-6QQtUjGfAL28ql7LPITTS4fjLcDIbAwpQ,11386
24
- linked_data_python-0.6.1.dist-info/licenses/LICENSE.md,sha256=R6KjQ098eAFd2k5mKaSRJqMH1LhY4o7xkHrKT5vjRfs,1074
25
- linked_data_python-0.6.1.dist-info/METADATA,sha256=SM4kVwA-gnxAZdihLeDPLR3Sys8n0iauEh40yw-8g_4,7974
26
- linked_data_python-0.6.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
27
- linked_data_python-0.6.1.dist-info/entry_points.txt,sha256=n8Vs52oH81BrKOe1DNUvdFLSOlEufRMpK2waPFa6aYk,224
28
- linked_data_python-0.6.1.dist-info/top_level.txt,sha256=dkG_F4jYkh6CS6gdpQeV_gKW3yaJBvl1ukUu0zo2KA8,5
29
- linked_data_python-0.6.1.dist-info/RECORD,,
24
+ linked_data_python-0.6.3.dist-info/licenses/LICENSE.md,sha256=R6KjQ098eAFd2k5mKaSRJqMH1LhY4o7xkHrKT5vjRfs,1074
25
+ linked_data_python-0.6.3.dist-info/METADATA,sha256=xtxE9wzvbhj1pnknzO3JZb0Pa9Y0ECyUZHDrnJzKyTY,7974
26
+ linked_data_python-0.6.3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
27
+ linked_data_python-0.6.3.dist-info/entry_points.txt,sha256=n8Vs52oH81BrKOe1DNUvdFLSOlEufRMpK2waPFa6aYk,224
28
+ linked_data_python-0.6.3.dist-info/top_level.txt,sha256=dkG_F4jYkh6CS6gdpQeV_gKW3yaJBvl1ukUu0zo2KA8,5
29
+ linked_data_python-0.6.3.dist-info/RECORD,,