kaqing 2.0.184__py3-none-any.whl → 2.0.214__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.

Potentially problematic release.


This version of kaqing might be problematic. Click here for more details.

Files changed (152) hide show
  1. adam/app_session.py +1 -1
  2. adam/batch.py +15 -15
  3. adam/commands/app/app.py +2 -2
  4. adam/commands/app/show_app_actions.py +1 -1
  5. adam/commands/{show → app}/show_login.py +1 -1
  6. adam/commands/app/utils_app.py +9 -1
  7. adam/commands/audit/audit.py +6 -20
  8. adam/commands/audit/audit_repair_tables.py +1 -1
  9. adam/commands/audit/audit_run.py +1 -1
  10. adam/commands/audit/completions_l.py +15 -0
  11. adam/commands/audit/show_last10.py +0 -1
  12. adam/commands/bash/bash.py +1 -1
  13. adam/commands/bash/utils_bash.py +1 -1
  14. adam/commands/cassandra/download_cassandra_log.py +45 -0
  15. adam/commands/cassandra/restart_cluster.py +47 -0
  16. adam/commands/cassandra/restart_node.py +51 -0
  17. adam/commands/cassandra/restart_nodes.py +47 -0
  18. adam/commands/{rollout.py → cassandra/rollout.py} +1 -1
  19. adam/commands/{show → cassandra}/show_cassandra_repairs.py +5 -3
  20. adam/commands/{show → cassandra}/show_cassandra_status.py +22 -15
  21. adam/commands/cassandra/show_processes.py +50 -0
  22. adam/commands/{show → cassandra}/show_storage.py +10 -8
  23. adam/commands/cli/__init__.py +0 -0
  24. adam/commands/{cli_commands.py → cli/cli_commands.py} +6 -1
  25. adam/commands/{clipboard_copy.py → cli/clipboard_copy.py} +2 -2
  26. adam/commands/{show/show_commands.py → cli/show_cli_commands.py} +2 -2
  27. adam/commands/command.py +22 -9
  28. adam/commands/commands_utils.py +14 -6
  29. adam/commands/config/__init__.py +0 -0
  30. adam/commands/{show → config}/show_params.py +1 -1
  31. adam/commands/{alter_tables.py → cql/alter_tables.py} +1 -1
  32. adam/commands/cql/completions_c.py +29 -0
  33. adam/commands/cql/cqlsh.py +2 -6
  34. adam/commands/cql/utils_cql.py +26 -17
  35. adam/commands/debug/__init__.py +0 -0
  36. adam/commands/debug/debug.py +22 -0
  37. adam/commands/debug/debug_completes.py +35 -0
  38. adam/commands/debug/debug_timings.py +35 -0
  39. adam/commands/debug/show_offloaded_completes.py +45 -0
  40. adam/commands/devices/device.py +30 -4
  41. adam/commands/devices/device_app.py +1 -1
  42. adam/commands/devices/device_export.py +5 -2
  43. adam/commands/devices/device_postgres.py +13 -3
  44. adam/commands/devices/devices.py +1 -1
  45. adam/commands/diag/__init__.py +0 -0
  46. adam/commands/{check.py → diag/check.py} +1 -1
  47. adam/commands/diag/generate_report.py +52 -0
  48. adam/commands/export/completions_x.py +11 -0
  49. adam/commands/export/download_export_session.py +2 -1
  50. adam/commands/export/export.py +0 -16
  51. adam/commands/export/export_databases.py +16 -10
  52. adam/commands/export/export_select.py +8 -33
  53. adam/commands/export/export_sessions.py +12 -11
  54. adam/commands/export/export_use.py +3 -3
  55. adam/commands/export/export_x_select.py +48 -0
  56. adam/commands/export/exporter.py +140 -53
  57. adam/commands/export/import_files.py +2 -2
  58. adam/commands/export/import_session.py +0 -4
  59. adam/commands/export/importer.py +11 -11
  60. adam/commands/export/importer_athena.py +15 -35
  61. adam/commands/export/importer_sqlite.py +19 -8
  62. adam/commands/export/show_column_counts.py +10 -10
  63. adam/commands/export/show_export_databases.py +2 -1
  64. adam/commands/export/show_export_session.py +1 -1
  65. adam/commands/export/show_export_sessions.py +1 -1
  66. adam/commands/export/utils_export.py +38 -15
  67. adam/commands/fs/__init__.py +0 -0
  68. adam/commands/{cat.py → fs/cat.py} +2 -2
  69. adam/commands/fs/cat_local.py +42 -0
  70. adam/commands/{cd.py → fs/cd.py} +2 -2
  71. adam/commands/{download_file.py → fs/download_file.py} +5 -5
  72. adam/commands/{find_files.py → fs/find_files.py} +4 -4
  73. adam/commands/{find_processes.py → fs/find_processes.py} +3 -3
  74. adam/commands/{head.py → fs/head.py} +2 -2
  75. adam/commands/{ls.py → fs/ls.py} +2 -2
  76. adam/commands/fs/ls_local.py +40 -0
  77. adam/commands/fs/rm.py +18 -0
  78. adam/commands/fs/rm_downloads.py +39 -0
  79. adam/commands/fs/rm_logs.py +38 -0
  80. adam/commands/{show → fs}/show_adam.py +1 -1
  81. adam/commands/intermediate_command.py +3 -0
  82. adam/commands/medusa/medusa_restore.py +2 -16
  83. adam/commands/medusa/utils_medusa.py +15 -0
  84. adam/commands/nodetool/__init__.py +0 -0
  85. adam/commands/{nodetool.py → nodetool/nodetool.py} +3 -8
  86. adam/commands/postgres/completions_p.py +22 -0
  87. adam/commands/postgres/postgres.py +7 -14
  88. adam/commands/postgres/postgres_databases.py +3 -3
  89. adam/commands/postgres/postgres_ls.py +1 -1
  90. adam/commands/postgres/utils_postgres.py +12 -2
  91. adam/commands/preview_table.py +1 -1
  92. adam/commands/reaper/reaper_schedule_activate.py +6 -2
  93. adam/commands/reaper/reaper_schedule_start.py +1 -2
  94. adam/commands/reaper/reaper_schedule_stop.py +1 -2
  95. adam/commands/reaper/utils_reaper.py +10 -1
  96. adam/commands/repair/repair_scan.py +0 -2
  97. adam/commands/repair/repair_stop.py +0 -1
  98. adam/commands/{show/show.py → show.py} +12 -11
  99. adam/config.py +4 -5
  100. adam/embedded_params.py +1 -1
  101. adam/repl.py +22 -9
  102. adam/repl_commands.py +50 -42
  103. adam/repl_session.py +9 -1
  104. adam/repl_state.py +16 -1
  105. adam/sql/async_executor.py +62 -0
  106. adam/sql/lark_completer.py +286 -0
  107. adam/sql/lark_parser.py +604 -0
  108. adam/sql/qingl.lark +1076 -0
  109. adam/sso/cred_cache.py +2 -5
  110. adam/utils.py +216 -79
  111. adam/utils_k8s/app_clusters.py +11 -4
  112. adam/utils_k8s/app_pods.py +10 -5
  113. adam/utils_k8s/cassandra_clusters.py +8 -4
  114. adam/utils_k8s/cassandra_nodes.py +14 -5
  115. adam/utils_k8s/k8s.py +9 -0
  116. adam/utils_k8s/kube_context.py +1 -4
  117. adam/{pod_exec_result.py → utils_k8s/pod_exec_result.py} +8 -2
  118. adam/utils_k8s/pods.py +83 -24
  119. adam/utils_k8s/statefulsets.py +5 -2
  120. adam/utils_local.py +78 -2
  121. adam/utils_repl/appendable_completer.py +6 -0
  122. adam/utils_repl/repl_completer.py +51 -4
  123. adam/utils_sqlite.py +3 -8
  124. adam/version.py +1 -1
  125. {kaqing-2.0.184.dist-info → kaqing-2.0.214.dist-info}/METADATA +1 -1
  126. kaqing-2.0.214.dist-info/RECORD +272 -0
  127. kaqing-2.0.214.dist-info/top_level.txt +2 -0
  128. teddy/__init__.py +0 -0
  129. teddy/lark_parser.py +436 -0
  130. teddy/lark_parser2.py +618 -0
  131. adam/commands/cql/cql_completions.py +0 -32
  132. adam/commands/export/export_select_x.py +0 -54
  133. adam/commands/logs.py +0 -37
  134. adam/commands/postgres/psql_completions.py +0 -11
  135. adam/commands/report.py +0 -61
  136. adam/commands/restart.py +0 -60
  137. adam/commands/show/show_processes.py +0 -49
  138. kaqing-2.0.184.dist-info/RECORD +0 -244
  139. kaqing-2.0.184.dist-info/top_level.txt +0 -1
  140. /adam/commands/{login.py → app/login.py} +0 -0
  141. /adam/commands/{show → cassandra}/__init__.py +0 -0
  142. /adam/commands/{show → cassandra}/show_cassandra_version.py +0 -0
  143. /adam/commands/{watch.py → cassandra/watch.py} +0 -0
  144. /adam/commands/{param_get.py → config/param_get.py} +0 -0
  145. /adam/commands/{param_set.py → config/param_set.py} +0 -0
  146. /adam/commands/{issues.py → diag/issues.py} +0 -0
  147. /adam/commands/{pwd.py → fs/pwd.py} +0 -0
  148. /adam/commands/{shell.py → fs/shell.py} +0 -0
  149. /adam/commands/{show → fs}/show_host.py +0 -0
  150. /adam/commands/{nodetool_commands.py → nodetool/nodetool_commands.py} +0 -0
  151. {kaqing-2.0.184.dist-info → kaqing-2.0.214.dist-info}/WHEEL +0 -0
  152. {kaqing-2.0.184.dist-info → kaqing-2.0.214.dist-info}/entry_points.txt +0 -0
@@ -1,17 +1,26 @@
1
1
  from adam.config import Config
2
2
  from adam.utils_k8s.pods import Pods
3
3
  from adam.utils_k8s.secrets import Secrets
4
- from adam.pod_exec_result import PodExecResult
4
+ from adam.utils_k8s.pod_exec_result import PodExecResult
5
5
  from adam.repl_session import ReplSession
6
6
 
7
7
  # utility collection on cassandra nodes; methods are all static
8
8
  class CassandraNodes:
9
- def exec(pod_name: str, namespace: str, command: str, show_out = True, throw_err = False, shell = '/bin/sh', backgrounded = False, log_file = None) -> PodExecResult:
9
+ def exec(pod_name: str,
10
+ namespace: str,
11
+ command: str,
12
+ show_out = True,
13
+ throw_err = False,
14
+ shell = '/bin/sh',
15
+ backgrounded = False,
16
+ log_file = None,
17
+ history = True) -> PodExecResult:
10
18
  r = Pods.exec(pod_name, "cassandra", namespace, command, show_out = show_out, throw_err = throw_err, shell = shell, backgrounded = backgrounded, log_file=log_file)
11
19
 
12
- if r and Config().get('repl.history.push-cat-remote-log-file', True):
13
- if r.log_file and ReplSession().prompt_session:
14
- ReplSession().prompt_session.history.append_string(f'@{r.pod} cat {r.log_file}')
20
+ if history and r and r.log_file:
21
+ entry = f':cat {r.log_file}'
22
+
23
+ ReplSession().append_history(entry)
15
24
 
16
25
  return r
17
26
 
adam/utils_k8s/k8s.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from collections.abc import Callable
2
+ import inspect
2
3
  import re
3
4
  import portforward
4
5
 
@@ -49,6 +50,14 @@ class PortForwardHandler:
49
50
  raise InvalidStateException(state)
50
51
 
51
52
  self.pod = pod
53
+
54
+ # pf = portforward.forward(state.namespace, pod, self.local_port + 1, self.target_port, log_level=portforward.LogLevel.DEBUG)
55
+ # print(inspect.getsource(pf.__enter__))
56
+ # print('test portforward START', state.namespace, pod, self.local_port + 1, self.target_port, pf.__enter__)
57
+ # with pf:
58
+ # print('test portforward BODY')
59
+ # print('test portforward OK')
60
+
52
61
  self.forward_connection = portforward.forward(state.namespace, pod, self.local_port, self.target_port)
53
62
  if self.inc_connection_cnt() == 1:
54
63
  self.forward_connection.__enter__()
@@ -102,7 +102,4 @@ class KubeContext:
102
102
  return name if re.match(r"^(?!pg-).*-k8spg-.*$", name) else None
103
103
 
104
104
  def show_out(s: bool):
105
- return s or Config().is_debug()
106
-
107
- def show_parallelism():
108
- return Config().get('debugs.show-parallelism', False)
105
+ return s or Config().is_debug()
@@ -1,8 +1,8 @@
1
1
  import yaml
