File: structs.py

package info (click to toggle)
python-aioxmpp 0.12.2-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 6,152 kB
  • sloc: python: 96,969; xml: 215; makefile: 155; sh: 72
file content (1428 lines) | stat: -rw-r--r-- 40,933 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
########################################################################
# File name: structs.py
# This file is part of: aioxmpp
#
# LICENSE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program.  If not, see
# <http://www.gnu.org/licenses/>.
#
########################################################################
"""
:mod:`~aioxmpp.structs` --- Simple data holders for common data types
#####################################################################

These classes provide a way to hold structured data which is commonly
encountered in the XMPP realm.

Stanza types
============

.. currentmodule:: aioxmpp

.. autoclass:: IQType

.. autoclass:: MessageType

.. autoclass:: PresenceType

.. autoclass:: ErrorType

Jabber IDs
==========

.. autoclass:: JID(localpart, domain, resource)

.. autofunction:: jid_escape

.. autofunction:: jid_unescape

Presence
========

.. autoclass:: PresenceShow

.. autoclass:: PresenceState

.. currentmodule:: aioxmpp.structs

Languages
=========

.. autoclass:: LanguageTag

.. autoclass:: LanguageRange

.. autoclass:: LanguageMap

Functions for working with language tags
----------------------------------------

.. autofunction:: basic_filter_languages

.. autofunction:: lookup_language

"""

import collections
import enum
import functools
import warnings

from .stringprep import nodeprep, resourceprep, nameprep


_USE_COMPAT_ENUM = True


class CompatibilityMixin:
    def __hash__(self):
        return hash(self.value)

    def __eq__(self, other):
        if not _USE_COMPAT_ENUM:
            return super().__eq__(other)

        if super().__eq__(other) is True:
            return True
        if self.value == other:
            warnings.warn(
                "as of aioxmpp 1.0, {} members will not compare equal to "
                "their values".format(type(self).__name__),
                DeprecationWarning,
                stacklevel=2,
            )
            return True
        return False


class ErrorType(CompatibilityMixin, enum.Enum):
    """
    Enumeration for the :rfc:`6120` specified stanza error types.

    These error types reflect are actually more reflecting the error classes,
    but the attribute is called "type" nonetheless. For consistency, we are
    calling it "type" here, too.

    The following types are specified. The quotations in the member
    descriptions are from :rfc:`6120`, Section 8.3.2.

    .. attribute:: AUTH

       The ``"auth"`` error type:

          retry after providing credentials

       When converted to an exception, it uses :exc:`~.XMPPAuthError`.

    .. attribute:: CANCEL

       The ``"cancel"`` error type:

          do not retry (the error cannot be remedied)

       When converted to an exception, it uses :exc:`~.XMPPCancelError`.

    .. attribute:: CONTINUE

       The ``"continue"`` error type:

          proceed (the condition was only a warning)

       When converted to an exception, it uses
       :exc:`~.XMPPContinueError`.

    .. attribute:: MODIFY

       The ``"modify"`` error type:

          retry after changing the data sent

       When converted to an exception, it uses
       :exc:`~.XMPPModifyError`.

    .. attribute:: WAIT

       The ``"wait"`` error type:

          retry after waiting (the error is temporary)

       When converted to an exception, it uses (guess what)
       :exc:`~.XMPPWaitError`.

    :class:`ErrorType` members compare and hash equal to their values. For
    example::

      assert ErrorType.CANCEL == "cancel"
      assert "cancel" == ErrorType.CANCEL
      assert hash(ErrorType.CANCEL) == hash("cancel")

    .. deprecated:: 0.7

       This behaviour will cease with aioxmpp 1.0, and the first assertion will
       fail, the second may fail.

       Please see the Changelog for :ref:`api-changelog-0.7` for further
       details on how to upgrade your code efficiently.

    """

    AUTH = "auth"
    CANCEL = "cancel"
    CONTINUE = "continue"
    MODIFY = "modify"
    WAIT = "wait"


