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.
- adam/__init__.py +0 -2
- adam/app_session.py +9 -12
- adam/apps.py +18 -4
- adam/batch.py +19 -19
- adam/checks/check_utils.py +16 -46
- adam/checks/cpu.py +7 -1
- adam/checks/cpu_metrics.py +52 -0
- adam/checks/disk.py +2 -3
- adam/columns/columns.py +3 -1
- adam/columns/cpu.py +3 -1
- adam/columns/cpu_metrics.py +22 -0
- adam/columns/memory.py +3 -4
- adam/commands/__init__.py +24 -0
- adam/commands/app/app.py +38 -0
- adam/commands/{app_ping.py → app/app_ping.py} +7 -13
- adam/commands/{login.py → app/login.py} +22 -24
- adam/commands/app/show_app_actions.py +49 -0
- adam/commands/{show → app}/show_app_id.py +8 -11
- adam/commands/{show → app}/show_app_queues.py +7 -14
- adam/commands/app/show_login.py +56 -0
- adam/commands/app/utils_app.py +106 -0
- adam/commands/audit/audit.py +22 -40
- adam/commands/audit/audit_repair_tables.py +15 -19
- adam/commands/audit/audit_run.py +15 -22
- adam/commands/audit/completions_l.py +15 -0
- adam/commands/audit/show_last10.py +4 -18
- adam/commands/audit/show_slow10.py +4 -17
- adam/commands/audit/show_top10.py +4 -16
- adam/commands/audit/utils_show_top10.py +15 -3
- adam/commands/bash/__init__.py +5 -0
- adam/commands/bash/bash.py +36 -0
- adam/commands/bash/bash_completer.py +93 -0
- adam/commands/bash/utils_bash.py +16 -0
- adam/commands/cassandra/__init__.py +0 -0
- adam/commands/cassandra/download_cassandra_log.py +45 -0
- adam/commands/{restart.py → cassandra/restart_cluster.py} +12 -26
- adam/commands/cassandra/restart_node.py +51 -0
- adam/commands/cassandra/restart_nodes.py +47 -0
- adam/commands/{rollout.py → cassandra/rollout.py} +20 -25
- adam/commands/cassandra/show_cassandra_repairs.py +37 -0
- adam/commands/cassandra/show_cassandra_status.py +117 -0
- adam/commands/{show → cassandra}/show_cassandra_version.py +5 -18
- adam/commands/cassandra/show_processes.py +50 -0
- adam/commands/cassandra/show_storage.py +44 -0
- adam/commands/{watch.py → cassandra/watch.py} +26 -29
- adam/commands/cli/__init__.py +0 -0
- adam/commands/{cli_commands.py → cli/cli_commands.py} +8 -4
- adam/commands/cli/clipboard_copy.py +86 -0
- adam/commands/cli/show_cli_commands.py +56 -0
- adam/commands/code.py +57 -0
- adam/commands/command.py +211 -40
- adam/commands/commands_utils.py +20 -27
- adam/commands/config/__init__.py +0 -0
- adam/commands/{param_get.py → config/param_get.py} +11 -14
- adam/commands/{param_set.py → config/param_set.py} +8 -12
- adam/commands/{show → config}/show_params.py +2 -5
- adam/commands/cql/alter_tables.py +66 -0
- adam/commands/cql/completions_c.py +29 -0
- adam/commands/cql/cqlsh.py +10 -32
- adam/commands/cql/utils_cql.py +306 -0
- adam/commands/debug/__init__.py +0 -0
- adam/commands/debug/debug.py +22 -0
- adam/commands/debug/debug_completes.py +35 -0
- adam/commands/debug/debug_timings.py +35 -0
- adam/commands/debug/show_offloaded_completes.py +45 -0
- adam/commands/deploy/code_start.py +7 -10
- adam/commands/deploy/code_stop.py +4 -21
- adam/commands/deploy/code_utils.py +3 -3
- adam/commands/deploy/deploy.py +4 -27
- adam/commands/deploy/deploy_frontend.py +14 -17
- adam/commands/deploy/deploy_pg_agent.py +3 -6
- adam/commands/deploy/deploy_pod.py +65 -73
- adam/commands/deploy/deploy_utils.py +14 -24
- adam/commands/deploy/undeploy.py +4 -27
- adam/commands/deploy/undeploy_frontend.py +4 -7
- adam/commands/deploy/undeploy_pg_agent.py +6 -8
- adam/commands/deploy/undeploy_pod.py +11 -12
- adam/commands/devices/__init__.py +0 -0
- adam/commands/devices/device.py +149 -0
- adam/commands/devices/device_app.py +163 -0
- adam/commands/devices/device_auit_log.py +49 -0
- adam/commands/devices/device_cass.py +179 -0
- adam/commands/devices/device_export.py +87 -0
- adam/commands/devices/device_postgres.py +160 -0
- adam/commands/devices/devices.py +25 -0
- adam/commands/diag/__init__.py +0 -0
- adam/commands/{check.py → diag/check.py} +16 -25
- adam/commands/diag/generate_report.py +52 -0
- adam/commands/diag/issues.py +43 -0
- adam/commands/exit.py +1 -4
- adam/commands/export/__init__.py +0 -0
- adam/commands/export/clean_up_all_export_sessions.py +37 -0
- adam/commands/export/clean_up_export_sessions.py +39 -0
- adam/commands/export/completions_x.py +11 -0
- adam/commands/export/download_export_session.py +40 -0
- adam/commands/export/drop_export_database.py +39 -0
- adam/commands/export/drop_export_databases.py +37 -0
- adam/commands/export/export.py +37 -0
- adam/commands/export/export_databases.py +251 -0
- adam/commands/export/export_select.py +34 -0
- adam/commands/export/export_sessions.py +210 -0
- adam/commands/export/export_use.py +49 -0
- adam/commands/export/export_x_select.py +48 -0
- adam/commands/export/exporter.py +419 -0
- adam/commands/export/import_files.py +44 -0
- adam/commands/export/import_session.py +40 -0
- adam/commands/export/importer.py +81 -0
- adam/commands/export/importer_athena.py +157 -0
- adam/commands/export/importer_sqlite.py +78 -0
- adam/commands/export/show_column_counts.py +45 -0
- adam/commands/export/show_export_databases.py +39 -0
- adam/commands/export/show_export_session.py +39 -0
- adam/commands/export/show_export_sessions.py +37 -0
- adam/commands/export/utils_export.py +366 -0
- adam/commands/fs/__init__.py +0 -0
- adam/commands/fs/cat.py +36 -0
- adam/commands/fs/cat_local.py +42 -0
- adam/commands/fs/cd.py +41 -0
- adam/commands/fs/download_file.py +47 -0
- adam/commands/fs/find_files.py +51 -0
- adam/commands/fs/find_processes.py +76 -0
- adam/commands/fs/head.py +36 -0
- adam/commands/fs/ls.py +41 -0
- adam/commands/fs/ls_local.py +40 -0
- adam/commands/fs/pwd.py +45 -0
- adam/commands/fs/rm.py +18 -0
- adam/commands/fs/rm_downloads.py +39 -0
- adam/commands/fs/rm_logs.py +38 -0
- adam/commands/{shell.py → fs/shell.py} +12 -4
- adam/commands/{show → fs}/show_adam.py +3 -3
- adam/commands/{show → fs}/show_host.py +1 -1
- adam/commands/help.py +5 -3
- adam/commands/intermediate_command.py +52 -0
- adam/commands/kubectl.py +38 -0
- adam/commands/medusa/medusa.py +4 -22
- adam/commands/medusa/medusa_backup.py +20 -27
- adam/commands/medusa/medusa_restore.py +35 -48
- adam/commands/medusa/medusa_show_backupjobs.py +16 -18
- adam/commands/medusa/medusa_show_restorejobs.py +13 -18
- adam/commands/medusa/utils_medusa.py +15 -0
- adam/commands/nodetool/__init__.py +0 -0
- adam/commands/{nodetool.py → nodetool/nodetool.py} +9 -20
- adam/commands/postgres/completions_p.py +22 -0
- adam/commands/postgres/postgres.py +47 -55
- adam/commands/postgres/postgres_databases.py +269 -0
- adam/commands/postgres/postgres_ls.py +5 -9
- adam/commands/postgres/postgres_preview.py +5 -9
- adam/commands/postgres/utils_postgres.py +80 -0
- adam/commands/preview_table.py +8 -44
- adam/commands/reaper/reaper.py +4 -27
- adam/commands/reaper/reaper_forward.py +49 -56
- adam/commands/reaper/reaper_forward_session.py +6 -0
- adam/commands/reaper/reaper_forward_stop.py +10 -16
- adam/commands/reaper/reaper_restart.py +7 -14
- adam/commands/reaper/reaper_run_abort.py +8 -33
- adam/commands/reaper/reaper_runs.py +43 -58
- adam/commands/reaper/reaper_runs_abort.py +29 -49
- adam/commands/reaper/reaper_schedule_activate.py +14 -33
- adam/commands/reaper/reaper_schedule_start.py +9 -33
- adam/commands/reaper/reaper_schedule_stop.py +9 -33
- adam/commands/reaper/reaper_schedules.py +4 -14
- adam/commands/reaper/reaper_status.py +8 -16
- adam/commands/reaper/utils_reaper.py +203 -0
- adam/commands/repair/repair.py +4 -22
- adam/commands/repair/repair_log.py +5 -11
- adam/commands/repair/repair_run.py +27 -34
- adam/commands/repair/repair_scan.py +32 -40
- adam/commands/repair/repair_stop.py +5 -12
- adam/commands/show.py +40 -0
- adam/config.py +5 -15
- adam/embedded_params.py +1 -1
- adam/log.py +4 -4
- adam/repl.py +83 -116
- adam/repl_commands.py +86 -45
- adam/repl_session.py +9 -1
- adam/repl_state.py +176 -40
- adam/sql/async_executor.py +62 -0
- adam/sql/lark_completer.py +286 -0
- adam/sql/lark_parser.py +604 -0
- adam/sql/qingl.lark +1076 -0
- adam/sql/sql_completer.py +52 -27
- adam/sql/sql_state_machine.py +131 -19
- adam/sso/authn_ad.py +6 -8
- adam/sso/authn_okta.py +4 -6
- adam/sso/cred_cache.py +4 -9
- adam/sso/idp.py +9 -12
- adam/utils.py +670 -31
- adam/utils_athena.py +145 -0
- adam/utils_audits.py +12 -103
- adam/utils_issues.py +32 -0
- adam/utils_k8s/app_clusters.py +35 -0
- adam/utils_k8s/app_pods.py +41 -0
- adam/utils_k8s/cassandra_clusters.py +35 -20
- adam/utils_k8s/cassandra_nodes.py +15 -6
- adam/utils_k8s/custom_resources.py +16 -17
- adam/utils_k8s/ingresses.py +2 -2
- adam/utils_k8s/jobs.py +7 -11
- adam/utils_k8s/k8s.py +96 -0
- adam/utils_k8s/kube_context.py +3 -6
- adam/{pod_exec_result.py → utils_k8s/pod_exec_result.py} +13 -4
- adam/utils_k8s/pods.py +159 -89
- adam/utils_k8s/secrets.py +4 -4
- adam/utils_k8s/service_accounts.py +5 -4
- adam/utils_k8s/services.py +2 -2
- adam/utils_k8s/statefulsets.py +6 -14
- adam/utils_local.py +80 -0
- adam/utils_net.py +4 -4
- adam/utils_repl/__init__.py +0 -0
- adam/utils_repl/appendable_completer.py +6 -0
- adam/utils_repl/automata_completer.py +48 -0
- adam/utils_repl/repl_completer.py +93 -0
- adam/utils_repl/state_machine.py +173 -0
- adam/utils_sqlite.py +132 -0
- adam/version.py +1 -1
- {kaqing-2.0.110.dist-info → kaqing-2.0.214.dist-info}/METADATA +1 -1
- kaqing-2.0.214.dist-info/RECORD +272 -0
- kaqing-2.0.214.dist-info/top_level.txt +2 -0
- teddy/__init__.py +0 -0
- teddy/lark_parser.py +436 -0
- teddy/lark_parser2.py +618 -0
- adam/commands/alter_tables.py +0 -81
- adam/commands/app.py +0 -67
- adam/commands/bash.py +0 -150
- adam/commands/cd.py +0 -125
- adam/commands/cp.py +0 -95
- adam/commands/cql/cql_completions.py +0 -15
- adam/commands/cql/cql_utils.py +0 -112
- adam/commands/devices.py +0 -118
- adam/commands/issues.py +0 -75
- adam/commands/logs.py +0 -40
- adam/commands/ls.py +0 -146
- adam/commands/postgres/postgres_context.py +0 -239
- adam/commands/postgres/postgres_utils.py +0 -31
- adam/commands/postgres/psql_completions.py +0 -10
- adam/commands/pwd.py +0 -77
- adam/commands/reaper/reaper_session.py +0 -159
- adam/commands/report.py +0 -63
- adam/commands/show/show.py +0 -54
- adam/commands/show/show_app_actions.py +0 -56
- adam/commands/show/show_cassandra_status.py +0 -128
- adam/commands/show/show_commands.py +0 -61
- adam/commands/show/show_login.py +0 -63
- adam/commands/show/show_processes.py +0 -53
- adam/commands/show/show_repairs.py +0 -47
- adam/commands/show/show_storage.py +0 -52
- kaqing-2.0.110.dist-info/RECORD +0 -187
- kaqing-2.0.110.dist-info/top_level.txt +0 -1
- /adam/commands/{show → app}/__init__.py +0 -0
- /adam/commands/{nodetool_commands.py → nodetool/nodetool_commands.py} +0 -0
- {kaqing-2.0.110.dist-info → kaqing-2.0.214.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
try:
|
|
22
|
+
return socket.gethostbyname(hostname)
|
|
23
|
+
except socket.gaierror:
|
|
24
|
+
return None
|
|
File without changes
|
|
@@ -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