Beispiel #1
0
class Command(xso.XSO):
    TAG = (namespaces.xep0050_commands, "command")

    actions = xso.Child([Actions])

    notes = xso.ChildList([Note])

    action = xso.Attr(
        "action",
        type_=xso.EnumType(ActionType),
        default=ActionType.EXECUTE,
    )

    status = xso.Attr(
        "status",
        type_=xso.EnumType(CommandStatus),
        default=None,
    )

    sessionid = xso.Attr(
        "sessionid",
        default=None,
    )

    node = xso.Attr(
        "node",
    )

    payload = xso.ChildList([
        aioxmpp.forms.Data,
    ])

    def __init__(self, node, *,
                 action=ActionType.EXECUTE,
                 status=None,
                 sessionid=None,
                 payload=[],
                 notes=[],
                 actions=None):
        super().__init__()
        self.node = node
        self.action = action
        self.status = status
        self.sessionid = sessionid
        if not isinstance(payload, collections.abc.Iterable):
            self.payload[:] = [payload]
        else:
            self.payload[:] = payload
        self.notes[:] = notes
        self.actions = actions

    @property
    def first_payload(self):
        try:
            return self.payload[0]
        except IndexError:
            return
Beispiel #2
0
 def test_parse_works_with_actual_enum(self):
     e = xso.EnumType(self.SomeEnum, xso.Integer())
     for enum_value in self.SomeEnum:
         self.assertEqual(
             e.parse(str(enum_value.value)),
             enum_value,
         )
Beispiel #3
0
 def test_init_with_enum(self):
     e = xso.EnumType(self.SomeEnum)
     self.assertIs(e.enum_class, self.SomeEnum)
     self.assertIsInstance(
         e.nested_type,
         xso.String,
     )
Beispiel #4
0
 def test_format_works_with_actual_enums(self):
     e = xso.EnumType(self.SomeEnum, xso.Integer())
     for enum_value in self.SomeEnum:
         self.assertEqual(
             e.format(enum_value),
             str(enum_value.value),
         )
Beispiel #5
0
 def test_init_with_custom_nested_type(self):
     e = xso.EnumType(self.SomeEnum,
                      nested_type=unittest.mock.sentinel.nested_type)
     self.assertIs(e.enum_class, self.SomeEnum)
     self.assertIs(
         e.nested_type,
         unittest.mock.sentinel.nested_type,
     )
Beispiel #6
0
    def test_reject_non_Enum_values_on_coerce(self):
        wrong = [1, "10", 10.2, object()]

        e = xso.EnumType(self.SomeEnum)

        for thing in wrong:
            with self.assertRaises(TypeError):
                e.coerce(thing)
Beispiel #7
0
    def test_allow_unknown_can_be_turned_off(self):
        enum_class = self.SomeEnum
        e = xso.EnumType(
            enum_class,
            xso.Integer(),
            allow_unknown=False,
        )

        with self.assertRaisesRegex(ValueError, r"10 is not a valid SomeEnum"):
            e.parse(10)
Beispiel #8
0
    def test_allow_unknown_by_default(self):
        enum_class = self.SomeEnum
        e = xso.EnumType(
            enum_class,
            xso.Integer(),
        )

        value = e.parse("10")
        self.assertIsInstance(value, xso.Unknown)
        self.assertEqual(xso.Unknown(10), value)
Beispiel #9
0
    def test_accept_unknown_by_default(self):
        enum_class = self.SomeEnum
        e = xso.EnumType(
            enum_class,
            xso.Integer(),
        )

        value = e.coerce(xso.Unknown(10))
        self.assertIsInstance(value, xso.Unknown)
        self.assertEqual(xso.Unknown(10), value)
Beispiel #10
0
    def test_value_error_propagates(self):
        exc = ValueError()

        enum_class = unittest.mock.Mock()
        enum_class.side_effect = exc
        e = xso.EnumType(enum_class, allow_coerce=True)

        with self.assertRaises(ValueError) as ctx:
            e.coerce(unittest.mock.sentinel.value)

        self.assertIs(ctx.exception, exc)
Beispiel #11
0
    def test_format_works_with_unknown(self):
        enum_class = self.SomeEnum
        e = xso.EnumType(
            enum_class,
            xso.Integer(),
        )

        self.assertEqual(
            e.format(xso.Unknown(10)),
            "10",
        )