class MessageType(CompatibilityMixin, enum.Enum):
    """
    Enumeration for the :rfc:`6121` specified Message stanza types.

    .. seealso::

       :attr:`~.Message.type_`
          Type attribute of Message stanzas.


    Each member has the following meta-information:

    .. autoattribute:: is_error

    .. autoattribute:: is_request

    .. autoattribute:: is_response

    .. note::

       The :attr:`is_error`, :attr:`is_request` and :attr:`is_response`
       meta-information attributes share semantics across :class:`MessageType`,
       :class:`PresenceType` and :class:`IQType`. You are encouraged to exploit
       this in full duck-typing manner in generic stanza handling code.

    The following types are specified. The quotations in the member
    descriptions are from :rfc:`6121`, Section 5.2.2.

    .. attribute:: NORMAL

       The ``"normal"`` Message type:

          The message is a standalone message that is sent outside the context
          of a one-to-one conversation or groupchat, and to which it is
          expected that the recipient will reply.  Typically a receiving client
          will present a message of type "normal" in an interface that enables
          the recipient to reply, but without a conversation history.  The
          default value of the 'type' attribute is "normal".

       Think of it as somewhat similar to "E-Mail via XMPP".

    .. attribute:: CHAT

       The ``"chat"`` Message type:

          The message is sent in the context of a one-to-one chat session.
          Typically an interactive client will present a message of type "chat"
          in an interface that enables one-to-one chat between the two parties,
          including an appropriate conversation history.

    .. attribute:: GROUPCHAT

       The ``"groupchat"`` Message type:

          The message is sent in the context of a multi-user chat environment
          […].  Typically a receiving client will present a message of type
          "groupchat" in an interface that enables many-to-many chat between
          the parties, including a roster of parties in the chatroom and an
          appropriate conversation history.

    .. attribute:: HEADLINE

       The ``"headline"`` Message type:

          The message provides an alert, a notification, or other transient
          information to which no reply is expected (e.g., news headlines,
          sports updates, near-real-time market data, or syndicated content).
          Because no reply to the message is expected, typically a receiving
          client will present a message of type "headline" in an interface that
          appropriately differentiates the message from standalone messages,
          chat messages, and groupchat messages (e.g., by not providing the
          recipient with the ability to reply).

       Do not confuse this message type with the
       :attr:`~.Message.subject` member of Messages!

    .. attribute:: ERROR

       The ``"error"`` Message type:

          The message is generated by an entity that experiences an error when
          processing a message received from another entity […].  A client that
          receives a message of type "error" SHOULD present an appropriate
          interface informing the original sender regarding the nature of the
          error.

       This is the only message type which is used in direct response to
       another message, in the sense that the Stanza ID is preserved in the
       response.

    :class:`MessageType` members compare and hash equal to their values. For
    example::

      assert MessageType.CHAT == "chat"
      assert "chat" == MessageType.CHAT
      assert hash(MessageType.CHAT) == hash("chat")

    .. deprecated:: 0.7

       This behaviour will cease with aioxmpp 1.0, and the first assertion will
       fail, the second may fail.

       Please see the Changelog for :ref:`api-changelog-0.7` for further
       details on how to upgrade your code efficiently.

    """

    NORMAL = "normal"
    CHAT = "chat"
    GROUPCHAT = "groupchat"
    HEADLINE = "headline"
    ERROR = "error"

    @property
    def is_error(self):
        """
        True for the :attr:`ERROR` type, false for all others.
        """
        return self == MessageType.ERROR

    @property
    def is_response(self):
        """
        True for the :attr:`ERROR` type, false for all others.

        This is intended. Request/Response semantics do not really apply for
        messages, except that errors are generally in response to other
        messages.
        """
        return self == MessageType.ERROR

    @property
    def is_request(self):
        """
        False. See :attr:`is_response`.
        """
        return False


class PresenceType(CompatibilityMixin, enum.Enum):
    """
    Enumeration for the :rfc:`6121` specified Presence stanza types.

    .. seealso::

       :attr:`~.Presence.type_`
          Type attribute of Presence stanzas.

    Each member has the following meta-information:

    .. autoattribute:: is_error

    .. autoattribute:: is_request

    .. autoattribute:: is_response

    .. autoattribute:: is_presence_state

    .. note::

       The :attr:`is_error`, :attr:`is_request` and :attr:`is_response`
       meta-information attributes share semantics across :class:`MessageType`,
       :class:`PresenceType` and :class:`IQType`. You are encouraged to exploit
       this in full duck-typing manner in generic stanza handling code.


    The following types are specified. The quotes in the member descriptions
    are from :rfc:`6121`, Section 4.7.1.

    .. attribute:: ERROR

       The ``"error"`` Presence type:

          An error has occurred regarding processing of a previously sent
          presence stanza; if the presence stanza is of type "error", it MUST
          include an <error/> child element […].

       This is the only presence stanza type which is used in direct response
       to another presence stanza, in the sense that the Stanza ID is preserved
       in the response.

       In addition, :attr:`ERROR` presence stanzas may be seen during presence
       broadcast if inter-server communication fails.

    .. attribute:: PROBE

       The ``"probe"`` Presence type:

          A request for an entity's current presence; SHOULD be generated only
          by a server on behalf of a user.

       This should not be seen in client code.

    .. attribute:: SUBSCRIBE

       The ``"subscribe"`` Presence type:

          The sender wishes to subscribe to the recipient's presence.

    .. attribute:: SUBSCRIBED

       The ``"subscribed"`` Presence type:

          The sender has allowed the recipient to receive their presence.

    .. attribute:: UNSUBSCRIBE

       The ``"unsubscribe"`` Presence type:

          The sender is unsubscribing from the receiver's presence.

    .. attribute:: UNSUBSCRIBED

       The ``"unsubscribed"`` Presence type:

          The subscription request has been denied or a previously granted
          subscription has been canceled.

    .. attribute:: AVAILABLE

       The Presence type signalled with an absent type attribute:

          The absence of a 'type' attribute signals that the relevant entity is
          available for communication […].

    .. attribute:: UNAVAILABLE

       The ``"unavailable"`` Presence type:

          The sender is no longer available for communication.

    :class:`PresenceType` members compare and hash equal to their values. For
    example::

      assert PresenceType.PROBE == "probe"
      assert "probe" == PresenceType.PROBE
      assert hash(PresenceType.PROBE) == hash("probe")

    .. deprecated:: 0.7

       This behaviour will cease with aioxmpp 1.0, and the first assertion will
       fail, the second may fail.

       Please see the Changelog for :ref:`api-changelog-0.7` for further
       details on how to upgrade your code efficiently.
    """

    ERROR = "error"
    PROBE = "probe"
    SUBSCRIBE = "subscribe"
    SUBSCRIBED = "subscribed"
    UNAVAILABLE = "unavailable"
    UNSUBSCRIBE = "unsubscribe"
    UNSUBSCRIBED = "unsubscribed"
    AVAILABLE = None

    @property
    def is_error(self):
        """
        True for the :attr:`ERROR` type, false otherwise.
        """
        return self == PresenceType.ERROR

    @property
    def is_response(self):
        """
        True for the :attr:`ERROR` type, false otherwise.

        This is intended. Request/Response semantics do not really apply for
        presence stanzas, except that errors are generally in response to other
        presence stanzas.
        """
        return self == PresenceType.ERROR

    @property
    def is_request(self):
        """
        False. See :attr:`is_response`.
        """
        return False

    @property
    def is_presence_state(self):
        """
        True for the :attr:`AVAILABLE` and :attr:`UNAVAILABLE` types, false
        otherwise.

        Useful to discern presence state notifications from meta-stanzas
        regarding presence broadcast control.
        """
        return (self == PresenceType.AVAILABLE or
                self == PresenceType.UNAVAILABLE)


class IQType(CompatibilityMixin, enum.Enum):
    """
    Enumeration for the :rfc:`6120` specified IQ stanza types.

    .. seealso::

       :attr:`~.IQ.type_`
          Type attribute of IQ stanzas.

    Each member has the following meta-information:

    .. autoattribute:: is_error

    .. autoattribute:: is_request

    .. autoattribute:: is_response

    .. note::

       The :attr:`is_error`, :attr:`is_request` and :attr:`is_response`
       meta-information attributes share semantics across :class:`MessageType`,
       :class:`PresenceType` and :class:`IQType`. You are encouraged to exploit
       this in full duck-typing manner in generic stanza handling code.

    The following types are specified. The quotations in the member
    descriptions are from :rfc:`6120`, Section 8.2.3.

    .. attribute:: GET

       The ``"get"`` IQ type:

           The stanza requests information, inquires about what
           data is needed in order to complete further operations, etc.

       A :attr:`GET` IQ must contain a payload, via the
       :attr:`~.IQ.payload` attribute.

    .. attribute:: SET

       The ``"set"`` IQ type:

           The stanza provides data that is needed for an operation to be
           completed, sets new values, replaces existing values, etc.

       A :attr:`SET` IQ must contain a payload, via the
       :attr:`~.IQ.payload` attribute.

    .. attribute:: ERROR

       The ``"error"`` IQ type:

           The stanza reports an error that has occurred regarding processing
           or delivery of a get or set request[…].

       :class:`~.IQ` objects carrying the :attr:`ERROR` type usually
       have the :attr:`~.IQ.error` set to a :class:`~.stanza.Error`
       instance describing the details of the error.

       The :attr:`~.IQ.payload` attribute may also be set if the sender
       of the :attr:`ERROR` was kind enough to include the data which caused
       the problem.

    .. attribute:: RESULT

       The ``"result"`` IQ type:

           The stanza is a response to a successful get or set request.

       A :attr:`RESULT` IQ may contain a payload with more data.

    :class:`IQType` members compare and hash equal to their values. For
    example::

      assert IQType.GET == "get"
      assert "get" == IQType.GET
      assert hash(IQType.GET) == hash("get")

    .. deprecated:: 0.7

       This behaviour will cease with aioxmpp 1.0, and the first assertion will
       fail, the second may fail.

       Please see the Changelog for :ref:`api-changelog-0.7` for further
       details on how to upgrade your code efficiently.
    """

    GET = "get"
    SET = "set"
    ERROR = "error"
    RESULT = "result"

    @property
    def is_error(self):
        """
        True for the :attr:`ERROR` type, false otherwise.
        """
        return self == IQType.ERROR

    @property
    def is_request(self):
        """
        True for request types (:attr:`GET` and :attr:`SET`), false otherwise.
        """
        return self == IQType.GET or self == IQType.SET

    @property
    def is_response(self):
        """
        True for the response types (:attr:`RESULT` and :attr:`ERROR`), false
        otherwise.
        """
        return self == IQType.RESULT or self == IQType.ERROR


class JID(collections.namedtuple("JID", ["localpart", "domain", "resource"])):
    """
    Represent a :term:`Jabber ID (JID) <Jabber ID>`.

    To construct a :class:`JID`, either use the actual constructor, or use the
    :meth:`fromstr` class method.

    :param localpart: The part in front of the ``@`` of the JID, or
        :data:`None` if the localpart shall be omitted (which is different from
        it being empty, which would be invalid).
    :type localpart: :class:`str` or :data:`None`
    :param domain: The domain of the JID. This is the only mandatory part of
        a JID.
    :type domain: :class:`str`
    :param resource: The resource part of the JID or :data:`None` to omit the
        resource part.
    :type resource: :class:`str` or :data:`None`
    :param strict: Enable strict validation
    :type strict: :class:`bool`
    :raises ValueError: if the JID composed of the given parts is invalid

    Construct a JID out of its parts. It validates the parts individually, as
    well as the JID as a whole.

    If `strict` is false, unassigned codepoints are allowed in any of the parts
    of the JID. In the future, other deviations from the respective stringprep
    profiles may be allowed, too.

    The idea is to use non-`strict` when output is received from outside and
    when it is reflected, following the old principle "be conservative in what
    you send and liberal in what you receive". Otherwise, strict checking
    should be enabled. This brings maximum interoperability.

    .. automethod:: fromstr

    Information about a JID:

    .. attribute:: localpart

       The localpart, stringprep’d from the argument to the constructor.

    .. attribute:: domain

       The domain, stringprep’d from the argument to the constructor.

    .. attribute:: resource

       The resource, stringprep’d from the argument to the constructor.

    .. autoattribute:: is_bare

    .. autoattribute:: is_domain

    :class:`JID` objects are immutable. To obtain a JID object with a changed
    property, use one of the following methods:

    .. automethod:: bare

    .. automethod:: replace(*, [localpart], [domain], [resource])
    """

    __slots__ = []

    def __new__(cls, localpart, domain, resource, *, strict=True):
        if localpart:
            localpart = nodeprep(
                localpart,
                allow_unassigned=not strict
            )
        if domain is not None:
            domain = nameprep(
                domain,
                allow_unassigned=not strict
            )
        if resource:
            resource = resourceprep(
                resource,
                allow_unassigned=not strict
            )

        if not domain:
            raise ValueError("domain must not be empty or None")
        if len(domain.encode("utf-8")) > 1023:
            raise ValueError("domain too long")
        if localpart is not None:
            if not localpart:
                raise ValueError("localpart must not be empty")
            if len(localpart.encode("utf-8")) > 1023:
                raise ValueError("localpart too long")
        if resource is not None:
            if not resource:
                raise ValueError("resource must not be empty")
            if len(resource.encode("utf-8")) > 1023:
                raise ValueError("resource too long")

        return super().__new__(cls, localpart, domain, resource)

    def replace(self, **kwargs):
        """
        Construct a new :class:`JID` object, using the values of the current
        JID. Use the arguments to override specific attributes on the new
        object.

        All arguments are keyword arguments.

        :param localpart: Set the local part of the resulting JID.
        :param domain: Set the domain of the resulting JID.
        :param resource: Set the resource part of the resulting JID.
        :raises: See :class:`JID`
        :return: A new :class:`JID` object with the corresponding
            substitutions performed.
        :rtype: :class:`JID`

        The attributes of parameters which are omitted are not modified and
        copied down to the result.
        """

        new_kwargs = {}

        strict = kwargs.pop("strict", True)

        try:
            localpart = kwargs.pop("localpart")
        except KeyError:
            pass
        else:
            if localpart:
                localpart = nodeprep(
                    localpart,
                    allow_unassigned=not strict
                )
            new_kwargs["localpart"] = localpart

        try:
            domain = kwargs.pop("domain")
        except KeyError:
            pass
        else:
            if not domain:
                raise ValueError("domain must not be empty or None")
            new_kwargs["domain"] = nameprep(
                domain,
                allow_unassigned=not strict
            )

        try:
            resource = kwargs.pop("resource")
        except KeyError:
            pass
        else:
            if resource:
                resource = resourceprep(
                    resource,
                    allow_unassigned=not strict
                )
            new_kwargs["resource"] = resource

        if kwargs:
            raise TypeError("replace() got an unexpected keyword argument"
                            " {!r}".format(
                                next(iter(kwargs))))

        return super()._replace(**new_kwargs)

    def __str__(self):
        result = self.domain
        if self.localpart:
            result = self.localpart + "@" + result
        if self.resource:
            result += "/" + self.resource
        return result

    def bare(self):
        """
        Create a copy of the :class:`JID` which is bare.

        :return: This JID with the :attr:`resource` set to :data:`None`.
        :rtype: :class:`JID`

        Return the bare version of this JID as new :class:`JID` object.
        """
        return self.replace(resource=None)

    @property
    def is_bare(self):
        """
        :data:`True` if the JID is bare, i.e. has an empty :attr:`resource`
        part.
        """
        return not self.resource

    @property
    def is_domain(self):
        """
        :data:`True` if the JID is a domain, i.e. if both the :attr:`localpart`
        and the :attr:`resource` are empty.
        """
        return not self.resource and not self.localpart

    @classmethod
    def fromstr(cls, s, *, strict=True):
        """
        Construct a JID out of a string containing it.

        :param s: The string to parse.
        :type s: :class:`str`
        :param strict: Whether to enable strict parsing.
        :type strict: :class:`bool`
        :raises: See :class:`JID`
        :return: The parsed JID
        :rtype: :class:`JID`

        See the :class:`JID` class level documentation for the semantics of
        `strict`.
        """
        nodedomain, sep, resource = s.partition("/")
        if not sep:
            resource = None

        localpart, sep, domain = nodedomain.partition("@")
        if not sep:
            domain = localpart
            localpart = None
        return cls(localpart, domain, resource, strict=strict)


@functools.total_ordering
class PresenceShow(CompatibilityMixin, enum.Enum):
    """
    Enumeration to support the ``show`` element of presence stanzas.

    The enumeration members support total ordering. The order is defined by
    relevance and is the following (from lesser to greater): :attr:`XA`,
    :attr:`AWAY`, :attr:`NONE`, :attr:`CHAT`, :attr:`DND`. The order is
    intended to be used to extract the most relevant resource e.g. in a roster.

    .. versionadded:: 0.8

    .. attribute:: XA
       :annotation: = "xa"

       .. epigraph::

          The entity or resource is away for an extended period (xa = "eXtended
          Away").

          -- :rfc:`6121`, Section 4.7.2.1

    .. attribute:: EXTENDED_AWAY
       :annotation: = "xa"

       Alias to :attr:`XA`.

    .. attribute:: AWAY
       :annotation: = "away"

       .. epigraph::

          The entity or resource is temporarily away.

          -- :rfc:`6121`, Section 4.7.2.1

    .. attribute:: NONE
       :annotation: = None

       Signifies absence of the ``show`` element.

    .. attribute:: PLAIN
       :annotation: = None

       Alias to :attr:`NONE`.

    .. attribute:: CHAT
       :annotation: = "chat"

       .. epigraph::

          The entity or resource is actively interested in chatting.

          -- :rfc:`6121`, Section 4.7.2.1

    .. attribute:: FREE_FOR_CHAT
       :annotation: = "chat"

       Alias to :attr:`CHAT`.

    .. attribute:: DND
       :annotation: = "dnd"

       .. epigraph::

          The entity or resource is busy (dnd = "Do Not Disturb").

          -- :rfc:`6121`, Section 4.7.2.1

    .. attribute:: DO_NOT_DISTURB
       :annotation: = "dnd"

       Alias to :attr:`DND`.

    """
    XA = "xa"
    EXTENDED_AWAY = "xa"
    AWAY = "away"
    NONE = None
    PLAIN = None
    CHAT = "chat"
    FREE_FOR_CHAT = "chat"
    DND = "dnd"
    DO_NOT_DISTURB = "dnd"

    def __lt__(self, other):
        try:
            w1 = self._WEIGHTS[self]
            w2 = self._WEIGHTS[other]
        except KeyError:
            return NotImplemented
        return w1 < w2


PresenceShow._WEIGHTS = {
    PresenceShow.XA: -2,
    PresenceShow.AWAY: -1,
    PresenceShow.NONE: 0,
    PresenceShow.CHAT: 1,
    PresenceShow.DND: 2,
}


@functools.total_ordering
class PresenceState:
    """
    Hold a presence state of an XMPP resource, as defined by the presence
    stanza semantics.

    `available` must be a boolean value, which defines whether the resource is
    available or not. If the resource is available, `show` may be set to one of
    ``"dnd"``, ``"xa"``, ``"away"``, :data:`None`, ``"chat"`` (it is a
    :class:`ValueError` to attempt to set `show` to a non-:data:`None` value if
    `available` is false).

    :class:`PresenceState` objects are ordered by their availability and by
    their show values. Non-availability sorts lower than availability, and for
    available presence states the order is in the order of valid values given
    for the `show` above.

    .. attribute:: available

       As per the argument to the constructor, converted to a :class:`bool`.

    .. attribute:: show

       As per the argument to the constructor.

    .. automethod:: apply_to_stanza

    .. automethod:: from_stanza

    :class:`PresenceState` objects are immutable.

    """

    __slots__ = ["_available", "_show"]

    def __init__(self, available=False, show=PresenceShow.NONE):
        super().__init__()
        if not available and show != PresenceShow.NONE:
            raise ValueError("Unavailable state cannot have show value")
        if not isinstance(show, PresenceShow):
            try:
                show = PresenceShow(show)
            except ValueError:
                raise ValueError("Not a valid show value") from None
            else:
                warnings.warn(
                    "as of aioxmpp 1.0, the show argument must use "
                    "PresenceShow instead of str",
                    DeprecationWarning,
                    stacklevel=2
                )

        self._available = bool(available)
        self._show = show

    @property
    def available(self):
        return self._available

    @property
    def show(self):
        return self._show

    def __lt__(self, other):
        my_key = (self.available, self.show)
        try:
            other_key = (other.available, other.show)
        except AttributeError:
            return NotImplemented
        return my_key < other_key

    def __eq__(self, other):
        try:
            return (self.available == other.available and
                    self.show == other.show)
        except AttributeError:
            return NotImplemented

    def __repr__(self):
        more = ""
        if self.available:
            if self.show != PresenceShow.NONE:
                more = " available show={!r}".format(self.show)
            else:
                more = " available"
        return "<PresenceState{}>".format(more)

    def apply_to_stanza(self, stanza_obj):
        """
        Apply the properties of this :class:`PresenceState` to a
        :class:`~aioxmpp.Presence` `stanza_obj`. The
        :attr:`~aioxmpp.Presence.type_` and
        :attr:`~aioxmpp.Presence.show` attributes of the object will be
        modified to fit the values in this object.
        """
        if self.available:
            stanza_obj.type_ = PresenceType.AVAILABLE
        else:
            stanza_obj.type_ = PresenceType.UNAVAILABLE
        stanza_obj.show = self.show

    @classmethod
    def from_stanza(cls, stanza_obj, strict=False):
        """
        Create and return a new :class:`PresenceState` object which inherits
        the presence state as advertised in the given
        :class:`~aioxmpp.Presence` stanza.

        If `strict` is :data:`True`, the value of `show` is strictly checked,
        that is, it is required to be :data:`None` if the stanza indicates an
        unavailable state.

        The default is not to check this.
        """

        if not stanza_obj.type_.is_presence_state:
            raise ValueError("presence state stanza required")
        available = stanza_obj.type_ == PresenceType.AVAILABLE
        if not strict:
            show = stanza_obj.show if available else PresenceShow.NONE
        else:
            show = stanza_obj.show
        return cls(available=available, show=show)


