Ejemplo n.º 1
0
    def __init__(self, body=None, batch=None, to_device=None, message=None):
        """
        Initialize EventData.

        :param body: The data to send in a single message.
        :type body: str, bytes or list
        :param batch: A data generator to send batched messages.
        :type batch: Generator
        :param to_device: An IoT device to route to.
        :type to_device: str
        :param message: The received message.
        :type message: ~uamqp.message.Message
        """
        self._partition_key = types.AMQPSymbol(EventData.PROP_PARTITION_KEY)
        self._annotations = {}
        self._app_properties = {}
        self.msg_properties = MessageProperties()
        if to_device:
            self.msg_properties.to = '/devices/{}/messages/devicebound'.format(to_device)
        if batch:
            self.message = BatchMessage(data=batch, multi_messages=True, properties=self.msg_properties)
        elif message:
            self.message = message
            self.msg_properties = message.properties
            self._annotations = message.annotations
            self._app_properties = message.application_properties
        else:
            if isinstance(body, list) and body:
                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)
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):
        # type: (Union[str, bytes, List[AnyStr]]) -> None
        self._last_enqueued_event_properties = {}  # type: Dict[str, 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 or long
        """
        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[Union[str, bytes], Any]
        """Metadata set by the Event Hubs Service associated with the event

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

    @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.º 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.application_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 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 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.
    Acts as a wrapper to an uamqp.message.Message object.

    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_TIMESTAMP = b"x-opt-enqueued-time"
    PROP_DEVICE_ID = b"iothub-connection-device-id"

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

        :param body: The data to send in a single message.
        :type body: str, bytes or list
        :param batch: A data generator to send batched messages.
        :type batch: Generator
        :param to_device: An IoT device to route to.
        :type to_device: str
        :param message: The received message.
        :type message: ~uamqp.message.Message
        """
        self._partition_key = types.AMQPSymbol(EventData.PROP_PARTITION_KEY)
        self._annotations = {}
        self._app_properties = {}
        self.msg_properties = MessageProperties()
        if to_device:
            self.msg_properties.to = '/devices/{}/messages/devicebound'.format(to_device)
        if batch:
            self.message = BatchMessage(data=batch, multi_messages=True, properties=self.msg_properties)
        elif message:
            self.message = message
            self.msg_properties = message.properties
            self._annotations = message.annotations
            self._app_properties = message.application_properties
        else:
            if isinstance(body, list) and body:
                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)

    @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: ~azure.eventhub.common.Offset
        """
        try:
            return Offset(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[self._partition_key]
        except KeyError:
            return self._annotations.get(EventData.PROP_PARTITION_KEY, None)

    @partition_key.setter
    def 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[self._partition_key] = value
        header = MessageHeader()
        header.durable = True
        self.message.annotations = annotations
        self.message.header = header
        self._annotations = annotations

    @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 = dict(self._app_properties)
        self.message.application_properties = properties

    @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))
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()
Ejemplo n.º 6
0
class EventData(object):
    """
    The EventData class is a holder of event content.
    Acts as a wrapper to an ~uamqp.message.Message object.
    """

    PROP_SEQ_NUMBER = b"x-opt-sequence-number"
    PROP_OFFSET = b"x-opt-offset"
    PROP_PARTITION_KEY = b"x-opt-partition-key"
    PROP_TIMESTAMP = b"x-opt-enqueued-time"
    PROP_DEVICE_ID = b"iothub-connection-device-id"

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

        :param body: The data to send in a single message.
        :type body: str, bytes or list
        :param batch: A data generator to send batched messages.
        :type batch: Generator
        :param message: The received message.
        :type message: ~uamqp.message.Message
        """
        self._partition_key = types.AMQPSymbol(EventData.PROP_PARTITION_KEY)
        self._annotations = {}
        self._app_properties = {}
        self.msg_properties = MessageProperties()
        if to_device:
            self.msg_properties.to = '/devices/{}/messages/devicebound'.format(
                to_device)
        if batch:
            self.message = BatchMessage(data=batch,
                                        multi_messages=True,
                                        properties=self.msg_properties)
        elif message:
            self.message = message
            self.msg_properties = message.properties
            self._annotations = message.annotations
            self._app_properties = message.application_properties
        else:
            if isinstance(body, list) and body:
                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)

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

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

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

        :rtype: int
        """
        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.fromtimestamp(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[self._partition_key]
        except KeyError:
            return self._annotations.get(EventData.PROP_PARTITION_KEY, None)

    @partition_key.setter
    def 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[self._partition_key] = value
        header = MessageHeader()
        header.durable = True
        self.message.annotations = annotations
        self.message.header = header
        self._annotations = annotations

    @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 = dict(self._app_properties)
        self.message.application_properties = properties

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

        :rtype: bytes or generator[bytes]
        """
        return self.message.get_data()
Ejemplo n.º 7
0
 async def _management_request_async(self, mgmt_msg: Message, op_type: bytes) -> Any:
     retried_times = 0
     last_exception = None
     while retried_times <= self._config.max_retries:
         mgmt_auth = await self._create_auth_async()
         mgmt_client = AMQPClientAsync(
             self._mgmt_target, auth=mgmt_auth, debug=self._config.network_tracing
         )
         try:
             conn = await self._conn_manager_async.get_connection(
                 self._address.hostname, mgmt_auth
             )
             mgmt_msg.application_properties["security_token"] = mgmt_auth.token
             await mgmt_client.open_async(connection=conn)
             response = await mgmt_client.mgmt_request_async(
                 mgmt_msg,
                 constants.READ_OPERATION,
                 op_type=op_type,
                 status_code_field=MGMT_STATUS_CODE,
                 description_fields=MGMT_STATUS_DESC,
             )
             status_code = int(response.application_properties[MGMT_STATUS_CODE])
             description = response.application_properties.get(
                 MGMT_STATUS_DESC
             )  # type: Optional[Union[str, bytes]]
             if description and isinstance(description, six.binary_type):
                 description = description.decode("utf-8")
             if status_code < 400:
                 return response
             if status_code in [401]:
                 raise errors.AuthenticationException(
                     "Management authentication failed. Status code: {}, Description: {!r}".format(
                         status_code, description
                     )
                 )
             if status_code in [404]:
                 raise ConnectError(
                     "Management connection failed. Status code: {}, Description: {!r}".format(
                         status_code, description
                     )
                 )
             raise errors.AMQPConnectionError(
                 "Management request error. Status code: {}, Description: {!r}".format(
                     status_code, description
                 )
             )
         except asyncio.CancelledError:  # pylint: disable=try-except-raise
             raise
         except Exception as exception:  # pylint:disable=broad-except
             last_exception = await _handle_exception(exception, self)
             await self._backoff_async(
                 retried_times=retried_times, last_exception=last_exception
             )
             retried_times += 1
             if retried_times > self._config.max_retries:
                 _LOGGER.info(
                     "%r returns an exception %r", self._container_id, last_exception
                 )
                 raise last_exception
         finally:
             await mgmt_client.close_async()
Ejemplo n.º 8
0
class EventData(object):
    """
    The EventData class is a holder of event content.
    Acts as a wrapper to an uamqp.message.Message object.

    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_TIMESTAMP = b"x-opt-enqueued-time"
    PROP_DEVICE_ID = b"iothub-connection-device-id"

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

        :param body: The data to send in a single message.
        :type body: str, bytes or list
        :param batch: A data generator to send batched messages.
        :type batch: Generator
        :param to_device: An IoT device to route to.
        :type to_device: str
        :param message: The received message.
        :type message: ~uamqp.message.Message
        """
        self._partition_key = types.AMQPSymbol(EventData.PROP_PARTITION_KEY)
        self._annotations = {}
        self._app_properties = {}
        self.msg_properties = MessageProperties()
        if to_device:
            self.msg_properties.to = '/devices/{}/messages/devicebound'.format(
                to_device)
        if batch:
            self.message = BatchMessage(data=batch,
                                        multi_messages=True,
                                        properties=self.msg_properties)
        elif message:
            self.message = message
            self.msg_properties = message.properties
            self._annotations = message.annotations
            self._app_properties = message.application_properties
        else:
            if isinstance(body, list) and body:
                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)

    @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: ~azure.eventhub.common.Offset
        """
        try:
            return Offset(
                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[self._partition_key]
        except KeyError:
            return self._annotations.get(EventData.PROP_PARTITION_KEY, None)

    @partition_key.setter
    def 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[self._partition_key] = value
        header = MessageHeader()
        header.durable = True
        self.message.annotations = annotations
        self.message.header = header
        self._annotations = annotations

    @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 = dict(self._app_properties)
        self.message.application_properties = properties

    @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))
Ejemplo n.º 9
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)
            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:
            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":
                self.message.delivery_annotations.get(
                    EventData.PROP_LAST_ENQUEUED_TIME_UTC, None),
                "retrieval_time":
                self.message.delivery_annotations.get(
                    EventData.PROP_RUNTIME_INFO_RETRIEVAL_TIME_UTC, 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()