Beispiel #12
0
    def test_accept_unknown_can_be_turned_off(self):
        enum_class = self.SomeEnum
        e = xso.EnumType(
            enum_class,
            xso.Integer(),
            accept_unknown=False,
        )

        with self.assertRaisesRegex(TypeError,
                                    r"not a valid .* value: <Unknown: 10>"):
            e.coerce(xso.Unknown(10))
Beispiel #13
0
    def test_get_formatted_type_delegates_to_nested_type(self):
        nested_type = unittest.mock.Mock()
        e = xso.EnumType(
            unittest.mock.sentinel.enum_class,
            nested_type,
        )

        result = e.get_formatted_type()
        nested_type.get_formatted_type.assert_called_with()
        self.assertEqual(
            result,
            nested_type.get_formatted_type(),
        )
Beispiel #14
0
    def test_format_uses_enum_value_and_nested_type(self):
        enum_class = unittest.mock.Mock()
        enum_value = unittest.mock.Mock()
        nested_type = unittest.mock.Mock()
        e = xso.EnumType(enum_class, nested_type)

        result = e.format(enum_value)

        nested_type.format.assert_called_with(enum_value.value, )

        self.assertEqual(
            result,
            nested_type.format(),
        )
Beispiel #15
0
    def test_parse_uses_enum_and_nested_type(self):
        enum_class = unittest.mock.Mock()
        nested_type = unittest.mock.Mock()
        e = xso.EnumType(enum_class, nested_type)

        result = e.parse(unittest.mock.sentinel.value)

        nested_type.parse.assert_called_with(unittest.mock.sentinel.value, )

        enum_class.assert_called_with(nested_type.parse(), )

        self.assertEqual(
            result,
            enum_class(),
        )
Beispiel #16
0
class Actions(xso.XSO):
    TAG = (namespaces.xep0050_commands, "actions")

    next_is_allowed = xso.ChildFlag(
        (namespaces.xep0050_commands, "next"),
    )

    prev_is_allowed = xso.ChildFlag(
        (namespaces.xep0050_commands, "prev"),
    )

    complete_is_allowed = xso.ChildFlag(
        (namespaces.xep0050_commands, "complete"),
    )

    execute = xso.Attr(
        "execute",
        type_=xso.EnumType(ActionType),
        validator=xso.RestrictToSet({
            ActionType.NEXT,
            ActionType.PREV,
            ActionType.COMPLETE,
        }),
        default=None,
    )

    @property
    def allowed_actions(self):
        result = [ActionType.EXECUTE, ActionType.CANCEL]
        if self.prev_is_allowed:
            result.append(ActionType.PREV)
        if self.next_is_allowed:
            result.append(ActionType.NEXT)
        if self.complete_is_allowed:
            result.append(ActionType.COMPLETE)
        return frozenset(result)

    @allowed_actions.setter
    def allowed_actions(self, values):
        values = frozenset(values)
        if ActionType.EXECUTE not in values:
            raise ValueError("EXECUTE must always be allowed")
        if ActionType.CANCEL not in values:
            raise ValueError("CANCEL must always be allowed")
        self.prev_is_allowed = ActionType.PREV in values
        self.next_is_allowed = ActionType.NEXT in values
        self.complete_is_allowed = ActionType.COMPLETE in values
Beispiel #17
0
    def test_try_to_coerce_if_allow_coerce_is_set(self):
        enum_class = unittest.mock.Mock()
        e = xso.EnumType(
            enum_class,
            allow_coerce=True,
        )

        with warnings.catch_warnings() as w:
            result = e.coerce(unittest.mock.sentinel.value)

        enum_class.assert_called_with(unittest.mock.sentinel.value, )

        self.assertEqual(
            result,
            enum_class(),
        )

        self.assertFalse(w)
Beispiel #18
0
class Note(xso.XSO):
    TAG = (namespaces.xep0050_commands, "note")

    body = xso.Text(
        default=None,
    )

    type_ = xso.Attr(
        "type",
        type_=xso.EnumType(
            NoteType,
        ),
        default=NoteType.INFO,
    )

    def __init__(self, type_, body):
        super().__init__()
        self.type_ = type_
        self.body = body
