metta-tables 0.9.2__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.
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: metta-tables
3
+ Version: 0.9.2
4
+ Summary: The tables accessor for MeTTa spaces
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.12
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: pymetta==0.9.2
9
+
10
+ # metta-tables
11
+
12
+ This package declares the `m.tables` accessor through `metta.seam.door`.
13
+ Its immutable `DOORS` records carry the signatures, effects, return shapes,
14
+ and evidence. Discovery imports those records. Calling an accessor loads the
15
+ existing implementation, whose exceptions and lifetime protocol apply.
16
+
17
+ Install beside the matching version of `pymetta`:
18
+
19
+ ```sh
20
+ pip install metta-tables
21
+ ```
@@ -0,0 +1,12 @@
1
+ # metta-tables
2
+
3
+ This package declares the `m.tables` accessor through `metta.seam.door`.
4
+ Its immutable `DOORS` records carry the signatures, effects, return shapes,
5
+ and evidence. Discovery imports those records. Calling an accessor loads the
6
+ existing implementation, whose exceptions and lifetime protocol apply.
7
+
8
+ Install beside the matching version of `pymetta`:
9
+
10
+ ```sh
11
+ pip install metta-tables
12
+ ```
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: metta-tables
3
+ Version: 0.9.2
4
+ Summary: The tables accessor for MeTTa spaces
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.12
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: pymetta==0.9.2
9
+
10
+ # metta-tables
11
+
12
+ This package declares the `m.tables` accessor through `metta.seam.door`.
13
+ Its immutable `DOORS` records carry the signatures, effects, return shapes,
14
+ and evidence. Discovery imports those records. Calling an accessor loads the
15
+ existing implementation, whose exceptions and lifetime protocol apply.
16
+
17
+ Install beside the matching version of `pymetta`:
18
+
19
+ ```sh
20
+ pip install metta-tables
21
+ ```
@@ -0,0 +1,10 @@
1
+ README.md
2
+ metta_tables.py
3
+ pyproject.toml
4
+ metta_tables.egg-info/PKG-INFO
5
+ metta_tables.egg-info/SOURCES.txt
6
+ metta_tables.egg-info/dependency_links.txt
7
+ metta_tables.egg-info/entry_points.txt
8
+ metta_tables.egg-info/requires.txt
9
+ metta_tables.egg-info/top_level.txt
10
+ tests/test_tables_doors.py
@@ -0,0 +1,2 @@
1
+ [metta.extensions]
2
+ metta-tables = metta_tables
@@ -0,0 +1 @@
1
+ pymetta==0.9.2
@@ -0,0 +1 @@
1
+ metta_tables
@@ -0,0 +1,138 @@
1
+ """Purpose: register the tables accessor using deferred implementation references.
2
+
3
+ Guarantees: registration imports no implementation and accessor calls preserve
4
+ its behavior [tested: test_table_namespace_preserves_ingestion_and_conversions; commit=b615b5a33b43252ef9826e5387da7c9bd7f6b543].
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from importlib import import_module
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ import metta.doors as _doors
13
+ from metta import seam
14
+
15
+ if TYPE_CHECKING:
16
+ from metta import Atom, SpaceLike
17
+
18
+
19
+
20
+ @_doors.door(
21
+ kind=_doors.Kind.write,
22
+ answers=_doors.AnswersAs.integer,
23
+ effect=_doors.EffectClass.oracleIO,
24
+ determinism=_doors.Determinism.det,
25
+ tiers=(_doors.Tier.sync, _doors.Tier.context),
26
+ refuses=(_doors.Refusal(_doors.RefusalKind.type, 'ext/metta-tables/tests/test_tables_doors.py::test_tables_add_refuses_an_unsupported_source'),),
27
+ provider=_doors.Provider('metta-tables', 'tables'),
28
+ evidence=('ext/metta-tables/tests/test_tables_doors.py::test_table_namespace_preserves_ingestion_and_conversions',),
29
+ )
30
+ def add(space: SpaceLike, head: Any, data: Any) -> int:
31
+ """Add a tabular source to a space as ``(head column...)`` facts.
32
+
33
+ space may be a context or a space.
34
+
35
+ The source may offer rows its own way (``iter_rows()`` for polars,
36
+ ``itertuples()`` for pandas, a mapping of columns, any iterable of rows)
37
+ or speak the Arrow PyCapsule Interface, which is how a DuckDB relation, a
38
+ pyarrow Table, a Parquet reader or an Ibis expression hands over rows
39
+ without a row-at-a-time Python door. A source with both keeps its own:
40
+ the two produce identical atoms, and the row door is the faster of them
41
+ [measured 2026-09-06: 10,000 rows, polars 14.07 ms through iter_rows
42
+ against 14.36 ms through the stream, pandas 20.39 ms against 25.11 ms].
43
+
44
+ An Arrow source is written one record batch at a time, so a reader larger
45
+ than memory loads, and the writes are one transaction each; wrap the call
46
+ in ``m.transaction(...)`` to make the whole load one.
47
+ """
48
+ return import_module('metta.tables').add(space, head, data)
49
+
50
+ @_doors.door(
51
+ kind=_doors.Kind.write,
52
+ answers=_doors.AnswersAs.atom,
53
+ effect=_doors.EffectClass.oracleIO,
54
+ determinism=_doors.Determinism.det,
55
+ tiers=(_doors.Tier.sync, _doors.Tier.context),
56
+ provider=_doors.Provider('metta-tables', 'tables'),
57
+ evidence=('ext/metta-tables/tests/test_tables_doors.py::test_table_namespace_preserves_ingestion_and_conversions',),
58
+ )
59
+ def declare(m: SpaceLike, name: str, declaration: Atom | str) -> Atom:
60
+ """Write one ctx-scoped bridge declaration into &metta, where explain
61
+ and any program can read the schema, and from_context will.
62
+
63
+ m may be a context or a space.
64
+ """ # noqa: D205 -- preserve the declared documentation
65
+ return import_module('metta.tables').declare(m, name, declaration)
66
+
67
+ @_doors.door(
68
+ kind=_doors.Kind.provider,
69
+ answers=_doors.AnswersAs.tuple,
70
+ effect=_doors.EffectClass.oracleIO,
71
+ determinism=_doors.Determinism.det,
72
+ tiers=(_doors.Tier.sync, _doors.Tier.context),
73
+ provider=_doors.Provider('metta-tables', 'tables'),
74
+ evidence=('ext/metta-tables/tests/test_tables_doors.py::test_table_namespace_preserves_ingestion_and_conversions',),
75
+ )
76
+ def accessors() -> tuple[str, ...]:
77
+ """Install the metta accessor for every registered frame library already imported.
78
+
79
+ Answers the libraries that now carry it, so a program can ask.
80
+
81
+ Registration never imports a frame library. `import metta.tables` costs
82
+ 15 ms and `import pandas` costs 531 ms [measured 2026-09-06,
83
+ time.perf_counter around each import in a fresh interpreter], so a module
84
+ that registered by importing would charge every tables user for a library
85
+ the program may never touch. It installs for whichever registered module
86
+ is in `sys.modules`, every door in this module calls it first, and a
87
+ program that imports a frame library afterwards and touches nothing else
88
+ here calls this by name. Idempotent, because a library warns when an
89
+ accessor name is replaced.
90
+
91
+ Which libraries these are is the `frame` point's rows, not a list here:
92
+ each row says which module it is and how that library spells an accessor,
93
+ so a third one installs `df.metta` by registering
94
+ (`metta.seam.frame.register(...)`, or the `metta.extensions` entry point).
95
+ """
96
+ return import_module('metta.tables').accessors()
97
+
98
+ @_doors.door(
99
+ kind=_doors.Kind.provider,
100
+ answers=_doors.AnswersAs.text,
101
+ effect=_doors.EffectClass.oracleIO,
102
+ determinism=_doors.Determinism.det,
103
+ tiers=(_doors.Tier.sync, _doors.Tier.context),
104
+ refuses=(_doors.Refusal(_doors.RefusalKind.type, 'ext/metta-tables/tests/test_tables_doors.py::test_tables_sql_function_refuses_noncallable_heads'),),
105
+ provider=_doors.Provider('metta-tables', 'tables'),
106
+ evidence=('ext/metta-tables/tests/test_tables_doors.py::test_table_namespace_preserves_ingestion_and_conversions',),
107
+ )
108
+ def sql_function(connection: Any, head: Any, name: str | None=None) -> str:
109
+ """Register a MeTTa head as a scalar SQL function, and answer its SQL name.
110
+
111
+ m.run("(: dbl (-> Number Number)) (= (dbl $x) (* 2 $x))")
112
+ tables.sql_function(connection, m.fn.dbl)
113
+ connection.sql("select dbl(age) from people")
114
+
115
+ The head is the callable from a space's `fn` namespace, which already
116
+ carries its own name, its arity and its arrow, so nothing about the
117
+ function is restated here; `name=` is the escape for a SQL identifier the
118
+ head's own name cannot be.
119
+
120
+ WHICH engines are known is the `sql` point's rows, and the first row that
121
+ claims the connection declares the function its own way: sqlite3 wants the
122
+ arity and no types, DuckDB wants the types and reads them from the head's
123
+ DECLARED arrow, refusing by name when there is none (an arrow
124
+ `inspect.signature` merely infers is a proposal, not a promise). A third
125
+ engine registers rather than being added here. A row that produces no
126
+ answer is SQL NULL and one that produces several refuses, because a scalar
127
+ function has one result per row; a SQL NULL argument reaches the head as
128
+ `Grounded(None)` and MeTTa decides what it means.
129
+ """
130
+ return import_module('metta.tables').sql_function(connection, head, name)
131
+
132
+
133
+ def register() -> None:
134
+ """Publish this package's complete accessor declaration atomically."""
135
+ seam.door.register('metta-tables', doors=_doors.declarations(__name__))
136
+
137
+
138
+ register()
@@ -0,0 +1,20 @@
1
+ # Purpose: distribute the tables door registrations independently of the core.
2
+ # Guarantees: discovery loads only metadata; each body imports when called.
3
+ [build-system]
4
+ requires = ["setuptools>=83"]
5
+ build-backend = "setuptools.build_meta"
6
+
7
+ [project]
8
+ name = "metta-tables"
9
+ version = "0.9.2"
10
+ description = "The tables accessor for MeTTa spaces"
11
+ readme = "README.md"
12
+ requires-python = ">=3.12"
13
+ license = "MIT"
14
+ dependencies = ["pymetta==0.9.2"]
15
+
16
+ [project.entry-points."metta.extensions"]
17
+ metta-tables = "metta_tables"
18
+
19
+ [tool.setuptools]
20
+ py-modules = ["metta_tables"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,46 @@
1
+ """Purpose: verify each registered tables namespace door at its public boundary.
2
+
3
+ Guarantees: ingestion, declarations, accessors, and SQL calls reach their bodies
4
+ [tested: test_table_namespace_preserves_ingestion_and_conversions; commit=b615b5a33b43252ef9826e5387da7c9bd7f6b543].
5
+ Owns resources: the test closes its database connection and engine context.
6
+ """
7
+
8
+ import sqlite3
9
+ from contextlib import closing
10
+
11
+ import metta_sqlite
12
+ import metta_tables
13
+ import pytest
14
+
15
+ from metta import MeTTa, S, V
16
+
17
+
18
+ def test_table_namespace_preserves_ingestion_and_conversions():
19
+ """Table namespace preserves ingestion and conversions."""
20
+ metta_tables.register()
21
+ metta_sqlite.register()
22
+ with MeTTa() as context, closing(sqlite3.connect(":memory:")) as connection:
23
+ assert context.tables.add(S.person, [("Ada", 2), ("Bob", 3)]) == 2
24
+ assert len(context.self[S.person(V.name, V.n)]) == 2
25
+ declaration = context.tables.declare("people", "(bridge (person $name $n) (row people (name $name) (n $n)))")
26
+ assert declaration in context.space("&metta")
27
+ assert isinstance(context.tables.accessors(), tuple)
28
+ context.run("(: twice (-> Number Number)) (= (twice $x) (* 2 $x))")
29
+ assert context.self.tables.sql_function(connection, context.fn.twice, "twice") == "twice"
30
+ assert connection.execute("select twice(21)").fetchall() == [(42,)]
31
+
32
+
33
+ def test_tables_add_refuses_an_unsupported_source():
34
+ """Tables add refuses an unsupported source."""
35
+ metta_tables.register()
36
+ with MeTTa() as context:
37
+ with pytest.raises(TypeError, match="offers none"):
38
+ context.tables.add(S.person, object())
39
+
40
+
41
+ def test_tables_sql_function_refuses_noncallable_heads():
42
+ """Tables sql function refuses noncallable heads."""
43
+ metta_tables.register()
44
+ with MeTTa() as context:
45
+ with pytest.raises(TypeError, match="not callable"):
46
+ context.tables.sql_function(object(), None)