harlequin-postgres 0.2.2__tar.gz → 0.4.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.
- {harlequin_postgres-0.2.2 → harlequin_postgres-0.4.0}/PKG-INFO +9 -3
- {harlequin_postgres-0.2.2 → harlequin_postgres-0.4.0}/README.md +6 -0
- {harlequin_postgres-0.2.2 → harlequin_postgres-0.4.0}/pyproject.toml +8 -7
- {harlequin_postgres-0.2.2 → harlequin_postgres-0.4.0}/src/harlequin_postgres/adapter.py +132 -26
- {harlequin_postgres-0.2.2 → harlequin_postgres-0.4.0}/src/harlequin_postgres/cli_options.py +1 -4
- {harlequin_postgres-0.2.2 → harlequin_postgres-0.4.0}/src/harlequin_postgres/completions.py +2 -2
- {harlequin_postgres-0.2.2 → harlequin_postgres-0.4.0}/LICENSE +0 -0
- {harlequin_postgres-0.2.2 → harlequin_postgres-0.4.0}/src/harlequin_postgres/__init__.py +0 -0
- {harlequin_postgres-0.2.2 → harlequin_postgres-0.4.0}/src/harlequin_postgres/keywords.tsv +0 -0
- {harlequin_postgres-0.2.2 → harlequin_postgres-0.4.0}/src/harlequin_postgres/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: harlequin-postgres
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A Harlequin adapter for Postgres.
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Ted Conbeer
|
|
@@ -11,8 +11,8 @@ Classifier: Programming Language :: Python :: 3
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.9
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
-
Requires-Dist: harlequin (>=1.
|
|
15
|
-
Requires-Dist:
|
|
14
|
+
Requires-Dist: harlequin (>=1.20,<2.0)
|
|
15
|
+
Requires-Dist: psycopg[binary,pool] (>=3.2,<4.0)
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
|
|
18
18
|
# harlequin-postgres
|
|
@@ -85,4 +85,10 @@ Many more options are available; to see the full list, run:
|
|
|
85
85
|
harlequin --help
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
## Manual Transactions
|
|
89
|
+
|
|
90
|
+
To use Manual transaction mode, click on the label in the Run Query Bar to toggle the transaction mode from Auto to Manual.
|
|
91
|
+
|
|
92
|
+
## Further Documentation
|
|
93
|
+
|
|
88
94
|
For more information, see the [Harlequin Docs](https://harlequin.sh/docs/postgres/index).
|
|
@@ -68,4 +68,10 @@ Many more options are available; to see the full list, run:
|
|
|
68
68
|
harlequin --help
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
## Manual Transactions
|
|
72
|
+
|
|
73
|
+
To use Manual transaction mode, click on the label in the Run Query Bar to toggle the transaction mode from Auto to Manual.
|
|
74
|
+
|
|
75
|
+
## Further Documentation
|
|
76
|
+
|
|
71
77
|
For more information, see the [Harlequin Docs](https://harlequin.sh/docs/postgres/index).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "harlequin-postgres"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "A Harlequin adapter for Postgres."
|
|
5
5
|
authors = ["Ted Conbeer <tconbeer@users.noreply.github.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -14,16 +14,15 @@ postgres = "harlequin_postgres:HarlequinPostgresAdapter"
|
|
|
14
14
|
|
|
15
15
|
[tool.poetry.dependencies]
|
|
16
16
|
python = ">=3.8.1,<4.0"
|
|
17
|
-
harlequin = "^1.
|
|
18
|
-
|
|
17
|
+
harlequin = "^1.20"
|
|
18
|
+
psycopg = { version = "^3.2", extras = ["binary", "pool"]}
|
|
19
19
|
|
|
20
20
|
[tool.poetry.group.dev.dependencies]
|
|
21
|
-
ruff = "^0.
|
|
21
|
+
ruff = "^0.5"
|
|
22
22
|
pytest = "^7.4.3"
|
|
23
|
-
mypy = "^1.
|
|
23
|
+
mypy = "^1.10.0"
|
|
24
24
|
pre-commit = "^3.5.0"
|
|
25
25
|
importlib_metadata = { version = ">=4.6.0", python = "<3.10.0" }
|
|
26
|
-
types-psycopg2 = "^2.9.21.16"
|
|
27
26
|
pyinstrument = "^4.6.1"
|
|
28
27
|
|
|
29
28
|
[build-system]
|
|
@@ -32,9 +31,11 @@ build-backend = "poetry.core.masonry.api"
|
|
|
32
31
|
|
|
33
32
|
|
|
34
33
|
[tool.ruff]
|
|
35
|
-
select = ["A", "B", "E", "F", "I"]
|
|
36
34
|
target-version = "py38"
|
|
37
35
|
|
|
36
|
+
[tool.ruff.lint]
|
|
37
|
+
select = ["A", "B", "E", "F", "I"]
|
|
38
|
+
|
|
38
39
|
[tool.mypy]
|
|
39
40
|
python_version = "3.8"
|
|
40
41
|
files = [
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from itertools import cycle
|
|
3
4
|
from typing import Any, Sequence
|
|
4
5
|
|
|
5
6
|
from harlequin import (
|
|
@@ -7,11 +8,14 @@ from harlequin import (
|
|
|
7
8
|
HarlequinCompletion,
|
|
8
9
|
HarlequinConnection,
|
|
9
10
|
HarlequinCursor,
|
|
11
|
+
HarlequinTransactionMode,
|
|
10
12
|
)
|
|
11
13
|
from harlequin.catalog import Catalog, CatalogItem
|
|
12
14
|
from harlequin.exception import HarlequinConnectionError, HarlequinQueryError
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
+
from psycopg import Connection, Cursor, conninfo
|
|
16
|
+
from psycopg.errors import QueryCanceled
|
|
17
|
+
from psycopg.pq import TransactionStatus
|
|
18
|
+
from psycopg_pool import ConnectionPool
|
|
15
19
|
from textual_fastdatatable.backend import AutoBackendType
|
|
16
20
|
|
|
17
21
|
from harlequin_postgres.cli_options import POSTGRES_OPTIONS
|
|
@@ -19,16 +23,19 @@ from harlequin_postgres.completions import _get_completions
|
|
|
19
23
|
|
|
20
24
|
|
|
21
25
|
class HarlequinPostgresCursor(HarlequinCursor):
|
|
22
|
-
def __init__(self, conn: HarlequinPostgresConnection, cur:
|
|
26
|
+
def __init__(self, conn: HarlequinPostgresConnection, cur: Cursor) -> None:
|
|
23
27
|
self.conn = conn
|
|
24
28
|
self.cur = cur
|
|
29
|
+
# we need to copy the description from the cursor in case the results are
|
|
30
|
+
# fetched and the cursor is closed before columns() is called.
|
|
31
|
+
assert cur.description is not None
|
|
32
|
+
self.description = cur.description.copy()
|
|
25
33
|
self._limit: int | None = None
|
|
26
34
|
|
|
27
35
|
def columns(self) -> list[tuple[str, str]]:
|
|
28
|
-
assert self.cur.description is not None
|
|
29
36
|
return [
|
|
30
37
|
(col.name, self.conn._get_short_type_from_oid(col.type_code))
|
|
31
|
-
for col in self.
|
|
38
|
+
for col in self.description
|
|
32
39
|
]
|
|
33
40
|
|
|
34
41
|
def set_limit(self, limit: int) -> HarlequinPostgresCursor:
|
|
@@ -41,6 +48,8 @@ class HarlequinPostgresCursor(HarlequinCursor):
|
|
|
41
48
|
return self.cur.fetchall()
|
|
42
49
|
else:
|
|
43
50
|
return self.cur.fetchmany(self._limit)
|
|
51
|
+
except QueryCanceled:
|
|
52
|
+
return []
|
|
44
53
|
except Exception as e:
|
|
45
54
|
raise HarlequinQueryError(
|
|
46
55
|
msg=str(e),
|
|
@@ -60,25 +69,73 @@ class HarlequinPostgresConnection(HarlequinConnection):
|
|
|
60
69
|
) -> None:
|
|
61
70
|
self.init_message = init_message
|
|
62
71
|
try:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
self.conn_info = conninfo.conninfo_to_dict(
|
|
73
|
+
conninfo=conn_str[0] if conn_str else "", **options
|
|
74
|
+
)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
raise HarlequinConnectionError(
|
|
77
|
+
msg=str(e),
|
|
78
|
+
title=(
|
|
79
|
+
"Harlequin could not connect to Postgres. "
|
|
80
|
+
"Invalid connection string."
|
|
81
|
+
),
|
|
82
|
+
) from e
|
|
83
|
+
try:
|
|
84
|
+
raw_timeout = self.conn_info.get("connect_timeout")
|
|
85
|
+
timeout = float(raw_timeout) if raw_timeout is not None else 30.0
|
|
86
|
+
except (TypeError, ValueError) as e:
|
|
87
|
+
raise HarlequinConnectionError(
|
|
88
|
+
msg=str(e),
|
|
89
|
+
title=(
|
|
90
|
+
"Harlequin could not connect to Postgres. "
|
|
91
|
+
"Invalid value for connection_timeout."
|
|
92
|
+
),
|
|
93
|
+
) from e
|
|
94
|
+
try:
|
|
95
|
+
self.pool: ConnectionPool = ConnectionPool(
|
|
96
|
+
conninfo=conn_str[0] if conn_str and conn_str[0] else "",
|
|
97
|
+
min_size=2,
|
|
98
|
+
max_size=5,
|
|
99
|
+
kwargs=options,
|
|
100
|
+
open=True,
|
|
101
|
+
timeout=timeout,
|
|
102
|
+
)
|
|
103
|
+
self._main_conn: Connection = self.pool.getconn()
|
|
69
104
|
except Exception as e:
|
|
70
105
|
raise HarlequinConnectionError(
|
|
71
106
|
msg=str(e), title="Harlequin could not connect to Postgres."
|
|
72
107
|
) from e
|
|
73
108
|
|
|
109
|
+
self._transaction_modes = cycle(
|
|
110
|
+
[
|
|
111
|
+
HarlequinTransactionMode(label="Auto"),
|
|
112
|
+
HarlequinTransactionMode(
|
|
113
|
+
label="Manual",
|
|
114
|
+
commit=self.commit,
|
|
115
|
+
rollback=self.rollback,
|
|
116
|
+
),
|
|
117
|
+
]
|
|
118
|
+
)
|
|
119
|
+
self.toggle_transaction_mode()
|
|
120
|
+
|
|
74
121
|
def execute(self, query: str) -> HarlequinCursor | None:
|
|
122
|
+
if (
|
|
123
|
+
self.transaction_mode.label != "Auto"
|
|
124
|
+
and self._main_conn.info.transaction_status == TransactionStatus.IDLE
|
|
125
|
+
):
|
|
126
|
+
cur = self._main_conn.cursor()
|
|
127
|
+
cur.execute(query="begin;")
|
|
128
|
+
cur.close()
|
|
129
|
+
|
|
75
130
|
try:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
131
|
+
cur = self._main_conn.cursor()
|
|
132
|
+
cur.execute(query=query)
|
|
133
|
+
except QueryCanceled:
|
|
134
|
+
cur.close()
|
|
135
|
+
return None
|
|
80
136
|
except Exception as e:
|
|
81
137
|
cur.close()
|
|
138
|
+
self.rollback()
|
|
82
139
|
raise HarlequinQueryError(
|
|
83
140
|
msg=str(e),
|
|
84
141
|
title="Harlequin encountered an error while executing your query.",
|
|
@@ -89,8 +146,15 @@ class HarlequinPostgresConnection(HarlequinConnection):
|
|
|
89
146
|
else:
|
|
90
147
|
cur.close()
|
|
91
148
|
return None
|
|
92
|
-
|
|
93
|
-
|
|
149
|
+
|
|
150
|
+
def cancel(self) -> None:
|
|
151
|
+
self._main_conn.cancel_safe()
|
|
152
|
+
|
|
153
|
+
def commit(self) -> None:
|
|
154
|
+
self._main_conn.commit()
|
|
155
|
+
|
|
156
|
+
def rollback(self) -> None:
|
|
157
|
+
self._main_conn.rollback()
|
|
94
158
|
|
|
95
159
|
def get_catalog(self) -> Catalog:
|
|
96
160
|
databases = self._get_databases()
|
|
@@ -142,14 +206,37 @@ class HarlequinPostgresConnection(HarlequinConnection):
|
|
|
142
206
|
return Catalog(items=db_items)
|
|
143
207
|
|
|
144
208
|
def get_completions(self) -> list[HarlequinCompletion]:
|
|
145
|
-
conn:
|
|
209
|
+
conn: Connection = self.pool.getconn()
|
|
146
210
|
completions = _get_completions(conn)
|
|
147
211
|
self.pool.putconn(conn)
|
|
148
212
|
return completions
|
|
149
|
-
|
|
213
|
+
|
|
214
|
+
def close(self) -> None:
|
|
215
|
+
self.pool.putconn(self._main_conn)
|
|
216
|
+
self.pool.close()
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def transaction_mode(self) -> HarlequinTransactionMode:
|
|
220
|
+
return self._transaction_mode
|
|
221
|
+
|
|
222
|
+
def toggle_transaction_mode(self) -> HarlequinTransactionMode:
|
|
223
|
+
self._transaction_mode = next(self._transaction_modes)
|
|
224
|
+
self._sync_transaction_mode()
|
|
225
|
+
return self._transaction_mode
|
|
226
|
+
|
|
227
|
+
def _sync_transaction_mode(self) -> None:
|
|
228
|
+
"""
|
|
229
|
+
Sync this class's transaction mode with the main connection
|
|
230
|
+
"""
|
|
231
|
+
conn = self._main_conn
|
|
232
|
+
if self.transaction_mode.label == "Auto":
|
|
233
|
+
conn.autocommit = True
|
|
234
|
+
conn.commit()
|
|
235
|
+
else:
|
|
236
|
+
conn.autocommit = False
|
|
150
237
|
|
|
151
238
|
def _get_databases(self) -> list[tuple[str]]:
|
|
152
|
-
conn:
|
|
239
|
+
conn: Connection = self.pool.getconn()
|
|
153
240
|
with conn.cursor() as cur:
|
|
154
241
|
cur.execute(
|
|
155
242
|
"""
|
|
@@ -166,7 +253,7 @@ class HarlequinPostgresConnection(HarlequinConnection):
|
|
|
166
253
|
return results
|
|
167
254
|
|
|
168
255
|
def _get_schemas(self, dbname: str) -> list[tuple[str]]:
|
|
169
|
-
conn:
|
|
256
|
+
conn: Connection = self.pool.getconn()
|
|
170
257
|
with conn.cursor() as cur:
|
|
171
258
|
cur.execute(
|
|
172
259
|
f"""
|
|
@@ -184,7 +271,7 @@ class HarlequinPostgresConnection(HarlequinConnection):
|
|
|
184
271
|
return results
|
|
185
272
|
|
|
186
273
|
def _get_relations(self, dbname: str, schema: str) -> list[tuple[str, str]]:
|
|
187
|
-
conn:
|
|
274
|
+
conn: Connection = self.pool.getconn()
|
|
188
275
|
with conn.cursor() as cur:
|
|
189
276
|
cur.execute(
|
|
190
277
|
f"""
|
|
@@ -203,7 +290,7 @@ class HarlequinPostgresConnection(HarlequinConnection):
|
|
|
203
290
|
def _get_columns(
|
|
204
291
|
self, dbname: str, schema: str, relation: str
|
|
205
292
|
) -> list[tuple[str, str]]:
|
|
206
|
-
conn:
|
|
293
|
+
conn: Connection = self.pool.getconn()
|
|
207
294
|
with conn.cursor() as cur:
|
|
208
295
|
cur.execute(
|
|
209
296
|
f"""
|
|
@@ -339,6 +426,7 @@ class HarlequinPostgresConnection(HarlequinConnection):
|
|
|
339
426
|
|
|
340
427
|
class HarlequinPostgresAdapter(HarlequinAdapter):
|
|
341
428
|
ADAPTER_OPTIONS = POSTGRES_OPTIONS
|
|
429
|
+
IMPLEMENTS_CANCEL = True
|
|
342
430
|
|
|
343
431
|
def __init__(
|
|
344
432
|
self,
|
|
@@ -351,14 +439,14 @@ class HarlequinPostgresAdapter(HarlequinAdapter):
|
|
|
351
439
|
passfile: str | None = None,
|
|
352
440
|
require_auth: str | None = None,
|
|
353
441
|
channel_binding: str | None = None,
|
|
354
|
-
connect_timeout: int | None = None,
|
|
442
|
+
connect_timeout: int | float | None = None,
|
|
355
443
|
sslmode: str | None = None,
|
|
356
444
|
sslcert: str | None = None,
|
|
357
445
|
sslkey: str | None = None,
|
|
358
446
|
**_: Any,
|
|
359
447
|
) -> None:
|
|
360
448
|
self.conn_str = conn_str
|
|
361
|
-
self.options = {
|
|
449
|
+
self.options: dict[str, str | int | None] = {
|
|
362
450
|
"host": host,
|
|
363
451
|
"port": port,
|
|
364
452
|
"dbname": dbname,
|
|
@@ -367,12 +455,30 @@ class HarlequinPostgresAdapter(HarlequinAdapter):
|
|
|
367
455
|
"passfile": passfile,
|
|
368
456
|
"require_auth": require_auth,
|
|
369
457
|
"channel_binding": channel_binding,
|
|
370
|
-
"connect_timeout": connect_timeout,
|
|
458
|
+
"connect_timeout": connect_timeout, # type: ignore[dict-item]
|
|
371
459
|
"sslmode": sslmode,
|
|
372
460
|
"sslcert": sslcert,
|
|
373
461
|
"sslkey": sslkey,
|
|
374
462
|
}
|
|
375
463
|
|
|
464
|
+
@property
|
|
465
|
+
def connection_id(self) -> str | None:
|
|
466
|
+
"""
|
|
467
|
+
Use a simplified connection string, with only the host, port, and database
|
|
468
|
+
"""
|
|
469
|
+
try:
|
|
470
|
+
conn_info = conninfo.conninfo_to_dict(
|
|
471
|
+
conninfo=self.conn_str[0] if self.conn_str else "",
|
|
472
|
+
**self.options,
|
|
473
|
+
)
|
|
474
|
+
except Exception:
|
|
475
|
+
return None
|
|
476
|
+
|
|
477
|
+
host = conn_info.get("host", "localhost")
|
|
478
|
+
port = conn_info.get("port", "5432")
|
|
479
|
+
dbname = conn_info.get("dbname", "postgres")
|
|
480
|
+
return f"{host}:{port}/{dbname}"
|
|
481
|
+
|
|
376
482
|
def connect(self) -> HarlequinPostgresConnection:
|
|
377
483
|
if len(self.conn_str) > 1:
|
|
378
484
|
raise HarlequinConnectionError(
|
|
@@ -33,10 +33,7 @@ port = TextOption(
|
|
|
33
33
|
|
|
34
34
|
dbname = TextOption(
|
|
35
35
|
name="dbname",
|
|
36
|
-
description=(
|
|
37
|
-
"Port number to connect to at the server host, or socket file name extension "
|
|
38
|
-
"for Unix-domain connections."
|
|
39
|
-
),
|
|
36
|
+
description=("The database name to use when connecting with the Postgres server."),
|
|
40
37
|
short_decls=["-d"],
|
|
41
38
|
default="postgres",
|
|
42
39
|
)
|
|
@@ -4,10 +4,10 @@ import csv
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
6
|
from harlequin import HarlequinCompletion
|
|
7
|
-
from
|
|
7
|
+
from psycopg import Connection
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def _get_completions(conn:
|
|
10
|
+
def _get_completions(conn: Connection) -> list[HarlequinCompletion]:
|
|
11
11
|
completions: list[HarlequinCompletion] = []
|
|
12
12
|
|
|
13
13
|
# source: https://www.postgresql.org/docs/current/sql-keywords-appendix.html
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|