kaqing 2.0.14__py3-none-any.whl → 2.0.145__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 (163) hide show
  1. adam/apps.py +2 -2
  2. adam/batch.py +13 -3
  3. adam/checks/check_utils.py +4 -4
  4. adam/checks/compactionstats.py +1 -1
  5. adam/checks/cpu.py +2 -2
  6. adam/checks/disk.py +1 -1
  7. adam/checks/gossip.py +1 -1
  8. adam/checks/memory.py +3 -3
  9. adam/checks/status.py +1 -1
  10. adam/commands/alter_tables.py +81 -0
  11. adam/commands/app.py +3 -3
  12. adam/commands/app_ping.py +2 -2
  13. adam/commands/audit/audit.py +86 -0
  14. adam/commands/audit/audit_repair_tables.py +77 -0
  15. adam/commands/audit/audit_run.py +58 -0
  16. adam/commands/audit/show_last10.py +51 -0
  17. adam/commands/audit/show_slow10.py +50 -0
  18. adam/commands/audit/show_top10.py +48 -0
  19. adam/commands/audit/utils_show_top10.py +59 -0
  20. adam/commands/bash/__init__.py +0 -0
  21. adam/commands/bash/bash.py +133 -0
  22. adam/commands/bash/bash_completer.py +93 -0
  23. adam/commands/cat.py +56 -0
  24. adam/commands/cd.py +12 -82
  25. adam/commands/check.py +6 -0
  26. adam/commands/cli_commands.py +3 -3
  27. adam/commands/code.py +60 -0
  28. adam/commands/command.py +48 -12
  29. adam/commands/commands_utils.py +4 -5
  30. adam/commands/cql/__init__.py +0 -0
  31. adam/commands/cql/cql_completions.py +28 -0
  32. adam/commands/cql/cql_utils.py +209 -0
  33. adam/commands/{cqlsh.py → cql/cqlsh.py} +15 -10
  34. adam/commands/deploy/code_utils.py +2 -2
  35. adam/commands/deploy/deploy.py +8 -21
  36. adam/commands/deploy/deploy_frontend.py +1 -1
  37. adam/commands/deploy/deploy_pg_agent.py +3 -3
  38. adam/commands/deploy/deploy_pod.py +28 -27
  39. adam/commands/deploy/deploy_utils.py +16 -26
  40. adam/commands/deploy/undeploy.py +8 -21
  41. adam/commands/deploy/undeploy_frontend.py +1 -1
  42. adam/commands/deploy/undeploy_pg_agent.py +5 -3
  43. adam/commands/deploy/undeploy_pod.py +12 -10
  44. adam/commands/devices/__init__.py +0 -0
  45. adam/commands/devices/device.py +27 -0
  46. adam/commands/devices/device_app.py +146 -0
  47. adam/commands/devices/device_auit_log.py +43 -0
  48. adam/commands/devices/device_cass.py +145 -0
  49. adam/commands/devices/device_export.py +86 -0
  50. adam/commands/devices/device_postgres.py +109 -0
  51. adam/commands/devices/devices.py +25 -0
  52. adam/commands/export/__init__.py +0 -0
  53. adam/commands/export/clean_up_export_session.py +53 -0
  54. adam/commands/export/clean_up_export_sessions.py +40 -0
  55. adam/commands/export/drop_export_database.py +58 -0
  56. adam/commands/export/drop_export_databases.py +46 -0
  57. adam/commands/export/export.py +83 -0
  58. adam/commands/export/export_databases.py +170 -0
  59. adam/commands/export/export_select.py +85 -0
  60. adam/commands/export/export_select_x.py +54 -0
  61. adam/commands/export/export_use.py +55 -0
  62. adam/commands/export/exporter.py +364 -0
  63. adam/commands/export/import_session.py +68 -0
  64. adam/commands/export/importer.py +67 -0
  65. adam/commands/export/importer_athena.py +80 -0
  66. adam/commands/export/importer_sqlite.py +47 -0
  67. adam/commands/export/show_column_counts.py +63 -0
  68. adam/commands/export/show_export_databases.py +39 -0
  69. adam/commands/export/show_export_session.py +51 -0
  70. adam/commands/export/show_export_sessions.py +47 -0
  71. adam/commands/export/utils_export.py +291 -0
  72. adam/commands/help.py +12 -7
  73. adam/commands/issues.py +6 -0
  74. adam/commands/kubectl.py +41 -0
  75. adam/commands/login.py +7 -4
  76. adam/commands/logs.py +2 -1
  77. adam/commands/ls.py +4 -107
  78. adam/commands/medusa/medusa.py +2 -26
  79. adam/commands/medusa/medusa_backup.py +2 -2
  80. adam/commands/medusa/medusa_restore.py +3 -4
  81. adam/commands/medusa/medusa_show_backupjobs.py +4 -3
  82. adam/commands/medusa/medusa_show_restorejobs.py +3 -3
  83. adam/commands/nodetool.py +9 -4
  84. adam/commands/param_set.py +1 -1
  85. adam/commands/postgres/postgres.py +42 -43
  86. adam/commands/postgres/{postgres_session.py → postgres_context.py} +43 -42
  87. adam/commands/postgres/postgres_utils.py +31 -0
  88. adam/commands/postgres/psql_completions.py +10 -0
  89. adam/commands/preview_table.py +18 -40
  90. adam/commands/pwd.py +2 -28
  91. adam/commands/reaper/reaper.py +4 -24
  92. adam/commands/reaper/reaper_restart.py +1 -1
  93. adam/commands/reaper/reaper_session.py +2 -2
  94. adam/commands/repair/repair.py +3 -27
  95. adam/commands/repair/repair_log.py +1 -1
  96. adam/commands/repair/repair_run.py +2 -2
  97. adam/commands/repair/repair_scan.py +1 -1
  98. adam/commands/repair/repair_stop.py +1 -1
  99. adam/commands/report.py +6 -0
  100. adam/commands/restart.py +2 -2
  101. adam/commands/rollout.py +1 -1
  102. adam/commands/show/show.py +11 -26
  103. adam/commands/show/show_app_actions.py +3 -0
  104. adam/commands/show/show_app_id.py +1 -1
  105. adam/commands/show/show_app_queues.py +3 -2
  106. adam/commands/show/show_cassandra_status.py +3 -3
  107. adam/commands/show/show_cassandra_version.py +3 -3
  108. adam/commands/show/show_host.py +33 -0
  109. adam/commands/show/show_login.py +3 -0
  110. adam/commands/show/show_processes.py +1 -1
  111. adam/commands/show/show_repairs.py +2 -2
  112. adam/commands/show/show_storage.py +1 -1
  113. adam/commands/watch.py +1 -1
  114. adam/config.py +16 -3
  115. adam/embedded_params.py +1 -1
  116. adam/pod_exec_result.py +10 -2
  117. adam/repl.py +127 -117
  118. adam/repl_commands.py +51 -16
  119. adam/repl_state.py +276 -55
  120. adam/sql/__init__.py +0 -0
  121. adam/sql/sql_completer.py +120 -0
  122. adam/sql/sql_state_machine.py +617 -0
  123. adam/sql/term_completer.py +76 -0
  124. adam/sso/authn_ad.py +1 -1
  125. adam/sso/cred_cache.py +1 -1
  126. adam/sso/idp.py +1 -1
  127. adam/utils.py +83 -2
  128. adam/utils_athena.py +145 -0
  129. adam/utils_audits.py +102 -0
  130. adam/utils_k8s/__init__.py +0 -0
  131. adam/utils_k8s/app_clusters.py +33 -0
  132. adam/utils_k8s/app_pods.py +31 -0
  133. adam/{k8s_utils → utils_k8s}/cassandra_clusters.py +6 -21
  134. adam/{k8s_utils → utils_k8s}/cassandra_nodes.py +12 -5
  135. adam/{k8s_utils → utils_k8s}/deployment.py +2 -2
  136. adam/{k8s_utils → utils_k8s}/kube_context.py +1 -1
  137. adam/{k8s_utils → utils_k8s}/pods.py +119 -26
  138. adam/{k8s_utils → utils_k8s}/secrets.py +4 -0
  139. adam/{k8s_utils → utils_k8s}/statefulsets.py +5 -4
  140. adam/utils_net.py +24 -0
  141. adam/utils_repl/__init__.py +0 -0
  142. adam/utils_repl/automata_completer.py +48 -0
  143. adam/utils_repl/repl_completer.py +46 -0
  144. adam/utils_repl/state_machine.py +173 -0
  145. adam/utils_sqlite.py +101 -0
  146. adam/version.py +1 -1
  147. {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/METADATA +1 -1
  148. kaqing-2.0.145.dist-info/RECORD +227 -0
  149. adam/commands/bash.py +0 -87
  150. adam/commands/cql_utils.py +0 -53
  151. adam/commands/devices.py +0 -89
  152. kaqing-2.0.14.dist-info/RECORD +0 -167
  153. /adam/{k8s_utils → commands/audit}/__init__.py +0 -0
  154. /adam/{k8s_utils → utils_k8s}/config_maps.py +0 -0
  155. /adam/{k8s_utils → utils_k8s}/custom_resources.py +0 -0
  156. /adam/{k8s_utils → utils_k8s}/ingresses.py +0 -0
  157. /adam/{k8s_utils → utils_k8s}/jobs.py +0 -0
  158. /adam/{k8s_utils → utils_k8s}/service_accounts.py +0 -0
  159. /adam/{k8s_utils → utils_k8s}/services.py +0 -0
  160. /adam/{k8s_utils → utils_k8s}/volumes.py +0 -0
  161. {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/WHEEL +0 -0
  162. {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/entry_points.txt +0 -0
  163. {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/top_level.txt +0 -0
adam/repl_state.py CHANGED
@@ -1,11 +1,15 @@
1
- import copy
1
+ from copy import copy
2
2
  from enum import Enum
3
-
4
- from adam.commands.postgres.postgres_session import PostgresSession
5
- from adam.k8s_utils.cassandra_clusters import CassandraClusters
6
- from adam.k8s_utils.cassandra_nodes import CassandraNodes
7
- from adam.k8s_utils.kube_context import KubeContext
8
- from adam.k8s_utils.secrets import Secrets
3
+ import re
4
+ from typing import Callable
5
+
6
+ from adam.commands.postgres.postgres_context import PostgresContext
7
+ from adam.utils_k8s.app_clusters import AppClusters
8
+ from adam.utils_k8s.app_pods import AppPods
9
+ from adam.utils_k8s.cassandra_clusters import CassandraClusters
10
+ from adam.utils_k8s.cassandra_nodes import CassandraNodes
11
+ from adam.utils_k8s.kube_context import KubeContext
12
+ from adam.utils_k8s.secrets import Secrets
9
13
  from adam.utils import display_help, log2, random_alphanumeric
10
14
 
11
15
  class BashSession:
@@ -16,7 +20,13 @@ class BashSession:
16
20
  def pwd(self, state: 'ReplState'):
17
21
  command = f'cat /tmp/.qing-{self.session_id}'
18
22
 
19
- if state.pod:
23
+ if state.device == ReplState.A and state.app_app:
24
+ if state.app_pod:
25
+ rs = [AppPods.exec(state.app_pod, state.namespace, command, show_out=False)]
26
+ else:
27
+ pods = AppPods.pod_names(state.namespace, state.app_env, state.app_pod)
28
+ rs = AppClusters.exec(pods, state.namespace, command, show_out=False)
29
+ elif state.pod:
20
30
  rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=False)]
21
31
  elif state.sts:
22
32
  rs = CassandraClusters.exec(state.sts, state.namespace, command, action='bash', show_out=False)
@@ -43,16 +53,22 @@ class RequiredState(Enum):
43
53
  NAMESPACE = 'namespace'
44
54
  PG_DATABASE = 'pg_database'
45
55
  APP_APP = 'app_app'
56
+ EXPORT_DB = 'export_db'
46
57
 
47
58
  class ReplState:
48
59
  A = 'a'
49
60
  C = 'c'
61
+ L = 'l'
50
62
  P = 'p'
63
+ X = 'x'
64
+
65
+ ANY = [A, C, L, P, X]
66
+ NON_L = [A, C, P, X]
51
67
 
52
68
  def __init__(self, device: str = None,
53
69
  sts: str = None, pod: str = None, namespace: str = None, ns_sts: str = None,
54
70
  pg_path: str = None,
55
- app_env: str = None, app_app: str = None,
71
+ app_env: str = None, app_app: str = None, app_pod: str = None,
56
72
  in_repl = False, bash_session: BashSession = None, remote_dir = None):
57
73
  self.namespace = KubeContext.in_cluster_namespace()
58
74
 
@@ -62,12 +78,15 @@ class ReplState:
62
78
  self.pg_path = pg_path
63
79
  self.app_env = app_env
64
80
  self.app_app = app_app
81
+ self.app_pod = app_pod
65
82
  if namespace:
66
83
  self.namespace = namespace
67
84
  self.in_repl = in_repl
68
85
  self.bash_session = bash_session
69
86
  self.remote_dir = remote_dir
70
- self.wait_log_flag = False
87
+ self.original_state: ReplState = None
88
+
89
+ self.export_session: str = None
71
90
 
72
91
  if ns_sts:
73
92
  nn = ns_sts.split('@')
@@ -82,13 +101,51 @@ class ReplState:
82
101
  def __hash__(self):
83
102
  return hash((self.sts, self.pod))
84
103
 
85
- def apply_args(self, args: list[str], cmd: list[str] = None, resolve_pg = True) -> tuple['ReplState', list[str]]:
104
+ def __str__(self):
105
+ msg = ''
106
+ if self.device == ReplState.P:
107
+ msg = f'{ReplState.P}:'
108
+ pg: PostgresContext = PostgresContext.apply(self.namespace, self.pg_path) if self.pg_path else None
109
+ if pg and pg.db:
110
+ msg += pg.db
111
+ elif pg and pg.host:
112
+ msg += pg.host
113
+ elif self.device == ReplState.A:
114
+ msg = f'{ReplState.A}:'
115
+ if self.app_env:
116
+ msg += self.app_env
117
+ if self.app_app:
118
+ msg += f'/{self.app_app}'
119
+ if self.app_pod:
120
+ # azops88-c3-c3-k8sdeploy-appleader-001-79957cf5b6-9k4bw
121
+ group = re.match(r".*?-.*?-.*?-.*?-(.*?-.*?)-.*", self.app_pod)
122
+ msg += '/' + group[1]
123
+ elif self.device == ReplState.L:
124
+ msg = f'{ReplState.L}:'
125
+ elif self.device == ReplState.X:
126
+ msg = f'{ReplState.X}:'
127
+ if self.export_session:
128
+ msg += self.export_session
129
+ else:
130
+ msg = f'{ReplState.C}:'
131
+ if self.pod:
132
+ # cs-d0767a536f-cs-d0767a536f-default-sts-0
133
+ group = re.match(r".*?-.*?-(.*)", self.pod)
134
+ msg += group[1]
135
+ elif self.sts:
136
+ # cs-d0767a536f-cs-d0767a536f-default-sts
137
+ group = re.match(r".*?-.*?-(.*)", self.sts)
138
+ msg += group[1]
139
+
140
+ return msg
141
+
142
+ def apply_args(self, args: list[str], cmd: list[str] = None, resolve_pg = True, args_to_check = 6) -> tuple['ReplState', list[str]]:
86
143
  state = self
87
144
 
88
145
  new_args = []
89
146
  for index, arg in enumerate(args):
90
- if index < 6:
91
- state = copy.copy(state)
147
+ if index < args_to_check:
148
+ state = copy(state)
92
149
 
93
150
  s, n = KubeContext.is_sts_name(arg)
94
151
  if s:
@@ -120,92 +177,256 @@ class ReplState:
120
177
 
121
178
  return (state, new_args)
122
179
 
123
- def validate(self, required: RequiredState = None, pg_required: RequiredState = None, app_required: RequiredState = None):
124
- if not pg_required and not app_required:
125
- if required == RequiredState.CLUSTER:
126
- if not self.namespace or not self.sts:
180
+ def apply_device_arg(self, args: list[str], cmd: list[str] = None) -> tuple['ReplState', list[str]]:
181
+ state = self
182
+
183
+ new_args = []
184
+ for index, arg in enumerate(args):
185
+ if index < 6:
186
+ state = copy(state)
187
+
188
+ groups = re.match(r'^([a|c|l|p|x]):(.*)$', arg)
189
+ if groups:
190
+ if groups[1] == 'p':
191
+ state.device = 'p'
192
+ state.pg_path = groups[2]
193
+ elif groups[1] == 'c':
194
+ state.device = 'c'
195
+ if path := groups[2]:
196
+ p_and_ns = path.split('@')
197
+ sts_and_pod = p_and_ns[0].split('/')
198
+ state.sts = sts_and_pod[0]
199
+ if len(sts_and_pod) > 1:
200
+ state.pod = sts_and_pod[1]
201
+ if len(p_and_ns) > 1:
202
+ state.namespace = p_and_ns[1]
203
+ elif ns := KubeContext.in_cluster_namespace():
204
+ state.namespace = ns
205
+ elif groups[1] == 'l':
206
+ state.device = 'l'
207
+ elif groups[1] == 'x':
208
+ state.device = 'x'
209
+ else:
210
+ state.device = 'a'
211
+ if path := groups[2]:
212
+ env_and_app = path.split('/')
213
+ state.app_env = env_and_app[0]
214
+ if len(env_and_app) > 1:
215
+ state.app_app = env_and_app[1]
216
+ else:
217
+ new_args.append(arg)
218
+ else:
219
+ new_args.append(arg)
220
+
221
+ if cmd:
222
+ new_args = new_args[len(cmd):]
223
+
224
+ return (state, new_args)
225
+
226
+ def validate(self, required: list[RequiredState] = [], show_err = True):
227
+ if not required:
228
+ return True
229
+
230
+ def default_err():
231
+ if self.in_repl:
232
+ log2(f'Not a valid command on {self.device}: drive.')
233
+ else:
234
+ log2('* on a wrong device.')
235
+ log2()
236
+ display_help()
237
+
238
+ if type(required) is not list:
239
+ valid, err = self._validate(required)
240
+ if valid:
241
+ return True
242
+
243
+ if show_err:
244
+ if err:
245
+ err()
246
+ else:
247
+ default_err()
248
+
249
+ return False
250
+
251
+ devices = [r for r in required if r in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X]]
252
+ non_devices = [r for r in required if r not in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X]]
253
+
254
+ first_error: Callable = None
255
+ for r in non_devices:
256
+ valid, err = self._validate(r)
257
+ if valid:
258
+ return True
259
+
260
+ if not first_error:
261
+ first_error = err
262
+
263
+ if devices:
264
+ valid, err = self._validate_device(devices)
265
+ if valid:
266
+ return True
267
+
268
+ if not first_error:
269
+ first_error = err
270
+
271
+ if show_err and first_error:
272
+ if first_error:
273
+ first_error()
274
+ else:
275
+ default_err()
276
+
277
+ return False
278
+
279
+ def _validate(self, required: RequiredState):
280
+ if required == RequiredState.CLUSTER:
281
+ if self.device != ReplState.C:
282
+ return (False, None)
283
+
284
+ if not self.namespace or not self.sts:
285
+ def error():
127
286
  if self.in_repl:
128
287
  log2('cd to a Cassandra cluster first.')
129
288
  else:
130
289
  log2('* Cassandra cluster is missing.')
131
290
  log2()
132
291
  display_help()
292
+ return (False, error)
293
+
294
+ elif required == RequiredState.POD:
295
+ if self.device != ReplState.C:
296
+ return (False, None)
133
297
 
134
- return False
135
- elif required == RequiredState.POD:
136
- if not self.namespace or not self.pod:
298
+ if not self.namespace or not self.pod:
299
+ def error():
137
300
  if self.in_repl:
138
301
  log2('cd to a pod first.')
139
302
  else:
140
303
  log2('* Pod is missing.')
141
304
  log2()
142
305
  display_help()
306
+ return (False, error)
143
307
 
144
- return False
145
- elif required == RequiredState.CLUSTER_OR_POD:
146
- if not self.namespace or not self.sts and not self.pod:
308
+ elif required == RequiredState.CLUSTER_OR_POD:
309
+ if self.device != ReplState.C:
310
+ return (False, None)
311
+
312
+ if not self.namespace or not self.sts and not self.pod:
313
+ def error():
147
314
  if self.in_repl:
148
315
  log2('cd to a Cassandra cluster first.')
149
316
  else:
150
317
  log2('* Cassandra cluster or pod is missing.')
