meerschaum 2.7.4__py3-none-any.whl → 2.7.6__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) 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/sql.py +14 -4
  6. meerschaum/actions/sync.py +22 -18
  7. meerschaum/api/dash/pipes.py +2 -3
  8. meerschaum/config/_default.py +11 -0
  9. meerschaum/config/_version.py +1 -1
  10. meerschaum/connectors/api/_misc.py +3 -2
  11. meerschaum/connectors/api/_pipes.py +8 -9
  12. meerschaum/connectors/sql/_SQLConnector.py +1 -0
  13. meerschaum/connectors/sql/_cli.py +18 -12
  14. meerschaum/connectors/sql/_create_engine.py +1 -0
  15. meerschaum/connectors/sql/_pipes.py +51 -14
  16. meerschaum/connectors/sql/_sql.py +109 -16
  17. meerschaum/jobs/_Job.py +1 -0
  18. meerschaum/plugins/__init__.py +7 -3
  19. meerschaum/utils/daemon/Daemon.py +11 -3
  20. meerschaum/utils/daemon/__init__.py +2 -2
  21. meerschaum/utils/misc.py +7 -6
  22. meerschaum/utils/packages/__init__.py +35 -28
  23. meerschaum/utils/packages/_packages.py +1 -1
  24. meerschaum/utils/prompt.py +54 -36
  25. meerschaum/utils/venv/_Venv.py +6 -1
  26. meerschaum/utils/venv/__init__.py +32 -16
  27. {meerschaum-2.7.4.dist-info → meerschaum-2.7.6.dist-info}/METADATA +4 -4
  28. {meerschaum-2.7.4.dist-info → meerschaum-2.7.6.dist-info}/RECORD +34 -34
  29. {meerschaum-2.7.4.dist-info → meerschaum-2.7.6.dist-info}/WHEEL +1 -1
  30. {meerschaum-2.7.4.dist-info → meerschaum-2.7.6.dist-info}/LICENSE +0 -0
  31. {meerschaum-2.7.4.dist-info → meerschaum-2.7.6.dist-info}/NOTICE +0 -0
  32. {meerschaum-2.7.4.dist-info → meerschaum-2.7.6.dist-info}/entry_points.txt +0 -0
  33. {meerschaum-2.7.4.dist-info → meerschaum-2.7.6.dist-info}/top_level.txt +0 -0
  34. {meerschaum-2.7.4.dist-info → meerschaum-2.7.6.dist-info}/zip-safe +0 -0
@@ -6,8 +6,10 @@ This module contains SQLConnector functions for executing SQL queries.
6
6
  """
7
7
 
8
8
  from __future__ import annotations
9
+
10
+ import meerschaum as mrsm
9
11
  from meerschaum.utils.typing import (
10
- Union, Mapping, List, Dict, SuccessTuple, Optional, Any, Iterable, Callable,
12
+ Union, List, Dict, SuccessTuple, Optional, Any, Iterable, Callable,
11
13
  Tuple, Hashable,
12
14
  )
13
15
 
@@ -15,7 +17,7 @@ from meerschaum.utils.debug import dprint
15
17
  from meerschaum.utils.warnings import warn
16
18
 
17
19
  ### database flavors that can use bulk insert
18
- _bulk_flavors = {'postgresql', 'timescaledb', 'citus'}
20
+ _bulk_flavors = {'postgresql', 'timescaledb', 'citus', 'mssql'}
19
21
  ### flavors that do not support chunks
20
22
  _disallow_chunks_flavors = ['duckdb']
21
23
  _max_chunks_flavors = {'sqlite': 1000}
@@ -776,9 +778,11 @@ def to_sql(
776
778
  import time
777
779
  import json
778
780
  from decimal import Decimal
781
+ from datetime import timedelta
779
782
  from meerschaum.utils.warnings import error, warn
780
783
  import warnings
781
784
  import functools
785
+
782
786
  if name is None:
783
787
  error(f"Name must not be `None` to insert data into {self}.")
784
788
 
@@ -805,6 +809,7 @@ def to_sql(
805
809
  quantize_decimal,
806
810
  coerce_timezone,
807
811
  encode_bytes_for_bytea,
812
+ serialize_bytes,
808
813
  )
809
814
  from meerschaum.utils.dtypes.sql import (
810
815
  NUMERIC_PRECISION_FLAVORS,
@@ -812,6 +817,7 @@ def to_sql(
812
817
  PD_TO_SQLALCHEMY_DTYPES_FLAVORS,
813
818
  get_db_type_from_pd_type,
814
819
  )
820
+ from meerschaum.utils.misc import interval_str
815
821
  from meerschaum.connectors.sql._create_engine import flavor_configs
816
822
  from meerschaum.utils.packages import attempt_import, import_pandas
817
823
  sqlalchemy = attempt_import('sqlalchemy', debug=debug)
@@ -821,24 +827,36 @@ def to_sql(
821
827
  bytes_cols = get_bytes_cols(df)
822
828
  numeric_cols = get_numeric_cols(df)
823
829
 
824
- stats = {'target': name,}
830
+ enable_bulk_insert = mrsm.get_config(
831
+ 'system', 'connectors', 'sql', 'bulk_insert'
832
+ ).get(self.flavor, False)
833
+ stats = {'target': name}
825
834
  ### resort to defaults if None
826
835
  copied = False
827
- use_psql_copy = False
836
+ use_bulk_insert = False
828
837
  if method == "":
829
- if self.flavor in _bulk_flavors:
830
- method = functools.partial(psql_insert_copy, schema=self.schema)
831
- use_psql_copy = True
838
+ if enable_bulk_insert:
839
+ method = (
840
+ functools.partial(mssql_insert_json, debug=debug)
841
+ if self.flavor == 'mssql'
842
+ else functools.partial(psql_insert_copy, debug=debug)
843
+ )
844
+ use_bulk_insert = True
832
845
  else:
833
846
  ### Should resolve to 'multi' or `None`.
834
847
  method = flavor_configs.get(self.flavor, {}).get('to_sql', {}).get('method', 'multi')
835
848
 
836
- if bytes_cols and (use_psql_copy or self.flavor == 'oracle'):
849
+ if bytes_cols and (use_bulk_insert or self.flavor == 'oracle'):
837
850
  if safe_copy and not copied:
838
851
  df = df.copy()
839
852
  copied = True
853
+ bytes_serializer = (
854
+ functools.partial(encode_bytes_for_bytea, with_prefix=(self.flavor != 'oracle'))
855
+ if self.flavor != 'mssql'
856
+ else serialize_bytes
857
+ )
840
858
  for col in bytes_cols:
841
- df[col] = df[col].apply(encode_bytes_for_bytea, with_prefix=(self.flavor != 'oracle'))
859
+ df[col] = df[col].apply(bytes_serializer)
842
860
 
843
861
  if self.flavor in NUMERIC_AS_TEXT_FLAVORS:
844
862
  if safe_copy and not copied:
@@ -982,13 +1000,19 @@ def to_sql(
982
1000
 
983
1001
  end = time.perf_counter()
984
1002
  if success:
985
- msg = f"It took {round(end - start, 2)} seconds to sync {len(df)} rows to {name}."
1003
+ num_rows = len(df)
1004
+ msg = (
1005
+ f"It took {interval_str(timedelta(seconds=(end - start)))} "
1006
+ + f"to sync {num_rows:,} row"
1007
+ + ('s' if num_rows != 1 else '')
1008
+ + f" to {name}."
1009
+ )
986
1010
  stats['start'] = start
987
1011
  stats['end'] = end
988
1012
  stats['duration'] = end - start
989
1013
 
990
1014
  if debug:
991
- print(f" done.", flush=True)
1015
+ print(" done.", flush=True)
992
1016
  dprint(msg)
993
1017
 
994
1018
  stats['success'] = success
@@ -1005,7 +1029,7 @@ def psql_insert_copy(
1005
1029
  conn: Union[sqlalchemy.engine.Engine, sqlalchemy.engine.Connection],
1006
1030
  keys: List[str],
1007
1031
  data_iter: Iterable[Any],
1008
- schema: Optional[str] = None,
1032
+ debug: bool = False,
1009
1033
  ) -> None:
1010
1034
  """
1011
1035
  Execute SQL statement inserting data for PostgreSQL.
