kaqing 2.0.174__py3-none-any.whl → 2.0.186__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 (134) hide show
  1. adam/app_session.py +2 -2
  2. adam/apps.py +18 -4
  3. adam/batch.py +1 -1
  4. adam/checks/check_utils.py +3 -1
  5. adam/commands/__init__.py +4 -2
  6. adam/commands/app/__init__.py +0 -0
  7. adam/commands/app/app.py +38 -0
  8. adam/commands/app/app_ping.py +38 -0
  9. adam/commands/app/show_app_actions.py +49 -0
  10. adam/commands/app/show_app_id.py +44 -0
  11. adam/commands/app/show_app_queues.py +38 -0
  12. adam/commands/app/utils_app.py +106 -0
  13. adam/commands/audit/audit.py +9 -27
  14. adam/commands/audit/audit_repair_tables.py +5 -7
  15. adam/commands/audit/audit_run.py +1 -1
  16. adam/commands/audit/completions_l.py +15 -0
  17. adam/commands/audit/show_last10.py +2 -14
  18. adam/commands/audit/show_slow10.py +2 -13
  19. adam/commands/audit/show_top10.py +2 -11
  20. adam/commands/audit/utils_show_top10.py +14 -1
  21. adam/commands/bash/bash.py +1 -1
  22. adam/commands/cat.py +3 -7
  23. adam/commands/check.py +2 -2
  24. adam/commands/cli_commands.py +6 -1
  25. adam/commands/{cp.py → clipboard_copy.py} +18 -12
  26. adam/commands/code.py +2 -2
  27. adam/commands/command.py +61 -11
  28. adam/commands/commands_utils.py +19 -12
  29. adam/commands/cql/completions_c.py +28 -0
  30. adam/commands/cql/cqlsh.py +3 -7
  31. adam/commands/cql/utils_cql.py +22 -58
  32. adam/commands/deploy/deploy_pg_agent.py +2 -2
  33. adam/commands/deploy/undeploy_pg_agent.py +2 -2
  34. adam/commands/devices/device.py +39 -8
  35. adam/commands/devices/device_app.py +18 -28
  36. adam/commands/devices/device_auit_log.py +3 -3
  37. adam/commands/devices/device_cass.py +16 -22
  38. adam/commands/devices/device_export.py +6 -3
  39. adam/commands/devices/device_postgres.py +79 -63
  40. adam/commands/download_file.py +47 -0
  41. adam/commands/export/clean_up_all_export_sessions.py +3 -3
  42. adam/commands/export/clean_up_export_sessions.py +5 -10
  43. adam/commands/export/completions_x.py +11 -0
  44. adam/commands/export/download_export_session.py +40 -0
  45. adam/commands/export/export.py +0 -16
  46. adam/commands/export/export_databases.py +26 -9
  47. adam/commands/export/export_select.py +9 -58
  48. adam/commands/export/export_sessions.py +90 -5
  49. adam/commands/export/export_use.py +13 -10
  50. adam/commands/export/export_x_select.py +48 -0
  51. adam/commands/export/exporter.py +60 -22
  52. adam/commands/export/import_files.py +44 -0
  53. adam/commands/export/import_session.py +8 -4
  54. adam/commands/export/importer.py +7 -0
  55. adam/commands/export/importer_athena.py +101 -34
  56. adam/commands/export/importer_sqlite.py +30 -5
  57. adam/commands/export/show_column_counts.py +11 -11
  58. adam/commands/export/show_export_databases.py +5 -3
  59. adam/commands/export/show_export_session.py +5 -6
  60. adam/commands/export/show_export_sessions.py +4 -11
  61. adam/commands/export/utils_export.py +42 -14
  62. adam/commands/find_files.py +51 -0
  63. adam/commands/find_processes.py +76 -0
  64. adam/commands/head.py +36 -0
  65. adam/commands/help.py +2 -2
  66. adam/commands/intermediate_command.py +6 -3
  67. adam/commands/ls.py +1 -1
  68. adam/commands/medusa/medusa_backup.py +12 -14
  69. adam/commands/medusa/medusa_restore.py +20 -15
  70. adam/commands/medusa/medusa_show_backupjobs.py +6 -4
  71. adam/commands/medusa/medusa_show_restorejobs.py +5 -3
  72. adam/commands/medusa/utils_medusa.py +15 -0
  73. adam/commands/nodetool.py +3 -8
  74. adam/commands/param_get.py +2 -3
  75. adam/commands/param_set.py +1 -1
  76. adam/commands/postgres/completions_p.py +22 -0
  77. adam/commands/postgres/postgres.py +14 -21
  78. adam/commands/postgres/postgres_databases.py +270 -0
  79. adam/commands/postgres/utils_postgres.py +29 -20
  80. adam/commands/preview_table.py +3 -1
  81. adam/commands/pwd.py +3 -3
  82. adam/commands/reaper/reaper_forward.py +2 -2
  83. adam/commands/reaper/reaper_runs.py +3 -3
  84. adam/commands/reaper/reaper_schedule_activate.py +6 -2
  85. adam/commands/reaper/reaper_schedule_start.py +1 -2
  86. adam/commands/reaper/reaper_schedule_stop.py +1 -2
  87. adam/commands/reaper/utils_reaper.py +13 -6
  88. adam/commands/repair/repair_scan.py +0 -2
  89. adam/commands/repair/repair_stop.py +0 -1
  90. adam/commands/shell.py +7 -5
  91. adam/commands/show/show.py +1 -1
  92. adam/commands/show/show_adam.py +3 -3
  93. adam/commands/show/show_cassandra_repairs.py +5 -3
  94. adam/commands/show/show_cassandra_status.py +27 -20
  95. adam/commands/show/{show_commands.py → show_cli_commands.py} +2 -2
  96. adam/commands/show/show_login.py +2 -2
  97. adam/commands/show/show_params.py +2 -5
  98. adam/commands/show/show_processes.py +15 -14
  99. adam/commands/show/show_storage.py +9 -8
  100. adam/config.py +1 -0
  101. adam/embedded_params.py +1 -1
  102. adam/repl.py +16 -9
  103. adam/repl_commands.py +16 -9
  104. adam/repl_session.py +8 -1
  105. adam/repl_state.py +33 -10
  106. adam/sql/lark_completer.py +280 -0
  107. adam/sql/lark_parser.py +604 -0
  108. adam/sql/sql_state_machine.py +8 -2
  109. adam/utils.py +116 -29
  110. adam/utils_athena.py +7 -8
  111. adam/utils_issues.py +2 -2
  112. adam/utils_k8s/app_clusters.py +2 -2
  113. adam/utils_k8s/app_pods.py +5 -2
  114. adam/utils_k8s/cassandra_clusters.py +11 -3
  115. adam/utils_k8s/cassandra_nodes.py +2 -2
  116. adam/utils_k8s/k8s.py +9 -0
  117. adam/utils_k8s/kube_context.py +2 -2
  118. adam/utils_k8s/pods.py +23 -5
  119. adam/utils_k8s/statefulsets.py +5 -2
  120. adam/utils_local.py +4 -0
  121. adam/utils_repl/appendable_completer.py +6 -0
  122. adam/utils_repl/repl_completer.py +128 -2
  123. adam/utils_sqlite.py +2 -2
  124. adam/version.py +1 -1
  125. {kaqing-2.0.174.dist-info → kaqing-2.0.186.dist-info}/METADATA +1 -1
  126. kaqing-2.0.186.dist-info/RECORD +250 -0
  127. adam/commands/cql/cql_completions.py +0 -32
  128. adam/commands/export/export_select_x.py +0 -54
  129. adam/commands/postgres/postgres_context.py +0 -272
  130. adam/commands/postgres/psql_completions.py +0 -10
  131. kaqing-2.0.174.dist-info/RECORD +0 -230
  132. {kaqing-2.0.174.dist-info → kaqing-2.0.186.dist-info}/WHEEL +0 -0
  133. {kaqing-2.0.174.dist-info → kaqing-2.0.186.dist-info}/entry_points.txt +0 -0
  134. {kaqing-2.0.174.dist-info → kaqing-2.0.186.dist-info}/top_level.txt +0 -0
