diff --git a/azure/functions/__init__.py b/azure/functions/__init__.py index b79e2c3..d9f52c6 100644 --- a/azure/functions/__init__.py +++ b/azure/functions/__init__.py @@ -11,7 +11,7 @@ Cardinality, AccessRights, HttpMethod, AsgiFunctionApp, WsgiFunctionApp, ExternalHttpFunctionApp, BlobSource, McpPropertyType, - PromptArgument) + CosmosDBChangeFeedMode, PromptArgument) from .decorators.mcp import mcp_content from ._durable_functions import OrchestrationContext, EntityContext from .decorators.function_app import (FunctionRegister, TriggerApi, @@ -55,9 +55,10 @@ 'Out', # Binding rich types, sorted alphabetically. + 'CloudEvent', + 'CosmosDBChangeFeedMode', 'Document', 'DocumentList', - 'CloudEvent', 'EventGridEvent', 'EventGridOutputEvent', 'EventHubEvent', diff --git a/azure/functions/cosmosdb.py b/azure/functions/cosmosdb.py index e8dc139..c9ae1e7 100644 --- a/azure/functions/cosmosdb.py +++ b/azure/functions/cosmosdb.py @@ -6,12 +6,19 @@ from azure.functions import _cosmosdb as cdb from ._jsonutils import json +from ._cosmosdb import CosmosDBChangeFeedMode from . import meta class CosmosDBConverter(meta.InConverter, meta.OutConverter, binding='cosmosDB'): + """Converter for Cosmos DB binding. + + By default, this binding processes latest document versions. + To include delete events and intermediate mutations, set changeFeedMode + to 'AllVersionsAndDeletes' in trigger configuration. + """ @classmethod def check_input_type_annotation(cls, pytype: type) -> bool: @@ -78,4 +85,10 @@ def encode(cls, obj: typing.Any, *, class CosmosDBTriggerConverter(CosmosDBConverter, binding='cosmosDBTrigger', trigger=True): + """Converter for Cosmos DB trigger binding. + + Supports change feed mode options: + - LatestVersion: Processes only the latest document version (default) + - AllVersionsAndDeletes: Includes intermediate mutations and delete events + """ pass diff --git a/azure/functions/decorators/__init__.py b/azure/functions/decorators/__init__.py index dfb1ded..beaf7ff 100644 --- a/azure/functions/decorators/__init__.py +++ b/azure/functions/decorators/__init__.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from .core import Cardinality, AccessRights +from .core import Cardinality, AccessRights, CosmosDBChangeFeedMode from .function_app import FunctionApp, Function, DecoratorApi, DataType, \ AuthLevel, Blueprint, ExternalHttpFunctionApp, AsgiFunctionApp, \ WsgiFunctionApp, FunctionRegister, TriggerApi, BindingApi, \ @@ -26,6 +26,7 @@ 'AccessRights', 'HttpMethod', 'BlobSource', + 'CosmosDBChangeFeedMode', 'McpPropertyType', 'PromptArgument' ] diff --git a/azure/functions/decorators/core.py b/azure/functions/decorators/core.py index 1e1ef4c..cbd60b5 100644 --- a/azure/functions/decorators/core.py +++ b/azure/functions/decorators/core.py @@ -73,6 +73,14 @@ class BlobSource(StringifyEnum): """Standard polling mechanism to detect changes in the container.""" +class CosmosDBChangeFeedMode(StringifyEnum): + """Change feed mode for Cosmos DB trigger bindings.""" + LATEST_VERSION = "LatestVersion" + """Latest version of the change feed.""" + ALL_VERSIONS_AND_DELETES = "AllVersionsAndDeletes" + """All versions and delete events in the change feed.""" + + class McpPropertyType(StringifyEnum): """MCP property types.""" INTEGER = "integer" diff --git a/azure/functions/decorators/cosmosdb.py b/azure/functions/decorators/cosmosdb.py index d9a281e..f40090f 100644 --- a/azure/functions/decorators/cosmosdb.py +++ b/azure/functions/decorators/cosmosdb.py @@ -5,7 +5,7 @@ from azure.functions.decorators.constants import COSMOS_DB, COSMOS_DB_TRIGGER from azure.functions.decorators.core import DataType, InputBinding, \ - OutputBinding, Trigger + OutputBinding, Trigger, CosmosDBChangeFeedMode # Used by cosmos_db_input_v3 @@ -192,6 +192,8 @@ def __init__(self, start_from_beginning: Optional[time] = None, start_from_time: Optional[time] = None, preferred_locations: Optional[str] = None, + change_feed_mode: Optional[Union[CosmosDBChangeFeedMode, + str]] = None, data_type: Optional[Union[DataType]] = None, **kwargs): self.connection = connection @@ -212,4 +214,8 @@ def __init__(self, self.start_from_beginning = start_from_beginning self.start_from_time = start_from_time self.preferred_locations = preferred_locations + if isinstance(change_feed_mode, CosmosDBChangeFeedMode): + self.change_feed_mode = change_feed_mode.value + else: + self.change_feed_mode = change_feed_mode super().__init__(name=name, data_type=data_type) diff --git a/azure/functions/decorators/function_app.py b/azure/functions/decorators/function_app.py index 9ed2b6f..3dd33e7 100644 --- a/azure/functions/decorators/function_app.py +++ b/azure/functions/decorators/function_app.py @@ -17,7 +17,7 @@ from azure.functions.decorators.blob import BlobTrigger, BlobInput, BlobOutput from azure.functions.decorators.core import Binding, Trigger, DataType, \ AuthLevel, SCRIPT_FILE_NAME, Cardinality, AccessRights, Setting, BlobSource, \ - McpPropertyType + McpPropertyType, CosmosDBChangeFeedMode from azure.functions.decorators.cosmosdb import CosmosDBTrigger, \ CosmosDBOutput, CosmosDBInput, CosmosDBTriggerV3, CosmosDBInputV3, \ CosmosDBOutputV3 @@ -1047,6 +1047,8 @@ def cosmos_db_trigger(self, start_from_beginning: Optional[time] = None, start_from_time: Optional[time] = None, preferred_locations: Optional[str] = None, + change_feed_mode: Optional[Union[CosmosDBChangeFeedMode, + str]] = None, data_type: Optional[ Union[DataType, str]] = None, **kwargs: Any) -> \ @@ -1108,6 +1110,9 @@ def cosmos_db_trigger(self, has no effect once the trigger has a lease state. :param preferred_locations: Preferred locations (regions) for geo-replicated Cosmos DB accounts. + :param change_feed_mode: (Optional) The change feed mode for the Cosmos DB + trigger. Can be :class:`CosmosDBChangeFeedMode.LATEST_VERSION` (default) + or :class:`CosmosDBChangeFeedMode.ALL_VERSIONS_AND_DELETES`. :param data_type: Defines how the Functions runtime should treat the parameter value. :param kwargs: Additional keyword arguments for specifying binding fields @@ -1134,6 +1139,7 @@ def cosmos_db_trigger(self, start_from_beginning=start_from_beginning, start_from_time=start_from_time, preferred_locations=preferred_locations, + change_feed_mode=change_feed_mode, data_type=parse_singular_param_to_enum(data_type, DataType), **kwargs) diff --git a/tests/test_cosmosdb.py b/tests/test_cosmosdb.py index d5ae658..1e07adf 100644 --- a/tests/test_cosmosdb.py +++ b/tests/test_cosmosdb.py @@ -196,3 +196,24 @@ def test_cosmosdb_encode_no_implementation_exception1(self): # then self.assertTrue(is_exception_raised) + + def test_cosmosdb_change_feed_mode_latest_version_support(self): + """Test that CosmosDBChangeFeedMode supports LatestVersion mode.""" + # Verify that LatestVersion is a valid change feed mode + mode = 'LatestVersion' + self.assertIn(mode, ['LatestVersion', 'AllVersionsAndDeletes']) + self.assertEqual(mode, 'LatestVersion') + + def test_cosmosdb_change_feed_mode_all_versions_and_deletes_support(self): + """Test that CosmosDBChangeFeedMode supports AllVersionsAndDeletes mode.""" + # Verify that AllVersionsAndDeletes is a valid change feed mode + mode = 'AllVersionsAndDeletes' + self.assertIn(mode, ['LatestVersion', 'AllVersionsAndDeletes']) + self.assertEqual(mode, 'AllVersionsAndDeletes') + + def test_cosmosdb_change_feed_mode_type_available(self): + """Test that CosmosDBChangeFeedMode type is available in public API.""" + # Verify that the type is exported from the module + self.assertTrue(hasattr(func, 'CosmosDBChangeFeedMode')) + # Verify that the type exists in cosmosdb module + self.assertTrue(hasattr(cdb, 'CosmosDBChangeFeedMode'))