Truncate table with pyodbc
Problem
You need to truncate a table using pyodbc
.
Solution
Here is an example of a function to truncate a database table, using pyodbc
connection.
You can find the full source in GitHub.
def truncate_table(table_ref, dbc):
try:
with dbc.cursor() as cursor:
cursor.execute(f'TRUNCATE TABLE {table_ref}')
cursor.commit()
except Exception as err:
dbc.rollback()
raise err
Testing the solution
Although the function is simple, it needs testing. The function should perform two steps:
- Truncate the table, executing
TRUNCATE TABLE
sql statement - Commit the transaction
This is the happy flow.
In addition to the happy flow there is an exception flow which happens when pyodbc fails to execute the TRUNCATE TABLE
sql statement.
Here is a sample implementation of the unit tests that cover above scenarios.
import unittest
from unittest import mock
from pyodbc_functions import truncate_table
class Test_function_truncate_table(unittest.TestCase):
def fix_dbc(self):
dbc = mock.MagicMock()
return dbc
def test_truncate_table_calls_proper_methods_given_database_execute_is_successful(self):
dbc = self.fix_dbc()
truncate_table('users', dbc)
with dbc.cursor() as cursor:
cursor.assert_has_calls([
mock.call.execute('TRUNCATE TABLE users'),
mock.call.commit()
])
def test_truncate_table_calls_rollback_on_and_propagates_exception_given_database_execute_fails(self):
dbc = self.fix_dbc()
with dbc.cursor() as cursor:
cursor.execute.side_effect = Exception('bad boy')
with self.assertRaises(Exception) as excinfo:
truncate_table('users', dbc)
self.assertEqual('bad boy', str(excinfo.exception))
cursor.asset_has_calls([
mock.call.execute(mock.call.ANY),
mock.call.rollback()
])
We use mock database connection.
In the happy flow test we pass mock database connection to the truncate_table
function. Once the function is executed, we assert that following steps were made in a sequence:
execute
was called on the database cursor with proper SQL statement as argumentcommit
with no arguments was called on the database cursor.
In the exception flow test, we again use a mock database connection, but this time we configure the execute
method of the cursor to throw an exception.
We make sure the exception is propagated with assertRaises()
unittest assert. We also check that the message of the exception is preserved.
We verify that the flow is calling:
execute
method of the cursor - we do not verify the arguments since we already validated this in the happy flow.rollback
method on the database connection.
Discussion
You can call the execute
method on a connection cursor directly, but it is always better to move the code into a separate routine:
- provides reusability - you have a tested piece of code that can be used everywhere.
- improves the readability of the code - you create your own idioms or dictionary which make your code more expressive.
- improves the testability of the code - imagine your code truncates the table in the middle of 200+ line code fragment. How would you test it works correctly? How would you cover both scenarios?
- isolates your code from the external system - one of the benefits of this isolation is that you can unit test your code.
You can find the pyodbc
documentation here.