Skip to content

Comments

Adding Storage wrappers for internal use cases#159

Closed
rodrigobr-msft wants to merge 8 commits intomainfrom
users/robrandao/storage-wrappers
Closed

Adding Storage wrappers for internal use cases#159
rodrigobr-msft wants to merge 8 commits intomainfrom
users/robrandao/storage-wrappers

Conversation

@rodrigobr-msft
Copy link
Contributor

This pull request refactors and reorganizes the storage subsystem, with a focus on transcript storage and storage wrappers. Key changes include moving transcript-related modules into a new internal _transcript package, introducing new storage wrapper classes for caching and namespacing, and modernizing type annotations. The updates improve code organization, extensibility, and type safety.

Transcript storage refactor and type annotation improvements:

  • Moved transcript-related modules (transcript_info, transcript_logger, transcript_store, transcript_file_store) into a new _transcript subpackage, updating all import paths and __all__ definitions accordingly. This improves modularity and encapsulation of transcript functionality. [1] [2]
  • Updated type annotations throughout transcript storage classes to use Python 3.9+ generics (e.g., list[Activity] instead of List[Activity]), and standardized the use of Optional and return types for consistency and clarity. [1] [2] [3] [4] [5] [6] [7] [8] [9]
  • Removed the unused _to_plain_dict helper from transcript_file_store.py, cleaning up legacy code.

Storage wrapper enhancements:

  • Added new storage wrapper classes: _CachedStorage and _ClearableCachedStorage for adding a caching layer to storage, _ItemStorage for type-safe item access, and _StorageNamespace for key namespacing based on user/channel. These wrappers are now exported from the _wrappers package. [1] [2] [3] [4]

Base storage class improvements:

  • Changed the abstract base class name from AsyncStorageBase to _AsyncStorageBase and updated its import/export. Replaced placeholder pass statements with raise NotImplementedError() in all unimplemented methods of Storage and _AsyncStorageBase to enforce correct subclassing. [1] [2] [3] [4]

These changes collectively modernize and modularize the storage layer, making it easier to extend and maintain.

Copilot AI review requested due to automatic review settings October 22, 2025 17:35
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

This PR refactors the storage subsystem by reorganizing transcript-related modules into a new _transcript internal package, introducing storage wrapper classes for caching and namespacing functionality, and modernizing type annotations to Python 3.9+ standards.

Key Changes:

  • Moved transcript modules into a new _transcript subpackage with updated imports
  • Added new storage wrapper classes (_CachedStorage, _StorageNamespace, _ItemStorage) for enhanced functionality
  • Renamed AsyncStorageBase to _AsyncStorageBase and replaced pass statements with raise NotImplementedError() in abstract methods

Reviewed Changes

Copilot reviewed 17 out of 21 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
tests/hosting_core/storage/wrappers/test_storage_namespace.py New test file for _StorageNamespace wrapper functionality
tests/hosting_core/storage/test_transcript_store_memory.py Updated import paths for transcript modules
tests/hosting_core/storage/test_transcript_logger_middleware.py Updated import paths for transcript modules
libraries/microsoft-agents-storage-cosmos/microsoft_agents/storage/cosmos/cosmos_db_storage.py Updated to use renamed _AsyncStorageBase
libraries/microsoft-agents-storage-blob/microsoft_agents/storage/blob/blob_storage.py Updated to use renamed _AsyncStorageBase
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/storage.py Renamed base class and replaced pass with raise NotImplementedError()
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/_wrappers/_storage_namespace.py New storage wrapper for namespacing keys
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/_wrappers/_memory_cache.py New cache implementations for storage wrappers
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/_wrappers/_cached_storage.py New storage wrapper adding caching layer
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/_wrappers/__init__.py Exports for new wrapper classes
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/_transcript/transcript_store.py Updated type annotations and moved to _transcript package
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/_transcript/transcript_memory_store.py Updated type annotations and moved to _transcript package
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/_transcript/transcript_logger.py Updated type annotations and moved to _transcript package
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/_transcript/transcript_file_store.py Updated type annotations, removed _to_plain_dict helper, moved to _transcript package
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/_transcript/__init__.py New __init__.py for _transcript package
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/__init__.py Updated exports for reorganized structure
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_oauth/_flow_storage_client.py Added commented placeholder code

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

StoreItemT = TypeVar("StoreItemT", bound=StoreItem)


class _DummyCache(Storage):
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

Missing import for Storage class. The file imports MemoryStorage and StoreItem but not Storage, which is required as the base class for _DummyCache.

Copilot uses AI. Check for mistakes.

class _DummyCache(Storage):

async def read(self, keys: list[str], **kwargs) -> dict[str, _FlowState]:
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

Missing import for _FlowState. The return type references _FlowState which is not imported in this file.

Copilot uses AI. Check for mistakes.
Comment on lines 12 to 15
async def read(self, keys: list[str], **kwargs) -> dict[str, _FlowState]:
return {}

async def write(self, changes: dict[str, _FlowState]) -> None:
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

The parameter type references _FlowState which is not imported. This should use a generic type variable like StoreItemT to match the file's existing generic pattern.

Suggested change
async def read(self, keys: list[str], **kwargs) -> dict[str, _FlowState]:
return {}
async def write(self, changes: dict[str, _FlowState]) -> None:
async def read(self, keys: list[str], **kwargs) -> dict[str, StoreItemT]:
return {}
async def write(self, changes: dict[str, StoreItemT]) -> None:

Copilot uses AI. Check for mistakes.
Comment on lines 47 to 55
self.clear()
return await super().read(keys, target_cls=target_cls, **kwargs)

async def write(self, changes: dict[str, StoreItem]):
self.clear()
return await super().write(changes)

async def delete(self, keys: list[str]):
self.clear()
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

Missing await keyword. The clear() method is defined as async on line 36 but is called without await here and on lines 51 and 55.

Suggested change
self.clear()
return await super().read(keys, target_cls=target_cls, **kwargs)
async def write(self, changes: dict[str, StoreItem]):
self.clear()
return await super().write(changes)
async def delete(self, keys: list[str]):
self.clear()
await self.clear()
return await super().read(keys, target_cls=target_cls, **kwargs)
async def write(self, changes: dict[str, StoreItem]):
await self.clear()
return await super().write(changes)
async def delete(self, keys: list[str]):
await self.clear()

Copilot uses AI. Check for mistakes.

def key(self, vkey: str) -> str:
"""Creates a storage key for a specific sign-in handler."""
return f"{self._base_key}:{vkey}"
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

Key construction uses a colon separator but the test in test_storage_namespace.py line 34 expects a forward slash separator (e.g., auth/{channel_id}/{user_id}/). This inconsistency will cause the key construction to produce keys like auth/channel/user/:handler instead of auth/channel/user/handler.

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +28
channel_id: used to create the prefix
user_id: used to create the prefix
storage: the backing storage
cache_class: the cache class to use (defaults to DummyCache, which performs no caching).
This cache's lifetime is tied to the FlowStorageClient instance.
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

The docstring describes parameters (channel_id, user_id, cache_class) that don't match the actual method signature, which only takes namespace and storage. This appears to be copy-pasted from _FlowStorageClient and needs to be updated to reflect the actual parameters.

Suggested change
channel_id: used to create the prefix
user_id: used to create the prefix
storage: the backing storage
cache_class: the cache class to use (defaults to DummyCache, which performs no caching).
This cache's lifetime is tied to the FlowStorageClient instance.
namespace: The key prefix used for storage operations to isolate state.
storage: The backing Storage instance.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,47 @@
from typing import TypeVar, Generic

from strenum import StrEnum
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

Unused import. The StrEnum class is imported but never used in this file.

Suggested change
from strenum import StrEnum

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,47 @@
from typing import TypeVar, Generic
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

Unused import. The Generic class is imported but never used in this file.

Suggested change
from typing import TypeVar, Generic
from typing import TypeVar

Copilot uses AI. Check for mistakes.
Comment on lines 1 to 10
from ._cached_storage import _CachedStorage, _ClearableCachedStorage
from ._item_storage import _ItemStorage
from ._storage_namespace import _StorageNamespace

__all__ = [
"_CachedStorage",
"_ClearableCachedStorage",
"_ItemStorage",
"_StorageNamespace",
]
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

Import error. _ClearableCachedStorage is imported but does not exist in _cached_storage.py. Only _CachedStorage is defined in that file.

Suggested change
from ._cached_storage import _CachedStorage, _ClearableCachedStorage
from ._item_storage import _ItemStorage
from ._storage_namespace import _StorageNamespace
__all__ = [
"_CachedStorage",
"_ClearableCachedStorage",
"_ItemStorage",
"_StorageNamespace",
]
from ._cached_storage import _CachedStorage
from ._item_storage import _ItemStorage
from ._storage_namespace import _StorageNamespace
__all__ = [
"_CachedStorage",
"_ItemStorage",
"_StorageNamespace",
]

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,10 @@
from ._cached_storage import _CachedStorage, _ClearableCachedStorage
from ._item_storage import _ItemStorage
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

Import error. The file _item_storage.py is referenced but was not included in the PR changes, suggesting this import will fail unless the file exists elsewhere.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings October 22, 2025 21:03
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 25 out of 30 changed files in this pull request and generated 5 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +27 to +31
channel_id: used to create the prefix
user_id: used to create the prefix
storage: the backing storage
cache_class: the cache class to use (defaults to DummyCache, which performs no caching).
This cache's lifetime is tied to the FlowStorageClient instance.
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

The docstring describes incorrect parameters. It mentions channel_id, user_id, and cache_class, but the actual parameters are namespace and storage. The docstring should be updated to accurately describe the actual parameters.

Suggested change
channel_id: used to create the prefix
user_id: used to create the prefix
storage: the backing storage
cache_class: the cache class to use (defaults to DummyCache, which performs no caching).
This cache's lifetime is tied to the FlowStorageClient instance.
namespace: The key prefix used for storage operations to isolate data.
storage: The backing Storage instance.

Copilot uses AI. Check for mistakes.
return self._base_key

def key(self, vkey: str) -> str:
"""Creates a storage key for a specific sign-in handler."""
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

The docstring mentions 'sign-in handler' which is too specific. This is a generic method that creates a namespaced storage key for any virtual key, not just sign-in handlers. Update the docstring to reflect its generic purpose.

Suggested change
"""Creates a storage key for a specific sign-in handler."""
"""Creates a namespaced storage key for the given virtual key."""

Copilot uses AI. Check for mistakes.
)
from microsoft_agents.hosting.core.storage import (
_ItemNamespace,
Namespaces
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

Importing Namespaces but the constant class is named _Namespaces (with underscore prefix) as shown in the _namespaces.py file. This import will fail. Should be _Namespaces.

Copilot uses AI. Check for mistakes.
if sign_in_response.tag == _FlowStateTag.COMPLETE:
if self._sign_in_success_handler:
await self._sign_in_success_handler(context, state, auth_handler_id)
await self._sign_in_state_store.delete(self._sign_in_state_vkey(context))
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

The sign-in state is being deleted twice - once through the new _sign_in_state_store.delete() on line 245, and again through the existing _delete_sign_in_state() method on line 246. This appears to be redundant. Either remove one of these calls or verify if both are intentionally needed for different purposes.

Suggested change
await self._sign_in_state_store.delete(self._sign_in_state_vkey(context))

Copilot uses AI. Check for mistakes.
Comment on lines +237 to +238
def _utc_iso_now() -> str:
return datetime.now(timezone.utc).isoformat()
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

The function _utc_iso_now() is defined but never used in the file. The removed _to_plain_dict() function was also unused. If this new function is not used elsewhere, it should be removed as part of the cleanup effort.

Suggested change
def _utc_iso_now() -> str:
return datetime.now(timezone.utc).isoformat()

Copilot uses AI. Check for mistakes.
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.

Refactor namespaced storage pattern into a unified, well-tested class, and document well-known namespaces as constants

1 participant