Skip to content

Comments

feat: add async context manager support for CopilotClient and Copilot…#475

Open
Sumanth007 wants to merge 3 commits intogithub:mainfrom
Sumanth007:feat/async-context-manager
Open

feat: add async context manager support for CopilotClient and Copilot…#475
Sumanth007 wants to merge 3 commits intogithub:mainfrom
Sumanth007:feat/async-context-manager

Conversation

@Sumanth007
Copy link

@Sumanth007 Sumanth007 commented Feb 15, 2026

Session with automatic resource cleanup #341

This pull request introduces async context manager support for both CopilotClient and CopilotSession, enabling automatic resource cleanup and following Python best practices for resource management. The documentation is updated to recommend this pattern, and comprehensive tests ensure correct behavior and error handling. These changes make it easier and safer to use the SDK, especially in batch or evaluation scenarios.

Async context manager support and resource management:

  • Added async context manager methods (__aenter__ and __aexit__) to CopilotClient and CopilotSession for automatic startup, cleanup, and error handling, ensuring resources are always released—even if exceptions occur. [1] [2]
  • Updated the python/README.md to document and recommend the context manager usage pattern, including example code and benefits, and highlighted this feature in the capabilities list. [1] [2] [3]

Testing and validation:

  • Added new end-to-end tests in python/e2e/test_context_managers.py to verify correct context manager behavior, including automatic cleanup, error propagation, and nested context scenarios.
  • Introduced unit tests in python/test_client.py to confirm that context manager methods return the correct values and propagate exceptions as expected.

Internal improvements:

  • Imported logging and TracebackType in relevant files to support error logging and context manager implementation. [1] [2]

Copilot AI review requested due to automatic review settings February 15, 2026 16:00
@Sumanth007 Sumanth007 requested a review from a team as a code owner February 15, 2026 16:00
Copy link
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

Adds async context manager (async with) support to the Python SDK’s CopilotClient and CopilotSession so callers can get automatic startup/teardown, and updates Python docs + tests to encourage/verify the pattern.

Changes:

  • Implemented __aenter__ / __aexit__ on CopilotClient to auto-start() and cleanup via stop().
  • Implemented __aenter__ / __aexit__ on CopilotSession to auto-cleanup via destroy().
  • Added unit/E2E tests and updated python/README.md to recommend async with usage.

Reviewed changes

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

Show a summary per file
File Description
python/copilot/client.py Adds async context manager methods and cleanup logging for the client lifecycle.
python/copilot/session.py Adds async context manager methods for session lifecycle cleanup.
python/test_client.py Adds unit tests for context manager return values / exception propagation behavior.
python/e2e/test_context_managers.py Adds E2E coverage for client/session context manager behavior and cleanup semantics.
python/README.md Documents and recommends async with usage patterns for safer resource cleanup.

Copy link
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 5 out of 5 changed files in this pull request and generated 3 comments.

Copy link
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 5 out of 5 changed files in this pull request and generated 1 comment.

Comment on lines +11 to +13
pytestmark = pytest.mark.asyncio(loop_scope="module")


Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

The E2E tests require snapshot files in the test/snapshots/context_managers/ directory to function properly. The test harness expects YAML snapshot files for each test to replay CAPI responses. The snapshot directory test/snapshots/context_managers/ and corresponding YAML files (e.g., should_auto_start_and_cleanup_with_context_manager.yaml, should_create_session_in_context.yaml, etc.) are missing.

The tests will fail without these snapshot files because the replaying proxy uses them to mock API responses. You'll need to either:

  1. Create the snapshot directory and YAML files with appropriate test data
  2. Generate the snapshots by running the tests in record mode first
  3. Copy and adapt existing snapshot files from similar tests
Suggested change
pytestmark = pytest.mark.asyncio(loop_scope="module")
SNAPSHOT_DIR = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
"test",
"snapshots",
"context_managers",
)
pytestmark = [
pytest.mark.asyncio(loop_scope="module"),
pytest.mark.skipif(
not os.path.isdir(SNAPSHOT_DIR),
reason="context_managers snapshots are missing; run E2E in record mode to generate them",
),
]

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@brettcannon brettcannon left a comment

Choose a reason for hiding this comment

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

This PR opens the question of whether embedding logging support is desired, and if so how to do that.

It also opens the question of whether to suppress exceptions when stopping a session or destroying a client. Typically you only suppress stuff that you simply don't care about or provide a way to gain access to the suppressed details after exiting the context manager. But usually you call nearly infallible code that you don't worry about the failures. There's a couple of ways to support this (e.g. attach failures to the object returned by __aenter__, return a tuple where the second object is a list that will contain the failure objects, etc.). But since you can always put a with inside a try, I don't think hiding all exceptions is the right approach and the question is how to handle destroy() returning anything.

# Log any session destruction errors
if stop_errors:
for error in stop_errors:
logging.warning("Error during session cleanup in CopilotClient: %s", error)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit concerned about this as not everyone wants 'logging' in their code as they may use a different logger. Plus this is too broad regardless as there isn't a way to filter out the logging for the library (i.e. this call uses the root logger).

I think more thought has to go into whether there's a desire for logging, and if there is then how to handle it.

Comment on lines +256 to +259
except Exception:
# Log the error but don't raise - we want cleanup to always complete
logging.warning("Error during CopilotClient cleanup", exc_info=True)
return False
Copy link
Contributor

Choose a reason for hiding this comment

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

This is incorrect if you're trying to suppress the exception; you would want to return True in that instance.

stop_errors = await self.stop()
# Log any session destruction errors
if stop_errors:
for error in stop_errors:
Copy link
Contributor

Choose a reason for hiding this comment

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

If StopError was an exception then you would want to use ExceptionGroup, but since that's a Python 3.11 thing and this library supports 3.9 it would need a shim for 3.9 and 3.10.

except Exception:
# Log the error but don't raise - we want cleanup to always complete
logging.warning("Error during CopilotSession cleanup", exc_info=True)
return False
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the exception that was raised should be suppressed without at least a configuration option.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants