kaqing 2.0.145__py3-none-any.whl → 2.0.172__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 (172) hide show
  1. adam/__init__.py +0 -2
  2. adam/app_session.py +8 -11
  3. adam/batch.py +3 -3
  4. adam/checks/check_utils.py +14 -46
  5. adam/checks/cpu.py +7 -1
  6. adam/checks/cpu_metrics.py +52 -0
  7. adam/checks/disk.py +2 -3
  8. adam/columns/columns.py +3 -1
  9. adam/columns/cpu.py +3 -1
  10. adam/columns/cpu_metrics.py +22 -0
  11. adam/columns/memory.py +3 -4
  12. adam/commands/__init__.py +18 -0
  13. adam/commands/alter_tables.py +43 -47
  14. adam/commands/audit/audit.py +22 -23
  15. adam/commands/audit/audit_repair_tables.py +14 -17
  16. adam/commands/audit/audit_run.py +15 -23
  17. adam/commands/audit/show_last10.py +10 -13
  18. adam/commands/audit/show_slow10.py +10 -13
  19. adam/commands/audit/show_top10.py +10 -13
  20. adam/commands/audit/utils_show_top10.py +2 -3
  21. adam/commands/bash/__init__.py +5 -0
  22. adam/commands/bash/bash.py +7 -104
  23. adam/commands/bash/utils_bash.py +16 -0
  24. adam/commands/cat.py +13 -19
  25. adam/commands/cd.py +8 -10
  26. adam/commands/check.py +20 -21
  27. adam/commands/cli_commands.py +2 -3
  28. adam/commands/code.py +20 -23
  29. adam/commands/command.py +120 -39
  30. adam/commands/commands_utils.py +8 -17
  31. adam/commands/cp.py +33 -39
  32. adam/commands/cql/cql_completions.py +9 -4
  33. adam/commands/cql/cqlsh.py +10 -30
  34. adam/commands/cql/{cql_utils.py → utils_cql.py} +149 -15
  35. adam/commands/deploy/code_start.py +7 -10
  36. adam/commands/deploy/code_stop.py +4 -21
  37. adam/commands/deploy/code_utils.py +3 -3
  38. adam/commands/deploy/deploy.py +4 -27
  39. adam/commands/deploy/deploy_frontend.py +14 -17
  40. adam/commands/deploy/deploy_pg_agent.py +2 -5
  41. adam/commands/deploy/deploy_pod.py +64 -68
  42. adam/commands/deploy/undeploy.py +4 -27
  43. adam/commands/deploy/undeploy_frontend.py +4 -7
  44. adam/commands/deploy/undeploy_pg_agent.py +4 -7
  45. adam/commands/deploy/undeploy_pod.py +9 -12
  46. adam/commands/devices/device.py +93 -2
  47. adam/commands/devices/device_app.py +37 -10
  48. adam/commands/devices/device_auit_log.py +8 -2
  49. adam/commands/devices/device_cass.py +47 -7
  50. adam/commands/devices/device_export.py +2 -2
  51. adam/commands/devices/device_postgres.py +41 -6
  52. adam/commands/exit.py +1 -4
  53. adam/commands/export/clean_up_all_export_sessions.py +37 -0
  54. adam/commands/export/clean_up_export_sessions.py +18 -7
  55. adam/commands/export/drop_export_database.py +15 -18
  56. adam/commands/export/drop_export_databases.py +6 -9
  57. adam/commands/export/export.py +8 -38
  58. adam/commands/export/export_databases.py +16 -12
  59. adam/commands/export/export_handlers.py +71 -0
  60. adam/commands/export/export_select.py +33 -24
  61. adam/commands/export/export_use.py +12 -15
  62. adam/commands/export/exporter.py +37 -48
  63. adam/commands/export/import_session.py +4 -32
  64. adam/commands/export/importer_athena.py +4 -7
  65. adam/commands/export/importer_sqlite.py +19 -27
  66. adam/commands/export/show_column_counts.py +13 -22
  67. adam/commands/export/show_export_databases.py +3 -6
  68. adam/commands/export/show_export_session.py +10 -13
  69. adam/commands/export/show_export_sessions.py +8 -11
  70. adam/commands/export/utils_export.py +24 -1
  71. adam/commands/intermediate_command.py +49 -0
  72. adam/commands/issues.py +11 -43
  73. adam/commands/kubectl.py +3 -6
  74. adam/commands/login.py +22 -24
  75. adam/commands/logs.py +3 -6
  76. adam/commands/ls.py +8 -9
  77. adam/commands/medusa/medusa.py +4 -22
  78. adam/commands/medusa/medusa_backup.py +20 -24
  79. adam/commands/medusa/medusa_restore.py +29 -33
  80. adam/commands/medusa/medusa_show_backupjobs.py +14 -18
  81. adam/commands/medusa/medusa_show_restorejobs.py +11 -18
  82. adam/commands/nodetool.py +6 -15
  83. adam/commands/param_get.py +11 -12
  84. adam/commands/param_set.py +9 -10
  85. adam/commands/postgres/postgres.py +29 -37
  86. adam/commands/postgres/postgres_context.py +47 -23
  87. adam/commands/postgres/postgres_ls.py +4 -8
  88. adam/commands/postgres/postgres_preview.py +5 -9
  89. adam/commands/postgres/psql_completions.py +1 -1
  90. adam/commands/postgres/utils_postgres.py +66 -0
  91. adam/commands/preview_table.py +5 -44
  92. adam/commands/pwd.py +13 -16
  93. adam/commands/reaper/reaper.py +4 -27
  94. adam/commands/reaper/reaper_forward.py +48 -55
  95. adam/commands/reaper/reaper_forward_session.py +6 -0
  96. adam/commands/reaper/reaper_forward_stop.py +10 -16
  97. adam/commands/reaper/reaper_restart.py +7 -14
  98. adam/commands/reaper/reaper_run_abort.py +11 -30
  99. adam/commands/reaper/reaper_runs.py +42 -57
  100. adam/commands/reaper/reaper_runs_abort.py +29 -49
  101. adam/commands/reaper/reaper_schedule_activate.py +11 -30
  102. adam/commands/reaper/reaper_schedule_start.py +10 -29
  103. adam/commands/reaper/reaper_schedule_stop.py +10 -29
  104. adam/commands/reaper/reaper_schedules.py +4 -14
  105. adam/commands/reaper/reaper_status.py +8 -16
  106. adam/commands/reaper/utils_reaper.py +196 -0
  107. adam/commands/repair/repair.py +4 -22
  108. adam/commands/repair/repair_log.py +5 -11
  109. adam/commands/repair/repair_run.py +27 -34
  110. adam/commands/repair/repair_scan.py +32 -38
  111. adam/commands/repair/repair_stop.py +5 -11
  112. adam/commands/report.py +27 -29
  113. adam/commands/restart.py +25 -26
  114. adam/commands/rollout.py +19 -24
  115. adam/commands/shell.py +10 -4
  116. adam/commands/show/show.py +10 -26
  117. adam/commands/show/show_cassandra_repairs.py +35 -0
  118. adam/commands/show/show_cassandra_status.py +32 -43
  119. adam/commands/show/show_cassandra_version.py +5 -18
  120. adam/commands/show/show_commands.py +19 -24
  121. adam/commands/show/show_host.py +1 -1
  122. adam/commands/show/show_login.py +20 -27
  123. adam/commands/show/show_processes.py +15 -19
  124. adam/commands/show/show_storage.py +10 -20
  125. adam/commands/watch.py +26 -29
  126. adam/config.py +4 -16
  127. adam/embedded_params.py +1 -1
  128. adam/log.py +4 -4
  129. adam/pod_exec_result.py +3 -3
  130. adam/repl.py +29 -32
  131. adam/repl_commands.py +11 -11
  132. adam/repl_state.py +52 -26
  133. adam/sql/sql_completer.py +4 -6
  134. adam/sql/sql_state_machine.py +21 -14
  135. adam/sso/authn_ad.py +6 -8
  136. adam/sso/authn_okta.py +4 -6
  137. adam/sso/cred_cache.py +3 -5
  138. adam/sso/idp.py +9 -12
  139. adam/utils.py +393 -33
  140. adam/utils_athena.py +14 -13
  141. adam/utils_audits.py +12 -12
  142. adam/utils_issues.py +32 -0
  143. adam/utils_k8s/app_clusters.py +13 -18
  144. adam/utils_k8s/app_pods.py +2 -0
  145. adam/utils_k8s/cassandra_clusters.py +21 -18
  146. adam/utils_k8s/custom_resources.py +16 -17
  147. adam/utils_k8s/ingresses.py +2 -2
  148. adam/utils_k8s/jobs.py +7 -11
  149. adam/utils_k8s/k8s.py +87 -0
  150. adam/utils_k8s/pods.py +14 -76
  151. adam/utils_k8s/secrets.py +4 -4
  152. adam/utils_k8s/service_accounts.py +5 -4
  153. adam/utils_k8s/services.py +2 -2
  154. adam/utils_k8s/statefulsets.py +1 -12
  155. adam/utils_repl/state_machine.py +3 -3
  156. adam/utils_sqlite.py +78 -42
  157. adam/version.py +1 -1
  158. {kaqing-2.0.145.dist-info → kaqing-2.0.172.dist-info}/METADATA +1 -1
  159. kaqing-2.0.172.dist-info/RECORD +230 -0
  160. adam/commands/app.py +0 -67
  161. adam/commands/app_ping.py +0 -44
  162. adam/commands/export/clean_up_export_session.py +0 -53
  163. adam/commands/postgres/postgres_utils.py +0 -31
  164. adam/commands/reaper/reaper_session.py +0 -159
  165. adam/commands/show/show_app_actions.py +0 -56
  166. adam/commands/show/show_app_id.py +0 -47
  167. adam/commands/show/show_app_queues.py +0 -45
  168. adam/commands/show/show_repairs.py +0 -47
  169. kaqing-2.0.145.dist-info/RECORD +0 -227
  170. {kaqing-2.0.145.dist-info → kaqing-2.0.172.dist-info}/WHEEL +0 -0
  171. {kaqing-2.0.145.dist-info → kaqing-2.0.172.dist-info}/entry_points.txt +0 -0
  172. {kaqing-2.0.145.dist-info → kaqing-2.0.172.dist-info}/top_level.txt +0 -0
adam/utils_k8s/pods.py CHANGED
@@ -1,9 +1,8 @@
1
1
  from collections.abc import Callable
2
- from concurrent.futures import ThreadPoolExecutor, as_completed
3
2
  from datetime import datetime
4
3
  import sys
5
4
  import time
6
- from typing import TypeVar, cast
5
+ from typing import TypeVar
7
6
  from kubernetes import client
8
7
  from kubernetes.stream import stream
9
8
  from kubernetes.stream.ws_client import ERROR_CHANNEL, WSClient
@@ -11,7 +10,7 @@ from kubernetes.stream.ws_client import ERROR_CHANNEL, WSClient
11
10
  from adam.config import Config
12
11
  from adam.utils_k8s.volumes import ConfigMapMount
13
12
  from adam.pod_exec_result import PodExecResult
14
- from adam.utils import elapsed_time, log2
13
+ from adam.utils import ParallelMapHandler, log2, debug, log_exc
15
14
  from .kube_context import KubeContext
16
15
 
17
16
  from websocket._core import WebSocket
@@ -30,11 +29,9 @@ class Pods:
30
29
  return _TEST_POD_EXEC_OUTS
31
30
 
32
31
  def delete(pod_name: str, namespace: str, grace_period_seconds: int = None):
33
- try:
32
+ with log_exc(lambda e: "Exception when calling CoreV1Api->delete_namespaced_pod: %s\n" % e):
34
33
  v1 = client.CoreV1Api()
35
34
  v1.delete_namespaced_pod(pod_name, namespace, grace_period_seconds=grace_period_seconds)
36
- except Exception as e:
37
- log2("Exception when calling CoreV1Api->delete_namespaced_pod: %s\n" % e)
38
35
 
39
36
  def delete_with_selector(namespace: str, label_selector: str, grace_period_seconds: int = None):
40
37
  v1 = client.CoreV1Api()
@@ -43,64 +40,13 @@ class Pods:
43
40
  for i in ret.items:
44
41
  v1.delete_namespaced_pod(name=i.metadata.name, namespace=namespace, grace_period_seconds=grace_period_seconds)
45
42
 
46
- def on_pods(pods: list[str],
47
- namespace: str,
48
- body: Callable[[ThreadPoolExecutor, str, str, bool], T],
49
- post: Callable[[T], T] = None,
50
- action: str = 'action',
51
- max_workers=0,
52
- show_out=True,
53
- on_any = False,
54
- background = False) -> list[T]:
55
- show_out = KubeContext.show_out(show_out)
56
-
43
+ def parallelize(collection: list, max_workers: int = 0, samples = sys.maxsize, msg: str = None, action: str = 'action'):
57
44
  if not max_workers:
58
45
  max_workers = Config().action_workers(action, 0)
59
- if not on_any and max_workers > 0:
60
- # if parallel, node sampling is suppressed
61
- if KubeContext.show_parallelism():
62
- log2(f'Executing on all nodes from statefulset in parallel...')
63
- start_time = time.time()
64
- try:
65
- with ThreadPoolExecutor(max_workers=max_workers) as executor:
66
- # disable stdout from the pod_exec, then show the output in a for loop
67
- futures = [body(executor, pod, namespace, show_out) for pod in pods]
68
- if len(futures) == 0:
69
- return cast(list[T], [])
70
-
71
- rs = [future.result() for future in as_completed(futures)]
72
- if post:
73
- rs = [post(r, show_out=show_out) for r in rs]
74
-
75
- return rs
76
- finally:
77
- if KubeContext.show_parallelism():
78
- log2(f"Parallel {action} elapsed time: {elapsed_time(start_time)} with {max_workers} workers")
79
- else:
80
- results: list[T] = []
81
-
82
- samples = 1 if on_any else Config().action_node_samples(action, sys.maxsize)
83
- l = min(len(pods), samples)
84
- adj = 'all'
85
- if l < len(pods):
86
- adj = f'{l} sample'
87
- if show_out:
88
- log2(f'Executing on {adj} nodes from statefulset...')
89
- for pod_name in pods:
90
- try:
91
- # disable stdout from the pod_exec, then show the output in a for loop
92
- result = body(None, pod_name, namespace, False)
93
- if post:
94
- result = post(result, show_out=show_out)
95
- results.append(result)
96
- if result:
97
- l -= 1
98
- if not l:
99
- break
100
- except Exception as e:
101
- log2(e)
102
-
103
- return results
46
+ if samples == sys.maxsize:
47
+ samples = Config().action_node_samples(action, sys.maxsize)
48
+
49
+ return ParallelMapHandler(collection, max_workers, samples = samples, msg = msg)
104
50
 
105
51
  def exec(pod_name: str, container: str, namespace: str, command: str,
106
52
  show_out = True, throw_err = False, shell = '/bin/sh',
@@ -138,8 +84,7 @@ class Pods:
138
84
  exec_command = [shell, '-c', command]
139
85
 
140
86
  k_command = f'kubectl exec {pod_name} -c {container} -n {namespace} -- {shell} -c "{command}"'
141
- if show_out:
142
- print(k_command)
87
+ debug(k_command)
143
88
 
144
89
  resp: WSClient = stream(
145
90
  api.connect_get_namespaced_pod_exec,
@@ -173,11 +118,9 @@ class Pods:
173
118
  stderr.append(frag)
174
119
  if show_out: print(frag, end="")
175
120
 
176
- try:
121
+ with log_exc():
177
122
  # get the exit code from server
178
123
  error_output = resp.read_channel(ERROR_CHANNEL)
179
- except Exception as e:
180
- pass
181
124
  except Exception as e:
182
125
  if throw_err:
183
126
  raise e
@@ -186,10 +129,8 @@ class Pods:
186
129
  finally:
187
130
  resp.close()
188
131
  if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
189
- try:
132
+ with log_exc():
190
133
  s.sock.close()
191
- except:
192
- pass
193
134
 
194
135
  return PodExecResult("".join(stdout), "".join(stderr), k_command, error_output, pod=pod_name, log_file=log_file)
195
136
 
@@ -214,20 +155,17 @@ class Pods:
214
155
  if resp.peek_stdout():
215
156
  yield resp.read_stdout()
216
157
 
217
- try:
158
+ with log_exc():
218
159
  # get the exit code from server
219
160
  error_output = resp.read_channel(ERROR_CHANNEL)
220
- except Exception as e:
221
- pass
222
161
  except Exception as e:
223
162
  raise e
224
163
  finally:
225
164
  resp.close()
226
165
  if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
227
- try:
166
+ with log_exc():
228
167
  s.sock.close()
229
- except:
230
- pass
168
+
231
169
  def get_container(namespace: str, pod_name: str, container_name: str):
232
170
  pod = Pods.get(namespace, pod_name)
233
171
  if not pod:
adam/utils_k8s/secrets.py CHANGED
@@ -6,13 +6,13 @@ from kubernetes import client
6
6
  from kubernetes.client import V1Secret
7
7
 
8
8
  from adam.config import Config
9
- from adam.utils import log2
9
+ from adam.utils import log2, wait_log
10
10
 
11
11
  # utility collection on secrets; methods are all static
12
12
  class Secrets:
13
13
  @functools.lru_cache()
14
14
  def list_secrets(namespace: str = None, name_pattern: str = None):
15
- Config().wait_log('Inspecting Cassandra Instances...')
15
+ wait_log('Inspecting Cassandra Instances...')
16
16
 
17
17
  secrets_names = []
18
18
 
@@ -39,14 +39,14 @@ class Secrets:
39
39
 
40
40
  return secrets_names
41
41
 
42
- def get_user_pass(ss_name: str, namespace: str, secret_path: str = 'cql.secret'):
42
+ def get_user_pass(sts_or_pod_name: str, namespace: str, secret_path: str = 'cql.secret'):
43
43
  # cs-d0767a536f-cs-d0767a536f-default-sts ->
44
44
  # cs-d0767a536f-superuser
45
45
  # cs-d0767a536f-reaper-ui
46
46
  user = 'superuser'
47
47
  if secret_path == 'reaper.secret':
48
48
  user = 'reaper-ui'
49
- groups = re.match(Config().get(f'{secret_path}.cluster-regex', r'(.*?-.*?)-.*'), ss_name)
49
+ groups = re.match(Config().get(f'{secret_path}.cluster-regex', r'(.*?-.*?)-.*'), sts_or_pod_name)
50
50
  secret_name = Config().get(f'{secret_path}.name', '{cluster}-' + user).replace('{cluster}', groups[1], 1)
51
51
 
52
52
  secret = Secrets.get_data(namespace, secret_name)
@@ -1,6 +1,7 @@
1
1
  from kubernetes import client, config
2
2
 
3
3
  from adam.config import Config
4
+ from adam.utils import debug
4
5
 
5
6
  # utility collection on service accounts; methods are all static
6
7
  class ServiceAccounts:
@@ -37,7 +38,7 @@ class ServiceAccounts:
37
38
  namespace=namespace,
38
39
  body=service_account
39
40
  )
40
- Config().debug(f"Service Account '{api_response.metadata.name}' created in namespace '{namespace}'.")
41
+ debug(f"Service Account '{api_response.metadata.name}' created in namespace '{namespace}'.")
41
42
 
42
43
  def delete_service_account(namespace: str, label_selector: str) -> list:
43
44
  refs = []
@@ -45,7 +46,7 @@ class ServiceAccounts:
45
46
  v1 = client.CoreV1Api()
46
47
  sas = v1.list_namespaced_service_account(namespace=namespace, label_selector=label_selector).items
47
48
  for sa in sas:
48
- Config().debug(f'delete {sa.metadata.name}')
49
+ debug(f'delete {sa.metadata.name}')
49
50
  v1.delete_namespaced_service_account(name=sa.metadata.name, namespace=namespace)
50
51
  refs.append(sa)
51
52
 
@@ -102,7 +103,7 @@ class ServiceAccounts:
102
103
  v1_rbac = client.RbacAuthorizationV1Api()
103
104
  cluster_role_bindings = v1_rbac.list_namespaced_role_binding(namespace=namespace, label_selector=label_selector).items
104
105
  for binding in cluster_role_bindings:
105
- Config().debug(f'delete {binding.metadata.name}')
106
+ debug(f'delete {binding.metadata.name}')
106
107
  v1_rbac.delete_namespaced_role_binding(name=binding.metadata.name, namespace=namespace)
107
108
  refs.append(binding)
108
109
 
@@ -162,7 +163,7 @@ class ServiceAccounts:
162
163
  v1_rbac = client.RbacAuthorizationV1Api()
163
164
  cluster_role_bindings = v1_rbac.list_cluster_role_binding(label_selector=label_selector).items
164
165
  for binding in cluster_role_bindings:
165
- Config().debug(f'delete {binding.metadata.name}')
166
+ debug(f'delete {binding.metadata.name}')
166
167
  v1_rbac.delete_cluster_role_binding(binding.metadata.name)
167
168
  refs.append(binding)
168
169
 
@@ -2,7 +2,7 @@ from typing import List
2
2
  from kubernetes import client
3
3
 
4
4
  from adam.config import Config
5
- from adam.utils import log2
5
+ from adam.utils import debug, log2
6
6
 
7
7
  from .kube_context import KubeContext
8
8
 
@@ -71,7 +71,7 @@ class Services:
71
71
  namespace=namespace,
72
72
  body=delete_options
73
73
  )
74
- Config().debug(f"200 Service '{name}' in namespace '{namespace}' deleted successfully.")
74
+ debug(f"200 Service '{name}' in namespace '{namespace}' deleted successfully.")
75
75
  except client.ApiException as e:
76
76
  log2(f"Error deleting Service '{name}': {e}")
77
77
  except Exception as e:
@@ -1,12 +1,9 @@
1
- from collections.abc import Callable
2
- from concurrent.futures import ThreadPoolExecutor
3
1
  from datetime import datetime
4
2
  import functools
5
3
  import re
6
4
  from typing import List, TypeVar, cast
7
5
  from kubernetes import client
8
6
 
9
- from .pods import Pods
10
7
  from .kube_context import KubeContext
11
8
  from adam.utils import log2
12
9
 
@@ -58,15 +55,6 @@ class StatefulSets:
58
55
 
59
56
  return statefulset_pods
60
57
 
61
- def on_cluster(statefulset: str,
62
- namespace: str,
63
- body: Callable[[ThreadPoolExecutor, str, str, bool], T],
64
- post: Callable[[T], T] = None,
65
- action: str = 'action', max_workers=0, show_out=True, on_any = False, background = False) -> list[T]:
66
- pods = StatefulSets.pod_names(statefulset, namespace)
67
-
68
- return Pods.on_pods(pods, namespace, body, post=post, action=action, max_workers=max_workers, show_out=show_out, on_any=on_any, background=background)
69
-
70
58
  @functools.lru_cache()
71
59
  def pod_names(ss: str, ns: str):
72
60
  return [pod.metadata.name for pod in StatefulSets.pods(ss, ns)]
@@ -92,6 +80,7 @@ class StatefulSets:
92
80
 
93
81
  return restarted, False
94
82
 
83
+ @functools.lru_cache()
95
84
  def get_datacenter(sts: str, ns: str) -> str:
96
85
  v1 = client.AppsV1Api()
97
86
  namespace = ns
@@ -1,6 +1,8 @@
1
1
  from abc import abstractmethod
2
2
  from typing import Generic, TypeVar
3
3
 
4
+ from adam.utils import log_exc
5
+
4
6
  __all__ = [
5
7
  'State',
6
8
  'StateMachine',
@@ -160,14 +162,12 @@ class StateMachine(Generic[T]):
160
162
  last_name = token
161
163
  token = 'word'
162
164
 
163
- try:
165
+ with log_exc():
164
166
  context = state.context
165
167
  state = self.states[f'{state.state} > {token}']
166
168
  state.context = context
167
169
 
168
170
  if last_name:
169
171
  state.context['last_name'] = last_name
170
- except:
171
- pass
172
172
 
173
173
  return state
adam/utils_sqlite.py CHANGED
@@ -1,3 +1,5 @@
1
+ from collections.abc import Callable
2
+
1
3
  import functools
2
4
  import glob
3
5
  import os
@@ -5,41 +7,63 @@ import sqlite3
5
7
  import pandas
6
8
 
7
9
  from adam.config import Config
8
- from adam.utils import lines_to_tabular, log
10
+ from adam.utils import lines_to_tabular, 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
+ if self.keyspace:
36
+ self.conn = sqlite3.connect(f'{SQLite.local_db_dir()}/{self.database}_{self.keyspace}.db')
37
+ else:
38
+ self.conn = SQLite.connect(self.database)
39
+
40
+ self.conn.__enter__()
41
+
42
+ return self.conn
43
+
44
+ def __exit__(self, exc_type, exc_val, exc_tb):
45
+ if self.conn:
46
+ self.conn.__exit__(exc_type, exc_val, exc_tb)
47
+
48
+ return False
49
+
50
+ def sqlite(database: str, keyspace = None):
51
+ return SQLiteConnectionHandler(database, keyspace=keyspace)
9
52
 
10
53
  # no state utility class
11
54
  class SQLite:
55
+ def cursor(conn: sqlite3.Connection):
56
+ return CursorHandler(conn)
57
+
12
58
  def local_db_dir():
13
59
  return Config().get('export.sqlite.local-db-dir', '/tmp/qing-db')
14
60
 
15
61
  def keyspace(database: str):
16
62
  return '_'.join(database.replace(".db", "").split('_')[1:])
17
63
 
18
- def connect(session: str):
19
- os.makedirs(SQLite.local_db_dir(), exist_ok=True)
20
-
21
- conn = None
22
-
23
- try:
24
- conn = sqlite3.connect(f'{SQLite.local_db_dir()}/{session}_root.db')
25
- cursor = None
26
- try:
27
- cursor = conn.cursor()
28
- for d in SQLite.database_names(session):
29
- if d != f'{session}_root.db':
30
- q = f"ATTACH DATABASE '{SQLite.local_db_dir()}/{d}' AS {SQLite.keyspace(d)};"
31
- cursor.execute(q)
32
- finally:
33
- if cursor:
34
- cursor.close()
35
- finally:
36
- pass
37
-
38
- return conn
39
-
40
64
  @functools.lru_cache()
41
65
  def database_names(prefix: str = None):
42
- Config().wait_log('Inspecting export databases...')
66
+ wait_log('Inspecting export databases...')
43
67
 
44
68
  pattern = f'{SQLite.local_db_dir()}/s*.db'
45
69
  if prefix:
@@ -60,15 +84,10 @@ class SQLite:
60
84
  tables = []
61
85
  try:
62
86
  conn = sqlite3.connect(f'{SQLite.local_db_dir()}/{ts_prefix}_{keyspace}.db')
63
- cursor = None
64
- try:
65
- cursor = conn.cursor()
87
+ with SQLite.cursor(conn) as cursor:
66
88
  cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;")
67
89
 
68
90
  tables = [row[0] for row in cursor.fetchall() if row[0] != "sqlite_sequence"]
69
- finally:
70
- if cursor:
71
- cursor.close()
72
91
 
73
92
  return tables
74
93
  except sqlite3.Error as e:
@@ -78,24 +97,41 @@ class SQLite:
78
97
  if conn:
79
98
  conn.close()
80
99
 
100
+ def connect(database: str):
101
+ os.makedirs(SQLite.local_db_dir(), exist_ok=True)
102
+
103
+ conn = sqlite3.connect(f'{SQLite.local_db_dir()}/{database}.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
+
81
112
  @functools.lru_cache()
82
113
  def column_names(tables: list[str] = [], database: str = None, function: str = 'audit', partition_cols_only = False):
83
114
  pass
84
115
 
85
- def run_query(query: str, database: str = None, conn_passed = None):
86
- conn = None
87
- try:
88
- if not conn_passed:
89
- conn = SQLite.connect(database)
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
90
122
 
91
- df = SQLite.query(conn_passed if conn_passed else conn, query)
92
- lines = ['\t'.join(map(str, line)) for line in df.values.tolist()]
93
- log(lines_to_tabular(lines, header='\t'.join(df.columns.tolist()), separator='\t'))
123
+ df = SQLite.query(conn, query)
124
+ lines = ['\t'.join(map(str, line)) for line in df.values.tolist()]
125
+ out = lines_to_tabular(lines, header='\t'.join(df.columns.tolist()), separator='\t')
126
+ if output:
127
+ log_file = output(out)
128
+ else:
129
+ log(out)
94
130
 
95
- return len(lines)
96
- finally:
97
- if conn:
98
- conn.close()
131
+ return len(lines), log_file
99
132
 
100
133
  def query(conn, sql: str) -> tuple[str, str, list]:
101
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.145" #: the working version
4
+ __version__ = "2.0.172" #: 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.145
3
+ Version: 2.0.172
4
4
  Summary: UNKNOWN
5
5
  Home-page: UNKNOWN
6
6
  License: UNKNOWN