@@ -75,6 +75,7 @@ class Connection:
7575 setencoding(encoding=None, ctype=None) -> None:
7676 setdecoding(sqltype, encoding=None, ctype=None) -> None:
7777 getdecoding(sqltype) -> dict:
78+ set_attr(attribute, value) -> None: # Add this line
7879 """
7980
8081 def __init__ (self , connection_str : str = "" , autocommit : bool = False , attrs_before : dict = None , ** kwargs ) -> None :
@@ -520,6 +521,78 @@ def rollback(self) -> None:
520521 self ._conn .rollback ()
521522 log ('info' , "Transaction rolled back successfully." )
522523
524+ def set_attr (self , attribute , value ):
525+ """
526+ Set a connection attribute.
527+
528+ This method sets a connection attribute using SQLSetConnectAttr.
529+ It provides pyodbc-compatible functionality for configuring connection
530+ behavior such as autocommit mode, transaction isolation level, and
531+ connection timeouts.
532+
533+ Args:
534+ attribute (int): The connection attribute to set. Should be one of the
535+ SQL_ATTR_* constants (e.g., SQL_ATTR_AUTOCOMMIT,
536+ SQL_ATTR_TXN_ISOLATION).
537+ value: The value to set for the attribute. Can be an integer or bytes/bytearray
538+ depending on the attribute type.
539+
540+ Raises:
541+ InterfaceError: If the connection is closed or attribute is invalid.
542+ ProgrammingError: If the value type or range is invalid.
543+
544+ Example:
545+ >>> conn.set_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF)
546+ >>> conn.set_attr(SQL_ATTR_TXN_ISOLATION, SQL_TXN_READ_COMMITTED)
547+
548+ Note:
549+ This method is compatible with pyodbc's set_attr functionality.
550+ Attribute values must be within valid SQLUINTEGER range (0 to 4294967295).
551+ """
552+ if self ._closed :
553+ raise InterfaceError ("Cannot set attribute on closed connection" , "Connection is closed" )
554+
555+ # Validate attribute type and range for SQLUINTEGER compatibility
556+ if not isinstance (attribute , int ) or attribute < 0 :
557+ raise ProgrammingError ("Connection attribute must be a non-negative integer" , f"Invalid attribute: { attribute } " )
558+
559+ # Validate attribute is within SQLUINTEGER range
560+ if attribute > 4294967295 : # 2^32 - 1
561+ raise ProgrammingError ("Connection attribute must be within SQLUINTEGER range (0-4294967295)" , f"Attribute out of range: { attribute } " )
562+
563+ # Validate value type - must be integer, bytes, or bytearray
564+ if not isinstance (value , (int , bytes , bytearray )):
565+ raise ProgrammingError ("Attribute value must be an integer, bytes, or bytearray" , f"Invalid value type: { type (value )} " )
566+
567+ # For integer values, validate SQLUINTEGER range
568+ if isinstance (value , int ):
569+ if value < 0 or value > 4294967295 : # 2^32 - 1
570+ raise ProgrammingError ("Attribute value out of range for SQLUINTEGER (0-4294967295)" , f"Value out of range: { value } " )
571+
572+ # Sanitize user input for security
573+ try :
574+ sanitized_input = sanitize_user_input (str (attribute ))
575+ log ('debug' , f"Setting connection attribute: { sanitized_input } " )
576+ except Exception :
577+ # If sanitization fails, log without user input
578+ log ('debug' , "Setting connection attribute" )
579+
580+ try :
581+ # Call the underlying C++ method
582+ self ._conn .set_attr (attribute , value )
583+ log ('info' , f"Connection attribute { attribute } set successfully" )
584+
585+ except Exception as e :
586+ error_msg = f"Failed to set connection attribute { attribute } : { str (e )} "
587+ log ('error' , error_msg )
588+
589+ # Determine appropriate exception type based on error content
590+ error_str = str (e ).lower ()
591+ if 'invalid' in error_str or 'unsupported' in error_str or 'cast' in error_str :
592+ raise InterfaceError (error_msg , str (e )) from e
593+ else :
594+ raise ProgrammingError (error_msg , str (e )) from e
595+
523596 def close (self ) -> None :
524597 """
525598 Close the connection now (rather than whenever .__del__() is called).
0 commit comments