Ejemplo n.º 1
0
class EventData(object):
    """The EventData class is a container for event content.

    :param body: The data to send in a single message. body can be type of str or bytes.
    :type body: str or bytes

    .. admonition:: Example:

        .. literalinclude:: ../samples/sync_samples/sample_code_eventhub.py
            :start-after: [START create_event_data]
            :end-before: [END create_event_data]
            :language: python
            :dedent: 4
            :caption: Create instances of EventData

    """
    def __init__(self, body=None):
        # type: (Union[str, bytes, List[AnyStr]]) -> None
        self._last_enqueued_event_properties = {}  # type: Dict[str, Any]
        self._sys_properties = None  # type: Optional[Dict[bytes, Any]]
        if body and isinstance(body, list):
            self.message = Message(body[0])
            for more in body[1:]:
                self.message._body.append(more)  # pylint: disable=protected-access
        elif body is None:
            raise ValueError("EventData cannot be None.")
        else:
            self.message = Message(body)
        self.message.annotations = {}
        self.message.application_properties = {}

    def __repr__(self):
        # type: () -> str
        # pylint: disable=bare-except
        try:
            body_str = self.body_as_str()
        except:
            body_str = "<read-error>"
        event_repr = "body='{}'".format(body_str)
        try:
            event_repr += ", properties={}".format(self.properties)
        except:
            event_repr += ", properties=<read-error>"
        try:
            event_repr += ", offset={}".format(self.offset)
        except:
            event_repr += ", offset=<read-error>"
        try:
            event_repr += ", sequence_number={}".format(self.sequence_number)
        except:
            event_repr += ", sequence_number=<read-error>"
        try:
            event_repr += ", partition_key={!r}".format(self.partition_key)
        except:
            event_repr += ", partition_key=<read-error>"
        try:
            event_repr += ", enqueued_time={!r}".format(self.enqueued_time)
        except:
            event_repr += ", enqueued_time=<read-error>"
        return "EventData({})".format(event_repr)

    def __str__(self):
        # type: () -> str
        try:
            body_str = self.body_as_str()
        except:  # pylint: disable=bare-except
            body_str = "<read-error>"
        event_str = "{{ body: '{}'".format(body_str)
        try:
            event_str += ", properties: {}".format(self.properties)
            if self.offset:
                event_str += ", offset: {}".format(self.offset)
            if self.sequence_number:
                event_str += ", sequence_number: {}".format(
                    self.sequence_number)
            if self.partition_key:
                event_str += ", partition_key={!r}".format(self.partition_key)
            if self.enqueued_time:
                event_str += ", enqueued_time={!r}".format(self.enqueued_time)
        except:  # pylint: disable=bare-except
            pass
        event_str += " }"
        return event_str

    @classmethod
    def _from_message(cls, message):
        # type: (Message) -> EventData
        """Internal use only.

        Creates an EventData object from a raw uamqp message.

        :param ~uamqp.Message message: A received uamqp message.
        :rtype: ~azure.eventhub.EventData
        """
        event_data = cls(body="")
        event_data.message = message
        return event_data

    def _encode_message(self):
        # type: () -> bytes
        return self.message.encode_message()

    @property
    def sequence_number(self):
        # type: () -> Optional[int]
        """The sequence number of the event.

        :rtype: int
        """
        return self.message.annotations.get(PROP_SEQ_NUMBER, None)

    @property
    def offset(self):
        # type: () -> Optional[str]
        """The offset of the event.

        :rtype: str
        """
        try:
            return self.message.annotations[PROP_OFFSET].decode("UTF-8")
        except (KeyError, AttributeError):
            return None

    @property
    def enqueued_time(self):
        # type: () -> Optional[datetime.datetime]
        """The enqueued timestamp of the event.

        :rtype: datetime.datetime
        """
        timestamp = self.message.annotations.get(PROP_TIMESTAMP, None)
        if timestamp:
            return utc_from_timestamp(float(timestamp) / 1000)
        return None

    @property
    def partition_key(self):
        # type: () -> Optional[bytes]
        """The partition key of the event.

        :rtype: bytes
        """
        try:
            return self.message.annotations[PROP_PARTITION_KEY_AMQP_SYMBOL]
        except KeyError:
            return self.message.annotations.get(PROP_PARTITION_KEY, None)

    @property
    def properties(self):
        # type: () -> Dict[Union[str, bytes], Any]
        """Application-defined properties on the event.

        :rtype: dict
        """
        return self.message.application_properties

    @properties.setter
    def properties(self, value):
        # type: (Dict[Union[str, bytes], Any]) -> None
        """Application-defined properties on the event.

        :param dict value: The application properties for the EventData.
        """
        properties = None if value is None else dict(value)
        self.message.application_properties = properties

    @property
    def system_properties(self):
        # type: () -> Dict[bytes, Any]
        """Metadata set by the Event Hubs Service associated with the event.

        An EventData could have some or all of the following meta data depending on the source
        of the event data.

            - b"x-opt-sequence-number" (int)
            - b"x-opt-offset" (bytes)
            - b"x-opt-partition-key" (bytes)
            - b"x-opt-enqueued-time" (int)
            - b"message-id" (bytes)
            - b"user-id" (bytes)
            - b"to" (bytes)
            - b"subject" (bytes)
            - b"reply-to" (bytes)
            - b"correlation-id" (bytes)
            - b"content-type" (bytes)
            - b"content-encoding" (bytes)
            - b"absolute-expiry-time" (int)
            - b"creation-time" (int)
            - b"group-id" (bytes)
            - b"group-sequence" (bytes)
            - b"reply-to-group-id" (bytes)

        :rtype: dict
        """

        if self._sys_properties is None:
            self._sys_properties = {}
            if self.message.properties:
                for key, prop_name in _SYS_PROP_KEYS_TO_MSG_PROPERTIES:
                    value = getattr(self.message.properties, prop_name, None)
                    if value:
                        self._sys_properties[key] = value
            self._sys_properties.update(self.message.annotations)
        return self._sys_properties

    @property
    def body(self):
        # type: () -> Union[bytes, Iterable[bytes]]
        """The content of the event.

        :rtype: bytes or Generator[bytes]
        """
        try:
            return self.message.get_data()
        except TypeError:
            raise ValueError("Event content empty.")

    def body_as_str(self, encoding="UTF-8"):
        # type: (str) -> str
        """The content of the event as a string, if the data is of a compatible type.

        :param encoding: The encoding to use for decoding event data.
         Default is 'UTF-8'
        :rtype: str
        """
        data = self.body
        try:
            return "".join(
                b.decode(encoding) for b in cast(Iterable[bytes], data))
        except TypeError:
            return six.text_type(data)
        except:  # pylint: disable=bare-except
            pass
        try:
            return cast(bytes, data).decode(encoding)
        except Exception as e:
            raise TypeError(
                "Message data is not compatible with string type: {}".format(
                    e))

    def body_as_json(self, encoding="UTF-8"):
        # type: (str) -> Dict[str, Any]
        """The content of the event loaded as a JSON object, if the data is compatible.

        :param encoding: The encoding to use for decoding event data.
         Default is 'UTF-8'
        :rtype: dict
        """
        data_str = self.body_as_str(encoding=encoding)
        try:
            return json.loads(data_str)
        except Exception as e:
            raise TypeError(
                "Event data is not compatible with JSON type: {}".format(e))
