From de32aff30bb431ce8129fd923480b70426ac7bd4 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Thu, 6 Aug 2026 10:14:14 -0700 Subject: [PATCH 1/2] Fixing _FlowStorageClient write cache usage --- README.md | 6 +----- changelog.md | 3 +++ .../core/_oauth/_flow_storage_client.py | 2 +- .../_oauth/test_flow_storage_client.py | 19 +++++++++++++++++++ 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8a3d629fd..5a5af9188 100644 --- a/README.md +++ b/README.md @@ -42,17 +42,13 @@ The packages should target Python 3.10 or greater, and can be used with modern P > Note: We recommend using Python 3.11 or later for optimal performance and compatibility with all features. The SDK supports Python 3.10, 3.11, 3.12, 3.13, and 3.14. -### Debugging - -The packages include source code to allow debugging in your preferred Python IDE or debugger. - ### Code Style We are using `black` and `flake8` for code formatting and linting. ## Contributing -#### Note for Microsoft intenral developers: +#### Note for Microsoft internal developers: - Internal Micrsoft Developers should join the Core identity group [Agents SDK Contrib](https://coreidentity.microsoft.com/manage/Entitlement/entitlement/agentssdkint-upyj) #### Non-Microsoft internal developers: diff --git a/changelog.md b/changelog.md index 9204863aa..ef4209a06 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,9 @@ ## New Models & APIs - **Regionalized UserTokenClient Support**: Added optional argument to `CloudAdapter` to configure Token Service endpoint used by `RestChannelServiceClientFactory` when creating `UserTokenClient` instances. +## Bug Fixes +- **OAuth Flow Storage**: Avoided redundant writes when the flow state is unchanged in the cache. + # Microsoft 365 Agents SDK for Python - Release Notes v1.3.0 **Release Date:** 2026-07-30 diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_oauth/_flow_storage_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_oauth/_flow_storage_client.py index 867b3aa6c..67a423f28 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_oauth/_flow_storage_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_oauth/_flow_storage_client.py @@ -80,7 +80,7 @@ async def write(self, value: _FlowState) -> None: """Saves the flow state for a specific authentication handler.""" key: str = self.key(value.auth_handler_id) cached_state = await self._cache.read([key], target_cls=_FlowState) - if not cached_state or cached_state != value: + if not cached_state or cached_state.get(key, None) != value: await self._cache.write({key: value}) await self._storage.write({key: value}) diff --git a/tests/hosting_core/_oauth/test_flow_storage_client.py b/tests/hosting_core/_oauth/test_flow_storage_client.py index 0acd448cc..4399de9d3 100644 --- a/tests/hosting_core/_oauth/test_flow_storage_client.py +++ b/tests/hosting_core/_oauth/test_flow_storage_client.py @@ -86,6 +86,25 @@ async def test_write(self, mocker, auth_handler_id): await client.write(flow_state) storage.write.assert_called_once_with({client.key(auth_handler_id): flow_state}) + @pytest.mark.asyncio + async def test_write_skips_unchanged_cached_state(self, mocker): + storage = mocker.AsyncMock() + cache = mocker.AsyncMock() + flow_state = _FlowState(auth_handler_id="handler") + key = f"auth/{DEFAULTS.channel_id}/{DEFAULTS.user_id}/handler" + cache.read.return_value = {key: flow_state.model_copy()} + client = _FlowStorageClient( + DEFAULTS.channel_id, + DEFAULTS.user_id, + storage, + cache_class=mocker.Mock(return_value=cache), + ) + + await client.write(flow_state) + + cache.write.assert_not_called() + storage.write.assert_not_called() + @pytest.mark.asyncio @pytest.mark.parametrize("auth_handler_id", ["handler", "auth_handler"]) async def test_delete(self, mocker, auth_handler_id): From 8d1c3d779ebf807f02879b3aed3d92d6b154a78c Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Fri, 7 Aug 2026 08:46:55 -0700 Subject: [PATCH 2/2] Removing unnecessary model validation step --- README.md | 2 +- .../hosting/core/_oauth/_flow_storage_client.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5a5af9188..f809ea54f 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ We are using `black` and `flake8` for code formatting and linting. ## Contributing #### Note for Microsoft internal developers: -- Internal Micrsoft Developers should join the Core identity group [Agents SDK Contrib](https://coreidentity.microsoft.com/manage/Entitlement/entitlement/agentssdkint-upyj) +- Internal Microsoft Developers should join the Core identity group [Agents SDK Contrib](https://coreidentity.microsoft.com/manage/Entitlement/entitlement/agentssdkint-upyj) #### Non-Microsoft internal developers: diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_oauth/_flow_storage_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_oauth/_flow_storage_client.py index 67a423f28..9011c1633 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_oauth/_flow_storage_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_oauth/_flow_storage_client.py @@ -1,8 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from typing import Optional - from ..storage import Storage from ._flow_state import _FlowState @@ -34,7 +32,7 @@ def __init__( channel_id: str, user_id: str, storage: Storage, - cache_class: Optional[type[Storage]] = None, + cache_class: type[Storage] | None = None, ): """ Args: @@ -65,7 +63,7 @@ def key(self, auth_handler_id: str) -> str: """Creates a storage key for a specific sign-in handler.""" return f"{self._base_key}{auth_handler_id}" - async def read(self, auth_handler_id: str) -> Optional[_FlowState]: + async def read(self, auth_handler_id: str) -> _FlowState | None: """Reads the flow state for a specific authentication handler.""" key: str = self.key(auth_handler_id) data = await self._cache.read([key], target_cls=_FlowState) @@ -74,7 +72,7 @@ async def read(self, auth_handler_id: str) -> Optional[_FlowState]: if key not in data: return None await self._cache.write({key: data[key]}) - return _FlowState.model_validate(data.get(key)) + return data.get(key) async def write(self, value: _FlowState) -> None: """Saves the flow state for a specific authentication handler."""