meerschaum 2.7.7__py3-none-any.whl → 2.7.9__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. meerschaum/_internal/term/TermPageHandler.py +54 -4
  2. meerschaum/_internal/term/__init__.py +13 -5
  3. meerschaum/_internal/term/tools.py +41 -6
  4. meerschaum/actions/copy.py +1 -0
  5. meerschaum/actions/start.py +25 -10
  6. meerschaum/api/dash/callbacks/dashboard.py +43 -2
  7. meerschaum/api/dash/components.py +13 -6
  8. meerschaum/api/dash/keys.py +82 -108
  9. meerschaum/api/dash/pages/dashboard.py +17 -17
  10. meerschaum/api/dash/sessions.py +1 -0
  11. meerschaum/api/dash/webterm.py +17 -6
  12. meerschaum/api/resources/static/js/terminado.js +0 -2
  13. meerschaum/api/resources/templates/termpage.html +47 -4
  14. meerschaum/api/routes/_webterm.py +15 -11
  15. meerschaum/config/_default.py +6 -0
  16. meerschaum/config/_version.py +1 -1
  17. meerschaum/config/static/__init__.py +2 -2
  18. meerschaum/connectors/sql/_SQLConnector.py +2 -9
  19. meerschaum/connectors/sql/_fetch.py +5 -30
  20. meerschaum/connectors/sql/_pipes.py +7 -4
  21. meerschaum/connectors/sql/_sql.py +56 -31
  22. meerschaum/connectors/valkey/_ValkeyConnector.py +2 -2
  23. meerschaum/core/Pipe/_fetch.py +4 -0
  24. meerschaum/core/Pipe/_sync.py +22 -15
  25. meerschaum/core/Pipe/_verify.py +1 -1
  26. meerschaum/utils/daemon/Daemon.py +24 -11
  27. meerschaum/utils/daemon/RotatingFile.py +3 -3
  28. meerschaum/utils/dataframe.py +42 -12
  29. meerschaum/utils/dtypes/__init__.py +153 -24
  30. meerschaum/utils/dtypes/sql.py +58 -9
  31. meerschaum/utils/formatting/__init__.py +2 -2
  32. meerschaum/utils/formatting/_pprint.py +13 -12
  33. meerschaum/utils/misc.py +32 -18
  34. meerschaum/utils/prompt.py +1 -1
  35. meerschaum/utils/sql.py +26 -8
  36. meerschaum/utils/venv/__init__.py +10 -14
  37. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/METADATA +1 -1
  38. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/RECORD +44 -44
  39. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/LICENSE +0 -0
  40. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/NOTICE +0 -0
  41. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/WHEEL +0 -0
  42. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/entry_points.txt +0 -0
  43. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/top_level.txt +0 -0
  44. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/zip-safe +0 -0
@@ -7,14 +7,64 @@ Define the terminal page handler class.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
- from meerschaum.utils.packages import attempt_import
11
- tornado_web = attempt_import('tornado.web', lazy=False)
10
+ from typing import Any
11
+
12
+ import meerschaum as mrsm
13
+ from meerschaum.utils.warnings import warn
14
+ tornado_web = mrsm.attempt_import('tornado.web', lazy=False)
15
+ terminado = mrsm.attempt_import('terminado', lazy=False)
16
+
17
+ tmux_suffix = mrsm.get_config('system', 'webterm', 'tmux', 'session_suffix')
18
+
12
19
 
13
20
  class TermPageHandler(tornado_web.RequestHandler):
14
- def get(self):
21
+
22
+ def get(self, term_name):
23
+ term_manager = self.application.settings['term_manager']
24
+ shell_command = [
25
+ cmd.replace('MRSM_SESSION', term_name + tmux_suffix)
26
+ for cmd in term_manager.shell_command
27
+ ]
28
+ terminal = term_manager.new_terminal(shell_command=shell_command)
29
+ terminal.term_name = term_name
30
+ term_manager.terminals[term_name] = terminal
31
+ term_manager.start_reading(terminal)
15
32
  return self.render(
16
33
  "termpage.html",
17
34
  static=self.static_url,
18
- ws_url_path="/websocket",
35
+ ws_url_path=f"/_websocket/{term_name}",
19
36
  )