Ejemplo n.º 2
0
class EventData(object):
    """The EventData class is a container for event content.

    :param body: The data to send in a single message. body can be type of str or bytes.
    :type body: str or bytes

    .. admonition:: Example:

        .. literalinclude:: ../samples/sync_samples/sample_code_eventhub.py
            :start-after: [START create_event_data]
            :end-before: [END create_event_data]
            :language: python
            :dedent: 4
            :caption: Create instances of EventData

    """

    def __init__(self, body=None):
        self._last_enqueued_event_properties = {}
        if body and isinstance(body, list):
            self.message = Message(body[0])
            for more in body[1:]:
                self.message._body.append(more)  # pylint: disable=protected-access
        elif body is None:
            raise ValueError("EventData cannot be None.")
        else:
            self.message = Message(body)
        self.message.annotations = {}
        self.message.application_properties = {}

    def __str__(self):
        try:
            body = self.body_as_str()
        except:  # pylint: disable=bare-except
            body = "<read-error>"
        message_as_dict = {
            'body': body,
            'application_properties': str(self.properties)
        }
        try:
            if self.sequence_number:
                message_as_dict['sequence_number'] = str(self.sequence_number)
            if self.offset:
                message_as_dict['offset'] = str(self.offset)
            if self.enqueued_time:
                message_as_dict['enqueued_time'] = str(self.enqueued_time)
            if self.partition_key:
                message_as_dict['partition_key'] = str(self.partition_key)
        except:  # pylint: disable=bare-except
            pass
        return str(message_as_dict)

    @classmethod
    def _from_message(cls, message):
        """Internal use only.

        Creates an EventData object from a raw uamqp message.

        :param ~uamqp.Message message: A received uamqp message.
        :rtype: ~azure.eventhub.EventData
        """
        event_data = cls(body='')
        event_data.message = message
        return event_data

    def _encode_message(self):
        return self.message.encode_message()

    @property
    def sequence_number(self):
        """The sequence number of the event.

        :rtype: int or long
        """
        return self.message.annotations.get(PROP_SEQ_NUMBER, None)

    @property
    def offset(self):
        """The offset of the event.

        :rtype: str
        """
        try:
            return self.message.annotations[PROP_OFFSET].decode('UTF-8')
        except (KeyError, AttributeError):
            return None

    @property
    def enqueued_time(self):
        """The enqueued timestamp of the event.

        :rtype: datetime.datetime
        """
        timestamp = self.message.annotations.get(PROP_TIMESTAMP, None)
        if timestamp:
            return utc_from_timestamp(float(timestamp)/1000)
        return None

    @property
    def partition_key(self):
        """The partition key of the event.

        :rtype: bytes
        """
        try:
            return self.message.annotations[PROP_PARTITION_KEY_AMQP_SYMBOL]
        except KeyError:
            return self.message.annotations.get(PROP_PARTITION_KEY, None)

    @property
    def properties(self):
        """Application-defined properties on the event.

        :rtype: dict
        """
        return self.message.application_properties

    @properties.setter
    def properties(self, value):
        """Application-defined properties on the event.

        :param dict value: The application properties for the EventData.
        """
        properties = None if value is None else dict(value)
        self.message.application_properties = properties

    @property
    def system_properties(self):
        """Metadata set by the Event Hubs Service associated with the event

        :rtype: dict
        """
        return self.message.annotations

    @property
    def body(self):
        """The content of the event.

        :rtype: bytes or Generator[bytes]
        """
        try:
            return self.message.get_data()
        except TypeError:
            raise ValueError("Event content empty.")

    def body_as_str(self, encoding='UTF-8'):
        """The content of the event as a string, if the data is of a compatible type.

        :param encoding: The encoding to use for decoding event data.
         Default is 'UTF-8'
        :rtype: str
        """
        data = self.body
        try:
            return "".join(b.decode(encoding) for b in data)
        except TypeError:
            return six.text_type(data)
        except:  # pylint: disable=bare-except
            pass
        try:
            return data.decode(encoding)
        except Exception as e:
            raise TypeError("Message data is not compatible with string type: {}".format(e))

    def body_as_json(self, encoding='UTF-8'):
        """The content of the event loaded as a JSON object, if the data is compatible.

        :param encoding: The encoding to use for decoding event data.
         Default is 'UTF-8'
        :rtype: dict
        """
        data_str = self.body_as_str(encoding=encoding)
        try:
            return json.loads(data_str)
        except Exception as e:
            raise TypeError("Event data is not compatible with JSON type: {}".format(e))

    @property
    def application_properties(self):
        # TODO: This method is for the purpose of livetest, because uamqp v.1.2.4 hasn't been released
        # The gather() in uamqp.message of v1.2.3 depends on application_properties attribute,
        # the livetest would all break if removing this property.
        # It should be removed after uamqp v.1.2.4 is released
        return self.properties

    def encode_message(self):
        # TODO: This method is for the purpose of livetest, because uamqp v.1.2.4 hasn't been released
        # The gather() in uamqp.message of v1.2.3 depends on encode_message method,
        # the livetest would all break if removing this method.
        # It should be removed after uamqp v.1.2.4 is released
        return self._encode_message()
Ejemplo n.º 3
0
class EventData(object):
    """
    The EventData class is a holder of event content.

    :param body: The data to send in a single message. body can be type of str or bytes.
    :type body: str or bytes

    .. admonition:: Example:

        .. literalinclude:: ../samples/sync_samples/sample_code_eventhub.py
            :start-after: [START create_event_data]
            :end-before: [END create_event_data]
            :language: python
            :dedent: 4
            :caption: Create instances of EventData

    """
    def __init__(self, body=None):
        self._last_enqueued_event_properties = {}
        if body and isinstance(body, list):
            self.message = Message(body[0])
            for more in body[1:]:
                self.message._body.append(more)  # pylint: disable=protected-access
        elif body is None:
            raise ValueError("EventData cannot be None.")
        else:
            self.message = Message(body)
        self.message.annotations = {}
        self.message.application_properties = {}

    def __str__(self):
        try:
            body = self.body_as_str()
        except:  # pylint: disable=bare-except
            body = "<read-error>"
        message_as_dict = {
            'body': body,
            'application_properties': str(self.properties)
        }
        try:
            if self.sequence_number:
                message_as_dict['sequence_number'] = str(self.sequence_number)
            if self.offset:
                message_as_dict['offset'] = str(self.offset)
            if self.enqueued_time:
                message_as_dict['enqueued_time'] = str(self.enqueued_time)
            if self.partition_key:
                message_as_dict['partition_key'] = str(self.partition_key)
        except:  # pylint: disable=bare-except
            pass
        return str(message_as_dict)

    @classmethod
    def _from_message(cls, message):
        """Internal use only.

        Creates an EventData object from a raw uamqp message.

        :param ~uamqp.Message message: A received uamqp message.
        :rtype: ~azure.eventhub.EventData
        """
        event_data = cls(body='')
        event_data.message = message
        return event_data

    def _get_last_enqueued_event_properties(self):
        """Extracts the last enqueued event in from the received event delivery annotations.

        :rtype: Dict[str, Any]
        """
        if self._last_enqueued_event_properties:
            return self._last_enqueued_event_properties

        if self.message.delivery_annotations:
            sequence_number = self.message.delivery_annotations.get(
                PROP_LAST_ENQUEUED_SEQUENCE_NUMBER, None)
            enqueued_time_stamp = self.message.delivery_annotations.get(
                PROP_LAST_ENQUEUED_TIME_UTC, None)
            if enqueued_time_stamp:
                enqueued_time_stamp = utc_from_timestamp(
                    float(enqueued_time_stamp) / 1000)
            retrieval_time_stamp = self.message.delivery_annotations.get(
                PROP_RUNTIME_INFO_RETRIEVAL_TIME_UTC, None)
            if retrieval_time_stamp:
                retrieval_time_stamp = utc_from_timestamp(
                    float(retrieval_time_stamp) / 1000)
            offset_bytes = self.message.delivery_annotations.get(
                PROP_LAST_ENQUEUED_OFFSET, None)
            offset = offset_bytes.decode('UTF-8') if offset_bytes else None
            self._last_enqueued_event_properties = {
                "sequence_number": sequence_number,
                "offset": offset,
                "enqueued_time": enqueued_time_stamp,
                "retrieval_time": retrieval_time_stamp
            }
            return self._last_enqueued_event_properties

        return None

    @property
    def sequence_number(self):
        """
        The sequence number of the event data object.

        :rtype: int or long
        """
        return self.message.annotations.get(PROP_SEQ_NUMBER, None)

    @property
    def offset(self):
        """
        The offset of the event data object.

        :rtype: str
        """
        try:
            return self.message.annotations[PROP_OFFSET].decode('UTF-8')
        except (KeyError, AttributeError):
            return None

    @property
    def enqueued_time(self):
        """
        The enqueued timestamp of the event data object.

        :rtype: datetime.datetime
        """
        timestamp = self.message.annotations.get(PROP_TIMESTAMP, None)
        if timestamp:
            return utc_from_timestamp(float(timestamp) / 1000)
        return None

    @property
    def partition_key(self):
        """
        The partition key of the event data object.

        :rtype: bytes
        """
        try:
            return self.message.annotations[PROP_PARTITION_KEY_AMQP_SYMBOL]
        except KeyError:
            return self.message.annotations.get(PROP_PARTITION_KEY, None)

    @property
    def properties(self):
        """
        Application defined properties on the message.

        :rtype: dict
        """
        return self.message.application_properties

    @properties.setter
    def properties(self, value):
        """
        Application defined properties on the message.

        :param dict value: The application properties for the EventData.
        """
        properties = None if value is None else dict(value)
        self.message.application_properties = properties

    @property
    def system_properties(self):
        """
        Metadata set by the Event Hubs Service associated with the EventData

        :rtype: dict
        """
        return self.message.annotations

    @property
    def body(self):
        """
        The body of the event data object.

        :rtype: bytes or Generator[bytes]
        """
        try:
            return self.message.get_data()
        except TypeError:
            raise ValueError("Message data empty.")

    @property
    def last_enqueued_event_properties(self):
        """
        The latest enqueued event information. This property will be updated each time an event is received when
        the receiver is created with `track_last_enqueued_event_properties` being `True`.
        The dict includes following information of the partition:

            - `sequence_number`
            - `offset`
            - `enqueued_time`
            - `retrieval_time`

        :rtype: dict or None
        """
        return self._get_last_enqueued_event_properties()

    def body_as_str(self, encoding='UTF-8'):
        """
        The body of the event data as a string if the data is of a
        compatible type.

        :param encoding: The encoding to use for decoding message data.
         Default is 'UTF-8'
        :rtype: str
        """
        data = self.body
        try:
            return "".join(b.decode(encoding) for b in data)
        except TypeError:
            return six.text_type(data)
        except:  # pylint: disable=bare-except
            pass
        try:
            return data.decode(encoding)
        except Exception as e:
            raise TypeError(
                "Message data is not compatible with string type: {}".format(
                    e))

    def body_as_json(self, encoding='UTF-8'):
        """
        The body of the event loaded as a JSON object is the data is compatible.

        :param encoding: The encoding to use for decoding message data.
         Default is 'UTF-8'
        :rtype: dict
        """
        data_str = self.body_as_str(encoding=encoding)
        try:
            return json.loads(data_str)
        except Exception as e:
            raise TypeError(
                "Event data is not compatible with JSON type: {}".format(e))

    def encode_message(self):
        return self.message.encode_message()