Beispiel #19
0
    def test_deprecate_coerce_does_not_emit_warning_for_enum_value(self):
        enum_class = self.SomeEnum
        e = xso.EnumType(
            enum_class,
            allow_coerce=True,
            deprecate_coerce=True,
        )

        value = enum_class.X

        with contextlib.ExitStack() as stack:
            warn = stack.enter_context(unittest.mock.patch("warnings.warn", ))

            result = e.coerce(value)

        self.assertFalse(warn.mock_calls)

        self.assertIs(
            value,
            result,
        )
Beispiel #20
0
    def test_deprecate_coerce_custom_stacklevel(self):
        enum_class = self.SomeEnum
        e = xso.EnumType(
            enum_class,
            allow_coerce=True,
            deprecate_coerce=unittest.mock.sentinel.stacklevel,
        )

        with contextlib.ExitStack() as stack:
            warn = stack.enter_context(unittest.mock.patch("warnings.warn", ))

            result = e.coerce(1)

        warn.assert_called_with(
            "assignment of non-enum values to this descriptor is deprecated",
            DeprecationWarning,
            stacklevel=unittest.mock.sentinel.stacklevel)

        self.assertEqual(
            result,
            enum_class(1),
        )
Beispiel #21
0
class Data(AbstractItem):
    """
    A :xep:`4` ``x`` element, that is, a Data Form.

    :param type_: Initial value for the :attr:`type_` attribute.

    .. attribute:: type_

       The ``type`` attribute of the form, represented by one of the members of
       the :class:`DataType` enumeration.

    .. attribute:: title

       The (optional) title of the form. Either a :class:`str` or :data:`None`.

    .. attribute:: instructions

       A sequence of strings which represent the instructions elements on the
       form.

    .. attribute:: fields

       If the :class:`Data` is a form, this is a sequence of :class:`Field`
       elements which represent the fields to be filled in.

       This does not make sense on :attr:`.DataType.RESULT` typed objects.

    .. attribute:: items

       If the :class:`Data` is a table, this is a sequence of :class:`Item`
       instances which represent the table rows.

       This only makes sense on :attr:`.DataType.RESULT` typed objects.

    .. attribute:: reported

       If the :class:`Data` is a table, this is a :class:`Reported` object
       representing the table header.

       This only makes sense on :attr:`.DataType.RESULT` typed objects.
    """

    TAG = (namespaces.xep0004_data, "x")

    type_ = xso.Attr(
        "type",
        type_=xso.EnumType(DataType)
    )

    title = xso.ChildText(
        (namespaces.xep0004_data, "title"),
        default=None,
    )

    instructions = xso.ChildValueList(
        type_=InstructionsElement()
    )

    items = xso.ChildList([Item])

    reported = xso.Child([Reported], required=False)

    def __init__(self, type_):
        super().__init__()
        self.type_ = type_

    def _validate_result(self):
        if self.fields:
            raise ValueError("field in report result")

        fieldvars = {field.var for field in self.reported.fields}

        if not fieldvars:
            raise ValueError("empty report header")

        for item in self.items:
            itemvars = {field.var for field in item.fields}
            if itemvars != fieldvars:
                raise ValueError("field mismatch between row and header")

    def validate(self):
        super().validate()

        if     (self.type_ != DataType.RESULT and
                (self.reported is not None or self.items)):
            raise ValueError("report in non-result")

        if     (self.type_ == DataType.RESULT and
                (self.reported is not None or self.items)):
            self._validate_result()
