qodalis-cli-server-plugin-data-explorer-postgres 1.0.0b18__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.
- qodalis_cli_data_explorer_postgres/__init__.py +3 -0
- qodalis_cli_data_explorer_postgres/postgres_provider.py +146 -0
- qodalis_cli_server_plugin_data_explorer_postgres-1.0.0b18.dist-info/METADATA +15 -0
- qodalis_cli_server_plugin_data_explorer_postgres-1.0.0b18.dist-info/RECORD +5 -0
- qodalis_cli_server_plugin_data_explorer_postgres-1.0.0b18.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""PostgreSQL-backed Data Explorer provider."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
import asyncpg
|
|
8
|
+
|
|
9
|
+
from qodalis_cli_server_abstractions import (
|
|
10
|
+
DataExplorerExecutionContext,
|
|
11
|
+
DataExplorerProviderOptions,
|
|
12
|
+
DataExplorerResult,
|
|
13
|
+
DataExplorerSchemaColumn,
|
|
14
|
+
DataExplorerSchemaResult,
|
|
15
|
+
DataExplorerSchemaTable,
|
|
16
|
+
IDataExplorerProvider,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PostgresDataExplorerProvider(IDataExplorerProvider):
|
|
21
|
+
"""Executes SQL queries against a PostgreSQL database."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, connection_string: str) -> None:
|
|
24
|
+
self._connection_string = connection_string
|
|
25
|
+
|
|
26
|
+
async def execute_async(
|
|
27
|
+
self, context: DataExplorerExecutionContext
|
|
28
|
+
) -> DataExplorerResult:
|
|
29
|
+
"""Execute a SQL query against the PostgreSQL database."""
|
|
30
|
+
start = time.monotonic()
|
|
31
|
+
conn: asyncpg.Connection | None = None
|
|
32
|
+
try:
|
|
33
|
+
conn = await asyncpg.connect(self._connection_string)
|
|
34
|
+
stmt = await conn.prepare(context.query)
|
|
35
|
+
attributes = stmt.get_attributes()
|
|
36
|
+
|
|
37
|
+
if attributes:
|
|
38
|
+
columns = [attr.name for attr in attributes]
|
|
39
|
+
rows_raw = await stmt.fetch(*( context.parameters or []))
|
|
40
|
+
rows = [list(row) for row in rows_raw]
|
|
41
|
+
return DataExplorerResult(
|
|
42
|
+
success=True,
|
|
43
|
+
source=context.options.name,
|
|
44
|
+
language=context.options.language,
|
|
45
|
+
default_output_format=context.options.default_output_format,
|
|
46
|
+
execution_time=int((time.monotonic() - start) * 1000),
|
|
47
|
+
columns=columns,
|
|
48
|
+
rows=rows,
|
|
49
|
+
row_count=len(rows),
|
|
50
|
+
truncated=False,
|
|
51
|
+
error=None,
|
|
52
|
+
)
|
|
53
|
+
else:
|
|
54
|
+
status = await conn.execute(context.query, *( context.parameters or []))
|
|
55
|
+
try:
|
|
56
|
+
affected = int(status.split()[-1])
|
|
57
|
+
except (ValueError, IndexError):
|
|
58
|
+
affected = 0
|
|
59
|
+
return DataExplorerResult(
|
|
60
|
+
success=True,
|
|
61
|
+
source=context.options.name,
|
|
62
|
+
language=context.options.language,
|
|
63
|
+
default_output_format=context.options.default_output_format,
|
|
64
|
+
execution_time=int((time.monotonic() - start) * 1000),
|
|
65
|
+
columns=["changes"],
|
|
66
|
+
rows=[[affected]],
|
|
67
|
+
row_count=1,
|
|
68
|
+
truncated=False,
|
|
69
|
+
error=None,
|
|
70
|
+
)
|
|
71
|
+
except Exception as exc:
|
|
72
|
+
return DataExplorerResult(
|
|
73
|
+
success=False,
|
|
74
|
+
source=context.options.name,
|
|
75
|
+
language=context.options.language,
|
|
76
|
+
default_output_format=context.options.default_output_format,
|
|
77
|
+
execution_time=int((time.monotonic() - start) * 1000),
|
|
78
|
+
columns=None,
|
|
79
|
+
rows=[],
|
|
80
|
+
row_count=0,
|
|
81
|
+
truncated=False,
|
|
82
|
+
error=str(exc),
|
|
83
|
+
)
|
|
84
|
+
finally:
|
|
85
|
+
if conn is not None:
|
|
86
|
+
await conn.close()
|
|
87
|
+
|
|
88
|
+
async def get_schema_async(
|
|
89
|
+
self, options: DataExplorerProviderOptions
|
|
90
|
+
) -> DataExplorerSchemaResult | None:
|
|
91
|
+
"""Return the public schema (tables, views, and their columns)."""
|
|
92
|
+
conn: asyncpg.Connection | None = None
|
|
93
|
+
try:
|
|
94
|
+
conn = await asyncpg.connect(self._connection_string)
|
|
95
|
+
|
|
96
|
+
table_rows = await conn.fetch(
|
|
97
|
+
"SELECT table_name, table_type FROM information_schema.tables "
|
|
98
|
+
"WHERE table_schema = 'public' "
|
|
99
|
+
"ORDER BY table_name"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
tables: list[DataExplorerSchemaTable] = []
|
|
103
|
+
for table_row in table_rows:
|
|
104
|
+
table_name = table_row["table_name"]
|
|
105
|
+
table_type = table_row["table_type"]
|
|
106
|
+
|
|
107
|
+
col_rows = await conn.fetch(
|
|
108
|
+
"SELECT column_name, data_type, is_nullable "
|
|
109
|
+
"FROM information_schema.columns "
|
|
110
|
+
"WHERE table_schema = 'public' AND table_name = $1 "
|
|
111
|
+
"ORDER BY ordinal_position",
|
|
112
|
+
table_name,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
pk_rows = await conn.fetch(
|
|
116
|
+
"SELECT kcu.column_name "
|
|
117
|
+
"FROM information_schema.table_constraints tc "
|
|
118
|
+
"JOIN information_schema.key_column_usage kcu "
|
|
119
|
+
" ON tc.constraint_name = kcu.constraint_name "
|
|
120
|
+
" AND tc.table_schema = kcu.table_schema "
|
|
121
|
+
"WHERE tc.constraint_type = 'PRIMARY KEY' "
|
|
122
|
+
" AND tc.table_schema = 'public' "
|
|
123
|
+
" AND tc.table_name = $1",
|
|
124
|
+
table_name,
|
|
125
|
+
)
|
|
126
|
+
pk_columns = {row["column_name"] for row in pk_rows}
|
|
127
|
+
|
|
128
|
+
columns = [
|
|
129
|
+
DataExplorerSchemaColumn(
|
|
130
|
+
name=row["column_name"],
|
|
131
|
+
type=row["data_type"],
|
|
132
|
+
nullable=row["is_nullable"] == "YES",
|
|
133
|
+
primary_key=row["column_name"] in pk_columns,
|
|
134
|
+
)
|
|
135
|
+
for row in col_rows
|
|
136
|
+
]
|
|
137
|
+
tables.append(DataExplorerSchemaTable(
|
|
138
|
+
name=table_name,
|
|
139
|
+
type=table_type,
|
|
140
|
+
columns=columns,
|
|
141
|
+
))
|
|
142
|
+
|
|
143
|
+
return DataExplorerSchemaResult(source=options.name, tables=tables)
|
|
144
|
+
finally:
|
|
145
|
+
if conn is not None:
|
|
146
|
+
await conn.close()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: qodalis-cli-server-plugin-data-explorer-postgres
|
|
3
|
+
Version: 1.0.0b18
|
|
4
|
+
Summary: PostgreSQL Data Explorer provider plugin for Qodalis CLI servers
|
|
5
|
+
Project-URL: Homepage, https://github.com/qodalis-solutions/cli-server-python
|
|
6
|
+
Project-URL: Repository, https://github.com/qodalis-solutions/cli-server-python
|
|
7
|
+
Author-email: Nicolae Lupei <nicolae.lupei@qodalis.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: cli,data-explorer,plugin,postgres,postgresql,qodalis
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Requires-Dist: asyncpg>=0.29.0
|
|
15
|
+
Requires-Dist: qodalis-cli-server-abstractions
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
qodalis_cli_data_explorer_postgres/__init__.py,sha256=HIW1z_te2j7ZGALtqeQmnolyb7G8WQDw9uz2Yjizmag,154
|
|
2
|
+
qodalis_cli_data_explorer_postgres/postgres_provider.py,sha256=XKgl7_ZoWvOjoBfpX4x40-UOi78-r_u-MYJoDVzrn4I,5661
|
|
3
|
+
qodalis_cli_server_plugin_data_explorer_postgres-1.0.0b18.dist-info/METADATA,sha256=NCDMSyDhGCMbHp3BXoVeVDqxy7_EbGPu6jD8LCFbdXs,714
|
|
4
|
+
qodalis_cli_server_plugin_data_explorer_postgres-1.0.0b18.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
5
|
+
qodalis_cli_server_plugin_data_explorer_postgres-1.0.0b18.dist-info/RECORD,,
|