20
37
 
38
+
39
+ class CustomTermSocket(terminado.TermSocket):
40
+
41
+ def open(self, url_component: Any = None) -> None:
42
+ super(terminado.TermSocket, self).open(url_component)
43
+ url_component = (
44
+ url_component.decode("utf-8")
45
+ if isinstance(url_component, bytes)
46
+ else url_component
47
+ )
48
+ self.term_name = url_component
49
+ shell_command = [
50
+ cmd.replace('MRSM_SESSION', self.term_name + tmux_suffix)
51
+ for cmd in self.term_manager.shell_command
52
+ ]
53
+ if self.term_name not in self.term_manager.terminals:
54
+ self.terminal = self.term_manager.new_terminal(shell_command=shell_command)
55
+ self.terminal.term_name = self.term_name
56
+ self.term_manager.terminals[self.term_name] = self.terminal
57
+ self.term_manager.start_reading(self.terminal)
58
+ else:
59
+ self.terminal = self.term_manager.terminals[self.term_name]
60
+ self.terminal.clients.append(self)
61
+ self.send_json_message(["setup", {}])
62
+ buffered = ""
63
+ preopen_buffer = self.terminal.read_buffer.copy()
64
+ while True:
65
+ if not preopen_buffer:
66
+ break
67
+ s = preopen_buffer.popleft()
68
+ buffered += s
69
+ if buffered:
70
+ self.on_pty_read(buffered)
@@ -9,10 +9,14 @@ Build the web console virtual terminal using Tornado and xterm.js.
9
9
  from __future__ import annotations
10
10
 
11
11
  from typing import Optional, Tuple
12
+
13
+ import meerschaum as mrsm
12
14
  from meerschaum.utils.packages import attempt_import
13
- from meerschaum._internal.term.TermPageHandler import TermPageHandler
15
+ from meerschaum._internal.term.TermPageHandler import TermPageHandler, CustomTermSocket
14
16
  from meerschaum.config._paths import API_TEMPLATES_PATH, API_STATIC_PATH
15
17
  from meerschaum.utils.venv import venv_executable
18
+ from meerschaum.utils.misc import is_tmux_available
19
+ from meerschaum.utils.daemon._names import get_new_daemon_name
16
20
 
17
21
  tornado, tornado_ioloop, terminado = attempt_import(
18
22
  'tornado', 'tornado.ioloop', 'terminado', lazy=False,
@@ -40,16 +44,19 @@ def get_webterm_app_and_manager(
40
44
  "from meerschaum._internal.entry import get_shell; "
41
45
  f"get_shell({shell_kwargs_str}).cmdloop()"
42
46
  ]
47
+ webterm_cf = mrsm.get_config('system', 'webterm')
48
+ if webterm_cf.get('tmux', {}).get('enabled', False) and is_tmux_available():
49
+ commands = ['tmux', 'new-session', '-A', '-s', 'MRSM_SESSION'] + commands
43
50
 
44
- term_manager = terminado.UniqueTermManager(shell_command=commands)
51
+ term_manager = terminado.NamedTermManager(shell_command=commands)
45
52
  handlers = [
46
53
  (
47
- r"/websocket",
48
- terminado.TermSocket,
54
+ r"/_websocket/(.+)/?",
55
+ CustomTermSocket,
49
56
  {'term_manager': term_manager}
50
57
  ),
51
58
  (
52
- r"/",
59
+ r"/webterm/(.+)/?",
53
60
  TermPageHandler
54
61
  ),
55
62
  ]
@@ -57,5 +64,6 @@ def get_webterm_app_and_manager(
57
64
  handlers,
58
65
  static_path=API_STATIC_PATH,
59
66
  template_path=API_TEMPLATES_PATH,
67
+ term_manager=term_manager,
60
68
  )
61
69
  return tornado_app, term_manager
@@ -6,16 +6,21 @@
6
6
  Utility functions regarding the webterm.
7
7
  """
8
8
 
9
- from __future__ import annotations
9
+ import meerschaum as mrsm
10
+ from meerschaum.utils.typing import List
10
11
 
11
- def is_webterm_running(host: str, port: int, protocol: str = 'http') -> int:
12
+
13
+ def is_webterm_running(
14
+ host: str,
15
+ port: int,
16
+ protocol: str = 'http',
17
+ session_id: str = 'mrsm',
18
+ ) -> int:
12
19
  """
13
20
  Determine whether the webterm service is running on a given host and port.
14
21
  """
15
- from meerschaum.utils.networking import find_open_ports, is_port_in_use
16
- from meerschaum.utils.packages import attempt_import
17
- requests = attempt_import('requests')
18
- url = f'{protocol}://{host}:{port}'
22
+ requests = mrsm.attempt_import('requests', lazy=False)
23
+ url = f'{protocol}://{host}:{port}/webterm/{session_id}'
19
24
  try:
20
25
  r = requests.get(url, timeout=3)
21
26
  except Exception as e:
@@ -23,3 +28,33 @@ def is_webterm_running(host: str, port: int, protocol: str = 'http') -> int:
23
28
  if not r:
24
29
  return False
25
30
  return '<title>Meerschaum Shell</title>' in r.text
31
+
32
+
33
+ def kill_tmux_session(session: str) -> bool:
34
+ """
35
+ Kill a tmux session if it exists.
36
+ """
37
+ from meerschaum.utils.process import run_process
38
+ command = ['tmux', 'kill-session', '-t', session]
39
+ return run_process(command, capture_output=True) == 0
40
+
41
+
42
+ def get_mrsm_tmux_sessions() -> List[str]:
43
+ """
44
+ Return a list of tmux sessions created by Meerschaum.
45
+ """
46
+ from meerschaum.utils.process import run_process
47
+ tmux_suffix = mrsm.get_config('system', 'webterm', 'tmux', 'session_suffix')
48
+ command = ['tmux', 'ls']
49
+ proc = run_process(command, capture_output=True, as_proc=True)
50
+ if proc.returncode != 0:
51
+ return []
52
+ sessions = [
53
+ line.split(':', maxsplit=1)[0]
54
+ for line in proc.stdout.read().decode('utf-8').split('\n')
55
+ ]
56
+ return [
57
+ session
58
+ for session in sessions
59
+ if session.endswith(tmux_suffix)
60
+ ]
@@ -9,6 +9,7 @@ Functions for copying elements.
9
9
  from __future__ import annotations
10
10
  from meerschaum.utils.typing import Any, SuccessTuple, Optional, List
11
11
 
12
+
12
13
  def copy(
13
14
  action: Optional[List[str]] = None,
14
15
  **kw : Any
@@ -314,6 +314,11 @@ def _start_gui(
314
314
  """
315
315
  Start the Meerschaum GUI application.
316
316
  """
317
+ import json
318
+ import time
319
+ import uuid
320
+ import platform
321
+
317
322
  from meerschaum.utils.venv import venv_exec
318
323
  from meerschaum.utils.packages import attempt_import
319
324
  from meerschaum.utils.warnings import warn
@@ -321,17 +326,14 @@ def _start_gui(
321
326
  from meerschaum.utils.networking import find_open_ports
322
327
  from meerschaum.connectors.parse import parse_instance_keys
323
328
  from meerschaum._internal.term.tools import is_webterm_running
324
- import platform
325
329
  webview, requests = attempt_import('webview', 'requests')
326
- import json
327
- import time
328
330
 
329
331
  success, msg = True, "Success"
330
332
  host = '127.0.0.1'
331
333
  if port is None:
332
334
  port = 8765
333
335
 
334
- if not is_webterm_running(host, port):
336
+ if not is_webterm_running(host, port, session_id='mrsm'):
335
337
  port = next(find_open_ports(port, 9000))
336
338
 
337
339
  api_kw = {
@@ -350,7 +352,7 @@ def _start_gui(
350
352
  )
351
353
  if debug:
352
354
  print(start_tornado_code)
353
- base_url = 'http://' + api_kw['host'] + ':' + str(api_kw['port'])
355
+ base_url = 'http://' + api_kw['host'] + ':' + str(api_kw['port']) + '/webterm/mrsm'
354
356
 
355
357
  process = venv_exec(
356
358
  start_tornado_code, as_proc=True, debug=debug, capture_output=(not debug)
@@ -375,7 +377,7 @@ def _start_gui(
375
377
  try:
376
378
  webview.create_window(
377
379
  'Meerschaum Shell',
378
- f'http://127.0.0.1:{port}',
380
+ f'http://127.0.0.1:{port}/webterm/mrsm',
379
381
  height=650,
380
382
  width=1000
381
383
  )
@@ -415,8 +417,13 @@ def _start_webterm(
415
417
  - `-i`, '--instance'
416
418
  The default instance to use for the Webterm shell.
417
419
  """
420
+ import uuid
418
421
  from meerschaum._internal.term import get_webterm_app_and_manager, tornado_ioloop
419
- from meerschaum._internal.term.tools import is_webterm_running
422
+ from meerschaum._internal.term.tools import (
423
+ is_webterm_running,
424
+ get_mrsm_tmux_sessions,
425
+ kill_tmux_session,
426
+ )
420
427
  from meerschaum.utils.networking import find_open_ports
421
428
  from meerschaum.utils.warnings import info
422
429
 
@@ -426,19 +433,23 @@ def _start_webterm(
426
433
  port = 8765
427
434
  if sysargs is None:
428
435
  sysargs = ['start', 'webterm']
436
+ session_id = 'mrsm'
429
437
  tornado_app, term_manager = get_webterm_app_and_manager(instance_keys=mrsm_instance)
430
438
 
431
- if is_webterm_running(host, port):
439
+ if is_webterm_running(host, port, session_id=session_id):
432
440
  if force:
433
441
  port = next(find_open_ports(port + 1, 9000))
434
442
  else:
435
443
  return False, (
436
- f"The webterm is already running at http://{host}:{port}\n\n"
444
+ f"The webterm is already running at http://{host}:{port}/webterm/{session_id}\n\n"
437
445
  + " Include `-f` to start another server on a new port\n"
438
446
  + " or specify a different port with `-p`."
439
447
  )
440
448
  if not nopretty:
441
- info(f"Starting the webterm at http://{host}:{port} ...\n Press CTRL+C to quit.")
449
+ info(
450
+ f"Starting the webterm at http://{host}:{port}/webterm/{session_id} ..."
451
+ "\n Press CTRL+C to quit."
452
+ )
442
453
  tornado_app.listen(port, host)
443
454
  loop = tornado_ioloop.IOLoop.instance()
444
455
  try:
@@ -451,6 +462,10 @@ def _start_webterm(
451
462
  term_manager.shutdown()
452
463
  loop.close()
453
464
 
465
+ sessions = get_mrsm_tmux_sessions()
466
+ for session in sessions:
467
+ kill_tmux_session(session)
468
+
454
469
  return True, "Success"
455
470
 
456
471
 
@@ -190,6 +190,7 @@ def update_page_layout_div(
190
190
  Input('go-button', 'n_clicks'),
191
191
  Input('get-pipes-button', 'n_clicks'),
192
192
  Input('get-jobs-button', 'n_clicks'),
193
+ Input('show-webterm-button', 'n_clicks'),
193
194
  Input('get-plugins-button', 'n_clicks'),
194
195
  Input('get-users-button', 'n_clicks'),
195
196
  Input('get-graphs-button', 'n_clicks'),
@@ -229,7 +230,7 @@ def update_content(*args):
229
230
  ### NOTE: functions MUST return a list of content and a list of alerts
230
231
  triggers = {
231
232
  'go-button': lambda x: ([], []),
232
- 'cancel-button': lambda x: ([], []),
233
+ 'show-webterm-button': lambda x: ([], []),
233
234
  'get-pipes-button': get_pipes_cards,
234
235
  'get-jobs-button': get_jobs_cards,
235
236
  'get-plugins-button': get_plugins_cards,
@@ -251,7 +252,12 @@ def update_content(*args):
251
252
  webterm_style = {
252
253
  'display': (
253
254
  'none'
254
- if trigger not in ('instance-select', 'cancel-button', 'go-button')
255
+ if trigger not in (
256
+ 'instance-select',
257
+ 'cancel-button',
258
+ 'go-button',
259
+ 'show-webterm-button',
260
+ )
255
261
  else 'block'
256
262
  )
257
263
  }
@@ -650,6 +656,41 @@ dash_app.clientside_callback(
650
656
  State('mrsm-location', 'href'),
651
657
  )
652
658
 
659
+ dash_app.clientside_callback(
660
+ """
661
+ function(n_clicks, url){
662
+ if (!n_clicks) { return dash_clientside.no_update; }
663
+ iframe = document.getElementById('webterm-iframe');
664
+ if (!iframe){ return dash_clientside.no_update; }
665
+
666
+ iframe.contentWindow.postMessage(
667
+ {
668
+ action: "__TMUX_NEW_WINDOW"
669
+ },
670
+ url
671
+ );
672
+ return dash_clientside.no_update;
673
+ }
674
+ """,
675
+ Output('mrsm-location', 'href'),
676
+ Input('webterm-new-tab-button', 'n_clicks'),
677
+ State('mrsm-location', 'href'),
678
+ )
679
+
680
+ dash_app.clientside_callback(
681
+ """
682
+ function(n_clicks, url){
683
+ if (!n_clicks) { return dash_clientside.no_update; }
684
+ iframe = document.getElementById('webterm-iframe');
685
+ if (!iframe){ return dash_clientside.no_update; }
686
+ iframe.src = iframe.src;
687
+ return dash_clientside.no_update;
688
+ }
689
+ """,
690
+ Output('mrsm-location', 'href'),
691
+ Input('webterm-refresh-button', 'n_clicks'),
692
+ State('mrsm-location', 'href'),
693
+ )
653
694
 
654
695
  @dash_app.callback(
655
696
  Output(component_id='connector-keys-input', component_property='value'),
@@ -23,7 +23,7 @@ dex = attempt_import('dash_extensions', lazy=False, check_update=CHECK_UPDATE)
23
23
  dash_ace = attempt_import('dash_ace', lazy=False, check_update=CHECK_UPDATE)
24
24
  _load_builtin_custom_connectors()
25
25
 
26
- go_button = dbc.Button('Execute', id='go-button', color='primary', style={'width': '100%'})
26
+ go_button = dbc.Button('Exec', id='go-button', color='primary', style={'width': '100%'})
27
27
  test_button = dbc.Button('Test', id='test-button', color='danger', style={'display': 'none'})
28
28
  get_items_menu = dbc.DropdownMenu(
29
29
  label='More', id='get-items-menu', children=[
@@ -50,19 +50,26 @@ show_jobs_button = dbc.Button(
50
50
  style={'width': '100%'},
51
51
  )
52
52
  cancel_button = dbc.Button(
53
- 'Terminal',
53
+ 'Term',
54
54
  id='cancel-button',
55
55
  color='dark',
56
56
  style={'width': '100%', 'background-color': 'black', 'display': 'none'},
57
57
  )
58
+ show_webterm_button = dbc.Button(
59
+ 'Term',
60
+ id='show-webterm-button',
61
+ color='dark',
62
+ style={'width': '100%', 'background-color': 'black'},
63
+ )
58
64
  bottom_buttons_content = dbc.Card(
59
65
  dbc.CardBody(
60
66
  dbc.Row([
61
- dbc.Col(go_button, lg=3, md=4, sm=12),
62
- dbc.Col(show_pipes_button, lg=3, md=4, sm=12),
63
- dbc.Col(show_jobs_button, lg=3, md=4, sm=12),
67
+ dbc.Col(go_button, xl=2, lg=3, md=3, sm=12),
68
+ dbc.Col(show_pipes_button, xl=2, lg=3, md=3, sm=12),
69
+ dbc.Col(show_jobs_button, xl=2, lg=3, md=3, sm=12),
70
+ dbc.Col(show_webterm_button, xl=2, lg=3, md=3, sm=12),
64
71
  dbc.Col(lg=True, md=False, sm=False),
65
- dbc.Col(get_items_menu, lg=2, md=12, sm=12),
72
+ dbc.Col(get_items_menu, xl=2, lg=12, md=12, sm=12),
66
73
  ])
67
74
  )
68
75
  )