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.
@@ -0,0 +1,3 @@
1
+ """Qodalis CLI Data Explorer PostgreSQL plugin."""
2
+ from .postgres_provider import PostgresDataExplorerProvider
3
+ __all__ = ["PostgresDataExplorerProvider"]
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any