meerschaum 2.2.5.dev3__py3-none-any.whl → 2.2.6__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 (48) hide show
  1. meerschaum/__init__.py +4 -1
  2. meerschaum/_internal/arguments/_parser.py +13 -2
  3. meerschaum/_internal/docs/index.py +523 -26
  4. meerschaum/_internal/term/__init__.py +2 -2
  5. meerschaum/actions/bootstrap.py +13 -14
  6. meerschaum/actions/python.py +11 -8
  7. meerschaum/actions/register.py +130 -32
  8. meerschaum/actions/show.py +79 -71
  9. meerschaum/actions/stop.py +11 -11
  10. meerschaum/api/dash/callbacks/login.py +21 -13
  11. meerschaum/api/dash/pages/login.py +2 -2
  12. meerschaum/api/routes/_login.py +5 -5
  13. meerschaum/config/__init__.py +8 -1
  14. meerschaum/config/_paths.py +20 -2
  15. meerschaum/config/_version.py +1 -1
  16. meerschaum/config/paths.py +21 -2
  17. meerschaum/config/static/__init__.py +1 -0
  18. meerschaum/connectors/Connector.py +7 -2
  19. meerschaum/connectors/__init__.py +7 -5
  20. meerschaum/connectors/api/APIConnector.py +7 -2
  21. meerschaum/connectors/api/_actions.py +23 -31
  22. meerschaum/connectors/api/_uri.py +5 -5
  23. meerschaum/core/Pipe/__init__.py +7 -3
  24. meerschaum/core/Pipe/_data.py +23 -15
  25. meerschaum/core/Pipe/_deduplicate.py +1 -1
  26. meerschaum/core/Pipe/_dtypes.py +5 -0
  27. meerschaum/core/Pipe/_fetch.py +18 -16
  28. meerschaum/core/Pipe/_sync.py +20 -15
  29. meerschaum/plugins/_Plugin.py +6 -6
  30. meerschaum/plugins/__init__.py +1 -1
  31. meerschaum/utils/daemon/RotatingFile.py +15 -16
  32. meerschaum/utils/dataframe.py +12 -4
  33. meerschaum/utils/debug.py +9 -15
  34. meerschaum/utils/formatting/__init__.py +13 -12
  35. meerschaum/utils/misc.py +117 -11
  36. meerschaum/utils/packages/__init__.py +7 -1
  37. meerschaum/utils/typing.py +1 -0
  38. meerschaum/utils/venv/__init__.py +5 -1
  39. meerschaum/utils/warnings.py +8 -1
  40. meerschaum/utils/yaml.py +2 -2
  41. {meerschaum-2.2.5.dev3.dist-info → meerschaum-2.2.6.dist-info}/METADATA +1 -1
  42. {meerschaum-2.2.5.dev3.dist-info → meerschaum-2.2.6.dist-info}/RECORD +48 -48
  43. {meerschaum-2.2.5.dev3.dist-info → meerschaum-2.2.6.dist-info}/WHEEL +1 -1
  44. {meerschaum-2.2.5.dev3.dist-info → meerschaum-2.2.6.dist-info}/LICENSE +0 -0
  45. {meerschaum-2.2.5.dev3.dist-info → meerschaum-2.2.6.dist-info}/NOTICE +0 -0
  46. {meerschaum-2.2.5.dev3.dist-info → meerschaum-2.2.6.dist-info}/entry_points.txt +0 -0
  47. {meerschaum-2.2.5.dev3.dist-info → meerschaum-2.2.6.dist-info}/top_level.txt +0 -0
  48. {meerschaum-2.2.5.dev3.dist-info → meerschaum-2.2.6.dist-info}/zip-safe +0 -0
@@ -9,14 +9,14 @@ from __future__ import annotations
9
9
  from meerschaum.utils.typing import SuccessTuple, Any, List, Optional
10
10
 
11
11
  def python(
12
- action: Optional[List[str]] = None,
13
- sub_args: Optional[List[str]] = None,
14
- nopretty: bool = False,
15
- noask: bool = False,
16
- venv: Optional[str] = None,
17
- debug: bool = False,
18
- **kw: Any
19
- ) -> SuccessTuple:
12
+ action: Optional[List[str]] = None,
13
+ sub_args: Optional[List[str]] = None,
14
+ nopretty: bool = False,
15
+ noask: bool = False,
16
+ venv: Optional[str] = None,
17
+ debug: bool = False,
18
+ **kw: Any
19
+ ) -> SuccessTuple:
20
20
  """
21
21
  Launch a virtual environment's Python interpreter with Meerschaum imported.
22
22
  You may pass flags to the Python binary by surrounding each flag with `[]`.
@@ -56,6 +56,9 @@ def python(
56
56
  if action is None:
57
57
  action = []
58
58
 
59
+ if noask:
60
+ nopretty = True
61
+
59
62
  joined_actions = (
60
63
  ["import meerschaum as mrsm"]
61
64
  if venv is None and not sub_args
@@ -7,12 +7,13 @@ Register new Pipes. Requires the API to be running.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
+ import meerschaum as mrsm
10
11
  from meerschaum.utils.typing import SuccessTuple, Any, List, Optional, Dict
11
12
 
12
13
  def register(
13
- action: Optional[List[str]] = None,
14
- **kw: Any
15
- ) -> SuccessTuple:
14
+ action: Optional[List[str]] = None,
15
+ **kw: Any
16
+ ) -> SuccessTuple:
16
17
  """
17
18
  Register new items (pipes, plugins, users).
18
19
 
@@ -23,22 +24,25 @@ def register(
23
24
  'pipes' : _register_pipes,
24
25
  'plugins' : _register_plugins,
25
26
  'users' : _register_users,
27
+ 'connectors': _register_connectors,
26
28
  }
27
29
  return choose_subaction(action, options, **kw)
28
30
 
29
31
 
30
32
  def _complete_register(
31
- action: Optional[List[str]] = None,
32
- **kw: Any
33
- ) -> List[str]:
33
+ action: Optional[List[str]] = None,
34
+ **kw: Any
35
+ ) -> List[str]:
34
36
  """
35
37
  Override the default Meerschaum `complete_` function.
36
38
  """
37
39
  if action is None:
38
40
  action = []
39
41
  options = {
40
- 'plugin' : _complete_register_plugins,
41
- 'plugins' : _complete_register_plugins,
42
+ 'plugin': _complete_register_plugins,
43
+ 'plugins': _complete_register_plugins,
44
+ 'connector': _complete_register_connectors,
45
+ 'connectors': _complete_register_connectors,
42
46
  }
43
47
 
44
48
  if len(action) > 0 and action[0] in options:
@@ -51,14 +55,14 @@ def _complete_register(
51
55
 
52
56
 
53
57
  def _register_pipes(
54
- connector_keys: Optional[List[str]] = None,
55
- metric_keys: Optional[List[str]] = None,
56
- location_keys: Optional[List[str]] = None,
57
- params: Optional[Dict[str, Any]] = None,
58
- tags: Optional[List[str]] = None,
59
- debug: bool = False,
60
- **kw: Any
61
- ) -> SuccessTuple:
58
+ connector_keys: Optional[List[str]] = None,
59
+ metric_keys: Optional[List[str]] = None,
60
+ location_keys: Optional[List[str]] = None,
61
+ params: Optional[Dict[str, Any]] = None,
62
+ tags: Optional[List[str]] = None,
63
+ debug: bool = False,
64
+ **kw: Any
65
+ ) -> SuccessTuple:
62
66
  """
63
67
  Create and register Pipe objects.
64
68
 
@@ -147,15 +151,18 @@ def _register_pipes(
147
151
 
148
152
 
149
153
  def _register_plugins(
150
- action: Optional[List[str]] = None,
151
- repository: Optional[str] = None,
152
- shell: bool = False,
153
- debug: bool = False,
154
- yes: bool = False,
155
- noask: bool = False,
156
- force: bool = False,
157
- **kw: Any
158
- ) -> SuccessTuple:
154
+ action: Optional[List[str]] = None,
155
+ repository: Optional[str] = None,
156
+ shell: bool = False,
157
+ debug: bool = False,
158
+ yes: bool = False,
159
+ noask: bool = False,
160
+ force: bool = False,
161
+ **kw: Any
162
+ ) -> SuccessTuple:
163
+ """
164
+ Upload plugins to an API instance (repository).
165
+ """
159
166
  from meerschaum.utils.debug import dprint
160
167
  from meerschaum.plugins import reload_plugins, get_plugins_names
161
168
  from meerschaum.connectors.parse import parse_repo_keys
@@ -246,17 +253,19 @@ def _register_plugins(
246
253
  reload_plugins(debug=debug)
247
254
  return total_success > 0, msg
248
255
 
256
+
249
257
  def _complete_register_plugins(*args, **kw):
250
258
  from meerschaum.actions.uninstall import _complete_uninstall_plugins
251
259
  return _complete_uninstall_plugins(*args, **kw)
252
260
 
261
+
253
262
  def _register_users(
254
- action: Optional[List[str]] = None,
255
- mrsm_instance: Optional[str] = None,
256
- shell: bool = False,
257
- debug: bool = False,
258
- **kw: Any
259
- ) -> SuccessTuple:
263
+ action: Optional[List[str]] = None,
264
+ mrsm_instance: Optional[str] = None,
265
+ shell: bool = False,
266
+ debug: bool = False,
267
+ **kw: Any
268
+ ) -> SuccessTuple:
260
269
  """
