- 
                Notifications
    You must be signed in to change notification settings 
- Fork 27
FEAT: Support for Native_UUID Attribute #282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a new native_uuid global setting to control whether UUID values are returned as uuid.UUID objects or strings, with a default value of False for backward compatibility.
Key changes:
- Added native_uuidsetting to global configuration with backward-compatible default
- Updated row processing logic to convert UUID objects to strings when native_uuid=False
- Enhanced test coverage to verify UUID handling behavior for both setting states
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description | 
|---|---|
| mssql_python/init.py | Added native_uuidsetting initialization and module-level access | 
| mssql_python/row.py | Implemented UUID conversion logic based on native_uuidsetting | 
| mssql_python/cursor.py | Removed unused UUID mapping code | 
| tests/test_004_cursor.py | Added comprehensive tests for native_uuidsetting and updated existing UUID tests | 
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few comments
| 📊 Code Coverage Report
 Diff CoverageDiff: main...HEAD, staged and unstaged changes
 Summary
 mssql_python/init.pyLines 275-284   275 for attr_name in dir(old_module):
  276     if attr_name != "__class__":
  277         try:
  278             setattr(new_module, attr_name, getattr(old_module, attr_name))
! 279         except AttributeError:
! 280             pass
  281 
  282 # Replace the module in sys.modules
  283 sys.modules[__name__] = new_modulemssql_python/cursor.pyLines 1057-1065   1057                 if desc and desc[1] == uuid.UUID:  # Column type code at index 1
  1058                     self._uuid_indices.append(i)
  1059                 # Verify we have complete description tuples (7 items per PEP-249)
  1060                 elif desc and len(desc) != 7:
! 1061                     log('warning', f"Column description at index {i} has incorrect tuple length: {len(desc)}")
  1062             self.rowcount = -1
  1063             self._reset_rownumber()
  1064         else:
  1065             self.rowcount = ddbc_bindings.DDBCSQLRowCount(self.hstmt)mssql_python/row.pyLines 24-33   24         # Use settings snapshot if provided, otherwise fallback to global settings
  25         if settings_snapshot is not None:
  26             self._settings = settings_snapshot
  27         else:
! 28             settings = get_settings()
! 29             self._settings = {
  30                 'lowercase': settings.lowercase,
  31                 'native_uuid': settings.native_uuid
  32             }
  33         # Create mapping of column names to indices firstLines 40-48   40                     if self._settings.get('lowercase'):
  41                         col_name = col_name.lower()
  42                     self._column_map[col_name] = i
  43         else:
! 44             self._column_map = column_map
  45         
  46         # First make a mutable copy of values
  47         processed_values = list(values)
  48         Lines 78-106    78                 for i in uuid_indices:
   79                     if i < len(processed_values) and processed_values[i] is not None:
   80                         value = processed_values[i]
   81                         if isinstance(value, str):
!  82                             try:
   83                                 # Remove braces if present
!  84                                 clean_value = value.strip('{}')
!  85                                 processed_values[i] = uuid.UUID(clean_value)
!  86                             except (ValueError, AttributeError):
!  87                                 pass  # Keep original if conversion fails
   88             # Fallback to scanning all columns if indices weren't pre-identified
   89             else:
!  90                 for i, value in enumerate(processed_values):
!  91                     if value is None:
!  92                         continue
   93                     
!  94                     if i < len(description) and description[i]:
   95                         # Check SQL type for UNIQUEIDENTIFIER (-11)
!  96                         sql_type = description[i][1]
!  97                         if sql_type == -11:  # SQL_GUID
!  98                             if isinstance(value, str):
!  99                                 try:
! 100                                     processed_values[i] = uuid.UUID(value.strip('{}'))
! 101                                 except (ValueError, AttributeError):
! 102                                     pass
  103         # When native_uuid is False, convert UUID objects to strings
  104         else:
  105             for i, value in enumerate(processed_values):
  106                 if isinstance(value, uuid.UUID):Lines 165-176   165                         try:
  166                             # Use signed=True to properly handle negative values
  167                             value_bytes = value.to_bytes(byte_size, byteorder='little', signed=True)
  168                             converted_values[i] = converter(value_bytes)
! 169                         except OverflowError as e:
  170                             # Log specific overflow error with details to help diagnose the issue
! 171                             if hasattr(self._cursor, 'log'):
! 172                                 self._cursor.log('warning', 
  173                                     f'Integer overflow: value {value} does not fit in {byte_size} bytes for SQL type {sql_type}')
  174                             # Keep the original value in this case
  175                     else:
  176                         # Pass the value directly for other typesLines 177-185   177                         converted_values[i] = converter(value)
  178                 except Exception as e:
  179                     # Log the exception for debugging without leaking sensitive data
  180                     if hasattr(self._cursor, 'log'):
! 181                         self._cursor.log('warning', f'Exception in output converter: {type(e).__name__} for SQL type {sql_type}')
  182                     # If conversion fails, keep the original value
  183         
  184         return converted_valuesLines 192-200   192         Allow accessing by column name as attribute: row.column_name
  193         """
  194         # _column_map should already be set in __init__, but check to be safe
  195         if not hasattr(self, '_column_map'):
! 196             self._column_map = {}
  197             
  198         # Try direct lookup first
  199         if name in self._column_map:
  200             return self._values[self._column_map[name]]Lines 202-211   202         # Use the snapshot lowercase setting instead of global
  203         if self._settings.get('lowercase'):
  204             # If lowercase is enabled, try case-insensitive lookup
  205             name_lower = name.lower()
! 206             if name_lower in self._column_map:
! 207                 return self._values[self._column_map[name_lower]]
  208         
  209         raise AttributeError(f"Row has no attribute '{name}'")
  210     
  211     def __eq__(self, other):📋 Files Needing Attention📉 Files with overall lowest coverage (click to expand)mssql_python.pybind.connection.connection.cpp: 68.3%
mssql_python.ddbc_bindings.py: 68.5%
mssql_python.pybind.ddbc_bindings.cpp: 70.5%
mssql_python.row.py: 76.3%
mssql_python.connection.py: 81.7%
mssql_python.cursor.py: 81.7%
mssql_python.helpers.py: 84.7%
mssql_python.auth.py: 85.3%
mssql_python.type.py: 86.8%
mssql_python.pooling.py: 87.5%🔗 Quick Links
 | 
Work Item / Issue Reference
Summary
This pull request introduces a new global setting,
native_uuid, to themssql_pythonpackage, allowing users to control whether UUID values are returned asuuid.UUIDobjects or as strings. The implementation includes updates to the package initialization, row processing logic, and a comprehensive set of tests to verify the new behavior and ensure backward compatibility.UUID Handling Improvements:
native_uuidsetting to the global configuration inmssql_python/__init__.py, defaulting toFalsefor backward compatibility. This setting controls whether UUIDs are returned asuuid.UUIDobjects or as strings.Rowclass inmssql_python/row.pyto check thenative_uuidsetting and convert UUID values to strings whennative_uuidisFalse, ensuring consistent output based on configuration.Testing Enhancements:
tests/test_004_cursor.pyto verify correct UUID handling for bothnative_uuid=Trueandnative_uuid=False, including new tests for the setting and resetting of thenative_uuidoption.Internal Refactoring:
mssql_python/cursor.pythat is now handled via the new setting and row processing logic.These changes provide greater flexibility and control over how UUIDs are handled in query results, improving the usability of the package in different application contexts.