Ejemplo n.º 4
0
class EventData(object):
    """
    The EventData class is a holder of event content.

    Example:
        .. literalinclude:: ../examples/test_examples_eventhub.py
            :start-after: [START create_event_data]
            :end-before: [END create_event_data]
            :language: python
            :dedent: 4
            :caption: Create instances of EventData

    """

    PROP_SEQ_NUMBER = b"x-opt-sequence-number"
    PROP_OFFSET = b"x-opt-offset"
    PROP_PARTITION_KEY = b"x-opt-partition-key"
    PROP_PARTITION_KEY_AMQP_SYMBOL = types.AMQPSymbol(PROP_PARTITION_KEY)
    PROP_TIMESTAMP = b"x-opt-enqueued-time"
    PROP_LAST_ENQUEUED_SEQUENCE_NUMBER = b"last_enqueued_sequence_number"
    PROP_LAST_ENQUEUED_OFFSET = b"last_enqueued_offset"
    PROP_LAST_ENQUEUED_TIME_UTC = b"last_enqueued_time_utc"
    PROP_RUNTIME_INFO_RETRIEVAL_TIME_UTC = b"runtime_info_retrieval_time_utc"

    def __init__(self, body=None):
        """
        Initialize EventData.

        :param body: The data to send in a single message.
        :type body: str, bytes or list
        """

        self._last_enqueued_event_properties = {}
        if body and isinstance(body, list):
            self.message = Message(body[0])
            for more in body[1:]:
                self.message._body.append(more)  # pylint: disable=protected-access
        elif body is None:
            raise ValueError("EventData cannot be None.")
        else:
            self.message = Message(body)
        self.message.annotations = {}

    def __str__(self):
        dic = {
            'body': self.body_as_str(),
            'application_properties': str(self.application_properties)
        }

        if self.sequence_number:
            dic['sequence_number'] = str(self.sequence_number)
        if self.offset:
            dic['offset'] = str(self.offset)
        if self.enqueued_time:
            dic['enqueued_time'] = str(self.enqueued_time)
        if self.partition_key:
            dic['partition_key'] = str(self.partition_key)
        return str(dic)

    def _set_partition_key(self, value):
        """
        Set the partition key of the event data object.

        :param value: The partition key to set.
        :type value: str or bytes
        """
        annotations = dict(self.message.annotations)
        annotations[EventData.PROP_PARTITION_KEY_AMQP_SYMBOL] = value
        header = MessageHeader()
        header.durable = True
        self.message.annotations = annotations
        self.message.header = header

    def _trace_message(self, parent_span=None):
        """Add tracing information to this message.

        Will open and close a "Azure.EventHubs.message" span, and
        add the "DiagnosticId" as app properties of the message.
        """
        span_impl_type = settings.tracing_implementation(
        )  # type: Type[AbstractSpan]
        if span_impl_type is not None:
            current_span = parent_span or span_impl_type(
                span_impl_type.get_current_span())
            message_span = current_span.span(name="Azure.EventHubs.message")
            message_span.start()
            app_prop = dict(self.application_properties
                            ) if self.application_properties else dict()
            app_prop.setdefault(
                b"Diagnostic-Id",
                message_span.get_trace_parent().encode('ascii'))
            self.application_properties = app_prop
            message_span.finish()

    def _trace_link_message(self, parent_span=None):
        """Link the current message to current span.

        Will extract DiagnosticId if available.
        """
        span_impl_type = settings.tracing_implementation(
        )  # type: Type[AbstractSpan]
        if span_impl_type is not None:
            current_span = parent_span or span_impl_type(
                span_impl_type.get_current_span())
            if current_span and self.application_properties:
                traceparent = self.application_properties.get(
                    b"Diagnostic-Id", "").decode('ascii')
                if traceparent:
                    current_span.link(traceparent)

    def _get_last_enqueued_event_properties(self):
        if self._last_enqueued_event_properties:
            return self._last_enqueued_event_properties

        if self.message.delivery_annotations:
            enqueued_time_stamp = \
                self.message.delivery_annotations.get(EventData.PROP_LAST_ENQUEUED_TIME_UTC, None)
            retrieval_time_stamp = \
                self.message.delivery_annotations.get(EventData.PROP_RUNTIME_INFO_RETRIEVAL_TIME_UTC, None)

            self._last_enqueued_event_properties = {
                "sequence_number":
                self.message.delivery_annotations.get(
                    EventData.PROP_LAST_ENQUEUED_SEQUENCE_NUMBER, None),
                "offset":
                self.message.delivery_annotations.get(
                    EventData.PROP_LAST_ENQUEUED_OFFSET, None),
                "enqueued_time":
                datetime.datetime.utcfromtimestamp(
                    float(enqueued_time_stamp) /
                    1000) if enqueued_time_stamp else None,
                "retrieval_time":
                datetime.datetime.utcfromtimestamp(
                    float(retrieval_time_stamp) /
                    1000) if retrieval_time_stamp else None
            }
            return self._last_enqueued_event_properties

        return None

    @classmethod
    def _from_message(cls, message):
        # pylint:disable=protected-access
        event_data = cls(body='')
        event_data.message = message
        return event_data

    @property
    def sequence_number(self):
        """
        The sequence number of the event data object.

        :rtype: int or long
        """
        return self.message.annotations.get(EventData.PROP_SEQ_NUMBER, None)

    @property
    def offset(self):
        """
        The offset of the event data object.

        :rtype: str
        """
        try:
            return self.message.annotations[EventData.PROP_OFFSET].decode(
                'UTF-8')
        except (KeyError, AttributeError):
            return None

    @property
    def enqueued_time(self):
        """
        The enqueued timestamp of the event data object.

        :rtype: datetime.datetime
        """
        timestamp = self.message.annotations.get(EventData.PROP_TIMESTAMP,
                                                 None)
        if timestamp:
            return datetime.datetime.utcfromtimestamp(float(timestamp) / 1000)
        return None

    @property
    def partition_key(self):
        """
        The partition key of the event data object.

        :rtype: bytes
        """
        try:
            return self.message.annotations[
                EventData.PROP_PARTITION_KEY_AMQP_SYMBOL]
        except KeyError:
            return self.message.annotations.get(EventData.PROP_PARTITION_KEY,
                                                None)

    @property
    def application_properties(self):
        """
        Application defined properties on the message.

        :rtype: dict
        """
        return self.message.application_properties

    @application_properties.setter
    def application_properties(self, value):
        """
        Application defined properties on the message.

        :param value: The application properties for the EventData.
        :type value: dict
        """
        properties = None if value is None else dict(value)
        self.message.application_properties = properties

    @property
    def system_properties(self):
        """
        Metadata set by the Event Hubs Service associated with the EventData

        :rtype: dict
        """
        return self.message.annotations

    @property
    def body(self):
        """
        The body of the event data object.

        :rtype: bytes or Generator[bytes]
        """
        try:
            return self.message.get_data()
        except TypeError:
            raise ValueError("Message data empty.")

    def body_as_str(self, encoding='UTF-8'):
        """
        The body of the event data as a string if the data is of a
        compatible type.

        :param encoding: The encoding to use for decoding message data.
         Default is 'UTF-8'
        :rtype: str or unicode
        """
        data = self.body
        try:
            return "".join(b.decode(encoding) for b in data)
        except TypeError:
            return six.text_type(data)
        except:  # pylint: disable=bare-except
            pass
        try:
            return data.decode(encoding)
        except Exception as e:
            raise TypeError(
                "Message data is not compatible with string type: {}".format(
                    e))

    def body_as_json(self, encoding='UTF-8'):
        """
        The body of the event loaded as a JSON object is the data is compatible.

        :param encoding: The encoding to use for decoding message data.
         Default is 'UTF-8'
        :rtype: dict
        """
        data_str = self.body_as_str(encoding=encoding)
        try:
            return json.loads(data_str)
        except Exception as e:
            raise TypeError(
                "Event data is not compatible with JSON type: {}".format(e))

    def encode_message(self):
        return self.message.encode_message()