@@ -1022,18 +1046,15 @@ def psql_insert_copy(
1022
1046
  data_iter: Iterable[Any]
1023
1047
  Iterable that iterates the values to be inserted
1024
1048
 
1025
- schema: Optional[str], default None
1026
- Optionally specify the schema of the table to be inserted into.
1027
-
1028
1049
  Returns
1029
1050
  -------
1030
1051
  None
1031
1052
  """
1032
1053
  import csv
1033
- from io import StringIO
1034
1054
  import json
1035
1055
 
1036
1056
  from meerschaum.utils.sql import sql_item_name
1057
+ from meerschaum.utils.warnings import dprint
1037
1058
 
1038
1059
  ### NOTE: PostgreSQL doesn't support NUL chars in text, so they're removed from strings.
1039
1060
  data_iter = (
@@ -1057,6 +1078,8 @@ def psql_insert_copy(
1057
1078
  table_name = sql_item_name(table.name, 'postgresql', table.schema)
1058
1079
  columns = ', '.join(f'"{k}"' for k in keys)
1059
1080
  sql = f"COPY {table_name} ({columns}) FROM STDIN WITH CSV NULL '\\N'"
1081
+ if debug:
1082
+ dprint(sql)
1060
1083
 
1061
1084
  dbapi_conn = conn.connection
1062
1085
  with dbapi_conn.cursor() as cur:
@@ -1065,6 +1088,76 @@ def psql_insert_copy(
1065
1088
  writer.writerows(data_iter)
1066
1089
 
1067
1090
 
1091
+ def mssql_insert_json(
1092
+ table: pandas.io.sql.SQLTable,
1093
+ conn: Union[sqlalchemy.engine.Engine, sqlalchemy.engine.Connection],
1094
+ keys: List[str],
1095
+ data_iter: Iterable[Any],
1096
+ cols_types: Optional[Dict[str, str]] = None,
1097
+ debug: bool = False,
1098
+ ):
1099
+ """
1100
+ Execute SQL statement inserting data via OPENJSON.
1101
+
1102
+ Adapted from this snippet:
1103
+ https://gist.github.com/gordthompson/1fb0f1c3f5edbf6192e596de8350f205
1104
+
1105
+ Parameters
1106
+ ----------
1107
+ table: pandas.io.sql.SQLTable
1108
+
1109
+ conn: Union[sqlalchemy.engine.Engine, sqlalchemy.engine.Connection]
1110
+
1111
+ keys: List[str]
1112
+ Column names
1113
+
1114
+ data_iter: Iterable[Any]
1115
+ Iterable that iterates the values to be inserted
1116
+
1117
+ cols_types: Optional[Dict[str, str]], default None
1118
+ If provided, use these as the columns and types for the table.
1119
+
1120
+ Returns
1121
+ -------
1122
+ None
1123
+ """
1124
+ import json
1125
+ from meerschaum.utils.sql import sql_item_name
1126
+ from meerschaum.utils.dtypes.sql import get_pd_type_from_db_type, get_db_type_from_pd_type
1127
+ from meerschaum.utils.warnings import dprint
1128
+ table_name = sql_item_name(table.name, 'mssql', table.schema)
1129
+ if not cols_types:
1130
+ pd_types = {
1131
+ str(column.name): get_pd_type_from_db_type(str(column.type))
1132
+ for column in table.table.columns
1133
+ }
1134
+ cols_types = {
1135
+ col: get_db_type_from_pd_type(typ, 'mssql')
1136
+ for col, typ in pd_types.items()
1137
+ }
1138
+ columns = ",\n ".join([f"[{k}]" for k in keys])
1139
+ json_data = [dict(zip(keys, row)) for row in data_iter]
1140
+ with_clause = ",\n ".join(
1141
+ [
1142
+ f"[{col_name}] {col_type} '$.\"{col_name}\"'"
1143
+ for col_name, col_type in cols_types.items()
1144
+ ]
1145
+ )
1146
+ placeholder = "?" if conn.dialect.paramstyle == "qmark" else "%s"
1147
+ sql = (
1148
+ f"INSERT INTO {table_name} (\n {columns}\n)\n"
1149
+ f"SELECT\n {columns}\n"
1150
+ f"FROM OPENJSON({placeholder})\n"
1151
+ "WITH (\n"
1152
+ f" {with_clause}\n"
1153
+ ");"
1154
+ )
1155
+ if debug:
1156
+ dprint(sql)
1157
+
1158
+ conn.exec_driver_sql(sql, (json.dumps(json_data, default=str),))
1159
+
1160
+
1068
1161
  def format_sql_query_for_dask(query: str) -> 'sqlalchemy.sql.selectable.Select':
1069
1162
  """
1070
1163
  Given a `SELECT` query, return a `sqlalchemy` query for Dask to use.
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']:
@@ -261,7 +261,10 @@ class Daemon:
261
261
  -------
262
262
  Nothing — this will exit the parent process.
263
263
  """
264
- import platform, sys, os, traceback
264
+ import platform
265
+ import sys
266
+ import os
267
+ import traceback
265
268
  from meerschaum.utils.warnings import warn
266
269
  from meerschaum.config import get_config
267
270
  daemon = attempt_import('daemon')
@@ -1014,7 +1017,8 @@ class Daemon:
1014
1017
 
1015
1018
  def read_pickle(self) -> Daemon:
1016
1019
  """Read a Daemon's pickle file and return the `Daemon`."""
1017
- import pickle, traceback
1020
+ import pickle
1021
+ import traceback
1018
1022
  if not self.pickle_path.exists():
1019
1023
  error(f"Pickle file does not exist for daemon '{self.daemon_id}'.")
1020
1024
 
@@ -1050,6 +1054,9 @@ class Daemon:
1050
1054
  if self._properties is None:
1051
1055
  self._properties = {}
1052
1056
 
1057
+ if self._properties.get('result', None) is None:
1058
+ _ = self._properties.pop('result', None)
1059
+
1053
1060
  if _file_properties is not None:
1054
1061
  self._properties = apply_patch_to_config(
1055
1062
  _file_properties,
@@ -1085,7 +1092,8 @@ class Daemon:
1085
1092
 
1086
1093
  def write_pickle(self) -> SuccessTuple:
1087
1094
  """Write the pickle file for the daemon."""
1088
- import pickle, traceback
1095
+ import pickle
1096
+ import traceback
1089
1097
  try:
1090
1098
  self.path.mkdir(parents=True, exist_ok=True)
1091
1099
  with open(self.pickle_path, 'wb+') as pickle_file:
@@ -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:
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,
@@ -831,15 +837,11 @@ def pip_install(
831
837
 
832
838
  """
833
839
  from meerschaum.config._paths import VIRTENV_RESOURCES_PATH
834
- from meerschaum.config import get_config
835
840
  from meerschaum.config.static import STATIC_CONFIG
836
841
  from meerschaum.utils.warnings import warn
837
842
  if args is None:
838
843
  args = ['--upgrade'] if not _uninstall else []
839
- if color:
840
- ANSI, UNICODE = True, True
841
- else:
842
- ANSI, UNICODE = False, False
844
+ ANSI = True if color else False
843
845
  if check_wheel:
844
846
  have_wheel = venv_contains_package('wheel', venv=venv, debug=debug)
845
847
 
@@ -878,7 +880,8 @@ def pip_install(
878
880
  )
879
881
 
880
882
  use_uv_pip = (
881
- venv_contains_package('uv', venv=None, debug=debug)
883
+ _use_uv_pip
884
+ and venv_contains_package('uv', venv=None, debug=debug)
882
885
  and uv_bin is not None
883
886
  and venv is not None
884
887
  and is_uv_enabled()
@@ -966,6 +969,10 @@ def pip_install(
966
969
 
967
970
  if '--target' not in _args and '-t' not in _args and not (not use_uv_pip and _uninstall):
968
971
  if venv is not None:
972
+ vtp = venv_target_path(venv, allow_nonexistent=True, debug=debug)
973
+ if not vtp.exists():
974
+ if not init_venv(venv, force=True):
975
+ vtp.mkdir(parents=True, exist_ok=True)
969
976
  _args += ['--target', venv_target_path(venv, debug=debug)]
970
977
  elif (
971
978
  '--target' not in _args
@@ -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',