class Info(xso.XSO): """ An info node specifying avatar metadata for a specific MIME type. .. attribute:: id_ The SHA1 of the avatar image data. .. attribute:: mime_type The MIME type of the avatar image. .. attribute:: nbytes The size of the image data in bytes. .. attribute:: width The width of the image in pixels. Defaults to :data:`None`. .. attribute:: height The height of the image in pixels. Defaults to :data:`None`. .. attribute:: url The URL of the image. Defaults to :data:`None`. """ TAG = (namespaces.xep0084_metadata, "info") id_ = xso.Attr(tag="id", type_=xso.String()) mime_type = xso.Attr(tag="type", type_=xso.String()) nbytes = xso.Attr(tag="bytes", type_=xso.Integer()) width = xso.Attr(tag="width", type_=xso.Integer(), default=None) height = xso.Attr(tag="height", type_=xso.Integer(), default=None) url = xso.Attr(tag="url", type_=xso.String(), default=None) def __init__(self, id_, mime_type, nbytes, width=None, height=None, url=None): self.id_ = id_ self.mime_type = mime_type self.nbytes = nbytes self.width = width self.height = height self.url = url
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), )
class First(_RangeLimitBase): """ .. attribute:: value Identifier of the first element in the result set. .. attribute:: index Approximate index of the first element in the result set. Can be used with :attr:`ResultSetMetadata.index` and :meth:`ResultSetMetadata.fetch_page` to approximately re-retrieve the page. .. seealso:: :meth:`~ResultSetMetadata.fetch_page` for hints on caveats and inaccuracies """ TAG = namespaces.xep0059_rsm, "first" index = xso.Attr( "index", type_=xso.Integer(), default=None, )
class Items(xso.XSO): TAG = (namespaces.xep0060, "items") max_items = xso.Attr( (None, "max_items"), type_=xso.Integer(), validator=xso.NumericRange(min_=1), default=None, ) node = xso.Attr( "node", ) subid = xso.Attr( "subid", default=None ) items = xso.ChildList( [Item] ) def __init__(self, node, subid=None, max_items=None): super().__init__() self.node = node self.subid = subid self.max_items = max_items
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, )
class Status(xso.XSO): TAG = (namespaces.xep0045_muc_user, "status") code = xso.Attr("code", type_=xso.Integer()) def __init__(self, code): super().__init__() self.code = code
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)
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)
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)
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))
class Data(xso.XSO): TAG = (namespaces.xep0047, "data") seq = xso.Attr("seq", type_=xso.Integer()) sid = xso.Attr("sid", type_=xso.String()) content = xso.Text(type_=xso.Base64Binary()) def __init__(self, sid, seq, content): self.seq = seq self.sid = sid self.content = content
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", )
def test_coerce_requires_integral_number(self): t = xso.Integer() values = [ 1.2, "1", decimal.Decimal("1"), fractions.Fraction(1, 1), "foo", [], (), 1. ] for value in values: with self.assertRaisesRegex(TypeError, "must be integral number"): t.coerce(value)
def test_coerce_passes_integral_numbers(self): t = xso.Integer() values = [-2, 0, 1, 2, 3, 4, 100] for value in values: self.assertIs(value, t.coerce(value)) import random value = random.randint(1, 1e10) self.assertIs(value, t.coerce(value)) value = -value self.assertIs(value, t.coerce(value))
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 History(xso.XSO): TAG = (namespaces.xep0045_muc, "history") maxchars = xso.Attr( "maxchars", type_=xso.Integer(), default=None, ) maxstanzas = xso.Attr( "maxstanzas", type_=xso.Integer(), default=None, ) seconds = xso.Attr( "seconds", type_=xso.Integer(), default=None, ) since = xso.Attr( "since", type_=xso.DateTime(), default=None, ) def __init__(self, *, maxchars=None, maxstanzas=None, seconds=None, since=None): super().__init__() self.maxchars = maxchars self.maxstanzas = maxstanzas self.seconds = seconds self.since = since
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
def test_parse_failure(self): t = xso.Integer() with self.assertRaises(ValueError): t.parse("123f")
def test_is_abstract_type(self): self.assertIsInstance(xso.Integer(), xso.AbstractType)
def test_parse(self): t = xso.Integer() self.assertEqual(123, t.parse("123"))
class ResultSetMetadata(xso.XSO): """ Represent the result set or query metadata. For requests, the following attributes are relevant: .. attribute:: after Either :data:`None` or a :class:`After` object. Generally mutually exclusive with :attr:`index`. .. attribute:: before Either :data:`None` or a :class:`Before` object. Generally mutually exclusive with :attr:`index`. .. attribute:: index The index of the first result to return, or :data:`None`. Generally mutually exclusive with :attr:`after` and :attr:`before`. .. attribute:: max The maximum number of items to return or :data:`None`. Setting :attr:`max` to zero will make the peer return a :class:`ResultSetMetadata` with the total number of items in the :attr:`count` field. These methods are useful when constructing queries: .. automethod:: fetch_page .. automethod:: limit .. automethod:: last_page For responses, the following attributes are relevant: .. attribute:: first Either :data:`None` or a :class:`First` object. .. attribute:: last Either :data:`None` or a :class:`Last` object. .. attribute:: count Either :data:`None` or the number of elements in the result set. If this is a response to a query with :attr:`max` set to zero, this is the total number of elements in the queried data. These methods are useful to construct a new request from a previous response: .. automethod:: next_page .. automethod:: previous_page """ TAG = namespaces.xep0059_rsm, "set" after = xso.Child([After]) before = xso.Child([Before]) first = xso.Child([First]) last = xso.Child([Last]) count = xso.ChildText( (namespaces.xep0059_rsm, "count"), type_=xso.Integer(), default=None, ) max_ = xso.ChildText( (namespaces.xep0059_rsm, "max"), type_=xso.Integer(), default=None, ) index = xso.ChildText( (namespaces.xep0059_rsm, "index"), type_=xso.Integer(), default=None, ) @classmethod def fetch_page(cls, index, max_=None): """ Return a query set which requests a specific page. :param index: Index of the first element of the page to fetch. :type index: :class:`int` :param max_: Maximum number of elements to fetch :type max_: :class:`int` or :data:`None` :rtype: :class:`ResultSetMetadata` :return: A new request set up to request a page starting with the element indexed by `index`. .. note:: This way of retrieving items may be approximate. See :xep:`59` and the embedding protocol for which RSM is used for specifics. """ result = cls() result.index = index result.max_ = max_ return result @magicmethod def limit(self, max_): """ Limit the result set to a given number of items. :param max_: Maximum number of items to return. :type max_: :class:`int` or :data:`None` :rtype: :class:`ResultSetMetadata` :return: A new request set up to request at most `max_` items. This method can be called on the class and on objects. When called on objects, it returns a copy of the object with :attr:`max_` set accordingly. When called on the class, it creates a fresh object with :attr:`max_` set accordingly. """ if isinstance(self, type): result = self() else: result = copy.deepcopy(self) result.max_ = max_ return result def next_page(self, max_=None): """ Return a query set which requests the page after this response. :param max_: Maximum number of items to return. :type max_: :class:`int` or :data:`None` :rtype: :class:`ResultSetMetadata` :return: A new request set up to request the next page. Must be called on a result set which has :attr:`last` set. """ result = type(self)() result.after = After(self.last.value) result.max_ = max_ return result def previous_page(self, max_=None): """ Return a query set which requests the page before this response. :param max_: Maximum number of items to return. :type max_: :class:`int` or :data:`None` :rtype: :class:`ResultSetMetadata` :return: A new request set up to request the previous page. Must be called on a result set which has :attr:`first` set. """ result = type(self)() result.before = Before(self.first.value) result.max_ = max_ return result @classmethod def last_page(self_or_cls, max_=None): """ Return a query set which requests the last page. :param max_: Maximum number of items to return. :type max_: :class:`int` or :data:`None` :rtype: :class:`ResultSetMetadata` :return: A new request set up to request the last page. """ result = self_or_cls() result.before = Before() result.max_ = max_ return result
class Pointer(xso.XSO): """ A pointer metadata node. The contents are implementation defined. The following attributes may be present (they default to :data:`None`): .. attribute:: id_ The SHA1 of the avatar image data. .. attribute:: mime_type The MIME type of the avatar image. .. attribute:: nbytes The size of the image data in bytes. .. attribute:: width The width of the image in pixels. .. attribute:: height The height of the image in pixels. """ TAG = (namespaces.xep0084_metadata, "pointer") # according to the XEP those MAY occur if their values are known id_ = xso.Attr(tag="id", type_=xso.String(), default=None) mime_type = xso.Attr(tag="type", type_=xso.String(), default=None) nbytes = xso.Attr(tag="bytes", type_=xso.Integer(), default=None) width = xso.Attr(tag="width", type_=xso.Integer(), default=None) height = xso.Attr(tag="height", type_=xso.Integer(), default=None) registered_payload = xso.Child([]) unregistered_payload = xso.Collector() @classmethod def as_payload_class(mycls, cls): """ Register the given class `cls` as possible payload for a :class:`Pointer`. Return the class, to allow this to be used as decorator. """ mycls.register_child( Pointer.registered_payload, cls ) return cls def __init__(self, payload, id_, mime_type, nbytes, width=None, height=None, url=None): self.registered_payload = payload self.id_ = id_ self.mime_type = mime_type self.nbytes = nbytes self.width = width self.height = height
def test_format(self): t = xso.Integer() self.assertEqual("123", t.format(123))