151
318
  log2()
152
319
  display_help()
320
+ return (False, error)
321
+
322
+ elif required == RequiredState.NAMESPACE:
323
+ if self.device != ReplState.C:
324
+ return (False, None)
153
325
 
154
- return False
155
- elif required == RequiredState.NAMESPACE:
156
- if not self.namespace:
326
+ if not self.namespace:
327
+ def error():
157
328
  if self.in_repl:
158
329
  log2('Namespace is required.')
159
330
  else:
160
331
  log2('* namespace is missing.')
161
332
  log2()
162
333
  display_help()
334
+ return (False, error)
163
335
 
164
- return False
336
+ elif required == RequiredState.PG_DATABASE:
337
+ if self.device != ReplState.P:
338
+ return (False, None)
165
339
 
166
- if pg_required == RequiredState.PG_DATABASE:
167
- pg = PostgresSession(self.namespace, self.pg_path)
340
+ pg: PostgresContext = PostgresContext.apply(self.namespace, self.pg_path)
168
341
  if not pg.db:
342
+ def error():
343
+ if self.in_repl:
344
+ log2('cd to a database first.')
345
+ else:
346
+ log2('* database is missing.')
347
+ log2()
348
+ display_help()
349
+ return (False, error)
350
+
351
+ elif required == RequiredState.APP_APP:
352
+ if self.device != ReplState.A:
353
+ return (False, None)
354
+
355
+ if not self.app_app:
356
+ def error():
357
+ if self.in_repl:
358
+ log2('cd to an app first.')
359
+ else:
360
+ log2('* app is missing.')
361
+ log2()
362
+ display_help()
363
+ return (False, error)
364
+
365
+ elif required == RequiredState.EXPORT_DB:
366
+ if self.device not in [ReplState.C, ReplState.X]:
367
+ return (False, None)
368
+
369
+ if not self.export_session:
370
+ def error():
371
+ if self.in_repl:
372
+ log2("Select an export database first with 'use' command.")
373
+ else:
374
+ log2('* export database is missing.')
375
+ log2()
376
+ display_help()
377
+ return (False, error)
378
+
379
+ elif required in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X] and self.device != required:
380
+ def error():
169
381
  if self.in_repl:
170
- log2('cd to a database first.')
382
+ log2(f'Switch to {required}: first.')
171
383
  else:
172
- log2('* database is missing.')
384
+ log2('* on a wrong device.')
173
385
  log2()
174
386
  display_help()
387
+ return (False, error)
175
388
 
176
- return False
177
-
178
- if app_required == RequiredState.APP_APP and not self.app_app:
179
- if self.in_repl:
180
- log2('cd to an app first.')
181
- else:
182
- log2('* app is missing.')
183
- log2()
184
- display_help()
389
+ return (True, None)
185
390
 
186
- return False
391
+ def _validate_device(self, devices: list[RequiredState]):
392
+ if self.device not in devices:
393
+ def error():
394
+ if self.in_repl:
395
+ log2(f'Not a valid command on {self.device}: drive.')
396
+ else:
397
+ log2('* on a wrong device.')
398
+ log2()
399
+ display_help()
400
+ return (False, error)
187
401
 
188
- return True
402
+ return (True, None)
189
403
 
190
404
  def user_pass(self, secret_path = 'cql.secret'):
191
405
  return Secrets.get_user_pass(self.pod if self.pod else self.sts, self.namespace, secret_path=secret_path)
192
406
 
193
407
  def enter_bash(self, bash_session: BashSession):
