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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. meerschaum/actions/copy.py +1 -0
  2. meerschaum/actions/drop.py +100 -22
  3. meerschaum/actions/index.py +71 -0
  4. meerschaum/actions/register.py +8 -12
  5. meerschaum/actions/sql.py +1 -1
  6. meerschaum/api/routes/_pipes.py +18 -0
  7. meerschaum/api/routes/_plugins.py +1 -1
  8. meerschaum/api/routes/_users.py +62 -61
  9. meerschaum/config/_version.py +1 -1
  10. meerschaum/connectors/api/_pipes.py +20 -0
  11. meerschaum/connectors/sql/_SQLConnector.py +8 -12
  12. meerschaum/connectors/sql/_create_engine.py +1 -1
  13. meerschaum/connectors/sql/_fetch.py +9 -39
  14. meerschaum/connectors/sql/_instance.py +3 -3
  15. meerschaum/connectors/sql/_pipes.py +262 -70
  16. meerschaum/connectors/sql/_plugins.py +11 -16
  17. meerschaum/connectors/sql/_sql.py +60 -39
  18. meerschaum/connectors/sql/_uri.py +9 -9
  19. meerschaum/connectors/sql/_users.py +10 -12
  20. meerschaum/connectors/sql/tables/__init__.py +13 -14
  21. meerschaum/connectors/valkey/_ValkeyConnector.py +2 -2
  22. meerschaum/core/Pipe/__init__.py +12 -2
  23. meerschaum/core/Pipe/_attributes.py +32 -38
  24. meerschaum/core/Pipe/_drop.py +73 -2
  25. meerschaum/core/Pipe/_fetch.py +4 -0
  26. meerschaum/core/Pipe/_index.py +68 -0
  27. meerschaum/core/Pipe/_sync.py +16 -9
  28. meerschaum/utils/daemon/Daemon.py +9 -2
  29. meerschaum/utils/daemon/RotatingFile.py +3 -3
  30. meerschaum/utils/dataframe.py +42 -12
  31. meerschaum/utils/dtypes/__init__.py +144 -24
  32. meerschaum/utils/dtypes/sql.py +52 -9
  33. meerschaum/utils/formatting/__init__.py +2 -2
  34. meerschaum/utils/formatting/_pprint.py +12 -11
  35. meerschaum/utils/misc.py +16 -18
  36. meerschaum/utils/prompt.py +1 -1
  37. meerschaum/utils/sql.py +106 -42
  38. {meerschaum-2.7.6.dist-info → meerschaum-2.7.8.dist-info}/METADATA +14 -2
  39. {meerschaum-2.7.6.dist-info → meerschaum-2.7.8.dist-info}/RECORD +45 -43
  40. {meerschaum-2.7.6.dist-info → meerschaum-2.7.8.dist-info}/WHEEL +1 -1
  41. {meerschaum-2.7.6.dist-info → meerschaum-2.7.8.dist-info}/LICENSE +0 -0
  42. {meerschaum-2.7.6.dist-info → meerschaum-2.7.8.dist-info}/NOTICE +0 -0
  43. {meerschaum-2.7.6.dist-info → meerschaum-2.7.8.dist-info}/entry_points.txt +0 -0
  44. {meerschaum-2.7.6.dist-info → meerschaum-2.7.8.dist-info}/top_level.txt +0 -0
  45. {meerschaum-2.7.6.dist-info → meerschaum-2.7.8.dist-info}/zip-safe +0 -0
@@ -8,15 +8,16 @@ Utility functions for working with data types.
8
8
 
9
9
  import traceback
10
10
  import uuid
11
- from datetime import timezone
12
- from decimal import Decimal, Context, InvalidOperation
11
+ from datetime import timezone, datetime
12
+ from decimal import Decimal, Context, InvalidOperation, ROUND_HALF_UP
13
13
 
14
14
  import meerschaum as mrsm
15
- from meerschaum.utils.typing import Dict, Union, Any
15
+ from meerschaum.utils.typing import Dict, Union, Any, Optional
16
16
  from meerschaum.utils.warnings import warn
17
17
 
18
18
  MRSM_ALIAS_DTYPES: Dict[str, str] = {
19
19
  'decimal': 'numeric',
20
+ 'Decimal': 'numeric',
20
21
  'number': 'numeric',
21
22
  'jsonl': 'json',
22
23
  'JSON': 'json',
@@ -56,6 +57,9 @@ def to_pandas_dtype(dtype: str) -> str:
56
57
  if alias_dtype is not None:
57
58
  return MRSM_PD_DTYPES[alias_dtype]
58
59
 
60
+ if dtype.startswith('numeric'):
61
+ return MRSM_PD_DTYPES['numeric']
62
+
59
63
  ### NOTE: Kind of a hack, but if the first word of the given dtype is in all caps,
60
64
  ### treat it as a SQL db type.
61
65
  if dtype.split(' ')[0].isupper():
@@ -118,8 +122,14 @@ def are_dtypes_equal(
118
122
  return False
119
123
 
120
124
  ### Sometimes pandas dtype objects are passed.
121
- ldtype = str(ldtype)
122
- rdtype = str(rdtype)
125
+ ldtype = str(ldtype).split('[', maxsplit=1)[0]
126
+ rdtype = str(rdtype).split('[', maxsplit=1)[0]
127
+
128
+ if ldtype in MRSM_ALIAS_DTYPES:
129
+ ldtype = MRSM_ALIAS_DTYPES[ldtype]
130
+
131
+ if rdtype in MRSM_ALIAS_DTYPES:
132
+ rdtype = MRSM_ALIAS_DTYPES[rdtype]
123
133
 
124
134
  json_dtypes = ('json', 'object')
125
135
  if ldtype in json_dtypes and rdtype in json_dtypes:
@@ -137,10 +147,7 @@ def are_dtypes_equal(
137
147
  if ldtype in bytes_dtypes and rdtype in bytes_dtypes:
138
148
  return True
139
149
 
140
- ldtype_clean = ldtype.split('[', maxsplit=1)[0]
141
- rdtype_clean = rdtype.split('[', maxsplit=1)[0]
142
-
143
- if ldtype_clean.lower() == rdtype_clean.lower():
150
+ if ldtype.lower() == rdtype.lower():
144
151
  return True
145
152
 
146
153
  datetime_dtypes = ('datetime', 'timestamp')
@@ -153,19 +160,19 @@ def are_dtypes_equal(
153
160
  return True
154
161
 
155
162
  string_dtypes = ('str', 'string', 'object')
156
- if ldtype_clean in string_dtypes and rdtype_clean in string_dtypes:
163
+ if ldtype in string_dtypes and rdtype in string_dtypes:
157
164
  return True
158
165
 
159
166
  int_dtypes = ('int', 'int64', 'int32', 'int16', 'int8')
160
- if ldtype_clean.lower() in int_dtypes and rdtype_clean.lower() in int_dtypes:
167
+ if ldtype.lower() in int_dtypes and rdtype.lower() in int_dtypes:
161
168
  return True
162
169
 
163
170
  float_dtypes = ('float', 'float64', 'float32', 'float16', 'float128', 'double')
164
- if ldtype_clean.lower() in float_dtypes and rdtype_clean.lower() in float_dtypes:
171
+ if ldtype.lower() in float_dtypes and rdtype.lower() in float_dtypes:
165
172
  return True
166
173
 
167
174
  bool_dtypes = ('bool', 'boolean')
168
- if ldtype_clean in bool_dtypes and rdtype_clean in bool_dtypes:
175
+ if ldtype in bool_dtypes and rdtype in bool_dtypes:
169
176
  return True
170
177
 
171
178
  return False
@@ -195,18 +202,45 @@ def is_dtype_numeric(dtype: str) -> bool:
195
202
  return False
196
203
 
197
204
 
198
- def attempt_cast_to_numeric(value: Any) -> Any:
205
+ def attempt_cast_to_numeric(
206
+ value: Any,
207
+ quantize: bool = False,
208
+ precision: Optional[int] = None,
209
+ scale: Optional[int] = None,
210
+ )-> Any:
199
211
  """
200
212
  Given a value, attempt to coerce it into a numeric (Decimal).
213
+
214
+ Parameters
215
+ ----------
216
+ value: Any
217
+ The value to be cast to a Decimal.
218
+
219
+ quantize: bool, default False
220
+ If `True`, quantize the decimal to the specified precision and scale.
221
+
222
+ precision: Optional[int], default None
223
+ If `quantize` is `True`, use this precision.
224
+
225
+ scale: Optional[int], default None
226
+ If `quantize` is `True`, use this scale.
227
+
228
+ Returns
229
+ -------
230
+ A `Decimal` if possible, or `value`.
201
231
  """
202
232
  if isinstance(value, Decimal):
233
+ if quantize and precision and scale:
234
+ return quantize_decimal(value, precision, scale)
203
235
  return value
204
236
  try:
205
- return (
206
- Decimal(str(value))
207
- if not value_is_null(value)
208
- else Decimal('NaN')
209
- )
237
+ if value_is_null(value):
238
+ return Decimal('NaN')
239
+
240
+ dec = Decimal(str(value))
241
+ if not quantize or not precision or not scale:
242
+ return dec
243
+ return quantize_decimal(dec, precision, scale)
210
244
  except Exception:
211
245
  return value
212
246
 
@@ -257,7 +291,7 @@ def none_if_null(value: Any) -> Any:
257
291
  return (None if value_is_null(value) else value)
258
292
 
259
293
 
260
- def quantize_decimal(x: Decimal, scale: int, precision: int) -> Decimal:
294
+ def quantize_decimal(x: Decimal, precision: int, scale: int) -> Decimal:
261
295
  """
262
296
  Quantize a given `Decimal` to a known scale and precision.
263
297
 
@@ -266,22 +300,61 @@ def quantize_decimal(x: Decimal, scale: int, precision: int) -> Decimal:
266
300
  x: Decimal
267
301
  The `Decimal` to be quantized.
268
302
 
269
- scale: int
303
+ precision: int
270
304
  The total number of significant digits.
271
305
 
272
- precision: int
306
+ scale: int
273
307
  The number of significant digits after the decimal point.
274
308
 
275
309
  Returns
276
310
  -------
277
311
  A `Decimal` quantized to the specified scale and precision.
278
312
  """
279
- precision_decimal = Decimal((('1' * scale) + '.' + ('1' * precision)))
313
+ precision_decimal = Decimal(('1' * (precision - scale)) + '.' + ('1' * scale))
280
314
  try:
281
- return x.quantize(precision_decimal, context=Context(prec=scale))
315
+ return x.quantize(precision_decimal, context=Context(prec=precision), rounding=ROUND_HALF_UP)
282
316
  except InvalidOperation:
317
+ pass
318
+
319
+ raise ValueError(f"Cannot quantize value '{x}' to {precision=}, {scale=}.")
320
+
321
+
322
+ def serialize_decimal(
323
+ x: Any,
324
+ quantize: bool = False,
325
+ precision: Optional[int] = None,
326
+ scale: Optional[int] = None,
327
+ ) -> Any:
328
+ """
329
+ Return a quantized string of an input decimal.
330
+
331
+ Parameters
332
+ ----------
333
+ x: Any
334
+ The potential decimal to be serialized.
335
+
336
+ quantize: bool, default False
337
+ If `True`, quantize the incoming Decimal to the specified scale and precision
338
+ before serialization.
339
+
340
+ precision: Optional[int], default None
341
+ The precision of the decimal to be quantized.
342
+
343
+ scale: Optional[int], default None
344
+ The scale of the decimal to be quantized.
345
+
346
+ Returns
347
+ -------
348
+ A string of the input decimal or the input if not a Decimal.
349
+ """
350
+ if not isinstance(x, Decimal):
283
351
  return x
284
352
 
353
+ if quantize and scale and precision:
354
+ x = quantize_decimal(x, precision, scale)
355
+
356
+ return f"{x:f}"
357
+
285
358
 
286
359
  def coerce_timezone(
287
360
  dt: Any,
@@ -434,3 +507,50 @@ def encode_bytes_for_bytea(data: bytes, with_prefix: bool = True) -> str | None:
434
507
  if not isinstance(data, bytes) and value_is_null(data):
435
508
  return data
436
509
  return ('\\x' if with_prefix else '') + binascii.hexlify(data).decode('utf-8')
510
+
511
+
512
+ def serialize_datetime(dt: datetime) -> Union[str, None]:
513
+ """
514
+ Serialize a datetime object into JSON (ISO format string).
515
+
516
+ Examples
517
+ --------
518
+ >>> import json
519
+ >>> from datetime import datetime
520
+ >>> json.dumps({'a': datetime(2022, 1, 1)}, default=json_serialize_datetime)
521
+ '{"a": "2022-01-01T00:00:00Z"}'
522
+
523
+ """
524
+ if not isinstance(dt, datetime):
525
+ return None
526
+ tz_suffix = 'Z' if dt.tzinfo is None else ''
527
+ return dt.isoformat() + tz_suffix
528
+
529
+
530
+ def json_serialize_value(x: Any, default_to_str: bool = True) -> str:
531
+ """
532
+ Serialize the given value to a JSON value. Accounts for datetimes, bytes, decimals, etc.
533
+
534
+ Parameters
535
+ ----------
536
+ x: Any
537
+ The value to serialize.
538
+
539
+ default_to_str: bool, default True
540
+ If `True`, return a string of `x` if x is not a designated type.
541
+ Otherwise return x.
542
+
543
+ Returns
544
+ -------
545
+ A serialized version of x, or x.
546
+ """
547
+ if hasattr(x, 'tzinfo'):
548
+ return serialize_datetime(x)
549
+
550
+ if isinstance(x, bytes):
551
+ return serialize_bytes(x)
552
+
553
+ if isinstance(x, Decimal):
554
+ return serialize_decimal(x)
555
+
556
+ return str(x) if default_to_str else x
@@ -7,7 +7,7 @@ Utility functions for working with SQL data types.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
- from meerschaum.utils.typing import Dict, Union, Tuple
10
+ from meerschaum.utils.typing import Dict, Union, Tuple, Optional
11
11
 
12
12
  NUMERIC_PRECISION_FLAVORS: Dict[str, Tuple[int, int]] = {
13
13
  'mariadb': (38, 20),
@@ -170,7 +170,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
170
170
  'mariadb': 'DATETIME',
171
171
  'mysql': 'DATETIME',
172
172
  'mssql': 'DATETIME2',
173
- 'oracle': 'TIMESTAMP',
173
+ 'oracle': 'TIMESTAMP(9)',
174
174
  'sqlite': 'DATETIME',
175
175
  'duckdb': 'TIMESTAMP',
176
176
  'citus': 'TIMESTAMP',
@@ -183,7 +183,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
183
183
  'mariadb': 'DATETIME',
184
184
  'mysql': 'DATETIME',
185
185
  'mssql': 'DATETIMEOFFSET',
186
- 'oracle': 'TIMESTAMP',
186
+ 'oracle': 'TIMESTAMP(9)',
187
187
  'sqlite': 'TIMESTAMP',
188
188
  'duckdb': 'TIMESTAMPTZ',
189
189
  'citus': 'TIMESTAMPTZ',
@@ -196,7 +196,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
196
196
  'mariadb': 'DATETIME',
197
197
  'mysql': 'DATETIME',
198
198
  'mssql': 'DATETIMEOFFSET',
199
- 'oracle': 'TIMESTAMP',
199
+ 'oracle': 'TIMESTAMP(9)',
200
200
  'sqlite': 'TIMESTAMP',
201
201
  'duckdb': 'TIMESTAMPTZ',
202
202
  'citus': 'TIMESTAMPTZ',
@@ -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
@@ -544,22 +544,29 @@ def get_db_type_from_pd_type(
544
544
  else PD_TO_SQLALCHEMY_DTYPES_FLAVORS
545
545
  )
546
546
 
547
+ precision, scale = None, None
548
+ og_pd_type = pd_type
547
549
  if pd_type in MRSM_ALIAS_DTYPES:
548
550
  pd_type = MRSM_ALIAS_DTYPES[pd_type]
549
551
 
550
552
  ### Check whether we are able to match this type (e.g. pyarrow support).
551
553
  found_db_type = False
552
- if pd_type not in types_registry:
554
+ if pd_type not in types_registry and not pd_type.startswith('numeric['):
553
555
  for mapped_pd_type in types_registry:
554
556
  if are_dtypes_equal(mapped_pd_type, pd_type):
555
557
  pd_type = mapped_pd_type
556
558
  found_db_type = True
557
559
  break
560
+ elif pd_type.startswith('numeric['):
561
+ og_pd_type = pd_type
562
+ pd_type = 'numeric'
563
+ precision, scale = get_numeric_precision_scale(flavor, og_pd_type)
564
+ found_db_type = True
558
565
  else:
559
566
  found_db_type = True
560
567
 
561
568
  if not found_db_type:
562
- warn(f"Unknown Pandas data type '{pd_type}'. Falling back to 'TEXT'.")
569
+ warn(f"Unknown Pandas data type '{pd_type}'. Falling back to 'TEXT'.", stacklevel=3)
563
570
  return (
564
571
  'TEXT'
565
572
  if not as_sqlalchemy
@@ -587,6 +594,9 @@ def get_db_type_from_pd_type(
587
594
  warn(f"Unknown flavor '{flavor}'. Falling back to '{default_flavor_type}' (default).")
588
595
  db_type = flavor_types.get(flavor, default_flavor_type)
589
596
  if not as_sqlalchemy:
597
+ if precision is not None and scale is not None:
598
+ db_type_bare = db_type.split('(', maxsplit=1)[0]
599
+ return f"{db_type_bare}({precision},{scale})"
590
600
  return db_type
591
601
 
592
602
  if db_type.startswith('sqlalchemy.dialects'):
@@ -603,9 +613,8 @@ def get_db_type_from_pd_type(
603
613
  return cls(*cls_args, **cls_kwargs)
604
614
 
605
615
  if 'numeric' in db_type.lower():
606
- if flavor not in NUMERIC_PRECISION_FLAVORS:
616
+ if precision is None or scale is None:
607
617
  return sqlalchemy_types.Numeric
608
- precision, scale = NUMERIC_PRECISION_FLAVORS[flavor]
609
618
  return sqlalchemy_types.Numeric(precision, scale)
610
619
 
611
620
  cls_args, cls_kwargs = None, None
@@ -619,3 +628,37 @@ def get_db_type_from_pd_type(
619
628
  if cls_args is None:
620
629
  return cls
621
630
  return cls(*cls_args, **cls_kwargs)
631
+
632
+
633
+ def get_numeric_precision_scale(
634
+ flavor: str,
635
+ dtype: Optional[str] = None,
636
+ ) -> Union[Tuple[int, int], Tuple[None, None]]:
637
+ """
638
+ Return the precision and scale to use for a numeric column for a given database flavor.
639
+
640
+ Parameters
641
+ ----------
642
+ flavor: str
643
+ The database flavor for which to return the precision and scale.
644
+
645
+ dtype: Optional[str], default None
646
+ If provided, return the precision and scale provided in the dtype (if applicable).
647
+
648
+ Returns
649
+ -------
650
+ A tuple of ints or a tuple of Nones.
651
+ """
652
+ from meerschaum.utils.dtypes import are_dtypes_equal
653
+ if dtype and are_dtypes_equal(dtype, 'numeric'):
654
+ if '[' in dtype and ',' in dtype:
655
+ try:
656
+ parts = dtype.split('[', maxsplit=1)[-1].rstrip(']').split(',', maxsplit=1)
657
+ return int(parts[0].strip()), int(parts[1].strip())
658
+ except Exception:
659
+ pass
660
+
661
+ if flavor not in NUMERIC_PRECISION_FLAVORS:
662
+ return None, None
663
+
664
+ return NUMERIC_PRECISION_FLAVORS[flavor]
@@ -217,8 +217,8 @@ def print_tuple(
217
217
  tup: mrsm.SuccessTuple,
218
218
  skip_common: bool = True,
219
219
  common_only: bool = False,
220
- upper_padding: int = 0,
221
- lower_padding: int = 0,
220
+ upper_padding: int = 1,
221
+ lower_padding: int = 1,
222
222
  left_padding: int = 1,
223
223
  calm: bool = False,
224
224
  _progress: Optional['rich.progress.Progress'] = None,
@@ -7,21 +7,22 @@ Pretty printing wrapper
7
7
  """
8
8
 
9
9
  def pprint(
10
- *args,
11
- detect_password: bool = True,
12
- nopretty: bool = False,
13
- **kw
14
- ) -> None:
10
+ *args,
11
+ detect_password: bool = True,
12
+ nopretty: bool = False,
13
+ **kw
14
+ ) -> None:
15
15
  """Pretty print an object according to the configured ANSI and UNICODE settings.
16
16
  If detect_password is True (default), search and replace passwords with '*' characters.
17
17
  Does not mutate objects.
18
18
  """
19
+ import copy
20
+ import json
19
21
  from meerschaum.utils.packages import attempt_import, import_rich
20
- from meerschaum.utils.formatting import ANSI, UNICODE, get_console, print_tuple
22
+ from meerschaum.utils.formatting import ANSI, get_console, print_tuple
21
23
  from meerschaum.utils.warnings import error
22
24
  from meerschaum.utils.misc import replace_password, dict_from_od, filter_keywords
23
25
  from collections import OrderedDict
24
- import copy, json
25
26
 
26
27
  if (
27
28
  len(args) == 1
@@ -52,7 +53,7 @@ def pprint(
52
53
  pprintpp = attempt_import('pprintpp', warn=False)
53
54
  try:
54
55
  _pprint = pprintpp.pprint
55
- except Exception as e:
56
+ except Exception :
56
57
  import pprint as _pprint_module
57
58
  _pprint = _pprint_module.pprint
58
59
 
@@ -62,7 +63,7 @@ def pprint(
62
63
 
63
64
  try:
64
65
  args_copy = copy.deepcopy(args)
65
- except Exception as e:
66
+ except Exception:
66
67
  args_copy = args
67
68
  modify = False
68
69
  _args = []
@@ -85,12 +86,12 @@ def pprint(
85
86
  try:
86
87
  c = json.dumps(c)
87
88
  is_json = True
88
- except Exception as e:
89
+ except Exception:
89
90
  is_json = False
90
91
  if not is_json:
91
92
  try:
92
93
  c = str(c)
93
- except Exception as e:
94
+ except Exception:
94
95
  pass
95
96
  _args.append(c)
96
97
 
meerschaum/utils/misc.py CHANGED
@@ -957,24 +957,6 @@ def get_connector_labels(
957
957
  return sorted(possibilities)
958
958
 
959
959
 
960
- def json_serialize_datetime(dt: datetime) -> Union[str, None]:
961
- """
962
- Serialize a datetime object into JSON (ISO format string).
963
-
964
- Examples
965
- --------
966
- >>> import json
967
- >>> from datetime import datetime
968
- >>> json.dumps({'a': datetime(2022, 1, 1)}, default=json_serialize_datetime)
969
- '{"a": "2022-01-01T00:00:00Z"}'
970
-
971
- """
972
- if not isinstance(dt, datetime):
973
- return None
974
- tz_suffix = 'Z' if dt.tzinfo is None else ''
975
- return dt.isoformat() + tz_suffix
976
-
977
-
978
960
  def wget(
979
961
  url: str,
980
962
  dest: Optional[Union[str, 'pathlib.Path']] = None,
@@ -1705,6 +1687,22 @@ def _get_subaction_names(*args, **kwargs) -> Any:
1705
1687
  return real_function(*args, **kwargs)
1706
1688
 
1707
1689
 
1690
+ def json_serialize_datetime(dt: datetime) -> Union[str, None]:
1691
+ """
1692
+ Serialize a datetime object into JSON (ISO format string).
1693
+
1694
+ Examples
1695
+ --------
1696
+ >>> import json
1697
+ >>> from datetime import datetime
1698
+ >>> json.dumps({'a': datetime(2022, 1, 1)}, default=json_serialize_datetime)
1699
+ '{"a": "2022-01-01T00:00:00Z"}'
1700
+
1701
+ """
1702
+ from meerschaum.utils.dtypes import serialize_datetime
1703
+ return serialize_datetime(dt)
1704
+
1705
+
1708
1706
  _current_module = sys.modules[__name__]
1709
1707
  __all__ = tuple(
1710
1708
  name
@@ -585,7 +585,7 @@ def get_connectors_completer(*types: str):
585
585
 
586
586
  class ConnectorCompleter(Completer):
587
587
  def get_completions(self, document, complete_event):
588
- for label in get_connector_labels(*types):
588
+ for label in get_connector_labels(*types, search_term=document.text):
589
589
  yield Completion(label, start_position=(-1 * len(document.text)))
590
590
 
591
591
  return ConnectorCompleter()