261
270
  Register a new user to a Meerschaum instance.
262
271
  """
@@ -294,7 +303,7 @@ def _register_users(
294
303
  nonregistered_users.append(user)
295
304
 
296
305
  ### prompt for passwords and emails, then try to register
297
- success = dict()
306
+ success = {}
298
307
  successfully_registered_users = set()
299
308
  for _user in nonregistered_users:
300
309
  try:
@@ -337,6 +346,95 @@ def _register_users(
337
346
  )
338
347
  return succeeded > 0, msg
339
348
 
349
+
350
+ def _register_connectors(
351
+ action: Optional[List[str]] = None,
352
+ connector_keys: Optional[List[str]] = None,
353
+ params: Optional[Dict[str, Any]] = None,
354
+ **kwargs: Any
355
+ ) -> SuccessTuple:
356
+ """
357
+ Create new connectors programmatically with `--params`.
358
+ See `bootstrap connector`.
359
+
360
+ Examples:
361
+
362
+ mrsm register connector sql:tmp --params 'uri:sqlite:////tmp/tmp.db'
363
+
364
+ mrsm register connector -c sql:new --params '{"database": "/tmp/new.db"}'
365
+ """
366
+ from meerschaum.config import get_config, write_config
367
+ from meerschaum.utils.prompt import yes_no
368
+ from meerschaum.utils.warnings import warn
369
+ all_keys = (action or []) + (connector_keys or [])
370
+ if len(all_keys) != 1:
371
+ return (
372
+ False,
373
+ "Provide one pair of keys for the connector to be registered."
374
+ )
375
+
376
+ keys = all_keys[0]
377
+
378
+ if keys.count(':') != 1:
379
+ return False, "Connector keys must be in the format `type:label`."
380
+
381
+ type_, label = keys.split(':', maxsplit=1)
382
+ mrsm_config = get_config('meerschaum')
383
+ if 'connectors' not in mrsm_config:
384
+ mrsm_config['connectors'] = {}
385
+
386
+ if type_ not in mrsm_config['connectors']:
387
+ mrsm_config['connectors'] = {}
388
+
389
+ is_new = True
390
+ if label in mrsm_config['connectors'][type_]:
391
+ rich_table, rich_json, rich_box = mrsm.attempt_import(
392
+ 'rich.table',
393
+ 'rich.json',
394
+ 'rich.box',
395
+ )
396
+ existing_params = mrsm_config['connectors'][type_][label]
397
+ if existing_params == params:
398
+ return True, "Connector exists, nothing to do."
399
+
400
+ table = rich_table.Table(box=rich_box.MINIMAL)
401
+ table.add_column('Existing Parameters')
402
+ table.add_column('New Parameters')
403
+ table.add_row(
404
+ rich_json.JSON.from_data(existing_params),
405
+ rich_json.JSON.from_data(params or {}),
406
+ )
407
+
408
+ mrsm.pprint(table)
409
+ warn(f"Connector '{keys}' already exists.", stack=False)
410
+ if not yes_no(
411
+ f"Do you want to overwrite connector '{keys}'?",
412
+ default='n',
413
+ **kwargs
414
+ ):
415
+ return False, "Nothing was changed."
416
+
417
+ is_new = False
418
+
419
+ mrsm_config['connectors'][type_][label] = params
420
+ if not write_config({'meerschaum': mrsm_config}):
421
+ return False, "Failed to update configuration."
422
+
423
+ msg = (
424
+ "Successfully "
425
+ + ("registered" if is_new else "updated")
426
+ + f" connector '{keys}'."
427
+ )
428
+ return True, msg
429
+
430
+
431
+ def _complete_register_connectors(
432
+ action: Optional[List[str]] = None, **kw: Any
433
+ ) -> List[str]:
434
+ from meerschaum.actions.show import _complete_show_connectors
435
+ return _complete_show_connectors(action)
436
+
437
+
340
438
  ### NOTE: This must be the final statement of the module.
341
439
  ### Any subactions added below these lines will not
342
440
  ### be added to the `help` docstring.
@@ -48,9 +48,9 @@ def show(
48
48
 
49
49
 
50
50
  def _complete_show(
51
- action: Optional[List[str]] = None,
52
- **kw: Any
53
- ) -> List[str]:
51
+ action: Optional[List[str]] = None,
52
+ **kw: Any
53
+ ) -> List[str]:
54
54
  """