408
+ self.push()
409
+
194
410
  self.bash_session = bash_session
195
- if self.device != ReplState.C:
196
- self.device = ReplState.C
197
- log2(f'Moved to {ReplState.C}: automatically. Will move back to {ReplState.P}: when you exit the bash session.')
198
411
 
199
412
  def exit_bash(self):
200
- if self.bash_session and self.bash_session.device:
201
- self.device = self.bash_session.device
202
-
413
+ self.pop()
203
414
  self.bash_session = None
204
415
 
205
- def wait_log(self, msg: str):
206
- if not self.wait_log_flag:
207
- log2(msg)
208
- self.wait_log_flag = True
209
-
210
- def clear_wait_log_flag(self):
211
- self.wait_log_flag = False
416
+ def push(self):
417
+ if not self.original_state:
418
+ self.original_state = copy(self)
419
+
420
+ def pop(self):
421
+ if o := self.original_state:
422
+ self.device = o.device
423
+ self.sts = o.sts
424
+ self.pod = o.pod
425
+ self.pg_path = o.pg_path
426
+ self.app_env = o.app_env
427
+ self.app_app = o.app_app
428
+ self.app_pod = o.app_pod
429
+ # self.export_session = o.export_session
430
+ self.namespace = o.namespace
431
+
432
+ self.original_state = None
adam/sql/__init__.py ADDED
File without changes
@@ -0,0 +1,120 @@
1
+ from enum import Enum
2
+ from typing import Callable
3
+
4
+ import sqlparse
5
+ from sqlparse.sql import Statement, Token
6
+
7
+ from adam.sql.term_completer import TermCompleter
8
+ from adam.utils_repl.automata_completer import AutomataCompleter
9
+ from adam.sql.sql_state_machine import AthenaStateMachine, CqlStateMachine, SqlStateMachine
10
+ from adam.utils_repl.state_machine import State
11
+
12
+ __all__ = [
13
+ "SqlCompleter",
14
+ ]
15
+
16
+ def default_columns(x: list[str]):
17
+ return 'id,x.,y.,z.'.split(',')
18
+
19
+ class SqlVariant(Enum):
20
+ SQL = 'sql'
21
+ CQL = 'cql'
22
+ ATHENA = 'athena'
23
+
24
+ class SqlCompleter(AutomataCompleter[Token]):
25
+ def tokens(self, text: str) -> list[Token]:
26
+ tokens = []
27
+
28
+ stmts = sqlparse.parse(text)
29
+ if not stmts:
30
+ tokens = []
31
+ else:
32
+ statement: Statement = stmts[0]
33
+ tokens = statement.tokens
34
+
35
+ return tokens
36
+
37
+ def __init__(self,
38
+ tables: Callable[[], list[str]],
39
+ dml: str = None,
40
+ expandables: dict = {},
41
+ variant: SqlVariant = SqlVariant.SQL,
42
+ debug = False):
43
+ machine = SqlStateMachine(debug=debug)
44
+ if variant == SqlVariant.CQL:
45
+ machine = CqlStateMachine(debug=debug)
46
+ elif variant == SqlVariant.ATHENA:
47
+ machine = AthenaStateMachine(debug=debug)
48
+ super().__init__(machine, dml, debug)
49
+
50
+ self.tables = tables
51
+ if 'columns' not in expandables:
52
+ expandables['columns'] = default_columns
53
+ self.expandables = expandables
54
+ self.variant = variant
55
+ self.debug = debug
56
+
57
+ def suggestions_completer(self, state: State, suggestions: str) -> list[str]:
58
+ if not suggestions:
59
+ return None
60
+
61
+ terms = []
62
+ for suggestion in suggestions.split(','):
63
+ terms.extend(self._terms(state, suggestion))
64
+
65
+ return TermCompleter(terms)
66
+
67
+ def _terms(self, state: State, word: str) -> list[str]:
68
+ terms = []
69
+
70
+ if word.startswith('`') and word.endswith('`'):
71
+ terms.append(word.strip('`'))
72
+ elif word == 'tables':
73
+ terms.extend(self.tables())
74
+ elif word == 'columns':
75
+ if 'last_name' in state.context and (n := state.context['last_name']):
76
+ if 'last_namespace' in state.context and (ns := state.context['last_namespace']):
77
+ n = f'{ns}.{n}'
78
+ terms.extend(self._call_expandable(word, [n]))
79
+ else:
80
+ terms.extend(self._call_expandable(word, []))
81
+ elif word == 'partition-columns':
82
+ terms.extend(self._call_expandable(word, []))
83
+ elif word == 'table-props':
84
+ terms.extend(self._call_expandable(word).keys())
85
+ elif word == 'table-prop-values':
86
+ if 'last_name' in state.context and state.context['last_name']:
87
+ table_props = self._call_expandable('table-props')
88
+ terms.extend(table_props[state.context['last_name']])
89
+ elif word == 'single':
90
+ terms.append("'")
91
+ elif word == 'comma':
92
+ terms.append(",")
93
+ elif word in self.machine.expandable_names():
94
+ terms.extend(self._call_expandable(word))
95
+ else:
96
+ terms.append(word)
97
+
98
+ return terms
99
+
100
+ def _call_expandable(self, name: str, *args):
101
+ if name in self.expandables:
102
+ c = self.expandables[name]
103
+ if args:
104
+ return c(args)
105
+ else:
106
+ return c()
107
+
108
+ return []
109
+
110
+ def completions_for_nesting(self, dml: str = None):
111
+ if dml:
112
+ return {dml: SqlCompleter(self.tables, dml, expandables=self.expandables, variant=self.variant)}
113
+
114
+ return {
115
+ word: SqlCompleter(self.tables, word, expandables=self.expandables, variant=self.variant)
116
+ for word in self.machine.suggestions[''].strip(' ').split(',')
117
+ }
118
+
119
+ def __str__(self):
120
+ return f'{self.variant}, {self.first_term}'