diff --git a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/__init__.py b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/__init__.py index 8c945af545a1..bb031e5ec320 100644 --- a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/__init__.py +++ b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/__init__.py @@ -10,7 +10,11 @@ from ._models import CloudEvent, EventGridEvent from ._version import VERSION -__all__ = ['EventGridPublisherClient', 'CloudEvent', - 'EventGridEvent', 'generate_sas', 'SystemEventNames' - ] +__all__ = [ + "EventGridPublisherClient", + "CloudEvent", + "EventGridEvent", + "generate_sas", + "SystemEventNames", +] __version__ = VERSION diff --git a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_constants.py b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_constants.py index e762ff44804a..0d26f09c4bdb 100644 --- a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_constants.py +++ b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_constants.py @@ -3,7 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -EVENTGRID_KEY_HEADER = 'aeg-sas-key' -EVENTGRID_TOKEN_HEADER = 'aeg-sas-token' +EVENTGRID_KEY_HEADER = "aeg-sas-key" +EVENTGRID_TOKEN_HEADER = "aeg-sas-token" DEFAULT_API_VERSION = "2018-01-01" -SAFE_ENCODE = '~()*!.\'' +SAFE_ENCODE = "~()*!.'" diff --git a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_event_mappings.py b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_event_mappings.py index cbd9c94828fc..f99040f4ce48 100644 --- a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_event_mappings.py +++ b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_event_mappings.py @@ -4,30 +4,52 @@ # -------------------------------------------------------------------------------------------- from enum import Enum + class SystemEventNames(str, Enum): """ This enum represents the names of the various event types for the system events published to Azure Event Grid. To check the list of recognizable system topics, visit https://docs.microsoft.com/azure/event-grid/system-topics. """ - ACSChatMemberAddedToThreadWithUserEventName = "Microsoft.Communication.ChatMemberAddedToThreadWithUser" - ACSChatMemberRemovedFromThreadWithUserEventName = "Microsoft.Communication.ChatMemberRemovedFromThreadWithUser" + + ACSChatMemberAddedToThreadWithUserEventName = ( + "Microsoft.Communication.ChatMemberAddedToThreadWithUser" + ) + ACSChatMemberRemovedFromThreadWithUserEventName = ( + "Microsoft.Communication.ChatMemberRemovedFromThreadWithUser" + ) ACSChatMessageDeletedEventName = "Microsoft.Communication.ChatMessageDeleted" ACSChatMessageEditedEventName = "Microsoft.Communication.ChatMessageEdited" ACSChatMessageReceivedEventName = "Microsoft.Communication.ChatMessageReceived" - ACSChatThreadCreatedWithUserEventName = "Microsoft.Communication.ChatThreadCreatedWithUser" - ACSChatThreadPropertiesUpdatedPerUserEventName = "Microsoft.Communication.ChatThreadPropertiesUpdatedPerUser" - ACSChatThreadWithUserDeletedEventName = "Microsoft.Communication.ChatThreadWithUserDeleted" - ACSSMSDeliveryReportReceivedEventName = "Microsoft.Communication.SMSDeliveryReportReceived" + ACSChatThreadCreatedWithUserEventName = ( + "Microsoft.Communication.ChatThreadCreatedWithUser" + ) + ACSChatThreadPropertiesUpdatedPerUserEventName = ( + "Microsoft.Communication.ChatThreadPropertiesUpdatedPerUser" + ) + ACSChatThreadWithUserDeletedEventName = ( + "Microsoft.Communication.ChatThreadWithUserDeleted" + ) + ACSSMSDeliveryReportReceivedEventName = ( + "Microsoft.Communication.SMSDeliveryReportReceived" + ) ACSSMSReceivedEventName = "Microsoft.Communication.SMSReceived" - AppConfigurationKeyValueDeletedEventName = "Microsoft.AppConfiguration.KeyValueDeleted" - AppConfigurationKeyValueModifiedEventName = "Microsoft.AppConfiguration.KeyValueModified" + AppConfigurationKeyValueDeletedEventName = ( + "Microsoft.AppConfiguration.KeyValueDeleted" + ) + AppConfigurationKeyValueModifiedEventName = ( + "Microsoft.AppConfiguration.KeyValueModified" + ) ContainerRegistryChartDeletedEventName = "Microsoft.ContainerRegistry.ChartDeleted" ContainerRegistryChartPushedEventName = "Microsoft.ContainerRegistry.ChartPushed" ContainerRegistryImageDeletedEventName = "Microsoft.ContainerRegistry.ImageDeleted" ContainerRegistryImagePushedEventName = "Microsoft.ContainerRegistry.ImagePushed" - EventGridSubscriptionDeletedEventName = "Microsoft.EventGrid.SubscriptionDeletedEvent" - EventGridSubscriptionValidationEventName = "Microsoft.EventGrid.SubscriptionValidationEvent" + EventGridSubscriptionDeletedEventName = ( + "Microsoft.EventGrid.SubscriptionDeletedEvent" + ) + EventGridSubscriptionValidationEventName = ( + "Microsoft.EventGrid.SubscriptionValidationEvent" + ) EventHubCaptureFileCreatedEventName = "Microsoft.EventHub.CaptureFileCreated" IoTHubDeviceConnectedEventName = "Microsoft.Devices.DeviceConnected" IoTHubDeviceCreatedEventName = "Microsoft.Devices.DeviceCreated" @@ -37,18 +59,32 @@ class SystemEventNames(str, Enum): KeyVaultAccessPolicyChangedEventName = "Microsoft.KeyVault.VaultAccessPolicyChanged" KeyVaultCertificateExpiredEventName = "Microsoft.KeyVault.CertificateExpired" KeyVaultCertificateNearExpiryEventName = "Microsoft.KeyVault.CertificateNearExpiry" - KeyVaultCertificateNewVersionCreatedEventName = "Microsoft.KeyVault.CertificateNewVersionCreated" + KeyVaultCertificateNewVersionCreatedEventName = ( + "Microsoft.KeyVault.CertificateNewVersionCreated" + ) KeyVaultKeyExpiredEventName = "Microsoft.KeyVault.KeyExpired" KeyVaultKeyNearExpiryEventName = "Microsoft.KeyVault.KeyNearExpiry" KeyVaultKeyNewVersionCreatedEventName = "Microsoft.KeyVault.KeyNewVersionCreated" KeyVaultSecretExpiredEventName = "Microsoft.KeyVault.SecretExpired" KeyVaultSecretNearExpiryEventName = "Microsoft.KeyVault.SecretNearExpiry" - KeyVaultSecretNewVersionCreatedEventName = "Microsoft.KeyVault.SecretNewVersionCreated" - MachineLearningServicesDatasetDriftDetectedEventName = "Microsoft.MachineLearningServices.DatasetDriftDetected" - MachineLearningServicesModelDeployedEventName = "Microsoft.MachineLearningServices.ModelDeployed" - MachineLearningServicesModelRegisteredEventName = "Microsoft.MachineLearningServices.ModelRegistered" - MachineLearningServicesRunCompletedEventName = "Microsoft.MachineLearningServices.RunCompleted" - MachineLearningServicesRunStatusChangedEventName = "Microsoft.MachineLearningServices.RunStatusChanged" + KeyVaultSecretNewVersionCreatedEventName = ( + "Microsoft.KeyVault.SecretNewVersionCreated" + ) + MachineLearningServicesDatasetDriftDetectedEventName = ( + "Microsoft.MachineLearningServices.DatasetDriftDetected" + ) + MachineLearningServicesModelDeployedEventName = ( + "Microsoft.MachineLearningServices.ModelDeployed" + ) + MachineLearningServicesModelRegisteredEventName = ( + "Microsoft.MachineLearningServices.ModelRegistered" + ) + MachineLearningServicesRunCompletedEventName = ( + "Microsoft.MachineLearningServices.RunCompleted" + ) + MachineLearningServicesRunStatusChangedEventName = ( + "Microsoft.MachineLearningServices.RunStatusChanged" + ) MapsGeofenceEnteredEventName = "Microsoft.Maps.GeofenceEntered" MapsGeofenceExitedEventName = "Microsoft.Maps.GeofenceExited" MapsGeofenceResultEventName = "Microsoft.Maps.GeofenceResult" @@ -67,15 +103,31 @@ class SystemEventNames(str, Enum): MediaJobProcessingEventName = "Microsoft.Media.JobProcessing" MediaJobScheduledEventName = "Microsoft.Media.JobScheduled" MediaJobStateChangeEventName = "Microsoft.Media.JobStateChange" - MediaLiveEventConnectionRejectedEventName = "Microsoft.Media.LiveEventConnectionRejected" - MediaLiveEventEncoderConnectedEventName = "Microsoft.Media.LiveEventEncoderConnected" - MediaLiveEventEncoderDisconnectedEventName = "Microsoft.Media.LiveEventEncoderDisconnected" - MediaLiveEventIncomingDataChunkDroppedEventName = "Microsoft.Media.LiveEventIncomingDataChunkDropped" - MediaLiveEventIncomingStreamReceivedEventName = "Microsoft.Media.LiveEventIncomingStreamReceived" - MediaLiveEventIncomingStreamsOutOfSyncEventName = "Microsoft.Media.LiveEventIncomingStreamsOutOfSync" - MediaLiveEventIncomingVideoStreamsOutOfSyncEventName = "Microsoft.Media.LiveEventIncomingVideoStreamsOutOfSync" + MediaLiveEventConnectionRejectedEventName = ( + "Microsoft.Media.LiveEventConnectionRejected" + ) + MediaLiveEventEncoderConnectedEventName = ( + "Microsoft.Media.LiveEventEncoderConnected" + ) + MediaLiveEventEncoderDisconnectedEventName = ( + "Microsoft.Media.LiveEventEncoderDisconnected" + ) + MediaLiveEventIncomingDataChunkDroppedEventName = ( + "Microsoft.Media.LiveEventIncomingDataChunkDropped" + ) + MediaLiveEventIncomingStreamReceivedEventName = ( + "Microsoft.Media.LiveEventIncomingStreamReceived" + ) + MediaLiveEventIncomingStreamsOutOfSyncEventName = ( + "Microsoft.Media.LiveEventIncomingStreamsOutOfSync" + ) + MediaLiveEventIncomingVideoStreamsOutOfSyncEventName = ( + "Microsoft.Media.LiveEventIncomingVideoStreamsOutOfSync" + ) MediaLiveEventIngestHeartbeatEventName = "Microsoft.Media.LiveEventIngestHeartbeat" - MediaLiveEventTrackDiscontinuityDetectedEventName = "Microsoft.Media.LiveEventTrackDiscontinuityDetected" + MediaLiveEventTrackDiscontinuityDetectedEventName = ( + "Microsoft.Media.LiveEventTrackDiscontinuityDetected" + ) ResourceActionCancelEventName = "Microsoft.Resources.ResourceActionCancel" ResourceActionFailureEventName = "Microsoft.Resources.ResourceActionFailure" ResourceActionSuccessEventName = "Microsoft.Resources.ResourceActionSuccess" @@ -85,17 +137,21 @@ class SystemEventNames(str, Enum): ResourceWriteCancelEventName = "Microsoft.Resources.ResourceWriteCancel" ResourceWriteFailureEventName = "Microsoft.Resources.ResourceWriteFailure" ResourceWriteSuccessEventName = "Microsoft.Resources.ResourceWriteSuccess" - ServiceBusActiveMessagesAvailableWithNoListenersEventName = \ - "Microsoft.ServiceBus.ActiveMessagesAvailableWithNoListeners" - ServiceBusDeadletterMessagesAvailableWithNoListenerEventName = \ - "Microsoft.ServiceBus.DeadletterMessagesAvailableWithNoListener" + ServiceBusActiveMessagesAvailableWithNoListenersEventName = ( + "Microsoft.ServiceBus.ActiveMessagesAvailableWithNoListeners" + ) + ServiceBusDeadletterMessagesAvailableWithNoListenerEventName = ( + "Microsoft.ServiceBus.DeadletterMessagesAvailableWithNoListener" + ) StorageBlobCreatedEventName = "Microsoft.Storage.BlobCreated" StorageBlobDeletedEventName = "Microsoft.Storage.BlobDeleted" StorageBlobRenamedEventName = "Microsoft.Storage.BlobRenamed" StorageDirectoryCreatedEventName = "Microsoft.Storage.DirectoryCreated" StorageDirectoryDeletedEventName = "Microsoft.Storage.DirectoryDeleted" StorageDirectoryRenamedEventName = "Microsoft.Storage.DirectoryRenamed" - StorageLifecyclePolicyCompletedEventName = "Microsoft.Storage.LifecyclePolicyCompleted" + StorageLifecyclePolicyCompletedEventName = ( + "Microsoft.Storage.LifecyclePolicyCompleted" + ) WebAppServicePlanUpdatedEventName = "Microsoft.Web.AppServicePlanUpdated" WebAppUpdatedEventName = "Microsoft.Web.AppUpdated" WebBackupOperationCompletedEventName = "Microsoft.Web.BackupOperationCompleted" @@ -107,5 +163,7 @@ class SystemEventNames(str, Enum): WebSlotSwapCompletedEventName = "Microsoft.Web.SlotSwapCompleted" WebSlotSwapFailedEventName = "Microsoft.Web.SlotSwapFailed" WebSlotSwapStartedEventName = "Microsoft.Web.SlotSwapStarted" - WebSlotSwapWithPreviewCancelledEventName = "Microsoft.Web.SlotSwapWithPreviewCancelled" + WebSlotSwapWithPreviewCancelledEventName = ( + "Microsoft.Web.SlotSwapWithPreviewCancelled" + ) WebSlotSwapWithPreviewStartedEventName = "Microsoft.Web.SlotSwapWithPreviewStarted" diff --git a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_helpers.py b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_helpers.py index 9f06d8666bad..2780ceb569bb 100644 --- a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_helpers.py +++ b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_helpers.py @@ -7,10 +7,11 @@ import hmac import base64 import six + try: from urllib.parse import quote except ImportError: - from urllib2 import quote # type: ignore + from urllib2 import quote # type: ignore from azure.core.pipeline.policies import AzureKeyCredentialPolicy from azure.core.credentials import AzureKeyCredential, AzureSasCredential @@ -20,102 +21,116 @@ if TYPE_CHECKING: from datetime import datetime + def generate_sas(endpoint, shared_access_key, expiration_date_utc, **kwargs): # type: (str, str, datetime, Any) -> str - """ Helper method to generate shared access signature given hostname, key, and expiration date. - :param str endpoint: The topic endpoint to send the events to. - Similar to .-1.eventgrid.azure.net - :param str shared_access_key: The shared access key to be used for generating the token - :param datetime.datetime expiration_date_utc: The expiration datetime in UTC for the signature. - :keyword str api_version: The API Version to include in the signature. - If not provided, the default API version will be used. - :rtype: str - - .. admonition:: Example: - - .. literalinclude:: ../samples/sync_samples/sample_generate_sas.py - :start-after: [START generate_sas] - :end-before: [END generate_sas] - :language: python - :dedent: 0 - :caption: Generate a shared access signature. + """Helper method to generate shared access signature given hostname, key, and expiration date. + :param str endpoint: The topic endpoint to send the events to. + Similar to .-1.eventgrid.azure.net + :param str shared_access_key: The shared access key to be used for generating the token + :param datetime.datetime expiration_date_utc: The expiration datetime in UTC for the signature. + :keyword str api_version: The API Version to include in the signature. + If not provided, the default API version will be used. + :rtype: str + + .. admonition:: Example: + + .. literalinclude:: ../samples/sync_samples/sample_generate_sas.py + :start-after: [START generate_sas] + :end-before: [END generate_sas] + :language: python + :dedent: 0 + :caption: Generate a shared access signature. """ full_endpoint = _get_full_endpoint(endpoint) full_endpoint = "{}?apiVersion={}".format( - full_endpoint, - kwargs.get('api_version', None) or constants.DEFAULT_API_VERSION + full_endpoint, kwargs.get("api_version", None) or constants.DEFAULT_API_VERSION ) encoded_resource = quote(full_endpoint, safe=constants.SAFE_ENCODE) encoded_expiration_utc = quote(str(expiration_date_utc), safe=constants.SAFE_ENCODE) unsigned_sas = "r={}&e={}".format(encoded_resource, encoded_expiration_utc) - signature = quote(_generate_hmac(shared_access_key, unsigned_sas), safe=constants.SAFE_ENCODE) + signature = quote( + _generate_hmac(shared_access_key, unsigned_sas), safe=constants.SAFE_ENCODE + ) signed_sas = "{}&s={}".format(unsigned_sas, signature) return signed_sas + def _get_endpoint_only_fqdn(endpoint): - if endpoint.startswith('http://'): + if endpoint.startswith("http://"): raise ValueError("HTTP is not supported. Only HTTPS is supported.") - if endpoint.startswith('https://'): + if endpoint.startswith("https://"): endpoint = endpoint.replace("https://", "") if endpoint.endswith("/api/events"): endpoint = endpoint.replace("/api/events", "") return endpoint + def _get_full_endpoint(endpoint): - if endpoint.startswith('http://'): + if endpoint.startswith("http://"): raise ValueError("HTTP is not supported. Only HTTPS is supported.") - if not endpoint.startswith('https://'): + if not endpoint.startswith("https://"): endpoint = "https://{}".format(endpoint) if not endpoint.endswith("/api/events"): endpoint = "{}/api/events".format(endpoint) return endpoint + def _generate_hmac(key, message): decoded_key = base64.b64decode(key) - bytes_message = message.encode('ascii') + bytes_message = message.encode("ascii") hmac_new = hmac.new(decoded_key, bytes_message, hashlib.sha256).digest() return base64.b64encode(hmac_new) + def _get_authentication_policy(credential): if credential is None: raise ValueError("Parameter 'self._credential' must not be None.") if isinstance(credential, AzureKeyCredential): - return AzureKeyCredentialPolicy(credential=credential, name=constants.EVENTGRID_KEY_HEADER) + return AzureKeyCredentialPolicy( + credential=credential, name=constants.EVENTGRID_KEY_HEADER + ) if isinstance(credential, AzureSasCredential): return EventGridSasCredentialPolicy( - credential=credential, - name=constants.EVENTGRID_TOKEN_HEADER + credential=credential, name=constants.EVENTGRID_TOKEN_HEADER ) - raise ValueError("The provided credential should be an instance of AzureSasCredential or AzureKeyCredential") + raise ValueError( + "The provided credential should be an instance of AzureSasCredential or AzureKeyCredential" + ) + def _is_cloud_event(event): # type: (Any) -> bool - required = ('id', 'source', 'specversion', 'type') + required = ("id", "source", "specversion", "type") try: - return all([_ in event for _ in required]) and event['specversion'] == "1.0" + return all([_ in event for _ in required]) and event["specversion"] == "1.0" except TypeError: return False + def _is_eventgrid_event(event): # type: (Any) -> bool - required = ('subject', 'eventType', 'data', 'dataVersion', 'id', 'eventTime') + required = ("subject", "eventType", "data", "dataVersion", "id", "eventTime") try: return all([prop in event for prop in required]) except TypeError: return False + def _eventgrid_data_typecheck(event): try: - data = event.get('data') + data = event.get("data") except AttributeError: data = event.data if isinstance(data, six.binary_type): - raise TypeError("Data in EventGridEvent cannot be bytes. Please refer to"\ - "https://docs.microsoft.com/en-us/azure/event-grid/event-schema") + raise TypeError( + "Data in EventGridEvent cannot be bytes. Please refer to" + "https://docs.microsoft.com/en-us/azure/event-grid/event-schema" + ) diff --git a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_models.py b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_models.py index c886e78553a6..ba79546c8bc8 100644 --- a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_models.py +++ b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_models.py @@ -9,13 +9,17 @@ import json import six from msrest.serialization import UTC -from ._generated.models import EventGridEvent as InternalEventGridEvent, CloudEvent as InternalCloudEvent +from ._generated.models import ( + EventGridEvent as InternalEventGridEvent, + CloudEvent as InternalCloudEvent, +) class EventMixin(object): """ Mixin for the event models comprising of some helper methods. """ + @staticmethod def _from_json(event, encode): """ @@ -31,7 +35,7 @@ def _from_json(event, encode): return event -class CloudEvent(EventMixin): #pylint:disable=too-many-instance-attributes +class CloudEvent(EventMixin): # pylint:disable=too-many-instance-attributes """Properties of an event published to an Event Grid topic using the CloudEvent 1.0 Schema. All required parameters must be populated in order to send to Azure. @@ -88,7 +92,8 @@ class CloudEvent(EventMixin): #pylint:disable=too-many-instance-attributes unique for each distinct event. If not provided, a random UUID will be generated and used. :vartype id: Optional[str] """ - def __init__(self, source, type, **kwargs): # pylint: disable=redefined-builtin + + def __init__(self, source, type, **kwargs): # pylint: disable=redefined-builtin # type: (str, str, Any) -> None self.source = source self.type = type @@ -101,10 +106,12 @@ def __init__(self, source, type, **kwargs): # pylint: disable=redefined-builtin self.subject = kwargs.pop("subject", None) self.data_base64 = kwargs.pop("data_base64", None) self.extensions = {} - self.extensions.update(dict(kwargs.pop('extensions', {}))) + self.extensions.update(dict(kwargs.pop("extensions", {}))) if self.data is not None and self.data_base64 is not None: - raise ValueError("data and data_base64 cannot be provided at the same time.\ - Use data_base64 only if you are sending bytes, and use data otherwise.") + raise ValueError( + "data and data_base64 cannot be provided at the same time.\ + Use data_base64 only if you are sending bytes, and use data otherwise." + ) @classmethod def _from_generated(cls, cloud_event, **kwargs): @@ -112,7 +119,7 @@ def _from_generated(cls, cloud_event, **kwargs): generated = InternalCloudEvent.deserialize(cloud_event) if generated.additional_properties: extensions = dict(generated.additional_properties) - kwargs.setdefault('extensions', extensions) + kwargs.setdefault("extensions", extensions) return cls( id=generated.id, source=generated.source, @@ -191,40 +198,40 @@ class EventGridEvent(InternalEventGridEvent, EventMixin): If not provided, EventGrid will stamp onto event. :vartype metadata_version: str :ivar id: An identifier for the event. In not provided, a random UUID will be generated and used. - :vartype id: Optional[str] + :vartype id: str :ivar event_time: The time (in UTC) of the event. If not provided, it will be the time (in UTC) the event was generated. - :vartype event_time: Optional[~datetime.datetime] + :vartype event_time: ~datetime.datetime """ _validation = { - 'id': {'required': True}, - 'subject': {'required': True}, - 'data': {'required': True}, - 'event_type': {'required': True}, - 'event_time': {'required': True}, - 'metadata_version': {'readonly': True}, - 'data_version': {'required': True}, + "id": {"required": True}, + "subject": {"required": True}, + "data": {"required": True}, + "event_type": {"required": True}, + "event_time": {"required": True}, + "metadata_version": {"readonly": True}, + "data_version": {"required": True}, } _attribute_map = { - 'id': {'key': 'id', 'type': 'str'}, - 'topic': {'key': 'topic', 'type': 'str'}, - 'subject': {'key': 'subject', 'type': 'str'}, - 'data': {'key': 'data', 'type': 'object'}, - 'event_type': {'key': 'eventType', 'type': 'str'}, - 'event_time': {'key': 'eventTime', 'type': 'iso-8601'}, - 'metadata_version': {'key': 'metadataVersion', 'type': 'str'}, - 'data_version': {'key': 'dataVersion', 'type': 'str'}, + "id": {"key": "id", "type": "str"}, + "topic": {"key": "topic", "type": "str"}, + "subject": {"key": "subject", "type": "str"}, + "data": {"key": "data", "type": "object"}, + "event_type": {"key": "eventType", "type": "str"}, + "event_time": {"key": "eventTime", "type": "iso-8601"}, + "metadata_version": {"key": "metadataVersion", "type": "str"}, + "data_version": {"key": "dataVersion", "type": "str"}, } def __init__(self, subject, event_type, data, data_version, **kwargs): # type: (str, str, object, str, Any) -> None - kwargs.setdefault('id', uuid.uuid4()) - kwargs.setdefault('subject', subject) + kwargs.setdefault("id", uuid.uuid4()) + kwargs.setdefault("subject", subject) kwargs.setdefault("event_type", event_type) - kwargs.setdefault('event_time', dt.datetime.now(UTC()).isoformat()) - kwargs.setdefault('data', data) - kwargs.setdefault('data_version', data_version) + kwargs.setdefault("event_time", dt.datetime.now(UTC()).isoformat()) + kwargs.setdefault("data", data) + kwargs.setdefault("data_version", data_version) super(EventGridEvent, self).__init__(**kwargs) diff --git a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_policies.py b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_policies.py index 5de3c5025249..7617f0c9a516 100644 --- a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_policies.py +++ b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_policies.py @@ -13,6 +13,7 @@ if TYPE_CHECKING: from azure.core.pipeline import PipelineRequest + class CloudEventDistributedTracingPolicy(SansIOHTTPPolicy): """CloudEventDistributedTracingPolicy is a policy which adds distributed tracing informatiom to a batch of cloud events. It does so by copying the `traceparent` and `tracestate` properties @@ -23,24 +24,27 @@ class CloudEventDistributedTracingPolicy(SansIOHTTPPolicy): See https://github.com/cloudevents/spec/blob/master/extensions/distributed-tracing.md for more information on distributed tracing and cloud events. """ + _CONTENT_TYPE = "application/cloudevents-batch+json; charset=utf-8" def on_request(self, request): # type: (PipelineRequest) -> None try: - traceparent = request.http_request.headers['traceparent'] - tracestate = request.http_request.headers['tracestate'] + traceparent = request.http_request.headers["traceparent"] + tracestate = request.http_request.headers["tracestate"] except KeyError: return - if (request.http_request.headers['content-type'] == CloudEventDistributedTracingPolicy._CONTENT_TYPE + if ( + request.http_request.headers["content-type"] + == CloudEventDistributedTracingPolicy._CONTENT_TYPE and traceparent is not None - ): + ): body = json.loads(request.http_request.body) for item in body: - if 'traceparent' not in item and 'tracestate' not in item: - item['traceparent'] = traceparent - item['tracestate'] = tracestate + if "traceparent" not in item and "tracestate" not in item: + item["traceparent"] = traceparent + item["tracestate"] = tracestate request.http_request.body = json.dumps(body) diff --git a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_publisher_client.py b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_publisher_client.py index 178eddc8f853..20b8b04df3b0 100644 --- a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_publisher_client.py +++ b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_publisher_client.py @@ -19,7 +19,7 @@ ProxyPolicy, DistributedTracingPolicy, HttpLoggingPolicy, - UserAgentPolicy + UserAgentPolicy, ) from ._models import CloudEvent, EventGridEvent @@ -28,30 +28,28 @@ _get_authentication_policy, _is_cloud_event, _is_eventgrid_event, - _eventgrid_data_typecheck + _eventgrid_data_typecheck, +) +from ._generated._event_grid_publisher_client import ( + EventGridPublisherClient as EventGridPublisherClientImpl, ) -from ._generated._event_grid_publisher_client import EventGridPublisherClient as EventGridPublisherClientImpl from ._policies import CloudEventDistributedTracingPolicy from ._version import VERSION -from ._generated.models import CloudEvent as InternalCloudEvent if TYPE_CHECKING: # pylint: disable=unused-import,ungrouped-imports from azure.core.credentials import AzureKeyCredential, AzureSasCredential + SendType = Union[ CloudEvent, EventGridEvent, Dict, List[CloudEvent], List[EventGridEvent], - List[Dict] + List[Dict], ] -ListEventType = Union[ - List[CloudEvent], - List[EventGridEvent], - List[Dict] -] +ListEventType = Union[List[CloudEvent], List[EventGridEvent], List[Dict]] class EventGridPublisherClient(object): @@ -86,15 +84,14 @@ def __init__(self, endpoint, credential, **kwargs): self._endpoint = endpoint self._client = EventGridPublisherClientImpl( - policies=EventGridPublisherClient._policies(credential, **kwargs), - **kwargs - ) + policies=EventGridPublisherClient._policies(credential, **kwargs), **kwargs + ) @staticmethod def _policies(credential, **kwargs): # type: (Union[AzureKeyCredential, AzureSasCredential], Any) -> List[Any] auth_policy = _get_authentication_policy(credential) - sdk_moniker = 'eventgrid/{}'.format(VERSION) + sdk_moniker = "eventgrid/{}".format(VERSION) policies = [ RequestIdPolicy(**kwargs), HeadersPolicy(**kwargs), @@ -108,7 +105,7 @@ def _policies(credential, **kwargs): NetworkTraceLoggingPolicy(**kwargs), DistributedTracingPolicy(**kwargs), CloudEventDistributedTracingPolicy(), - HttpLoggingPolicy(**kwargs) + HttpLoggingPolicy(**kwargs), ] return policies @@ -175,31 +172,31 @@ def send(self, events, **kwargs): Has default value "application/json; charset=utf-8" for EventGridEvents, with "cloudevents-batch+json" for CloudEvents :rtype: None - """ + """ if not isinstance(events, list): events = cast(ListEventType, [events]) if isinstance(events[0], CloudEvent) or _is_cloud_event(events[0]): try: - events = [cast(CloudEvent, e)._to_generated(**kwargs) for e in events] # pylint: disable=protected-access + events = [ + cast(CloudEvent, e)._to_generated(**kwargs) for e in events # pylint: disable=protected-access + ] except AttributeError: - pass # means it's a dictionary - kwargs.setdefault("content_type", "application/cloudevents-batch+json; charset=utf-8") - return self._client.publish_cloud_event_events( - self._endpoint, - cast(List[InternalCloudEvent], events), - **kwargs - ) - kwargs.setdefault("content_type", "application/json; charset=utf-8") - if isinstance(events[0], EventGridEvent) or _is_eventgrid_event(events[0]): + pass # means it's a dictionary + kwargs.setdefault( + "content_type", "application/cloudevents-batch+json; charset=utf-8" + ) + elif isinstance(events[0], EventGridEvent) or _is_eventgrid_event(events[0]): + kwargs.setdefault("content_type", "application/json; charset=utf-8") for event in events: _eventgrid_data_typecheck(event) - return self._client.publish_custom_event_events(self._endpoint, cast(List, events), **kwargs) + return self._client.publish_custom_event_events( + self._endpoint, cast(List, events), **kwargs + ) def close(self): # type: () -> None - """Close the :class:`~azure.eventgrid.EventGridPublisherClient` session. - """ + """Close the :class:`~azure.eventgrid.EventGridPublisherClient` session.""" return self._client.close() def __enter__(self): diff --git a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_signature_credential_policy.py b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_signature_credential_policy.py index d3d7e33d4046..d0c6fc47d5e0 100644 --- a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_signature_credential_policy.py +++ b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/_signature_credential_policy.py @@ -20,6 +20,7 @@ class EventGridSasCredentialPolicy(SansIOHTTPPolicy): :param str name: The name of the token header used for the credential. :raises: ValueError or TypeError """ + def __init__(self, credential, name, **kwargs): # pylint: disable=unused-argument # type: (AzureSasCredential, str, Any) -> None super(EventGridSasCredentialPolicy, self).__init__() diff --git a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/aio/__init__.py b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/aio/__init__.py index 2de020d81e02..0d2dce7aaea2 100644 --- a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/aio/__init__.py +++ b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/aio/__init__.py @@ -6,4 +6,4 @@ from ._publisher_client_async import EventGridPublisherClient -__all__ = ['EventGridPublisherClient'] +__all__ = ["EventGridPublisherClient"] diff --git a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/aio/_publisher_client_async.py b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/aio/_publisher_client_async.py index 797d2191b00c..5029e9a5f963 100644 --- a/sdk/eventgrid/azure-eventgrid/azure/eventgrid/aio/_publisher_client_async.py +++ b/sdk/eventgrid/azure-eventgrid/azure/eventgrid/aio/_publisher_client_async.py @@ -20,7 +20,7 @@ ProxyPolicy, DistributedTracingPolicy, HttpLoggingPolicy, - UserAgentPolicy + UserAgentPolicy, ) from .._policies import CloudEventDistributedTracingPolicy from .._models import CloudEvent, EventGridEvent @@ -29,28 +29,19 @@ _get_authentication_policy, _is_cloud_event, _is_eventgrid_event, - _eventgrid_data_typecheck + _eventgrid_data_typecheck, ) from .._generated.aio import EventGridPublisherClient as EventGridPublisherClientAsync -from .._generated.models import CloudEvent as InternalCloudEvent from .._version import VERSION SendType = Union[ - CloudEvent, - EventGridEvent, - Dict, - List[CloudEvent], - List[EventGridEvent], - List[Dict] + CloudEvent, EventGridEvent, Dict, List[CloudEvent], List[EventGridEvent], List[Dict] ] -ListEventType = Union[ - List[CloudEvent], - List[EventGridEvent], - List[Dict] -] +ListEventType = Union[List[CloudEvent], List[EventGridEvent], List[Dict]] + -class EventGridPublisherClient(): +class EventGridPublisherClient: """Asynchronous EventGridPublisherClient publishes events to an EventGrid topic or domain. It can be used to publish either an EventGridEvent, a CloudEvent or a Custom Schema. @@ -81,21 +72,20 @@ def __init__( self, endpoint: str, credential: Union[AzureKeyCredential, AzureSasCredential], - **kwargs: Any) -> None: + **kwargs: Any + ) -> None: self._client = EventGridPublisherClientAsync( - policies=EventGridPublisherClient._policies(credential, **kwargs), - **kwargs - ) + policies=EventGridPublisherClient._policies(credential, **kwargs), **kwargs + ) endpoint = _get_endpoint_only_fqdn(endpoint) self._endpoint = endpoint @staticmethod def _policies( - credential: Union[AzureKeyCredential, AzureSasCredential], - **kwargs: Any - ) -> List[Any]: + credential: Union[AzureKeyCredential, AzureSasCredential], **kwargs: Any + ) -> List[Any]: auth_policy = _get_authentication_policy(credential) - sdk_moniker = 'eventgridpublisherclient/{}'.format(VERSION) + sdk_moniker = "eventgridpublisherclient/{}".format(VERSION) policies = [ RequestIdPolicy(**kwargs), HeadersPolicy(**kwargs), @@ -109,15 +99,12 @@ def _policies( NetworkTraceLoggingPolicy(**kwargs), DistributedTracingPolicy(**kwargs), CloudEventDistributedTracingPolicy(), - HttpLoggingPolicy(**kwargs) + HttpLoggingPolicy(**kwargs), ] return policies @distributed_trace_async - async def send( - self, - events: SendType, - **kwargs: Any) -> None: + async def send(self, events: SendType, **kwargs: Any) -> None: """Sends events to a topic or a domain specified during the client initialization. A single instance or a list of dictionaries, CloudEvents or EventGridEvents are accepted. @@ -178,26 +165,27 @@ async def send( Has default value "application/json; charset=utf-8" for EventGridEvents, with "cloudevents-batch+json" for CloudEvents :rtype: None - """ + """ if not isinstance(events, list): events = cast(ListEventType, [events]) if isinstance(events[0], CloudEvent) or _is_cloud_event(events[0]): try: - events = [cast(CloudEvent, e)._to_generated(**kwargs) for e in events] # pylint: disable=protected-access + events = [ + cast(CloudEvent, e)._to_generated(**kwargs) for e in events # pylint: disable=protected-access + ] except AttributeError: - pass # means it's a dictionary - kwargs.setdefault("content_type", "application/cloudevents-batch+json; charset=utf-8") - return await self._client.publish_cloud_event_events( - self._endpoint, - cast(List[InternalCloudEvent], events), - **kwargs - ) - kwargs.setdefault("content_type", "application/json; charset=utf-8") - if isinstance(events[0], EventGridEvent) or _is_eventgrid_event(events[0]): + pass # means it's a dictionary + kwargs.setdefault( + "content_type", "application/cloudevents-batch+json; charset=utf-8" + ) + elif isinstance(events[0], EventGridEvent) or _is_eventgrid_event(events[0]): + kwargs.setdefault("content_type", "application/json; charset=utf-8") for event in events: _eventgrid_data_typecheck(event) - return await self._client.publish_custom_event_events(self._endpoint, cast(List, events), **kwargs) + return await self._client.publish_custom_event_events( + self._endpoint, cast(List, events), **kwargs + ) async def __aenter__(self) -> "EventGridPublisherClient": await self._client.__aenter__() @@ -207,6 +195,5 @@ async def __aexit__(self, *args: "Any") -> None: await self._client.__aexit__(*args) async def close(self) -> None: - """Close the :class:`~azure.eventgrid.aio.EventGridPublisherClient` session. - """ + """Close the :class:`~azure.eventgrid.aio.EventGridPublisherClient` session.""" await self._client.__aexit__() diff --git a/sdk/eventgrid/azure-eventgrid/tests/recordings/test_eg_publisher_client.test_send_event_grid_event_dict_data_dict.yaml b/sdk/eventgrid/azure-eventgrid/tests/recordings/test_eg_publisher_client.test_send_event_grid_event_dict_data_dict.yaml new file mode 100644 index 000000000000..820b5a3f8473 --- /dev/null +++ b/sdk/eventgrid/azure-eventgrid/tests/recordings/test_eg_publisher_client.test_send_event_grid_event_dict_data_dict.yaml @@ -0,0 +1,38 @@ +interactions: +- request: + body: '[{"subject": "sample", "data": {"key1": "Sample.EventGrid.Event"}, "eventType": + "Sample.EventGrid.Event", "dataVersion": "2.0", "id": "a63fbe5f-6e9d-49be-b660-c7e22eca46d9", + "eventTime": "2021-03-01 01:35:26.507333"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '217' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - azsdk-python-eventgrid/2.0.0b6 Python/3.7.3 (Windows-10-10.0.18362-SP0) + method: POST + uri: https://eventgridtestegtopic.westus-1.eventgrid.azure.net/api/events?api-version=2018-01-01 + response: + body: + string: '' + headers: + api-supported-versions: + - '2018-01-01' + content-length: + - '0' + date: + - Mon, 01 Mar 2021 09:35:25 GMT + server: + - Microsoft-HTTPAPI/2.0 + strict-transport-security: + - max-age=31536000; includeSubDomains + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/eventgrid/azure-eventgrid/tests/test_eg_publisher_client.py b/sdk/eventgrid/azure-eventgrid/tests/test_eg_publisher_client.py index efd2458cf3d7..5792a07dec03 100644 --- a/sdk/eventgrid/azure-eventgrid/tests/test_eg_publisher_client.py +++ b/sdk/eventgrid/azure-eventgrid/tests/test_eg_publisher_client.py @@ -102,6 +102,24 @@ def test_send_event_grid_event_dict_data_bytes(self, resource_group, eventgrid_t with pytest.raises(TypeError, match="Data in EventGridEvent cannot be bytes*"): client.send(eg_event) + @CachedResourceGroupPreparer(name_prefix='eventgridtest') + @CachedEventGridTopicPreparer(name_prefix='eventgridtest') + def test_send_event_grid_event_dict_data_dict(self, resource_group, eventgrid_topic, eventgrid_topic_primary_key, eventgrid_topic_endpoint): + akc_credential = AzureKeyCredential(eventgrid_topic_primary_key) + client = EventGridPublisherClient(eventgrid_topic_endpoint, akc_credential) + eg_event = { + "subject":"sample", + "data":{"key1": "Sample.EventGrid.Event"}, + "eventType":"Sample.EventGrid.Event", + "dataVersion":"2.0", + "id": uuid.uuid4(), + "eventTime": datetime.now() + } + client.send(eg_event) + + + ### CLOUD EVENT TESTS + @CachedResourceGroupPreparer(name_prefix='eventgridtest') @CachedEventGridTopicPreparer(name_prefix='cloudeventgridtest') def test_send_cloud_event_data_dict(self, resource_group, eventgrid_topic, eventgrid_topic_primary_key, eventgrid_topic_endpoint): @@ -257,6 +275,12 @@ def test_send_signature_credential(self, resource_group, eventgrid_topic, eventg ) client.send(eg_event) + @CachedResourceGroupPreparer(name_prefix='eventgridtest') + @CachedEventGridTopicPreparer(name_prefix='eventgridtest') + def test_send_NONE_credential(self, resource_group, eventgrid_topic, eventgrid_topic_primary_key, eventgrid_topic_endpoint): + with pytest.raises(ValueError, match="Parameter 'self._credential' must not be None."): + client = EventGridPublisherClient(eventgrid_topic_endpoint, None) + @CachedResourceGroupPreparer(name_prefix='eventgridtest') @CachedEventGridTopicPreparer(name_prefix='customeventgridtest') def test_send_custom_schema_event(self, resource_group, eventgrid_topic, eventgrid_topic_primary_key, eventgrid_topic_endpoint): diff --git a/sdk/eventgrid/azure-eventgrid/tests/test_eg_publisher_client_async.py b/sdk/eventgrid/azure-eventgrid/tests/test_eg_publisher_client_async.py index 5a9152432c05..040866a36ac2 100644 --- a/sdk/eventgrid/azure-eventgrid/tests/test_eg_publisher_client_async.py +++ b/sdk/eventgrid/azure-eventgrid/tests/test_eg_publisher_client_async.py @@ -284,3 +284,10 @@ async def test_send_and_close_async_session(self, resource_group, eventgrid_topi type="Sample.Cloud.Event" ) await client.send(cloud_event) + + @CachedResourceGroupPreparer(name_prefix='eventgridtest') + @CachedEventGridTopicPreparer(name_prefix='cloudeventgridtest') + @pytest.mark.asyncio + def test_send_NONE_credential_async(self, resource_group, eventgrid_topic, eventgrid_topic_primary_key, eventgrid_topic_endpoint): + with pytest.raises(ValueError, match="Parameter 'self._credential' must not be None."): + client = EventGridPublisherClient(eventgrid_topic_endpoint, None) \ No newline at end of file diff --git a/sdk/eventgrid/azure-eventgrid/tests/test_generate_sas.py b/sdk/eventgrid/azure-eventgrid/tests/test_generate_sas.py new file mode 100644 index 000000000000..1ffa75c1b55d --- /dev/null +++ b/sdk/eventgrid/azure-eventgrid/tests/test_generate_sas.py @@ -0,0 +1,29 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +#-------------------------------------------------------------------------- +import pytest +import datetime as dt +from datetime import timedelta +from msrest.serialization import UTC + +from azure.eventgrid import generate_sas + +def test_generate_sas_fails_with_http(): + expiration_date_utc = dt.datetime.now(UTC()) + timedelta(hours=1) + http_endpoint = "http://topic.eventgrid.endpoint" + with pytest.raises(ValueError): + signature = generate_sas(http_endpoint, "eventgrid_topic_primary_key", expiration_date_utc) + +def test_generate_sas_adds_https_if_not_exists(): + expiration_date_utc = dt.datetime.now(UTC()) + timedelta(hours=1) + http_endpoint = "topic.eventgrid.endpoint" + signature = generate_sas(http_endpoint, "eventgrid_topic_primary_key", expiration_date_utc) + assert signature.startswith("r=https") + +def test_generate_sas_adds_https_appends_api_events(): + expiration_date_utc = dt.datetime.now(UTC()) + timedelta(hours=1) + http_endpoint = "https://topic.eventgrid.endpoint" + signature = generate_sas(http_endpoint, "eventgrid_topic_primary_key", expiration_date_utc) + assert "%2Fapi%2Fevents" in signature \ No newline at end of file