kaqing 2.0.115__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.
- adam/__init__.py +0 -2
- adam/app_session.py +8 -11
- adam/batch.py +3 -3
- adam/checks/check_utils.py +14 -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 +18 -0
- adam/commands/alter_tables.py +43 -47
- adam/commands/audit/audit.py +24 -25
- adam/commands/audit/audit_repair_tables.py +14 -17
- adam/commands/audit/audit_run.py +15 -23
- adam/commands/audit/show_last10.py +10 -13
- adam/commands/audit/show_slow10.py +10 -13
- adam/commands/audit/show_top10.py +10 -14
- adam/commands/audit/utils_show_top10.py +2 -3
- adam/commands/bash/__init__.py +5 -0
- adam/commands/bash/bash.py +8 -96
- adam/commands/bash/utils_bash.py +16 -0
- adam/commands/cat.py +14 -19
- adam/commands/cd.py +12 -100
- adam/commands/check.py +20 -21
- adam/commands/cli_commands.py +2 -3
- adam/commands/code.py +20 -23
- adam/commands/command.py +123 -39
- adam/commands/commands_utils.py +8 -17
- adam/commands/cp.py +33 -39
- adam/commands/cql/cql_completions.py +28 -10
- adam/commands/cql/cqlsh.py +10 -30
- adam/commands/cql/utils_cql.py +343 -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 +2 -5
- 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 +5 -7
- adam/commands/deploy/undeploy_pod.py +11 -12
- adam/commands/devices/__init__.py +0 -0
- adam/commands/devices/device.py +118 -0
- adam/commands/devices/device_app.py +173 -0
- adam/commands/devices/device_auit_log.py +49 -0
- adam/commands/devices/device_cass.py +185 -0
- adam/commands/devices/device_export.py +86 -0
- adam/commands/devices/device_postgres.py +144 -0
- adam/commands/devices/devices.py +25 -0
- adam/commands/exit.py +1 -4
- adam/commands/export/clean_up_all_export_sessions.py +37 -0
- adam/commands/export/clean_up_export_sessions.py +51 -0
- adam/commands/export/drop_export_database.py +55 -0
- adam/commands/export/drop_export_databases.py +43 -0
- adam/commands/export/export.py +19 -26
- adam/commands/export/export_databases.py +174 -0
- adam/commands/export/export_handlers.py +71 -0
- adam/commands/export/export_select.py +48 -22
- adam/commands/export/export_select_x.py +54 -0
- adam/commands/export/export_use.py +19 -23
- adam/commands/export/exporter.py +353 -0
- adam/commands/export/import_session.py +40 -0
- adam/commands/export/importer.py +67 -0
- adam/commands/export/importer_athena.py +77 -0
- adam/commands/export/importer_sqlite.py +39 -0
- adam/commands/export/show_column_counts.py +54 -0
- adam/commands/export/show_export_databases.py +36 -0
- adam/commands/export/show_export_session.py +48 -0
- adam/commands/export/show_export_sessions.py +44 -0
- adam/commands/export/utils_export.py +223 -162
- adam/commands/help.py +1 -1
- adam/commands/intermediate_command.py +49 -0
- adam/commands/issues.py +11 -43
- adam/commands/kubectl.py +3 -6
- adam/commands/login.py +22 -24
- adam/commands/logs.py +3 -6
- adam/commands/ls.py +11 -128
- adam/commands/medusa/medusa.py +4 -22
- adam/commands/medusa/medusa_backup.py +20 -24
- adam/commands/medusa/medusa_restore.py +29 -33
- adam/commands/medusa/medusa_show_backupjobs.py +14 -18
- adam/commands/medusa/medusa_show_restorejobs.py +11 -18
- adam/commands/nodetool.py +6 -15
- adam/commands/param_get.py +11 -12
- adam/commands/param_set.py +9 -10
- adam/commands/postgres/postgres.py +41 -34
- adam/commands/postgres/postgres_context.py +57 -24
- adam/commands/postgres/postgres_ls.py +4 -8
- adam/commands/postgres/postgres_preview.py +5 -9
- adam/commands/postgres/psql_completions.py +1 -1
- adam/commands/postgres/utils_postgres.py +66 -0
- adam/commands/preview_table.py +5 -44
- adam/commands/pwd.py +14 -47
- adam/commands/reaper/reaper.py +4 -27
- adam/commands/reaper/reaper_forward.py +48 -55
- 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 +11 -30
- adam/commands/reaper/reaper_runs.py +42 -57
- adam/commands/reaper/reaper_runs_abort.py +29 -49
- adam/commands/reaper/reaper_schedule_activate.py +11 -30
- adam/commands/reaper/reaper_schedule_start.py +10 -29
- adam/commands/reaper/reaper_schedule_stop.py +10 -29
- adam/commands/reaper/reaper_schedules.py +4 -14
- adam/commands/reaper/reaper_status.py +8 -16
- adam/commands/reaper/utils_reaper.py +196 -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 -38
- adam/commands/repair/repair_stop.py +5 -11
- adam/commands/report.py +27 -29
- adam/commands/restart.py +25 -26
- adam/commands/rollout.py +19 -24
- adam/commands/shell.py +10 -4
- adam/commands/show/show.py +10 -25
- adam/commands/show/show_cassandra_repairs.py +35 -0
- adam/commands/show/show_cassandra_status.py +32 -43
- adam/commands/show/show_cassandra_version.py +5 -18
- adam/commands/show/show_commands.py +19 -24
- adam/commands/show/show_host.py +1 -1
- adam/commands/show/show_login.py +20 -27
- adam/commands/show/show_processes.py +15 -19
- adam/commands/show/show_storage.py +10 -20
- adam/commands/watch.py +26 -29
- adam/config.py +5 -14
- adam/embedded_params.py +1 -1
- adam/log.py +4 -4
- adam/pod_exec_result.py +3 -3
- adam/repl.py +40 -103
- adam/repl_commands.py +32 -16
- adam/repl_state.py +57 -28
- adam/sql/sql_completer.py +44 -28
- adam/sql/sql_state_machine.py +89 -28
- adam/sso/authn_ad.py +6 -8
- adam/sso/authn_okta.py +4 -6
- adam/sso/cred_cache.py +3 -5
- adam/sso/idp.py +9 -12
- adam/utils.py +435 -6
- adam/utils_athena.py +57 -37
- adam/utils_audits.py +12 -14
- adam/utils_issues.py +32 -0
- adam/utils_k8s/app_clusters.py +13 -18
- adam/utils_k8s/app_pods.py +2 -0
- adam/utils_k8s/cassandra_clusters.py +22 -19
- adam/utils_k8s/cassandra_nodes.py +2 -2
- 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 +87 -0
- adam/utils_k8s/pods.py +40 -77
- 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 +1 -12
- adam/utils_net.py +4 -4
- adam/utils_repl/__init__.py +0 -0
- adam/utils_repl/automata_completer.py +48 -0
- adam/utils_repl/repl_completer.py +46 -0
- adam/utils_repl/state_machine.py +173 -0
- adam/utils_sqlite.py +137 -0
- adam/version.py +1 -1
- {kaqing-2.0.115.dist-info → kaqing-2.0.172.dist-info}/METADATA +1 -1
- kaqing-2.0.172.dist-info/RECORD +230 -0
- adam/commands/app.py +0 -67
- adam/commands/app_ping.py +0 -44
- adam/commands/cql/cql_utils.py +0 -204
- adam/commands/devices.py +0 -147
- adam/commands/export/export_on_x.py +0 -76
- adam/commands/export/export_rmdbs.py +0 -65
- adam/commands/postgres/postgres_utils.py +0 -31
- adam/commands/reaper/reaper_session.py +0 -159
- adam/commands/show/show_app_actions.py +0 -56
- adam/commands/show/show_app_id.py +0 -47
- adam/commands/show/show_app_queues.py +0 -45
- adam/commands/show/show_repairs.py +0 -47
- adam/utils_export.py +0 -42
- kaqing-2.0.115.dist-info/RECORD +0 -203
- {kaqing-2.0.115.dist-info → kaqing-2.0.172.dist-info}/WHEEL +0 -0
- {kaqing-2.0.115.dist-info → kaqing-2.0.172.dist-info}/entry_points.txt +0 -0
- {kaqing-2.0.115.dist-info → kaqing-2.0.172.dist-info}/top_level.txt +0 -0
adam/repl_state.py
CHANGED
|
@@ -20,31 +20,8 @@ class BashSession:
|
|
|
20
20
|
def pwd(self, state: 'ReplState'):
|
|
21
21
|
command = f'cat /tmp/.qing-{self.session_id}'
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
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:
|
|
30
|
-
rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=False)]
|
|
31
|
-
elif state.sts:
|
|
32
|
-
rs = CassandraClusters.exec(state.sts, state.namespace, command, action='bash', show_out=False)
|
|
33
|
-
|
|
34
|
-
dir = None
|
|
35
|
-
for r in rs:
|
|
36
|
-
if r.exit_code(): # if fails to read the session file, ignore
|
|
37
|
-
continue
|
|
38
|
-
|
|
39
|
-
dir0 = r.stdout.strip(' \r\n')
|
|
40
|
-
if dir:
|
|
41
|
-
if dir != dir0:
|
|
42
|
-
log2('Inconsitent working dir found across multiple pods.')
|
|
43
|
-
return None
|
|
44
|
-
else:
|
|
45
|
-
dir = dir0
|
|
46
|
-
|
|
47
|
-
return dir
|
|
23
|
+
with device(state) as pods:
|
|
24
|
+
return pods.exec(command, action='bash', show_out=False)
|
|
48
25
|
|
|
49
26
|
class RequiredState(Enum):
|
|
50
27
|
CLUSTER = 'cluster'
|
|
@@ -124,6 +101,8 @@ class ReplState:
|
|
|
124
101
|
msg = f'{ReplState.L}:'
|
|
125
102
|
elif self.device == ReplState.X:
|
|
126
103
|
msg = f'{ReplState.X}:'
|
|
104
|
+
if self.export_session:
|
|
105
|
+
msg += self.export_session
|
|
127
106
|
else:
|
|
128
107
|
msg = f'{ReplState.C}:'
|
|
129
108
|
if self.pod:
|
|
@@ -361,13 +340,13 @@ class ReplState:
|
|
|
361
340
|
return (False, error)
|
|
362
341
|
|
|
363
342
|
elif required == RequiredState.EXPORT_DB:
|
|
364
|
-
if self.device
|
|
343
|
+
if self.device not in [ReplState.C, ReplState.X]:
|
|
365
344
|
return (False, None)
|
|
366
345
|
|
|
367
346
|
if not self.export_session:
|
|
368
347
|
def error():
|
|
369
348
|
if self.in_repl:
|
|
370
|
-
log2(
|
|
349
|
+
log2("Select an export database first with 'use' command.")
|
|
371
350
|
else:
|
|
372
351
|
log2('* export database is missing.')
|
|
373
352
|
log2()
|
|
@@ -424,6 +403,56 @@ class ReplState:
|
|
|
424
403
|
self.app_env = o.app_env
|
|
425
404
|
self.app_app = o.app_app
|
|
426
405
|
self.app_pod = o.app_pod
|
|
406
|
+
# self.export_session = o.export_session
|
|
427
407
|
self.namespace = o.namespace
|
|
428
408
|
|
|
429
|
-
self.original_state = None
|
|
409
|
+
self.original_state = None
|
|
410
|
+
|
|
411
|
+
class DevicePodService:
|
|
412
|
+
def __init__(self, handler: 'DeviceExecHandler'):
|
|
413
|
+
self.handler = handler
|
|
414
|
+
|
|
415
|
+
def exec(self, command: str, action='bash', show_out = True):
|
|
416
|
+
state = self.handler.state
|
|
417
|
+
|
|
418
|
+
rs = None
|
|
419
|
+
if state.device == ReplState.A and state.app_app:
|
|
420
|
+
if state.app_pod:
|
|
421
|
+
rs = [AppPods.exec(state.app_pod, state.namespace, command, show_out=show_out)]
|
|
422
|
+
else:
|
|
423
|
+
pods = AppPods.pod_names(state.namespace, state.app_env, state.app_app)
|
|
424
|
+
rs = AppClusters.exec(pods, state.namespace, command, show_out=show_out)
|
|
425
|
+
elif state.pod:
|
|
426
|
+
rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=show_out)]
|
|
427
|
+
elif state.sts:
|
|
428
|
+
rs = CassandraClusters.exec(state.sts, state.namespace, command, action=action, show_out=show_out)
|
|
429
|
+
# assume that pg-agent or ops pod is single pod
|
|
430
|
+
|
|
431
|
+
dir = None
|
|
432
|
+
if rs:
|
|
433
|
+
for r in rs:
|
|
434
|
+
if r.exit_code(): # if fails to read the session file, ignore
|
|
435
|
+
continue
|
|
436
|
+
|
|
437
|
+
dir0 = r.stdout.strip(' \r\n')
|
|
438
|
+
if dir:
|
|
439
|
+
if dir != dir0:
|
|
440
|
+
log2('Inconsitent working dir found across multiple pods.')
|
|
441
|
+
return None
|
|
442
|
+
else:
|
|
443
|
+
dir = dir0
|
|
444
|
+
|
|
445
|
+
return dir
|
|
446
|
+
|
|
447
|
+
class DeviceExecHandler:
|
|
448
|
+
def __init__(self, state: ReplState):
|
|
449
|
+
self.state = state
|
|
450
|
+
|
|
451
|
+
def __enter__(self):
|
|
452
|
+
return DevicePodService(self)
|
|
453
|
+
|
|
454
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
455
|
+
return False
|
|
456
|
+
|
|
457
|
+
def device(state: ReplState):
|
|
458
|
+
return DeviceExecHandler(state)
|
adam/sql/sql_completer.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
from enum import Enum
|
|
1
2
|
from typing import Callable
|
|
2
3
|
|
|
3
4
|
import sqlparse
|
|
4
|
-
from sqlparse.sql import
|
|
5
|
+
from sqlparse.sql import Token
|
|
5
6
|
|
|
6
7
|
from adam.sql.term_completer import TermCompleter
|
|
7
8
|
from adam.utils_repl.automata_completer import AutomataCompleter
|
|
@@ -15,38 +16,39 @@ __all__ = [
|
|
|
15
16
|
def default_columns(x: list[str]):
|
|
16
17
|
return 'id,x.,y.,z.'.split(',')
|
|
17
18
|
|
|
19
|
+
class SqlVariant(Enum):
|
|
20
|
+
SQL = 'sql'
|
|
21
|
+
CQL = 'cql'
|
|
22
|
+
ATHENA = 'athena'
|
|
23
|
+
|
|
18
24
|
class SqlCompleter(AutomataCompleter[Token]):
|
|
19
25
|
def tokens(self, text: str) -> list[Token]:
|
|
20
26
|
tokens = []
|
|
21
27
|
|
|
22
28
|
stmts = sqlparse.parse(text)
|
|
23
|
-
if
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
statement: Statement = stmts[0]
|
|
27
|
-
tokens = statement.tokens
|
|
29
|
+
if stmts:
|
|
30
|
+
for stmt in stmts:
|
|
31
|
+
tokens.extend(stmt.tokens)
|
|
28
32
|
|
|
29
33
|
return tokens
|
|
30
34
|
|
|
31
35
|
def __init__(self,
|
|
32
36
|
tables: Callable[[], list[str]],
|
|
33
37
|
dml: str = None,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
table_props: Callable[[], dict[str,list[str]]] = lambda: [],
|
|
37
|
-
variant = 'sql',
|
|
38
|
+
expandables: dict = {},
|
|
39
|
+
variant: SqlVariant = SqlVariant.SQL,
|
|
38
40
|
debug = False):
|
|
39
41
|
machine = SqlStateMachine(debug=debug)
|
|
40
|
-
if variant ==
|
|
42
|
+
if variant == SqlVariant.CQL:
|
|
41
43
|
machine = CqlStateMachine(debug=debug)
|
|
42
|
-
elif variant ==
|
|
44
|
+
elif variant == SqlVariant.ATHENA:
|
|
43
45
|
machine = AthenaStateMachine(debug=debug)
|
|
44
46
|
super().__init__(machine, dml, debug)
|
|
45
47
|
|
|
46
48
|
self.tables = tables
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
self.
|
|
49
|
+
if 'columns' not in expandables:
|
|
50
|
+
expandables['columns'] = default_columns
|
|
51
|
+
self.expandables = expandables
|
|
50
52
|
self.variant = variant
|
|
51
53
|
self.debug = debug
|
|
52
54
|
|
|
@@ -63,40 +65,54 @@ class SqlCompleter(AutomataCompleter[Token]):
|
|
|
63
65
|
def _terms(self, state: State, word: str) -> list[str]:
|
|
64
66
|
terms = []
|
|
65
67
|
|
|
66
|
-
if word
|
|
68
|
+
if word.startswith('`') and word.endswith('`'):
|
|
69
|
+
terms.append(word.strip('`'))
|
|
70
|
+
elif word == 'tables':
|
|
67
71
|
terms.extend(self.tables())
|
|
68
|
-
elif word == '`tables`':
|
|
69
|
-
terms.append('tables')
|
|
70
72
|
elif word == 'columns':
|
|
71
73
|
if 'last_name' in state.context and (n := state.context['last_name']):
|
|
72
74
|
if 'last_namespace' in state.context and (ns := state.context['last_namespace']):
|
|
73
75
|
n = f'{ns}.{n}'
|
|
74
|
-
terms.extend(self.
|
|
76
|
+
terms.extend(self._call_expandable(word, [n]))
|
|
75
77
|
else:
|
|
76
|
-
terms.extend(self.
|
|
78
|
+
terms.extend(self._call_expandable(word, []))
|
|
77
79
|
elif word == 'partition-columns':
|
|
78
|
-
terms.extend(self.
|
|
80
|
+
terms.extend(self._call_expandable(word, []))
|
|
79
81
|
elif word == 'table-props':
|
|
80
|
-
terms.extend(self.
|
|
82
|
+
terms.extend(self._call_expandable(word).keys())
|
|
81
83
|
elif word == 'table-prop-values':
|
|
82
84
|
if 'last_name' in state.context and state.context['last_name']:
|
|
83
|
-
|
|
85
|
+
table_props = self._call_expandable('table-props')
|
|
86
|
+
terms.extend(table_props[state.context['last_name']])
|
|
84
87
|
elif word == 'single':
|
|
85
88
|
terms.append("'")
|
|
86
89
|
elif word == 'comma':
|
|
87
90
|
terms.append(",")
|
|
91
|
+
elif word in self.machine.expandable_names():
|
|
92
|
+
terms.extend(self._call_expandable(word))
|
|
88
93
|
else:
|
|
89
94
|
terms.append(word)
|
|
90
95
|
|
|
91
96
|
return terms
|
|
92
97
|
|
|
98
|
+
def _call_expandable(self, name: str, *args):
|
|
99
|
+
if name in self.expandables:
|
|
100
|
+
c = self.expandables[name]
|
|
101
|
+
if args:
|
|
102
|
+
return c(args)
|
|
103
|
+
else:
|
|
104
|
+
return c()
|
|
105
|
+
|
|
106
|
+
return []
|
|
107
|
+
|
|
93
108
|
def completions_for_nesting(self, dml: str = None):
|
|
94
109
|
if dml:
|
|
95
|
-
return {dml: SqlCompleter(self.tables, dml,
|
|
96
|
-
table_props=self.table_props, variant=self.variant)}
|
|
110
|
+
return {dml: SqlCompleter(self.tables, dml, expandables=self.expandables, variant=self.variant)}
|
|
97
111
|
|
|
98
112
|
return {
|
|
99
|
-
word
|
|
100
|
-
table_props=self.table_props, variant=self.variant)
|
|
113
|
+
word: SqlCompleter(self.tables, word, expandables=self.expandables, variant=self.variant)
|
|
101
114
|
for word in self.machine.suggestions[''].strip(' ').split(',')
|
|
102
|
-
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
def __str__(self):
|
|
118
|
+
return f'{self.variant}, {self.first_term}'
|
adam/sql/sql_state_machine.py
CHANGED
|
@@ -2,6 +2,7 @@ from typing import Callable
|
|
|
2
2
|
from sqlparse.sql import Token
|
|
3
3
|
from sqlparse import tokens as TOKEN
|
|
4
4
|
|
|
5
|
+
from adam.utils import log_exc
|
|
5
6
|
from adam.utils_repl.state_machine import StateMachine, State
|
|
6
7
|
|
|
7
8
|
__all__ = [
|
|
@@ -76,8 +77,8 @@ SQL_SPEC = [
|
|
|
76
77
|
'- > ; > ',
|
|
77
78
|
'select_from_sq_ > as > select_from_x_as ^ as',
|
|
78
79
|
'select_from_x_comma_ > name|audit > select_from_x ^ tables',
|
|
79
|
-
'select_from_x_ ^ as,where,inner join,left outer join,right outer join,full outer join,group by,order by,limit',
|
|
80
|
-
'select_from_x_as_x_ > , > select_from_x_comma_ ^ where,inner join,left outer join,right outer join,full outer join,group by,order by,limit',
|
|
80
|
+
'select_from_x_ ^ as,where,inner join,left outer join,right outer join,full outer join,group by,order by,limit,&',
|
|
81
|
+
'select_from_x_as_x_ > , > select_from_x_comma_ ^ where,inner join,left outer join,right outer join,full outer join,group by,order by,limit,&',
|
|
81
82
|
'- > as > select_from_x_as',
|
|
82
83
|
'- > where > select_where',
|
|
83
84
|
'- > order > select_order',
|
|
@@ -96,6 +97,7 @@ SQL_SPEC = [
|
|
|
96
97
|
'- > full > select_from_x_full',
|
|
97
98
|
'- > full outer join > select_join',
|
|
98
99
|
'- > ; > ',
|
|
100
|
+
'- > & > select_from_x$',
|
|
99
101
|
'select_from_x_as_ > name > select_from_x_as_x ^ x,y,z',
|
|
100
102
|
'select_from_x_as_x > , > select_from_x_as_x_comma_',
|
|
101
103
|
'- > ; > ',
|
|
@@ -118,34 +120,38 @@ SQL_SPEC = [
|
|
|
118
120
|
'select_where_a_not_op > name|single|num > select_where_sc ^ single',
|
|
119
121
|
'select_where_a_op > name|single|num > select_where_sc ^ single',
|
|
120
122
|
'select_where_sc > ; > ',
|
|
121
|
-
'select_where_sc_ > and|or > select_where ^ and,or,order by,group by,limit',
|
|
123
|
+
'select_where_sc_ > and|or > select_where ^ and,or,order by,group by,limit,&',
|
|
122
124
|
'- > group > select_group',
|
|
123
125
|
'- > group by > select_group_by',
|
|
124
126
|
'- > order > select_order',
|
|
125
127
|
'- > order by > select_order_by',
|
|
126
128
|
'- > limit > select_where_sc_limit',
|
|
127
129
|
'- > ; > ',
|
|
130
|
+
'- > & > select_from_x$',
|
|
128
131
|
'select_group_ > by > select_group_by ^ by',
|
|
129
132
|
'select_group_by_ > name > select_group_by_a ^ columns',
|
|
130
133
|
'select_group_by_a > , > select_group_by_a_comma_ ^ columns',
|
|
131
134
|
'- > ; > ',
|
|
132
135
|
'select_group_by_a_comma_ > name > select_group_by_a ^ columns',
|
|
133
|
-
'select_group_by_a_ > limit > select_where_sc_limit ^ limit,order by',
|
|
136
|
+
'select_group_by_a_ > limit > select_where_sc_limit ^ limit,order by,&',
|
|
134
137
|
'- > order > select_order',
|
|
135
138
|
'- > order by > select_order_by',
|
|
136
139
|
'- > ; > ',
|
|
140
|
+
'- > & > select_from_x$',
|
|
137
141
|
'select_order_ > by > select_order_by ^ by',
|
|
138
142
|
'select_order_by_ > name > select_order_by_a ^ columns',
|
|
139
143
|
'select_order_by_a > , > select_order_by_a_comma_',
|
|
140
144
|
'- > ; > ',
|
|
141
145
|
'select_order_by_a_comma_ > name > select_order_by_a ^ columns',
|
|
142
|
-
'select_order_by_a_ > desc|asc > select_order_by_a_desc ^ desc,asc,limit',
|
|
146
|
+
'select_order_by_a_ > desc|asc > select_order_by_a_desc ^ desc,asc,limit,&',
|
|
143
147
|
'- > limit > select_where_sc_limit',
|
|
144
148
|
'- > ; > ',
|
|
149
|
+
'- > & > select_from_x$',
|
|
145
150
|
'select_order_by_a_desc > , > select_order_by_a_comma_',
|
|
146
151
|
'- > ; > ',
|
|
147
|
-
'select_order_by_a_desc_ > limit > select_where_sc_limit ^ limit',
|
|
152
|
+
'select_order_by_a_desc_ > limit > select_where_sc_limit ^ limit,&',
|
|
148
153
|
'- > ; > ',
|
|
154
|
+
'- > & > select_from_x$',
|
|
149
155
|
'select_where_sc_limit_ > num > select_where_sc_limit_num ^ 1',
|
|
150
156
|
'select_where_sc_limit_num > ; > ',
|
|
151
157
|
'select_where_sc_limit_num_rp__ > as > select_from_x_as ^ as',
|
|
@@ -201,7 +207,9 @@ SQL_SPEC = [
|
|
|
201
207
|
'insert_values > ( > insert_values_lp_',
|
|
202
208
|
'insert_values_lp_ > name|single|num > insert_values_lp_v ^ single',
|
|
203
209
|
'insert_values_lp_v > , > insert_values_lp_v_comma_',
|
|
210
|
+
'- > ) > insert_values_lp_v_rp_',
|
|
204
211
|
'insert_values_lp_v_comma_ > name|single|num > insert_values_lp_v',
|
|
212
|
+
'insert_values_lp_v_rp__ > & > insert_values_lp_v_rp_$ ^ &',
|
|
205
213
|
|
|
206
214
|
# <update_statement> ::= UPDATE <table_name>
|
|
207
215
|
# SET <set_clause_list>
|
|
@@ -222,8 +230,9 @@ SQL_SPEC = [
|
|
|
222
230
|
'update_set_a_op > name|single|num > update_set_sc ^ single',
|
|
223
231
|
'update_set_sc > , > update_set_sc_comma_',
|
|
224
232
|
'update_set_sc_comma_ > name > update_set_a ^ id',
|
|
225
|
-
'update_set_sc_ > , > update_set_sc_comma_ ^ where',
|
|
233
|
+
'update_set_sc_ > , > update_set_sc_comma_ ^ where,&',
|
|
226
234
|
'- > where > update_where',
|
|
235
|
+
'- > & > update_set_sc$ ^ &',
|
|
227
236
|
'update_where_ > name > update_where_a ^ id',
|
|
228
237
|
'update_where_a > comparison > update_where_a_op',
|
|
229
238
|
'update_where_a_ > comparison > update_where_a_op ^ =,<,<=,>,>=,<>,like,not,in',
|
|
@@ -240,7 +249,7 @@ SQL_SPEC = [
|
|
|
240
249
|
'update_where_a_in_lp_a_comma_ > name|single|num > update_where_a_in_lp_a ^ single',
|
|
241
250
|
'update_where_a_not_op > name|single|num > update_where_sc ^ single',
|
|
242
251
|
'update_where_a_op > name|single|num > update_where_sc ^ single',
|
|
243
|
-
'update_where_sc_ > and|or > update_where ^ and,or',
|
|
252
|
+
'update_where_sc_ > and|or > update_where ^ and,or,&',
|
|
244
253
|
|
|
245
254
|
# <delete_statement> ::= DELETE FROM <table_name> [ WHERE <search_condition> ]
|
|
246
255
|
|
|
@@ -335,10 +344,10 @@ SQL_KEYWORDS = [
|
|
|
335
344
|
'describe', 'preview'
|
|
336
345
|
]
|
|
337
346
|
|
|
338
|
-
EXPANDABLE_NAMES = {'tables', 'columns', 'partition-columns', 'table-props', 'table-props-values'}
|
|
347
|
+
EXPANDABLE_NAMES = {'keyspaces', 'tables', 'columns', 'partition-columns', 'table-props', 'table-props-values'}
|
|
339
348
|
|
|
340
349
|
CQL_SPEC = SQL_SPEC + [
|
|
341
|
-
' > select > select ^ select,insert,update,delete,alter,describe,preview,consistency',
|
|
350
|
+
' > select > select ^ select,insert,update,delete,alter,describe,preview,consistency,export,import,drop,clean',
|
|
342
351
|
|
|
343
352
|
# ALTER TABLE [ <keyspace_name> . ] <table_name>
|
|
344
353
|
# ( ALTER <column_name> TYPE <cql_type>
|
|
@@ -357,7 +366,7 @@ CQL_SPEC = SQL_SPEC + [
|
|
|
357
366
|
'alter_table_with_p_op_v_ > --include-reaper > alter_table_with_p_op_v_$ ^ --include-reaper',
|
|
358
367
|
|
|
359
368
|
' > describe > describe',
|
|
360
|
-
'describe_ > table > desc_table ^ table,`tables`,keyspace
|
|
369
|
+
'describe_ > table > desc_table ^ table,`tables`,keyspace,`keyspaces`,schema',
|
|
361
370
|
'- > tables > desc_tables',
|
|
362
371
|
'- > keyspace > desc_keyspace',
|
|
363
372
|
'- > keyspaces > desc_keyspaces',
|
|
@@ -365,20 +374,24 @@ CQL_SPEC = SQL_SPEC + [
|
|
|
365
374
|
'desc_table_ > name > desc_table_t ^ tables',
|
|
366
375
|
'desc_table_t_ > & > desc_table_t_$ ^ &',
|
|
367
376
|
'desc_tables_ > & > desc_tables_$ ^ &',
|
|
368
|
-
'desc_keyspace_ > name > desc_keyspace_k',
|
|
377
|
+
'desc_keyspace_ > name > desc_keyspace_k ^ keyspaces',
|
|
369
378
|
'desc_keyspace_k_ > & > desc_keyspace_k_$ ^ &',
|
|
370
379
|
'desc_schema_ > & > desc_schema_$ ^ &',
|
|
371
380
|
|
|
372
381
|
' > export > export',
|
|
373
|
-
'export_ > name > export_table ^
|
|
382
|
+
'export_ > name > export_table ^ *,tables',
|
|
383
|
+
'- > * > export_all',
|
|
384
|
+
'export_all_ > in > export_in ^ in,with',
|
|
374
385
|
'- > with > export_with',
|
|
375
386
|
'export_table > ( > export_table_lp_ ^ (,comma,with,tables',
|
|
376
|
-
'- > , >
|
|
387
|
+
'- > , > export_table_comma_',
|
|
377
388
|
'- > . > export_table ^ tables',
|
|
378
|
-
'export_table_ > ( > export_table_lp_ ^ as,(,comma,with consistency',
|
|
379
|
-
'- > , >
|
|
389
|
+
'export_table_ > ( > export_table_lp_ ^ as,(,comma,with consistency,to',
|
|
390
|
+
'- > , > export_table_comma_',
|
|
380
391
|
'- > as > export_as',
|
|
381
392
|
'- > with > export_with',
|
|
393
|
+
'- > to > export_table_to',
|
|
394
|
+
'export_table_comma_ > name > export_table ^ tables',
|
|
382
395
|
'export_table_lp_ > name > export_table_lp_a ^ columns',
|
|
383
396
|
'export_table_lp_a > , > export_table_lp_a_comma_',
|
|
384
397
|
'- > ) > export_table_lp_a_comma_rp_',
|
|
@@ -387,25 +400,56 @@ CQL_SPEC = SQL_SPEC + [
|
|
|
387
400
|
'- > , > export ^ with consistency',
|
|
388
401
|
'- > with > export_with',
|
|
389
402
|
'export_as_ > name > export_as_f',
|
|
390
|
-
'export_as_f > , >
|
|
391
|
-
'export_as_f_ > , >
|
|
403
|
+
'export_as_f > , > export_table_comma_',
|
|
404
|
+
'export_as_f_ > , > export_table_comma_ ^ with consistency,to',
|
|
392
405
|
'- > with > export_with',
|
|
406
|
+
'- > to > export_table_to',
|
|
407
|
+
'export_in_ > name > export_in_k ^ keyspaces',
|
|
408
|
+
'export_in_k_ > with > export_with ^ with consistency',
|
|
393
409
|
'export_with_ > consistency > export_with_consistency ^ consistency',
|
|
394
410
|
'export_with_consistency_ > quorum|all|serial|one|each_quorum|local_quorum|any|local_one|two|three|local_serial > export_with_quorum ^ quorum,all,serial,one,each_quorum,local_quorum,any,local_one,two,three,local_serial',
|
|
411
|
+
'export_table_to_ > athena|sqlite|csv > export_table_to$ ^ athena,sqlite,csv',
|
|
412
|
+
|
|
413
|
+
' > import > import',
|
|
414
|
+
'import_ > session > import_session ^ session',
|
|
415
|
+
'import_session_ > name > import_session_s ^ export-sessions-incomplete',
|
|
416
|
+
'import_session_s_ > to > import_session_to ^ to',
|
|
417
|
+
'import_session_to_ > athena|sqlite > import_session_to$ ^ athena,sqlite',
|
|
395
418
|
|
|
396
419
|
' > consistency > consistency',
|
|
397
420
|
'consistency_ > quorum|all|serial|one|each_quorum|local_quorum|any|local_one|two|three|local_serial > consistency_quorum ^ quorum,all,serial,one,each_quorum,local_quorum,any,local_one,two,three,local_serial',
|
|
398
|
-
'consistency_quorum > ; > '
|
|
421
|
+
'consistency_quorum > ; > ',
|
|
422
|
+
|
|
423
|
+
' > drop > drop',
|
|
424
|
+
'drop_ > all > drop_all ^ all export databases,export database',
|
|
425
|
+
'- > export > drop_export',
|
|
426
|
+
'drop_all_ > export > drop_all_export ^ export databases',
|
|
427
|
+
'drop_all_export_ > databases > drop_all_dbs ^ databases',
|
|
428
|
+
'drop_export_ > database > drop_export_db ^ database',
|
|
429
|
+
'drop_export_db_ > name > drop_export_db$ ^ export-dbs',
|
|
430
|
+
|
|
431
|
+
' > clean > clean',
|
|
432
|
+
'clean_ > up > clean_up ^ up all export sessions,up export session',
|
|
433
|
+
'clean_up_ > all > clean_up_all ^ all export sessions,export sessions',
|
|
434
|
+
'- > export > clean_up_export',
|
|
435
|
+
'clean_up_all_ > export > clean_up_all_export ^ export sessions',
|
|
436
|
+
'clean_up_all_export_ > sessions > clean_up_all_sessions ^ sessions',
|
|
437
|
+
'clean_up_export_ > sessions > clean_up_export_sessions ^ sessions',
|
|
438
|
+
'clean_up_export_sessions_ > name > clean_up_export_sessions$ ^ export-sessions',
|
|
399
439
|
]
|
|
400
440
|
|
|
401
441
|
CQL_KEYWORDS = SQL_KEYWORDS + [
|
|
402
|
-
'schema', 'keyspace', 'keyspaces', 'tables', 'export', 'consistency',
|
|
403
|
-
'quorum', 'all', 'serial', 'one', 'each_quorum', 'local_quorum', 'any', 'local_one', 'two', 'three', 'local_serial', 'to'
|
|
442
|
+
'schema', 'keyspace', 'keyspaces', 'tables', 'export', 'copy', 'consistency',
|
|
443
|
+
'quorum', 'all', 'serial', 'one', 'each_quorum', 'local_quorum', 'any', 'local_one', 'two', 'three', 'local_serial', 'to',
|
|
444
|
+
'database', 'databases', 'session', 'sessions', 'clean', 'up', 'athena', 'sqlite', 'csv', 'import'
|
|
404
445
|
]
|
|
405
446
|
|
|
447
|
+
CQL_EXPANDABLE_NAMES = EXPANDABLE_NAMES | {
|
|
448
|
+
'export-dbs', 'export-sessions', 'export-sessions-incomplete'
|
|
449
|
+
}
|
|
450
|
+
|
|
406
451
|
ATHENA_SPEC = SQL_SPEC + [
|
|
407
|
-
' > select > select ^ select,insert,update,delete,alter,describe,preview',
|
|
408
|
-
' > &select > select ^ select,insert,update,delete,alter,describe,preview',
|
|
452
|
+
' > select > select ^ select,insert,update,delete,alter,describe,preview,drop',
|
|
409
453
|
|
|
410
454
|
'alter_table_t_ > add > alter_table_add ^ add partition,drop partition',
|
|
411
455
|
'alter_table_add_ > partition > alter_partition ^ partition',
|
|
@@ -421,13 +465,26 @@ ATHENA_SPEC = SQL_SPEC + [
|
|
|
421
465
|
'describe_ > name > desc_t ^ tables',
|
|
422
466
|
'desc_t_ > name > desc_t_',
|
|
423
467
|
|
|
424
|
-
'repair'
|
|
468
|
+
'repair',
|
|
469
|
+
|
|
470
|
+
' > drop > drop',
|
|
471
|
+
'drop_ > all > drop_all ^ all export databases,export database',
|
|
472
|
+
'- > export > drop_export',
|
|
473
|
+
'drop_all_ > export > drop_all_export ^ export databases',
|
|
474
|
+
'drop_all_export_ > databases > drop_all_dbs ^ databases',
|
|
475
|
+
'drop_export_ > database > drop_export_db ^ database',
|
|
476
|
+
'drop_export_db_ > name > drop_export_db$ ^ export-dbs',
|
|
425
477
|
]
|
|
426
478
|
|
|
427
479
|
ATHENA_KEYWORDS = SQL_KEYWORDS + [
|
|
428
|
-
'partition',
|
|
480
|
+
'partition',
|
|
481
|
+
'database', 'databases', 'session', 'sessions', 'clean', 'up', 'all', 'export'
|
|
429
482
|
]
|
|
430
483
|
|
|
484
|
+
ATHENA_EXPANDABLE_NAMES = EXPANDABLE_NAMES | {
|
|
485
|
+
'export-dbs'
|
|
486
|
+
}
|
|
487
|
+
|
|
431
488
|
class SqlStateMachine(StateMachine[Token]):
|
|
432
489
|
def __init__(self, indent=0, push_level = 0, debug = False):
|
|
433
490
|
super().__init__(indent, push_level, debug)
|
|
@@ -498,7 +555,7 @@ class SqlStateMachine(StateMachine[Token]):
|
|
|
498
555
|
elif token.ttype == TOKEN.Operator.Comparison:
|
|
499
556
|
it = 'comparison'
|
|
500
557
|
|
|
501
|
-
|
|
558
|
+
with log_exc(False):
|
|
502
559
|
# print(f'\n{state.to_s} > {it} > ', end='')
|
|
503
560
|
if comeback_state:
|
|
504
561
|
state = comeback_state
|
|
@@ -509,8 +566,6 @@ class SqlStateMachine(StateMachine[Token]):
|
|
|
509
566
|
|
|
510
567
|
if last_name:
|
|
511
568
|
state.context['last_name'] = last_name
|
|
512
|
-
except:
|
|
513
|
-
pass
|
|
514
569
|
|
|
515
570
|
return state
|
|
516
571
|
|
|
@@ -552,6 +607,9 @@ class CqlStateMachine(SqlStateMachine):
|
|
|
552
607
|
def keywords(self):
|
|
553
608
|
return CQL_KEYWORDS
|
|
554
609
|
|
|
610
|
+
def expandable_names(self):
|
|
611
|
+
return CQL_EXPANDABLE_NAMES
|
|
612
|
+
|
|
555
613
|
class AthenaStateMachine(SqlStateMachine):
|
|
556
614
|
def __init__(self, indent=0, push_level = 0, debug = False):
|
|
557
615
|
super().__init__(indent, push_level, debug)
|
|
@@ -561,3 +619,6 @@ class AthenaStateMachine(SqlStateMachine):
|
|
|
561
619
|
|
|
562
620
|
def keywords(self):
|
|
563
621
|
return ATHENA_KEYWORDS
|
|
622
|
+
|
|
623
|
+
def expandable_names(self):
|
|
624
|
+
return ATHENA_EXPANDABLE_NAMES
|
adam/sso/authn_ad.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import re
|
|
3
|
-
import traceback
|
|
4
3
|
import jwt
|
|
5
4
|
import requests
|
|
6
5
|
from urllib.parse import urlparse, parse_qs
|
|
@@ -8,6 +7,7 @@ from urllib.parse import urlparse, parse_qs
|
|
|
8
7
|
from adam.log import Log
|
|
9
8
|
from adam.sso.authenticator import Authenticator
|
|
10
9
|
from adam.sso.id_token import IdToken
|
|
10
|
+
from adam.utils import debug, log_exc
|
|
11
11
|
from .idp_login import IdpLogin
|
|
12
12
|
from adam.config import Config
|
|
13
13
|
|
|
@@ -33,7 +33,7 @@ class AdAuthenticator(Authenticator):
|
|
|
33
33
|
|
|
34
34
|
session = requests.Session()
|
|
35
35
|
r = session.get(idp_uri)
|
|
36
|
-
|
|
36
|
+
debug(f'{r.status_code} {idp_uri}')
|
|
37
37
|
|
|
38
38
|
config = self.validate_and_return_config(r)
|
|
39
39
|
|
|
@@ -52,7 +52,7 @@ class AdAuthenticator(Authenticator):
|
|
|
52
52
|
r = session.post(login_uri, data=body, headers={
|
|
53
53
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
54
54
|
})
|
|
55
|
-
|
|
55
|
+
debug(f'{r.status_code} {login_uri}')
|
|
56
56
|
|
|
57
57
|
config = self.validate_and_return_config(r)
|
|
58
58
|
|
|
@@ -69,7 +69,7 @@ class AdAuthenticator(Authenticator):
|
|
|
69
69
|
r = session.post(kmsi_uri, data=body, headers={
|
|
70
70
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
71
71
|
})
|
|
72
|
-
|
|
72
|
+
debug(f'{r.status_code} {kmsi_uri}')
|
|
73
73
|
|
|
74
74
|
if (config := self.extract_config_object(r.text)):
|
|
75
75
|
if 'sErrorCode' in config and config['sErrorCode'] == '50058':
|
|
@@ -101,7 +101,7 @@ class AdAuthenticator(Authenticator):
|
|
|
101
101
|
|
|
102
102
|
def validate_and_return_config(self, r: requests.Response):
|
|
103
103
|
if r.status_code < 200 or r.status_code >= 300:
|
|
104
|
-
|
|
104
|
+
debug(r.text)
|
|
105
105
|
|
|
106
106
|
return None
|
|
107
107
|
|
|
@@ -138,7 +138,7 @@ class AdAuthenticator(Authenticator):
|
|
|
138
138
|
|
|
139
139
|
def parse_id_token(self, id_token: str) -> IdToken:
|
|
140
140
|
jwks_url = Config().get('idps.ad.jwks-uri', '')
|
|
141
|
-
|
|
141
|
+
with log_exc():
|
|
142
142
|
jwks_client = jwt.PyJWKClient(jwks_url, cache_jwk_set=True, lifespan=360)
|
|
143
143
|
signing_key = jwks_client.get_signing_key_from_jwt(id_token)
|
|
144
144
|
data = jwt.decode(
|
|
@@ -163,7 +163,5 @@ class AdAuthenticator(Authenticator):
|
|
|
163
163
|
nbf=data['nbf'] if 'nbf' in data else 0,
|
|
164
164
|
exp=data['exp'] if 'exp' in data else 0
|
|
165
165
|
)
|
|
166
|
-
except:
|
|
167
|
-
Config().debug(traceback.format_exc())
|
|
168
166
|
|
|
169
167
|
return None
|
adam/sso/authn_okta.py
CHANGED
|
@@ -8,7 +8,7 @@ from adam.sso.id_token import IdToken
|
|
|
8
8
|
|
|
9
9
|
from .idp_login import IdpLogin
|
|
10
10
|
from adam.config import Config
|
|
11
|
-
from adam.utils import log2
|
|
11
|
+
from adam.utils import debug, log2, log_exc
|
|
12
12
|
|
|
13
13
|
class OktaException(Exception):
|
|
14
14
|
pass
|
|
@@ -49,7 +49,7 @@ class OktaAuthenticator(Authenticator):
|
|
|
49
49
|
|
|
50
50
|
session = requests.Session()
|
|
51
51
|
response = session.post(authn_uri, headers=headers, data=json.dumps(payload))
|
|
52
|
-
|
|
52
|
+
debug(f'{response.status_code} {authn_uri}')
|
|
53
53
|
auth_response = response.json()
|
|
54
54
|
|
|
55
55
|
if 'sessionToken' not in auth_response:
|
|
@@ -59,7 +59,7 @@ class OktaAuthenticator(Authenticator):
|
|
|
59
59
|
|
|
60
60
|
url = f'{idp_uri}&sessionToken={session_token}'
|
|
61
61
|
r = session.get(url)
|
|
62
|
-
|
|
62
|
+
debug(f'{r.status_code} {url}')
|
|
63
63
|
|
|
64
64
|
id_token = OktaAuthenticator().extract(r.text, r'.*name=\"id_token\" value=\"(.*?)\".*')
|
|
65
65
|
if not id_token:
|
|
@@ -95,7 +95,7 @@ class OktaAuthenticator(Authenticator):
|
|
|
95
95
|
return None
|
|
96
96
|
|
|
97
97
|
jwks_url = Config().get('idps.okta.jwks-uri', 'https://c3energy.okta.com/oauth2/v1/keys')
|
|
98
|
-
|
|
98
|
+
with log_exc():
|
|
99
99
|
jwks_client = jwt.PyJWKClient(jwks_url, cache_jwk_set=True, lifespan=360)
|
|
100
100
|
signing_key = jwks_client.get_signing_key_from_jwt(id_token)
|
|
101
101
|
data = jwt.decode(
|
|
@@ -121,7 +121,5 @@ class OktaAuthenticator(Authenticator):
|
|
|
121
121
|
nbf=data['nbf'] if 'nbf' in data else 0,
|
|
122
122
|
exp=data['exp'] if 'exp' in data else 0
|
|
123
123
|
)
|
|
124
|
-
except:
|
|
125
|
-
pass
|
|
126
124
|
|
|
127
125
|
return None
|