meerschaum 2.7.5__py3-none-any.whl → 2.7.7__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 (50) hide show
  1. meerschaum/_internal/shell/Shell.py +4 -6
  2. meerschaum/_internal/shell/ShellCompleter.py +6 -5
  3. meerschaum/actions/clear.py +6 -3
  4. meerschaum/actions/copy.py +33 -27
  5. meerschaum/actions/drop.py +100 -22
  6. meerschaum/actions/index.py +71 -0
  7. meerschaum/actions/register.py +8 -12
  8. meerschaum/actions/sql.py +1 -1
  9. meerschaum/actions/sync.py +22 -18
  10. meerschaum/api/dash/pipes.py +2 -3
  11. meerschaum/api/routes/_pipes.py +18 -0
  12. meerschaum/api/routes/_plugins.py +1 -1
  13. meerschaum/api/routes/_users.py +62 -61
  14. meerschaum/config/_default.py +5 -0
  15. meerschaum/config/_version.py +1 -1
  16. meerschaum/connectors/api/_misc.py +3 -2
  17. meerschaum/connectors/api/_pipes.py +28 -9
  18. meerschaum/connectors/sql/_SQLConnector.py +7 -3
  19. meerschaum/connectors/sql/_create_engine.py +1 -1
  20. meerschaum/connectors/sql/_fetch.py +4 -9
  21. meerschaum/connectors/sql/_instance.py +3 -3
  22. meerschaum/connectors/sql/_pipes.py +292 -76
  23. meerschaum/connectors/sql/_plugins.py +11 -16
  24. meerschaum/connectors/sql/_sql.py +13 -9
  25. meerschaum/connectors/sql/_uri.py +9 -9
  26. meerschaum/connectors/sql/_users.py +10 -12
  27. meerschaum/connectors/sql/tables/__init__.py +13 -14
  28. meerschaum/core/Pipe/__init__.py +12 -2
  29. meerschaum/core/Pipe/_attributes.py +32 -38
  30. meerschaum/core/Pipe/_drop.py +73 -2
  31. meerschaum/core/Pipe/_index.py +68 -0
  32. meerschaum/jobs/_Job.py +1 -0
  33. meerschaum/plugins/__init__.py +7 -3
  34. meerschaum/utils/daemon/Daemon.py +5 -1
  35. meerschaum/utils/daemon/__init__.py +2 -2
  36. meerschaum/utils/dtypes/sql.py +2 -2
  37. meerschaum/utils/misc.py +7 -6
  38. meerschaum/utils/packages/__init__.py +31 -27
  39. meerschaum/utils/packages/_packages.py +1 -1
  40. meerschaum/utils/prompt.py +54 -36
  41. meerschaum/utils/sql.py +80 -34
  42. meerschaum/utils/venv/__init__.py +12 -3
  43. {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/METADATA +17 -5
  44. {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/RECORD +50 -48
  45. {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/WHEEL +1 -1
  46. {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/LICENSE +0 -0
  47. {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/NOTICE +0 -0
  48. {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/entry_points.txt +0 -0
  49. {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/top_level.txt +0 -0
  50. {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/zip-safe +0 -0
meerschaum/jobs/_Job.py CHANGED
@@ -313,6 +313,7 @@ class Job:
313
313
  if not cleanup_success:
314
314
  return cleanup_success, cleanup_msg
315
315
 
316
+ _ = self.daemon._properties.pop('result', None)
316
317
  return cleanup_success, f"Deleted {self}."
317
318
 
318
319
  def is_running(self) -> bool:
@@ -126,8 +126,8 @@ def pre_sync_hook(
126
126
 
127
127
 
128
128
  def post_sync_hook(
129
- function: Callable[[Any], Any],
130
- ) -> Callable[[Any], Any]:
129
+ function: Callable[[Any], Any],
130
+ ) -> Callable[[Any], Any]:
131
131
  """
132
132
  Register a function as a sync hook to be executed upon completion of a sync.
133
133
 
@@ -143,10 +143,14 @@ def post_sync_hook(
143
143
  Examples
144
144
  --------
145
145
  >>> from meerschaum.plugins import post_sync_hook
146
+ >>> from meerschaum.utils.misc import interval_str
147
+ >>> from datetime import timedelta
146
148
  >>>
147
149
  >>> @post_sync_hook
148
150
  ... def log_sync(pipe, success_tuple, duration=None, **kwargs):
149
- ... print(f"It took {round(duration, 2)} seconds to sync {pipe}.")
151
+ ... duration_delta = timedelta(seconds=duration)
152
+ ... duration_text = interval_str(duration_delta)
153
+ ... print(f"It took {duration_text} to sync {pipe}.")
150
154
  >>>
151
155
  """
152
156
  with _locks['_post_sync_hooks']:
@@ -1017,7 +1017,8 @@ class Daemon:
1017
1017
 
1018
1018
  def read_pickle(self) -> Daemon:
1019
1019
  """Read a Daemon's pickle file and return the `Daemon`."""
1020
- import pickle, traceback
1020
+ import pickle
1021
+ import traceback
1021
1022
  if not self.pickle_path.exists():
1022
1023
  error(f"Pickle file does not exist for daemon '{self.daemon_id}'.")
1023
1024
 
@@ -1053,6 +1054,9 @@ class Daemon:
1053
1054
  if self._properties is None:
1054
1055
  self._properties = {}
1055
1056
 
1057
+ if self._properties.get('result', None) is None:
1058
+ _ = self._properties.pop('result', None)
1059
+
1056
1060
  if _file_properties is not None:
1057
1061
  self._properties = apply_patch_to_config(
1058
1062
  _file_properties,
@@ -37,7 +37,7 @@ def daemon_entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
37
37
  filtered_sysargs = [arg for arg in sysargs if arg not in ('-d', '--daemon')]
38
38
  try:
39
39
  label = shlex.join(filtered_sysargs) if sysargs else None
40
- except Exception as e:
40
+ except Exception:
41
41
  label = ' '.join(filtered_sysargs) if sysargs else None
42
42
 
43
43
  name = _args.get('name', None)
@@ -45,7 +45,7 @@ def daemon_entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
45
45
  if name:
46
46
  try:
47
47
  daemon = Daemon(daemon_id=name)
48
- except Exception as e:
48
+ except Exception:
49
49
  daemon = None
50
50
 
51
51
  if daemon is not None:
@@ -536,7 +536,7 @@ def get_db_type_from_pd_type(
536
536
  from meerschaum.utils.packages import attempt_import
537
537
  from meerschaum.utils.dtypes import are_dtypes_equal, MRSM_ALIAS_DTYPES
538
538
  from meerschaum.utils.misc import parse_arguments_str
539
- sqlalchemy_types = attempt_import('sqlalchemy.types')
539
+ sqlalchemy_types = attempt_import('sqlalchemy.types', lazy=False)
540
540
 
541
541
  types_registry = (
542
542
  PD_TO_DB_DTYPES_FLAVORS
@@ -559,7 +559,7 @@ def get_db_type_from_pd_type(
559
559
  found_db_type = True
560
560
 
561
561
  if not found_db_type:
562
- warn(f"Unknown Pandas data type '{pd_type}'. Falling back to 'TEXT'.")
562
+ warn(f"Unknown Pandas data type '{pd_type}'. Falling back to 'TEXT'.", stacklevel=3)
563
563
  return (
564
564
  'TEXT'
565
565
  if not as_sqlalchemy
meerschaum/utils/misc.py CHANGED
@@ -90,20 +90,21 @@ def add_method_to_class(
90
90
 
91
91
 
92
92
  def generate_password(length: int = 12) -> str:
93
- """Generate a secure password of given length.
93
+ """
94
+ Generate a secure password of given length.
94
95
 
95
96
  Parameters
96
97
  ----------
97
- length : int, default 12
98
+ length: int, default 12
98
99
  The length of the password.
99
100
 
100
101
  Returns
101
102
  -------
102
103
  A random password string.
103
-
104
104
  """
105
- import secrets, string
106
- return ''.join((secrets.choice(string.ascii_letters) for i in range(length)))
105
+ import secrets
106
+ import string
107
+ return ''.join((secrets.choice(string.ascii_letters + string.digits) for i in range(length)))
107
108
 
108
109
  def is_int(s : str) -> bool:
109
110
  """
@@ -121,7 +122,7 @@ def is_int(s : str) -> bool:
121
122
  """
122
123
  try:
123
124
  float(s)
124
- except Exception as e:
125
+ except Exception:
125
126
  return False
126
127
 
127
128
  return float(s).is_integer()
@@ -337,14 +337,14 @@ def get_install_no_version(install_name: str) -> str:
337
337
 
338
338
  import_versions = {}
339
339
  def determine_version(
340
- path: pathlib.Path,
341
- import_name: Optional[str] = None,
342
- venv: Optional[str] = 'mrsm',
343
- search_for_metadata: bool = True,
344
- split: bool = True,
345
- warn: bool = False,
346
- debug: bool = False,
347
- ) -> Union[str, None]:
340
+ path: pathlib.Path,
341
+ import_name: Optional[str] = None,
342
+ venv: Optional[str] = 'mrsm',
343
+ search_for_metadata: bool = True,
344
+ split: bool = True,
345
+ warn: bool = False,
346
+ debug: bool = False,
347
+ ) -> Union[str, None]:
348
348
  """
349
349
  Determine a module's `__version__` string from its filepath.
350
350
 
@@ -381,11 +381,8 @@ def determine_version(
381
381
  with _locks['import_versions']:
382
382
  if venv not in import_versions:
383
383
  import_versions[venv] = {}
384
- import importlib.metadata
385
- import re, os
384
+ import os
386
385
  old_cwd = os.getcwd()
387
- if debug:
388
- from meerschaum.utils.debug import dprint
389
386
  from meerschaum.utils.warnings import warn as warn_function
390
387
  if import_name is None:
391
388
  import_name = path.parent.stem if path.stem == '__init__' else path.stem
@@ -395,7 +392,10 @@ def determine_version(
395
392
  _version = None
396
393
  module_parent_dir = (
397
394
  path.parent.parent if path.stem == '__init__' else path.parent
398
- ) if path is not None else venv_target_path(venv, debug=debug)
395
+ ) if path is not None else venv_target_path(venv, allow_nonexistent=True, debug=debug)
396
+
397
+ if not module_parent_dir.exists():
398
+ return None
399
399
 
400
400
  installed_dir_name = _import_to_dir_name(import_name)
401
401
  clean_installed_dir_name = installed_dir_name.lower().replace('-', '_')
@@ -403,7 +403,11 @@ def determine_version(
403
403
  ### First, check if a dist-info directory exists.
404
404
  _found_versions = []
405
405
  if search_for_metadata:
406
- for filename in os.listdir(module_parent_dir):
406
+ try:
407
+ filenames = os.listdir(module_parent_dir)
408
+ except FileNotFoundError:
409
+ filenames = []
410
+ for filename in filenames:
407
411
  if not filename.endswith('.dist-info'):
408
412
  continue
409
413
  filename_lower = filename.lower()
@@ -430,7 +434,7 @@ def determine_version(
430
434
  try:
431
435
  os.chdir(module_parent_dir)
432
436
  _version = importlib_metadata.metadata(import_name)['Version']
433
- except Exception as e:
437
+ except Exception:
434
438
  _version = None
435
439
  finally:
436
440
  os.chdir(old_cwd)
@@ -698,7 +702,7 @@ def need_update(
698
702
  (not semver.Version.parse(version).match(required_version))
699
703
  if required_version else False
700
704
  )
701
- except AttributeError as e:
705
+ except AttributeError:
702
706
  pip_install(_import_to_install_name('semver'), venv='mrsm', debug=debug)
703
707
  semver = manually_import_module('semver', venv='mrsm', debug=debug)
704
708
  return (
@@ -724,10 +728,10 @@ def need_update(
724
728
 
725
729
 
726
730
  def get_pip(
727
- venv: Optional[str] = 'mrsm',
728
- color: bool = True,
729
- debug: bool = False,
730
- ) -> bool:
731
+ venv: Optional[str] = 'mrsm',
732
+ color: bool = True,
733
+ debug: bool = False,
734
+ ) -> bool:
731
735
  """
732
736
  Download and run the get-pip.py script.
733
737
 
@@ -747,7 +751,8 @@ def get_pip(
747
751
  A bool indicating success.
748
752
 
749
753
  """
750
- import sys, subprocess
754
+ import sys
755
+ import subprocess
751
756
  from meerschaum.utils.misc import wget
752
757
  from meerschaum.config._paths import CACHE_RESOURCES_PATH
753
758
  from meerschaum.config.static import STATIC_CONFIG
@@ -755,7 +760,7 @@ def get_pip(
755
760
  dest = CACHE_RESOURCES_PATH / 'get-pip.py'
756
761
  try:
757
762
  wget(url, dest, color=False, debug=debug)
758
- except Exception as e:
763
+ except Exception:
759
764
  print(f"Failed to fetch pip from '{url}'. Please install pip and restart Meerschaum.")
760
765
  sys.exit(1)
761
766
  if venv is not None:
@@ -776,6 +781,7 @@ def pip_install(
776
781
  _uninstall: bool = False,
777
782
  _from_completely_uninstall: bool = False,
778
783
  _install_uv_pip: bool = True,
784
+ _use_uv_pip: bool = True,
779
785
  color: bool = True,
780
786
  silent: bool = False,
781
787
  debug: bool = False,
@@ -835,10 +841,7 @@ def pip_install(
835
841
  from meerschaum.utils.warnings import warn
836
842
  if args is None:
837
843
  args = ['--upgrade'] if not _uninstall else []
838
- if color:
839
- ANSI, UNICODE = True, True
840
- else:
841
- ANSI, UNICODE = False, False
844
+ ANSI = True if color else False
842
845
  if check_wheel:
843
846
  have_wheel = venv_contains_package('wheel', venv=venv, debug=debug)
844
847
 
@@ -877,7 +880,8 @@ def pip_install(
877
880
  )
878
881
 
879
882
  use_uv_pip = (
880
- venv_contains_package('uv', venv=None, debug=debug)
883
+ _use_uv_pip
884
+ and venv_contains_package('uv', venv=None, debug=debug)
881
885
  and uv_bin is not None
882
886
  and venv is not None
883
887
  and is_uv_enabled()
@@ -136,7 +136,7 @@ packages['sql'] = {
136
136
  'numpy' : 'numpy>=1.18.5',
137
137
  'pandas' : 'pandas[parquet]>=2.0.1',
138
138
  'pyarrow' : 'pyarrow>=16.1.0',
139
- 'dask' : 'dask[complete]>=2024.5.1',
139
+ 'dask' : 'dask[complete]>=2024.12.1',
140
140
  'partd' : 'partd>=1.4.2',
141
141
  'pytz' : 'pytz',
142
142
  'joblib' : 'joblib>=0.17.0',
@@ -7,7 +7,9 @@ Functions for interacting with the user.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
+
10
11
  import os
12
+ import meerschaum as mrsm
11
13
  from meerschaum.utils.typing import Any, Union, Optional, Tuple, List
12
14
 
13
15
 
@@ -63,9 +65,8 @@ def prompt(
63
65
 
64
66
  """
65
67
  from meerschaum.utils.packages import attempt_import
66
- from meerschaum.utils.formatting import colored, ANSI, CHARSET, highlight_pipes, fill_ansi
68
+ from meerschaum.utils.formatting import ANSI, CHARSET, highlight_pipes, fill_ansi
67
69
  from meerschaum.config import get_config
68
- from meerschaum.config.static import _static_config
69
70
  from meerschaum.utils.misc import filter_keywords
70
71
  from meerschaum.utils.daemon import running_in_daemon
71
72
  noask = check_noask(noask)
@@ -175,8 +176,6 @@ def yes_no(
175
176
  ```
176
177
  """
177
178
  from meerschaum.utils.warnings import error, warn
178
- from meerschaum.utils.formatting import ANSI, UNICODE
179
- from meerschaum.utils.packages import attempt_import
180
179
 
181
180
  default = options[0] if yes else default
182
181
  noask = yes or check_noask(noask)
@@ -195,7 +194,7 @@ def yes_no(
195
194
  success = False
196
195
 
197
196
  if not success:
198
- error(f"Error getting response. Aborting...", stack=False)
197
+ error("Error getting response. Aborting...", stack=False)
199
198
  if answer == "":
200
199
  answer = default
201
200
 
@@ -205,19 +204,20 @@ def yes_no(
205
204
 
206
205
  return answer.lower() == options[0].lower()
207
206
 
207
+
208
208
  def choose(
209
- question: str,
210
- choices: List[Union[str, Tuple[str, str]]],
211
- default: Union[str, List[str], None] = None,
212
- numeric: bool = True,
213
- multiple: bool = False,
214
- as_indices: bool = False,
215
- delimiter: str = ',',
216
- icon: bool = True,
217
- warn: bool = True,
218
- noask: bool = False,
219
- **kw
220
- ) -> Union[str, Tuple[str], None]:
209
+ question: str,
210
+ choices: List[Union[str, Tuple[str, str]]],
211
+ default: Union[str, List[str], None] = None,
212
+ numeric: bool = True,
213
+ multiple: bool = False,
214
+ as_indices: bool = False,
215
+ delimiter: str = ',',
216
+ icon: bool = True,
217
+ warn: bool = True,
218
+ noask: bool = False,
219
+ **kw
220
+ ) -> Union[str, Tuple[str], None]:
221
221
  """
222
222
  Present a list of options and return the user's choice.
223
223
 
@@ -418,8 +418,8 @@ def choose(
418
418
  valid = (len(answers) > 1 or not (len(answers) == 1 and answers[0] is None))
419
419
  for a in answers:
420
420
  if (
421
- not a in {_original for _new, _original in altered_choices.items()}
422
- and not a in _choices
421
+ a not in {_original for _new, _original in altered_choices.items()}
422
+ and a not in _choices
423
423
  and a != default
424
424
  and not noask
425
425
  ):
@@ -428,21 +428,22 @@ def choose(
428
428
  if valid:
429
429
  break
430
430
  if warn:
431
- _warn(f"Please pick a valid choice.", stack=False)
431
+ _warn("Please pick a valid choice.", stack=False)
432
432
 
433
433
  if not multiple:
434
434
  if not numeric:
435
435
  return answer
436
436
  try:
437
437
  _answer = choices[int(answer) - 1]
438
- if as_indices and isinstance(choice, tuple):
438
+ if as_indices and isinstance(_answer, tuple):
439
439
  return _answer[0]
440
440
  return _answer
441
- except Exception as e:
441
+ except Exception:
442
442
  _warn(f"Could not cast answer '{answer}' to an integer.", stacklevel=3)
443
443
 
444
444
  if not numeric:
445
445
  return answers
446
+
446
447
  _answers = []
447
448
  for a in answers:
448
449
  try:
@@ -451,17 +452,17 @@ def choose(
451
452
  if isinstance(_answer_to_return, tuple) and as_indices:
452
453
  _answer_to_return = _answer_to_return[0]
453
454
  _answers.append(_answer_to_return)
454
- except Exception as e:
455
+ except Exception:
455
456
  _warn(f"Could not cast answer '{a}' to an integer.", stacklevel=3)
456
457
  return _answers
457
458
 
458
459
 
459
460
  def get_password(
460
- username: Optional[str] = None,
461
- minimum_length: Optional[int] = None,
462
- confirm: bool = True,
463
- **kw: Any
464
- ) -> str:
461
+ username: Optional[str] = None,
462
+ minimum_length: Optional[int] = None,
463
+ confirm: bool = True,
464
+ **kw: Any
465
+ ) -> str:
465
466
  """
466
467
  Prompt the user for a password.
467
468
 
@@ -493,15 +494,15 @@ def get_password(
493
494
  from meerschaum.utils.warnings import warn
494
495
  while True:
495
496
  password = prompt(
496
- f"Password" + (f" for user '{username}':" if username is not None else ":"),
497
- is_password = True,
497
+ "Password" + (f" for user '{username}':" if username is not None else ":"),
498
+ is_password=True,
498
499
  **kw
499
500
  )
500
501
  if minimum_length is not None and len(password) < minimum_length:
501
502
  warn(
502
503
  "Password is too short. " +
503
504
  f"Please enter a password that is at least {minimum_length} characters.",
504
- stack = False
505
+ stack=False
505
506
  )
506
507
  continue
507
508
 
@@ -509,12 +510,12 @@ def get_password(
509
510
  return password
510
511
 
511
512
  _password = prompt(
512
- f"Confirm password" + (f" for user '{username}':" if username is not None else ":"),
513
- is_password = True,
513
+ "Confirm password" + (f" for user '{username}':" if username is not None else ":"),
514
+ is_password=True,
514
515
  **kw
515
516
  )
516
517
  if password != _password:
517
- warn(f"Passwords do not match! Please try again.", stack=False)
518
+ warn("Passwords do not match! Please try again.", stack=False)
518
519
  continue
519
520
  else:
520
521
  return password
@@ -550,13 +551,13 @@ def get_email(username: Optional[str] = None, allow_omit: bool = True, **kw: Any
550
551
  from meerschaum.utils.misc import is_valid_email
551
552
  while True:
552
553
  email = prompt(
553
- f"Email" + (f" for user '{username}'" if username is not None else "") +
554
+ "Email" + (f" for user '{username}'" if username is not None else "") +
554
555
  (" (empty to omit):" if allow_omit else ": "),
555
556
  **kw
556
557
  )
557
558
  if (allow_omit and email == '') or is_valid_email(email):
558
559
  return email
559
- warn(f"Invalid email! Please try again.", stack=False)
560
+ warn("Invalid email! Please try again.", stack=False)
560
561
 
561
562
 
562
563
  def check_noask(noask: bool = False) -> bool:
@@ -571,3 +572,20 @@ def check_noask(noask: bool = False) -> bool:
571
572
  os.environ.get(NOASK, 'false').lower()
572
573
  in ('1', 'true')
573
574
  )
575
+
576
+
577
+ def get_connectors_completer(*types: str):
578
+ """
579
+ Return a prompt-toolkit Completer object to pass into `prompt()`.
580
+ """
581
+ from meerschaum.utils.misc import get_connector_labels
582
+ prompt_toolkit_completion = mrsm.attempt_import('prompt_toolkit.completion', lazy=False)
583
+ Completer = prompt_toolkit_completion.Completer
584
+ Completion = prompt_toolkit_completion.Completion
585
+
586
+ class ConnectorCompleter(Completer):
587
+ def get_completions(self, document, complete_event):
588
+ for label in get_connector_labels(*types):
589
+ yield Completion(label, start_position=(-1 * len(document.text)))
590
+
591
+ return ConnectorCompleter()