55
55
  Override the default Meerschaum `complete_` function.
56
56
  """
@@ -91,7 +91,13 @@ def _show_actions(**kw: Any) -> SuccessTuple:
91
91
  from meerschaum.utils.misc import print_options
92
92
  from meerschaum._internal.shell.Shell import hidden_commands
93
93
  _actions = [ _a for _a in actions if _a not in hidden_commands ]
94
- print_options(options=_actions, name='actions', actions=False, **kw)
94
+ print_options(
95
+ options=_actions,
96
+ name='actions',
97
+ actions=False,
98
+ sort_options=True,
99
+ **kw
100
+ )
95
101
  return True, "Success"
96
102
 
97
103
 
@@ -247,16 +253,16 @@ def _show_connectors(
247
253
 
248
254
 
249
255
  def _complete_show_connectors(
250
- action: Optional[List[str]] = None, **kw: Any
251
- ) -> List[str]:
256
+ action: Optional[List[str]] = None, **kw: Any
257
+ ) -> List[str]:
252
258
  from meerschaum.utils.misc import get_connector_labels
253
259
  _text = action[0] if action else ""
254
260
  return get_connector_labels(search_term=_text, ignore_exact_match=True)
255
261
 
256
262
 
257
263
  def _show_arguments(
258
- **kw: Any
259
- ) -> SuccessTuple:
264
+ **kw: Any
265
+ ) -> SuccessTuple:
260
266
  """
261
267
  Show the parsed keyword arguments.
262
268
  """
@@ -266,16 +272,16 @@ def _show_arguments(
266
272
 
267
273
 
268
274
  def _show_data(
269
- action: Optional[List[str]] = None,
270
- gui: bool = False,
271
- begin: Optional[datetime.datetime] = None,
272
- end: Optional[datetime.datetime] = None,
273
- params: Optional[Dict[str, Any]] = None,
274
- chunksize: Optional[int] = -1,
275
- nopretty: bool = False,
276
- debug: bool = False,
277
- **kw: Any
278
- ) -> SuccessTuple:
275
+ action: Optional[List[str]] = None,
276
+ gui: bool = False,
277
+ begin: Optional[datetime.datetime] = None,
278
+ end: Optional[datetime.datetime] = None,
279
+ params: Optional[Dict[str, Any]] = None,
280
+ chunksize: Optional[int] = -1,
281
+ nopretty: bool = False,
282
+ debug: bool = False,
283
+ **kw: Any
284
+ ) -> SuccessTuple:
279
285
  """
280
286
  Show pipes data as Pandas DataFrames.
281
287
 
@@ -374,11 +380,11 @@ def _show_data(
374
380
 
375
381
 
376
382
  def _show_columns(
377
- action: Optional[List[str]] = None,
378
- debug: bool = False,
379
- nopretty: bool = False,
380
- **kw: Any
381
- ) -> SuccessTuple:
383
+ action: Optional[List[str]] = None,
384
+ debug: bool = False,
385
+ nopretty: bool = False,
386
+ **kw: Any
387
+ ) -> SuccessTuple:
382
388
  """
383
389
  Show the registered and table columns for pipes.
384
390
  """
