linked-data-python 0.5.1__tar.gz → 0.6.0__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 (72) hide show
  1. {linked_data_python-0.5.1/linked_data_python.egg-info → linked_data_python-0.6.0}/PKG-INFO +1 -1
  2. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/__main__.py +4 -1
  3. linked_data_python-0.6.0/ldpy/backend.py +90 -0
  4. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/build.py +41 -6
  5. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/console.py +13 -0
  6. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/debug.py +24 -2
  7. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/runtime.py +178 -56
  8. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/sparql.py +4 -4
  9. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/transpiler/core.py +32 -11
  10. {linked_data_python-0.5.1 → linked_data_python-0.6.0/linked_data_python.egg-info}/PKG-INFO +1 -1
  11. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/linked_data_python.egg-info/SOURCES.txt +3 -1
  12. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/pyproject.toml +1 -1
  13. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_console.py +58 -0
  14. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_debug.py +41 -0
  15. linked_data_python-0.6.0/tests/test_docs.py +103 -0
  16. linked_data_python-0.6.0/tests/test_target_micropython.py +134 -0
  17. linked_data_python-0.5.1/tests/test_docs.py +0 -46
  18. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/LICENSE.md +0 -0
  19. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/README.md +0 -0
  20. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/__init__.py +0 -0
  21. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/formatter.py +0 -0
  22. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/importer.py +0 -0
  23. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/lsp/__init__.py +0 -0
  24. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/lsp/__main__.py +0 -0
  25. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/lsp/backend.py +0 -0
  26. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/lsp/hover.py +0 -0
  27. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/lsp/islanddoc.py +0 -0
  28. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/lsp/rpc.py +0 -0
  29. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/lsp/server.py +0 -0
  30. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/lsp/translate.py +0 -0
  31. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/pygments_lexer.py +0 -0
  32. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/transpiler/__init__.py +0 -0
  33. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/transpiler/errors.py +0 -0
  34. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/ldpy/transpiler/linemap.py +0 -0
  35. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/linked_data_python.egg-info/dependency_links.txt +0 -0
  36. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/linked_data_python.egg-info/entry_points.txt +0 -0
  37. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/linked_data_python.egg-info/requires.txt +0 -0
  38. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/linked_data_python.egg-info/top_level.txt +0 -0
  39. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/setup.cfg +0 -0
  40. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_bench.py +0 -0
  41. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_bindings.py +0 -0
  42. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_build.py +0 -0
  43. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_call_suffix.py +0 -0
  44. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_charsets.py +0 -0
  45. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_coercion.py +0 -0
  46. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_current_graph.py +0 -0
  47. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_debug_stepping.py +0 -0
  48. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_disambiguation.py +0 -0
  49. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_docstrings.py +0 -0
  50. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_examples.py +0 -0
  51. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_formatter.py +0 -0
  52. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_generator.py +0 -0
  53. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_graphs.py +0 -0
  54. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_hover_parts.py +0 -0
  55. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_identity.py +0 -0
  56. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_import_hook.py +0 -0
  57. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_islanddoc.py +0 -0
  58. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_islands_terms.py +0 -0
  59. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_linemap.py +0 -0
  60. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_lsp_rpc.py +0 -0
  61. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_lsp_server.py +0 -0
  62. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_lsp_translate.py +0 -0
  63. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_match_island.py +0 -0
  64. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_micropython_subset.py +0 -0
  65. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_prefix_import.py +0 -0
  66. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_prefix_scoping.py +0 -0
  67. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_pygments_lexer.py +0 -0
  68. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_scope_modifiers.py +0 -0
  69. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_semantics.py +0 -0
  70. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_sourcemap_v3.py +0 -0
  71. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_sparql_expr.py +0 -0
  72. {linked_data_python-0.5.1 → linked_data_python-0.6.0}/tests/test_sparql_island.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: linked-data-python
3
- Version: 0.5.1
3
+ Version: 0.6.0
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
@@ -30,6 +30,9 @@ def main(argv=None):
30
30
  help="open the interactive console after the script.")
31
31
  parser.add_argument("-m", "--map", action="store_true",
32
32
  help="also write the language map (<source>.map).")
33
+ parser.add_argument("--target", choices=["micropython"], default=None,
34
+ help="transpile for a device: s{ } is refused here, "
35
+ "at build time, where the message can be read.")
33
36
  parser.add_argument("source", nargs="?",
34
37
  help=".ldpy (or .py) file to run.")
35
38
  args = parser.parse_args(argv)
@@ -45,7 +48,7 @@ def main(argv=None):
45
48
  with open(args.source, "r", encoding="utf-8") as f:
46
49
  source = f.read()
47
50
  try:
