class Command(xso.XSO): TAG = (namespaces.xep0050_commands, "command") actions = xso.Child([Actions]) notes = xso.ChildList([Note]) action = xso.Attr( "action", type_=xso.EnumCDataType(ActionType), default=ActionType.EXECUTE, ) status = xso.Attr( "status", type_=xso.EnumCDataType(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
class Open(xso.XSO): TAG = (namespaces.xep0047, "open") block_size = xso.Attr("block-size", type_=xso.Integer()) # XXX: sid should be restricted to NMTOKEN sid = xso.Attr("sid", type_=xso.String()) stanza = xso.Attr( "stanza", type_=xso.EnumCDataType(IBBStanzaType), default=IBBStanzaType.IQ, )
class Status(xso.XSO): TAG = (namespaces.xep0045_muc_user, "status") code = xso.Attr("code", type_=xso.EnumCDataType( StatusCode, xso.Integer(), allow_coerce=True, pass_unknown=True, )) def __init__(self, code): super().__init__() self.code = code
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.EnumCDataType(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
class Note(xso.XSO): TAG = (namespaces.xep0050_commands, "note") body = xso.Text( default=None, ) type_ = xso.Attr( "type", type_=xso.EnumCDataType( NoteType, ), default=NoteType.INFO, ) def __init__(self, type_, body): super().__init__() self.type_ = type_ self.body = body
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. .. automethod:: get_form_type """ TAG = (namespaces.xep0004_data, "x") type_ = xso.Attr("type", type_=xso.EnumCDataType(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() def get_form_type(self): """ Extract the ``FORM_TYPE`` from the fields. :return: ``FORM_TYPE`` value or :data:`None` :rtype: :class:`str` or :data:`None` Return :data:`None` if no well-formed ``FORM_TYPE`` field is found in the list of fields. .. versionadded:: 0.8 """ for field in self.fields: if field.var == "FORM_TYPE" and field.type_ == FieldType.HIDDEN: if len(field.values) != 1: return None return field.values[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.EnumCDataType(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")