Closed hb2638 closed 1 year ago
Is there a complete repro script? I want to see if I get that error and where is it coming from.
Is there a complete repro script? I want to see if I get that error and where is it coming from.
import contextlib
import typing
import unittest
import unittest.mock
import pyodbc
class MyCursor:
def commit(self) -> typing.NoReturn:
raise NotImplementedError()
def rollback(self) -> typing.NoReturn:
raise NotImplementedError()
def close(self) -> None:
return
class MyConnection:
def cursor(self) -> None:
return MyCursor()
def commit(self) -> typing.NoReturn:
raise NotImplementedError()
def rollback(self) -> typing.NoReturn:
raise NotImplementedError()
def close(self) -> typing.NoReturn:
raise NotImplementedError()
class MyTestCase(unittest.TestCase):
def test_mocking(self):
exit_stack = contextlib.ExitStack()
conn = pyodbc.connect("Driver={ODBC Driver 18 for SQL Server};TrustServerCertificate=yes;Server=.;Trusted_Connection=yes;MARS_Connection=yes")
#uncomment below line to see that it works if we create a wrapper class
#conn = MyConnection()
create_cursor = conn.cursor
def create_no_commit_cursor() -> pyodbc.Cursor:
cursor = create_cursor()
exit_stack.enter_context(unittest.mock.patch.object(cursor, "commit", new=lambda: None))
exit_stack.enter_context(unittest.mock.patch.object(cursor, "rollback", new=lambda: None))
return cursor
exit_stack.enter_context(unittest.mock.patch.object(conn, "cursor", new=create_no_commit_cursor))
exit_stack.enter_context(unittest.mock.patch.object(conn, "commit", new=lambda: None))
exit_stack.enter_context(unittest.mock.patch.object(conn, "rollback", new=lambda: None))
exit_stack.enter_context(unittest.mock.patch.object(conn, "close", new=lambda: None))
cursor = conn.cursor()
cursor.commit()
cursor.rollback()
cursor.close()
conn.close()
conn.commit()
conn.rollback()
if __name__ == '__main__':
unittest.main()
Error Traceback (most recent call last): File "C:\Program Files\Python311\Lib\unittest\mock.py", line 1546, in enter setattr(self.target, self.attribute, new_attr) AttributeError: 'pyodbc.Connection' object attribute 'cursor' is read-only
During handling of the above exception, another exception occurred:
Traceback (most recent call last): File "test_stuff.py", line 40, in test_mocking exit_stack.enter_context(unittest.mock.patch.object(conn, "cursor", new=create_no_commit_cursor)) File "C:\Program Files\Python311\Lib\contextlib.py", line 502, in enter_context result = _enter(cm) ^^^^^^^^^^ File "C:\Program Files\Python311\Lib\unittest\mock.py", line 1559, in enter if not self.exit(*sys.exc_info()): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Program Files\Python311\Lib\unittest\mock.py", line 1567, in exit delattr(self.target, self.attribute) AttributeError: 'pyodbc.Connection' object attribute 'cursor' is read-only
It's a C extension, so I don't think it will be possible. And, I think you will find that the wrappers are actually pretty useful. Implement them with __getattr__
instead of trying to rewrite each method and it will literally be just a few lines of code.
I always have a central function in each app to get a connection and I sometimes have debug configuration I can even wrap the connection with multiple wrappers. Some to log every SQL and parameter, or to measure timing and log out the longest queries, etc.
I'm going to close this because it isn't something we can do right now. We are considering reorganizing the code to use more Python code with C code implementing some core functions in in future versions, but it will depend on performance.
Environment
Issue
Is there anyway you can make pyodbc.Connection and pyodbc.Cursor patch friendly. We're running some integration tests and want to stub the "commit" functions of cursors and connections so that it's a no-op. I like this approach because everything will be in a transaction that will get rolled back after the test.
Unfortunately I get the eror AttributeError: 'pyodbc.Connection' object attribute 'cursor' is read-only when I use unittest.mock.patch.object
E.x.:
The work around we're doing for now is creating wrapper classes for connection and cursors... not ideal