Skip to content

Commit 0fe24f9

Browse files
Add executemany functionality to RedshiftClient
2 parents 07eb89e + b2e203b commit 0fe24f9

4 files changed

Lines changed: 43 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Changelog
2+
## v1.1.5 6/6/24
3+
- Use executemany instead of execute when appropriate in RedshiftClient.execute_transaction
4+
25
## v1.1.4 3/14/24
36
- Fix bug with oauth2 requests after token refresh
47

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "nypl_py_utils"
7-
version = "1.1.4"
7+
version = "1.1.5"
88
authors = [
99
{ name="Aaron Friedman", email="aaronfriedman@nypl.org" },
1010
]

src/nypl_py_utils/classes/redshift_client.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,21 @@ def connect(self):
3535

3636
def execute_query(self, query, dataframe=False):
3737
"""
38-
Executes an arbitrary query against the given database connection.
38+
Executes an arbitrary read query against the given database connection.
3939
4040
Parameters
4141
----------
4242
query: str
43-
The query to execute
43+
The query to execute, assumed to be a read query
4444
dataframe: bool, optional
4545
Whether the data will be returned as a pandas DataFrame. Defaults
4646
to False, which means the data is returned as a list of tuples.
4747
4848
Returns
4949
-------
5050
None or sequence
51-
None if is_write_query is True. A list of tuples or a pandas
52-
DataFrame (based on the dataframe input) if is_write_query is
53-
False.
51+
A list of tuples or a pandas DataFrame (based on the `dataframe`
52+
input)
5453
"""
5554
self.logger.info('Querying {} database'.format(self.database))
5655
self.logger.debug('Executing query {}'.format(query))
@@ -82,7 +81,12 @@ def execute_transaction(self, queries):
8281
----------
8382
queries: list<tuple>
8483
A list of tuples containing a query and the values to be used if
85-
the query is parameterized (or None if it's not)
84+
the query is parameterized (or None if it's not). The values can
85+
be for a single insert query -- e.g. execute_transaction(
86+
"INSERT INTO x VALUES (%s, %s)", (1, "a"))
87+
or for multiple -- e.g execute_transaction(
88+
"INSERT INTO x VALUES (%s, %s)", [(1, "a"), (2, "b")])
89+
8690
"""
8791
self.logger.info('Executing transaction against {} database'.format(
8892
self.database))
@@ -91,7 +95,13 @@ def execute_transaction(self, queries):
9195
cursor.execute('BEGIN TRANSACTION;')
9296
for query in queries:
9397
self.logger.debug('Executing query {}'.format(query))
94-
cursor.execute(query[0], query[1])
98+
if query[1] is not None and all(
99+
isinstance(el, tuple) or isinstance(el, list)
100+
for el in query[1]
101+
):
102+
cursor.executemany(query[0], query[1])
103+
else:
104+
cursor.execute(query[0], query[1])
95105
cursor.execute('END TRANSACTION;')
96106
self.conn.commit()
97107
except Exception as e:

tests/test_redshift_client.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,28 @@ def test_execute_transaction(self, mock_redshift_conn, test_instance,
7676
mocker.call('query 1', None),
7777
mocker.call('query 2 %s %s', ('a', 1)),
7878
mocker.call('END TRANSACTION;')])
79+
mock_cursor.executemany.assert_not_called()
80+
test_instance.conn.commit.assert_called_once()
81+
mock_cursor.close.assert_called_once()
82+
83+
def test_execute_transaction_with_many(self, mock_redshift_conn,
84+
test_instance, mocker):
85+
test_instance.connect()
86+
87+
mock_cursor = mocker.MagicMock()
88+
test_instance.conn.cursor.return_value = mock_cursor
89+
90+
test_instance.execute_transaction([
91+
('query 1', None), ('query 2 %s %s', (None, 1)),
92+
('query 3 %s %s', [(None, 10), ('b', 20)]), ('query 4', None)])
93+
mock_cursor.execute.assert_has_calls([
94+
mocker.call('BEGIN TRANSACTION;'),
95+
mocker.call('query 1', None),
96+
mocker.call('query 2 %s %s', (None, 1)),
97+
mocker.call('query 4', None),
98+
mocker.call('END TRANSACTION;')])
99+
mock_cursor.executemany.assert_called_once_with(
100+
'query 3 %s %s', [(None, 10), ('b', 20)])
79101
test_instance.conn.commit.assert_called_once()
80102
mock_cursor.close.assert_called_once()
81103

0 commit comments

Comments
 (0)