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

Sign up to get free protection for your applications and to get access to all the features.
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()