Beispiel #22
0
class Field(xso.XSO):
    """
    Represent a single field in a Data Form.

    :param type_: Field type, must be one of the valid field types specified in
                  :xep:`4`.
    :type type_: :class:`FieldType`
    :param options: A mapping of values to labels defining the options in a
                    ``list-*`` field.
    :type options: :class:`dict` mapping :class:`str` to :class:`str`
    :param values: A sequence of values currently given for the field. Having
                   more than one value is only valid in ``*-multi`` fields.
    :type values: :class:`list` of :class:`str`
    :param desc: Description which can be shown in a tool-tip or similar,
                 without newlines.
    :type desc: :class:`str` or :data:`None`s
    :param label: Human-readable label to be shown next to the field input
    :type label: :class:`str` or :data:`None`
    :param required: Flag to indicate that the field is required
    :type required: :class:`bool`
    :param var: "ID" identifying the field uniquely inside the form. Only
                required for fields carrying a meaning (thus, not for
                ``fixed``).
    :type var: :class:`str` or :data:`None`

    The semantics of a :class:`Field` are different depending on where it
    occurs: in a :class:`Data`, it is a form field to be filled in, in a
    :class:`Item` it is a cell of a row and in a :class:`Reported` it
    represents a column header.

    .. attribute:: required

       A boolean flag indicating whether the field is required.

       If true, the XML serialisation will contain the corresponding
       ``<required/>`` tag.

    .. attribute:: desc

       Single line of description for the field. This attribute represents the
       ``<desc/>`` element from :xep:`4`.

    .. attribute:: values

       A sequence of strings representing the ``<value/>`` elements of the
       field, one string for each value.

       .. note::

          Since the requirements on the sequence of strings in :attr:`values`
          change depending on the :attr:`type_` attribute, validation and type
          conversion on assignment is very lax. The attribute accepts all
          sequences of strings, even if the field is for example a
          :attr:`FieldType.BOOLEAN` field, which allows for at most one string
          of a well-defined format (see the documentation there for the
          details).

          This makes it easy to inadvertendly generate invalid forms, which is
          why you should be using :class:`Form` subclasses when accessing forms
          from within normal code and some other, generic mechanism taking care
          of these details when showing forms in a UI framework to users. Note
          that devising such a mechanism is out of scope for :mod:`aioxmpp`, as
          every UI framework has different requirements.

    .. attribute:: options

       A dictionary mapping values to human-readable labels, representing the
       ``<option/>`` elements of the field.

    .. attribute:: var

       The uniquely identifying string of the (valued, that is,
       non-:attr:`FieldType.FIXED` field). Represents the ``var`` attribute of
       the field.

    .. attribute:: type_

       The type of the field. The :attr:`type_` must be a :class:`FieldType`
       enumeration value and determines restrictions and constraints on other
       attributes. See the :class:`FieldType` enumeration and :xep:`4` for
       details.

    .. attribute:: label

       The human-readable label for the field, representing the ``label``
       attribute of the field. May be :data:`None` if the label is omitted.

    """

    TAG = (namespaces.xep0004_data, "field")

    required = xso.ChildFlag(
        (namespaces.xep0004_data, "required"),
    )

    desc = xso.ChildText(
        (namespaces.xep0004_data, "desc"),
        default=None
    )

    values = xso.ChildValueList(
        type_=ValueElement()
    )

    options = xso.ChildValueMap(
        type_=OptionElement(),
        mapping_type=collections.OrderedDict,
    )

    var = xso.Attr(
        (None, "var"),
        default=None
    )

    type_ = xso.Attr(
        (None, "type"),
        type_=xso.EnumType(
            FieldType,
        ),
        default=FieldType.TEXT_SINGLE,
    )

    label = xso.Attr(
        (None, "label"),
        default=None
    )

    def __init__(self, *,
                 type_=FieldType.TEXT_SINGLE,
                 options={},
                 values=[],
                 desc=None,
                 label=None,
                 required=False,
                 var=None):
        super().__init__()
        self.type_ = type_
        self.options.update(options)
        self.values[:] = values
        self.desc = desc
        self.label = label
        self.required = required
        self.var = var

    def validate(self):
        super().validate()

        if self.type_ != FieldType.FIXED and not self.var:
            raise ValueError("missing attribute var")

        if not self.type_.has_options and self.options:
            raise ValueError("unexpected option on non-list field")

        if not self.type_.is_multivalued and len(self.values) > 1:
            raise ValueError("too many values on non-multi field")

        values_list = [opt for opt in self.options.values()]
        values_set = set(values_list)

        if len(values_list) != len(values_set):
            raise ValueError("duplicate option value")
Beispiel #23
0
 def test_pass_Enum_values_through_coerce(self):
     e = xso.EnumType(self.SomeEnum)
     for enum_value in self.SomeEnum:
         self.assertIs(enum_value, e.coerce(enum_value))
Beispiel #24
0
 def test_init_default(self):
     with self.assertRaises(TypeError):
         xso.EnumType()