jusi-sql 0.2.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.
jusi_sql-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 notawhaleble
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.5
2
+ Name: jusi-sql
3
+ Version: 0.2.0
4
+ Summary: The SQL plugin-family contract and reusable provider toolkit for Jusi 1.0
5
+ Author: Jusi contributors
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 notawhaleble
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ License-File: LICENSE
28
+ Requires-Python: >=3.9
29
+ Requires-Dist: jusi<2,>=1.0
30
+ Requires-Dist: sqlparse<0.6,>=0.5
31
+ Provides-Extra: test
32
+ Requires-Dist: pytest<9,>=8; extra == 'test'
33
+ Provides-Extra: visidata
34
+ Requires-Dist: visidata<4,>=3; extra == 'visidata'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # jusi-sql
38
+
39
+ `jusi-sql` is the independently installable SQL plugin family for Jusi 1.0.
40
+ It owns the user-facing `%%sql` contract and the provider-neutral code that
41
+ SQLite, PostgreSQL, ClickHouse, and future database plugins should share. It is
42
+ not an exact provider and therefore publishes no `jusi.plugins.v1` entry point.
43
+
44
+ ## Family contract
45
+
46
+ Every exact SQL provider advertises the same catalog claim:
47
+
48
+ - family `sql`, magic `sql`
49
+ - capabilities `execute`, `followup`, `complete`, `interrupt`, and
50
+ `editor_actions`
51
+ - shared `sql` syntax and indentation profiles
52
+ - terminal-interactive presentation
53
+
54
+ An exact provider may refine syntax after handoff (for example `pgsql` or
55
+ `clickhouse`), but may not change the shared capabilities or presentation.
56
+ Providers own connections, transactions, dialect metadata queries,
57
+ cancellation, result rendering, selection meaning, and application IPC.
58
+
59
+ The family owns target configuration and selection, deterministic ownership of
60
+ one `%%sql` magic, the initial handoff payload, continuing-operation parsing,
61
+ metadata-aware completion, safe cache paths, and private application staging.
62
+ There is no process-global live-session registry. A worker and its exact
63
+ provider session belong to one client in one notebook-runtime generation.
64
+
65
+ ## Configuration
66
+
67
+ The canonical 1.0 shape is:
68
+
69
+ ```toml
70
+ [sql.targets.warehouse]
71
+ provider = "clickhouse"
72
+ host = "db.internal"
73
+ database = "analytics"
74
+
75
+ [sql.targets.reporting]
76
+ provider = "postgres"
77
+ host = "pg.internal"
78
+ dbname = "reports"
79
+ ```
80
+
81
+ Then a cell starts with `%%sql warehouse`. The provider is resolved only inside
82
+ the target kernel against adapters installed in that runtime. Missing, unknown,
83
+ unavailable, and ambiguous providers fail without printing target options. The
84
+ historical `[sql.warehouse]` shape is accepted during migration, but it cannot
85
+ be mixed with `[sql.targets.*]`.
86
+
87
+ Secrets may be provider options because the target worker needs them. They are
88
+ never placed in catalog metadata or error messages. Prefer provider-owned
89
+ environment or secret resolution where possible.
90
+
91
+ ## Exact-provider integration
92
+
93
+ Catalog code stays lightweight:
94
+
95
+ ```python
96
+ from jusi_sql import sql_catalog_entry
97
+
98
+ def catalog_entry():
99
+ return sql_catalog_entry(
100
+ plugin_id="postgres",
101
+ plugin_version="1.0.0",
102
+ distribution="jusi-postgres",
103
+ kernel_extension="jusi_postgres.kernel",
104
+ worker_entry_point="jusi_postgres.worker:create_worker",
105
+ provider_presentation={"syntax": "pgsql", "indent": "sql"},
106
+ )
107
+ ```
108
+
109
+ The exact kernel module retains its own attested identity while sharing the
110
+ dispatcher:
111
+
112
+ ```python
113
+ from jusi_sql.kernel import SqlKernelAdapter
114
+
115
+ _adapter = SqlKernelAdapter(
116
+ plugin_id="postgres",
117
+ plugin_version="1.0.0",
118
+ selectors=("postgres", "postgresql"),
119
+ empty_sql="SELECT 1 WHERE false",
120
+ )
121
+
122
+ jusi_kernel_adapter_v1 = _adapter.manifest
123
+ configure_jusi_runtime_v1 = _adapter.configure
124
+ load_ipython_extension = _adapter.load
125
+ ```
126
+
127
+ Each installed adapter registers its identity in the shared dispatcher. After
128
+ all catalog modules attest, `%%sql` resolves the alias and emits one exact 1.0
129
+ handoff. Registration rejects selector collisions and an unrelated extension
130
+ already owning `%%sql`; it never relies on import order.
131
+
132
+ Worker code uses composition rather than the retired handler hierarchy:
133
+
134
+ ```python
135
+ from jusi_sql.worker import SqlWorker
136
+
137
+ def create_worker(context):
138
+ return SqlWorker(context, PostgresSession)
139
+ ```
140
+
141
+ `PostgresSession` implements `execute`, `followup`, `complete`, `interrupt`,
142
+ `editor_action`, and `close`. `interrupt` is called concurrently and must be
143
+ prompt and thread-safe. Recoverable database errors should raise
144
+ `jusi.plugin_api.OperationRejected`; broken sessions should fail normally so
145
+ Jusi fences the worker.
146
+
147
+ ## Completion and metadata
148
+
149
+ `complete_sql` consumes a `MetadataSnapshot` and Jusi's full completion payload.
150
+ It returns absolute Unicode code-point ranges, preserves suffix text, handles
151
+ empty prefixes, and understands schema, table, alias, and column qualification.
152
+ Exact providers only collect dialect metadata and supply keyword lists.
153
+
154
+ `MetadataCache` provides stale-while-refresh snapshots.
155
+ `sql_cache_directory` creates stable provider-scoped paths while recursively
156
+ redacting secret values from the fingerprint.
157
+
158
+ Providers using VisiData may install the optional `visidata` extra and use
159
+ `SqlSheetActions`, `bind_sql_actions`, and `install_visidata_commands`. This
160
+ supplies the common numeric/`gf` fetch and `gc`/`gr` transaction commands on
161
+ result sheets and all derived sheets. Live callbacks remain attached to their
162
+ own provider sheet; the family does not keep a global current session.
163
+
164
+ ## Migration from 0.x providers
165
+
166
+ Remove `DisplayHandlerSpec`, `BaseSqlHandler`, `SqlSheetRuntime`, environment
167
+ payloads, and the `jusi plugin-runtime` bootstrap. Split each provider into
168
+ catalog, kernel adapter, worker, and terminal-application import boundaries.
169
+ Keep VisiData and driver imports out of catalog and worker startup.
170
+
171
+ PostgreSQL- and ClickHouse-specific connection, streaming, binary-value,
172
+ Kerberos, transaction, and cancellation code remains in those packages. Their
173
+ duplicated target parsing, magic registration, completion models, relation
174
+ parsing, metadata cache, safe cache path, and operation dispatch should be
175
+ deleted in favor of this family package during their 1.0 migrations.
@@ -0,0 +1,139 @@
1
+ # jusi-sql
2
+
3
+ `jusi-sql` is the independently installable SQL plugin family for Jusi 1.0.
4
+ It owns the user-facing `%%sql` contract and the provider-neutral code that
5
+ SQLite, PostgreSQL, ClickHouse, and future database plugins should share. It is
6
+ not an exact provider and therefore publishes no `jusi.plugins.v1` entry point.
7
+
8
+ ## Family contract
9
+
10
+ Every exact SQL provider advertises the same catalog claim:
11
+
12
+ - family `sql`, magic `sql`
13
+ - capabilities `execute`, `followup`, `complete`, `interrupt`, and
14
+ `editor_actions`
15
+ - shared `sql` syntax and indentation profiles
16
+ - terminal-interactive presentation
17
+
18
+ An exact provider may refine syntax after handoff (for example `pgsql` or
19
+ `clickhouse`), but may not change the shared capabilities or presentation.
20
+ Providers own connections, transactions, dialect metadata queries,
21
+ cancellation, result rendering, selection meaning, and application IPC.
22
+
23
+ The family owns target configuration and selection, deterministic ownership of
24
+ one `%%sql` magic, the initial handoff payload, continuing-operation parsing,
25
+ metadata-aware completion, safe cache paths, and private application staging.
26
+ There is no process-global live-session registry. A worker and its exact
27
+ provider session belong to one client in one notebook-runtime generation.
28
+
29
+ ## Configuration
30
+
31
+ The canonical 1.0 shape is:
32
+
33
+ ```toml
34
+ [sql.targets.warehouse]
35
+ provider = "clickhouse"
36
+ host = "db.internal"
37
+ database = "analytics"
38
+
39
+ [sql.targets.reporting]
40
+ provider = "postgres"
41
+ host = "pg.internal"
42
+ dbname = "reports"
43
+ ```
44
+
45
+ Then a cell starts with `%%sql warehouse`. The provider is resolved only inside
46
+ the target kernel against adapters installed in that runtime. Missing, unknown,
47
+ unavailable, and ambiguous providers fail without printing target options. The
48
+ historical `[sql.warehouse]` shape is accepted during migration, but it cannot
49
+ be mixed with `[sql.targets.*]`.
50
+
51
+ Secrets may be provider options because the target worker needs them. They are
52
+ never placed in catalog metadata or error messages. Prefer provider-owned
53
+ environment or secret resolution where possible.
54
+
55
+ ## Exact-provider integration
56
+
57
+ Catalog code stays lightweight:
58
+
59
+ ```python
60
+ from jusi_sql import sql_catalog_entry
61
+
62
+ def catalog_entry():
63
+ return sql_catalog_entry(
64
+ plugin_id="postgres",
65
+ plugin_version="1.0.0",
66
+ distribution="jusi-postgres",
67
+ kernel_extension="jusi_postgres.kernel",
68
+ worker_entry_point="jusi_postgres.worker:create_worker",
69
+ provider_presentation={"syntax": "pgsql", "indent": "sql"},
70
+ )
71
+ ```
72
+
73
+ The exact kernel module retains its own attested identity while sharing the
74
+ dispatcher:
75
+
76
+ ```python
77
+ from jusi_sql.kernel import SqlKernelAdapter
78
+
79
+ _adapter = SqlKernelAdapter(
80
+ plugin_id="postgres",
81
+ plugin_version="1.0.0",
82
+ selectors=("postgres", "postgresql"),
83
+ empty_sql="SELECT 1 WHERE false",
84
+ )
85
+
86
+ jusi_kernel_adapter_v1 = _adapter.manifest
87
+ configure_jusi_runtime_v1 = _adapter.configure
88
+ load_ipython_extension = _adapter.load
89
+ ```
90
+
91
+ Each installed adapter registers its identity in the shared dispatcher. After
92
+ all catalog modules attest, `%%sql` resolves the alias and emits one exact 1.0
93
+ handoff. Registration rejects selector collisions and an unrelated extension
94
+ already owning `%%sql`; it never relies on import order.
95
+
96
+ Worker code uses composition rather than the retired handler hierarchy:
97
+
98
+ ```python
99
+ from jusi_sql.worker import SqlWorker
100
+
101
+ def create_worker(context):
102
+ return SqlWorker(context, PostgresSession)
103
+ ```
104
+
105
+ `PostgresSession` implements `execute`, `followup`, `complete`, `interrupt`,
106
+ `editor_action`, and `close`. `interrupt` is called concurrently and must be
107
+ prompt and thread-safe. Recoverable database errors should raise
108
+ `jusi.plugin_api.OperationRejected`; broken sessions should fail normally so
109
+ Jusi fences the worker.
110
+
111
+ ## Completion and metadata
112
+
113
+ `complete_sql` consumes a `MetadataSnapshot` and Jusi's full completion payload.
114
+ It returns absolute Unicode code-point ranges, preserves suffix text, handles
115
+ empty prefixes, and understands schema, table, alias, and column qualification.
116
+ Exact providers only collect dialect metadata and supply keyword lists.
117
+
118
+ `MetadataCache` provides stale-while-refresh snapshots.
119
+ `sql_cache_directory` creates stable provider-scoped paths while recursively
120
+ redacting secret values from the fingerprint.
121
+
122
+ Providers using VisiData may install the optional `visidata` extra and use
123
+ `SqlSheetActions`, `bind_sql_actions`, and `install_visidata_commands`. This
124
+ supplies the common numeric/`gf` fetch and `gc`/`gr` transaction commands on
125
+ result sheets and all derived sheets. Live callbacks remain attached to their
126
+ own provider sheet; the family does not keep a global current session.
127
+
128
+ ## Migration from 0.x providers
129
+
130
+ Remove `DisplayHandlerSpec`, `BaseSqlHandler`, `SqlSheetRuntime`, environment
131
+ payloads, and the `jusi plugin-runtime` bootstrap. Split each provider into
132
+ catalog, kernel adapter, worker, and terminal-application import boundaries.
133
+ Keep VisiData and driver imports out of catalog and worker startup.
134
+
135
+ PostgreSQL- and ClickHouse-specific connection, streaming, binary-value,
136
+ Kerberos, transaction, and cancellation code remains in those packages. Their
137
+ duplicated target parsing, magic registration, completion models, relation
138
+ parsing, metadata cache, safe cache path, and operation dispatch should be
139
+ deleted in favor of this family package during their 1.0 migrations.
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "jusi-sql"
7
+ version = "0.2.0"
8
+ description = "The SQL plugin-family contract and reusable provider toolkit for Jusi 1.0"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { file = "LICENSE" }
12
+ authors = [
13
+ { name = "Jusi contributors" }
14
+ ]
15
+ dependencies = [
16
+ "jusi>=1.0,<2",
17
+ "sqlparse>=0.5,<0.6",
18
+ ]
19
+
20
+ [project.optional-dependencies]
21
+ visidata = [
22
+ "visidata>=3,<4",
23
+ ]
24
+ test = [
25
+ "pytest>=8,<9",
26
+ ]
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["src/jusi_sql"]
30
+
31
+ [tool.pytest.ini_options]
32
+ addopts = "-ra"
33
+ testpaths = ["tests"]
@@ -0,0 +1,63 @@
1
+ """Provider-neutral SQL family primitives for Jusi 1.0.
2
+
3
+ This module intentionally imports neither IPython nor a terminal application.
4
+ Catalog discovery can therefore import it without crossing runtime boundaries.
5
+ """
6
+
7
+ from .catalog import (
8
+ FAMILY_ID,
9
+ MAGIC_NAME,
10
+ SQL_CAPABILITIES,
11
+ SQL_PRESENTATION,
12
+ sql_catalog_entry,
13
+ sql_family_claim,
14
+ )
15
+ from .completion import (
16
+ CompletionColumn,
17
+ CompletionItem,
18
+ CompletionObject,
19
+ MetadataSnapshot,
20
+ SqlCompletionRequest,
21
+ complete_sql,
22
+ parse_query_relations,
23
+ )
24
+ from .config import (
25
+ ResolvedSqlTarget,
26
+ SqlConfigError,
27
+ SqlFamilyConfig,
28
+ SqlProviderIdentity,
29
+ SqlTarget,
30
+ resolve_sql_target,
31
+ )
32
+ from .metadata import MetadataCache, sql_cache_directory
33
+ from .visidata import SqlSheetActions, bind_sql_actions, find_sql_actions, install_visidata_commands
34
+
35
+ __all__ = [
36
+ "FAMILY_ID",
37
+ "MAGIC_NAME",
38
+ "SQL_CAPABILITIES",
39
+ "SQL_PRESENTATION",
40
+ "CompletionColumn",
41
+ "CompletionItem",
42
+ "CompletionObject",
43
+ "MetadataSnapshot",
44
+ "MetadataCache",
45
+ "ResolvedSqlTarget",
46
+ "SqlCompletionRequest",
47
+ "SqlConfigError",
48
+ "SqlFamilyConfig",
49
+ "SqlProviderIdentity",
50
+ "SqlSheetActions",
51
+ "SqlTarget",
52
+ "complete_sql",
53
+ "bind_sql_actions",
54
+ "find_sql_actions",
55
+ "install_visidata_commands",
56
+ "parse_query_relations",
57
+ "resolve_sql_target",
58
+ "sql_catalog_entry",
59
+ "sql_cache_directory",
60
+ "sql_family_claim",
61
+ ]
62
+
63
+ __version__ = "0.2.0"
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Iterable, Mapping
4
+
5
+
6
+ FAMILY_ID = "sql"
7
+ MAGIC_NAME = "sql"
8
+ SQL_CAPABILITIES = ("execute", "followup", "complete", "interrupt", "editor_actions")
9
+ SQL_PRESENTATION = {"syntax": "sql", "indent": "sql"}
10
+
11
+
12
+ def sql_family_claim(
13
+ *,
14
+ provider_presentation: Mapping[str, str] | None = None,
15
+ ) -> dict[str, Any]:
16
+ """Return the one canonical catalog claim shared by every SQL provider."""
17
+ claim: dict[str, Any] = {
18
+ "family_id": FAMILY_ID,
19
+ "magic_name": MAGIC_NAME,
20
+ "capabilities": list(SQL_CAPABILITIES),
21
+ "presentation": dict(SQL_PRESENTATION),
22
+ }
23
+ if provider_presentation:
24
+ claim["provider_presentation"] = dict(provider_presentation)
25
+ return claim
26
+
27
+
28
+ def sql_catalog_entry(
29
+ *,
30
+ plugin_id: str,
31
+ plugin_version: str,
32
+ distribution: str,
33
+ kernel_extension: str,
34
+ worker_entry_point: str,
35
+ provider_presentation: Mapping[str, str] | None = None,
36
+ media_types: Iterable[str] = ("text/x-ansi",),
37
+ ) -> dict[str, Any]:
38
+ """Build an exact-provider catalog entry without importing runtime code."""
39
+ return {
40
+ "plugin_id": _required(plugin_id, "plugin_id"),
41
+ "plugin_version": _required(plugin_version, "plugin_version"),
42
+ "distribution": _required(distribution, "distribution"),
43
+ "families": [sql_family_claim(provider_presentation=provider_presentation)],
44
+ "kernel_extensions": [_required(kernel_extension, "kernel_extension")],
45
+ "worker_entry_point": _required(worker_entry_point, "worker_entry_point"),
46
+ "media_types": list(dict.fromkeys(media_types)),
47
+ "interaction": "terminal_interactive",
48
+ }
49
+
50
+
51
+ def _required(value: str, name: str) -> str:
52
+ normalized = str(value).strip()
53
+ if not normalized:
54
+ raise ValueError(f"{name} must be a non-empty string")
55
+ return normalized