@@ -392,11 +398,11 @@ def _show_columns(
392
398
 
393
399
 
394
400
  def _show_rowcounts(
395
- action: Optional[List[str]] = None,
396
- workers: Optional[int] = None,
397
- debug: bool = False,
398
- **kw: Any
399
- ) -> SuccessTuple:
401
+ action: Optional[List[str]] = None,
402
+ workers: Optional[int] = None,
403
+ debug: bool = False,
404
+ **kw: Any
405
+ ) -> SuccessTuple:
400
406
  """
401
407
  Show the rowcounts for pipes.
402
408
 
@@ -433,12 +439,12 @@ def _show_rowcounts(
433
439
  return True, "Success"
434
440
 
435
441
  def _show_plugins(
436
- action: Optional[List[str]] = None,
437
- repository: Optional[str] = None,
438
- nopretty: bool = False,
439
- debug: bool = False,
440
- **kw: Any
441
- ) -> SuccessTuple:
442
+ action: Optional[List[str]] = None,
443
+ repository: Optional[str] = None,
444
+ nopretty: bool = False,
445
+ debug: bool = False,
446
+ **kw: Any
447
+ ) -> SuccessTuple:
442
448
  """
443
449
  Show the installed plugins.
444
450
  """
@@ -475,10 +481,10 @@ def _show_plugins(
475
481
  return True, "Success"
476
482
 
477
483
  def _show_users(
478
- mrsm_instance: Optional[str] = None,
479
- debug: bool = False,
480
- **kw: Any
481
- ) -> SuccessTuple:
484
+ mrsm_instance: Optional[str] = None,
485
+ debug: bool = False,
486
+ **kw: Any
487
+ ) -> SuccessTuple:
482
488
  """
483
489
  Show the registered users in a Meerschaum instance (default is the current instance).
484
490
  """
@@ -498,10 +504,10 @@ def _show_users(
498
504
  return True, "Success"
499
505
 
500
506
  def _show_packages(
501
- action: Optional[List[str]] = None,
502
- nopretty: bool = False,
503
- **kw: Any
504
- ) -> SuccessTuple:
507
+ action: Optional[List[str]] = None,
508
+ nopretty: bool = False,
509
+ **kw: Any
510
+ ) -> SuccessTuple:
505
511
  """
506
512
  Show the packages in dependency groups, or as a list with `--nopretty`.
507
513
  """
@@ -530,9 +536,9 @@ def _show_packages(
530
536
  return True, "Success"
531
537
 
532
538
  def _complete_show_packages(
533
- action: Optional[List[str]] = None,
534
- **kw: Any
535
- ) -> List[str]:
539
+ action: Optional[List[str]] = None,
540
+ **kw: Any
541
+ ) -> List[str]:
536
542
  from meerschaum.utils.packages import packages
537
543
  if not action:
538
544
  return sorted(list(packages.keys()))
@@ -545,10 +551,10 @@ def _complete_show_packages(
545
551
  return possibilities
546
552
 
547
553
  def _show_jobs(
548
- action: Optional[List[str]] = None,
549
- nopretty: bool = False,
550
- **kw: Any
551
- ) -> SuccessTuple:
554
+ action: Optional[List[str]] = None,
555
+ nopretty: bool = False,
556
+ **kw: Any
557
+ ) -> SuccessTuple:
552
558
  """
553
559
  Show the currently running and stopped jobs.
554
560
  """
@@ -572,10 +578,10 @@ def _show_jobs(
572
578
 
573
579
 
574
580
  def _show_logs(
575
- action: Optional[List[str]] = None,
576
- nopretty: bool = False,
577
- **kw
578
- ) -> SuccessTuple:
581
+ action: Optional[List[str]] = None,
582
+ nopretty: bool = False,
583
+ **kw
584
+ ) -> SuccessTuple:
579
585
  """
580
586
  Show the logs for jobs.
581
587
 