@functools.total_ordering
class LanguageTag:
    """
    Implementation of a language tag. This may be a fully RFC5646 compliant
    implementation some day, but for now it is only very simplistic stub.

    There is no input validation of any kind.

    :class:`LanguageTag` instances compare and hash case-insensitively.

    .. automethod:: fromstr

    .. autoattribute:: match_str

    .. autoattribute:: print_str

    """

    __slots__ = ("_tag",)

    def __init__(self, *, tag=None):
        if not tag:
            raise ValueError("tag cannot be empty")

        self._tag = tag

    @property
    def match_str(self):
        """
        The string which is used for matching two lanugage tags. This is the
        lower-cased version of the :attr:`print_str`.
        """
        return self._tag.lower()

    @property
    def print_str(self):
        """
        The stringified language tag.
        """
        return self._tag

    @classmethod
    def fromstr(cls, s):
        """
        Create a language tag from the given string `s`.

        .. note::

           This is a stub implementation which merely refers to the given
           string as the :attr:`print_str` and derives the :attr:`match_str`
           from that.

        """
        return cls(tag=s)

    def __str__(self):
        return self.print_str

    def __eq__(self, other):
        try:
            return self.match_str == other.match_str
        except AttributeError:
            return False

    def __lt__(self, other):
        try:
            return self.match_str < other.match_str
        except AttributeError:
            return NotImplemented

    def __le__(self, other):
        try:
            return self.match_str <= other.match_str
        except AttributeError:
            return NotImplemented

    def __hash__(self):
        return hash(self.match_str)

    def __repr__(self):
        return "<{}.{}.fromstr({!r})>".format(
            type(self).__module__,
            type(self).__qualname__,
            str(self))


class LanguageRange:
    """
    Implementation of a language range. This may be a fully RFC4647 compliant
    implementation some day, but for now it is only very simplistic stub.

    There is no input validation of any kind.

    :class:`LanguageRange` instances compare and hash case-insensitively.

    .. automethod:: fromstr

    .. automethod:: strip_rightmost

    .. autoattribute:: match_str

    .. autoattribute:: print_str

    """

    __slots__ = ("_tag",)

    def __init__(self, *, tag=None):
        if not tag:
            raise ValueError("range cannot be empty")

        self._tag = tag

    @property
    def match_str(self):
        """
        The string which is used for matching two lanugage tags. This is the
        lower-cased version of the :attr:`print_str`.
        """
        return self._tag.lower()

    @property
    def print_str(self):
        """
        The stringified language tag.
        """
        return self._tag

    @classmethod
    def fromstr(cls, s):
        """
        Create a language tag from the given string `s`.

        .. note::

           This is a stub implementation which merely refers to the given
           string as the :attr:`print_str` and derives the :attr:`match_str`
           from that.

        """
        if s == "*":
            return cls.WILDCARD

        return cls(tag=s)

    def __str__(self):
        return self.print_str

    def __eq__(self, other):
        try:
            return self.match_str == other.match_str
        except AttributeError:
            return False

    def __hash__(self):
        return hash(self.match_str)

    def __repr__(self):
        return "<{}.{}.fromstr({!r})>".format(
            type(self).__module__,
            type(self).__qualname__,
            str(self))

    def strip_rightmost(self):
        """
        Strip the rightmost part of the language range. If the new rightmost
        part is a singleton or ``x`` (i.e. starts an extension or private use
        part), it is also stripped.

        Return the newly created :class:`LanguageRange`.
        """

        parts = self.print_str.split("-")
        parts.pop()
        if parts and len(parts[-1]) == 1:
            parts.pop()
        return type(self).fromstr("-".join(parts))


LanguageRange.WILDCARD = LanguageRange(tag="*")


def basic_filter_languages(languages, ranges):
    """
    Filter languages using the string-based basic filter algorithm described in
    RFC4647.

    `languages` must be a sequence of :class:`LanguageTag` instances which are
    to be filtered.

    `ranges` must be an iterable which represent the basic language ranges to
    filter with, in priority order. The language ranges must be given as
    :class:`LanguageRange` objects.

    Return an iterator of languages which matched any of the `ranges`. The
    sequence produced by the iterator is in match order and duplicate-free. The
    first range to match a language yields the language into the iterator, no
    other range can yield that language afterwards.
    """

    if LanguageRange.WILDCARD in ranges:
        yield from languages
        return

    found = set()

    for language_range in ranges:
        range_str = language_range.match_str
        for language in languages:
            if language in found:
                continue

            match_str = language.match_str
            if match_str == range_str:
                yield language
                found.add(language)
                continue

            if len(range_str) < len(match_str):
                if     (match_str[:len(range_str)] == range_str and
                        match_str[len(range_str)] == "-"):
                    yield language
                    found.add(language)
                    continue