Ejemplo n.º 5
0
class EventData(object):
    """
    The EventData class is a holder of event content.

    Example:
        .. literalinclude:: ../examples/test_examples_eventhub.py
            :start-after: [START create_event_data]
            :end-before: [END create_event_data]
            :language: python
            :dedent: 4
            :caption: Create instances of EventData

    """

    PROP_SEQ_NUMBER = b"x-opt-sequence-number"
    PROP_OFFSET = b"x-opt-offset"
    PROP_PARTITION_KEY = b"x-opt-partition-key"
    PROP_PARTITION_KEY_AMQP_SYMBOL = types.AMQPSymbol(PROP_PARTITION_KEY)
    PROP_TIMESTAMP = b"x-opt-enqueued-time"
    PROP_DEVICE_ID = b"iothub-connection-device-id"

    def __init__(self, body=None, to_device=None):
        """
        Initialize EventData.

        :param body: The data to send in a single message.
        :type body: str, bytes or list
        :param to_device: An IoT device to route to.
        :type to_device: str
        """

        self._annotations = {}
        self._app_properties = {}
        self._msg_properties = MessageProperties()
        if to_device:
            self._msg_properties.to = '/devices/{}/messages/devicebound'.format(
                to_device)
        if body and isinstance(body, list):
            self.message = Message(body[0], properties=self._msg_properties)
            for more in body[1:]:
                self.message._body.append(more)  # pylint: disable=protected-access
        elif body is None:
            raise ValueError("EventData cannot be None.")
        else:
            self.message = Message(body, properties=self._msg_properties)

    def __str__(self):
        dic = {
            'body': self.body_as_str(),
            'application_properties': str(self.application_properties)
        }

        if self.sequence_number:
            dic['sequence_number'] = str(self.sequence_number)
        if self.offset:
            dic['offset'] = str(self.offset)
        if self.enqueued_time:
            dic['enqueued_time'] = str(self.enqueued_time)
        if self.device_id:
            dic['device_id'] = str(self.device_id)
        if self.partition_key:
            dic['partition_key'] = str(self.partition_key)
        return str(dic)

    def _set_partition_key(self, value):
        """
        Set the partition key of the event data object.

        :param value: The partition key to set.
        :type value: str or bytes
        """
        annotations = dict(self._annotations)
        annotations[EventData.PROP_PARTITION_KEY_AMQP_SYMBOL] = value
        header = MessageHeader()
        header.durable = True
        self.message.annotations = annotations
        self.message.header = header
        self._annotations = annotations

    @staticmethod
    def _from_message(message):
        event_data = EventData(body='')
        event_data.message = message
        event_data._msg_properties = message.properties  # pylint:disable=protected-access
        event_data._annotations = message.annotations  # pylint:disable=protected-access
        event_data._app_properties = message.application_properties  # pylint:disable=protected-access
        return event_data

    @property
    def sequence_number(self):
        """
        The sequence number of the event data object.

        :rtype: int or long
        """
        return self._annotations.get(EventData.PROP_SEQ_NUMBER, None)

    @property
    def offset(self):
        """
        The offset of the event data object.

        :rtype: str
        """
        try:
            return self._annotations[EventData.PROP_OFFSET].decode('UTF-8')
        except (KeyError, AttributeError):
            return None

    @property
    def enqueued_time(self):
        """
        The enqueued timestamp of the event data object.

        :rtype: datetime.datetime
        """
        timestamp = self._annotations.get(EventData.PROP_TIMESTAMP, None)
        if timestamp:
            return datetime.datetime.utcfromtimestamp(float(timestamp) / 1000)
        return None

    @property
    def device_id(self):
        """
        The device ID of the event data object. This is only used for
        IoT Hub implementations.

        :rtype: bytes
        """
        return self._annotations.get(EventData.PROP_DEVICE_ID, None)

    @property
    def partition_key(self):
        """
        The partition key of the event data object.

        :rtype: bytes
        """
        try:
            return self._annotations[EventData.PROP_PARTITION_KEY_AMQP_SYMBOL]
        except KeyError:
            return self._annotations.get(EventData.PROP_PARTITION_KEY, None)

    @property
    def application_properties(self):
        """
        Application defined properties on the message.

        :rtype: dict
        """
        return self._app_properties

    @application_properties.setter
    def application_properties(self, value):
        """
        Application defined properties on the message.

        :param value: The application properties for the EventData.
        :type value: dict
        """
        self._app_properties = value
        properties = None if value is None else dict(self._app_properties)
        self.message.application_properties = properties

    @property
    def system_properties(self):
        """
        Metadata set by the Event Hubs Service associated with the EventData

        :rtype: dict
        """
        return self._annotations

    @property
    def body(self):
        """
        The body of the event data object.

        :rtype: bytes or Generator[bytes]
        """
        try:
            return self.message.get_data()
        except TypeError:
            raise ValueError("Message data empty.")

    def body_as_str(self, encoding='UTF-8'):
        """
        The body of the event data as a string if the data is of a
        compatible type.

        :param encoding: The encoding to use for decoding message data.
         Default is 'UTF-8'
        :rtype: str or unicode
        """
        data = self.body
        try:
            return "".join(b.decode(encoding) for b in data)
        except TypeError:
            return six.text_type(data)
        except:  # pylint: disable=bare-except
            pass
        try:
            return data.decode(encoding)
        except Exception as e:
            raise TypeError(
                "Message data is not compatible with string type: {}".format(
                    e))

    def body_as_json(self, encoding='UTF-8'):
        """
        The body of the event loaded as a JSON object is the data is compatible.

        :param encoding: The encoding to use for decoding message data.
         Default is 'UTF-8'
        :rtype: dict
        """
        data_str = self.body_as_str(encoding=encoding)
        try:
            return json.loads(data_str)
        except Exception as e:
            raise TypeError(
                "Event data is not compatible with JSON type: {}".format(e))

    def encode_message(self):
        return self.message.encode_message()