jusi-postgres 0.2.0__tar.gz → 0.2.1__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_postgres-0.2.0 → jusi_postgres-0.2.1}/PKG-INFO +6 -2
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/README.md +4 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/pyproject.toml +2 -2
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/src/jusi_postgres/__init__.py +1 -1
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/src/jusi_postgres/runner.py +37 -5
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/tests/test_plugin.py +56 -1
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/.gitignore +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/Dockerfile +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/LICENSE +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/docker/postgres/initdb.d/001_jusi_demo.sql +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/docker/postgres/initdb.d/002_jusi_blob_fixture.sql +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/examples/jusi.toml +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/fixtures/blob-readme.txt +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/fixtures/test-blob.zip +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/scripts/install-blob-fixture.sh +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/scripts/run-postgres.sh +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/src/jusi_postgres/catalog.py +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/src/jusi_postgres/config.py +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/src/jusi_postgres/constants.py +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/src/jusi_postgres/ipc.py +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/src/jusi_postgres/kernel.py +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/src/jusi_postgres/metadata.py +0 -0
- {jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/src/jusi_postgres/worker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jusi-postgres
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: PostgreSQL provider plugin for Jusi SQL
|
|
5
5
|
Author: Jusi contributors
|
|
6
6
|
License: MIT License
|
|
@@ -27,7 +27,7 @@ License: MIT License
|
|
|
27
27
|
License-File: LICENSE
|
|
28
28
|
Requires-Python: >=3.9
|
|
29
29
|
Requires-Dist: jusi-sql<0.3,>=0.2
|
|
30
|
-
Requires-Dist: jusi<2,>=1.0
|
|
30
|
+
Requires-Dist: jusi<2,>=1.0.2
|
|
31
31
|
Requires-Dist: psycopg[binary]>=3.1
|
|
32
32
|
Requires-Dist: visidata<4,>=3
|
|
33
33
|
Provides-Extra: test
|
|
@@ -91,6 +91,10 @@ transaction, and server-side cursors. `JusiInterrupt` requests cancellation on
|
|
|
91
91
|
that connection. Closing the client closes its cursors, metadata connection,
|
|
92
92
|
main connection, private control socket, and staged launch data.
|
|
93
93
|
|
|
94
|
+
The terminal application loads normal VisiData user configuration and plugins
|
|
95
|
+
before installing Jusi and PostgreSQL commands. This includes `~/.visidatarc`
|
|
96
|
+
and paths selected through `VD_CONFIG` and `VD_DIR`.
|
|
97
|
+
|
|
94
98
|
## Local development database
|
|
95
99
|
|
|
96
100
|
Start a disposable PostgreSQL fixture:
|
|
@@ -55,6 +55,10 @@ transaction, and server-side cursors. `JusiInterrupt` requests cancellation on
|
|
|
55
55
|
that connection. Closing the client closes its cursors, metadata connection,
|
|
56
56
|
main connection, private control socket, and staged launch data.
|
|
57
57
|
|
|
58
|
+
The terminal application loads normal VisiData user configuration and plugins
|
|
59
|
+
before installing Jusi and PostgreSQL commands. This includes `~/.visidatarc`
|
|
60
|
+
and paths selected through `VD_CONFIG` and `VD_DIR`.
|
|
61
|
+
|
|
58
62
|
## Local development database
|
|
59
63
|
|
|
60
64
|
Start a disposable PostgreSQL fixture:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "jusi-postgres"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "PostgreSQL provider plugin for Jusi SQL"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -13,7 +13,7 @@ authors = [
|
|
|
13
13
|
{ name = "Jusi contributors" }
|
|
14
14
|
]
|
|
15
15
|
dependencies = [
|
|
16
|
-
"jusi>=1.0,<2",
|
|
16
|
+
"jusi>=1.0.2,<2",
|
|
17
17
|
"jusi-sql>=0.2,<0.3",
|
|
18
18
|
"psycopg[binary]>=3.1",
|
|
19
19
|
"visidata>=3,<4",
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
import curses
|
|
5
5
|
from pathlib import Path
|
|
6
|
+
import re
|
|
6
7
|
import sys
|
|
7
8
|
import tempfile
|
|
8
9
|
import threading
|
|
@@ -37,6 +38,9 @@ POSTGRES_KEYWORDS = (
|
|
|
37
38
|
"VALUES", "UPDATE", "SET", "DELETE", "RETURNING", "WITH", "AS", "DISTINCT",
|
|
38
39
|
"CREATE", "ALTER", "DROP", "TABLE", "VIEW", "INDEX", "BEGIN", "COMMIT", "ROLLBACK",
|
|
39
40
|
)
|
|
41
|
+
POSTGRES_RELATION_KEYWORDS = frozenset({
|
|
42
|
+
"FROM", "JOIN", "INTO", "UPDATE", "TABLE", "VIEW", "DESCRIBE", "DESC",
|
|
43
|
+
})
|
|
40
44
|
_PENDING_SHEETS: deque[Any] = deque()
|
|
41
45
|
|
|
42
46
|
|
|
@@ -129,7 +133,7 @@ class PostgresSession:
|
|
|
129
133
|
def complete(self, request: SqlCompletionRequest) -> dict[str, Any]:
|
|
130
134
|
self.show_metadata_warnings()
|
|
131
135
|
snapshot = MetadataSnapshot() if not self.collect_metadata else self.metadata.snapshot()
|
|
132
|
-
return
|
|
136
|
+
return _complete_postgres_sql(snapshot, request)
|
|
133
137
|
|
|
134
138
|
def enter_cell(self) -> None:
|
|
135
139
|
if not self.collect_metadata:
|
|
@@ -240,6 +244,30 @@ class PostgresSession:
|
|
|
240
244
|
sheet.cursor_closed_reason = reason
|
|
241
245
|
|
|
242
246
|
|
|
247
|
+
def _complete_postgres_sql(
|
|
248
|
+
snapshot: MetadataSnapshot,
|
|
249
|
+
request: SqlCompletionRequest,
|
|
250
|
+
) -> dict[str, list[dict[str, Any]]]:
|
|
251
|
+
if _is_blank_relation_context(request.prefix):
|
|
252
|
+
snapshot = MetadataSnapshot(
|
|
253
|
+
schemas=list(snapshot.schemas),
|
|
254
|
+
refreshed_at=snapshot.refreshed_at,
|
|
255
|
+
)
|
|
256
|
+
return complete_sql(
|
|
257
|
+
snapshot,
|
|
258
|
+
request,
|
|
259
|
+
keywords=POSTGRES_KEYWORDS,
|
|
260
|
+
relation_keywords=POSTGRES_RELATION_KEYWORDS,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _is_blank_relation_context(prefix: str) -> bool:
|
|
265
|
+
if not prefix or not prefix[-1].isspace():
|
|
266
|
+
return False
|
|
267
|
+
words = re.findall(r"[A-Za-z_][A-Za-z0-9_]*", prefix)
|
|
268
|
+
return bool(words and words[-1].upper() in POSTGRES_RELATION_KEYWORDS)
|
|
269
|
+
|
|
270
|
+
|
|
243
271
|
class PostgresResultSheet(SequenceSheet):
|
|
244
272
|
rowtype = "rows"
|
|
245
273
|
|
|
@@ -396,6 +424,13 @@ def install_postgres_commands() -> None:
|
|
|
396
424
|
setattr(visidata.BaseSheet, "_jusi_postgres_commands_v1", True)
|
|
397
425
|
|
|
398
426
|
|
|
427
|
+
def _initialize_visidata_application() -> None:
|
|
428
|
+
from jusi.visidata_support import initialize_visidata
|
|
429
|
+
|
|
430
|
+
initialize_visidata(open_name="selection.sql", open_filetype="sql")
|
|
431
|
+
install_postgres_commands()
|
|
432
|
+
|
|
433
|
+
|
|
399
434
|
def _queue_sheet(sheet: PostgresResultSheet) -> None:
|
|
400
435
|
_PENDING_SHEETS.append(sheet)
|
|
401
436
|
vd.queueCommand("jusi-postgres-open-pending-sheet")
|
|
@@ -540,10 +575,7 @@ def _handle_application_operation(session: PostgresSession, operation: str, payl
|
|
|
540
575
|
def run_postgres_application(payload_path: Path, socket_path: str) -> int:
|
|
541
576
|
session: PostgresSession | None = None
|
|
542
577
|
try:
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
install_postgres_commands()
|
|
546
|
-
install_editor_actions()
|
|
578
|
+
_initialize_visidata_application()
|
|
547
579
|
visidata.vd.timeouts_before_idle = -1
|
|
548
580
|
payload = _read_payload(payload_path)
|
|
549
581
|
query = str(payload.get("sql", "")).strip() or POSTGRES_BOOTSTRAP_SQL
|
|
@@ -12,7 +12,7 @@ import pytest
|
|
|
12
12
|
|
|
13
13
|
from jusi.plugin_api import OperationRejected, WorkerContext
|
|
14
14
|
from jusi.protocol import validate_plugin_kernel_message
|
|
15
|
-
from jusi_sql import CompletionColumn, CompletionObject, MetadataSnapshot, find_sql_actions
|
|
15
|
+
from jusi_sql import CompletionColumn, CompletionObject, MetadataSnapshot, SqlCompletionRequest, find_sql_actions
|
|
16
16
|
from jusi_sql.kernel import _reset_runtime_for_tests, dispatch_sql
|
|
17
17
|
|
|
18
18
|
from jusi_postgres.catalog import catalog_entry
|
|
@@ -24,9 +24,11 @@ from jusi_postgres.runner import (
|
|
|
24
24
|
PostgresResultSheet,
|
|
25
25
|
PostgresSession,
|
|
26
26
|
RawBinaryValue,
|
|
27
|
+
_complete_postgres_sql,
|
|
27
28
|
_connect_psycopg,
|
|
28
29
|
_display_row,
|
|
29
30
|
_followup_sql,
|
|
31
|
+
_initialize_visidata_application,
|
|
30
32
|
_looks_like_result_query,
|
|
31
33
|
_write_raw_value_file,
|
|
32
34
|
)
|
|
@@ -120,6 +122,59 @@ def test_followup_preserves_literal_sql_but_removes_magic_header() -> None:
|
|
|
120
122
|
assert _followup_sql("%%sql analytics\nselect α") == "select α"
|
|
121
123
|
|
|
122
124
|
|
|
125
|
+
def test_blank_relation_completion_returns_only_schemas() -> None:
|
|
126
|
+
prefix = "select * from "
|
|
127
|
+
request = SqlCompletionRequest(prefix, prefix, len(prefix), 0, len(prefix))
|
|
128
|
+
snapshot = MetadataSnapshot(
|
|
129
|
+
schemas=["demo", "public"],
|
|
130
|
+
objects=[CompletionObject("accounts", "demo", "table")],
|
|
131
|
+
columns=[CompletionColumn("accounts", "email", "demo", "text")],
|
|
132
|
+
functions=[CompletionObject("decode_event", "demo", "function")],
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
result = _complete_postgres_sql(snapshot, request)
|
|
136
|
+
|
|
137
|
+
assert [(item["text"], item["kind"]) for item in result["items"]] == [
|
|
138
|
+
("demo", "schema"),
|
|
139
|
+
("public", "schema"),
|
|
140
|
+
]
|
|
141
|
+
assert {(item["start"], item["end"]) for item in result["items"]} == {
|
|
142
|
+
(len(prefix), len(prefix)),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_typed_relation_completion_keeps_matching_metadata() -> None:
|
|
147
|
+
prefix = "select * from acc"
|
|
148
|
+
request = SqlCompletionRequest(prefix, prefix, len(prefix), 0, len(prefix))
|
|
149
|
+
snapshot = MetadataSnapshot(
|
|
150
|
+
schemas=["demo"],
|
|
151
|
+
objects=[CompletionObject("accounts", "demo", "table")],
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
result = _complete_postgres_sql(snapshot, request)
|
|
155
|
+
|
|
156
|
+
assert "accounts" in {item["text"] for item in result["items"]}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_application_loads_visidata_config_before_provider_commands(monkeypatch) -> None: # type: ignore[no-untyped-def]
|
|
160
|
+
calls = []
|
|
161
|
+
monkeypatch.setattr(
|
|
162
|
+
"jusi.visidata_support.initialize_visidata",
|
|
163
|
+
lambda **kwargs: calls.append(("initialize", kwargs)),
|
|
164
|
+
)
|
|
165
|
+
monkeypatch.setattr(
|
|
166
|
+
"jusi_postgres.runner.install_postgres_commands",
|
|
167
|
+
lambda: calls.append(("commands", {})),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
_initialize_visidata_application()
|
|
171
|
+
|
|
172
|
+
assert calls == [
|
|
173
|
+
("initialize", {"open_name": "selection.sql", "open_filetype": "sql"}),
|
|
174
|
+
("commands", {}),
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
|
|
123
178
|
def test_worker_returns_one_terminal_and_delegates_to_family_router(monkeypatch) -> None: # type: ignore[no-untyped-def]
|
|
124
179
|
monkeypatch.setattr("jusi_postgres.worker.find_spec", lambda _name: object())
|
|
125
180
|
worker = create_worker(worker_context())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jusi_postgres-0.2.0 → jusi_postgres-0.2.1}/docker/postgres/initdb.d/002_jusi_blob_fixture.sql
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
|