django-pyodbc-azure icon indicating copy to clipboard operation
django-pyodbc-azure copied to clipboard

Cursors in 2.0.1.0: directly addressing cursor doesn't return column as property

Open FlipperPA opened this issue 7 years ago • 2 comments
trafficstars

This used to work before 2.0.1.0:

from django.db import connections

cursor = connections['sql_server_connection'].cursor()

cursor.execute("SELECT 'ASDF' AS fake_column")
row = cursor.fetchone()

print(row.fake_column)

It now returns AttributeError: 'tuple' object has no attribute 'fake_column'.

As a work around, assigning the cursor to an object does work as intended:

from django.db import connections

cursor = connections['mssqlotis'].cursor()

res = cursor.execute("SELECT 'ASDF' AS fake_column")
row = res.fetchone()

print(row.fake_column)

This returns ASDF as expected. I haven't had a chance to dig deeper, and it isn't the end of the world, but thought it would be worth raising.

FlipperPA avatar Mar 02 '18 19:03 FlipperPA

It is not a Django feature but a pyodbc feature described in the wiki below. https://github.com/mkleehammer/pyodbc/wiki/Row

  • Values can be accessed by column name.

You can't utilize the feature in Django 2 unfortunately. In Django 2, the auth module assumes that rows from database adapters are hashable and django-pyodbc-azure needs to return a row as a tuple instead of a raw pyodbc Row object. As a side effect of this, rows from django-pyodbc-azure do not have column name properties now. https://github.com/michiya/django-pyodbc-azure/blob/2.0.3.0/sql_server/pyodbc/base.py#L578

We will see errors like the following if the backend returns a raw pyodbc Row object to the auth module.

======================================================================
ERROR: test_unrelated_model_lookups_forwards (migrations.test_executor.ExecutorTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\michiya\workspace\Django-2.0.3\django\test\testcases.py", line 202, in __call__
    self._pre_setup()
  File "C:\Users\michiya\workspace\Django-2.0.3\django\test\testcases.py", line 817, in _pre_setup
    emit_post_migrate_signal(verbosity=0, interactive=False, db=db_name)
  File "C:\Users\michiya\workspace\Django-2.0.3\django\core\management\sql.py", line 51, in emit_post_migrate_signal
    **kwargs
  File "C:\Users\michiya\workspace\Django-2.0.3\django\dispatch\dispatcher.py", line 178, in send
    for receiver in self._live_receivers(sender)
  File "C:\Users\michiya\workspace\Django-2.0.3\django\dispatch\dispatcher.py", line 178, in <listcomp>
    for receiver in self._live_receivers(sender)
  File "C:\Users\michiya\workspace\Django-2.0.3\django\contrib\auth\management\__init__.py", line 71, in create_permissions
    "content_type", "codename"
TypeError: unhashable type: 'pyodbc.Row'

If you want to retireve data from a row using a column name, now you will need to implement it on your own following the Django document below. https://docs.djangoproject.com/en/2.0/topics/db/sql/#executing-custom-sql-directly

michiya avatar Mar 17 '18 13:03 michiya

FWIW, one could use a collections.namedtuple to keep this cake while eating it. Since the row already carries the cursor_description, this wouldn't be very costly.

shaib avatar Mar 17 '18 13:03 shaib