execsql2 2.21.1__py3-none-any.whl → 2.22.0__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 (82) hide show
  1. execsql/api.py +21 -4
  2. execsql/cli/run.py +37 -17
  3. execsql/config.py +37 -22
  4. execsql/data/execsql.conf.template +6 -0
  5. execsql/db/access.py +6 -5
  6. execsql/db/base.py +23 -5
  7. execsql/db/dsn.py +2 -0
  8. execsql/db/duckdb.py +1 -1
  9. execsql/db/mysql.py +6 -8
  10. execsql/db/sqlite.py +1 -1
  11. execsql/exceptions.py +14 -11
  12. execsql/exporters/base.py +1 -1
  13. execsql/exporters/delimited.py +22 -16
  14. execsql/exporters/duckdb.py +6 -1
  15. execsql/exporters/feather.py +2 -2
  16. execsql/exporters/json.py +5 -3
  17. execsql/exporters/latex.py +2 -2
  18. execsql/exporters/markdown.py +1 -0
  19. execsql/exporters/ods.py +7 -3
  20. execsql/exporters/parquet.py +1 -1
  21. execsql/exporters/raw.py +8 -8
  22. execsql/exporters/sqlite.py +11 -1
  23. execsql/exporters/templates.py +2 -1
  24. execsql/exporters/xls.py +10 -10
  25. execsql/exporters/xml.py +1 -0
  26. execsql/exporters/yaml.py +1 -0
  27. execsql/exporters/zip.py +9 -7
  28. execsql/format.py +6 -6
  29. execsql/gui/console.py +3 -3
  30. execsql/gui/desktop.py +28 -10
  31. execsql/gui/tui.py +8 -6
  32. execsql/importers/base.py +14 -8
  33. execsql/importers/csv.py +8 -3
  34. execsql/importers/xls.py +1 -0
  35. execsql/metacommands/conditions.py +14 -14
  36. execsql/metacommands/connect.py +2 -2
  37. execsql/metacommands/control.py +1 -1
  38. execsql/metacommands/data.py +2 -1
  39. execsql/metacommands/io_export.py +4 -0
  40. execsql/metacommands/io_fileops.py +85 -39
  41. execsql/metacommands/io_import.py +1 -0
  42. execsql/metacommands/io_write.py +1 -1
  43. execsql/metacommands/prompt.py +24 -22
  44. execsql/metacommands/system.py +14 -1
  45. execsql/models.py +56 -17
  46. execsql/parser.py +8 -8
  47. execsql/plugins.py +1 -1
  48. execsql/script/engine.py +4 -4
  49. execsql/script/executor.py +67 -28
  50. execsql/script/parser.py +13 -12
  51. execsql/script/variables.py +4 -2
  52. execsql/state.pyi +122 -0
  53. execsql/types.py +31 -26
  54. execsql/utils/auth.py +6 -2
  55. execsql/utils/datetime.py +3 -3
  56. execsql/utils/errors.py +7 -5
  57. execsql/utils/fileio.py +77 -31
  58. execsql/utils/gui.py +15 -11
  59. execsql/utils/mail.py +9 -2
  60. execsql/utils/numeric.py +1 -1
  61. execsql/utils/regex.py +9 -9
  62. {execsql2-2.21.1.dist-info → execsql2-2.22.0.dist-info}/METADATA +3 -3
  63. execsql2-2.22.0.dist-info/RECORD +123 -0
  64. execsql2-2.21.1.dist-info/RECORD +0 -122
  65. {execsql2-2.21.1.data → execsql2-2.22.0.data}/data/execsql2_extras/README.md +0 -0
  66. {execsql2-2.21.1.data → execsql2-2.22.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  67. {execsql2-2.21.1.data → execsql2-2.22.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  68. {execsql2-2.21.1.data → execsql2-2.22.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
  69. {execsql2-2.21.1.data → execsql2-2.22.0.data}/data/execsql2_extras/md_compare.sql +0 -0
  70. {execsql2-2.21.1.data → execsql2-2.22.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
  71. {execsql2-2.21.1.data → execsql2-2.22.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
  72. {execsql2-2.21.1.data → execsql2-2.22.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
  73. {execsql2-2.21.1.data → execsql2-2.22.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  74. {execsql2-2.21.1.data → execsql2-2.22.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  75. {execsql2-2.21.1.data → execsql2-2.22.0.data}/data/execsql2_extras/script_template.sql +0 -0
  76. {execsql2-2.21.1.data → execsql2-2.22.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
  77. {execsql2-2.21.1.data → execsql2-2.22.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  78. {execsql2-2.21.1.data → execsql2-2.22.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  79. {execsql2-2.21.1.dist-info → execsql2-2.22.0.dist-info}/WHEEL +0 -0
  80. {execsql2-2.21.1.dist-info → execsql2-2.22.0.dist-info}/entry_points.txt +0 -0
  81. {execsql2-2.21.1.dist-info → execsql2-2.22.0.dist-info}/licenses/LICENSE.txt +0 -0
  82. {execsql2-2.21.1.dist-info → execsql2-2.22.0.dist-info}/licenses/NOTICE +0 -0
execsql/script/parser.py CHANGED
@@ -27,6 +27,7 @@ from __future__ import annotations
27
27
  import re
28
28
  from collections.abc import Iterable
29
29
  from pathlib import Path
30
+ from typing import cast
30
31
 
31
32
  from execsql.exceptions import ErrInfo
32
33
  from execsql.utils.errors import write_warning
@@ -555,7 +556,7 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
555
556
  other_msg=_unclosed_block_msg(block_stack[-1]),
556
557
  )
557
558
  frame = block_stack[-1]
558
- script_node = frame.node
559
+ script_node = cast(ScriptBlock, frame.node)
559
560
  if end_name is not None and end_name != script_node.name: # type: ignore[union-attr]
560
561
  raise ErrInfo(
561
562
  type="cmd",
@@ -619,8 +620,8 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
619
620
  frame = block_stack[-1]
620
621
  frame._in_else = False
621
622
  frame._in_elseif = True
622
- if_node = frame.node
623
- if_node.elseif_clauses.append( # type: ignore[union-attr]
623
+ if_node = cast(IfBlock, frame.node)
624
+ if_node.elseif_clauses.append(
624
625
  ElseIfClause(
625
626
  condition=m.group("cond").strip(),
626
627
  span=SourceSpan(source_name, file_lineno),
@@ -643,11 +644,11 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
643
644
  span=SourceSpan(source_name, file_lineno),
644
645
  )
645
646
  frame = block_stack[-1]
646
- if_node = frame.node
647
- if frame._in_elseif and if_node.elseif_clauses: # type: ignore[union-attr]
648
- if_node.elseif_clauses[-1].condition_modifiers.append(modifier) # type: ignore[union-attr]
647
+ if_node = cast(IfBlock, frame.node)
648
+ if frame._in_elseif and if_node.elseif_clauses:
649
+ if_node.elseif_clauses[-1].condition_modifiers.append(modifier)
649
650
  else:
650
- if_node.condition_modifiers.append(modifier) # type: ignore[union-attr]
651
+ if_node.condition_modifiers.append(modifier)
651
652
  continue
652
653
 
653
654
  # -- ORIF --
@@ -665,11 +666,11 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
665
666
  span=SourceSpan(source_name, file_lineno),
666
667
  )
667
668
  frame = block_stack[-1]
668
- if_node = frame.node
669
- if frame._in_elseif and if_node.elseif_clauses: # type: ignore[union-attr]
670
- if_node.elseif_clauses[-1].condition_modifiers.append(modifier) # type: ignore[union-attr]
669
+ if_node = cast(IfBlock, frame.node)
670
+ if frame._in_elseif and if_node.elseif_clauses:
671
+ if_node.elseif_clauses[-1].condition_modifiers.append(modifier)
671
672
  else:
672
- if_node.condition_modifiers.append(modifier) # type: ignore[union-attr]
673
+ if_node.condition_modifiers.append(modifier)
673
674
  continue
674
675
 
675
676
  # -- ELSE --
@@ -684,7 +685,7 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
684
685
  frame = block_stack[-1]
685
686
  frame._in_else = True
686
687
  frame._in_elseif = False
687
- frame.node.else_span = SourceSpan(source_name, file_lineno) # type: ignore[union-attr]
688
+ cast(IfBlock, frame.node).else_span = SourceSpan(source_name, file_lineno)
688
689
  continue
689
690
 
690
691
  # -- ENDIF --
@@ -91,7 +91,7 @@ class SubVarSet:
91
91
  self._lazy_providers: dict[str, Any] = {}
92
92
  self.prefix_list: list[str] = ["$", "&", "@"]
93
93
  # Don't construct/compile on init because deepcopy() can't handle compiled regexes.
94
- self.var_rx = None
94
+ self.var_rx: re.Pattern[str] | None = None
95
95
 
96
96
  @property
97
97
  def substitutions(self) -> list[tuple]:
@@ -114,7 +114,9 @@ class SubVarSet:
114
114
  def var_name_ok(self, varname: str) -> bool:
115
115
  if self.var_rx is None:
116
116
  self.compile_var_rx()
117
- return self.var_rx.match(varname) is not None
117
+ var_rx = self.var_rx
118
+ assert var_rx is not None
119
+ return var_rx.match(varname) is not None
118
120
 
119
121
  def check_var_name(self, varname: str) -> None:
120
122
  if not self.var_name_ok(varname.lower()):
execsql/state.pyi ADDED
@@ -0,0 +1,122 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import threading
5
+ from contextlib import AbstractContextManager
6
+ from dataclasses import dataclass
7
+ from typing import Any
8
+
9
+ from execsql.config import ConfigData, StatObj
10
+ from execsql.db.base import DatabasePool
11
+ from execsql.exporters.base import ExportMetadata, WriteSpec
12
+ from execsql.script import CounterVars, MetaCommandList, ScriptExecSpec, SubVarSet
13
+ from execsql.utils.fileio import FileWriter, Logger, TempFileMgr
14
+ from execsql.utils.mail import MailSpec
15
+ from execsql.utils.timer import Timer
16
+
17
+ @dataclass
18
+ class ExecFrame:
19
+ kind: str
20
+ label: str = ...
21
+ source: str = ...
22
+ line: int | None = ...
23
+ iteration: int = ...
24
+ params: dict[str, str] | None = ...
25
+ localvars: Any = ...
26
+ paramvals: Any = ...
27
+ paramnames: list[str] | None = ...
28
+ scope_ref: ExecFrame | None = ...
29
+
30
+ varlike: re.Pattern[str]
31
+ endloop_rx: re.Pattern[str]
32
+ loop_rx: re.Pattern[str]
33
+ defer_rx: re.Pattern[str]
34
+ stringtypes: type[str]
35
+ primary_vno: int
36
+ secondary_vno: int
37
+ tertiary_vno: int
38
+
39
+ conf: ConfigData
40
+ logfile_encoding: str
41
+ last_command: Any
42
+ upass: str | None
43
+ err_halt_writespec: WriteSpec | None
44
+ err_halt_email: MailSpec | None
45
+ err_halt_exec: ScriptExecSpec | None
46
+ cancel_halt_writespec: WriteSpec | None
47
+ cancel_halt_mailspec: MailSpec | None
48
+ cancel_halt_exec: ScriptExecSpec | None
49
+ cmds_run: int
50
+ exec_log: Logger
51
+ subvars: SubVarSet
52
+ status: StatObj
53
+ output: Any
54
+ filewriter: FileWriter
55
+ counters: CounterVars
56
+ timer: Timer
57
+ dbs: DatabasePool
58
+ tempfiles: TempFileMgr
59
+ export_metadata: ExportMetadata
60
+ metacommandlist: MetaCommandList
61
+ conditionallist: MetaCommandList
62
+ gui_console: Any
63
+ gui_manager_queue: Any
64
+ gui_manager_thread: threading.Thread | None
65
+ profile_data: list[tuple[Any, ...]] | None
66
+ step_mode: bool
67
+ ast_scripts: dict[str, Any]
68
+ include_chain: list[str]
69
+ ast_exec_stack: list[ExecFrame]
70
+
71
+ class RuntimeContext:
72
+ conf: ConfigData | None
73
+ logfile_encoding: str
74
+ last_command: Any
75
+ upass: str | None
76
+ err_halt_writespec: WriteSpec | None
77
+ err_halt_email: MailSpec | None
78
+ err_halt_exec: ScriptExecSpec | None
79
+ cancel_halt_writespec: WriteSpec | None
80
+ cancel_halt_mailspec: MailSpec | None
81
+ cancel_halt_exec: ScriptExecSpec | None
82
+ cmds_run: int
83
+ exec_log: Logger | None
84
+ subvars: SubVarSet | None
85
+ status: StatObj | None
86
+ output: Any
87
+ filewriter: FileWriter | None
88
+ counters: CounterVars | None
89
+ timer: Timer | None
90
+ dbs: DatabasePool | None
91
+ tempfiles: TempFileMgr | None
92
+ export_metadata: ExportMetadata | None
93
+ metacommandlist: MetaCommandList | None
94
+ conditionallist: MetaCommandList | None
95
+ gui_console: Any
96
+ gui_manager_queue: Any
97
+ gui_manager_thread: threading.Thread | None
98
+ profile_data: list[tuple[Any, ...]] | None
99
+ step_mode: bool
100
+ ast_scripts: dict[str, Any]
101
+ include_chain: list[str]
102
+ ast_exec_stack: list[ExecFrame]
103
+ def __init__(self) -> None: ...
104
+ def current_scope(self) -> ExecFrame | None: ...
105
+ def current_localvars(self) -> Any: ...
106
+ def current_paramvals(self) -> Any: ...
107
+ def outer_script_scopes(self) -> list[ExecFrame]: ...
108
+
109
+ def xcmd_test(teststr: str) -> bool: ...
110
+ def current_scope() -> ExecFrame | None: ...
111
+ def current_localvars() -> Any: ...
112
+ def current_paramvals() -> Any: ...
113
+ def outer_script_scopes() -> list[ExecFrame]: ...
114
+ def get_context() -> RuntimeContext: ...
115
+ def set_context(ctx: RuntimeContext) -> None: ...
116
+ def reset() -> None: ...
117
+ def initialize(config: ConfigData, dispatch_table: object, conditional_table: object) -> None: ...
118
+
119
+ class active_context(AbstractContextManager[RuntimeContext]):
120
+ def __init__(self, ctx: RuntimeContext) -> None: ...
121
+ def __enter__(self) -> RuntimeContext: ...
122
+ def __exit__(self, exc_type: object, exc: object, tb: object) -> None: ...
execsql/types.py CHANGED
@@ -34,7 +34,9 @@ provided at module level for every supported DBMS:
34
34
  import collections
35
35
  import datetime
36
36
  import re
37
+ from collections.abc import Callable
37
38
  from decimal import Decimal
39
+ from typing import Any, cast
38
40
 
39
41
  from execsql.exceptions import DataTypeError, DbTypeError
40
42
  from execsql.utils.numeric import leading_zero_num
@@ -71,13 +73,13 @@ __all__ = [
71
73
  class DataType:
72
74
  """Abstract base class for all data-type matchers used during column inference."""
73
75
 
74
- data_type_name = None
75
- data_type = None
76
+ data_type_name: str = "unknown"
77
+ data_type: type[Any] = object
76
78
  lenspec = False # Is a length specification required for a (SQL) declaration of this data type?
77
79
  varlen = False # Do we need to know if a set of data values varies in length?
78
80
  precspec = False # Do we need to know the precision and scale of the data?
79
- precision = None # Precision (total number of digits) for numeric values.
80
- scale = None # Scale (number of digits to the right of the decimal point) for numeric values.
81
+ precision: int | None = None # Precision (total number of digits) for numeric values.
82
+ scale: int | None = None # Scale (number of digits to the right of the decimal point) for numeric values.
81
83
  _CONV_ERR = "Can't convert %s"
82
84
 
83
85
  def __repr__(self) -> str:
@@ -402,7 +404,7 @@ class DT_Integer(DataType):
402
404
  if isinstance(data, str) and not self._INT_RX.match(data):
403
405
  raise DataTypeError(self.data_type_name, self._CONV_ERR % data)
404
406
  try:
405
- i = int(data)
407
+ i = int(cast(Any, data))
406
408
  except Exception as e:
407
409
  raise DataTypeError(self.data_type_name, self._CONV_ERR % data) from e
408
410
  if leading_zero_num(data):
@@ -440,7 +442,7 @@ class DT_Long(DataType):
440
442
  if isinstance(data, str) and not data.isdigit():
441
443
  raise DataTypeError(self.data_type_name, self._CONV_ERR % data)
442
444
  try:
443
- i = int(data)
445
+ i = int(cast(Any, data))
444
446
  except Exception as e:
445
447
  raise DataTypeError(self.data_type_name, self._CONV_ERR % data) from e
446
448
  return i
@@ -466,7 +468,7 @@ class DT_Float(DataType):
466
468
  if isinstance(data, str) and not self._FLOAT_RX.match(data):
467
469
  return False
468
470
  try:
469
- float(data)
471
+ float(cast(Any, data))
470
472
  except Exception:
471
473
  return False
472
474
  return True
@@ -481,7 +483,7 @@ class DT_Float(DataType):
481
483
  if isinstance(data, str) and not self._FLOAT_RX.match(data):
482
484
  raise DataTypeError(self.data_type_name, self._CONV_ERR % data)
483
485
  try:
484
- i = float(data)
486
+ i = float(cast(Any, data))
485
487
  except Exception as e:
486
488
  raise DataTypeError(self.data_type_name, self._CONV_ERR % data) from e
487
489
  return i
@@ -503,11 +505,16 @@ class DT_Decimal(DataType):
503
505
  # 'dec' should be Decimal.
504
506
  x = dec.as_tuple()
505
507
  digits = len(x.digits)
506
- if x.exponent < 0 and abs(x.exponent) > digits:
507
- self.precision = abs(x.exponent) + 1
508
+ exponent = x.exponent
509
+ if not isinstance(exponent, int):
510
+ self.precision = digits
511
+ self.scale = 0
512
+ return
513
+ if exponent < 0 and abs(exponent) > digits:
514
+ self.precision = abs(exponent) + 1
508
515
  else:
509
516
  self.precision = digits
510
- self.scale = abs(x.exponent)
517
+ self.scale = abs(exponent)
511
518
 
512
519
  def _from_data(self, data: object) -> object:
513
520
  if data is None:
@@ -643,22 +650,22 @@ class DbType:
643
650
  # 3. a function to perform a dbms-specific modification of the type conversion result
644
651
  # 4. the precision for numeric data types.
645
652
  # 5. the scale for numeric data types.
646
- self.dialect = None
653
+ self.dialect: dict[type[DataType], tuple[str, bool, str | None, Callable[[Any], Any] | None, Any, Any]] = {}
647
654
  # The dt_xlate dictionary translates one data type to another.
648
- self.dt_xlate: dict = {}
655
+ self.dt_xlate: dict[type[DataType], type[DataType]] = {}
649
656
 
650
657
  def __repr__(self) -> str:
651
658
  return f"DbType({self.dbms_id!r}, {self.quotechars!r})"
652
659
 
653
660
  def name_datatype(
654
661
  self,
655
- data_type: object,
662
+ data_type: type[DataType],
656
663
  dbms_name: str,
657
664
  length_required: bool = False,
658
- casting_name: object = None,
659
- conv_mod_fn: object = None,
660
- precision: object = None,
661
- scale: object = None,
665
+ casting_name: str | None = None,
666
+ conv_mod_fn: Callable[[Any], Any] | None = None,
667
+ precision: Any = None,
668
+ scale: Any = None,
662
669
  ) -> None:
663
670
  """Register a DBMS-specific SQL type name for a DataType class."""
664
671
  # data_type is a DataType class object.
@@ -666,11 +673,9 @@ class DbType:
666
673
  # length_required indicates whether length information is required.
667
674
  # casting_name is an alternate to the data type name to use in SQL "cast(x as <casting_name>)" expressions.
668
675
  # conv_mod_fn is a function that modifies the result of data_type().from_data(x).
669
- if self.dialect is None:
670
- self.dialect = {}
671
676
  self.dialect[data_type] = (dbms_name, length_required, casting_name, conv_mod_fn, precision, scale)
672
677
 
673
- def datatype_name(self, data_type: object) -> str:
678
+ def datatype_name(self, data_type: type[DataType]) -> str:
674
679
  """Return the DBMS-specific SQL type name for the given DataType class."""
675
680
  # A convenience function to simplify access to data type names.
676
681
  try:
@@ -690,7 +695,7 @@ class DbType:
690
695
  return self.quotechars[0] + dbms_object + self.quotechars[1]
691
696
  return dbms_object
692
697
 
693
- def spec_type(self, data_type: object) -> object:
698
+ def spec_type(self, data_type: type[DataType]) -> type[DataType]:
694
699
  """Return the translated data type, or the original if no translation exists."""
695
700
  # Returns a translated data type or the original if there is no translation.
696
701
  if data_type in self.dt_xlate:
@@ -700,11 +705,11 @@ class DbType:
700
705
  def column_spec(
701
706
  self,
702
707
  column_name: str,
703
- data_type: object,
704
- max_len: object = None,
708
+ data_type: type[DataType],
709
+ max_len: int | None = None,
705
710
  is_nullable: bool = False,
706
- precision: object = None,
707
- scale: object = None,
711
+ precision: int | None = None,
712
+ scale: int | None = None,
708
713
  ) -> str:
709
714
  """Return a column specification string suitable for a CREATE TABLE statement."""
710
715
  # Returns a column specification as it would be used in a CREATE TABLE statement.
execsql/utils/auth.py CHANGED
@@ -26,6 +26,7 @@ Keyring service names follow the pattern
26
26
  """
27
27
 
28
28
  import getpass
29
+ from typing import Any, cast
29
30
 
30
31
  import execsql.state as _state
31
32
 
@@ -181,6 +182,7 @@ def get_password(
181
182
  if other_msg is not None:
182
183
  prompt = f"{prompt}\n{other_msg}"
183
184
 
185
+ passwd = ""
184
186
  # Try GUI path if a GUI manager thread is running.
185
187
  use_gui = False
186
188
  try:
@@ -189,9 +191,10 @@ def get_password(
189
191
  gui_manager_thread = getattr(_state, "gui_manager_thread", None)
190
192
  gui_manager_queue = getattr(_state, "gui_manager_queue", None)
191
193
  if gui_manager_thread:
192
- return_queue = queue.Queue()
194
+ return_queue: queue.Queue[Any] = queue.Queue()
193
195
  from execsql.utils.gui import GuiSpec, QUERY_CONSOLE
194
196
 
197
+ assert gui_manager_queue is not None
195
198
  gui_manager_queue.put(GuiSpec(QUERY_CONSOLE, {}, return_queue))
196
199
  user_response = return_queue.get(block=True)
197
200
  use_gui = user_response["console_running"]
@@ -215,10 +218,11 @@ def get_password(
215
218
  "hidetext": True,
216
219
  }
217
220
  gui_manager_queue = getattr(_state, "gui_manager_queue", None)
221
+ assert gui_manager_queue is not None
218
222
  gui_manager_queue.put(GuiSpec(GUI_DISPLAY, gui_args, return_queue))
219
223
  user_response = return_queue.get(block=True)
220
224
  btn = user_response["button"]
221
- passwd = user_response["return_value"]
225
+ passwd = cast(str, user_response["return_value"])
222
226
  if not btn and _state.status and _state.status.cancel_halt:
223
227
  if _state.exec_log:
224
228
  _state.exec_log.log_exit_halt(
execsql/utils/datetime.py CHANGED
@@ -13,7 +13,7 @@ Delegates to ``dateutil.parser.parse()`` for robust, format-agnostic parsing.
13
13
 
14
14
  import datetime
15
15
  import re
16
- from typing import Any
16
+ from typing import Any, cast
17
17
 
18
18
  from dateutil import parser as _dateutil_parser
19
19
 
@@ -66,7 +66,7 @@ def parse_datetime(datestr: Any) -> datetime.datetime | None:
66
66
  if _looks_time_only(datestr):
67
67
  return None
68
68
  try:
69
- return _dateutil_parser.parse(datestr)
69
+ return cast(datetime.datetime, _dateutil_parser.parse(datestr))
70
70
  except (ValueError, OverflowError, TypeError):
71
71
  return None
72
72
 
@@ -90,7 +90,7 @@ def parse_datetimetz(data: Any) -> datetime.datetime | None:
90
90
  if _looks_numeric(data):
91
91
  return None
92
92
  try:
93
- dt = _dateutil_parser.parse(data)
93
+ dt = cast(datetime.datetime, _dateutil_parser.parse(data))
94
94
  except (ValueError, OverflowError, TypeError):
95
95
  return None
96
96
  if dt.tzinfo is None or dt.tzinfo.utcoffset(dt) is None:
execsql/utils/errors.py CHANGED
@@ -48,11 +48,13 @@ def exception_info() -> tuple:
48
48
  exc_param = sys.exc_info()[1]
49
49
  if isinstance(exc_param, str):
50
50
  exc_message = exc_param
51
- else:
52
- if hasattr(exc_param, "message") and isinstance(exc_param.message, str) and len(exc_param.message) > 0:
53
- exc_message = exc_param.message
54
- elif hasattr(exc_param, "value") and isinstance(exc_param.value, str) and len(exc_param.value) > 0:
55
- exc_message = exc_param.value
51
+ elif exc_param is not None:
52
+ message = getattr(exc_param, "message", None)
53
+ value = getattr(exc_param, "value", None)
54
+ if isinstance(message, str) and len(message) > 0:
55
+ exc_message = message
56
+ elif isinstance(value, str) and len(value) > 0:
57
+ exc_message = value
56
58
  else:
57
59
  exc_message = str(exc_param)
58
60
  try: