2626This module source should run correctly in CPython versions 2.7 and later,
2727or CPython 3.4 or later.
2828"""
29-
30- __version__ = "2.6.2.0"
31- version = "adodbapi v" + __version__
29+ from __future__ import annotations
3230
3331import copy
3432import decimal
3533import os
3634import sys
3735import weakref
36+ from collections .abc import Iterable , Mapping , Sequence
37+ from typing import TYPE_CHECKING , Callable , Literal , NoReturn , cast
3838
3939from . import ado_consts as adc , apibase as api , process_connect_string
4040
41+ if TYPE_CHECKING :
42+ from win32com .client import DispatchBaseClass
43+ from win32com .client .dynamic import CDispatch
44+
45+ __version__ = "2.6.2.0"
46+ version = "adodbapi v" + __version__
47+
4148try :
4249 verbose = int (os .environ ["ADODBAPI_VERBOSE" ])
4350except :
@@ -59,9 +66,6 @@ def getIndexedValue(obj, index):
5966 return obj (index )
6067
6168
62- from collections .abc import Mapping
63-
64-
6569# ----------------- The .connect method -----------------
6670def make_COM_connecter ():
6771 try :
@@ -175,9 +179,10 @@ def _configure_parameter(p, value, adotype, settings_known):
175179
176180 elif isinstance (value , decimal .Decimal ):
177181 p .Value = value
178- exponent = value .as_tuple ()[ 2 ]
179- digit_count = len (value .as_tuple ()[ 1 ] )
182+ exponent = value .as_tuple (). exponent
183+ digit_count = len (value .as_tuple (). digits )
180184 p .Precision = digit_count
185+ assert isinstance (exponent , int )
181186 if exponent == 0 :
182187 p .NumericScale = 0
183188 elif exponent < 0 :
@@ -224,25 +229,37 @@ class Connection:
224229 FetchFailedError = api .FetchFailedError # (special for django)
225230 # ...class attributes... (can be overridden by instance attributes)
226231 verbose = api .verbose
232+ # Can be set to override api.variantConversions in Cursor
233+ variantConversions : api .MultiMap
227234
228235 @property
229236 def dbapi (self ): # a proposed db-api version 3 extension.
230237 "Return a reference to the DBAPI module for this Connection."
231238 return api
232239
233- def __init__ (self ): # now define the instance attributes
234- self .connector = None
240+ def __init__ (self ) -> None : # now define the instance attributes
241+ # Let it error if a connection is used before calling connect or after calling close
242+ self .connector : CDispatch | DispatchBaseClass = None # type: ignore[assignment]
235243 self .paramstyle = api .paramstyle
236244 self .supportsTransactions = False
237245 self .connection_string = ""
238246 self .cursors = weakref .WeakValueDictionary [int , Cursor ]()
239247 self .dbms_name = ""
240248 self .dbms_version = ""
241- self .errorhandler = None # use the standard error handler for this instance
249+ self .errorhandler : Callable [..., NoReturn ] | None = (
250+ None # use the standard error handler for this instance
251+ )
242252 self .transaction_level = 0 # 0 == Not in a transaction, at the top level
243253 self ._autocommit = False
244-
245- def connect (self , kwargs , connection_maker = make_COM_connecter ):
254+ self .messages : list [tuple [type [api .Error ], api .Error ]] = []
255+
256+ def connect (
257+ self ,
258+ kwargs ,
259+ connection_maker : Callable [
260+ [], CDispatch | DispatchBaseClass
261+ ] = make_COM_connecter ,
262+ ):
246263 if verbose > 9 :
247264 print (f"kwargs={ kwargs !r} " )
248265 try :
@@ -299,7 +316,7 @@ def connect(self, kwargs, connection_maker=make_COM_connecter):
299316 if verbose :
300317 print ("adodbapi New connection at %X" % id (self ))
301318
302- def _raiseConnectionError (self , errorclass , errorvalue ):
319+ def _raiseConnectionError (self , errorclass , errorvalue ) -> NoReturn :
303320 eh = self .errorhandler
304321 if eh is None :
305322 eh = api .standardErrorHandler
@@ -337,7 +354,7 @@ def close(self):
337354 except Exception as e :
338355 self ._raiseConnectionError (sys .exc_info ()[0 ], sys .exc_info ()[1 ])
339356
340- self .connector = None # v2.4.2.2 fix subtle timeout bug
357+ self .connector = None # pyright: ignore[reportAttributeAccessIssue] # v2.4.2.2 fix subtle timeout bug
341358 # per M.Hammond: "I expect the benefits of uninitializing are probably fairly small,
342359 # so never uninitializing will probably not cause any problems."
343360
@@ -421,22 +438,20 @@ def __setattr__(self, name, value):
421438 value = copy .copy (value )
422439 object .__setattr__ (self , name , value )
423440
424- def __getattr__ (self , item ):
425- if (
426- item == "rollback"
427- ): # the rollback method only appears if the database supports transactions
428- if self .supportsTransactions :
429- return (
430- self ._rollback
431- ) # return the rollback method so the caller can execute it.
432- else :
433- raise AttributeError ("this data provider does not support Rollback" )
434- elif item == "autocommit" :
435- return self ._autocommit
441+ @property
442+ def rollback (self ):
443+ """Gets the rollback method so the caller can execute it.
444+
445+ The rollback method only appears if the database supports transactions
446+ """
447+ if self .supportsTransactions :
448+ return self ._rollback
436449 else :
437- raise AttributeError (
438- 'no such attribute in ADO connection object as="%s"' % item
439- )
450+ raise AttributeError ("this data provider does not support Rollback" )
451+
452+ @property
453+ def autocommit (self ):
454+ return self ._autocommit
440455
441456 def cursor (self ):
442457 "Return a new Cursor Object using the connection."
@@ -465,7 +480,7 @@ def printADOerrors(self):
465480 print ("Error: %s %s " % (e .Number , adc .adoErrors .get (e .Number , "unknown" )))
466481 if e .Number == adc .ado_error_TIMEOUT :
467482 print (
468- "Timeout Error: Try using adodbpi .connect(constr,timeout=Nseconds)"
483+ "Timeout Error: Try using adodbapi .connect(constr,timeout=Nseconds)"
469484 )
470485 print ("Source: %s" % e .Source )
471486 print ("NativeError: %s" % e .NativeError )
@@ -492,7 +507,7 @@ def __del__(self):
492507 self ._closeAdoConnection () # v2.1 Rose
493508 except :
494509 pass
495- self .connector = None
510+ self .connector = None # pyright: ignore[reportAttributeAccessIssue]
496511
497512 def __enter__ (self ): # Connections are context managers
498513 return self
@@ -542,17 +557,21 @@ class Cursor:
542557 ## errorhandler...
543558 ## allows the programmer to override the connection's default error handler
544559
545- def __init__ (self , connection ) :
560+ def __init__ (self , connection : Connection ) -> None :
546561 self .command = None
547- self ._ado_prepared = False
548- self .messages = []
549- self .connection = connection
550- self .paramstyle = connection .paramstyle # used for overriding the paramstyle
551- self ._parameter_names = []
562+ self ._ado_prepared : bool | Literal ["setup" ] = False
563+ self .messages : list [tuple [type [api .Error ], api .Error ]] = []
564+ self .connection : Connection = connection
565+ self .paramstyle = connection .paramstyle
566+ """Used for overriding the paramstyle"""
567+ self ._parameter_names : list [str ] = []
552568 self .recordset_is_remote = False
553- self .rs = None # the ADO recordset for this cursor
554- self .converters = [] # conversion function for each column
555- self .columnNames = {} # names of columns {lowercase name : number,...}
569+ self .rs = None
570+ """The ADO recordset for this cursor"""
571+ self .converters : list [Callable [[object ], object ]] = []
572+ """Conversion function for each column"""
573+ self .columnNames : dict [str , int ] = {}
574+ """Names of columns {lowercase name : number,...}"""
556575 self .numberOfColumns = 0
557576 self ._description = None
558577 self .rowcount = - 1
@@ -587,7 +606,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
587606 "Allow database cursors to be used with context managers."
588607 self .close ()
589608
590- def _raiseCursorError (self , errorclass , errorvalue ):
609+ def _raiseCursorError (self , errorclass , errorvalue ) -> NoReturn :
591610 eh = self .errorhandler
592611 if eh is None :
593612 eh = api .standardErrorHandler
@@ -613,9 +632,8 @@ def build_column_info(self, recordset):
613632 for i in range (self .numberOfColumns ):
614633 f = getIndexedValue (self .rs .Fields , i )
615634 try :
616- self .converters .append (
617- varCon [f .Type ]
618- ) # conversion function for this column
635+ # conversion function for this column
636+ self .converters .append (varCon [f .Type ])
619637 except KeyError :
620638 self ._raiseCursorError (
621639 api .InternalError , "Data column of Unknown ADO type=%s" % f .Type
@@ -633,7 +651,7 @@ def _makeDescriptionFromRS(self):
633651 if self .rs .EOF or self .rs .BOF :
634652 display_size = None
635653 else :
636- # TODO: Is this the correct defintion according to the DB API 2 Spec ?
654+ # TODO: Is this the correct definition according to the DB API 2 Spec ?
637655 display_size = f .ActualSize
638656 null_ok = bool (f .Attributes & adc .adFldMayBeNull ) # v2.1 Cole
639657 desc .append (
@@ -654,17 +672,16 @@ def get_description(self):
654672 self ._makeDescriptionFromRS ()
655673 return self ._description
656674
657- def __getattr__ (self , item ):
658- if item == "description" :
659- return self .get_description ()
660- object .__getattribute__ (
661- self , item
662- ) # may get here on Remote attribute calls for existing attributes
675+ @property
676+ def description (self ):
677+ return self .get_description ()
663678
664679 def format_description (self , d ):
665680 """Format db_api description tuple for printing."""
666681 if self .description is None :
667682 self ._makeDescriptionFromRS ()
683+ if self .description is None :
684+ return "None"
668685 if isinstance (d , int ):
669686 d = self .description [d ]
670687 desc = (
@@ -690,17 +707,15 @@ def close(self, dont_tell_me=False):
690707 return
691708 self .messages = []
692709 if (
710+ # rs exists and is open #v2.1 Rose
693711 self .rs and self .rs .State != adc .adStateClosed
694- ): # rs exists and is open #v2.1 Rose
712+ ):
695713 self .rs .Close () # v2.1 Rose
696714 self .rs = None # let go of the recordset so ADO will let it be disposed #v2.1 Rose
697715 if not dont_tell_me :
698- self .connection ._i_am_closing (
699- self
700- ) # take me off the connection's cursors list
701- self .connection = (
702- None # this will make all future method calls on me throw an exception
703- )
716+ # take me off the connection's cursors list
717+ self .connection ._i_am_closing (self )
718+ self .connection = None # pyright: ignore[reportAttributeAccessIssue] # this will make all future method calls on me throw an exception
704719 if verbose :
705720 print ("adodbapi Closed cursor at %X" % id (self ))
706721
@@ -711,7 +726,6 @@ def __del__(self):
711726 pass
712727
713728 def _new_command (self , command_type = adc .adCmdText ):
714- self .cmd = None
715729 self .messages = []
716730
717731 if self .connection is None :
@@ -830,15 +844,16 @@ def _reformat_operation(self, operation, parameters):
830844 elif self .paramstyle == "named" or (
831845 self .paramstyle == "dynamic" and isinstance (parameters , Mapping )
832846 ):
833- operation , self ._parameter_names = api .changeNamedToQmark (
834- operation
835- ) # convert :name to ?
847+ # convert :name to ?
848+ operation , self ._parameter_names = api .changeNamedToQmark (operation )
836849 return operation
837850
838- def _buildADOparameterList (self , parameters , sproc = False ):
851+ def _buildADOparameterList (
852+ self , parameters : Mapping [str , object ] | Sequence [object ] | None , sproc = False
853+ ):
839854 self .parameters = parameters
840855 if parameters is None :
841- parameters = []
856+ parameters = {}
842857
843858 # Note: ADO does not preserve the parameter list, even if "Prepared" is True, so we must build every time.
844859 parameters_known = False
@@ -866,6 +881,7 @@ def _buildADOparameterList(self, parameters, sproc=False):
866881 i = 0
867882 if parameters_known : # use ado parameter list
868883 if self ._parameter_names : # named parameters
884+ assert isinstance (parameters , Mapping )
869885 for i , pm_name in enumerate (self ._parameter_names ):
870886 p = getIndexedValue (self .cmd .Parameters , i )
871887 try :
@@ -906,6 +922,7 @@ def _buildADOparameterList(self, parameters, sproc=False):
906922 else : # -- build own parameter list
907923 # we expect a dictionary of parameters, this is the list of expected names
908924 if self ._parameter_names :
925+ assert isinstance (parameters , Mapping )
909926 for parm_name in self ._parameter_names :
910927 elem = parameters [parm_name ]
911928 adotype = api .pyTypeToADOType (elem )
@@ -957,9 +974,8 @@ def _buildADOparameterList(self, parameters, sproc=False):
957974 )
958975 i += 1
959976 if self ._ado_prepared == "setup" :
960- self ._ado_prepared = (
961- True # parameters will be "known" by ADO next loop
962- )
977+ # parameters will be "known" by ADO next loop
978+ self ._ado_prepared = True
963979
964980 def execute (self , operation , parameters = None ):
965981 """Prepare and execute a database operation (query or command).
@@ -1013,9 +1029,9 @@ def executemany(self, operation, seq_of_parameters):
10131029 """Prepare a database operation (query or command)
10141030 and then execute it against all parameter sequences or mappings found in the sequence seq_of_parameters.
10151031
1016- Return values are not defined.
1032+ Return values are not defined.
10171033 """
1018- self .messages = list ()
1034+ self .messages = []
10191035 total_recordcount = 0
10201036
10211037 self .prepare (operation )
@@ -1036,7 +1052,7 @@ def _fetch(self, limit=None):
10361052 self ._raiseCursorError (
10371053 api .FetchFailedError , "fetch() on closed connection or empty query set"
10381054 )
1039- return
1055+ return # Still return in case a custom errorHandler doesn't raise
10401056
10411057 if self .rs .State == adc .adStateClosed or self .rs .BOF or self .rs .EOF :
10421058 return list ()
0 commit comments