2
2
 
3
- from adam.utils import log_exc
3
+ from adam.utils import ExecResult, log_exc
4
4
 
5
- class PodExecResult:
5
+ class PodExecResult(ExecResult):
6
6
  # {
7
7
  # 'metadata': {},
8
8
  # 'status': 'Failure',
@@ -34,6 +34,12 @@ class PodExecResult:
34
34
 
35
35
  return code
36
36
 
37
+ def cat_log_file_cmd(self):
38
+ if self.pod and self.log_file:
39
+ return f'@{self.pod} cat {self.log_file}'
40
+
41
+ return None
42
+
37
43
  def __str__(self):
38
44
  return f'{"OK" if self.exit_code() == 0 else self.exit_code()} {self.command}'
39
45
 
adam/utils_k8s/pods.py CHANGED
@@ -1,6 +1,8 @@
1
1
  from collections.abc import Callable
2
2
  from datetime import datetime
3
3
  import os
4
+ import re
5
+ import subprocess
4
6
  import sys
5
7
  import time
6
8
  from typing import TypeVar
@@ -9,10 +11,11 @@ from kubernetes.stream import stream
9
11
  from kubernetes.stream.ws_client import ERROR_CHANNEL, WSClient
10
12
 
11
13
  from adam.config import Config
14
+ from adam.repl_session import ReplSession
12
15
  from adam.utils_k8s.volumes import ConfigMapMount
13
- from adam.pod_exec_result import PodExecResult
14
- from adam.utils import GeneratorStream, ParallelMapHandler, log2, debug, log_exc
15
- from adam.utils_local import local_tmp_dir
16
+ from adam.utils_k8s.pod_exec_result import PodExecResult
17
+ from adam.utils import GeneratorStream, ParallelMapHandler, log2, debug, log_dir, log_exc
18
+ from adam.utils_local import local_downloads_dir
16
19
  from .kube_context import KubeContext
17
20
 
18
21
  from websocket._core import WebSocket
@@ -66,6 +69,33 @@ class Pods:
66
69
 
67
70
  show_out = KubeContext.show_out(show_out)
68
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
+ if env_prefix:
85
+ command = f'{env_prefix} {command}'
86
+
87
+ command = command.replace('"', '\\"')
88
+ cmd = f'nohup kubectl exec {pod_name} -c {container} -- {shell} -c "{command} &" > {log_pod_file} 2>&1 &'
89
+ if log_all_file:
90
+ cmd = f'{cmd} >> {log_all_file}'
91
+
92
+ if show_out:
93
+ log2(cmd)
94
+
95
+ result = subprocess.run(cmd, capture_output=True, text=True, shell=True)
96
+
97
+ return PodExecResult(result.stdout, result.stderr, cmd, None, pod=pod_name, log_file=log_pod_file)
98
+
69
99
  api = client.CoreV1Api()
70
100
 
71
101
  tty = True
@@ -73,22 +103,22 @@ class Pods:
73
103
  if env_prefix:
74
104
  exec_command = [shell, '-c', f'{env_prefix} {command}']
75
105
 
76
- if backgrounded or command.endswith(' &'):
77
- # should be false for starting a background process
78
- tty = False
79
-
80
- if Config().get('repl.background-process.auto-nohup', True):
81
- command = command.strip(' &')
82
- cmd_name = ''
83
- if command.startswith('nodetool '):
84
- cmd_name = f".{'_'.join(command.split(' ')[5:])}"
85
-
86
- if not log_file:
87
- log_file = f'{log_prefix()}-{datetime.now().strftime("%d%H%M%S")}{cmd_name}.log'
88
- command = f"nohup {command} > {log_file} 2>&1 &"
89
- if env_prefix:
90
- command = f'{env_prefix} {command}'
91
- exec_command = [shell, '-c', command]
106
+ # if backgrounded or command.endswith(' &'):
107
+ # # should be false for starting a background process
108
+ # tty = False
109
+
110
+ # if Config().get('repl.background-process.auto-nohup', True):
111
+ # command = command.strip(' &')
112
+ # cmd_name = ''
113
+ # if command.startswith('nodetool '):
114
+ # cmd_name = f".{'_'.join(command.split(' ')[5:])}"
115
+
116
+ # if not log_file:
117
+ # log_file = f'{log_prefix()}-{datetime.now().strftime("%d%H%M%S")}{cmd_name}.log'
118
+ # command = f"nohup {command} > {log_file} 2>&1 &"
119
+ # if env_prefix:
120
+ # command = f'{env_prefix} {command}'
121
+ # exec_command = [shell, '-c', command]
92
122
 
93
123
  k_command = f'kubectl exec {pod_name} -c {container} -n {namespace} -- {shell} -c "{command}"'
94
124
  debug(k_command)
@@ -141,6 +171,36 @@ class Pods:
141
171
 
142
172
  return PodExecResult("".join(stdout), "".join(stderr), k_command, error_output, pod=pod_name, log_file=log_file)
143
173
 
174
+ def log_file(command: str, pod_name: str = None, dt: datetime = None):
175
+ cmd_name = ''
176
+ if command.startswith('nodetool '):
177
+ command = command.strip(' &')
178
+ cmd_name = f".{'_'.join(command.split(' ')[5:])}"
179
+
180
+ pod_suffix = '{pod}'
181
+ if pod_name:
182
+ pod_suffix = pod_name
183
+ if groups := re.match(r'.*-(.*)', pod_name):
184
+ pod_suffix = f'-{groups[1]}'
185
+
186
+ return f'{log_dir()}/{Pods.job_id()}{cmd_name}{pod_suffix}.log'
187
+
188
+ def job_id(dt: datetime = None):
189
+ if not dt:
190
+ dt = datetime.now()
191
+
192
+ return dt.strftime("%d%H%M%S")
193
+
194
+ def log_file_from_template(log_file: str, pod_name: str):
195
+ pod_suffix = pod_name
196
+ if pod_name and (groups := re.match(r'.*-(.*)', pod_name)):
197
+ pod_suffix = f'-{groups[1]}'
198
+
199
+ if not pod_suffix.startswith('-'):
200
+ pod_suffix = f'-{pod_suffix}'
201
+
202
+ return log_file.replace('{pod}', pod_suffix)
203
+
144
204
  def read_file(pod_name: str, container: str, namespace: str, file_path: str):
145
205
  v1 = client.CoreV1Api()
146
206
 
@@ -175,13 +235,15 @@ class Pods:
175
235
 
176
236
  def download_file(pod_name: str, container: str, namespace: str, from_path: str, to_path: str = None):
177
237
  if not to_path:
178
- to_path = f'{local_tmp_dir()}/{os.path.basename(from_path)}'
238
+ to_path = f'{local_downloads_dir()}/{os.path.basename(from_path)}'
179
239
 
180
240
  bytes = Pods.read_file(pod_name, container, namespace, from_path)
181
241
  with open(to_path, 'wb') as f:
182
242
  for item in GeneratorStream(bytes):
183
243
  f.write(item)
184
244
 
245
+ ReplSession().append_history(f':cat {to_path}')
246
+
185
247
  return to_path
186
248
 
187
249
  def get_container(namespace: str, pod_name: str, container_name: str):
@@ -292,7 +354,4 @@ class Pods:
292
354
  log2(' Timed Out')
293
355
 
294
356
  def completed(namespace: str, pod_name: str):
295
- return Pods.get(namespace, pod_name).status.phase in ['Succeeded', 'Failed']
296
-
297
- def log_prefix():
298
- return Config().get('log-prefix', '/tmp/qing')
357
+ return Pods.get(namespace, pod_name).status.phase in ['Succeeded', 'Failed']
@@ -56,8 +56,11 @@ class StatefulSets:
56
56
  return statefulset_pods
57
57
 
58
58
  @functools.lru_cache()
59
- def pod_names(ss: str, ns: str):
60
- return [pod.metadata.name for pod in StatefulSets.pods(ss, ns)]
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 CHANGED
@@ -1,4 +1,80 @@
1
+ import os
2
+ import subprocess
3
+ import traceback
4
+
1
5
  from adam.config import Config
6
+ from adam.utils import ExecResult, creating_dir, debug
7
+
8
+ def local_qing_dir():
9
+ return creating_dir(Config().get('local-qing-dir', '/tmp/qing-db/q'))
10
+
11
+ def local_downloads_dir():
12
+ return creating_dir(Config().get('local-downloads-dir', '/tmp/qing-db/q/downloads'))
13
+
14
+ class LocalExecResult(ExecResult):
15
+ def __init__(self, stdout: str, stderr: str, command: str = None, code = 0, log_file: str = None):
16
+ self.stdout: str = stdout
17
+ self.stderr: str = stderr
18
+ self.command: str = command
19
+ self.code = code
20
+ self.pod = 'local'
21
+ self.log_file = log_file
22
+
23
+ def exit_code(self) -> int:
24
+ return self.code
25
+
26
+ def cat_log_file_cmd(self):
27
+ if self.log_file:
28
+ return f':cat {self.log_file}'
29
+
30
+ return None
31
+
32
+ def __str__(self):
33
+ return f'{"OK" if self.exit_code() == 0 else self.exit_code()} {self.command}'
34
+
35
+ def __audit_extra__(self):
36
+ return self.log_file if self.log_file else None
37
+
38
+ def local_exec(cmd: list[str], shell=False, show_out=False):
39
+ stdout = ''
40
+ stderr = ''
41
+ returncode = 0
42
+
43
+ try:
44
+ if show_out:
45
+ debug(' '.join(cmd))
46
+
47
+ r = subprocess.run(cmd, capture_output=True, text=True, shell=shell)
48
+ stdout = r.stdout
49
+ stderr = r.stderr
50
+ returncode = r.returncode
51
+ except FileNotFoundError as e:
52
+ pass
53
+
54
+ return LocalExecResult(stdout, stderr, ' '.join(cmd), returncode)
55
+
56
+ def find_local_files(pattern: str = f'{local_qing_dir()}/*', file_type: str = None, max_depth = 0, mmin: int = 0):
57
+ # find . -maxdepth 1 -type f -name '*'
58
+ log_files = []
59
+ try:
60
+ dir = os.path.dirname(pattern)
61
+ base = os.path.basename(pattern)
62
+ cmd = ['find', dir]
63
+ if file_type:
64
+ cmd += ['-type', file_type]
65
+ if max_depth:
66
+ cmd += ['-maxdepth', str(max_depth)]
67
+ if mmin:
68
+ cmd += ['-mmin', f'-{mmin}']
69
+ cmd += ['-name', base]
70
+
71
+ stdout = local_exec(cmd, show_out=Config().is_debug()).stdout
72
+
73
+ for line in stdout.split('\n'):
74
+ line = line.strip(' \r')
75
+ if line:
76
+ log_files.append(line)
77
+ except:
78
+ traceback.print_exc()
2
79
 
3
- def local_tmp_dir():
4
- return Config().get('local-tmp-dir', '/tmp/qing-db')
80
+ return log_files
@@ -0,0 +1,6 @@
1
+ from abc import abstractmethod
2
+
3
+ class AppendableCompleter:
4
+ @abstractmethod
5
+ def append_completions(self, key: str, value: dict[str, any]):
6
+ pass
@@ -1,14 +1,57 @@
1
+ import copy
1
2
  import re
2
- from typing import Iterable, TypeVar
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
@@ -33,8 +76,12 @@ class ReplCompleter(NestedCompleter):
33
76
  cursor_position=document.cursor_position - move_cursor,
34
77
  )
35
78
 
36
- for c in completer.get_completions(new_document, complete_event):
37
- yield c
79
+ try:
80
+ # potential thread racing
81
+ for c in completer.get_completions(new_document, complete_event):
82
+ yield c
83
+ except:
84
+ pass
38
85
 
39
86
  # No space in the input: behave exactly like `WordCompleter`.
40
87
  else:
@@ -43,4 +90,4 @@ class ReplCompleter(NestedCompleter):
43
90
  list(self.options.keys()), ignore_case=self.ignore_case, pattern=re.compile(r"([a-zA-Z0-9_\.\@\&]+|[^a-zA-Z0-9_\.\@\&\s]+)")
44
91
  )
45
92
  for c in completer.get_completions(document, complete_event):
46
- yield c
93
+ yield c
adam/utils_sqlite.py CHANGED
@@ -7,7 +7,7 @@ import sqlite3
7
7
  import pandas
8
8
 
9
9
  from adam.config import Config
10
- from adam.utils import tabulize, log, wait_log
10
+ from adam.utils import creating_dir, tabulize, log, wait_log
11
11
 
12
12
  class CursorHandler:
13
13
  def __init__(self, conn: sqlite3.Connection):
@@ -53,7 +53,7 @@ class SQLite:
53
53
  return CursorHandler(conn)
54
54
 
55
55
  def local_db_dir():
56
- return Config().get('export.sqlite.local-db-dir', '/tmp/qing-db')
56
+ return creating_dir(Config().get('export.sqlite.local-db-dir', '/tmp/qing-db/q/export/db'))
57
57
 
58
58
  def keyspace(database: str):
59
59
  return '_'.join(database.replace(".db", "").split('_')[1:])
@@ -95,8 +95,6 @@ class SQLite:
95
95
  conn.close()
96
96
 
97
97
  def connect(database: str, keyspace: str = None):
98
- os.makedirs(SQLite.local_db_dir(), exist_ok=True)
99
-
100
98
  if keyspace:
101
99
  return sqlite3.connect(f'{SQLite.local_db_dir()}/{database}_{keyspace}.db')
102
100
  else:
@@ -131,7 +129,4 @@ class SQLite:
131
129
  return len(lines), log_file
132
130
 
133
131
  def query(conn, sql: str) -> tuple[str, str, list]:
134
- return pandas.read_sql_query(sql, conn)
135
-
136
- def log_prefix():
137
- return Config().get('export.log-prefix', '/tmp/qing')
132
+ return pandas.read_sql_query(sql, conn)
adam/version.py CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
- __version__ = "2.0.184" #: the working version
4
+ __version__ = "2.0.214" #: the working version
5
5
  __release__ = "1.0.0" #: the release version
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kaqing
3
- Version: 2.0.184
3
+ Version: 2.0.214
4
4
  Summary: UNKNOWN
5
5
  Home-page: UNKNOWN
6
6
  License: UNKNOWN