def lookup_language(languages, ranges):
    """
    Look up a single language in the sequence `languages` using the lookup
    mechansim described in RFC4647. If no match is found, :data:`None` is
    returned. Otherwise, the first matching language is returned.

    `languages` must be a sequence of :class:`LanguageTag` objects, while
    `ranges` must be an iterable of :class:`LanguageRange` objects.
    """

    for language_range in ranges:
        while True:
            try:
                return next(iter(basic_filter_languages(
                    languages,
                    [language_range])))
            except StopIteration:
                pass

            try:
                language_range = language_range.strip_rightmost()
            except ValueError:
                break


class LanguageMap(dict):
    """
    A :class:`dict` subclass specialized for holding :class:`LanugageTag`
    instances as keys.

    In addition to the interface provided by :class:`dict`, instances of this
    class also have the following methods:

    .. automethod:: lookup

    .. automethod:: any
    """

    def lookup(self, language_ranges):
        """
        Perform an RFC4647 language range lookup on the keys in the
        dictionary. `language_ranges` must be a sequence of
        :class:`LanguageRange` instances.

        Return the entry in the dictionary with a key as produced by
        `lookup_language`. If `lookup_language` does not find a match and the
        mapping contains an entry with key :data:`None`, that entry is
        returned, otherwise :class:`KeyError` is raised.
        """
        keys = list(self.keys())
        try:
            keys.remove(None)
        except ValueError:
            pass
        keys.sort()
        key = lookup_language(keys, language_ranges)
        return self[key]

    def any(self):
        """
        Returns any element from the language map, preferring the :data:`None`
        key if it is available.

        Guarantees to always return the same element for a map with the same
        keys, even if the keys are iterated over in a different order.
        """
        if not self:
            raise ValueError("any() on empty map")

        try:
            return self[None]
        except KeyError:
            return self[min(self)]


# \ is treated specially because it is only escaped if followed by a valid
# escape sequence... that is so weird.
ESCAPABLE_CODEPOINTS = " \"&'/:<>@"


def jid_escape(s):
    """
    Return an escaped version of a string for use in a JID localpart.

    .. seealso::

        :func:`jid_unescape`
            for the reverse transformation

    :param s: The string to escape for use as localpart.
    :type s: :class:`str`
    :raise ValueError: If the string starts or ends with a space.
    :return: The escaped string.
    :rtype: :class:`str`

    .. note::

        JID Escaping does not allow embedding arbitrary characters in the
        localpart. Only a defined subset of characters can be escaped.
        Refer to :xep:`0106` for details.

    .. note::

        No validity check is made on the result. It is assumed that the
        result is passed to the :class:`JID` constructor, which will
        perform validity checks on its own.

    """

    # we first escape all backslashes which need to be escaped
    for cp in "\\" + ESCAPABLE_CODEPOINTS:
        seq = "\\{:02x}".format(ord(cp))
        s = s.replace(seq, "\\5c{:02x}".format(ord(cp)))

    # now we escape all the other stuff
    for cp in ESCAPABLE_CODEPOINTS:
        s = s.replace(cp, "\\{:02x}".format(ord(cp)))

    return s


def jid_unescape(localpart):
    """
    Un-escape a JID Escaped localpart.

    .. seealso::

        :func:`jid_escape`
            for the reverse transformation

    :param localpart: The escaped localpart
    :type localpart: :class:`str`
    :return: The unescaped localpart.
    :rtype: :class:`str`

    .. note::

        JID Escaping does not allow embedding arbitrary characters in the
        localpart. Only a defined subset of characters can be escaped.
        Refer to :xep:`0106` for details.
    """
    s = localpart

    for cp in ESCAPABLE_CODEPOINTS:
        s = s.replace("\\{:02x}".format(ord(cp)), cp)

    for cp in ESCAPABLE_CODEPOINTS + "\\":
        s = s.replace(
            "\\5c{:02x}".format(ord(cp)),
            "\\{:02x}".format(ord(cp)),
        )

    return s