Skip to content

feat: Add ConnectionPool class#666

Merged
luisremis merged 39 commits into
developfrom
feat/598-connection-pool
May 20, 2026
Merged

feat: Add ConnectionPool class#666
luisremis merged 39 commits into
developfrom
feat/598-connection-pool

Conversation

@ad-claw000
Copy link
Copy Markdown
Contributor

Closes #598

This PR brings the connection pool implementation from the workflows repo directly into aperturedb-python, making it generally available as aperturedb.ConnectionPool.ConnectionPool.

Included tests verify connection checkout/return functionality.

Copy link
Copy Markdown
Contributor

Copilot AI left a 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 adds a reusable, thread-safe ConnectionPool utility to aperturedb-python, ported from the workflows repository, and introduces a basic test suite intended to verify checkout/return and concurrent usage.

Changes:

  • Added aperturedb.ConnectionPool.ConnectionPool implementing a fixed-size pool backed by a blocking queue and a context-managed checkout API.
  • Added test/test_ConnectionPool.py to exercise pool initialization, borrowing/returning, a convenience query() method, and basic concurrency.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
aperturedb/ConnectionPool.py Introduces the new ConnectionPool class and its initialization / checkout / query convenience APIs.
test/test_ConnectionPool.py Adds tests for pool behavior, including concurrency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread aperturedb/ConnectionPool.py Outdated
Comment thread aperturedb/ConnectionPool.py Outdated
Comment thread aperturedb/ConnectionPool.py Outdated
Comment thread test/test_ConnectionPool.py Outdated
@ad-claw000 ad-claw000 force-pushed the feat/598-connection-pool branch from ab83370 to c802a45 Compare May 4, 2026 09:32
@ad-claw000
Copy link
Copy Markdown
Contributor Author

The ConnectionPool feature is ready and all tests are passing. PR is ready for final review and merge.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 15 changed files in this pull request and generated 8 comments.

Comments suppressed due to low confidence (5)

aperturedb/CSVWriter.py:71

  • Multi-line expression inside an f-string requires Python 3.12+ (PEP 701). The project supports Python >=3.10, so this will be a SyntaxError on 3.10/3.11. Same issue as elsewhere in this file (lines 33-34, 70-71, 87-88, 146-147, 151-152, 160-161). All occurrences should be reverted to single-line f-strings or rewritten without newlines between { and }.
    assert source_column in df.columns, f"source_column {
        source_column} not found in the input data"

aperturedb/CSVWriter.py:88

  • Same multi-line-f-string issue: requires Python 3.12+, but the project supports Python 3.10+. Will fail to import on 3.10/3.11.
        assert unique_key in df.columns, f"unique_key {
            unique_key} not found in the input data"

aperturedb/CSVWriter.py:147

  • Same multi-line-f-string issue: requires Python 3.12+, but the project supports Python 3.10+. Will fail to import on 3.10/3.11.
    assert source_column in df.columns, f"source_column {
        source_column} not found in the input data"

aperturedb/CSVWriter.py:152

  • Same multi-line-f-string issue: requires Python 3.12+, but the project supports Python 3.10+. Will fail to import on 3.10/3.11.
    assert destination_column in df.columns, f"destination_column {
        destination_column} not found in the input data"

aperturedb/CSVWriter.py:161

  • Same multi-line-f-string issue: requires Python 3.12+, but the project supports Python 3.10+. Will fail to import on 3.10/3.11.
        assert unique_key in df.columns, f"unique_key {
            unique_key} not found in the input data"

Comment thread aperturedb/CSVWriter.py Outdated
Comment thread examples/CelebADataKaggle.py Outdated
Comment thread aperturedb/ConnectionPool.py
Comment thread aperturedb/ConnectionPool.py
Comment thread aperturedb/ConnectionPool.py Outdated
Comment thread test/test_ConnectionPool.py
Comment thread test/test_ConnectionPool.py Outdated
Comment thread aperturedb/ConnectionPool.py
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 6 changed files in this pull request and generated 5 comments.