@@ -607,13 +613,15 @@ def _show_logs(
607
613
  now_follow_str = now.strftime(follow_timestamp_format)
608
614
 
609
615
  def build_buffer_spaces(daemons) -> Dict[str, str]:
610
- max_len_id = max(len(d.daemon_id) for d in daemons) if daemons else 0
616
+ max_len_id = (
617
+ max(len(d.daemon_id) for d in daemons) + 1
618
+ ) if daemons else 0
611
619
  buffer_len = max(
612
620
  get_config('jobs', 'logs', 'min_buffer_len'),
613
- max_len_id
621
+ max_len_id
614
622
  )
615
623
  return {
616
- d.daemon_id: ''.join([' ' for i in range(buffer_len - len(d.daemon_id))])
624
+ d.daemon_id: ''.join([' '] * (buffer_len - len(d.daemon_id)))
617
625
  for d in daemons
618
626
  }
619
627
 
@@ -763,9 +771,9 @@ def _show_logs(
763
771
 
764
772
 
765
773
  def _show_environment(
766
- nopretty: bool = False,
767
- **kw
768
- ) -> SuccessTuple:
774
+ nopretty: bool = False,
775
+ **kw
776
+ ) -> SuccessTuple:
769
777
  """
770
778
  Show all of the current environment variables with begin with `'MRSM_'`.
771
779
  """
@@ -783,12 +791,12 @@ def _show_environment(
783
791
 
784
792
 
785
793
  def _show_tags(
786
- action: Optional[List[str]] = None,
787
- tags: Optional[List[str]] = None,
788
- workers: Optional[int] = None,
789
- nopretty: bool = False,
790
- **kwargs
791
- ) -> SuccessTuple:
794
+ action: Optional[List[str]] = None,
795
+ tags: Optional[List[str]] = None,
796
+ workers: Optional[int] = None,
797
+ nopretty: bool = False,
798
+ **kwargs
799
+ ) -> SuccessTuple:
792
800
  """
793
801
  Show the existing tags and their associated pipes.
794
802
  """
@@ -863,10 +871,10 @@ def _show_tags(
863
871
 
864
872
 
865
873
  def _show_schedules(
866
- action: Optional[List[str]] = None,
867
- nopretty: bool = False,
868
- **kwargs: Any
869
- ) -> SuccessTuple:
874
+ action: Optional[List[str]] = None,
875
+ nopretty: bool = False,
876
+ **kwargs: Any
877
+ ) -> SuccessTuple:
870
878
  """
871
879
  Print the upcoming timestamps according to the given schedule.
872
880
 
@@ -914,8 +922,8 @@ def _show_schedules(
914
922
 
915
923
 
916
924
  def _show_venvs(
917
- **kwargs: Any
918
- ):
925
+ **kwargs: Any
926
+ ):
919
927
  """
920
928
  Print the available virtual environments in the current MRSM_ROOT_DIR.
921
929
  """
@@ -21,9 +21,9 @@ def stop(action: Optional[List[str]] = None, **kw) -> SuccessTuple:
21
21
 
22
22
 
23
23
  def _complete_stop(
24
- action: Optional[List[str]] = None,
25
- **kw: Any
26
- ) -> List[str]:
24
+ action: Optional[List[str]] = None,
25
+ **kw: Any
26
+ ) -> List[str]:
27
27
  """
28
28
  Override the default Meerschaum `complete_` function.
29
29
  """
@@ -49,14 +49,14 @@ def _complete_stop(
49
49
 
50
50
 
51
51
  def _stop_jobs(
52
- action: Optional[List[str]] = None,
53
- timeout_seconds: Optional[int] = None,
54
- noask: bool = False,
55
- force: bool = False,
56
- yes: bool = False,
57
- nopretty: bool = False,
58
- **kw
59
- ) -> SuccessTuple:
52
+ action: Optional[List[str]] = None,
53
+ timeout_seconds: Optional[int] = None,
54
+ noask: bool = False,
55
+ force: bool = False,
56
+ yes: bool = False,
57
+ nopretty: bool = False,
58
+ **kw
59
+ ) -> SuccessTuple:
60
60
  """
61
61
  Stop running jobs that were started with `-d` or `start job`.
62
62
 
@@ -17,6 +17,7 @@ from dash.dependencies import Input, Output, State
17
17
  from meerschaum.api.dash import dash_app, debug, pipes, _get_pipes, active_sessions
18
18
  from meerschaum.api.dash.connectors import get_web_connector
19
19
  from meerschaum.api.routes._login import login
20
+ from meerschaum.api.dash.components import alert_from_success_tuple
20
21
  from fastapi_login.exceptions import InvalidCredentialsException
21
22
  from fastapi.exceptions import HTTPException
22
23
  dbc = attempt_import('dash_bootstrap_components', lazy=False, check_update=CHECK_UPDATE)
@@ -39,6 +40,7 @@ def show_registration_disabled_collapse(n_clicks, is_open):
39
40
  Output('session-store', 'data'),
40
41
  Output('username-input', 'className'),
41
42
  Output('location', 'pathname'),
43
+ Output('login-alert-div', 'children'),
42
44
  Input('username-input', 'n_submit'),
43
45
  Input('password-input', 'n_submit'),
44
46
  Input('login-button', 'n_clicks'),
@@ -48,14 +50,14 @@ def show_registration_disabled_collapse(n_clicks, is_open):
48
50
  State('location', 'pathname'),
49
51
  )
50
52
  def login_button_click(
51
- username_submit,
52
- password_submit,
53
- n_clicks,
54
- username,
55
- password,
56
- location_href,
57
- location_pathname,
58
- ):
53
+ username_submit,
54
+ password_submit,
55
+ n_clicks,
56
+ username,
57
+ password,
58
+ location_href,
59
+ location_pathname,
60
+ ):
59
61
  """
60
62
  When the user submits the login form, check the login.
61
63
  On successful login, set the session id.
@@ -63,12 +65,18 @@ def login_button_click(
63
65
  form_class = 'form-control'
64
66
  ctx = dash.callback_context
65
67
  if not username or not password or not ctx.triggered:
66
- return {}, form_class, dash.no_update
68
+ raise PreventUpdate
69
+
67
70
  try:
68
- token_dict = login({'username': username, 'password': password})
69
- session_data = {'session-id': str(uuid.uuid4()), 'location.href': location_href}
71
+ _ = login({'username': username, 'password': password})
72
+ session_data = {
73
+ 'session-id': str(uuid.uuid4()),
74
+ 'location.href': location_href,
75
+ }
70
76
  active_sessions[session_data['session-id']] = {'username': username}
77
+ alerts = []
71
78
  except HTTPException:
72
79
  form_class += ' is-invalid'
73
- session_data = None
74
- return session_data, form_class, dash.no_update
80
+ session_data = dash.no_update
81
+ alerts = alert_from_success_tuple((False, "Invalid login."))
82
+ return session_data, form_class, dash.no_update, alerts
@@ -31,7 +31,7 @@ registration_div = html.Div(
31
31
  else [
32
32
  dcc.Markdown("""
33
33
  #### **Web registration is disabled for security.**
34
- You can still register users on this instance with a SQL connector.
34
+ You can register users via the CLI with `mrsm register user`.
35
35
  """),
36
36
  dbc.Button(
37
37
  'More information.',
@@ -80,6 +80,7 @@ layout = dbc.Container([
80
80
  html.Br(),
81
81
  dbc.Container([
82
82
  dcc.Location(id='location-login', refresh=True),
83
+ html.Div(id="login-alert-div"),
83
84
  html.Div([
84
85
  dbc.Container(
85
86
  html.Img(
@@ -129,4 +130,3 @@ layout = dbc.Container([
129
130
  ]),
130
131
  ], className='jumbotron')
131
132
  ])
132
-
@@ -32,16 +32,16 @@ def load_user(
32
32
 
33
33
 
34
34
  @app.post(endpoints['login'], tags=['Users'])
35
- async def login(
35
+ def login(
36
36
  data: CustomOAuth2PasswordRequestForm = fastapi.Depends()
37
- # data: dict[str, str],
38
- # request: Request
39
37
  ) -> Dict[str, Any]:
40
38
  """
41
39
  Login and set the session token.
42
40
  """
43
41
  username, password = (
44
- (data.username, data.password)
42
+ (data['username'], data['password'])
43
+ if isinstance(data, dict)
44
+ else (data.username, data.password)
45
45
  ) if not no_auth else ('no-auth', 'no-auth')
46
46
 
47
47
  user = User(username, password)
@@ -62,7 +62,7 @@ async def login(
62
62
  return {
63
63
  'access_token': access_token,
64
64
  'token_type': 'bearer',
65
- 'expires' : expires_dt,
65
+ 'expires': expires_dt,
66
66
  }
67
67
 
68
68