kaqing 2.0.171__py3-none-any.whl → 2.0.204__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.
- adam/app_session.py +5 -10
- adam/apps.py +18 -4
- adam/batch.py +7 -7
- adam/checks/check_utils.py +3 -1
- adam/checks/disk.py +2 -3
- adam/columns/memory.py +3 -4
- adam/commands/__init__.py +15 -6
- adam/commands/alter_tables.py +26 -41
- adam/commands/app/__init__.py +0 -0
- adam/commands/{app_cmd.py → app/app.py} +2 -2
- adam/commands/{show → app}/show_app_actions.py +7 -15
- adam/commands/{show → app}/show_app_queues.py +1 -4
- adam/{utils_app.py → commands/app/utils_app.py} +9 -1
- adam/commands/audit/audit.py +9 -26
- adam/commands/audit/audit_repair_tables.py +5 -7
- adam/commands/audit/audit_run.py +1 -1
- adam/commands/audit/completions_l.py +15 -0
- adam/commands/audit/show_last10.py +2 -14
- adam/commands/audit/show_slow10.py +2 -13
- adam/commands/audit/show_top10.py +2 -11
- adam/commands/audit/utils_show_top10.py +15 -3
- adam/commands/bash/bash.py +1 -1
- adam/commands/bash/utils_bash.py +1 -1
- adam/commands/cassandra/__init__.py +0 -0
- adam/commands/cassandra/download_cassandra_log.py +45 -0
- adam/commands/cassandra/nodetool.py +64 -0
- adam/commands/cassandra/nodetool_commands.py +120 -0
- adam/commands/cassandra/restart_cluster.py +47 -0
- adam/commands/cassandra/restart_node.py +51 -0
- adam/commands/cassandra/restart_nodes.py +47 -0
- adam/commands/cassandra/rollout.py +88 -0
- adam/commands/cat.py +5 -19
- adam/commands/cd.py +7 -9
- adam/commands/check.py +10 -18
- adam/commands/cli_commands.py +6 -1
- adam/commands/{cp.py → clipboard_copy.py} +34 -36
- adam/commands/code.py +2 -2
- adam/commands/command.py +139 -22
- adam/commands/commands_utils.py +14 -12
- adam/commands/cql/alter_tables.py +66 -0
- adam/commands/cql/completions_c.py +29 -0
- adam/commands/cql/cqlsh.py +3 -7
- adam/commands/cql/utils_cql.py +23 -61
- adam/commands/debug/__init__.py +0 -0
- adam/commands/debug/debug.py +22 -0
- adam/commands/debug/debug_completes.py +35 -0
- adam/commands/debug/debug_timings.py +35 -0
- adam/commands/deploy/deploy_pg_agent.py +2 -2
- adam/commands/deploy/deploy_pod.py +2 -4
- adam/commands/deploy/undeploy_pg_agent.py +2 -2
- adam/commands/devices/device.py +40 -9
- adam/commands/devices/device_app.py +19 -29
- adam/commands/devices/device_auit_log.py +3 -3
- adam/commands/devices/device_cass.py +17 -23
- adam/commands/devices/device_export.py +12 -11
- adam/commands/devices/device_postgres.py +79 -63
- adam/commands/devices/devices.py +1 -1
- adam/commands/download_cassandra_log.py +45 -0
- adam/commands/download_file.py +47 -0
- adam/commands/export/clean_up_all_export_sessions.py +3 -3
- adam/commands/export/clean_up_export_sessions.py +7 -19
- adam/commands/export/completions_x.py +11 -0
- adam/commands/export/download_export_session.py +40 -0
- adam/commands/export/drop_export_database.py +6 -22
- adam/commands/export/drop_export_databases.py +3 -9
- adam/commands/export/export.py +1 -17
- adam/commands/export/export_databases.py +109 -32
- adam/commands/export/export_select.py +8 -55
- adam/commands/export/export_sessions.py +211 -0
- adam/commands/export/export_use.py +13 -16
- adam/commands/export/export_x_select.py +48 -0
- adam/commands/export/exporter.py +176 -167
- adam/commands/export/import_files.py +44 -0
- adam/commands/export/import_session.py +10 -6
- adam/commands/export/importer.py +24 -9
- adam/commands/export/importer_athena.py +114 -44
- adam/commands/export/importer_sqlite.py +45 -23
- adam/commands/export/show_column_counts.py +11 -20
- adam/commands/export/show_export_databases.py +5 -2
- adam/commands/export/show_export_session.py +6 -15
- adam/commands/export/show_export_sessions.py +4 -11
- adam/commands/export/utils_export.py +79 -27
- adam/commands/find_files.py +51 -0
- adam/commands/find_processes.py +76 -0
- adam/commands/generate_report.py +52 -0
- adam/commands/head.py +36 -0
- adam/commands/help.py +2 -2
- adam/commands/intermediate_command.py +6 -3
- adam/commands/login.py +3 -6
- adam/commands/ls.py +2 -2
- adam/commands/medusa/medusa_backup.py +13 -16
- adam/commands/medusa/medusa_restore.py +26 -37
- adam/commands/medusa/medusa_show_backupjobs.py +7 -7
- adam/commands/medusa/medusa_show_restorejobs.py +6 -6
- adam/commands/medusa/utils_medusa.py +15 -0
- adam/commands/nodetool.py +3 -8
- adam/commands/os/__init__.py +0 -0
- adam/commands/os/cat.py +36 -0
- adam/commands/os/download_file.py +47 -0
- adam/commands/os/find_files.py +51 -0
- adam/commands/os/find_processes.py +76 -0
- adam/commands/os/head.py +36 -0
- adam/commands/os/shell.py +41 -0
- adam/commands/param_get.py +10 -12
- adam/commands/param_set.py +7 -10
- adam/commands/postgres/completions_p.py +22 -0
- adam/commands/postgres/postgres.py +25 -40
- adam/commands/postgres/postgres_databases.py +269 -0
- adam/commands/postgres/utils_postgres.py +33 -20
- adam/commands/preview_table.py +4 -2
- adam/commands/pwd.py +4 -6
- adam/commands/reaper/reaper_forward.py +2 -2
- adam/commands/reaper/reaper_run_abort.py +4 -10
- adam/commands/reaper/reaper_runs.py +3 -3
- adam/commands/reaper/reaper_schedule_activate.py +12 -12
- adam/commands/reaper/reaper_schedule_start.py +7 -12
- adam/commands/reaper/reaper_schedule_stop.py +7 -12
- adam/commands/reaper/utils_reaper.py +13 -6
- adam/commands/repair/repair_log.py +1 -4
- adam/commands/repair/repair_run.py +3 -8
- adam/commands/repair/repair_scan.py +1 -6
- adam/commands/repair/repair_stop.py +1 -5
- adam/commands/restart_cluster.py +47 -0
- adam/commands/restart_node.py +51 -0
- adam/commands/restart_nodes.py +47 -0
- adam/commands/shell.py +9 -2
- adam/commands/show/show.py +4 -4
- adam/commands/show/show_adam.py +3 -3
- adam/commands/show/show_cassandra_repairs.py +5 -6
- adam/commands/show/show_cassandra_status.py +29 -29
- adam/commands/show/show_cassandra_version.py +1 -4
- adam/commands/show/{show_commands.py → show_cli_commands.py} +3 -6
- adam/commands/show/show_login.py +3 -9
- adam/commands/show/show_params.py +2 -5
- adam/commands/show/show_processes.py +15 -16
- adam/commands/show/show_storage.py +9 -8
- adam/config.py +4 -5
- adam/embedded_params.py +1 -1
- adam/log.py +4 -4
- adam/repl.py +26 -18
- adam/repl_commands.py +32 -20
- adam/repl_session.py +9 -1
- adam/repl_state.py +39 -10
- adam/sql/async_executor.py +44 -0
- adam/sql/lark_completer.py +286 -0
- adam/sql/lark_parser.py +604 -0
- adam/sql/qingl.lark +1076 -0
- adam/sql/sql_completer.py +4 -6
- adam/sql/sql_state_machine.py +25 -13
- adam/sso/authn_ad.py +2 -5
- adam/sso/authn_okta.py +2 -4
- adam/sso/cred_cache.py +2 -5
- adam/sso/idp.py +8 -11
- adam/utils.py +299 -105
- adam/utils_athena.py +18 -18
- adam/utils_audits.py +3 -7
- adam/utils_issues.py +2 -2
- adam/utils_k8s/app_clusters.py +4 -4
- adam/utils_k8s/app_pods.py +8 -6
- adam/utils_k8s/cassandra_clusters.py +16 -5
- adam/utils_k8s/cassandra_nodes.py +7 -6
- adam/utils_k8s/custom_resources.py +11 -17
- adam/utils_k8s/jobs.py +7 -11
- adam/utils_k8s/k8s.py +14 -5
- adam/utils_k8s/kube_context.py +3 -6
- adam/{pod_exec_result.py → utils_k8s/pod_exec_result.py} +4 -4
- adam/utils_k8s/pods.py +98 -36
- adam/utils_k8s/statefulsets.py +5 -2
- adam/utils_local.py +42 -0
- adam/utils_repl/appendable_completer.py +6 -0
- adam/utils_repl/repl_completer.py +45 -2
- adam/utils_repl/state_machine.py +3 -3
- adam/utils_sqlite.py +58 -30
- adam/version.py +1 -1
- {kaqing-2.0.171.dist-info → kaqing-2.0.204.dist-info}/METADATA +1 -1
- kaqing-2.0.204.dist-info/RECORD +277 -0
- kaqing-2.0.204.dist-info/top_level.txt +2 -0
- teddy/__init__.py +0 -0
- teddy/lark_parser.py +436 -0
- teddy/lark_parser2.py +618 -0
- adam/commands/cql/cql_completions.py +0 -33
- adam/commands/export/export_handlers.py +0 -71
- adam/commands/export/export_select_x.py +0 -54
- adam/commands/logs.py +0 -37
- adam/commands/postgres/postgres_context.py +0 -274
- adam/commands/postgres/psql_completions.py +0 -10
- adam/commands/report.py +0 -61
- adam/commands/restart.py +0 -60
- kaqing-2.0.171.dist-info/RECORD +0 -236
- kaqing-2.0.171.dist-info/top_level.txt +0 -1
- /adam/commands/{app_ping.py → app/app_ping.py} +0 -0
- /adam/commands/{show → app}/show_app_id.py +0 -0
- {kaqing-2.0.171.dist-info → kaqing-2.0.204.dist-info}/WHEEL +0 -0
- {kaqing-2.0.171.dist-info → kaqing-2.0.204.dist-info}/entry_points.txt +0 -0
adam/utils_k8s/pods.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
2
|
from datetime import datetime
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
3
6
|
import sys
|
|
4
7
|
import time
|
|
5
8
|
from typing import TypeVar
|
|
@@ -8,9 +11,11 @@ from kubernetes.stream import stream
|
|
|
8
11
|
from kubernetes.stream.ws_client import ERROR_CHANNEL, WSClient
|
|
9
12
|
|
|
10
13
|
from adam.config import Config
|
|
14
|
+
from adam.repl_session import ReplSession
|
|
11
15
|
from adam.utils_k8s.volumes import ConfigMapMount
|
|
12
|
-
from adam.pod_exec_result import PodExecResult
|
|
13
|
-
from adam.utils import ParallelMapHandler, log2
|
|
16
|
+
from adam.utils_k8s.pod_exec_result import PodExecResult
|
|
17
|
+
from adam.utils import GeneratorStream, ParallelMapHandler, log2, debug, log_exc
|
|
18
|
+
from adam.utils_local import local_tmp_dir
|
|
14
19
|
from .kube_context import KubeContext
|
|
15
20
|
|
|
16
21
|
from websocket._core import WebSocket
|
|
@@ -29,11 +34,9 @@ class Pods:
|
|
|
29
34
|
return _TEST_POD_EXEC_OUTS
|
|
30
35
|
|
|
31
36
|
def delete(pod_name: str, namespace: str, grace_period_seconds: int = None):
|
|
32
|
-
|
|
37
|
+
with log_exc(lambda e: "Exception when calling CoreV1Api->delete_namespaced_pod: %s\n" % e):
|
|
33
38
|
v1 = client.CoreV1Api()
|
|
34
39
|
v1.delete_namespaced_pod(pod_name, namespace, grace_period_seconds=grace_period_seconds)
|
|
35
|
-
except Exception as e:
|
|
36
|
-
log2("Exception when calling CoreV1Api->delete_namespaced_pod: %s\n" % e)
|
|
37
40
|
|
|
38
41
|
def delete_with_selector(namespace: str, label_selector: str, grace_period_seconds: int = None):
|
|
39
42
|
v1 = client.CoreV1Api()
|
|
@@ -50,9 +53,14 @@ class Pods:
|
|
|
50
53
|
|
|
51
54
|
return ParallelMapHandler(collection, max_workers, samples = samples, msg = msg)
|
|
52
55
|
|
|
53
|
-
def exec(pod_name: str,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
def exec(pod_name: str,
|
|
57
|
+
container: str,
|
|
58
|
+
namespace: str,
|
|
59
|
+
command: str,
|
|
60
|
+
show_out = True,
|
|
61
|
+
throw_err = False,
|
|
62
|
+
shell = '/bin/sh',
|
|
63
|
+
backgrounded = False,
|
|
56
64
|
log_file = None,
|
|
57
65
|
interaction: Callable[[any, list[str]], any] = None,
|
|
58
66
|
env_prefix: str = None):
|
|
@@ -61,6 +69,27 @@ class Pods:
|
|
|
61
69
|
|
|
62
70
|
show_out = KubeContext.show_out(show_out)
|
|
63
71
|
|
|
72
|
+
if backgrounded or command.endswith(' &'):
|
|
73
|
+
command = command.strip(' &')
|
|
74
|
+
|
|
75
|
+
log_all_file = None
|
|
76
|
+
log_pod_file = None
|
|
77
|
+
if log_file:
|
|
78
|
+
log_pod_file = Pods.log_file_from_template(log_file, pod_name=pod_name)
|
|
79
|
+
if (a := Pods.log_file_from_template(log_file, pod_name='all')) != log_file:
|
|
80
|
+
log_all_file = a
|
|
81
|
+
else:
|
|
82
|
+
log_pod_file = Pods.log_file(command, pod_name=pod_name)
|
|
83
|
+
|
|
84
|
+
command = command.replace('"', '\\"')
|
|
85
|
+
cmd = f'nohup kubectl exec {pod_name} -c {container} -- {shell} -c "{command} &" > {log_pod_file} 2>&1 &'
|
|
86
|
+
if log_all_file:
|
|
87
|
+
cmd = f'{cmd} >> {log_all_file}'
|
|
88
|
+
|
|
89
|
+
result = subprocess.run(cmd, capture_output=True, text=True, shell=True)
|
|
90
|
+
|
|
91
|
+
return PodExecResult(result.stdout, result.stderr, cmd, None, pod=pod_name, log_file=log_pod_file)
|
|
92
|
+
|
|
64
93
|
api = client.CoreV1Api()
|
|
65
94
|
|
|
66
95
|
tty = True
|
|
@@ -68,26 +97,26 @@ class Pods:
|
|
|
68
97
|
if env_prefix:
|
|
69
98
|
exec_command = [shell, '-c', f'{env_prefix} {command}']
|
|
70
99
|
|
|
71
|
-
if
|
|
72
|
-
|
|
73
|
-
|
|
100
|
+
# if backgrounded or command.endswith(' &'):
|
|
101
|
+
# print('!!!!SEAN backgrounded, but no via-sh!!!!!')
|
|
102
|
+
# # should be false for starting a background process
|
|
103
|
+
# tty = False
|
|
74
104
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
105
|
+
# if Config().get('repl.background-process.auto-nohup', True):
|
|
106
|
+
# command = command.strip(' &')
|
|
107
|
+
# cmd_name = ''
|
|
108
|
+
# if command.startswith('nodetool '):
|
|
109
|
+
# cmd_name = f".{'_'.join(command.split(' ')[5:])}"
|
|
80
110
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
111
|
+
# if not log_file:
|
|
112
|
+
# log_file = f'{log_prefix()}-{datetime.now().strftime("%d%H%M%S")}{cmd_name}.log'
|
|
113
|
+
# command = f"nohup {command} > {log_file} 2>&1 &"
|
|
114
|
+
# if env_prefix:
|
|
115
|
+
# command = f'{env_prefix} {command}'
|
|
116
|
+
# exec_command = [shell, '-c', command]
|
|
87
117
|
|
|
88
118
|
k_command = f'kubectl exec {pod_name} -c {container} -n {namespace} -- {shell} -c "{command}"'
|
|
89
|
-
|
|
90
|
-
log2(k_command)
|
|
119
|
+
debug(k_command)
|
|
91
120
|
|
|
92
121
|
resp: WSClient = stream(
|
|
93
122
|
api.connect_get_namespaced_pod_exec,
|
|
@@ -121,11 +150,9 @@ class Pods:
|
|
|
121
150
|
stderr.append(frag)
|
|
122
151
|
if show_out: print(frag, end="")
|
|
123
152
|
|
|
124
|
-
|
|
153
|
+
with log_exc():
|
|
125
154
|
# get the exit code from server
|
|
126
155
|
error_output = resp.read_channel(ERROR_CHANNEL)
|
|
127
|
-
except Exception as e:
|
|
128
|
-
pass
|
|
129
156
|
except Exception as e:
|
|
130
157
|
if throw_err:
|
|
131
158
|
raise e
|
|
@@ -134,13 +161,38 @@ class Pods:
|
|
|
134
161
|
finally:
|
|
135
162
|
resp.close()
|
|
136
163
|
if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
|
|
137
|
-
|
|
164
|
+
with log_exc():
|
|
138
165
|
s.sock.close()
|
|
139
|
-
except:
|
|
140
|
-
pass
|
|
141
166
|
|
|
142
167
|
return PodExecResult("".join(stdout), "".join(stderr), k_command, error_output, pod=pod_name, log_file=log_file)
|
|
143
168
|
|
|
169
|
+
def log_file(command: str, pod_name: str = None, dt: datetime = None):
|
|
170
|
+
cmd_name = ''
|
|
171
|
+
if command.startswith('nodetool '):
|
|
172
|
+
command = command.strip(' &')
|
|
173
|
+
cmd_name = f".{'_'.join(command.split(' ')[5:])}"
|
|
174
|
+
|
|
175
|
+
pod_suffix = '{pod}'
|
|
176
|
+
if pod_name:
|
|
177
|
+
pod_suffix = pod_name
|
|
178
|
+
if groups := re.match(r'.*-(.*)', pod_name):
|
|
179
|
+
pod_suffix = f'-{groups[1]}'
|
|
180
|
+
|
|
181
|
+
if not dt:
|
|
182
|
+
dt = datetime.now()
|
|
183
|
+
|
|
184
|
+
return f'{log_prefix()}-{dt.strftime("%d%H%M%S")}{cmd_name}{pod_suffix}.log'
|
|
185
|
+
|
|
186
|
+
def log_file_from_template(log_file: str, pod_name: str):
|
|
187
|
+
pod_suffix = pod_name
|
|
188
|
+
if pod_name and (groups := re.match(r'.*-(.*)', pod_name)):
|
|
189
|
+
pod_suffix = f'-{groups[1]}'
|
|
190
|
+
|
|
191
|
+
if not pod_suffix.startswith('-'):
|
|
192
|
+
pod_suffix = f'-{pod_suffix}'
|
|
193
|
+
|
|
194
|
+
return log_file.replace('{pod}', pod_suffix)
|
|
195
|
+
|
|
144
196
|
def read_file(pod_name: str, container: str, namespace: str, file_path: str):
|
|
145
197
|
v1 = client.CoreV1Api()
|
|
146
198
|
|
|
@@ -162,20 +214,30 @@ class Pods:
|
|
|
162
214
|
if resp.peek_stdout():
|
|
163
215
|
yield resp.read_stdout()
|
|
164
216
|
|
|
165
|
-
|
|
217
|
+
with log_exc():
|
|
166
218
|
# get the exit code from server
|
|
167
219
|
error_output = resp.read_channel(ERROR_CHANNEL)
|
|
168
|
-
except Exception as e:
|
|
169
|
-
pass
|
|
170
220
|
except Exception as e:
|
|
171
221
|
raise e
|
|
172
222
|
finally:
|
|
173
223
|
resp.close()
|
|
174
224
|
if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
|
|
175
|
-
|
|
225
|
+
with log_exc():
|
|
176
226
|
s.sock.close()
|
|
177
|
-
|
|
178
|
-
|
|
227
|
+
|
|
228
|
+
def download_file(pod_name: str, container: str, namespace: str, from_path: str, to_path: str = None):
|
|
229
|
+
if not to_path:
|
|
230
|
+
to_path = f'{local_tmp_dir()}/{os.path.basename(from_path)}'
|
|
231
|
+
|
|
232
|
+
bytes = Pods.read_file(pod_name, container, namespace, from_path)
|
|
233
|
+
with open(to_path, 'wb') as f:
|
|
234
|
+
for item in GeneratorStream(bytes):
|
|
235
|
+
f.write(item)
|
|
236
|
+
|
|
237
|
+
ReplSession().append_history(f':sh cat {to_path}')
|
|
238
|
+
|
|
239
|
+
return to_path
|
|
240
|
+
|
|
179
241
|
def get_container(namespace: str, pod_name: str, container_name: str):
|
|
180
242
|
pod = Pods.get(namespace, pod_name)
|
|
181
243
|
if not pod:
|
adam/utils_k8s/statefulsets.py
CHANGED
|
@@ -56,8 +56,11 @@ class StatefulSets:
|
|
|
56
56
|
return statefulset_pods
|
|
57
57
|
|
|
58
58
|
@functools.lru_cache()
|
|
59
|
-
def pod_names(
|
|
60
|
-
|
|
59
|
+
def pod_names(sts: str, ns: str):
|
|
60
|
+
if not sts:
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
return [pod.metadata.name for pod in StatefulSets.pods(sts, ns)]
|
|
61
64
|
|
|
62
65
|
def restarted_at(ss: str, ns: str):
|
|
63
66
|
# returns timestamp and if being rolled out
|
adam/utils_local.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from adam.config import Config
|
|
3
|
+
from adam.utils import ExecResult, debug
|
|
4
|
+
|
|
5
|
+
def local_tmp_dir():
|
|
6
|
+
return Config().get('local-tmp-dir', '/tmp/qing-db')
|
|
7
|
+
|
|
8
|
+
class LocalExecResult(ExecResult):
|
|
9
|
+
def __init__(self, stdout: str, stderr: str, command: str = None, code = 0, log_file: str = None):
|
|
10
|
+
self.stdout: str = stdout
|
|
11
|
+
self.stderr: str = stderr
|
|
12
|
+
self.command: str = command
|
|
13
|
+
self.code = code
|
|
14
|
+
self.pod = 'local'
|
|
15
|
+
self.log_file = log_file
|
|
16
|
+
|
|
17
|
+
def exit_code(self) -> int:
|
|
18
|
+
return self.code
|
|
19
|
+
|
|
20
|
+
def __str__(self):
|
|
21
|
+
return f'{"OK" if self.exit_code() == 0 else self.exit_code()} {self.command}'
|
|
22
|
+
|
|
23
|
+
def __audit_extra__(self):
|
|
24
|
+
return self.log_file if self.log_file else None
|
|
25
|
+
|
|
26
|
+
def local_exec(cmd: list[str], shell=False, show_out=False):
|
|
27
|
+
stdout = ''
|
|
28
|
+
stderr = ''
|
|
29
|
+
returncode = 0
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
if show_out:
|
|
33
|
+
debug(' '.join(cmd))
|
|
34
|
+
|
|
35
|
+
r = subprocess.run(cmd, capture_output=True, text=True, shell=shell)
|
|
36
|
+
stdout = r.stdout
|
|
37
|
+
stderr = r.stderr
|
|
38
|
+
returncode = r.returncode
|
|
39
|
+
except FileNotFoundError as e:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
return LocalExecResult(stdout, stderr, ' '.join(cmd), returncode)
|
|
@@ -1,14 +1,57 @@
|
|
|
1
|
+
import copy
|
|
1
2
|
import re
|
|
2
|
-
|
|
3
|
+
import traceback
|
|
4
|
+
from typing import Iterable, TypeVar, cast
|
|
3
5
|
from prompt_toolkit.completion import CompleteEvent, Completion, NestedCompleter, WordCompleter
|
|
4
6
|
from prompt_toolkit.document import Document
|
|
5
7
|
|
|
8
|
+
from adam.utils import debug_complete, log2
|
|
9
|
+
from adam.utils_repl.appendable_completer import AppendableCompleter
|
|
10
|
+
|
|
11
|
+
import nest_asyncio
|
|
12
|
+
nest_asyncio.apply()
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
|
|
6
16
|
__all__ = [
|
|
7
17
|
"ReplCompleter",
|
|
8
18
|
]
|
|
9
19
|
|
|
10
20
|
T = TypeVar('T')
|
|
11
21
|
|
|
22
|
+
def merge_completions(dict1, dict2):
|
|
23
|
+
if isinstance(dict1, dict):
|
|
24
|
+
target = dict1.copy()
|
|
25
|
+
else:
|
|
26
|
+
target = copy.copy(dict1)
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
for key, value in dict2.items():
|
|
30
|
+
if key in target:
|
|
31
|
+
debug_complete(f'[{key}] {type(dict2)} is being merged to {type(target[key])} completions')
|
|
32
|
+
if isinstance(value, dict):
|
|
33
|
+
if isinstance(target[key], dict):
|
|
34
|
+
target[key] = merge_completions(target[key], value)
|
|
35
|
+
elif isinstance(target[key], AppendableCompleter):
|
|
36
|
+
cast(AppendableCompleter, target[key]).append_completions(key, value)
|
|
37
|
+
elif isinstance(target[key], NestedCompleter):
|
|
38
|
+
cast(NestedCompleter, target[key]).options = merge_completions(cast(NestedCompleter, target[key]).options, value)
|
|
39
|
+
elif isinstance(value, AppendableCompleter):
|
|
40
|
+
if isinstance(target[key], dict):
|
|
41
|
+
cast(AppendableCompleter, value).append_completions(key, target[key])
|
|
42
|
+
target[key] = value
|
|
43
|
+
else:
|
|
44
|
+
log2(f'* {key} of {type(value)} is overriding existing {type(target[key])} completions')
|
|
45
|
+
else:
|
|
46
|
+
target[key] = value
|
|
47
|
+
else:
|
|
48
|
+
target[key] = value
|
|
49
|
+
|
|
50
|
+
except Exception as e:
|
|
51
|
+
traceback.print_exc()
|
|
52
|
+
|
|
53
|
+
return target
|
|
54
|
+
|
|
12
55
|
class ReplCompleter(NestedCompleter):
|
|
13
56
|
def get_completions(
|
|
14
57
|
self, document: Document, complete_event: CompleteEvent
|
|
@@ -43,4 +86,4 @@ class ReplCompleter(NestedCompleter):
|
|
|
43
86
|
list(self.options.keys()), ignore_case=self.ignore_case, pattern=re.compile(r"([a-zA-Z0-9_\.\@\&]+|[^a-zA-Z0-9_\.\@\&\s]+)")
|
|
44
87
|
)
|
|
45
88
|
for c in completer.get_completions(document, complete_event):
|
|
46
|
-
yield c
|
|
89
|
+
yield c
|
adam/utils_repl/state_machine.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
2
|
from typing import Generic, TypeVar
|
|
3
3
|
|
|
4
|
+
from adam.utils import log_exc
|
|
5
|
+
|
|
4
6
|
__all__ = [
|
|
5
7
|
'State',
|
|
6
8
|
'StateMachine',
|
|
@@ -160,14 +162,12 @@ class StateMachine(Generic[T]):
|
|
|
160
162
|
last_name = token
|
|
161
163
|
token = 'word'
|
|
162
164
|
|
|
163
|
-
|
|
165
|
+
with log_exc():
|
|
164
166
|
context = state.context
|
|
165
167
|
state = self.states[f'{state.state} > {token}']
|
|
166
168
|
state.context = context
|
|
167
169
|
|
|
168
170
|
if last_name:
|
|
169
171
|
state.context['last_name'] = last_name
|
|
170
|
-
except:
|
|
171
|
-
pass
|
|
172
172
|
|
|
173
173
|
return state
|
adam/utils_sqlite.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
1
3
|
import functools
|
|
2
4
|
import glob
|
|
3
5
|
import os
|
|
@@ -5,11 +7,12 @@ import sqlite3
|
|
|
5
7
|
import pandas
|
|
6
8
|
|
|
7
9
|
from adam.config import Config
|
|
8
|
-
from adam.utils import
|
|
10
|
+
from adam.utils import tabulize, log, wait_log
|
|
9
11
|
|
|
10
12
|
class CursorHandler:
|
|
11
13
|
def __init__(self, conn: sqlite3.Connection):
|
|
12
14
|
self.conn = conn
|
|
15
|
+
self.cursor = None
|
|
13
16
|
|
|
14
17
|
def __enter__(self):
|
|
15
18
|
self.cursor = self.conn.cursor()
|
|
@@ -22,6 +25,28 @@ class CursorHandler:
|
|
|
22
25
|
|
|
23
26
|
return False
|
|
24
27
|
|
|
28
|
+
class SQLiteConnectionHandler:
|
|
29
|
+
def __init__(self, database: str, keyspace = None):
|
|
30
|
+
self.database = database
|
|
31
|
+
self.keyspace = keyspace
|
|
32
|
+
self.conn = None
|
|
33
|
+
|
|
34
|
+
def __enter__(self) -> sqlite3.Connection:
|
|
35
|
+
self.conn = SQLite.connect(self.database, self.keyspace)
|
|
36
|
+
|
|
37
|
+
self.conn.__enter__()
|
|
38
|
+
|
|
39
|
+
return self.conn
|
|
40
|
+
|
|
41
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
42
|
+
if self.conn:
|
|
43
|
+
self.conn.__exit__(exc_type, exc_val, exc_tb)
|
|
44
|
+
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
def sqlite(database: str, keyspace = None):
|
|
48
|
+
return SQLiteConnectionHandler(database, keyspace=keyspace)
|
|
49
|
+
|
|
25
50
|
# no state utility class
|
|
26
51
|
class SQLite:
|
|
27
52
|
def cursor(conn: sqlite3.Connection):
|
|
@@ -33,23 +58,6 @@ class SQLite:
|
|
|
33
58
|
def keyspace(database: str):
|
|
34
59
|
return '_'.join(database.replace(".db", "").split('_')[1:])
|
|
35
60
|
|
|
36
|
-
def connect(session: str):
|
|
37
|
-
os.makedirs(SQLite.local_db_dir(), exist_ok=True)
|
|
38
|
-
|
|
39
|
-
conn = None
|
|
40
|
-
|
|
41
|
-
try:
|
|
42
|
-
conn = sqlite3.connect(f'{SQLite.local_db_dir()}/{session}_root.db')
|
|
43
|
-
with SQLite.cursor(conn) as cursor:
|
|
44
|
-
for d in SQLite.database_names(session):
|
|
45
|
-
if d != f'{session}_root.db':
|
|
46
|
-
q = f"ATTACH DATABASE '{SQLite.local_db_dir()}/{d}' AS {SQLite.keyspace(d)};"
|
|
47
|
-
cursor.execute(q)
|
|
48
|
-
finally:
|
|
49
|
-
pass
|
|
50
|
-
|
|
51
|
-
return conn
|
|
52
|
-
|
|
53
61
|
@functools.lru_cache()
|
|
54
62
|
def database_names(prefix: str = None):
|
|
55
63
|
wait_log('Inspecting export databases...')
|
|
@@ -86,24 +94,44 @@ class SQLite:
|
|
|
86
94
|
if conn:
|
|
87
95
|
conn.close()
|
|
88
96
|
|
|
97
|
+
def connect(database: str, keyspace: str = None):
|
|
98
|
+
os.makedirs(SQLite.local_db_dir(), exist_ok=True)
|
|
99
|
+
|
|
100
|
+
if keyspace:
|
|
101
|
+
return sqlite3.connect(f'{SQLite.local_db_dir()}/{database}_{keyspace}.db')
|
|
102
|
+
else:
|
|
103
|
+
conn = sqlite3.connect(f'{SQLite.local_db_dir()}/{database}_root.db')
|
|
104
|
+
with SQLite.cursor(conn) as cursor:
|
|
105
|
+
for d in SQLite.database_names(database):
|
|
106
|
+
if d != f'{database}.db':
|
|
107
|
+
q = f"ATTACH DATABASE '{SQLite.local_db_dir()}/{d}' AS {SQLite.keyspace(d)}"
|
|
108
|
+
cursor.execute(q)
|
|
109
|
+
|
|
110
|
+
return conn
|
|
111
|
+
|
|
89
112
|
@functools.lru_cache()
|
|
90
113
|
def column_names(tables: list[str] = [], database: str = None, function: str = 'audit', partition_cols_only = False):
|
|
91
114
|
pass
|
|
92
115
|
|
|
93
|
-
def run_query(query: str, database: str = None,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
def run_query(query: str, database: str = None, output: Callable[[str], str] = None) -> tuple[int, str]:
|
|
117
|
+
with sqlite(database) as conn:
|
|
118
|
+
return SQLite.run_query_with_conn(conn, query, output=output)
|
|
119
|
+
|
|
120
|
+
def run_query_with_conn(conn, query: str, output: Callable[[str], str] = None) -> tuple[int, str]:
|
|
121
|
+
log_file = None
|
|
98
122
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
123
|
+
df = SQLite.query(conn, query)
|
|
124
|
+
lines = ['\t'.join(map(str, line)) for line in df.values.tolist()]
|
|
125
|
+
out = tabulize(lines, header='\t'.join(df.columns.tolist()), separator='\t', to=0)
|
|
126
|
+
if output:
|
|
127
|
+
log_file = output(out)
|
|
128
|
+
else:
|
|
129
|
+
log(out)
|
|
102
130
|
|
|
103
|
-
|
|
104
|
-
finally:
|
|
105
|
-
if conn:
|
|
106
|
-
conn.close()
|
|
131
|
+
return len(lines), log_file
|
|
107
132
|
|
|
108
133
|
def query(conn, sql: str) -> tuple[str, str, list]:
|
|
109
134
|
return pandas.read_sql_query(sql, conn)
|
|
135
|
+
|
|
136
|
+
def log_prefix():
|
|
137
|
+
return Config().get('export.log-prefix', '/tmp/qing')
|
adam/version.py
CHANGED