Comment thread aperturedb/ConnectionPool.py Outdated
Comment thread aperturedb/ConnectionPool.py
Comment thread aperturedb/ConnectionPool.py Outdated
Comment thread test/test_ConnectionPool.py
Comment thread test/test_ConnectionPool.py Outdated
@ad-claw000 ad-claw000 force-pushed the feat/598-connection-pool branch from 34e9f58 to 4082326 Compare May 19, 2026 02:38
@ad-claw000 ad-claw000 requested a review from luisremis May 19, 2026 04:08
luisremis
luisremis previously approved these changes May 19, 2026
Comment thread test/test_ConnectionPool.py Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (4)

aperturedb/ConnectionPool.py:97

  • self._pool_size -= 1 is executed from within get_connection, which may be called concurrently from multiple threads. Since plain integer decrement is not atomic across thread context switches and there is no lock protecting _pool_size, concurrent failed reconnect attempts can race and corrupt the counter. Consider protecting mutations to _pool_size with a threading.Lock, or use queue.Queue's own bookkeeping rather than a separately maintained counter.
                    # Reduce total pool size since connection could not be recreated
                    self._pool_size -= 1

aperturedb/ConnectionPool.py:97

  • If _connection_factory() fails repeatedly here (e.g., transient network issue when an exception bubbles up from a query), _pool_size is decremented but no new item is ever placed back into the queue. Over time the pool can shrink to zero, at which point any subsequent self._pool.get() (in get_connection) will block forever since Queue.get() has no default timeout. Consider either retrying the factory, providing a timeout to get(), or raising when the pool becomes empty so callers don't hang indefinitely.
            else:
                try:
                    new_connection = self._connection_factory()
                    self._pool.put(new_connection)
                except Exception as e:
                    logger.error(
                        f"Failed to recreate connection for pool: {e}")
                    # Reduce total pool size since connection could not be recreated
                    self._pool_size -= 1

aperturedb/ConnectionPool.py:95

  • Any exception raised by connection.query(...) inside the user's with pool.get_connection() block (or inside pool.query(...)) is treated as a sign that the connection is broken, causing it to be discarded and replaced. However, many exceptions (e.g., a malformed query, application logic errors, server-side query errors) do not indicate a broken transport. As written, every such error churns a connection through the factory unnecessarily and can mask the original error if the recreate also fails. Consider narrowing the discard logic to specific connection/transport errors.
        except Exception:
            return_to_pool = False
            raise
        finally:
            # This block is guaranteed to execute, ensuring the connection
            # is always returned to the pool unless an exception occurred.
            if return_to_pool:
                self._pool.put(connection)
            else:
                try:
                    new_connection = self._connection_factory()
                    self._pool.put(new_connection)
                except Exception as e:
                    logger.error(
                        f"Failed to recreate connection for pool: {e}")

aperturedb/ConnectionPool.py:62

  • There is no close()/shutdown() method, nor support for the context manager protocol on ConnectionPool itself, to dispose of all underlying connections when the pool is no longer needed. For long-running services this is fine since the pool lives for the process lifetime, but for shorter-lived usage (and for the tests in this PR, which create multiple pools), connections are leaked until garbage collection. Consider adding an explicit close() that drains the queue and closes each Connector.
    def available(self) -> int:
        """Returns the number of available connections in the pool."""
        return self._pool.qsize()

    def total(self) -> int:
        """Returns the total number of connections in the pool."""
        return self._pool_size

Comment thread aperturedb/ConnectionPool.py Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

Comment thread aperturedb/ConnectionPool.py Outdated
Comment thread aperturedb/ConnectionPool.py Outdated
Comment thread aperturedb/ConnectionPool.py Outdated
Comment thread aperturedb/ConnectionPool.py Outdated
Comment thread test/test_ConnectionPool.py
Copy link
Copy Markdown
Contributor Author

