Skip to content

Commit 648d14d

Browse files
authored
Add engine accessor to SQLAlchemySession for closing it when it's created from a URL (openai#1960)
1 parent 59a8b0f commit 648d14d

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

src/agents/extensions/memory/sqlalchemy_session.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,16 @@ async def clear_session(self) -> None:
319319
await sess.execute(
320320
delete(self._sessions).where(self._sessions.c.session_id == self.session_id)
321321
)
322+
323+
@property
324+
def engine(self) -> AsyncEngine:
325+
"""Access the underlying SQLAlchemy AsyncEngine.
326+
327+
This property provides direct access to the engine for advanced use cases,
328+
such as checking connection pool status, configuring engine settings,
329+
or manually disposing the engine when needed.
330+
331+
Returns:
332+
AsyncEngine: The SQLAlchemy async engine instance.
333+
"""
334+
return self._engine

tests/extensions/memory/test_sqlalchemy_session.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
Summary,
1515
)
1616
from sqlalchemy import select, text, update
17+
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
1718
from sqlalchemy.sql import Select
1819

1920
pytest.importorskip("sqlalchemy") # Skip tests if SQLAlchemy is not installed
@@ -390,3 +391,56 @@ async def recording_execute(statement: Any, *args: Any, **kwargs: Any) -> Any:
390391

391392
assert _item_ids(retrieved_full) == ["rs_first", "msg_second"]
392393
assert _item_ids(retrieved_limited) == ["rs_first", "msg_second"]
394+
395+
396+
async def test_engine_property_from_url():
397+
"""Test that the engine property returns the AsyncEngine from from_url."""
398+
session_id = "engine_property_test"
399+
session = SQLAlchemySession.from_url(session_id, url=DB_URL, create_tables=True)
400+
401+
# Verify engine property returns an AsyncEngine instance
402+
assert isinstance(session.engine, AsyncEngine)
403+
404+
# Verify we can use the engine for advanced operations
405+
# For example, check pool status
406+
assert session.engine.pool is not None
407+
408+
# Verify we can manually dispose the engine
409+
await session.engine.dispose()
410+
411+
412+
async def test_engine_property_from_external_engine():
413+
"""Test that the engine property returns the external engine."""
414+
session_id = "external_engine_test"
415+
416+
# Create engine externally
417+
external_engine = create_async_engine(DB_URL)
418+
419+
# Create session with external engine
420+
session = SQLAlchemySession(session_id, engine=external_engine, create_tables=True)
421+
422+
# Verify engine property returns the same engine instance
423+
assert session.engine is external_engine
424+
425+
# Verify we can use the engine
426+
assert isinstance(session.engine, AsyncEngine)
427+
428+
# Clean up - user is responsible for disposing external engine
429+
await external_engine.dispose()
430+
431+
432+
async def test_engine_property_is_read_only():
433+
"""Test that the engine property cannot be modified."""
434+
session_id = "readonly_engine_test"
435+
session = SQLAlchemySession.from_url(session_id, url=DB_URL, create_tables=True)
436+
437+
# Verify engine property exists
438+
assert hasattr(session, "engine")
439+
440+
# Verify it's a property (read-only, cannot be set)
441+
# Type ignore needed because mypy correctly detects this is read-only
442+
with pytest.raises(AttributeError):
443+
session.engine = create_async_engine(DB_URL) # type: ignore[misc]
444+
445+
# Clean up
446+
await session.engine.dispose()

0 commit comments

Comments
 (0)