meerschaum 2.7.7__py3-none-any.whl → 2.7.9__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.
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
  )