48
- result = transpile(source, args.source)
51
+ result = transpile(source, args.source, target=args.target)
49
52
  except LdpySyntaxError as e:
50
53
  print(str(e), file=sys.stderr)
51
54
  return 1
@@ -0,0 +1,90 @@
1
+ """The RDF backend of the façade: rdflib on CPython, urdflib on MicroPython.
2
+
3
+ The generated code never touches rdflib: it goes through `ldpy.runtime`,
4
+ and `ldpy.runtime` takes its terms and graphs from HERE. So this module is
5
+ the one place that knows which library is underneath — and the contract a
6
+ backend must meet is exactly its public names (record urdflib/306 measures
7
+ them with `urdflib/tools/api_gap.py`).
8
+
9
+ Two backends:
10
+
11
+ * **rdflib**, whenever it imports — the default on CPython, and the only one
12
+ with a SPARQL engine (`s{ }`);
13
+ * **urdflib**, the MicroPython C module of the linked-data-python
14
+ organisation, when rdflib is absent. It has the terms and the graphs;
15
+ `Namespace`, `RDF` and `XSD` are pure Python and provided here.
16
+
17
+ The two are not exclusive options of the same kind: the backend is chosen
18
+ by what is importable, and SPARQL queries are a capability of one backend.
19
+ `e{ }` expressions (ldpy.sparql) are pure Python over the terms, and run on
20
+ both.
21
+ """
22
+
23
+ try:
24
+ import rdflib as _lib
25
+ from rdflib import RDF, XSD, BNode, Literal, Namespace, Variable
26
+ from rdflib.term import Node
27
+ URIRef = _lib.URIRef
28
+ Graph = _lib.Graph
29
+ NAME = "rdflib"
30
+ HAS_SPARQL = True
31
+ HAS_NAMESPACE_MANAGER = True
32
+ except ImportError: # MicroPython
33
+ import urdflib as _lib
34
+ from urdflib import BNode, Literal, Node, URIRef, Variable, Graph
35
+ NAME = "urdflib"
36
+ HAS_SPARQL = False
37
+ HAS_NAMESPACE_MANAGER = False
38
+
39
+ class Namespace(str):
40
+ """A namespace IRI whose attributes and items are terms — rdflib's
41
+ Namespace, reduced to what the emitted code uses."""
42
+
43
+ def __getattr__(self, name):
44
+ if name.startswith("__"):
45
+ raise AttributeError(name)
46
+ return URIRef(self + name)
47
+
48
+ def __getitem__(self, name):
49
+ return URIRef(self + name)
50
+
51
+ def term(self, name):
52
+ return URIRef(self + name)
53
+
54
+ RDF = Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#")
55
+ XSD = Namespace("http://www.w3.org/2001/XMLSchema#")
56
+
57
+
58
+ def new_graph(base=None, identifier=None):
59
+ """A graph of the backend. rdflib's takes a base, urdflib's does not
60
+ (a base is a lexical matter, resolved by the transpiler already)."""
61
+ if NAME == "rdflib":
62
+ return Graph(base=base, identifier=identifier)
63
+ return Graph(identifier=identifier) if identifier is not None else Graph()
64
+
65
+
66
+ def bind_namespaces(graph, namespaces):
67
+ """Give `graph` the serialisation prefixes of `namespaces` (a dict
68
+ prefix -> namespace IRI). On rdflib the runtime shares a cached
69
+ NamespaceManager instead (record ldpy/021); this is the plain path."""
70
+ for prefix, ns in namespaces.items():
71
+ graph.bind(prefix, str(ns))
72
+
73
+
74
+ def prepare_sparql(text, namespaces, update=False):
75
+ """A prepared SPARQL query or update — rdflib's engine, or an error
76
+ that says which backend is missing it."""
77
+ if not HAS_SPARQL:
78
+ raise RuntimeError(
79
+ "s{ } needs a SPARQL engine, and the %s backend has none: "
80
+ "transpile with target='micropython' to be told at build time, "
81
+ "and use m{ } and e{ } on the device" % NAME)
82
+ from rdflib.plugins.sparql import prepareQuery, prepareUpdate
83
+ return (prepareUpdate if update else prepareQuery)(
84
+ text, initNs=dict(namespaces))
85
+
86
+
87
+ __all__ = ["NAME", "HAS_SPARQL", "HAS_NAMESPACE_MANAGER",
88
+ "URIRef", "BNode", "Literal", "Variable", "Node", "Graph",
89
+ "Namespace", "RDF", "XSD",
90
+ "new_graph", "bind_namespaces", "prepare_sparql"]
@@ -16,7 +16,35 @@ from ldpy.transpiler import transpile, LdpySyntaxError
16
16
  DEFAULT_OUT = ".ldpy-build"
17
17
 
18
18
 
19
- def build_file(src_path, out_dir, rel=None):
19
+ #: What a device needs of ldpy at run time, and nothing else: the façade,
20
+ #: its backend, and the expressions of e{ }. The transpiler stays on the
21
+ #: host. `ldpy/__init__.py` is NOT copied — it imports the transpiler and
22
+ #: the import hook — and is replaced by the docstring below.
23
+ DEVICE_RUNTIME = ("runtime.py", "backend.py", "sparql.py")
24
+ DEVICE_INIT = ('"""ldpy runtime for a device: what `import ldpy.runtime as '
25
+ '_ldpy_` needs.\n\nWritten by `python -m ldpy.build --target '
26
+ 'micropython`; the transpiler is not here.\n"""\n')
27
+
28
+
29
+ def bundle_device_runtime(out_dir):
30
+ """Copy the device runtime into <out_dir>/ldpy/, so that the emitted
31
+ files and what they import travel together. Returns the paths."""
32
+ here = os.path.dirname(os.path.abspath(__file__))
33
+ dst = os.path.join(out_dir, "ldpy")
34
+ os.makedirs(dst, exist_ok=True)
35
+ written = []
36
+ with open(os.path.join(dst, "__init__.py"), "w", encoding="utf-8") as f:
37
+ f.write(DEVICE_INIT)
38
+ written.append(os.path.join(dst, "__init__.py"))
39
+ for name in DEVICE_RUNTIME:
40
+ with open(os.path.join(here, name), "rb") as fi, \
41
+ open(os.path.join(dst, name), "wb") as fo:
42
+ fo.write(fi.read())
43
+ written.append(os.path.join(dst, name))
44
+ return written
45
+
46
+
47
+ def build_file(src_path, out_dir, rel=None, target=None):
20
48
  """Transpile one file; returns (py_path, map_path, result)."""
21
49
  with open(src_path, "r", encoding="utf-8") as f:
22
50
  source = f.read()
@@ -24,7 +52,7 @@ def build_file(src_path, out_dir, rel=None):
24
52
  stem = rel[:-5] if rel.endswith(".ldpy") else rel
25
53
  py_path = os.path.join(out_dir, stem + ".py")
26
54
  map_path = os.path.join(out_dir, stem + ".ldpy.map")
27
- result = transpile(source, src_path)
55
+ result = transpile(source, src_path, target=target)
28
56
  result.map.generated_name = py_path
29
57
  os.makedirs(os.path.dirname(py_path) or ".", exist_ok=True)
30
58
  with open(py_path, "w", encoding="utf-8") as f:
@@ -37,7 +65,7 @@ def build_file(src_path, out_dir, rel=None):
37
65
  return py_path, map_path, result
38
66
 
39
67
 
40
- def build_tree(root, out_dir):
68
+ def build_tree(root, out_dir, target=None):
41
69
  """Recursively transpile every .ldpy under root. Plain .py files are
42
70
  copied as they are (a mixed package must stay importable)."""
43
71
  built, errors = [], []
@@ -49,7 +77,7 @@ def build_tree(root, out_dir):
49
77
  rel = os.path.relpath(src, root)
50
78
  if name.endswith(".ldpy"):
51
79
  try:
52
- built.append(build_file(src, out_dir, rel))
80
+ built.append(build_file(src, out_dir, rel, target))
53
81
  except LdpySyntaxError as e:
54
82
  errors.append(e)
55
83
  elif name.endswith(".py"):
@@ -68,16 +96,23 @@ def main(argv=None):
68
96
  parser.add_argument("source", help=".ldpy file or directory")
69
97
  parser.add_argument("-o", "--out", default=DEFAULT_OUT,
70
98
  help="output directory (default: %(default)s)")
99
+ parser.add_argument("--target", choices=["micropython"], default=None,
100
+ help="build for a device: s{ } is refused, and the "
101
+ "ldpy runtime is copied next to the output")
71
102
  args = parser.parse_args(argv)
72
103
 
104
+ if args.target == "micropython":
105
+ os.makedirs(args.out, exist_ok=True)
106
+ bundle_device_runtime(args.out)
73
107
  if os.path.isdir(args.source):
74
- built, errors = build_tree(args.source, args.out)
108
+ built, errors = build_tree(args.source, args.out, args.target)
75
109
  for e in errors:
76
110
  print(str(e), file=sys.stderr)
77
111
  print("%d file(s) transpiled into %s" % (len(built), args.out))
78
112
  return 1 if errors else 0
79
113
  try:
80
- py_path, _, result = build_file(args.source, args.out)
114
+ py_path, _, result = build_file(args.source, args.out,
115
+ target=args.target)
81
116
  except LdpySyntaxError as e:
82
117
  print(str(e), file=sys.stderr)
83
118
  return 1
@@ -70,6 +70,12 @@ class LdpyConsole(code.InteractiveConsole):
70
70
  self._prefixes = dict(prefixes or {})
71
71
  self._prefix_cols = {k: 0 for k in self._prefixes}
72
72
  self._base = base
73
+ # The current graph and the current bindings are declarations too:
74
+ # like @prefix, a top-level @graph must survive to the next entry
75
+ # (otherwise `+{ ... }` after `@graph as g` sees no graph at all).
76
+ self._graph_var = None
77
+ self._bindings_var = None
78
+ self._counter = 0
73
79
 
74
80
  def runsource(self, source, filename=None, symbol="single"):
75
81
  """Transpile then compile one entry; True = incomplete entry
@@ -79,6 +85,10 @@ class LdpyConsole(code.InteractiveConsole):
79
85
  t.prefixes = dict(self._prefixes)
80
86
  t._prefix_col = dict(self._prefix_cols)
81
87
  t.base = self._base
88
+ t._graph_var = self._graph_var
89
+ t._bindings_var = self._bindings_var
90
+ # fresh names must not collide with those of the previous entries
91
+ t._ns_counter = self._counter
82
92
  try:
83
93
  result = t.run()
84
94
  except LdpySyntaxError as e:
@@ -98,6 +108,9 @@ class LdpyConsole(code.InteractiveConsole):
98
108
  self._prefixes = dict(t.prefixes)
99
109
  self._prefix_cols = dict(t._prefix_col)
100
110
  self._base = t.base
111
+ self._graph_var = t._graph_var
112
+ self._bindings_var = t._bindings_var
113
+ self._counter = t._ns_counter
101
114
  for w in result.warnings:
102
115
  self.write(str(w) + "\n")
103
116
  self.runcode(code_obj)
@@ -92,11 +92,30 @@ def probe():
92
92
  #: re-exported: debug tooling looks for them here (pure functions, defined
93
93
  #: alongside the map in ldpy/transpiler/linemap.py)
94
94
  __all__ = ["stepping_rules", "probe", "package_dir", "load_map",
95
+ "shadow_rel",
95
96
  "translate_breakpoints", "translate_frames",
96
97
  "snap_breakpoint_line", "snap_breakpoint_lines",
97
98
  "run_direct", "main"]
98
99
 
99
100
 
101
+ def shadow_rel(source, root=None):
102
+ """Path of the shadow inside the output directory.
103
+
104
+ Without a root, the basename: the shadow sits beside the source, and two
105
+ files never meet. With one — a workspace whose whole build is gathered in
106
+ a single directory — the tree is mirrored, which is what keeps `a/m.ldpy`
107
+ and `b/m.ldpy` from writing the same `m.py`. A source outside the root
108
+ falls back to its basename rather than climbing out of the output
109
+ directory with `../`."""
110
+ name = os.path.basename(source)
111
+ if not root:
112
+ return name
113
+ rel = os.path.relpath(os.path.abspath(source), os.path.abspath(root))
114
+ if rel.startswith(os.pardir + os.sep) or rel == os.pardir:
115
+ return name
116
+ return rel
117
+
118
+
100
119
  def load_map(map_path):
101
120
  """Load a LanguageMap from a .ldpy.map file (JSON)."""
102
121
  with open(map_path, "r", encoding="utf-8") as f:
@@ -167,6 +186,10 @@ def main(argv=None):
167
186
  "straight in the .ldpy)")
168
187
  parser.add_argument("-o", "--out", default=DEFAULT_OUT,
169
188
  help="shadow directory (default: %(default)s)")
189
+ parser.add_argument("--root", metavar="DIR",
190
+ help="tree root the shadow mirrors: the .py is "
191
+ "written at OUT/<path of the source relative "
192
+ "to DIR> (default: OUT/<basename>)")
170
193
  parser.add_argument("--listen", metavar="HOTE:PORT",
171
194
  help="start debugpy listening (e.g. 127.0.0.1:5678)")
172
195
  parser.add_argument("--wait-for-client", action="store_true",
@@ -201,8 +224,7 @@ def main(argv=None):
201
224
 
202
225
  try:
203
226
  py_path, map_path, result = build_file(
204
- args.source, args.out,
205
- rel=os.path.basename(args.source))
227
+ args.source, args.out, rel=shadow_rel(args.source, args.root))
206
228
  except LdpySyntaxError as e:
207
229
  print(str(e), file=sys.stderr)
208
230
  return 1