kaqing 2.0.110__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 (251) hide show
  1. adam/__init__.py +0 -2
  2. adam/app_session.py +9 -12
  3. adam/apps.py +18 -4
  4. adam/batch.py +19 -19
  5. adam/checks/check_utils.py +16 -46
  6. adam/checks/cpu.py +7 -1
  7. adam/checks/cpu_metrics.py +52 -0
  8. adam/checks/disk.py +2 -3
  9. adam/columns/columns.py +3 -1
  10. adam/columns/cpu.py +3 -1
  11. adam/columns/cpu_metrics.py +22 -0
  12. adam/columns/memory.py +3 -4
  13. adam/commands/__init__.py +24 -0
  14. adam/commands/app/app.py +38 -0
  15. adam/commands/{app_ping.py → app/app_ping.py} +7 -13
  16. adam/commands/{login.py → app/login.py} +22 -24
  17. adam/commands/app/show_app_actions.py +49 -0
  18. adam/commands/{show → app}/show_app_id.py +8 -11
  19. adam/commands/{show → app}/show_app_queues.py +7 -14
  20. adam/commands/app/show_login.py +56 -0
  21. adam/commands/app/utils_app.py +106 -0
  22. adam/commands/audit/audit.py +22 -40
  23. adam/commands/audit/audit_repair_tables.py +15 -19
  24. adam/commands/audit/audit_run.py +15 -22
  25. adam/commands/audit/completions_l.py +15 -0
  26. adam/commands/audit/show_last10.py +4 -18
  27. adam/commands/audit/show_slow10.py +4 -17
  28. adam/commands/audit/show_top10.py +4 -16
  29. adam/commands/audit/utils_show_top10.py +15 -3
  30. adam/commands/bash/__init__.py +5 -0
  31. adam/commands/bash/bash.py +36 -0
  32. adam/commands/bash/bash_completer.py +93 -0
  33. adam/commands/bash/utils_bash.py +16 -0
  34. adam/commands/cassandra/__init__.py +0 -0
  35. adam/commands/cassandra/download_cassandra_log.py +45 -0
  36. adam/commands/{restart.py → cassandra/restart_cluster.py} +12 -26
  37. adam/commands/cassandra/restart_node.py +51 -0
  38. adam/commands/cassandra/restart_nodes.py +47 -0
  39. adam/commands/{rollout.py → cassandra/rollout.py} +20 -25
  40. adam/commands/cassandra/show_cassandra_repairs.py +37 -0
  41. adam/commands/cassandra/show_cassandra_status.py +117 -0
  42. adam/commands/{show → cassandra}/show_cassandra_version.py +5 -18
  43. adam/commands/cassandra/show_processes.py +50 -0
  44. adam/commands/cassandra/show_storage.py +44 -0
  45. adam/commands/{watch.py → cassandra/watch.py} +26 -29
  46. adam/commands/cli/__init__.py +0 -0
  47. adam/commands/{cli_commands.py → cli/cli_commands.py} +8 -4
  48. adam/commands/cli/clipboard_copy.py +86 -0
  49. adam/commands/cli/show_cli_commands.py +56 -0
  50. adam/commands/code.py +57 -0
  51. adam/commands/command.py +211 -40
  52. adam/commands/commands_utils.py +20 -27
  53. adam/commands/config/__init__.py +0 -0
  54. adam/commands/{param_get.py → config/param_get.py} +11 -14
  55. adam/commands/{param_set.py → config/param_set.py} +8 -12
  56. adam/commands/{show → config}/show_params.py +2 -5
  57. adam/commands/cql/alter_tables.py +66 -0
  58. adam/commands/cql/completions_c.py +29 -0
  59. adam/commands/cql/cqlsh.py +10 -32
  60. adam/commands/cql/utils_cql.py +306 -0
  61. adam/commands/debug/__init__.py +0 -0
  62. adam/commands/debug/debug.py +22 -0
  63. adam/commands/debug/debug_completes.py +35 -0
  64. adam/commands/debug/debug_timings.py +35 -0
  65. adam/commands/debug/show_offloaded_completes.py +45 -0
  66. adam/commands/deploy/code_start.py +7 -10
  67. adam/commands/deploy/code_stop.py +4 -21
  68. adam/commands/deploy/code_utils.py +3 -3
  69. adam/commands/deploy/deploy.py +4 -27
  70. adam/commands/deploy/deploy_frontend.py +14 -17
  71. adam/commands/deploy/deploy_pg_agent.py +3 -6
  72. adam/commands/deploy/deploy_pod.py +65 -73
  73. adam/commands/deploy/deploy_utils.py +14 -24
  74. adam/commands/deploy/undeploy.py +4 -27
  75. adam/commands/deploy/undeploy_frontend.py +4 -7
  76. adam/commands/deploy/undeploy_pg_agent.py +6 -8
  77. adam/commands/deploy/undeploy_pod.py +11 -12
  78. adam/commands/devices/__init__.py +0 -0
  79. adam/commands/devices/device.py +149 -0
  80. adam/commands/devices/device_app.py +163 -0
  81. adam/commands/devices/device_auit_log.py +49 -0
  82. adam/commands/devices/device_cass.py +179 -0
  83. adam/commands/devices/device_export.py +87 -0
  84. adam/commands/devices/device_postgres.py +160 -0
  85. adam/commands/devices/devices.py +25 -0
  86. adam/commands/diag/__init__.py +0 -0
  87. adam/commands/{check.py → diag/check.py} +16 -25
  88. adam/commands/diag/generate_report.py +52 -0
  89. adam/commands/diag/issues.py +43 -0
  90. adam/commands/exit.py +1 -4
  91. adam/commands/export/__init__.py +0 -0
  92. adam/commands/export/clean_up_all_export_sessions.py +37 -0
  93. adam/commands/export/clean_up_export_sessions.py +39 -0
  94. adam/commands/export/completions_x.py +11 -0
  95. adam/commands/export/download_export_session.py +40 -0
  96. adam/commands/export/drop_export_database.py +39 -0
  97. adam/commands/export/drop_export_databases.py +37 -0
  98. adam/commands/export/export.py +37 -0
  99. adam/commands/export/export_databases.py +251 -0
  100. adam/commands/export/export_select.py +34 -0
  101. adam/commands/export/export_sessions.py +210 -0
  102. adam/commands/export/export_use.py +49 -0
  103. adam/commands/export/export_x_select.py +48 -0
  104. adam/commands/export/exporter.py +419 -0
  105. adam/commands/export/import_files.py +44 -0
  106. adam/commands/export/import_session.py +40 -0
  107. adam/commands/export/importer.py +81 -0
  108. adam/commands/export/importer_athena.py +157 -0
  109. adam/commands/export/importer_sqlite.py +78 -0
  110. adam/commands/export/show_column_counts.py +45 -0
  111. adam/commands/export/show_export_databases.py +39 -0
  112. adam/commands/export/show_export_session.py +39 -0
  113. adam/commands/export/show_export_sessions.py +37 -0
  114. adam/commands/export/utils_export.py +366 -0
  115. adam/commands/fs/__init__.py +0 -0
  116. adam/commands/fs/cat.py +36 -0
  117. adam/commands/fs/cat_local.py +42 -0
  118. adam/commands/fs/cd.py +41 -0
  119. adam/commands/fs/download_file.py +47 -0
  120. adam/commands/fs/find_files.py +51 -0
  121. adam/commands/fs/find_processes.py +76 -0
  122. adam/commands/fs/head.py +36 -0
  123. adam/commands/fs/ls.py +41 -0
  124. adam/commands/fs/ls_local.py +40 -0
  125. adam/commands/fs/pwd.py +45 -0
  126. adam/commands/fs/rm.py +18 -0
  127. adam/commands/fs/rm_downloads.py +39 -0
  128. adam/commands/fs/rm_logs.py +38 -0
  129. adam/commands/{shell.py → fs/shell.py} +12 -4
  130. adam/commands/{show → fs}/show_adam.py +3 -3
  131. adam/commands/{show → fs}/show_host.py +1 -1
  132. adam/commands/help.py +5 -3
  133. adam/commands/intermediate_command.py +52 -0
  134. adam/commands/kubectl.py +38 -0
  135. adam/commands/medusa/medusa.py +4 -22
  136. adam/commands/medusa/medusa_backup.py +20 -27
  137. adam/commands/medusa/medusa_restore.py +35 -48
  138. adam/commands/medusa/medusa_show_backupjobs.py +16 -18
  139. adam/commands/medusa/medusa_show_restorejobs.py +13 -18
  140. adam/commands/medusa/utils_medusa.py +15 -0
  141. adam/commands/nodetool/__init__.py +0 -0
  142. adam/commands/{nodetool.py → nodetool/nodetool.py} +9 -20
  143. adam/commands/postgres/completions_p.py +22 -0
  144. adam/commands/postgres/postgres.py +47 -55
  145. adam/commands/postgres/postgres_databases.py +269 -0
  146. adam/commands/postgres/postgres_ls.py +5 -9
  147. adam/commands/postgres/postgres_preview.py +5 -9
  148. adam/commands/postgres/utils_postgres.py +80 -0
  149. adam/commands/preview_table.py +8 -44
  150. adam/commands/reaper/reaper.py +4 -27
  151. adam/commands/reaper/reaper_forward.py +49 -56
  152. adam/commands/reaper/reaper_forward_session.py +6 -0
  153. adam/commands/reaper/reaper_forward_stop.py +10 -16
  154. adam/commands/reaper/reaper_restart.py +7 -14
  155. adam/commands/reaper/reaper_run_abort.py +8 -33
  156. adam/commands/reaper/reaper_runs.py +43 -58
  157. adam/commands/reaper/reaper_runs_abort.py +29 -49
  158. adam/commands/reaper/reaper_schedule_activate.py +14 -33
  159. adam/commands/reaper/reaper_schedule_start.py +9 -33
  160. adam/commands/reaper/reaper_schedule_stop.py +9 -33
  161. adam/commands/reaper/reaper_schedules.py +4 -14
  162. adam/commands/reaper/reaper_status.py +8 -16
  163. adam/commands/reaper/utils_reaper.py +203 -0
  164. adam/commands/repair/repair.py +4 -22
  165. adam/commands/repair/repair_log.py +5 -11
  166. adam/commands/repair/repair_run.py +27 -34
  167. adam/commands/repair/repair_scan.py +32 -40
  168. adam/commands/repair/repair_stop.py +5 -12
  169. adam/commands/show.py +40 -0
  170. adam/config.py +5 -15
  171. adam/embedded_params.py +1 -1
  172. adam/log.py +4 -4
  173. adam/repl.py +83 -116
  174. adam/repl_commands.py +86 -45
  175. adam/repl_session.py +9 -1
  176. adam/repl_state.py +176 -40
  177. adam/sql/async_executor.py +62 -0
  178. adam/sql/lark_completer.py +286 -0
  179. adam/sql/lark_parser.py +604 -0
  180. adam/sql/qingl.lark +1076 -0
  181. adam/sql/sql_completer.py +52 -27
  182. adam/sql/sql_state_machine.py +131 -19
  183. adam/sso/authn_ad.py +6 -8
  184. adam/sso/authn_okta.py +4 -6
  185. adam/sso/cred_cache.py +4 -9
  186. adam/sso/idp.py +9 -12
  187. adam/utils.py +670 -31
  188. adam/utils_athena.py +145 -0
  189. adam/utils_audits.py +12 -103
  190. adam/utils_issues.py +32 -0
  191. adam/utils_k8s/app_clusters.py +35 -0
  192. adam/utils_k8s/app_pods.py +41 -0
  193. adam/utils_k8s/cassandra_clusters.py +35 -20
  194. adam/utils_k8s/cassandra_nodes.py +15 -6
  195. adam/utils_k8s/custom_resources.py +16 -17
  196. adam/utils_k8s/ingresses.py +2 -2
  197. adam/utils_k8s/jobs.py +7 -11
  198. adam/utils_k8s/k8s.py +96 -0
  199. adam/utils_k8s/kube_context.py +3 -6
  200. adam/{pod_exec_result.py → utils_k8s/pod_exec_result.py} +13 -4
  201. adam/utils_k8s/pods.py +159 -89
  202. adam/utils_k8s/secrets.py +4 -4
  203. adam/utils_k8s/service_accounts.py +5 -4
  204. adam/utils_k8s/services.py +2 -2
  205. adam/utils_k8s/statefulsets.py +6 -14
  206. adam/utils_local.py +80 -0
  207. adam/utils_net.py +4 -4
  208. adam/utils_repl/__init__.py +0 -0
  209. adam/utils_repl/appendable_completer.py +6 -0
  210. adam/utils_repl/automata_completer.py +48 -0
  211. adam/utils_repl/repl_completer.py +93 -0
  212. adam/utils_repl/state_machine.py +173 -0
  213. adam/utils_sqlite.py +132 -0
  214. adam/version.py +1 -1
  215. {kaqing-2.0.110.dist-info → kaqing-2.0.214.dist-info}/METADATA +1 -1
  216. kaqing-2.0.214.dist-info/RECORD +272 -0
  217. kaqing-2.0.214.dist-info/top_level.txt +2 -0
  218. teddy/__init__.py +0 -0
  219. teddy/lark_parser.py +436 -0
  220. teddy/lark_parser2.py +618 -0
  221. adam/commands/alter_tables.py +0 -81
  222. adam/commands/app.py +0 -67
  223. adam/commands/bash.py +0 -150
  224. adam/commands/cd.py +0 -125
  225. adam/commands/cp.py +0 -95
  226. adam/commands/cql/cql_completions.py +0 -15
  227. adam/commands/cql/cql_utils.py +0 -112
  228. adam/commands/devices.py +0 -118
  229. adam/commands/issues.py +0 -75
  230. adam/commands/logs.py +0 -40
  231. adam/commands/ls.py +0 -146
  232. adam/commands/postgres/postgres_context.py +0 -239
  233. adam/commands/postgres/postgres_utils.py +0 -31
  234. adam/commands/postgres/psql_completions.py +0 -10
  235. adam/commands/pwd.py +0 -77
  236. adam/commands/reaper/reaper_session.py +0 -159
  237. adam/commands/report.py +0 -63
  238. adam/commands/show/show.py +0 -54
  239. adam/commands/show/show_app_actions.py +0 -56
  240. adam/commands/show/show_cassandra_status.py +0 -128
  241. adam/commands/show/show_commands.py +0 -61
  242. adam/commands/show/show_login.py +0 -63
  243. adam/commands/show/show_processes.py +0 -53
  244. adam/commands/show/show_repairs.py +0 -47
  245. adam/commands/show/show_storage.py +0 -52
  246. kaqing-2.0.110.dist-info/RECORD +0 -187
  247. kaqing-2.0.110.dist-info/top_level.txt +0 -1
  248. /adam/commands/{show → app}/__init__.py +0 -0
  249. /adam/commands/{nodetool_commands.py → nodetool/nodetool_commands.py} +0 -0
  250. {kaqing-2.0.110.dist-info → kaqing-2.0.214.dist-info}/WHEEL +0 -0
  251. {kaqing-2.0.110.dist-info → kaqing-2.0.214.dist-info}/entry_points.txt +0 -0
adam/utils_local.py ADDED
@@ -0,0 +1,80 @@
1
+ import os
2
+ import subprocess
3
+ import traceback
4
+
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()
79
+
80
+ return log_files
adam/utils_net.py CHANGED
@@ -18,7 +18,7 @@ def get_my_host():
18
18
  return MY_HOST
19
19
 
20
20
  def get_ip_from_hostname(hostname):
21
- try:
22
- return socket.gethostbyname(hostname)
23
- except socket.gaierror:
24
- return None
21
+ try:
22
+ return socket.gethostbyname(hostname)
23
+ except socket.gaierror:
24
+ return None
File without changes
@@ -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
@@ -0,0 +1,48 @@
1
+ from typing import Generic, Iterable, TypeVar
2
+ from prompt_toolkit.completion import CompleteEvent, Completer, Completion, WordCompleter
3
+ from prompt_toolkit.document import Document
4
+
5
+ from adam.utils_repl.state_machine import StateMachine, State
6
+
7
+ __all__ = [
8
+ "AutomataCompleter",
9
+ ]
10
+
11
+ T = TypeVar('T')
12
+
13
+ class AutomataCompleter(Completer, Generic[T]):
14
+ def __init__(self,
15
+ state_machine: StateMachine,
16
+ first_term: str = '',
17
+ debug = False):
18
+ super().__init__()
19
+ self.machine = state_machine
20
+ self.first_term = first_term
21
+ self.debug = debug
22
+
23
+ def get_completions(
24
+ self, document: Document, complete_event: CompleteEvent
25
+ ) -> Iterable[Completion]:
26
+ text = document.text_before_cursor.lstrip()
27
+ state = ''
28
+ if self.first_term:
29
+ text = f'{self.first_term} {text}'
30
+
31
+ completer: Completer = None
32
+ state: State = self.machine.traverse_tokens(self.tokens(text), State(state))
33
+ if self.debug:
34
+ print('\n =>', state.state if isinstance(state, State) else '')
35
+
36
+ if state.state in self.machine.suggestions:
37
+ if completer := self.suggestions_completer(state, self.machine.suggestions[state.state].strip(' ')):
38
+ for c in completer.get_completions(document, complete_event):
39
+ yield c
40
+
41
+ def tokens(self, text: str) -> list[T]:
42
+ return text.split(' ')
43
+
44
+ def suggestions_completer(self, _: State, suggestions: str) -> list[str]:
45
+ if not suggestions:
46
+ return None
47
+
48
+ return WordCompleter(suggestions.split(','))
@@ -0,0 +1,93 @@
1
+ import copy
2
+ import re
3
+ import traceback
4
+ from typing import Iterable, TypeVar, cast
5
+ from prompt_toolkit.completion import CompleteEvent, Completion, NestedCompleter, WordCompleter
6
+ from prompt_toolkit.document import Document
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
+
16
+ __all__ = [
17
+ "ReplCompleter",
18
+ ]
19
+
20
+ T = TypeVar('T')
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
+
55
+ class ReplCompleter(NestedCompleter):
56
+ def get_completions(
57
+ self, document: Document, complete_event: CompleteEvent
58
+ ) -> Iterable[Completion]:
59
+ # Split document.
60
+ text = document.text_before_cursor.lstrip()
61
+ stripped_len = len(document.text_before_cursor) - len(text)
62
+
63
+ # If there is a space, check for the first term, and use a
64
+ # subcompleter.
65
+ if " " in text:
66
+ first_term = text.split()[0]
67
+ completer = self.options.get(first_term)
68
+
69
+ # If we have a sub completer, use this for the completions.
70
+ if completer is not None:
71
+ remaining_text = text[len(first_term) :].lstrip()
72
+ move_cursor = len(text) - len(remaining_text) + stripped_len
73
+
74
+ new_document = Document(
75
+ remaining_text,
76
+ cursor_position=document.cursor_position - move_cursor,
77
+ )
78
+
79
+ try:
80
+ # potential thread racing
81
+ for c in completer.get_completions(new_document, complete_event):
82
+ yield c
83
+ except:
84
+ pass
85
+
86
+ # No space in the input: behave exactly like `WordCompleter`.
87
+ else:
88
+ completer = WordCompleter(
89
+ # Allow dot in the middle or a word
90
+ list(self.options.keys()), ignore_case=self.ignore_case, pattern=re.compile(r"([a-zA-Z0-9_\.\@\&]+|[^a-zA-Z0-9_\.\@\&\s]+)")
91
+ )
92
+ for c in completer.get_completions(document, complete_event):
93
+ 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,132 @@
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 creating_dir, 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 creating_dir(Config().get('export.sqlite.local-db-dir', '/tmp/qing-db/q/export/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
+ if keyspace:
99
+ return sqlite3.connect(f'{SQLite.local_db_dir()}/{database}_{keyspace}.db')
100
+ else:
101
+ conn = sqlite3.connect(f'{SQLite.local_db_dir()}/{database}_root.db')
102
+ with SQLite.cursor(conn) as cursor:
103
+ for d in SQLite.database_names(database):
104
+ if d != f'{database}.db':
105
+ q = f"ATTACH DATABASE '{SQLite.local_db_dir()}/{d}' AS {SQLite.keyspace(d)}"
106
+ cursor.execute(q)
107
+
108
+ return conn
109
+
110
+ @functools.lru_cache()
111
+ def column_names(tables: list[str] = [], database: str = None, function: str = 'audit', partition_cols_only = False):
112
+ pass
113
+
114
+ def run_query(query: str, database: str = None, output: Callable[[str], str] = None) -> tuple[int, str]:
115
+ with sqlite(database) as conn:
116
+ return SQLite.run_query_with_conn(conn, query, output=output)
117
+
118
+ def run_query_with_conn(conn, query: str, output: Callable[[str], str] = None) -> tuple[int, str]:
119
+ log_file = None
120
+
121
+ df = SQLite.query(conn, query)
122
+ lines = ['\t'.join(map(str, line)) for line in df.values.tolist()]
123
+ out = tabulize(lines, header='\t'.join(df.columns.tolist()), separator='\t', to=0)
124
+ if output:
125
+ log_file = output(out)
126
+ else:
127
+ log(out)
128
+
129
+ return len(lines), log_file
130
+
131
+ def query(conn, sql: str) -> tuple[str, str, list]:
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.110" #: 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.110
3
+ Version: 2.0.214
4
4
  Summary: UNKNOWN
5
5
  Home-page: UNKNOWN
6
6
  License: UNKNOWN