@ad-claw000 ad-claw000 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have addressed all recent review comments in the latest commit:

  1. ConnectionPool Initializer: Re-written to properly capture the last exception and raise a detailed ConnectionError if the pool cannot be fully initialized to the requested pool_size.
  2. Concurrency / _pool_size: Removed the unguarded mutations of _pool_size and removed the recreation logic inside get_connection(). Connections are always returned to the pool; if a connection is severed, the Connector class itself natively handles reconnecting under the hood upon its next usage.
  3. Timeout: Added a timeout parameter to get_connection() (and properly catching queue.Empty to raise TimeoutError) so callers do not block indefinitely.
  4. Test worker concurrency: Caught exceptions in the thread worker are now appended to the results array, and explicitly asserted at the end of the test to provide visible tracebacks upon failure.
  5. Multline f-strings in CSVWriter: This was already addressed in a separate PR (#676) that reformatted those lines to be Python 3.10 compatible.

Let me know if there's anything else!

Copilot AI review requested due to automatic review settings May 19, 2026 23:23
Copy link
Copy Markdown
Contributor Author

@ad-claw000 ad-claw000 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied autopep8 formatting to the new log messages to fix the pre-commit CI failure.

Copilot AI review requested due to automatic review settings May 20, 2026 05:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

Comment thread aperturedb/ConnectionPool.py Outdated
Comment thread aperturedb/ConnectionPool.py
Comment thread aperturedb/ConnectionPool.py Outdated
Comment on lines +87 to +88
# This block is guaranteed to execute, ensuring the connection
# is always returned to the pool unless an exception occurred.
@@ -0,0 +1,119 @@
import threading
Comment thread test/test_ConnectionPool.py Outdated
- Fix f-string syntax error across lines
- Drain and close created connections if initialization fails
- Add note about connection health responsibility on exception
- Invoke close() on Connector instances when draining pool
- Remove duplicate test definition
Copilot AI review requested due to automatic review settings May 20, 2026 08:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

aperturedb/ConnectionPool.py:61

  • The ConnectionError message uses an f-string where the {pool_size} expression is split across lines. That syntax will raise a SyntaxError on Python 3.10/3.11 (the project supports >=3.10). Build this message without newlines inside {...} (e.g., keep the expression on one line or assemble the message separately).
            raise ConnectionError(
                f"Failed to initialize pool: expected {
                    pool_size} connections, got {self._pool.qsize()}."
            ) from last_error

Comment thread aperturedb/ConnectionPool.py Outdated
@@ -0,0 +1,133 @@
import threading
Comment on lines +25 to +33
class TestConnectionPool(unittest.TestCase):
def setUp(self):
pass

def test_pool_initialization(self):
pool = ConnectionPool(
pool_size=3, connection_factory=_make_connector)
self.assertEqual(pool.total(), 3)
self.assertEqual(pool.available(), 3)
yield connection
finally:
# This block is guaranteed to execute, ensuring the connection
# is always returned to the pool unless an exception occurred.
Comment on lines +123 to +133
def close(self):
"""
Closes all connections in the pool.
"""
while not self._pool.empty():
try:
conn = self._pool.get_nowait()
if hasattr(conn, 'close'):
conn.close()
except queue.Empty:
break
Copilot AI review requested due to automatic review settings May 20, 2026 17:11
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

aperturedb/ConnectionPool.py:134

  • close() currently won’t reliably close all pooled connections: it only drains currently-available items, and it calls conn.close() only when present (but Connector/ConnectorRest don’t expose close()). Connections checked out at the time of close() will be returned later via get_connection()’s finally, effectively “reopening” the pool and leaking resources. Consider adding a pool-level closed flag so returned connections are closed instead of re-queued, and ensure Connector instances have a deterministic close/shutdown API.
    def close(self):
        """
        Closes all connections in the pool.
        """
        while not self._pool.empty():
            try:
                conn = self._pool.get_nowait()
                if hasattr(conn, 'close'):
                    conn.close()
            except queue.Empty:

@@ -0,0 +1,135 @@
import threading
Comment on lines +52 to +57
while not self._pool.empty():
try:
conn = self._pool.get_nowait()
if hasattr(conn, 'close'):
conn.close()
except queue.Empty:
yield connection
finally:
# This block is guaranteed to execute, ensuring the connection
# is always returned to the pool unless an exception occurred.
@luisremis luisremis merged commit 8b66933 into develop May 20, 2026
6 checks passed
@luisremis luisremis deleted the feat/598-connection-pool branch May 20, 2026 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Try to generalize and refactor connection pool from workflows.

3 participants