kaqing 2.0.14__py3-none-any.whl → 2.0.189__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 (228) hide show
  1. adam/__init__.py +0 -2
  2. adam/app_session.py +9 -12
  3. adam/apps.py +20 -6
  4. adam/batch.py +16 -6
  5. adam/checks/check_utils.py +19 -49
  6. adam/checks/compactionstats.py +1 -1
  7. adam/checks/cpu.py +9 -3
  8. adam/checks/cpu_metrics.py +52 -0
  9. adam/checks/disk.py +3 -4
  10. adam/checks/gossip.py +1 -1
  11. adam/checks/memory.py +3 -3
  12. adam/checks/status.py +1 -1
  13. adam/columns/columns.py +3 -1
  14. adam/columns/cpu.py +3 -1
  15. adam/columns/cpu_metrics.py +22 -0
  16. adam/columns/memory.py +3 -4
  17. adam/commands/__init__.py +24 -0
  18. adam/commands/alter_tables.py +66 -0
  19. adam/commands/app/app.py +38 -0
  20. adam/commands/{app_ping.py → app/app_ping.py} +8 -14
  21. adam/commands/app/show_app_actions.py +49 -0
  22. adam/commands/{show → app}/show_app_id.py +9 -12
  23. adam/commands/{show → app}/show_app_queues.py +8 -14
  24. adam/commands/app/utils_app.py +106 -0
  25. adam/commands/audit/__init__.py +0 -0
  26. adam/commands/audit/audit.py +67 -0
  27. adam/commands/audit/audit_repair_tables.py +72 -0
  28. adam/commands/audit/audit_run.py +50 -0
  29. adam/commands/audit/completions_l.py +15 -0
  30. adam/commands/audit/show_last10.py +36 -0
  31. adam/commands/audit/show_slow10.py +36 -0
  32. adam/commands/audit/show_top10.py +36 -0
  33. adam/commands/audit/utils_show_top10.py +71 -0
  34. adam/commands/bash/__init__.py +5 -0
  35. adam/commands/bash/bash.py +36 -0
  36. adam/commands/bash/bash_completer.py +93 -0
  37. adam/commands/bash/utils_bash.py +16 -0
  38. adam/commands/cat.py +36 -0
  39. adam/commands/cd.py +14 -88
  40. adam/commands/check.py +18 -21
  41. adam/commands/cli_commands.py +11 -7
  42. adam/commands/clipboard_copy.py +87 -0
  43. adam/commands/code.py +57 -0
  44. adam/commands/command.py +220 -19
  45. adam/commands/commands_utils.py +28 -31
  46. adam/commands/cql/__init__.py +0 -0
  47. adam/commands/cql/completions_c.py +28 -0
  48. adam/commands/{cqlsh.py → cql/cqlsh.py} +13 -32
  49. adam/commands/cql/utils_cql.py +305 -0
  50. adam/commands/deploy/code_start.py +7 -10
  51. adam/commands/deploy/code_stop.py +4 -21
  52. adam/commands/deploy/code_utils.py +5 -5
  53. adam/commands/deploy/deploy.py +4 -40
  54. adam/commands/deploy/deploy_frontend.py +15 -18
  55. adam/commands/deploy/deploy_pg_agent.py +4 -7
  56. adam/commands/deploy/deploy_pod.py +74 -77
  57. adam/commands/deploy/deploy_utils.py +16 -26
  58. adam/commands/deploy/undeploy.py +4 -40
  59. adam/commands/deploy/undeploy_frontend.py +5 -8
  60. adam/commands/deploy/undeploy_pg_agent.py +7 -8
  61. adam/commands/deploy/undeploy_pod.py +16 -17
  62. adam/commands/devices/__init__.py +0 -0
  63. adam/commands/devices/device.py +149 -0
  64. adam/commands/devices/device_app.py +163 -0
  65. adam/commands/devices/device_auit_log.py +49 -0
  66. adam/commands/devices/device_cass.py +179 -0
  67. adam/commands/devices/device_export.py +87 -0
  68. adam/commands/devices/device_postgres.py +160 -0
  69. adam/commands/devices/devices.py +25 -0
  70. adam/commands/download_file.py +47 -0
  71. adam/commands/exit.py +1 -4
  72. adam/commands/export/__init__.py +0 -0
  73. adam/commands/export/clean_up_all_export_sessions.py +37 -0
  74. adam/commands/export/clean_up_export_sessions.py +39 -0
  75. adam/commands/export/completions_x.py +11 -0
  76. adam/commands/export/download_export_session.py +40 -0
  77. adam/commands/export/drop_export_database.py +39 -0
  78. adam/commands/export/drop_export_databases.py +37 -0
  79. adam/commands/export/export.py +37 -0
  80. adam/commands/export/export_databases.py +246 -0
  81. adam/commands/export/export_select.py +34 -0
  82. adam/commands/export/export_sessions.py +209 -0
  83. adam/commands/export/export_use.py +49 -0
  84. adam/commands/export/export_x_select.py +48 -0
  85. adam/commands/export/exporter.py +332 -0
  86. adam/commands/export/import_files.py +44 -0
  87. adam/commands/export/import_session.py +44 -0
  88. adam/commands/export/importer.py +81 -0
  89. adam/commands/export/importer_athena.py +148 -0
  90. adam/commands/export/importer_sqlite.py +67 -0
  91. adam/commands/export/show_column_counts.py +45 -0
  92. adam/commands/export/show_export_databases.py +39 -0
  93. adam/commands/export/show_export_session.py +39 -0
  94. adam/commands/export/show_export_sessions.py +37 -0
  95. adam/commands/export/utils_export.py +344 -0
  96. adam/commands/find_files.py +51 -0
  97. adam/commands/find_processes.py +76 -0
  98. adam/commands/head.py +36 -0
  99. adam/commands/help.py +14 -9
  100. adam/commands/intermediate_command.py +52 -0
  101. adam/commands/issues.py +14 -40
  102. adam/commands/kubectl.py +38 -0
  103. adam/commands/login.py +26 -25
  104. adam/commands/logs.py +5 -7
  105. adam/commands/ls.py +11 -115
  106. adam/commands/medusa/medusa.py +4 -46
  107. adam/commands/medusa/medusa_backup.py +22 -29
  108. adam/commands/medusa/medusa_restore.py +51 -49
  109. adam/commands/medusa/medusa_show_backupjobs.py +20 -21
  110. adam/commands/medusa/medusa_show_restorejobs.py +16 -21
  111. adam/commands/medusa/utils_medusa.py +15 -0
  112. adam/commands/nodetool.py +8 -17
  113. adam/commands/param_get.py +11 -14
  114. adam/commands/param_set.py +9 -13
  115. adam/commands/postgres/completions_p.py +22 -0
  116. adam/commands/postgres/postgres.py +49 -73
  117. adam/commands/postgres/postgres_databases.py +270 -0
  118. adam/commands/postgres/postgres_ls.py +4 -8
  119. adam/commands/postgres/postgres_preview.py +5 -9
  120. adam/commands/postgres/utils_postgres.py +79 -0
  121. adam/commands/preview_table.py +10 -69
  122. adam/commands/pwd.py +14 -43
  123. adam/commands/reaper/reaper.py +6 -49
  124. adam/commands/reaper/reaper_forward.py +49 -56
  125. adam/commands/reaper/reaper_forward_session.py +6 -0
  126. adam/commands/reaper/reaper_forward_stop.py +10 -16
  127. adam/commands/reaper/reaper_restart.py +8 -15
  128. adam/commands/reaper/reaper_run_abort.py +8 -33
  129. adam/commands/reaper/reaper_runs.py +43 -58
  130. adam/commands/reaper/reaper_runs_abort.py +29 -49
  131. adam/commands/reaper/reaper_schedule_activate.py +14 -33
  132. adam/commands/reaper/reaper_schedule_start.py +9 -33
  133. adam/commands/reaper/reaper_schedule_stop.py +9 -33
  134. adam/commands/reaper/reaper_schedules.py +4 -14
  135. adam/commands/reaper/reaper_status.py +8 -16
  136. adam/commands/reaper/utils_reaper.py +203 -0
  137. adam/commands/repair/repair.py +4 -46
  138. adam/commands/repair/repair_log.py +6 -12
  139. adam/commands/repair/repair_run.py +29 -36
  140. adam/commands/repair/repair_scan.py +33 -41
  141. adam/commands/repair/repair_stop.py +6 -13
  142. adam/commands/report.py +25 -21
  143. adam/commands/restart.py +27 -28
  144. adam/commands/rollout.py +20 -25
  145. adam/commands/shell.py +12 -4
  146. adam/commands/show/show.py +15 -46
  147. adam/commands/show/show_adam.py +3 -3
  148. adam/commands/show/show_cassandra_repairs.py +37 -0
  149. adam/commands/show/show_cassandra_status.py +48 -52
  150. adam/commands/show/show_cassandra_version.py +5 -18
  151. adam/commands/show/show_cli_commands.py +56 -0
  152. adam/commands/show/show_host.py +33 -0
  153. adam/commands/show/show_login.py +23 -27
  154. adam/commands/show/show_params.py +2 -5
  155. adam/commands/show/show_processes.py +18 -21
  156. adam/commands/show/show_storage.py +11 -20
  157. adam/commands/watch.py +27 -30
  158. adam/config.py +8 -6
  159. adam/embedded_params.py +1 -1
  160. adam/log.py +4 -4
  161. adam/pod_exec_result.py +13 -5
  162. adam/repl.py +136 -120
  163. adam/repl_commands.py +66 -24
  164. adam/repl_session.py +8 -1
  165. adam/repl_state.py +343 -73
  166. adam/sql/__init__.py +0 -0
  167. adam/sql/lark_completer.py +284 -0
  168. adam/sql/lark_parser.py +604 -0
  169. adam/sql/sql_completer.py +118 -0
  170. adam/sql/sql_state_machine.py +630 -0
  171. adam/sql/term_completer.py +76 -0
  172. adam/sso/authn_ad.py +7 -9
  173. adam/sso/authn_okta.py +4 -6
  174. adam/sso/cred_cache.py +4 -6
  175. adam/sso/idp.py +10 -13
  176. adam/utils.py +539 -11
  177. adam/utils_athena.py +145 -0
  178. adam/utils_audits.py +102 -0
  179. adam/utils_issues.py +32 -0
  180. adam/utils_k8s/__init__.py +0 -0
  181. adam/utils_k8s/app_clusters.py +28 -0
  182. adam/utils_k8s/app_pods.py +36 -0
  183. adam/utils_k8s/cassandra_clusters.py +44 -0
  184. adam/{k8s_utils → utils_k8s}/cassandra_nodes.py +12 -5
  185. adam/{k8s_utils → utils_k8s}/custom_resources.py +16 -17
  186. adam/{k8s_utils → utils_k8s}/deployment.py +2 -2
  187. adam/{k8s_utils → utils_k8s}/ingresses.py +2 -2
  188. adam/{k8s_utils → utils_k8s}/jobs.py +7 -11
  189. adam/utils_k8s/k8s.py +96 -0
  190. adam/{k8s_utils → utils_k8s}/kube_context.py +3 -3
  191. adam/{k8s_utils → utils_k8s}/pods.py +132 -83
  192. adam/{k8s_utils → utils_k8s}/secrets.py +7 -3
  193. adam/{k8s_utils → utils_k8s}/service_accounts.py +5 -4
  194. adam/{k8s_utils → utils_k8s}/services.py +2 -2
  195. adam/{k8s_utils → utils_k8s}/statefulsets.py +9 -16
  196. adam/utils_local.py +4 -0
  197. adam/utils_net.py +24 -0
  198. adam/utils_repl/__init__.py +0 -0
  199. adam/utils_repl/appendable_completer.py +6 -0
  200. adam/utils_repl/automata_completer.py +48 -0
  201. adam/utils_repl/repl_completer.py +172 -0
  202. adam/utils_repl/state_machine.py +173 -0
  203. adam/utils_sqlite.py +137 -0
  204. adam/version.py +1 -1
  205. {kaqing-2.0.14.dist-info → kaqing-2.0.189.dist-info}/METADATA +1 -1
  206. kaqing-2.0.189.dist-info/RECORD +253 -0
  207. kaqing-2.0.189.dist-info/top_level.txt +2 -0
  208. teddy/__init__.py +0 -0
  209. teddy/lark_parser.py +436 -0
  210. teddy/lark_parser2.py +618 -0
  211. adam/commands/app.py +0 -67
  212. adam/commands/bash.py +0 -87
  213. adam/commands/cp.py +0 -95
  214. adam/commands/cql_utils.py +0 -53
  215. adam/commands/devices.py +0 -89
  216. adam/commands/postgres/postgres_session.py +0 -247
  217. adam/commands/reaper/reaper_session.py +0 -159
  218. adam/commands/show/show_app_actions.py +0 -53
  219. adam/commands/show/show_commands.py +0 -61
  220. adam/commands/show/show_repairs.py +0 -47
  221. adam/k8s_utils/cassandra_clusters.py +0 -48
  222. kaqing-2.0.14.dist-info/RECORD +0 -167
  223. kaqing-2.0.14.dist-info/top_level.txt +0 -1
  224. /adam/{k8s_utils → commands/app}/__init__.py +0 -0
  225. /adam/{k8s_utils → utils_k8s}/config_maps.py +0 -0
  226. /adam/{k8s_utils → utils_k8s}/volumes.py +0 -0
  227. {kaqing-2.0.14.dist-info → kaqing-2.0.189.dist-info}/WHEEL +0 -0
  228. {kaqing-2.0.14.dist-info → kaqing-2.0.189.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,172 @@
1
+ from concurrent.futures import ThreadPoolExecutor
2
+ import copy
3
+ import inspect
4
+ import re
5
+ import threading
6
+ import traceback
7
+ from typing import Iterable, TypeVar, cast
8
+ from prompt_toolkit.completion import CompleteEvent, Completer, Completion, NestedCompleter, WordCompleter
9
+ from prompt_toolkit.document import Document
10
+
11
+ from adam.utils import debug, debug_complete, log2, log_timing
12
+ from adam.utils_repl.appendable_completer import AppendableCompleter
13
+
14
+ import nest_asyncio
15
+ nest_asyncio.apply()
16
+
17
+ import asyncio
18
+
19
+ __all__ = [
20
+ "ReplCompleter",
21
+ ]
22
+
23
+ T = TypeVar('T')
24
+
25
+ def merge_completions(dict1, dict2):
26
+ if isinstance(dict1, dict):
27
+ target = dict1.copy()
28
+ else:
29
+ target = copy.copy(dict1)
30
+
31
+ try:
32
+ for key, value in dict2.items():
33
+ if key in target:
34
+ debug_complete(f'[{key}] {type(dict2)} is being merged to {type(target[key])} completions')
35
+ if isinstance(value, dict):
36
+ if isinstance(target[key], dict):
37
+ target[key] = merge_completions(target[key], value)
38
+ elif isinstance(target[key], AppendableCompleter):
39
+ cast(AppendableCompleter, target[key]).append_completions(key, value)
40
+ elif isinstance(value, AppendableCompleter):
41
+ if isinstance(target[key], dict):
42
+ cast(AppendableCompleter, value).append_completions(key, target[key])
43
+ target[key] = value
44
+ else:
45
+ log2(f'* {key} of {type(value)} is overriding existing {type(target[key])} completions')
46
+ else:
47
+ target[key] = value
48
+ else:
49
+ target[key] = value
50
+
51
+ except Exception as e:
52
+ traceback.print_exc()
53
+
54
+ return target
55
+
56
+ class ReplCompleter(NestedCompleter):
57
+ def get_completions(
58
+ self, document: Document, complete_event: CompleteEvent
59
+ ) -> Iterable[Completion]:
60
+ # Split document.
61
+ text = document.text_before_cursor.lstrip()
62
+ stripped_len = len(document.text_before_cursor) - len(text)
63
+
64
+ # If there is a space, check for the first term, and use a
65
+ # subcompleter.
66
+ if " " in text:
67
+ first_term = text.split()[0]
68
+ completer = self.options.get(first_term)
69
+
70
+ # If we have a sub completer, use this for the completions.
71
+ if completer is not None:
72
+ remaining_text = text[len(first_term) :].lstrip()
73
+ move_cursor = len(text) - len(remaining_text) + stripped_len
74
+
75
+ new_document = Document(
76
+ remaining_text,
77
+ cursor_position=document.cursor_position - move_cursor,
78
+ )
79
+
80
+ for c in completer.get_completions(new_document, complete_event):
81
+ yield c
82
+
83
+ # No space in the input: behave exactly like `WordCompleter`.
84
+ else:
85
+ completer = WordCompleter(
86
+ # Allow dot in the middle or a word
87
+ list(self.options.keys()), ignore_case=self.ignore_case, pattern=re.compile(r"([a-zA-Z0-9_\.\@\&]+|[^a-zA-Z0-9_\.\@\&\s]+)")
88
+ )
89
+ for c in completer.get_completions(document, complete_event):
90
+ yield c
91
+
92
+ lock = threading.Lock()
93
+ in_queue = set()
94
+
95
+ def preload(action: callable, log_key: str = None):
96
+ with lock:
97
+ if not LazyNestedCompleter.loop:
98
+ LazyNestedCompleter.loop = asyncio.new_event_loop()
99
+ LazyNestedCompleter.async_exec = ThreadPoolExecutor(max_workers=6, thread_name_prefix='async')
100
+ LazyNestedCompleter.loop.set_default_executor(LazyNestedCompleter.async_exec)
101
+
102
+ # some lib does not handle asyncio loop properly, as sync exec submit does not work, use another async loop
103
+ async def a():
104
+ try:
105
+ arg_needed = len(action.__code__.co_varnames)
106
+
107
+ if log_key:
108
+ with log_timing(log_key):
109
+ r = action(None) if arg_needed else action()
110
+ else:
111
+ r = action(None) if arg_needed else action()
112
+ if inspect.isawaitable(r):
113
+ await r
114
+
115
+ in_queue.remove(log_key)
116
+ except Exception as e:
117
+ log2('preloading error', e, inspect.getsourcelines(action)[0][0])
118
+ traceback.print_exc()
119
+
120
+ if log_key not in in_queue:
121
+ in_queue.add(log_key)
122
+ LazyNestedCompleter.async_exec.submit(lambda: LazyNestedCompleter.loop.run_until_complete(a()))
123
+
124
+ class LazyNestedCompleter(NestedCompleter):
125
+ loop: asyncio.AbstractEventLoop = None
126
+ async_exec: ThreadPoolExecutor = None
127
+
128
+ def __init__(self, name: str, options_lambda: callable, ignore_case: bool = True, auto: str = 'lazy') -> None:
129
+ super().__init__(None, ignore_case)
130
+ self.options_lambda = options_lambda
131
+ if auto == 'lazy':
132
+ preload(options_lambda, log_key=name)
133
+
134
+ def __repr__(self) -> str:
135
+ return "LazyNestedCompleter(%r, ignore_case=%r)" % (self.options, self.ignore_case)
136
+
137
+ def get_completions(
138
+ self, document: Document, complete_event: CompleteEvent
139
+ ) -> Iterable[Completion]:
140
+ if not self.options:
141
+ self.options = self.options_lambda()
142
+
143
+ # Split document.
144
+ text = document.text_before_cursor.lstrip()
145
+ stripped_len = len(document.text_before_cursor) - len(text)
146
+
147
+ # If there is a space, check for the first term, and use a
148
+ # subcompleter.
149
+ if " " in text:
150
+ first_term = text.split()[0]
151
+ completer = self.options.get(first_term)
152
+
153
+ # If we have a sub completer, use this for the completions.
154
+ if completer is not None:
155
+ remaining_text = text[len(first_term) :].lstrip()
156
+ move_cursor = len(text) - len(remaining_text) + stripped_len
157
+
158
+ new_document = Document(
159
+ remaining_text,
160
+ cursor_position=document.cursor_position - move_cursor,
161
+ )
162
+
163
+ for c in completer.get_completions(new_document, complete_event):
164
+ yield c
165
+
166
+ # No space in the input: behave exactly like `WordCompleter`.
167
+ else:
168
+ completer = WordCompleter(
169
+ list(self.options.keys()), ignore_case=self.ignore_case
170
+ )
171
+ for c in completer.get_completions(document, complete_event):
172
+ yield c
@@ -0,0 +1,173 @@
1
+ from abc import abstractmethod
2
+ from typing import Generic, TypeVar
3
+
4
+ from adam.utils import log_exc
5
+
6
+ __all__ = [
7
+ 'State',
8
+ 'StateMachine',
9
+ ]
10
+
11
+ T = TypeVar('T')
12
+
13
+ class State:
14
+ def __init__(self, state: str, comeback_token: str = None, comeback_state: str = None):
15
+ self.state = state
16
+ self.comeback_token = comeback_token
17
+ self.comeback_state = comeback_state
18
+ self.context: dict[str, str] = {}
19
+
20
+ def __str__(self):
21
+ return f'{self.state if self.state else None} comeback[{self.comeback_token} {self.comeback_state}]'
22
+
23
+ class StateMachine(Generic[T]):
24
+ @abstractmethod
25
+ def spec(self) -> str:
26
+ return None
27
+
28
+ @abstractmethod
29
+ def keywords(self) -> list[str]:
30
+ return None
31
+
32
+ def expandable_names(self):
33
+ return []
34
+
35
+ def incomplete_name_transition_condition(self, from_s: str, token: str, to_s: str, suggestions: str) -> list[str]:
36
+ if not suggestions:
37
+ return None
38
+
39
+ tokens = [token]
40
+ if '|' in token:
41
+ tokens = token.split('|')
42
+
43
+ if 'name' not in tokens:
44
+ return None
45
+
46
+ return tokens
47
+
48
+ def __init__(self, indent=0, push_level = 0, debug = False):
49
+ self.states: dict[str, State] = {}
50
+ self.suggestions: dict[str, str] = {}
51
+
52
+ self.indent = indent
53
+ self.push_level = push_level
54
+ self.comebacks: dict[int, str] = {}
55
+ self.debug = debug
56
+
57
+ from_ss_to_add = []
58
+ from_ss = ['']
59
+ words: str = None
60
+ for l in self.spec():
61
+ t_and_w = l.split('^')
62
+ if len(t_and_w) > 1:
63
+ words = t_and_w[1].strip()
64
+ else:
65
+ words = None
66
+
67
+ tks = t_and_w[0].strip(' ').split('>')
68
+ if not l.startswith('-'):
69
+ if words:
70
+ self.suggestions[tks[0].strip(' ')] = words
71
+
72
+ if len(tks) == 1:
73
+ from_ss_to_add.append(tks[0].strip(' '))
74
+ continue
75
+
76
+ from_ss = []
77
+ from_ss.extend(from_ss_to_add)
78
+ from_ss_to_add = []
79
+ from_ss.append(tks[0].strip(' '))
80
+
81
+ self.add_transitions(from_ss, tks, words)
82
+
83
+ def add_transitions(self, from_ss: list[str], tks: list[str], words: str):
84
+ token = tks[1].strip(' ')
85
+ if len(tks) > 2:
86
+ to_s = tks[2].strip(' ')
87
+ for from_s in from_ss:
88
+ self.add_whitespace_transition(from_s, to_s)
89
+ self.add_transition(from_s, token, to_s)
90
+ self.add_incomplete_name_transition(from_s, token, to_s, words)
91
+ elif '<' in tks[0]:
92
+ from_and_token = tks[0].split('<')
93
+ if len(from_and_token) > 1:
94
+ for from_s in from_ss:
95
+ self.add_comeback_transition(from_s, from_and_token[1], tks[1].strip(' '))
96
+
97
+ def add_whitespace_transition(self, from_s: str, to_s: str):
98
+ if self.witespace_transition_condition(from_s, to_s):
99
+ if self.debug:
100
+ print(f'{from_s[:-1]} > _ = {to_s}')
101
+ self.states[f'{from_s[:-1]} > _'] = State(from_s)
102
+
103
+ def witespace_transition_condition(self, from_s: str, to_s: str):
104
+ return from_s.endswith('_')
105
+
106
+ def add_incomplete_name_transition(self, from_s: str, token: str, to_s: str, words: str):
107
+ if tokens := self.incomplete_name_transition_condition(from_s, token, to_s, words):
108
+ self.suggestions[to_s] = words
109
+ for token in tokens:
110
+ if self.debug:
111
+ print(f'{to_s} > {token} = {to_s}')
112
+ self.states[f'{to_s} > {token}'] = State(to_s)
113
+
114
+ def add_transition(self, from_s: str, token: str, to_s: str):
115
+ tokens = [token]
116
+ if '|' in token:
117
+ tokens = token.split('|')
118
+
119
+ for t in tokens:
120
+ if t == '_or_':
121
+ t = '||'
122
+ elif t == 'pipe':
123
+ t = '|'
124
+ elif t == '_rdr0_':
125
+ t = '<'
126
+ elif t == '_rdr1_':
127
+ t = '>'
128
+ elif t == '_rdr2_':
129
+ t = '2>'
130
+
131
+ if self.debug:
132
+ print(f'{from_s} > {t} = {to_s}')
133
+ self.states[f'{from_s} > {t}'] = State(to_s)
134
+
135
+ def add_comeback_transition(self, from_s: str, token: str, to_s: str):
136
+ key = f'{from_s} > ('
137
+ orig = self.states[key]
138
+ if not orig:
139
+ raise Exception(f'from state not found for {key}')
140
+
141
+ orig.comeback_token = token
142
+ orig.comeback_state = to_s
143
+ if self.debug:
144
+ print(f'{from_s} > ) = {to_s}')
145
+ self.states[key] = orig
146
+
147
+ def traverse_tokens(self, tokens: list[str], state: State = State('')):
148
+ for token in tokens[:-1]:
149
+ if not token:
150
+ continue
151
+
152
+ if self.debug:
153
+ print(f'{token} ', end='')
154
+
155
+ last_name = None
156
+
157
+ if (t := token.lower()) in self.keywords():
158
+ token = t
159
+ elif token in ['*', ',', '.']:
160
+ pass
161
+ else:
162
+ last_name = token
163
+ token = 'word'
164
+
165
+ with log_exc():
166
+ context = state.context
167
+ state = self.states[f'{state.state} > {token}']
168
+ state.context = context
169
+
170
+ if last_name:
171
+ state.context['last_name'] = last_name
172
+
173
+ return state
adam/utils_sqlite.py ADDED
@@ -0,0 +1,137 @@
1
+ from collections.abc import Callable
2
+
3
+ import functools
4
+ import glob
5
+ import os
6
+ import sqlite3
7
+ import pandas
8
+
9
+ from adam.config import Config
10
+ from adam.utils import tabulize, log, wait_log
11
+
12
+ class CursorHandler:
13
+ def __init__(self, conn: sqlite3.Connection):
14
+ self.conn = conn
15
+ self.cursor = None
16
+
17
+ def __enter__(self):
18
+ self.cursor = self.conn.cursor()
19
+
20
+ return self.cursor
21
+
22
+ def __exit__(self, exc_type, exc_val, exc_tb):
23
+ if self.cursor:
24
+ self.cursor.close()
25
+
26
+ return False
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
+
50
+ # no state utility class
51
+ class SQLite:
52
+ def cursor(conn: sqlite3.Connection):
53
+ return CursorHandler(conn)
54
+
55
+ def local_db_dir():
56
+ return Config().get('export.sqlite.local-db-dir', '/tmp/qing-db')
57
+
58
+ def keyspace(database: str):
59
+ return '_'.join(database.replace(".db", "").split('_')[1:])
60
+
61
+ @functools.lru_cache()
62
+ def database_names(prefix: str = None):
63
+ wait_log('Inspecting export databases...')
64
+
65
+ pattern = f'{SQLite.local_db_dir()}/s*.db'
66
+ if prefix:
67
+ pattern = f'{SQLite.local_db_dir()}/{prefix}*'
68
+ return [os.path.basename(f) for f in glob.glob(pattern)]
69
+
70
+ def clear_cache(cache: str = None):
71
+ SQLite.database_names.cache_clear()
72
+ SQLite.table_names.cache_clear()
73
+
74
+ @functools.lru_cache()
75
+ def table_names(database: str):
76
+ tokens = database.replace('.db', '').split('_')
77
+ ts_prefix = tokens[0]
78
+ keyspace = '_'.join(tokens[1:])
79
+
80
+ conn = None
81
+ tables = []
82
+ try:
83
+ conn = sqlite3.connect(f'{SQLite.local_db_dir()}/{ts_prefix}_{keyspace}.db')
84
+ with SQLite.cursor(conn) as cursor:
85
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;")
86
+
87
+ tables = [row[0] for row in cursor.fetchall() if row[0] != "sqlite_sequence"]
88
+
89
+ return tables
90
+ except sqlite3.Error as e:
91
+ print(f"Error connecting to or querying the database: {e}")
92
+ return []
93
+ finally:
94
+ if conn:
95
+ conn.close()
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
+
112
+ @functools.lru_cache()
113
+ def column_names(tables: list[str] = [], database: str = None, function: str = 'audit', partition_cols_only = False):
114
+ pass
115
+
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
122
+
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)
130
+
131
+ return len(lines), log_file
132
+
133
+ 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')
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.14" #: the working version
4
+ __version__ = "2.0.189" #: 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.14
3
+ Version: 2.0.189
4
4
  Summary: UNKNOWN
5
5
  Home-page: UNKNOWN
6
6
  License: UNKNOWN