adam/embedded_params.py CHANGED
@@ -1,2 +1,2 @@
1
1
  def config():
2
- return {'app': {'console-endpoint': 'https://{host}/{env}/{app}/static/console/index.html', 'container-name': 'c3-server', 'cr': {'cluster-regex': '(.*?-.*?)-.*', 'group': 'ops.c3.ai', 'v': 'v2', 'plural': 'c3cassandras'}, 'label': 'c3__app_id-0', 'login': {'admin-group': '{host}/C3.ClusterAdmin', 'ingress': '{app_id}-k8singr-appleader-001', 'timeout': 5, 'session-check-url': 'https://{host}/{env}/{app}/api/8/C3/userSessionToken', 'cache-creds': True, 'cache-username': True, 'url': 'https://{host}/{env}/{app}', 'another': "You're logged in to {has}. However, for this app, you need to log in to {need}.", 'token-server-url': 'http://localhost:{port}', 'password-max-length': 128}, 'strip': '0'}, 'audit': {'endpoint': 'https://4psvtaxlcb.execute-api.us-west-2.amazonaws.com/prod/', 'workers': 3, 'timeout': 10, 'log-audit-queries': False, 'athena': {'auto-repair': {'elapsed_hours': 12}, 'region': 'us-west-2', 'catalog': 'AwsDataCatalog', 'database': 'audit', 'repair-partition-tables': 'audit', 'output': 's3://s3.ops--audit/ddl/results', 'repair-cluster-tables': 'cluster'}}, 'bash': {'workers': 32}, 'cassandra': {'service-name': 'all-pods-service'}, 'cql': {'workers': 32, 'samples': 3, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-superuser', 'password-item': 'password'}, 'alter-tables': {'excludes': 'system_auth,system_traces,reaper_db,system_distributed,system_views,system,system_schema,system_virtual_schema', 'gc-grace-periods': '3600,86400,864000,7776000', 'batching': True}}, 'export': {'workers': 8, 'csv_dir': '/c3/cassandra/tmp', 'column_counts_query': 'select id, count(id) as columns from {table} group by id order by columns desc limit 10', 'default-importer': 'sqlite', 'sqlite': {'workers': 8, 'columns': '<row-key>', 'local-db-dir': '/tmp/qing-db'}, 'athena': {'workers': 8, 'columns': '<keys>', 'bucket': 'c3.ops--qing'}, 'csv': {'workers': 8, 'columns': '<row-key>'}, 'log-prefix': '/tmp/qing'}, 'checks': {'compactions-threshold': 250, 'cpu-busy-threshold': 98.0, 'cpu-threshold': 0.0, 'cassandra-data-path': '/c3/cassandra', 'root-disk-threshold': 50, 'cassandra-disk-threshold': 50, 'snapshot-size-cmd': "ls /c3/cassandra/data/data/*/*/snapshots | grep snapshots | sed 's/:$//g' | xargs -I {} du -sk {} | awk '{print $1}' | awk '{s+=$1} END {print s}'", 'snapshot-size-threshold': '40G', 'table-sizes-cmd': "ls -Al /c3/cassandra/data/data/ | awk '{print $9}' | sed 's/\\^r//g' | xargs -I {} du -sk /c3/cassandra/data/data/{}"}, 'get-host-id': {'workers': 32}, 'idps': {'ad': {'email-pattern': '.*@c3.ai', 'uri': 'https://login.microsoftonline.com/53ad779a-93e7-485c-ba20-ac8290d7252b/oauth2/v2.0/authorize?response_type=id_token&response_mode=form_post&client_id=00ff94a8-6b0a-4715-98e0-95490012d818&scope=openid+email+profile&redirect_uri=https%3A%2F%2Fplat.c3ci.cloud%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://login.microsoftonline.com/common/discovery/keys', 'contact': 'Please contact ted.tran@c3.ai.', 'whitelist-file': '/kaqing/members'}, 'okta': {'default': True, 'email-pattern': '.*@c3iot.com', 'uri': 'https://c3energy.okta.com/oauth2/v1/authorize?response_type=id_token&response_mode=form_post&client_id={client_id}&scope=openid+email+profile+groups&redirect_uri=https%3A%2F%2F{host}%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://c3energy.okta.com/oauth2/v1/keys'}}, 'issues': {'workers': 32}, 'logs': {'path': '/c3/cassandra/logs/system.log'}, 'log-prefix': '/tmp/qing', 'medusa': {'restore-auto-complete': False}, 'nodetool': {'workers': 32, 'samples': 3, 'commands_in_line': 40}, 'pg': {'name-pattern': '^{namespace}.*-k8spg-.*', 'excludes': '.helm., -admin-secret', 'agent': {'name': 'ops-pg-agent', 'just-in-time': False, 'timeout': 86400, 'image': 'seanahnsf/kaqing'}, 'default-db': 'postgres', 'default-schema': 'postgres', 'secret': {'endpoint-key': 'postgres-db-endpoint', 'port-key': 'postgres-db-port', 'username-key': 'postgres-admin-username', 'password-key': 'postgres-admin-password'}}, 'pod': {'name': 'ops', 'image': 'seanahnsf/kaqing-cloud', 'sa': {'name': 'ops', 'proto': 'c3', 'additional-cluster-roles': 'c3aiops-k8ssandra-operator'}, 'label-selector': 'run=ops'}, 'preview': {'rows': 10}, 'processes': {'columns': 'pod,cpu-metrics,mem', 'header': 'POD_NAME,M_CPU(USAGE/LIMIT),MEM/LIMIT'}, 'processes-qing': {'columns': 'pod,cpu,mem', 'header': 'POD_NAME,Q_CPU/TOTAL,MEM/LIMIT'}, 'reaper': {'service-name': 'reaper-service', 'port-forward': {'timeout': 86400, 'local-port': 9001}, 'abort-runs-batch': 10, 'show-runs-batch': 100, 'pod': {'cluster-regex': '(.*?-.*?-.*?-.*?)-.*', 'label-selector': 'k8ssandra.io/reaper={cluster}-reaper'}, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-reaper-ui', 'password-item': 'password'}}, 'repair': {'log-path': '/home/cassrepair/logs/', 'image': 'ci-registry.c3iot.io/cloudops/cassrepair:2.0.14', 'secret': 'ciregistryc3iotio', 'env': {'interval': 24, 'timeout': 60, 'pr': False, 'runs': 1}}, 'repl': {'start-drive': 'c', 'a': {'auto-enter': 'c3/c3/*'}, 'c': {'auto-enter': 'cluster'}, 'x': {'auto-enter': 'latest'}, 'history': {'push-cat-log-file': True, 'push-cat-remote-log-file': True}, 'background-process': {'auto-nohup': True}}, 'status': {'columns': 'status,address,load,tokens,owns,host_id,gossip,compactions', 'header': '--,Address,Load,Tokens,Owns,Host ID,GOSSIP,COMPACTIONS'}, 'storage': {'columns': 'pod,volume_root,volume_cassandra,snapshots,data,compactions', 'header': 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS'}, 'watch': {'auto': 'rollout', 'timeout': 3600, 'interval': 10}, 'debug': False, 'debugs': {'timings': False, 'exit-on-error': False, 'show-parallelism': False}}
2
+ return {'app': {'console-endpoint': 'https://{host}/{env}/{app}/static/console/index.html', 'container-name': 'c3-server', 'cr': {'cluster-regex': '(.*?-.*?)-.*', 'group': 'ops.c3.ai', 'v': 'v2', 'plural': 'c3cassandras'}, 'label': 'c3__app_id-0', 'login': {'admin-group': '{host}/C3.ClusterAdmin', 'ingress': '{app_id}-k8singr-appleader-001', 'timeout': 5, 'session-check-url': 'https://{host}/{env}/{app}/api/8/C3/userSessionToken', 'cache-creds': True, 'cache-username': True, 'url': 'https://{host}/{env}/{app}', 'another': "You're logged in to {has}. However, for this app, you need to log in to {need}.", 'token-server-url': 'http://localhost:{port}', 'password-max-length': 128}, 'strip': '0'}, 'audit': {'endpoint': 'https://4psvtaxlcb.execute-api.us-west-2.amazonaws.com/prod/', 'workers': 3, 'timeout': 10, 'log-audit-queries': False, 'athena': {'auto-repair': {'elapsed_hours': 12}, 'region': 'us-west-2', 'catalog': 'AwsDataCatalog', 'database': 'audit', 'repair-partition-tables': 'audit', 'output': 's3://s3.ops--audit/ddl/results', 'repair-cluster-tables': 'cluster'}, 'queries': {'last10': "SELECT * FROM audit\nWHERE drive <> 'z' and ({date_condition})\nORDER BY ts DESC LIMIT {limit}", 'slow10': "SELECT * FROM audit\nWHERE drive <> 'z' and ({date_condition})\nORDER BY CAST(duration AS REAL) DESC LIMIT {limit}", 'top10': "SELECT min(c) AS cluster, line, COUNT(*) AS cnt, avg(CAST(duration AS REAL)) AS duration\nFROM audit WHERE drive <> 'z' and ({date_condition})\nGROUP BY line ORDER BY cnt DESC LIMIT {limit}"}}, 'bash': {'workers': 32}, 'cassandra': {'service-name': 'all-pods-service'}, 'cql': {'workers': 32, 'samples': 3, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-superuser', 'password-item': 'password'}, 'alter-tables': {'excludes': 'system_auth,system_traces,reaper_db,system_distributed,system_views,system,system_schema,system_virtual_schema', 'gc-grace-periods': '3600,86400,864000,7776000', 'batching': True}}, 'checks': {'compactions-threshold': 250, 'cpu-busy-threshold': 98.0, 'cpu-threshold': 0.0, 'cassandra-data-path': '/c3/cassandra', 'root-disk-threshold': 50, 'cassandra-disk-threshold': 50, 'snapshot-size-cmd': "ls /c3/cassandra/data/data/*/*/snapshots | grep snapshots | sed 's/:$//g' | xargs -I {} du -sk {} | awk '{print $1}' | awk '{s+=$1} END {print s}'", 'snapshot-size-threshold': '40G', 'table-sizes-cmd': "ls -Al /c3/cassandra/data/data/ | awk '{print $9}' | sed 's/\\^r//g' | xargs -I {} du -sk /c3/cassandra/data/data/{}"}, 'download': {'workers': 8}, 'export': {'workers': 8, 'csv_dir': '/c3/cassandra/tmp', 'column_counts_query': 'select id, count(id) as columns from {table} group by id order by columns desc limit 10', 'default-importer': 'sqlite', 'sqlite': {'workers': 8, 'columns': '<row-key>', 'local-db-dir': '/tmp/qing-db'}, 'athena': {'workers': 8, 'columns': '<keys>', 'bucket': 'c3.ops--qing'}, 'csv': {'workers': 8, 'columns': '<row-key>'}, 'log-prefix': '/tmp/qing'}, 'get-host-id': {'workers': 32}, 'idps': {'ad': {'email-pattern': '.*@c3.ai', 'uri': 'https://login.microsoftonline.com/53ad779a-93e7-485c-ba20-ac8290d7252b/oauth2/v2.0/authorize?response_type=id_token&response_mode=form_post&client_id=00ff94a8-6b0a-4715-98e0-95490012d818&scope=openid+email+profile&redirect_uri=https%3A%2F%2Fplat.c3ci.cloud%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://login.microsoftonline.com/common/discovery/keys', 'contact': 'Please contact ted.tran@c3.ai.', 'whitelist-file': '/kaqing/members'}, 'okta': {'default': True, 'email-pattern': '.*@c3iot.com', 'uri': 'https://c3energy.okta.com/oauth2/v1/authorize?response_type=id_token&response_mode=form_post&client_id={client_id}&scope=openid+email+profile+groups&redirect_uri=https%3A%2F%2F{host}%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://c3energy.okta.com/oauth2/v1/keys'}}, 'issues': {'workers': 32}, 'local-tmp-dir': '/tmp/qing-db', 'logs': {'path': '/c3/cassandra/logs/system.log'}, 'log-prefix': '/tmp/qing', 'nodetool': {'workers': 96, 'commands_in_line': 40, 'status': {'workers': 32, 'samples': 3, 'commands_in_line': 40}}, 'pg': {'name-pattern': '^{namespace}.*-k8spg-.*', 'excludes': '.helm., -admin-secret', 'agent': {'name': 'ops-pg-agent', 'just-in-time': False, 'timeout': 86400, 'image': 'seanahnsf/kaqing'}, 'default-db': 'postgres', 'default-schema': 'postgres', 'secret': {'endpoint-key': 'postgres-db-endpoint', 'port-key': 'postgres-db-port', 'username-key': 'postgres-admin-username', 'password-key': 'postgres-admin-password'}}, 'pod': {'name': 'ops', 'image': 'seanahnsf/kaqing-cloud', 'sa': {'name': 'ops', 'proto': 'c3', 'additional-cluster-roles': 'c3aiops-k8ssandra-operator'}, 'label-selector': 'run=ops'}, 'preview': {'rows': 10}, 'processes': {'columns': 'pod,cpu-metrics,mem', 'header': 'POD_NAME,M_CPU(USAGE/LIMIT),MEM/LIMIT'}, 'processes-qing': {'columns': 'pod,cpu,mem', 'header': 'POD_NAME,Q_CPU/TOTAL,MEM/LIMIT'}, 'reaper': {'service-name': 'reaper-service', 'port-forward': {'timeout': 86400, 'local-port': 9001}, 'abort-runs-batch': 10, 'show-runs-batch': 100, 'pod': {'cluster-regex': '(.*?-.*?-.*?-.*?)-.*', 'label-selector': 'k8ssandra.io/reaper={cluster}-reaper'}, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-reaper-ui', 'password-item': 'password'}}, 'repair': {'log-path': '/home/cassrepair/logs/', 'image': 'ci-registry.c3iot.io/cloudops/cassrepair:2.0.14', 'secret': 'ciregistryc3iotio', 'env': {'interval': 24, 'timeout': 60, 'pr': False, 'runs': 1}}, 'repl': {'start-drive': 'c', 'a': {'auto-enter': 'c3/c3/*'}, 'c': {'auto-enter': 'cluster'}, 'x': {'auto-enter': 'latest'}, 'history': {'push-cat-log-file': True, 'push-cat-remote-log-file': True}, 'background-process': {'auto-nohup': True}}, 'status': {'columns': 'status,address,load,tokens,owns,host_id,gossip,compactions', 'header': '--,Address,Load,Tokens,Owns,Host ID,GOSSIP,COMPACTIONS'}, 'storage': {'columns': 'pod,volume_root,volume_cassandra,snapshots,data,compactions', 'header': 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS'}, 'watch': {'auto': 'rollout', 'timeout': 3600, 'interval': 10}, 'auto-complete': {'c': {'tables': 'lazy'}, 'x': {'tables': 'lazy'}, 'cli': {'cp': 'jit'}, 'export': {'databases': 'jit'}, 'medusa': {'backups': 'jit'}, 'reaper': {'schedules': 'lazy'}}, 'debug': False, 'debugs': {'timings': False, 'exit-on-error': False, 'show-parallelism': False}}
adam/repl.py CHANGED
@@ -16,11 +16,16 @@ from adam.log import Log
16
16
  from adam.repl_commands import ReplCommands
17
17
  from adam.repl_session import ReplSession
18
18
  from adam.repl_state import ReplState
19
- from adam.utils import clear_wait_log_flag, debug_trace, deep_merge_dicts, deep_sort_dict, lines_to_tabular, log2, log_exc, log_timing
19
+ from adam.utils import clear_wait_log_flag, debug_trace, deep_sort_dict, tabulize, log2, log_exc, log_timing
20
20
  from adam.apps import Apps
21
- from adam.utils_repl.repl_completer import ReplCompleter
21
+ from adam.utils_repl.repl_completer import ReplCompleter, merge_completions
22
22
  from . import __version__
23
23
 
24
+ import nest_asyncio
25
+ nest_asyncio.apply()
26
+
27
+ import asyncio
28
+
24
29
  def enter_repl(state: ReplState):
25
30
  if os.getenv('QING_DROPPED', 'false') == 'true':
26
31
  log2('You have dropped to bash from another qing instance. Please enter "exit" to go back to qing.')
@@ -68,7 +73,7 @@ def enter_repl(state: ReplState):
68
73
 
69
74
  for c in sorted_cmds:
70
75
  with log_exc(f'* {c.command()} command returned None completions.'):
71
- completions = log_timing(c.command(), lambda: deep_sort_dict(deep_merge_dicts(completions, c.completion(state))))
76
+ completions = log_timing(c.command(), lambda: deep_sort_dict(merge_completions(completions, c.completion(state))))
72
77
 
73
78
  # print(json.dumps(completions, indent=4))
74
79
  completer = ReplCompleter.from_nested_dict(completions)
@@ -88,17 +93,17 @@ def enter_repl(state: ReplState):
88
93
  return state, cmd
89
94
 
90
95
  if state.device == ReplState.A and state.app_app or state.device == ReplState.P:
91
- state.push()
96
+ state.push(pod_targetted=True)
92
97
 
93
98
  state.app_pod = arry[0].strip('@')
94
99
  cmd = ' '.join(arry[1:])
95
100
  elif state.device == ReplState.P:
96
- state.push()
101
+ state.push(pod_targetted=True)
97
102
 
98
103
  state.app_pod = arry[0].strip('@')
99
104
  cmd = ' '.join(arry[1:])
100
105
  elif state.sts:
101
- state.push()
106
+ state.push(pod_targetted=True)
102
107
 
103
108
  state.pod = arry[0].strip('@')
104
109
  cmd = ' '.join(arry[1:])
@@ -143,8 +148,7 @@ def try_device_default_action(state: ReplState, cmds: Command, cmd_list: list[Co
143
148
  if not action_taken:
144
149
  log2(f'* Invalid command: {cmd}')
145
150
  log2()
146
- lines = [c.help(state) for c in cmd_list if c.help(state)]
147
- log2(lines_to_tabular(lines, separator='\t'))
151
+ tabulize([c.help(state) for c in cmd_list if c.help(state)], separator='\t', to=2)
148
152
 
149
153
  return result
150
154
 
@@ -176,6 +180,9 @@ def repl(kubeconfig: str, config: str, param: list[str], cluster:str, namespace:
176
180
  if not KubeContext.init_params(config, param):
177
181
  return
178
182
 
179
- state = ReplState(device=Config().get('repl.start-drive', 'a'), ns_sts=cluster, namespace=namespace, in_repl=True)
183
+ state = ReplState(ns_sts=cluster, namespace=namespace, in_repl=True)
180
184
  state, _ = state.apply_device_arg(extra_args)
185
+ if not state.device:
186
+ state.device=Config().get('repl.start-drive', 'a')
187
+
181
188
  enter_repl(state)
adam/repl_commands.py CHANGED
@@ -7,6 +7,7 @@ from adam.commands.app.show_app_queues import ShowAppQueues
7
7
  from adam.commands.audit.audit import Audit
8
8
  from adam.commands.cat import Cat
9
9
  from adam.commands.code import Code
10
+ from adam.commands.download_file import DownloadFile
10
11
  from adam.commands.deploy.code_start import CodeStart
11
12
  from adam.commands.deploy.code_stop import CodeStop
12
13
  from adam.commands.deploy.deploy import Deploy
@@ -22,22 +23,27 @@ from adam.commands.devices.device_auit_log import DeviceAuditLog
22
23
  from adam.commands.devices.device_cass import DeviceCass
23
24
  from adam.commands.devices.device_export import DeviceExport
24
25
  from adam.commands.devices.device_postgres import DevicePostgres
26
+ from adam.commands.export.download_export_session import DownloadExportSession
25
27
  from adam.commands.export.drop_export_database import DropExportDatabase
26
28
  from adam.commands.export.export import ExportTables
29
+ from adam.commands.export.import_files import ImportCSVFiles
27
30
  from adam.commands.export.import_session import ImportSession
28
31
  from adam.commands.export.clean_up_export_sessions import CleanUpExportSessions
29
32
  from adam.commands.export.clean_up_all_export_sessions import CleanUpAllExportSessions
30
33
  from adam.commands.export.drop_export_databases import DropExportDatabases
31
- from adam.commands.export.export_select import ExportSelect
34
+ from adam.commands.export.export_x_select import ExportXSelect
32
35
  from adam.commands.export.export_use import ExportUse
33
- from adam.commands.export.export_select_x import ExportSelectX
36
+ from adam.commands.export.export_select import ExportSelect
34
37
  from adam.commands.export.show_column_counts import ShowColumnCounts
35
38
  from adam.commands.export.show_export_databases import ShowExportDatabases
36
39
  from adam.commands.export.show_export_session import ShowExportSession
37
40
  from adam.commands.export.show_export_sessions import ShowExportSessions
41
+ from adam.commands.find_files import FindLocalFiles
42
+ from adam.commands.find_processes import FindProcesses
43
+ from adam.commands.head import Head
38
44
  from adam.commands.kubectl import Kubectl
39
45
  from adam.commands.shell import Shell
40
- from adam.commands.cp import ClipboardCopy
46
+ from adam.commands.clipboard_copy import ClipboardCopy
41
47
  from adam.commands.bash.bash import Bash
42
48
  from adam.commands.cd import Cd
43
49
  from adam.commands.check import Check
@@ -61,7 +67,7 @@ from adam.commands.param_set import SetParam
61
67
  from adam.commands.show.show import Show
62
68
  from adam.commands.show.show_cassandra_status import ShowCassandraStatus
63
69
  from adam.commands.show.show_cassandra_version import ShowCassandraVersion
64
- from adam.commands.show.show_commands import ShowKubectlCommands
70
+ from adam.commands.show.show_cli_commands import ShowKubectlCommands
65
71
  from adam.commands.show.show_host import ShowHost
66
72
  from adam.commands.show.show_login import ShowLogin
67
73
  from adam.commands.show.show_params import ShowParams
@@ -93,16 +99,17 @@ class ReplCommands:
93
99
  return deduped
94
100
 
95
101
  def navigation() -> list[Command]:
96
- return [Ls(), PreviewTable(), DeviceApp(), DevicePostgres(), DeviceCass(), DeviceAuditLog(), DeviceExport(), Cd(), Cat(), Pwd(), ClipboardCopy(),
102
+ return [Ls(), PreviewTable(), DeviceApp(), DevicePostgres(), DeviceCass(), DeviceAuditLog(), DeviceExport(),
103
+ Cd(), Cat(), Head(), DownloadFile(), FindLocalFiles(), FindProcesses(), Pwd(), ClipboardCopy(),
97
104
  GetParam(), SetParam(), ShowParams(), ShowKubectlCommands(), ShowLogin(), ShowAdam(), ShowHost()]
98
105
 
99
106
  def cassandra_ops() -> list[Command]:
100
107
  return [Cqlsh(), ShowCassandraStatus(), ShowCassandraVersion(), ShowCassandraRepairs(), ShowStorage(), ShowProcesses(),
101
108
  Check(), Issues(), NodeTool(), Report(), AlterTables(), Bash(),
102
- ExportTables(), ExportSelect(), ExportUse(), ShowExportDatabases(), ShowColumnCounts(),
109
+ ExportTables(), ExportXSelect(), ExportUse(), ShowExportDatabases(), ShowColumnCounts(),
103
110
  DropExportDatabase(), DropExportDatabases(),
104
- ShowExportSessions(), ShowExportSession(),
105
- CleanUpExportSessions(), CleanUpAllExportSessions(), ImportSession()] + \
111
+ ShowExportSessions(), ShowExportSession(), DownloadExportSession(),
112
+ CleanUpExportSessions(), CleanUpAllExportSessions(), ImportSession(), ImportCSVFiles()] + \
106
113
  Medusa().cmd_list() + [Restart(), RollOut(), Watch()] + Reaper().cmd_list() + Repair().cmd_list()
107
114
 
108
115
  def postgres_ops() -> list[Command]:
@@ -115,7 +122,7 @@ class ReplCommands:
115
122
  return [Audit()] + Audit().cmd_list()
116
123
 
117
124
  def export_ops() -> list[Command]:
118
- return [ExportSelectX(), DropExportDatabase(), DropExportDatabases(), ShowColumnCounts()]
125
+ return [ExportSelect(), DropExportDatabase(), DropExportDatabases(), ShowColumnCounts()]
119
126
 
120
127
  def tools() -> list[Command]:
121
128
  return [Shell(), CodeStart(), CodeStop(), DeployFrontend(), UndeployFrontend(), DeployPod(), UndeployPod(), Kubectl(), Code()]
adam/repl_session.py CHANGED
@@ -1,6 +1,8 @@
1
1
  from prompt_toolkit import PromptSession
2
2
  from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
3
3
 
4
+ from adam.config import Config
5
+
4
6
  class ReplSession:
5
7
  # the singleton pattern
6
8
  def __new__(cls, *args, **kwargs):
@@ -10,4 +12,9 @@ class ReplSession:
10
12
 
11
13
  def __init__(self):
12
14
  if not hasattr(self, 'prompt_session'):
13
- self.prompt_session = PromptSession(auto_suggest=AutoSuggestFromHistory())
15
+ self.prompt_session = PromptSession(auto_suggest=AutoSuggestFromHistory())
16
+
17
+ def append_history(self, entry: str):
18
+ if entry and Config().get('repl.history.push-cat-remote-log-file', True):
19
+ if self.prompt_session:
20
+ self.prompt_session.history.append_string(entry)
adam/repl_state.py CHANGED
@@ -3,7 +3,6 @@ from enum import Enum
3
3
  import re
4
4
  from typing import Callable
5
5
 
6
- from adam.commands.postgres.postgres_context import PostgresContext
7
6
  from adam.utils_k8s.app_clusters import AppClusters
8
7
  from adam.utils_k8s.app_pods import AppPods
9
8
  from adam.utils_k8s.cassandra_clusters import CassandraClusters
@@ -62,6 +61,7 @@ class ReplState:
62
61
  self.bash_session = bash_session
63
62
  self.remote_dir = remote_dir
64
63
  self.original_state: ReplState = None
64
+ self.pod_targetted = False
65
65
 
66
66
  self.export_session: str = None
67
67
 
@@ -82,11 +82,11 @@ class ReplState:
82
82
  msg = ''
83
83
  if self.device == ReplState.P:
84
84
  msg = f'{ReplState.P}:'
85
- pg: PostgresContext = PostgresContext.apply(self.namespace, self.pg_path) if self.pg_path else None
86
- if pg and pg.db:
87
- msg += pg.db
88
- elif pg and pg.host:
89
- msg += pg.host
85
+ host, database = self.pg_host_n_database()
86
+ if database:
87
+ msg += database
88
+ elif host:
89
+ msg += host
90
90
  elif self.device == ReplState.A:
91
91
  msg = f'{ReplState.A}:'
92
92
  if self.app_env:
@@ -314,8 +314,8 @@ class ReplState:
314
314
  if self.device != ReplState.P:
315
315
  return (False, None)
316
316
 
317
- pg: PostgresContext = PostgresContext.apply(self.namespace, self.pg_path)
318
- if not pg.db:
317
+ _, database = self.pg_host_n_database()
318
+ if not database:
319
319
  def error():
320
320
  if self.in_repl:
321
321
  log2('cd to a database first.')
@@ -346,7 +346,10 @@ class ReplState:
346
346
  if not self.export_session:
347
347
  def error():
348
348
  if self.in_repl:
349
- log2("Select an export database first with 'use' command.")
349
+ if self.device == ReplState.C:
350
+ log2("Select an export database first with 'use' command.")
351
+ else:
352
+ log2('cd to an export database first.')
350
353
  else:
351
354
  log2('* export database is missing.')
352
355
  log2()
@@ -390,9 +393,10 @@ class ReplState:
390
393
  self.pop()
391
394
  self.bash_session = None
392
395
 
393
- def push(self):
396
+ def push(self, pod_targetted=False):
394
397
  if not self.original_state:
395
398
  self.original_state = copy(self)
399
+ self.pod_targetted = pod_targetted
396
400
 
397
401
  def pop(self):
398
402
  if o := self.original_state:
@@ -407,6 +411,25 @@ class ReplState:
407
411
  self.namespace = o.namespace
408
412
 
409
413
  self.original_state = None
414
+ self.pod_targetted = False
415
+
416
+ def pg_host_n_database(self):
417
+ host = None
418
+ database = None
419
+
420
+ if self.pg_path:
421
+ host_n_db = self.pg_path.split('/')
422
+ host = host_n_db[0]
423
+ if len(host_n_db) > 1:
424
+ database = host_n_db[1]
425
+
426
+ return host, database
427
+
428
+ def with_no_pod(self):
429
+ state1 = copy(self)
430
+ state1.pod = None
431
+
432
+ return state1
410
433
 
411
434
  class DevicePodService:
412
435
  def __init__(self, handler: 'DeviceExecHandler'):
@@ -0,0 +1,280 @@
1
+ import functools
2
+ import os
3
+ from typing import Iterable
4
+ from prompt_toolkit.completion import CompleteEvent, Completer, Completion, NestedCompleter, WordCompleter
5
+ from prompt_toolkit.document import Document
6
+
7
+ from adam.config import Config
8
+ from adam.sql.lark_parser import LarkParser
9
+ from adam.utils import debug, log_timing, offload
10
+ from adam.utils_repl.appendable_completer import AppendableCompleter
11
+ from adam.utils_repl.repl_completer import merge_completions, preload
12
+
13
+ __all__ = [
14
+ "LarkCompleter",
15
+ ]
16
+
17
+ def default_columns(x: list[str]):
18
+ return 'id,x.,y.,z.'.split(',')
19
+
20
+ class LarkCompleter(Completer, AppendableCompleter):
21
+ SYSTEM = 'system'
22
+
23
+ def __init__(self,
24
+ dml: str = None,
25
+ expandables: dict = {},
26
+ variant: str = 'c',
27
+
28
+ name: str = None,
29
+ options_lambda: callable = None,
30
+ auto: str = 'lazy',
31
+ debug = False
32
+ ) -> None:
33
+ self.nested: NestedCompleter = None
34
+ self.options_lambda = options_lambda
35
+ if options_lambda and auto == 'lazy':
36
+ preload(options_lambda, log_key=name)
37
+
38
+ self.variant = variant
39
+ self.parser = None
40
+ self.dml = dml
41
+ self.expandables = expandables
42
+
43
+ self.display_dict = {}
44
+ self.meta_dict = {}
45
+ self.WORD = None
46
+ self.sentence = False
47
+ self.match_middle = False
48
+ self.pattern = None
49
+
50
+ self.debug = debug
51
+
52
+ if variant:
53
+ self.parser = LarkCompleter.lark_parser(variant)
54
+ self.preload_lazy_auto_completes()
55
+
56
+ def __repr__(self):
57
+ return f"LarkCompleter.{self.variant}"
58
+
59
+ def preload_lazy_auto_completes(self):
60
+ for key, value in self.expandables.items():
61
+ if callable(value):
62
+ if self.auto_complete(key) == 'lazy':
63
+ preload(value, log_key=key)
64
+
65
+ def from_lambda(name: str, options_lambda: callable, auto: str = 'lazy'):
66
+ return LarkCompleter(name=name, options_lambda=options_lambda, auto=auto, variant=None)
67
+
68
+ @functools.lru_cache()
69
+ def lark_parser(variant: str):
70
+ dir_path = os.path.dirname(os.path.realpath(__file__))
71
+ with open(dir_path + f"/qingl.lark") as f:
72
+ grammar: str = None
73
+ with log_timing(f'lark.{variant}.file-read'):
74
+ grammar = f.read()
75
+
76
+ common_contexts = {
77
+ 'cd_command.file_name': 'direct-dirs',
78
+ }
79
+
80
+ if variant in ['a', 'c0', 'p0', 'x0']:
81
+ grammar = grammar.replace('start: statement_sequence', f'start: qing_{variant}_statement')
82
+ contexts_by_path = {
83
+ } | common_contexts
84
+
85
+ debug(f'* GRAMMAR replaced to start: qing_{variant}_statement')
86
+
87
+ return LarkParser(grammar, contexts_by_path)
88
+ elif variant == 'system':
89
+ grammar = grammar.replace('start: statement_sequence', f'start: qing_{variant}_statement')
90
+ contexts_by_path = {
91
+ } | common_contexts
92
+
93
+ return LarkParser(grammar, contexts_by_path)
94
+
95
+ grammar = grammar.replace('qing_statement: qing_p_statement', f'qing_statement: qing_{variant}_statement')
96
+ debug(f'* GRAMMAR replaced to qing_statement: qing_{variant}_statement')
97
+
98
+ bash_contexts = {
99
+ 'bash_statement.host_name': 'hosts',
100
+ 'bash_statement.bash_command': 'bash-commands',
101
+ }
102
+
103
+ contexts_by_path = {
104
+ 'describe_keyspace.keyspace_name': 'keyspaces',
105
+ 'keyspace_ref.keyspace_path.namespace_ref.identifier_ref': 'tables',
106
+ 'preview_table_statement.path.identifier_ref': 'tables',
107
+
108
+ 'insert_statement.insert_select': 'column-names',
109
+ 'update_statement.set_clause.path.identifier_ref': 'column-names',
110
+ 'update_statement.where_clause.cond.expr.path.identifier_ref': 'column-names',
111
+ 'delete_statement.where_clause.cond.expr.path.identifier_ref': 'column-names',
112
+
113
+ 'select_clause.projection.result_expr.expr.path.identifier_ref': 'columns',
114
+ 'select_from.where_clause.cond.expr.path.identifier_ref': 'columns',
115
+ 'select_from.where_clause.cond.expr.logical_term.and_expr.cond.expr.path.identifier_ref': 'columns',
116
+ 'select_from.group_by_clause.group_term.expr.path.identifier_ref': 'columns',
117
+ 'select_statement.order_by_clause.ordering_term.expr.path.identifier_ref': 'columns',
118
+ 'select_from.from_clause.from_terms.join_clause.ansi_join_clause.ansi_join_predicate.expr.path.identifier_ref': 'columns',
119
+ 'select_from.from_clause.from_terms.join_clause.ansi_join_clause.ansi_join_predicate.expr.comparison_term.relational_expr.expr.path.identifier_ref': 'columns',
120
+
121
+ 'select_from.from_clause.from_terms.from_generic.alias.identifier_ref': 'column-aliases',
122
+
123
+ 'select_statement.limit_clause.expr.literal.nbr.digit': 'limits',
124
+ } | common_contexts
125
+
126
+ if variant == 'p':
127
+ contexts_by_path = bash_contexts | contexts_by_path
128
+ elif variant == 'c':
129
+ contexts_by_path = {
130
+ 'export_table.path.identifier_ref': 'tables',
131
+ 'show_column_counts_command.path.identifier_ref': 'tables',
132
+ 'export_statement.export_tables.keyspace_name': 'keyspaces',
133
+
134
+ 'alter_cql_table_statement.properties.property.property_name': 'table-props',
135
+ 'alter_tables_statement.properties.property.property_name': 'table-props',
136
+ 'alter_cql_table_statement.properties.property.property_value.literal': 'table-props-value',
137
+
138
+ 'select_clause.projection.result_expr.expr.path.identifier_ref': 'columns',
139
+ 'export_statement.export_tables.export_table.column_name_list.column_name': 'columns',
140
+
141
+ 'consistency_statement.consistency': 'consistencies',
142
+ 'export_statement.export_to.export_database_type': 'export-database-types',
143
+ 'drop_export_database.export_database_name': 'export-databases',
144
+ 'use_export_db_statement.export_database_name': 'export-databases',
145
+ 'clean_up_export_session_statement.clean_up_export_sessions.export_session_name': 'export-sessions',
146
+ 'show_export_command.export_session_name': 'export-sessions',
147
+ 'import_statement.import_session.export_session_name': 'export-sessions-incomplete',
148
+ 'download_session_statement.export_session_name': 'export-sessions-incomplete',
149
+ } | bash_contexts | contexts_by_path
150
+ elif variant == 'l':
151
+ contexts_by_path = {
152
+ 'add_partition_action.partition_ref.partition_name': 'partition-columns',
153
+ 'show_topn_statement.topn_count': 'topn-counts',
154
+ 'show_topn_statement.topn_type': 'topn-types',
155
+ 'show_topn_statement.topn_window': 'topn-windows'
156
+ } | contexts_by_path
157
+ elif variant == 'x':
158
+ contexts_by_path = {
159
+ 'show_column_counts_command.path.identifier_ref': 'tables',
160
+ 'drop_export_database.export_database_name': 'export-databases',
161
+ 'use_export_db_statement.export_database_name': 'export-databases',
162
+ } | contexts_by_path
163
+
164
+ grammar = grammar.replace('select_clause: "SELECT"i hint_comment? projection', 'select_clause: ("SELECT"i | "XELECT"i) hint_comment? projection')
165
+
166
+ with offload():
167
+ with open('/tmp/grammar.lark', 'wt') as f:
168
+ f.write(grammar)
169
+
170
+ return LarkParser(grammar, contexts_by_path)
171
+
172
+ def get_completions(
173
+ self, document: Document, complete_event: CompleteEvent
174
+ ) -> Iterable[Completion]:
175
+ if not self.nested and self.options_lambda:
176
+ # for lazy completions
177
+ self.nested = NestedCompleter.from_nested_dict(self.options_lambda())
178
+
179
+ if self.nested:
180
+ # from NestedCompleter
181
+
182
+ # Split document.
183
+ text = document.text_before_cursor.lstrip()
184
+ stripped_len = len(document.text_before_cursor) - len(text)
185
+
186
+ # If there is a space, check for the first term, and use a
187
+ # subcompleter.
188
+ if " " in text:
189
+ first_term = text.split()[0]
190
+ completer = self.nested.options.get(first_term)
191
+
192
+ # If we have a sub completer, use this for the completions.
193
+ if completer is not None:
194
+ remaining_text = text[len(first_term) :].lstrip()
195
+ move_cursor = len(text) - len(remaining_text) + stripped_len
196
+
197
+ new_document = Document(
198
+ remaining_text,
199
+ cursor_position=document.cursor_position - move_cursor,
200
+ )
201
+
202
+ for c in completer.get_completions(new_document, complete_event):
203
+ yield c
204
+
205
+ # No space in the input: behave exactly like `WordCompleter`.
206
+ else:
207
+ completer = WordCompleter(
208
+ list(self.nested.options.keys()), ignore_case=self.nested.ignore_case
209
+ )
210
+ for c in completer.get_completions(document, complete_event):
211
+ yield c
212
+
213
+ if self.parser:
214
+ full = document.text_before_cursor
215
+ if self.dml:
216
+ full = self.dml + ' ' + full
217
+
218
+ words0 = []
219
+ words1 = []
220
+ context = {}
221
+ for word in self.parser.next_terminals(full, context=context):
222
+ if ex := self.expandable(word):
223
+ if ex in self.expandables:
224
+ e = self.expandables[ex]
225
+ if callable(e):
226
+ if self.auto_complete(ex) != 'off':
227
+ ctx = None
228
+ if 'last-id' in context:
229
+ ctx = context['last-id']
230
+ e = e(ctx)
231
+ words0.extend(e)
232
+ else:
233
+ words0.extend(e)
234
+ else:
235
+ words1.append(word)
236
+ words = words0 + words1
237
+
238
+ word_before_cursor = document.get_word_before_cursor(
239
+ WORD=self.WORD, pattern=self.pattern
240
+ )
241
+
242
+ word_before_cursor = word_before_cursor.lower()
243
+
244
+ def word_matches(word: str) -> bool:
245
+ return word.lower().startswith(word_before_cursor)
246
+
247
+ for a in words:
248
+ if word_matches(a):
249
+ display = self.display_dict.get(a, a)
250
+ display_meta = self.meta_dict.get(a, "")
251
+ yield Completion(
252
+ a,
253
+ -len(word_before_cursor),
254
+ display=display,
255
+ display_meta=display_meta,
256
+ )
257
+
258
+ def completions_for_nesting(self, dml: str = None):
259
+ if dml:
260
+ return {dml: LarkCompleter(dml, expandables=self.expandables, variant=self.variant)}
261
+
262
+ return {
263
+ word.text.lower(): LarkCompleter(word.text, expandables=self.expandables, variant=self.variant)
264
+ for word in self.get_completions(Document(''), None)
265
+ }
266
+
267
+ def expandable(self, word: str):
268
+ return word.strip('`') if word.startswith('`') else None
269
+
270
+ def append_completions(self, key: str, value: dict[str, any]):
271
+ if isinstance(value, LarkCompleter) and self.variant == value.variant:
272
+ return
273
+
274
+ if self.nested:
275
+ self.nested = NestedCompleter.from_nested_dict(merge_completions(self.nested.options, value))
276
+ else:
277
+ self.nested = NestedCompleter.from_nested_dict(value)
278
+
279
+ def auto_complete(self, key: str, default = 'lazy'):
280
+ return Config().get(f'auto-complete.{self.variant}.{key}', default=default)