Python: Update sessions python plugin to work with the correct endpoints (#7070)

### Motivation and Context

The Sessions Python Plugin was working properly, but some users reported
that the endpoints needed updating. We had made previous updates to the
plugin, but they didn't account for the update API endpoints and request
body changes shown
[here](https://learn.microsoft.com/en-us/azure/container-apps/sessions-code-interpreter).

<!-- Thank you for your contribution to the semantic-kernel repo!
Please help reviewers and future users, providing the following
information:
  1. Why is this change required?
  2. What problem does it solve?
  3. What scenario does it contribute to?
  4. If it fixes an open issue, please link to the issue here.
-->

### Description

This PR updates the plugin tool to work with the documented endpoints
and request bodies. The unit tests were also updated. The concept sample
for the Azure Python sessions plugin was tested with all methods to:
execute code, upload multiple files, list files, and download them.
- Additionally, improve error handling for each of the function calls so
the proper error message is surfaced to the user via SK exceptions.
- Fix path building and making sure the sessions endpoint is converted
to a string from the Pydantic https type.
- Closes #6934 

<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->

### Contribution Checklist

<!-- Before submitting this PR, please make sure: -->

- [X] The code builds clean without any errors or warnings
- [X] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [X] All unit tests pass, and I have added new tests where possible
- [ ] I didn't break anyone 😄
This commit is contained in:
Evan Mattson
2024-07-04 11:43:47 +00:00
committed by GitHub
parent f02321da3e
commit 007e3dab8b
3 changed files with 167 additions and 109 deletions
@@ -76,10 +76,22 @@ async def test_call_to_container_succeeds(mock_post, aca_python_sessions_unit_te
"semantic_kernel.core_plugins.sessions_python_tool.sessions_python_plugin.SessionsPythonTool._ensure_auth_token",
return_value="test_token",
):
mock_request = httpx.Request(method="POST", url="https://example.com/python/execute/")
mock_request = httpx.Request(method="POST", url="https://example.com/code/execute/")
mock_response = httpx.Response(
status_code=200, json={"result": "success", "stdout": "", "stderr": ""}, request=mock_request
status_code=200,
json={
"$id": "1",
"properties": {
"$id": "2",
"status": "Success",
"stdout": "",
"stderr": "",
"result": "even_numbers = [2 * i for i in range(1, 11)]\\nprint(even_numbers)",
"executionTimeInMilliseconds": 12,
},
},
request=mock_request,
)
mock_post.return_value = await async_return(mock_response)
@@ -101,7 +113,7 @@ async def test_call_to_container_fails_raises_exception(mock_post, aca_python_se
"semantic_kernel.core_plugins.sessions_python_tool.sessions_python_plugin.SessionsPythonTool._ensure_auth_token",
return_value="test_token",
):
mock_request = httpx.Request(method="POST", url="https://example.com/python/execute/")
mock_request = httpx.Request(method="POST", url="https://example.com/code/execute/")
mock_response = httpx.Response(status_code=500, request=mock_request)
@@ -135,19 +147,22 @@ async def test_upload_file_with_local_path(mock_post, aca_python_sessions_unit_t
),
patch("builtins.open", mock_open(read_data=b"file data")),
):
mock_request = httpx.Request(method="POST", url="https://example.com/python/uploadFile?identifier=None")
mock_request = httpx.Request(method="POST", url="https://example.com/files/upload?identifier=None")
mock_response = httpx.Response(
status_code=200,
json={
"$id": "1",
"$values": [
"value": [
{
"$id": "2",
"filename": "test.txt",
"size": 123,
"last_modified_time": "2024-06-03T17:48:46.2672398Z",
}
"properties": {
"$id": "3",
"filename": "hello.py",
"size": 123,
"lastModifiedTime": "2024-07-02T19:29:23.4369699Z",
},
},
],
},
request=mock_request,
@@ -159,10 +174,10 @@ async def test_upload_file_with_local_path(mock_post, aca_python_sessions_unit_t
env_file_path="test.env",
)
result = await plugin.upload_file(local_file_path="test.txt", remote_file_path="uploaded_test.txt")
assert result.filename == "test.txt"
result = await plugin.upload_file(local_file_path="hello.py", remote_file_path="hello.py")
assert result.filename == "hello.py"
assert result.size_in_bytes == 123
assert result.full_path == "/mnt/data/test.txt"
assert result.full_path == "/mnt/data/hello.py"
mock_post.assert_awaited_once()
@@ -181,19 +196,22 @@ async def test_upload_file_with_local_path_and_no_remote(mock_post, aca_python_s
),
patch("builtins.open", mock_open(read_data=b"file data")),
):
mock_request = httpx.Request(method="POST", url="https://example.com/python/uploadFile?identifier=None")
mock_request = httpx.Request(method="POST", url="https://example.com/files/upload?identifier=None")
mock_response = httpx.Response(
status_code=200,
json={
"$id": "1",
"$values": [
"value": [
{
"$id": "2",
"filename": "test.txt",
"size": 123,
"last_modified_time": "2024-06-03T17:00:00.0000000Z",
}
"properties": {
"$id": "3",
"filename": "hello.py",
"size": 123,
"lastModifiedTime": "2024-07-02T19:29:23.4369699Z",
},
},
],
},
request=mock_request,
@@ -205,8 +223,8 @@ async def test_upload_file_with_local_path_and_no_remote(mock_post, aca_python_s
env_file_path="test.env",
)
result = await plugin.upload_file(local_file_path="test.txt")
assert result.filename == "test.txt"
result = await plugin.upload_file(local_file_path="hello.py")
assert result.filename == "hello.py"
assert result.size_in_bytes == 123
mock_post.assert_awaited_once()
@@ -235,19 +253,22 @@ async def test_upload_file_with_buffer(
),
patch("builtins.open", mock_open(read_data="print('hello, world~')")),
):
mock_request = httpx.Request(method="POST", url="https://example.com/python/uploadFile?identifier=None")
mock_request = httpx.Request(method="POST", url="https://example.com/files/upload?identifier=None")
mock_response = httpx.Response(
status_code=200,
json={
"$id": "1",
"$values": [
"value": [
{
"$id": "2",
"filename": expected_remote_file_path,
"size": 456,
"last_modified_time": "2024-06-03T17:00:00.0000000Z",
}
"properties": {
"$id": "3",
"filename": expected_remote_file_path,
"size": 456,
"lastModifiedTime": "2024-07-02T19:29:23.4369699Z",
},
},
],
},
request=mock_request,
@@ -286,25 +307,31 @@ async def test_list_files(mock_get, aca_python_sessions_unit_test_env):
"semantic_kernel.core_plugins.sessions_python_tool.sessions_python_plugin.SessionsPythonTool._ensure_auth_token",
return_value="test_token",
):
mock_request = httpx.Request(method="GET", url="https://example.com/python/files?identifier=None")
mock_request = httpx.Request(method="GET", url="https://example.com/files?identifier=None")
mock_response = httpx.Response(
status_code=200,
json={
"$id": "1",
"$values": [
"value": [
{
"$id": "2",
"filename": "test1.txt",
"size": 123,
"last_modified_time": "2024-06-03T17:00:00.0000000Z",
}, # noqa: E501
"properties": {
"$id": "3",
"filename": "hello.py",
"size": 123,
"lastModifiedTime": "2024-07-02T19:29:23.4369699Z",
},
},
{
"$id": "3",
"filename": "test2.txt",
"size": 456,
"last_modified_time": "2024-06-03T18:00:00.0000000Z",
}, # noqa: E501
"$id": "4",
"properties": {
"$id": "5",
"filename": "world.py",
"size": 456,
"lastModifiedTime": "2024-07-02T19:29:38.1329088Z",
},
},
],
},
request=mock_request,
@@ -315,9 +342,9 @@ async def test_list_files(mock_get, aca_python_sessions_unit_test_env):
files = await plugin.list_files()
assert len(files) == 2
assert files[0].filename == "test1.txt"
assert files[0].filename == "hello.py"
assert files[0].size_in_bytes == 123
assert files[1].filename == "test2.txt"
assert files[1].filename == "world.py"
assert files[1].size_in_bytes == 456
mock_get.assert_awaited_once()
@@ -341,7 +368,8 @@ async def test_download_file_to_local(mock_get, aca_python_sessions_unit_test_en
patch("builtins.open", mock_open()) as mock_file,
):
mock_request = httpx.Request(
method="GET", url="https://example.com/python/downloadFile?identifier=None&filename=remote_test.txt"
method="GET",
url="https://example.com/python/files/content/remote_text.txt?identifier=None&filename=remote_test.txt",
)
mock_response = httpx.Response(status_code=200, content=b"file data", request=mock_request)
@@ -352,7 +380,7 @@ async def test_download_file_to_local(mock_get, aca_python_sessions_unit_test_en
env_file_path="test.env",
)
await plugin.download_file(remote_file_path="remote_test.txt", local_file_path="local_test.txt")
await plugin.download_file(remote_file_name="remote_test.txt", local_file_path="local_test.txt")
mock_get.assert_awaited_once()
mock_file.assert_called_once_with("local_test.txt", "wb")
mock_file().write.assert_called_once_with(b"file data")
@@ -374,7 +402,8 @@ async def test_download_file_to_buffer(mock_get, aca_python_sessions_unit_test_e
return_value="test_token",
):
mock_request = httpx.Request(
method="GET", url="https://example.com/python/downloadFile?identifier=None&filename=remote_test.txt"
method="GET",
url="https://example.com/files/content/remote_test.txt?identifier=None&filename=remote_test.txt",
)
mock_response = httpx.Response(status_code=200, content=b"file data", request=mock_request)
@@ -382,7 +411,7 @@ async def test_download_file_to_buffer(mock_get, aca_python_sessions_unit_test_e
plugin = SessionsPythonTool(auth_callback=mock_auth_callback)
buffer = await plugin.download_file(remote_file_path="remote_test.txt")
buffer = await plugin.download_file(remote_file_name="remote_test.txt")
assert buffer is not None
assert buffer.read() == b"file data"
mock_get.assert_awaited_once()