meerschaum 3.0.0rc4__py3-none-any.whl → 3.0.0rc8__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 (117) hide show
  1. meerschaum/_internal/arguments/_parser.py +14 -2
  2. meerschaum/_internal/cli/__init__.py +6 -0
  3. meerschaum/_internal/cli/daemons.py +103 -0
  4. meerschaum/_internal/cli/entry.py +220 -0
  5. meerschaum/_internal/cli/workers.py +435 -0
  6. meerschaum/_internal/docs/index.py +1 -2
  7. meerschaum/_internal/entry.py +44 -8
  8. meerschaum/_internal/shell/Shell.py +115 -24
  9. meerschaum/_internal/shell/__init__.py +4 -1
  10. meerschaum/_internal/static.py +4 -1
  11. meerschaum/_internal/term/TermPageHandler.py +1 -2
  12. meerschaum/_internal/term/__init__.py +40 -6
  13. meerschaum/_internal/term/tools.py +33 -8
  14. meerschaum/actions/__init__.py +6 -4
  15. meerschaum/actions/api.py +39 -11
  16. meerschaum/actions/attach.py +1 -0
  17. meerschaum/actions/delete.py +4 -2
  18. meerschaum/actions/edit.py +27 -8
  19. meerschaum/actions/login.py +8 -8
  20. meerschaum/actions/register.py +13 -7
  21. meerschaum/actions/reload.py +22 -5
  22. meerschaum/actions/restart.py +14 -0
  23. meerschaum/actions/show.py +69 -4
  24. meerschaum/actions/start.py +135 -14
  25. meerschaum/actions/stop.py +36 -3
  26. meerschaum/actions/sync.py +6 -1
  27. meerschaum/api/__init__.py +35 -13
  28. meerschaum/api/_events.py +2 -2
  29. meerschaum/api/_oauth2.py +47 -4
  30. meerschaum/api/dash/callbacks/dashboard.py +29 -0
  31. meerschaum/api/dash/callbacks/jobs.py +3 -2
  32. meerschaum/api/dash/callbacks/login.py +10 -1
  33. meerschaum/api/dash/callbacks/register.py +9 -2
  34. meerschaum/api/dash/pages/login.py +2 -2
  35. meerschaum/api/dash/pipes.py +72 -36
  36. meerschaum/api/dash/webterm.py +14 -6
  37. meerschaum/api/models/_pipes.py +7 -1
  38. meerschaum/api/resources/static/js/terminado.js +3 -0
  39. meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
  40. meerschaum/api/resources/templates/termpage.html +1 -0
  41. meerschaum/api/routes/_jobs.py +23 -11
  42. meerschaum/api/routes/_login.py +73 -5
  43. meerschaum/api/routes/_pipes.py +6 -4
  44. meerschaum/api/routes/_webterm.py +3 -3
  45. meerschaum/config/__init__.py +60 -13
  46. meerschaum/config/_default.py +89 -61
  47. meerschaum/config/_edit.py +10 -8
  48. meerschaum/config/_formatting.py +2 -0
  49. meerschaum/config/_patch.py +4 -2
  50. meerschaum/config/_paths.py +127 -12
  51. meerschaum/config/_read_config.py +32 -12
  52. meerschaum/config/_version.py +1 -1
  53. meerschaum/config/environment.py +262 -0
  54. meerschaum/config/stack/__init__.py +7 -5
  55. meerschaum/connectors/_Connector.py +1 -2
  56. meerschaum/connectors/__init__.py +37 -2
  57. meerschaum/connectors/api/_APIConnector.py +1 -1
  58. meerschaum/connectors/api/_jobs.py +11 -0
  59. meerschaum/connectors/api/_pipes.py +7 -1
  60. meerschaum/connectors/instance/_plugins.py +9 -1
  61. meerschaum/connectors/instance/_tokens.py +20 -3
  62. meerschaum/connectors/instance/_users.py +8 -1
  63. meerschaum/connectors/parse.py +1 -1
  64. meerschaum/connectors/sql/_create_engine.py +3 -0
  65. meerschaum/connectors/sql/_pipes.py +93 -79
  66. meerschaum/connectors/sql/_users.py +8 -1
  67. meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
  68. meerschaum/connectors/valkey/_pipes.py +7 -5
  69. meerschaum/core/Pipe/__init__.py +45 -71
  70. meerschaum/core/Pipe/_attributes.py +66 -90
  71. meerschaum/core/Pipe/_cache.py +555 -0
  72. meerschaum/core/Pipe/_clear.py +0 -11
  73. meerschaum/core/Pipe/_data.py +0 -50
  74. meerschaum/core/Pipe/_deduplicate.py +0 -13
  75. meerschaum/core/Pipe/_delete.py +12 -21
  76. meerschaum/core/Pipe/_drop.py +11 -23
  77. meerschaum/core/Pipe/_dtypes.py +1 -1
  78. meerschaum/core/Pipe/_index.py +8 -14
  79. meerschaum/core/Pipe/_sync.py +12 -18
  80. meerschaum/core/Plugin/_Plugin.py +7 -1
  81. meerschaum/core/Token/_Token.py +1 -1
  82. meerschaum/core/User/_User.py +1 -2
  83. meerschaum/jobs/_Executor.py +88 -4
  84. meerschaum/jobs/_Job.py +146 -36
  85. meerschaum/jobs/systemd.py +7 -2
  86. meerschaum/plugins/__init__.py +277 -81
  87. meerschaum/utils/daemon/Daemon.py +197 -42
  88. meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
  89. meerschaum/utils/daemon/RotatingFile.py +63 -36
  90. meerschaum/utils/daemon/StdinFile.py +53 -13
  91. meerschaum/utils/daemon/__init__.py +18 -5
  92. meerschaum/utils/daemon/_names.py +6 -3
  93. meerschaum/utils/debug.py +34 -4
  94. meerschaum/utils/dtypes/__init__.py +5 -1
  95. meerschaum/utils/formatting/__init__.py +4 -1
  96. meerschaum/utils/formatting/_jobs.py +1 -1
  97. meerschaum/utils/formatting/_pipes.py +47 -46
  98. meerschaum/utils/formatting/_shell.py +33 -9
  99. meerschaum/utils/misc.py +22 -38
  100. meerschaum/utils/packages/__init__.py +15 -13
  101. meerschaum/utils/packages/_packages.py +1 -0
  102. meerschaum/utils/pipes.py +33 -5
  103. meerschaum/utils/process.py +1 -1
  104. meerschaum/utils/prompt.py +172 -143
  105. meerschaum/utils/sql.py +12 -2
  106. meerschaum/utils/threading.py +42 -0
  107. meerschaum/utils/venv/__init__.py +2 -0
  108. meerschaum/utils/warnings.py +19 -13
  109. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/METADATA +3 -1
  110. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/RECORD +116 -110
  111. meerschaum/config/_environment.py +0 -145
  112. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/WHEEL +0 -0
  113. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/entry_points.txt +0 -0
  114. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/LICENSE +0 -0
  115. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/NOTICE +0 -0
  116. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/top_level.txt +0 -0
  117. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/zip-safe +0 -0
@@ -15,6 +15,7 @@ from dash.exceptions import PreventUpdate
15
15
  from meerschaum.core import User
16
16
  from meerschaum._internal.static import STATIC_CONFIG
17
17
  from meerschaum.utils.packages import attempt_import
18
+ from meerschaum.api._oauth2 import CustomOAuth2PasswordRequestForm
18
19
  dash = attempt_import('dash', check_update=CHECK_UPDATE)
19
20
  from fastapi.exceptions import HTTPException
20
21
 
@@ -97,10 +98,16 @@ def register_button_click(
97
98
  form_class += ' is-invalid'
98
99
  return {}, form_class, dash.no_update
99
100
  try:
100
- _ = login({'username': username, 'password': password})
101
+ form = CustomOAuth2PasswordRequestForm(
102
+ grant_type='password',
103
+ username=username,
104
+ password=password,
105
+ scope=' '.join(STATIC_CONFIG['tokens']['scopes'])
106
+ )
107
+ _ = login(form)
101
108
  session_data = {'session-id': str(uuid.uuid4())}
102
109
  set_session(session_data['session-id'], {'username': username})
103
- except HTTPException as e:
110
+ except HTTPException:
104
111
  form_class += ' is-invalid'
105
112
  session_data = None
106
113
  return session_data, form_class, (dash.no_update if not session_data else endpoints['dash'])
@@ -49,11 +49,11 @@ registration_div = html.Div(
49
49
  html.Code('mrsm register user newuser -i sql:main', className='codeblock'),
50
50
  ),
51
51
  dcc.Markdown("""
52
- To enable online registration, open the `system` configuration file and""" +
52
+ To enable online registration, open the `api` configuration file and""" +
53
53
  """ set the permissions to `true`:"""
54
54
  ),
55
55
  html.Pre(
56
- html.Code('mrsm edit config system', className='codeblock'),
56
+ html.Code('mrsm edit config api', className='codeblock'),
57
57
  ),
58
58
  html.Br(),
59
59
  dcc.Markdown('The settings file should look something like this:'),
@@ -377,48 +377,48 @@ def accordion_items_from_pipe(
377
377
  html.Th(
378
378
  html.Span(
379
379
  "Key",
380
- id={'type': 'key-table-header', 'id': pipe_meta_str},
380
+ id={'type': 'key-table-header', 'index': pipe_meta_str},
381
381
  style={"textDecoration": "underline", "cursor": "pointer"},
382
382
  ),
383
383
  ),
384
384
  html.Th(
385
385
  html.Span(
386
386
  "Column",
387
- id={'type': 'column-table-header', 'id': pipe_meta_str},
387
+ id={'type': 'column-table-header', 'index': pipe_meta_str},
388
388
  style={"textDecoration": "underline", "cursor": "pointer"},
389
389
  ),
390
390
  ),
391
391
  html.Th(
392
392
  html.Span(
393
393
  "Index",
394
- id={'type': 'index-table-header', 'id': pipe_meta_str},
394
+ id={'type': 'index-table-header', 'index': pipe_meta_str},
395
395
  style={"textDecoration": "underline", "cursor": "pointer"},
396
396
  ),
397
397
  ),
398
398
  html.Th(
399
399
  html.Span(
400
400
  "Is Composite",
401
- id={'type': 'is-composite-table-header', 'id': pipe_meta_str},
401
+ id={'type': 'is-composite-table-header', 'index': pipe_meta_str},
402
402
  style={"textDecoration": "underline", "cursor": "pointer"},
403
403
  ),
404
404
  ),
405
405
  dbc.Tooltip(
406
406
  "Unique reference name for the index "
407
407
  "(e.g. `datetime` for the range axis)",
408
- target={'type': 'key-table-header', 'id': pipe_meta_str},
408
+ target={'type': 'key-table-header', 'index': pipe_meta_str},
409
409
  ),
410
410
  dbc.Tooltip(
411
411
  "The actual column (field name) in the target dataset.",
412
- target={'type': 'column-table-header', 'id': pipe_meta_str},
412
+ target={'type': 'column-table-header', 'index': pipe_meta_str},
413
413
  ),
414
414
  dbc.Tooltip(
415
415
  "The name of the index created on the given columns.",
416
- target={'type': 'index-table-header', 'id': pipe_meta_str},
416
+ target={'type': 'index-table-header', 'index': pipe_meta_str},
417
417
  ),
418
418
  dbc.Tooltip(
419
419
  "Whether the column is used in the composite primary key "
420
420
  "to determine updates.",
421
- target={'type': 'is-composite-table-header', 'id': pipe_meta_str},
421
+ target={'type': 'is-composite-table-header', 'index': pipe_meta_str},
422
422
  ),
423
423
  ]
424
424
  )
@@ -483,9 +483,14 @@ def accordion_items_from_pipe(
483
483
  ])
484
484
  )
485
485
 
486
- items_bodies['overview'] = dbc.Table(
487
- overview_header + [html.Tbody(overview_rows)],
488
- bordered=False, hover=True, striped=False,
486
+ items_bodies['overview'] = html.Div(
487
+ dbc.Table(
488
+ overview_header + [html.Tbody(overview_rows)],
489
+ bordered=False,
490
+ hover=True,
491
+ striped=False,
492
+ ),
493
+ style={'overflowX': 'auto'},
489
494
  )
490
495
 
491
496
  if 'stats' in active_items:
@@ -497,17 +502,13 @@ def accordion_items_from_pipe(
497
502
  (newest_time - oldest_time) if newest_time is not None and oldest_time is not None
498
503
  else None
499
504
  )
500
- rowcount = pipe.get_rowcount(debug=debug)
501
505
  except Exception:
502
506
  oldest_time = None
503
507
  newest_time = None
504
508
  interval = None
505
- rowcount = None
506
509
 
507
510
  stats_rows = []
508
- if rowcount is not None:
509
- stats_rows.append(html.Tr([html.Td("Row Count"), html.Td(f"{rowcount:,}")]))
510
- if interval is not None:
511
+ if interval is not None and not isinstance(interval, int):
511
512
  stats_rows.append(
512
513
  html.Tr([html.Td("Timespan"), html.Td(humanfriendly.format_timespan(interval))])
513
514
  )
@@ -516,7 +517,39 @@ def accordion_items_from_pipe(
516
517
  if newest_time is not None:
517
518
  stats_rows.append(html.Tr([html.Td("Newest time"), html.Td(str(newest_time))]))
518
519
 
519
- items_bodies['stats'] = dbc.Table(stats_header + [html.Tbody(stats_rows)], hover=True)
520
+ precision = pipe.precision
521
+ if precision:
522
+ stats_rows.append(
523
+ html.Tr([
524
+ html.Td("Precision"),
525
+ html.Td(str(precision.get('interval', 1)) + ' ' + str(precision.get('unit', 'unit')))
526
+ ])
527
+ )
528
+
529
+ stats_rows.append(
530
+ html.Tr([
531
+ html.Td("Row count"),
532
+ html.Td(
533
+ html.Div(
534
+ dbc.Button(
535
+ "Calculate",
536
+ color='link',
537
+ size='sm',
538
+ style={'text-decoration': 'none'},
539
+ id={'type': 'calculate-rowcount-button', 'index': pipe_meta_str},
540
+ )
541
+ if pipe.exists(debug=debug)
542
+ else '0'
543
+ ),
544
+ id={'type': 'calculate-rowcount-div', 'index': pipe_meta_str},
545
+ )
546
+ ])
547
+ )
548
+
549
+ items_bodies['stats'] = html.Div(
550
+ dbc.Table(stats_header + [html.Tbody(stats_rows)], hover=True),
551
+ style={'overflowX': 'auto'},
552
+ )
520
553
 
521
554
  if 'columns' in active_items:
522
555
  try:
@@ -546,7 +579,7 @@ def accordion_items_from_pipe(
546
579
  mode='norm',
547
580
  tabSize=4,
548
581
  theme='twilight',
549
- id={'type': 'parameters-editor', 'index': json.dumps(pipe.meta)},
582
+ id={'type': 'parameters-editor', 'index': pipe_meta_str},
550
583
  width='100%',
551
584
  height='500px',
552
585
  readOnly=False,
@@ -558,19 +591,19 @@ def accordion_items_from_pipe(
558
591
  )
559
592
  update_parameters_button = dbc.Button(
560
593
  "Update",
561
- id={'type': 'update-parameters-button', 'index': json.dumps(pipe.meta)},
594
+ id={'type': 'update-parameters-button', 'index': pipe_meta_str},
562
595
  )
563
596
 
564
597
  as_yaml_button = dbc.Button(
565
598
  "YAML",
566
- id={'type': 'parameters-as-yaml-button', 'index': json.dumps(pipe.meta)},
599
+ id={'type': 'parameters-as-yaml-button', 'index': pipe_meta_str},
567
600
  color='link',
568
601
  size='sm',
569
602
  style={'text-decoration': 'none'},
570
603
  )
571
604
  as_json_button = dbc.Button(
572
605
  "JSON",
573
- id={'type': 'parameters-as-json-button', 'index': json.dumps(pipe.meta)},
606
+ id={'type': 'parameters-as-json-button', 'index': pipe_meta_str},
574
607
  color='link',
575
608
  size='sm',
576
609
  style={'text-decoration': 'none', 'margin-left': '10px'},
@@ -596,7 +629,7 @@ def accordion_items_from_pipe(
596
629
  html.Div(
597
630
  id={
598
631
  'type': 'update-parameters-success-div',
599
- 'index': json.dumps(pipe.meta),
632
+ 'index': pipe_meta_str,
600
633
  }
601
634
  )
602
635
  ],
@@ -640,7 +673,7 @@ def accordion_items_from_pipe(
640
673
  mode='sql',
641
674
  tabSize=4,
642
675
  theme='twilight',
643
- id={'type': 'sql-editor', 'index': json.dumps(pipe.meta)},
676
+ id={'type': 'sql-editor', 'index': pipe_meta_str},
644
677
  width='100%',
645
678
  height='500px',
646
679
  readOnly=False,
@@ -652,7 +685,7 @@ def accordion_items_from_pipe(
652
685
  )
653
686
  update_sql_button = dbc.Button(
654
687
  "Update",
655
- id={'type': 'update-sql-button', 'index': json.dumps(pipe.meta)},
688
+ id={'type': 'update-sql-button', 'index': pipe_meta_str},
656
689
  )
657
690
  items_bodies['sql'] = html.Div([
658
691
  sql_editor,
@@ -661,7 +694,7 @@ def accordion_items_from_pipe(
661
694
  dbc.Col([update_sql_button], width=2),
662
695
  dbc.Col([
663
696
  html.Div(
664
- id={'type': 'update-sql-success-div', 'index': json.dumps(pipe.meta)}
697
+ id={'type': 'update-sql-success-div', 'index': pipe_meta_str}
665
698
  )
666
699
  ],
667
700
  width=True,
@@ -683,7 +716,7 @@ def accordion_items_from_pipe(
683
716
  mode='norm',
684
717
  tabSize=4,
685
718
  theme='twilight',
686
- id={'type': 'query-editor', 'index': json.dumps(pipe.meta)},
719
+ id={'type': 'query-editor', 'index': pipe_meta_str},
687
720
  width='100%',
688
721
  height='200px',
689
722
  readOnly=False,
@@ -695,17 +728,17 @@ def accordion_items_from_pipe(
695
728
  )
696
729
  query_data_button = dbc.Button(
697
730
  "Query",
698
- id={'type': 'query-data-button', 'index': json.dumps(pipe.meta)},
731
+ id={'type': 'query-data-button', 'index': pipe_meta_str},
699
732
  )
700
733
 
701
734
  begin_end_input_group = dbc.InputGroup(
702
735
  [
703
736
  dbc.Input(
704
- id={'type': 'query-data-begin-input', 'index': json.dumps(pipe.meta)},
737
+ id={'type': 'query-data-begin-input', 'index': pipe_meta_str},
705
738
  placeholder="Begin",
706
739
  ),
707
740
  dbc.Input(
708
- id={'type': 'query-data-end-input', 'index': json.dumps(pipe.meta)},
741
+ id={'type': 'query-data-end-input', 'index': pipe_meta_str},
709
742
  placeholder="End",
710
743
  ),
711
744
  ],
@@ -719,9 +752,12 @@ def accordion_items_from_pipe(
719
752
  value=10,
720
753
  step=1,
721
754
  placeholder="Limit",
722
- id={'type': 'limit-input', 'index': json.dumps(pipe.meta)},
755
+ id={'type': 'limit-input', 'index': pipe_meta_str},
756
+ )
757
+ query_result_div = html.Div(
758
+ id={'type': 'query-result-div', 'index': pipe_meta_str},
759
+ style={'overflowX': 'auto'},
723
760
  )
724
- query_result_div = html.Div(id={'type': 'query-result-div', 'index': json.dumps(pipe.meta)})
725
761
 
726
762
  items_bodies['query-data'] = html.Div([
727
763
  query_editor,
@@ -746,7 +782,7 @@ def accordion_items_from_pipe(
746
782
  mode = 'norm',
747
783
  tabSize = 4,
748
784
  theme = 'twilight',
749
- id = {'type': 'sync-editor', 'index': json.dumps(pipe.meta)},
785
+ id = {'type': 'sync-editor', 'index': pipe_meta_str},
750
786
  width = '100%',
751
787
  height = '500px',
752
788
  readOnly = False,
@@ -759,14 +795,14 @@ def accordion_items_from_pipe(
759
795
 
760
796
  sync_as_json_button = dbc.Button(
761
797
  "JSON",
762
- id={'type': 'sync-as-json-button', 'index': json.dumps(pipe.meta)},
798
+ id={'type': 'sync-as-json-button', 'index': pipe_meta_str},
763
799
  color='link',
764
800
  size='sm',
765
801
  style={'text-decoration': 'none', 'margin-left': '10px'},
766
802
  )
767
803
  sync_as_lines_button = dbc.Button(
768
804
  "Lines",
769
- id={'type': 'sync-as-lines-button', 'index': json.dumps(pipe.meta)},
805
+ id={'type': 'sync-as-lines-button', 'index': pipe_meta_str},
770
806
  color='link',
771
807
  size='sm',
772
808
  style={'text-decoration': 'none', 'margin-left': '10px'},
@@ -774,9 +810,9 @@ def accordion_items_from_pipe(
774
810
 
775
811
  update_sync_button = dbc.Button(
776
812
  "Sync",
777
- id = {'type': 'update-sync-button', 'index': json.dumps(pipe.meta)},
813
+ id = {'type': 'update-sync-button', 'index': pipe_meta_str},
778
814
  )
779
- sync_success_div = html.Div(id={'type': 'sync-success-div', 'index': json.dumps(pipe.meta)})
815
+ sync_success_div = html.Div(id={'type': 'sync-success-div', 'index': pipe_meta_str})
780
816
  items_bodies['sync-data'] = html.Div([
781
817
  sync_editor,
782
818
  html.Br(),
@@ -7,12 +7,13 @@ Functions for interacting with the Webterm via the dashboard.
7
7
  """
8
8
 
9
9
  import time
10
+ from typing import Optional, Tuple, Any
10
11
 
11
12
  import meerschaum as mrsm
12
- from meerschaum.api import CHECK_UPDATE, get_api_connector
13
+ from meerschaum.api import CHECK_UPDATE, get_api_connector, webterm_port
13
14
  from meerschaum.api.dash.sessions import is_session_authenticated, get_username_from_session
14
15
  from meerschaum.api.dash.components import alert_from_success_tuple, console_div
15
- from meerschaum.utils.typing import WebState, Tuple, Any
16
+ from meerschaum.utils.typing import WebState
16
17
  from meerschaum.utils.packages import attempt_import, import_html, import_dcc
17
18
  from meerschaum._internal.term.tools import is_webterm_running
18
19
  from meerschaum.utils.threading import Thread, RLock
@@ -22,7 +23,7 @@ dbc = attempt_import('dash_bootstrap_components', lazy=False, check_update=CHECK
22
23
 
23
24
  MAX_WEBTERM_ATTEMPTS: int = 10
24
25
  TMUX_IS_ENABLED: bool = (
25
- is_tmux_available() and mrsm.get_config('system', 'webterm', 'tmux', 'enabled')
26
+ is_tmux_available() and mrsm.get_config('api', 'webterm', 'tmux', 'enabled')
26
27
  )
27
28
 
28
29
  _locks = {'webterm_thread': RLock()}
@@ -51,7 +52,7 @@ def get_webterm(state: WebState) -> Tuple[Any, Any]:
51
52
  )
52
53
 
53
54
  for i in range(MAX_WEBTERM_ATTEMPTS):
54
- if is_webterm_running('localhost', 8765, session_id=(username or session_id)):
55
+ if is_webterm_running('localhost', webterm_port, session_id=(username or session_id)):
55
56
  return (
56
57
  [
57
58
  html.Div(
@@ -91,7 +92,7 @@ def get_webterm(state: WebState) -> Tuple[Any, Any]:
91
92
 
92
93
 
93
94
  webterm_procs = {}
94
- def start_webterm() -> None:
95
+ def start_webterm(webterm_port: Optional[int] = None) -> None:
95
96
  """
96
97
  Start the webterm thread.
97
98
  """
@@ -101,7 +102,14 @@ def start_webterm() -> None:
101
102
  conn = get_api_connector()
102
103
  _ = run_python_package(
103
104
  'meerschaum',
104
- ['start', 'webterm', '-i', str(conn)],
105
+ (
106
+ ['start', 'webterm', '-i', str(conn)]
107
+ + (
108
+ []
109
+ if not webterm_port
110
+ else ['-p', str(webterm_port)]
111
+ )
112
+ ),
105
113
  capture_output=True,
106
114
  as_proc=True,
107
115
  store_proc_dict=webterm_procs,
@@ -54,7 +54,13 @@ class PipeModel(BasePipeModel):
54
54
 
55
55
 
56
56
  class FetchPipesKeysResponseModel(
57
- RootModel[List[Tuple[ConnectorKeysModel, MetricKeyModel, LocationKeyModel]]]
57
+ RootModel[
58
+ Union[
59
+ List[Tuple[ConnectorKeysModel, MetricKeyModel, LocationKeyModel]],
60
+ List[Tuple[ConnectorKeysModel, MetricKeyModel, LocationKeyModel, Dict[str, Any]]],
61
+ List[Tuple[ConnectorKeysModel, MetricKeyModel, LocationKeyModel, List[str]]],
62
+ ]
63
+ ]
58
64
  ):
59
65
  """
60
66
  A list of tuples containing connector, metric, and location keys.
@@ -11,7 +11,10 @@ function make_terminal(element, size, ws_url) {
11
11
  useStyle: true,
12
12
  scrollback: 9999999,
13
13
  cursorBlink: true,
14
+ allowProposedApi: true
14
15
  });
16
+ term.loadAddon(new Unicode11Addon.Unicode11Addon());
17
+ term.unicode.activeVersion = '11';
15
18
  term.attachCustomKeyEventHandler(copyPasteKeyEventHandler);
16
19
  term.open(element);
17
20
 
@@ -0,0 +1,2 @@
1
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Unicode11Addon=t():e.Unicode11Addon=t()}(this,(()=>(()=>{"use strict";var e={433:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeV11=void 0;const r=i(938),s=[[768,879],[1155,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1541],[1552,1562],[1564,1564],[1611,1631],[1648,1648],[1750,1757],[1759,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2045,2045],[2070,2073],[2075,2083],[2085,2087],[2089,2093],[2137,2139],[2259,2306],[2362,2362],[2364,2364],[2369,2376],[2381,2381],[2385,2391],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2558,2558],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2641,2641],[2672,2673],[2677,2677],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2810,2815],[2817,2817],[2876,2876],[2879,2879],[2881,2884],[2893,2893],[2902,2902],[2914,2915],[2946,2946],[3008,3008],[3021,3021],[3072,3072],[3076,3076],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3170,3171],[3201,3201],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3328,3329],[3387,3388],[3393,3396],[3405,3405],[3426,3427],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3981,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4151],[4153,4154],[4157,4158],[4184,4185],[4190,4192],[4209,4212],[4226,4226],[4229,4230],[4237,4237],[4253,4253],[4448,4607],[4957,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6158],[6277,6278],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6683,6683],[6742,6742],[6744,6750],[6752,6752],[6754,6754],[6757,6764],[6771,6780],[6783,6783],[6832,6846],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7040,7041],[7074,7077],[7080,7081],[7083,7085],[7142,7142],[7144,7145],[7149,7149],[7151,7153],[7212,7219],[7222,7223],[7376,7378],[7380,7392],[7394,7400],[7405,7405],[7412,7412],[7416,7417],[7616,7673],[7675,7679],[8203,8207],[8234,8238],[8288,8292],[8294,8303],[8400,8432],[11503,11505],[11647,11647],[11744,11775],[12330,12333],[12441,12442],[42607,42610],[42612,42621],[42654,42655],[42736,42737],[43010,43010],[43014,43014],[43019,43019],[43045,43046],[43204,43205],[43232,43249],[43263,43263],[43302,43309],[43335,43345],[43392,43394],[43443,43443],[43446,43449],[43452,43453],[43493,43493],[43561,43566],[43569,43570],[43573,43574],[43587,43587],[43596,43596],[43644,43644],[43696,43696],[43698,43700],[43703,43704],[43710,43711],[43713,43713],[43756,43757],[43766,43766],[44005,44005],[44008,44008],[44013,44013],[64286,64286],[65024,65039],[65056,65071],[65279,65279],[65529,65531]],n=[[66045,66045],[66272,66272],[66422,66426],[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[68325,68326],[68900,68903],[69446,69456],[69633,69633],[69688,69702],[69759,69761],[69811,69814],[69817,69818],[69821,69821],[69837,69837],[69888,69890],[69927,69931],[69933,69940],[70003,70003],[70016,70017],[70070,70078],[70089,70092],[70191,70193],[70196,70196],[70198,70199],[70206,70206],[70367,70367],[70371,70378],[70400,70401],[70459,70460],[70464,70464],[70502,70508],[70512,70516],[70712,70719],[70722,70724],[70726,70726],[70750,70750],[70835,70840],[70842,70842],[70847,70848],[70850,70851],[71090,71093],[71100,71101],[71103,71104],[71132,71133],[71219,71226],[71229,71229],[71231,71232],[71339,71339],[71341,71341],[71344,71349],[71351,71351],[71453,71455],[71458,71461],[71463,71467],[71727,71735],[71737,71738],[72148,72151],[72154,72155],[72160,72160],[72193,72202],[72243,72248],[72251,72254],[72263,72263],[72273,72278],[72281,72283],[72330,72342],[72344,72345],[72752,72758],[72760,72765],[72767,72767],[72850,72871],[72874,72880],[72882,72883],[72885,72886],[73009,73014],[73018,73018],[73020,73021],[73023,73029],[73031,73031],[73104,73105],[73109,73109],[73111,73111],[73459,73460],[78896,78904],[92912,92916],[92976,92982],[94031,94031],[94095,94098],[113821,113822],[113824,113827],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[121344,121398],[121403,121452],[121461,121461],[121476,121476],[121499,121503],[121505,121519],[122880,122886],[122888,122904],[122907,122913],[122915,122916],[122918,122922],[123184,123190],[123628,123631],[125136,125142],[125252,125258],[917505,917505],[917536,917631],[917760,917999]],o=[[4352,4447],[8986,8987],[9001,9002],[9193,9196],[9200,9200],[9203,9203],[9725,9726],[9748,9749],[9800,9811],[9855,9855],[9875,9875],[9889,9889],[9898,9899],[9917,9918],[9924,9925],[9934,9934],[9940,9940],[9962,9962],[9970,9971],[9973,9973],[9978,9978],[9981,9981],[9989,9989],[9994,9995],[10024,10024],[10060,10060],[10062,10062],[10067,10069],[10071,10071],[10133,10135],[10160,10160],[10175,10175],[11035,11036],[11088,11088],[11093,11093],[11904,11929],[11931,12019],[12032,12245],[12272,12283],[12288,12329],[12334,12350],[12353,12438],[12443,12543],[12549,12591],[12593,12686],[12688,12730],[12736,12771],[12784,12830],[12832,12871],[12880,19903],[19968,42124],[42128,42182],[43360,43388],[44032,55203],[63744,64255],[65040,65049],[65072,65106],[65108,65126],[65128,65131],[65281,65376],[65504,65510]],c=[[94176,94179],[94208,100343],[100352,101106],[110592,110878],[110928,110930],[110948,110951],[110960,111355],[126980,126980],[127183,127183],[127374,127374],[127377,127386],[127488,127490],[127504,127547],[127552,127560],[127568,127569],[127584,127589],[127744,127776],[127789,127797],[127799,127868],[127870,127891],[127904,127946],[127951,127955],[127968,127984],[127988,127988],[127992,128062],[128064,128064],[128066,128252],[128255,128317],[128331,128334],[128336,128359],[128378,128378],[128405,128406],[128420,128420],[128507,128591],[128640,128709],[128716,128716],[128720,128722],[128725,128725],[128747,128748],[128756,128762],[128992,129003],[129293,129393],[129395,129398],[129402,129442],[129445,129450],[129454,129482],[129485,129535],[129648,129651],[129656,129658],[129664,129666],[129680,129685],[131072,196605],[196608,262141]];let l;function d(e,t){let i,r=0,s=t.length-1;if(e<t[0][0]||e>t[s][1])return!1;for(;s>=r;)if(i=r+s>>1,e>t[i][1])r=i+1;else{if(!(e<t[i][0]))return!0;s=i-1}return!1}t.UnicodeV11=class{constructor(){if(this.version="11",!l){l=new Uint8Array(65536),l.fill(1),l[0]=0,l.fill(0,1,32),l.fill(0,127,160);for(let e=0;e<s.length;++e)l.fill(0,s[e][0],s[e][1]+1);for(let e=0;e<o.length;++e)l.fill(2,o[e][0],o[e][1]+1)}}wcwidth(e){return e<32?0:e<127?1:e<65536?l[e]:d(e,n)?0:d(e,c)?2:1}charProperties(e,t){let i=this.wcwidth(e),s=0===i&&0!==t;if(s){const e=r.UnicodeService.extractWidth(t);0===e?s=!1:e>i&&(i=e)}return r.UnicodeService.createPropertyValue(0,i,s)}}},345:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.runAndSubscribe=t.forwardEvent=t.EventEmitter=void 0,t.EventEmitter=class{constructor(){this._listeners=[],this._disposed=!1}get event(){return this._event||(this._event=e=>(this._listeners.push(e),{dispose:()=>{if(!this._disposed)for(let t=0;t<this._listeners.length;t++)if(this._listeners[t]===e)return void this._listeners.splice(t,1)}})),this._event}fire(e,t){const i=[];for(let e=0;e<this._listeners.length;e++)i.push(this._listeners[e]);for(let r=0;r<i.length;r++)i[r].call(void 0,e,t)}dispose(){this.clearListeners(),this._disposed=!0}clearListeners(){this._listeners&&(this._listeners.length=0)}},t.forwardEvent=function(e,t){return e((e=>t.fire(e)))},t.runAndSubscribe=function(e,t){return t(void 0),e((e=>t(e)))}},490:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeV6=void 0;const r=i(938),s=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],n=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]];let o;t.UnicodeV6=class{constructor(){if(this.version="6",!o){o=new Uint8Array(65536),o.fill(1),o[0]=0,o.fill(0,1,32),o.fill(0,127,160),o.fill(2,4352,4448),o[9001]=2,o[9002]=2,o.fill(2,11904,42192),o[12351]=1,o.fill(2,44032,55204),o.fill(2,63744,64256),o.fill(2,65040,65050),o.fill(2,65072,65136),o.fill(2,65280,65377),o.fill(2,65504,65511);for(let e=0;e<s.length;++e)o.fill(0,s[e][0],s[e][1]+1)}}wcwidth(e){return e<32?0:e<127?1:e<65536?o[e]:function(e,t){let i,r=0,s=t.length-1;if(e<t[0][0]||e>t[s][1])return!1;for(;s>=r;)if(i=r+s>>1,e>t[i][1])r=i+1;else{if(!(e<t[i][0]))return!0;s=i-1}return!1}(e,n)?0:e>=131072&&e<=196605||e>=196608&&e<=262141?2:1}charProperties(e,t){let i=this.wcwidth(e),s=0===i&&0!==t;if(s){const e=r.UnicodeService.extractWidth(t);0===e?s=!1:e>i&&(i=e)}return r.UnicodeService.createPropertyValue(0,i,s)}}},938:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeService=void 0;const r=i(345),s=i(490);class n{static extractShouldJoin(e){return 0!=(1&e)}static extractWidth(e){return e>>1&3}static extractCharKind(e){return e>>3}static createPropertyValue(e,t,i=!1){return(16777215&e)<<3|(3&t)<<1|(i?1:0)}constructor(){this._providers=Object.create(null),this._active="",this._onChange=new r.EventEmitter,this.onChange=this._onChange.event;const e=new s.UnicodeV6;this.register(e),this._active=e.version,this._activeProvider=e}dispose(){this._onChange.dispose()}get versions(){return Object.keys(this._providers)}get activeVersion(){return this._active}set activeVersion(e){if(!this._providers[e])throw new Error(`unknown Unicode version "${e}"`);this._active=e,this._activeProvider=this._providers[e],this._onChange.fire(e)}register(e){this._providers[e.version]=e}wcwidth(e){return this._activeProvider.wcwidth(e)}getStringCellWidth(e){let t=0,i=0;const r=e.length;for(let s=0;s<r;++s){let o=e.charCodeAt(s);if(55296<=o&&o<=56319){if(++s>=r)return t+this.wcwidth(o);const i=e.charCodeAt(s);56320<=i&&i<=57343?o=1024*(o-55296)+i-56320+65536:t+=this.wcwidth(i)}const c=this.charProperties(o,i);let l=n.extractWidth(c);n.extractShouldJoin(c)&&(l-=n.extractWidth(i)),t+=l,i=c}return t}charProperties(e,t){return this._activeProvider.charProperties(e,t)}}t.UnicodeService=n}},t={};function i(r){var s=t[r];if(void 0!==s)return s.exports;var n=t[r]={exports:{}};return e[r](n,n.exports,i),n.exports}var r={};return(()=>{var e=r;Object.defineProperty(e,"__esModule",{value:!0}),e.Unicode11Addon=void 0;const t=i(433);e.Unicode11Addon=class{activate(e){e.unicode.register(new t.UnicodeV11)}dispose(){}}})(),r})()));
2
+ //# sourceMappingURL=addon-unicode11.js.map
@@ -9,6 +9,7 @@
9
9
  </style>
10
10
  <link rel="stylesheet" href="{{ static('css/xterm.css') }}"/>
11
11
  <script src="{{ static('js/xterm.js') }}"></script>
12
+ <script src="{{ static('js/xterm-addon-unicode11.js') }}"></script>
12
13
  <script src="{{ static('js/terminado.js') }}"></script>
13
14
  <script>
14
15
 
@@ -54,7 +54,7 @@ def _get_job(name: str, sysargs: Union[str, List[str], None] = None):
54
54
 
55
55
  @app.get(endpoints['jobs'], tags=['Jobs'])
56
56
  def get_jobs(
57
- curr_user=fastapi.Depends(ScopedAuth(['jobs:read'])),
57
+ curr_user=fastapi.Security(ScopedAuth(['jobs:read'])),
58
58
  ) -> Dict[str, Dict[str, Any]]:
59
59
  """
60
60
  Return metadata about the current jobs.
@@ -83,7 +83,7 @@ def get_jobs(
83
83
  @app.get(endpoints['jobs'] + '/{name}', tags=['Jobs'])
84
84
  def get_job(
85
85
  name: str,
86
- curr_user=fastapi.Depends(ScopedAuth(['jobs:read'])),
86
+ curr_user=fastapi.Security(ScopedAuth(['jobs:read'])),
87
87
  ) -> Dict[str, Any]:
88
88
  """
89
89
  Return metadata for a single job.
@@ -134,7 +134,7 @@ def clean_sysargs(sysargs: List[str]) -> List[str]:
134
134
  def create_job(
135
135
  name: str,
136
136
  metadata: Union[List[str], Dict[str, Any]],
137
- curr_user=fastapi.Depends(ScopedAuth(['jobs:execute', 'jobs:write'])),
137
+ curr_user=fastapi.Security(ScopedAuth(['jobs:execute', 'jobs:write'])),
138
138
  ) -> SuccessTuple:
139
139
  """
140
140
  Create and start a new job.
@@ -167,7 +167,7 @@ def create_job(
167
167
  @app.delete(endpoints['jobs'] + '/{name}', tags=['Jobs'])
168
168
  def delete_job(
169
169
  name: str,
170
- curr_user=fastapi.Depends(ScopedAuth(['jobs:delete'])),
170
+ curr_user=fastapi.Security(ScopedAuth(['jobs:delete'])),
171
171
  ) -> SuccessTuple:
172
172
  """
173
173
  Delete a job.
@@ -182,7 +182,7 @@ def delete_job(
182
182
  @app.get(endpoints['jobs'] + '/{name}/exists', tags=['Jobs'])
183
183
  def get_job_exists(
184
184
  name: str,
185
- curr_user=fastapi.Depends(ScopedAuth(['jobs:read'])),
185
+ curr_user=fastapi.Security(ScopedAuth(['jobs:read'])),
186
186
  ) -> bool:
187
187
  """
188
188
  Return whether a job exists.
@@ -194,7 +194,7 @@ def get_job_exists(
194
194
  @app.get(endpoints['logs'] + '/{name}', tags=['Jobs'])
195
195
  def get_logs(
196
196
  name: str,
197
- curr_user=fastapi.Depends(ScopedAuth(['jobs:read', 'logs:read'])),
197
+ curr_user=fastapi.Security(ScopedAuth(['jobs:read', 'logs:read'])),
198
198
  ) -> Union[str, None]:
199
199
  """
200
200
  Return a job's log text.
@@ -213,7 +213,7 @@ def get_logs(
213
213
  @app.post(endpoints['jobs'] + '/{name}/start', tags=['Jobs'])
214
214
  def start_job(
215
215
  name: str,
216
- curr_user=fastapi.Depends(ScopedAuth(['jobs:execute'])),
216
+ curr_user=fastapi.Security(ScopedAuth(['jobs:execute'])),
217
217
  ) -> SuccessTuple:
218
218
  """
219
219
  Start a job if stopped.
@@ -234,7 +234,7 @@ def start_job(
234
234
  @app.post(endpoints['jobs'] + '/{name}/stop', tags=['Jobs'])
235
235
  def stop_job(
236
236
  name: str,
237
- curr_user=fastapi.Depends(ScopedAuth(['jobs:execute', 'josb:stop'])),
237
+ curr_user=fastapi.Security(ScopedAuth(['jobs:execute', 'josb:stop'])),
238
238
  ) -> SuccessTuple:
239
239
  """
240
240
  Stop a job if running.
@@ -255,7 +255,7 @@ def stop_job(
255
255
  @app.post(endpoints['jobs'] + '/{name}/pause', tags=['Jobs'])
256
256
  def pause_job(
257
257
  name: str,
258
- curr_user=fastapi.Depends(ScopedAuth(['jobs:execute', 'jobs:pause'])),
258
+ curr_user=fastapi.Security(ScopedAuth(['jobs:execute', 'jobs:pause'])),
259
259
  ) -> SuccessTuple:
260
260
  """
261
261
  Pause a job if running.
@@ -276,7 +276,7 @@ def pause_job(
276
276
  @app.get(endpoints['jobs'] + '/{name}/stop_time', tags=['Jobs'])
277
277
  def get_stop_time(
278
278
  name: str,
279
- curr_user=fastapi.Depends(ScopedAuth(['jobs:read'])),
279
+ curr_user=fastapi.Security(ScopedAuth(['jobs:read'])),
280
280
  ) -> Union[datetime, None]:
281
281
  """
282
282
  Get the timestamp when the job was manually stopped.
@@ -288,7 +288,7 @@ def get_stop_time(
288
288
  @app.get(endpoints['jobs'] + '/{name}/is_blocking_on_stdin', tags=['Jobs'])
289
289
  def get_is_blocking_on_stdin(
290
290
  name: str,
291
- curr_user=fastapi.Depends(ScopedAuth(['jobs:read'])),
291
+ curr_user=fastapi.Security(ScopedAuth(['jobs:read'])),
292
292
  ) -> bool:
293
293
  """
294
294
  Return whether a job is blocking on stdin.
@@ -297,6 +297,18 @@ def get_is_blocking_on_stdin(
297
297
  return job.is_blocking_on_stdin()
298
298
 
299
299
 
300
+ @app.get(endpoints['jobs'] + '{name}/prompt_kwargs', tags=['Jobs'])
301
+ def get_prompt_kwargs(
302
+ name: str,
303
+ curr_user=fastapi.Security(ScopedAuth(['jobs:read'])),
304
+ ) -> Dict[str, Any]:
305
+ """
306
+ Return the kwargs for the blocking `prompt`, if available.
307
+ """
308
+ job = _get_job(name)
309
+ return job.get_prompt_kwargs()
310
+
311
+
300
312
  _job_clients = defaultdict(lambda: [])
301
313
  _job_stop_events = defaultdict(lambda: asyncio.Event())
302
314
  _job_queues = defaultdict(lambda: asyncio.Queue())