slidge 0.1.0rc1__py3-none-any.whl → 0.1.2__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- slidge/__init__.py +54 -31
- slidge/__main__.py +51 -5
- slidge/command/__init__.py +28 -0
- slidge/command/adhoc.py +258 -0
- slidge/command/admin.py +193 -0
- slidge/command/base.py +441 -0
- slidge/command/categories.py +3 -0
- slidge/command/chat_command.py +288 -0
- slidge/command/register.py +179 -0
- slidge/command/user.py +250 -0
- slidge/contact/__init__.py +8 -0
- slidge/contact/contact.py +452 -0
- slidge/contact/roster.py +192 -0
- slidge/core/__init__.py +2 -0
- slidge/core/cache.py +121 -39
- slidge/core/config.py +116 -11
- slidge/core/gateway/__init__.py +3 -0
- slidge/core/gateway/base.py +895 -0
- slidge/core/gateway/caps.py +63 -0
- slidge/core/gateway/delivery_receipt.py +52 -0
- slidge/core/gateway/disco.py +80 -0
- slidge/core/gateway/mam.py +75 -0
- slidge/core/gateway/muc_admin.py +35 -0
- slidge/core/gateway/ping.py +66 -0
- slidge/core/gateway/presence.py +95 -0
- slidge/core/gateway/registration.py +53 -0
- slidge/core/gateway/search.py +102 -0
- slidge/core/gateway/session_dispatcher.py +795 -0
- slidge/core/gateway/vcard_temp.py +130 -0
- slidge/core/mixins/__init__.py +9 -1
- slidge/core/mixins/attachment.py +506 -0
- slidge/core/mixins/avatar.py +167 -0
- slidge/core/mixins/base.py +6 -19
- slidge/core/mixins/disco.py +66 -15
- slidge/core/mixins/lock.py +31 -0
- slidge/core/mixins/message.py +254 -252
- slidge/core/mixins/message_maker.py +154 -0
- slidge/core/mixins/presence.py +128 -31
- slidge/core/mixins/recipient.py +43 -0
- slidge/core/pubsub.py +275 -116
- slidge/core/session.py +586 -518
- slidge/group/__init__.py +10 -0
- slidge/group/archive.py +125 -0
- slidge/group/bookmarks.py +163 -0
- slidge/group/participant.py +458 -0
- slidge/group/room.py +1103 -0
- slidge/migration.py +18 -0
- slidge/slixfix/__init__.py +68 -0
- slidge/{util/xep_0050 → slixfix/link_preview}/__init__.py +4 -5
- slidge/slixfix/link_preview/link_preview.py +17 -0
- slidge/slixfix/link_preview/stanza.py +99 -0
- slidge/slixfix/roster.py +60 -0
- slidge/{util → slixfix}/xep_0077/register.py +1 -2
- slidge/slixfix/xep_0077/stanza.py +104 -0
- slidge/{util → slixfix}/xep_0100/gateway.py +17 -12
- slidge/slixfix/xep_0153/__init__.py +10 -0
- slidge/slixfix/xep_0153/stanza.py +25 -0
- slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
- slidge/slixfix/xep_0264/__init__.py +5 -0
- slidge/slixfix/xep_0264/stanza.py +36 -0
- slidge/slixfix/xep_0264/thumbnail.py +23 -0
- slidge/slixfix/xep_0292/__init__.py +5 -0
- slidge/slixfix/xep_0292/vcard4.py +100 -0
- slidge/slixfix/xep_0313/__init__.py +12 -0
- slidge/slixfix/xep_0313/mam.py +262 -0
- slidge/slixfix/xep_0313/stanza.py +359 -0
- slidge/slixfix/xep_0317/__init__.py +5 -0
- slidge/slixfix/xep_0317/hats.py +17 -0
- slidge/slixfix/xep_0317/stanza.py +28 -0
- slidge/{util → slixfix}/xep_0356_old/privilege.py +9 -7
- slidge/slixfix/xep_0424/__init__.py +9 -0
- slidge/slixfix/xep_0424/retraction.py +77 -0
- slidge/slixfix/xep_0424/stanza.py +28 -0
- slidge/slixfix/xep_0490/__init__.py +8 -0
- slidge/slixfix/xep_0490/mds.py +47 -0
- slidge/slixfix/xep_0490/stanza.py +17 -0
- slidge/util/__init__.py +4 -6
- slidge/util/archive_msg.py +61 -0
- slidge/util/conf.py +25 -4
- slidge/util/db.py +23 -69
- slidge/util/schema.sql +126 -0
- slidge/util/sql.py +508 -0
- slidge/util/test.py +136 -86
- slidge/util/types.py +155 -14
- slidge/util/util.py +225 -51
- slidge-0.1.2.dist-info/METADATA +111 -0
- slidge-0.1.2.dist-info/RECORD +96 -0
- {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/WHEEL +1 -1
- slidge/core/adhoc.py +0 -492
- slidge/core/chat_command.py +0 -197
- slidge/core/contact.py +0 -441
- slidge/core/disco.py +0 -59
- slidge/core/gateway.py +0 -899
- slidge/core/muc/__init__.py +0 -3
- slidge/core/muc/bookmarks.py +0 -74
- slidge/core/muc/participant.py +0 -152
- slidge/core/muc/room.py +0 -348
- slidge/plugins/discord/__init__.py +0 -121
- slidge/plugins/discord/client.py +0 -121
- slidge/plugins/discord/session.py +0 -172
- slidge/plugins/dummy.py +0 -334
- slidge/plugins/facebook.py +0 -591
- slidge/plugins/hackernews.py +0 -209
- slidge/plugins/mattermost/__init__.py +0 -1
- slidge/plugins/mattermost/api.py +0 -288
- slidge/plugins/mattermost/gateway.py +0 -417
- slidge/plugins/mattermost/websocket.py +0 -248
- slidge/plugins/signal/__init__.py +0 -4
- slidge/plugins/signal/config.py +0 -4
- slidge/plugins/signal/contact.py +0 -104
- slidge/plugins/signal/gateway.py +0 -379
- slidge/plugins/signal/group.py +0 -76
- slidge/plugins/signal/session.py +0 -515
- slidge/plugins/signal/txt.py +0 -13
- slidge/plugins/signal/util.py +0 -32
- slidge/plugins/skype.py +0 -310
- slidge/plugins/steam.py +0 -400
- slidge/plugins/telegram/__init__.py +0 -6
- slidge/plugins/telegram/client.py +0 -325
- slidge/plugins/telegram/config.py +0 -21
- slidge/plugins/telegram/contact.py +0 -154
- slidge/plugins/telegram/gateway.py +0 -182
- slidge/plugins/telegram/group.py +0 -184
- slidge/plugins/telegram/session.py +0 -275
- slidge/plugins/telegram/util.py +0 -153
- slidge/plugins/whatsapp/__init__.py +0 -6
- slidge/plugins/whatsapp/config.py +0 -17
- slidge/plugins/whatsapp/contact.py +0 -33
- slidge/plugins/whatsapp/event.go +0 -455
- slidge/plugins/whatsapp/gateway.go +0 -156
- slidge/plugins/whatsapp/gateway.py +0 -69
- slidge/plugins/whatsapp/go.mod +0 -17
- slidge/plugins/whatsapp/go.sum +0 -22
- slidge/plugins/whatsapp/session.go +0 -371
- slidge/plugins/whatsapp/session.py +0 -370
- slidge/util/xep_0030/__init__.py +0 -13
- slidge/util/xep_0030/disco.py +0 -811
- slidge/util/xep_0030/stanza/__init__.py +0 -7
- slidge/util/xep_0030/stanza/info.py +0 -270
- slidge/util/xep_0030/stanza/items.py +0 -147
- slidge/util/xep_0030/static.py +0 -467
- slidge/util/xep_0050/adhoc.py +0 -631
- slidge/util/xep_0050/stanza.py +0 -180
- slidge/util/xep_0077/stanza.py +0 -71
- slidge/util/xep_0292/__init__.py +0 -1
- slidge/util/xep_0292/stanza.py +0 -167
- slidge/util/xep_0292/vcard4.py +0 -74
- slidge/util/xep_0356/__init__.py +0 -7
- slidge/util/xep_0356/permissions.py +0 -35
- slidge/util/xep_0356/privilege.py +0 -160
- slidge/util/xep_0356/stanza.py +0 -44
- slidge/util/xep_0461/__init__.py +0 -6
- slidge/util/xep_0461/reply.py +0 -48
- slidge/util/xep_0461/stanza.py +0 -80
- slidge-0.1.0rc1.dist-info/METADATA +0 -171
- slidge-0.1.0rc1.dist-info/RECORD +0 -99
- /slidge/{plugins/__init__.py → py.typed} +0 -0
- /slidge/{util → slixfix}/xep_0077/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0100/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0100/stanza.py +0 -0
- /slidge/{util → slixfix}/xep_0356_old/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0356_old/stanza.py +0 -0
- {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/LICENSE +0 -0
- {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,167 @@
|
|
1
|
+
from asyncio import Task, create_task
|
2
|
+
from hashlib import sha1
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
5
|
+
|
6
|
+
from slixmpp import JID
|
7
|
+
|
8
|
+
from ...util.types import (
|
9
|
+
URL,
|
10
|
+
AnyBaseSession,
|
11
|
+
AvatarIdType,
|
12
|
+
AvatarType,
|
13
|
+
LegacyFileIdType,
|
14
|
+
)
|
15
|
+
from ..cache import avatar_cache
|
16
|
+
|
17
|
+
if TYPE_CHECKING:
|
18
|
+
from ..pubsub import PepAvatar
|
19
|
+
|
20
|
+
|
21
|
+
class AvatarMixin:
|
22
|
+
"""
|
23
|
+
Mixin for XMPP entities that have avatars that represent them.
|
24
|
+
|
25
|
+
Both :py:class:`slidge.LegacyContact` and :py:class:`slidge.LegacyMUC` use
|
26
|
+
:py:class:`.AvatarMixin`.
|
27
|
+
"""
|
28
|
+
|
29
|
+
jid: JID = NotImplemented
|
30
|
+
session: AnyBaseSession = NotImplemented
|
31
|
+
_avatar_pubsub_broadcast: bool = NotImplemented
|
32
|
+
_avatar_bare_jid: bool = NotImplemented
|
33
|
+
|
34
|
+
def __init__(self) -> None:
|
35
|
+
super().__init__()
|
36
|
+
self._set_avatar_task: Optional[Task] = None
|
37
|
+
self.__avatar_unique_id: Optional[AvatarIdType] = None
|
38
|
+
|
39
|
+
@property
|
40
|
+
def __avatar_jid(self):
|
41
|
+
return JID(self.jid.bare) if self._avatar_bare_jid else self.jid
|
42
|
+
|
43
|
+
@property
|
44
|
+
def avatar_id(self) -> Optional[AvatarIdType]:
|
45
|
+
"""
|
46
|
+
The unique ID of this entity's avatar.
|
47
|
+
"""
|
48
|
+
return self.__avatar_unique_id
|
49
|
+
|
50
|
+
@property
|
51
|
+
def avatar(self) -> Optional[AvatarIdType]:
|
52
|
+
"""
|
53
|
+
This property can be used to set the avatar, but
|
54
|
+
:py:meth:`~.AvatarMixin.set_avatar()` should be preferred because you can
|
55
|
+
provide a unique ID for the avatar for efficient caching.
|
56
|
+
Setting this is OKish in case the avatar type is a URL or a local path
|
57
|
+
that can act as a legacy ID.
|
58
|
+
|
59
|
+
Python's ``property`` is abused here to maintain backwards
|
60
|
+
compatibility, but when getting it you actually get the avatar legacy
|
61
|
+
ID.
|
62
|
+
"""
|
63
|
+
return self.__avatar_unique_id
|
64
|
+
|
65
|
+
@avatar.setter
|
66
|
+
def avatar(self, a: Optional[AvatarType]):
|
67
|
+
if self._set_avatar_task:
|
68
|
+
self._set_avatar_task.cancel()
|
69
|
+
self.session.log.debug("Setting avatar with property")
|
70
|
+
self._set_avatar_task = self.session.xmpp.loop.create_task(
|
71
|
+
self.set_avatar(a, None, blocking=True, cancel=False),
|
72
|
+
name=f"Set avatar of {self} from property",
|
73
|
+
)
|
74
|
+
|
75
|
+
@staticmethod
|
76
|
+
def __get_uid(a: Optional[AvatarType]) -> Optional[AvatarIdType]:
|
77
|
+
if isinstance(a, str):
|
78
|
+
return URL(a)
|
79
|
+
elif isinstance(a, Path):
|
80
|
+
return str(a)
|
81
|
+
elif isinstance(a, bytes):
|
82
|
+
return sha1(a).hexdigest()
|
83
|
+
elif a is None:
|
84
|
+
return None
|
85
|
+
raise TypeError("Bad avatar", a)
|
86
|
+
|
87
|
+
async def __set_avatar(self, a: Optional[AvatarType], uid: Optional[AvatarIdType]):
|
88
|
+
self.__avatar_unique_id = uid
|
89
|
+
await self.session.xmpp.pubsub.set_avatar(
|
90
|
+
jid=self.__avatar_jid,
|
91
|
+
avatar=a,
|
92
|
+
unique_id=None if isinstance(uid, URL) else uid,
|
93
|
+
broadcast_to=self.session.user.jid.bare,
|
94
|
+
broadcast=self._avatar_pubsub_broadcast,
|
95
|
+
)
|
96
|
+
self._post_avatar_update()
|
97
|
+
|
98
|
+
async def _no_change(self, a: Optional[AvatarType], uid: Optional[AvatarIdType]):
|
99
|
+
if a is None:
|
100
|
+
return self.__avatar_unique_id is None
|
101
|
+
if not self.__avatar_unique_id:
|
102
|
+
return False
|
103
|
+
if isinstance(uid, URL):
|
104
|
+
if self.__avatar_unique_id != uid:
|
105
|
+
return False
|
106
|
+
return not await avatar_cache.url_has_changed(uid)
|
107
|
+
return self.__avatar_unique_id == uid
|
108
|
+
|
109
|
+
async def set_avatar(
|
110
|
+
self,
|
111
|
+
a: Optional[AvatarType],
|
112
|
+
avatar_unique_id: Optional[LegacyFileIdType] = None,
|
113
|
+
blocking=False,
|
114
|
+
cancel=True,
|
115
|
+
) -> None:
|
116
|
+
"""
|
117
|
+
Set an avatar for this entity
|
118
|
+
|
119
|
+
:param a:
|
120
|
+
:param avatar_unique_id:
|
121
|
+
:param blocking:
|
122
|
+
:param cancel:
|
123
|
+
"""
|
124
|
+
if avatar_unique_id is None and a is not None:
|
125
|
+
avatar_unique_id = self.__get_uid(a)
|
126
|
+
if await self._no_change(a, avatar_unique_id):
|
127
|
+
return
|
128
|
+
if cancel and self._set_avatar_task:
|
129
|
+
self._set_avatar_task.cancel()
|
130
|
+
awaitable = create_task(
|
131
|
+
self.__set_avatar(a, avatar_unique_id),
|
132
|
+
name=f"Set pubsub avatar of {self}",
|
133
|
+
)
|
134
|
+
if not self._set_avatar_task or self._set_avatar_task.done():
|
135
|
+
self._set_avatar_task = awaitable
|
136
|
+
if blocking:
|
137
|
+
await awaitable
|
138
|
+
|
139
|
+
def get_avatar(self) -> Optional["PepAvatar"]:
|
140
|
+
if not self.__avatar_unique_id:
|
141
|
+
return None
|
142
|
+
return self.session.xmpp.pubsub.get_avatar(self.__avatar_jid)
|
143
|
+
|
144
|
+
def _post_avatar_update(self) -> None:
|
145
|
+
return
|
146
|
+
|
147
|
+
async def avatar_wrap_update_info(self):
|
148
|
+
cached_id = avatar_cache.get_cached_id_for(self.__avatar_jid)
|
149
|
+
self.__avatar_unique_id = cached_id
|
150
|
+
try:
|
151
|
+
await self.update_info() # type:ignore
|
152
|
+
except NotImplementedError:
|
153
|
+
return
|
154
|
+
new_id = self.avatar
|
155
|
+
if isinstance(new_id, URL) and not await avatar_cache.url_has_changed(new_id):
|
156
|
+
return
|
157
|
+
elif new_id != cached_id:
|
158
|
+
# at this point it means that update_info set the avatar, and we don't
|
159
|
+
# need to do anything else
|
160
|
+
return
|
161
|
+
|
162
|
+
await self.session.xmpp.pubsub.set_avatar_from_cache(
|
163
|
+
self.__avatar_jid,
|
164
|
+
new_id is None and cached_id is not None,
|
165
|
+
self.session.user.jid.bare,
|
166
|
+
self._avatar_pubsub_broadcast,
|
167
|
+
)
|
slidge/core/mixins/base.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
from abc import ABCMeta
|
2
|
-
from typing import TYPE_CHECKING
|
2
|
+
from typing import TYPE_CHECKING
|
3
3
|
|
4
|
-
from slixmpp import JID
|
4
|
+
from slixmpp import JID
|
5
5
|
|
6
|
-
from
|
6
|
+
from ...util.types import MessageOrPresenceTypeVar
|
7
7
|
|
8
8
|
if TYPE_CHECKING:
|
9
9
|
from slidge.core.gateway import BaseGateway
|
@@ -25,20 +25,7 @@ class Base:
|
|
25
25
|
|
26
26
|
|
27
27
|
class BaseSender(Base):
|
28
|
-
def _send(
|
28
|
+
def _send(
|
29
|
+
self, stanza: MessageOrPresenceTypeVar, **send_kwargs
|
30
|
+
) -> MessageOrPresenceTypeVar:
|
29
31
|
raise NotImplementedError
|
30
|
-
|
31
|
-
|
32
|
-
class ReactionRecipientMixin:
|
33
|
-
REACTIONS_SINGLE_EMOJI = False
|
34
|
-
|
35
|
-
async def available_emojis(
|
36
|
-
self, legacy_msg_id: LegacyMessageType
|
37
|
-
) -> Optional[set[str]]:
|
38
|
-
"""
|
39
|
-
Override this to restrict the subset of reactions this recipient
|
40
|
-
can handle.
|
41
|
-
|
42
|
-
:return: A set of emojis or None if any emoji is allowed
|
43
|
-
"""
|
44
|
-
return None
|
slidge/core/mixins/disco.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
import
|
1
|
+
from typing import Optional
|
2
2
|
|
3
|
-
from
|
3
|
+
from slixmpp.plugins.xep_0004 import Form
|
4
|
+
from slixmpp.plugins.xep_0030.stanza.info import DiscoInfo
|
5
|
+
from slixmpp.types import OptJid
|
4
6
|
|
5
7
|
from .base import Base
|
6
8
|
|
@@ -11,26 +13,48 @@ class BaseDiscoMixin(Base):
|
|
11
13
|
DISCO_NAME: str = NotImplemented
|
12
14
|
DISCO_LANG = None
|
13
15
|
|
16
|
+
def __init__(self):
|
17
|
+
super().__init__()
|
18
|
+
self.__caps_cache: Optional[str] = None
|
19
|
+
|
20
|
+
def _get_disco_name(self):
|
21
|
+
if self.DISCO_NAME is NotImplemented:
|
22
|
+
return self.xmpp.COMPONENT_NAME
|
23
|
+
return self.DISCO_NAME or self.xmpp.COMPONENT_NAME
|
24
|
+
|
14
25
|
def features(self):
|
15
26
|
return []
|
16
27
|
|
17
|
-
def extended_features(self):
|
18
|
-
return
|
28
|
+
async def extended_features(self) -> Optional[list[Form]]:
|
29
|
+
return None
|
19
30
|
|
20
|
-
def get_disco_info(self):
|
31
|
+
async def get_disco_info(self, jid: OptJid = None, node: Optional[str] = None):
|
21
32
|
info = DiscoInfo()
|
22
33
|
for feature in self.features():
|
23
34
|
info.add_feature(feature)
|
24
35
|
info.add_identity(
|
25
36
|
category=self.DISCO_CATEGORY,
|
26
37
|
itype=self.DISCO_TYPE,
|
27
|
-
name=self.
|
38
|
+
name=self._get_disco_name(),
|
28
39
|
lang=self.DISCO_LANG,
|
29
40
|
)
|
30
|
-
if
|
31
|
-
|
41
|
+
if forms := await self.extended_features():
|
42
|
+
for form in forms:
|
43
|
+
info.append(form)
|
32
44
|
return info
|
33
45
|
|
46
|
+
async def get_caps_ver(self, jid: OptJid = None, node: Optional[str] = None):
|
47
|
+
if self.__caps_cache:
|
48
|
+
return self.__caps_cache
|
49
|
+
info = await self.get_disco_info(jid, node)
|
50
|
+
caps = self.xmpp.plugin["xep_0115"]
|
51
|
+
ver = caps.generate_verstring(info, caps.hash)
|
52
|
+
self.__caps_cache = ver
|
53
|
+
return ver
|
54
|
+
|
55
|
+
def reset_caps_cache(self):
|
56
|
+
self.__caps_cache = None
|
57
|
+
|
34
58
|
|
35
59
|
class ChatterDiscoMixin(BaseDiscoMixin):
|
36
60
|
AVATAR = True
|
@@ -42,6 +66,7 @@ class ChatterDiscoMixin(BaseDiscoMixin):
|
|
42
66
|
REACTION = True
|
43
67
|
RETRACTION = True
|
44
68
|
REPLIES = True
|
69
|
+
INVITATION_RECIPIENT = False
|
45
70
|
|
46
71
|
DISCO_TYPE = "pc"
|
47
72
|
DISCO_CATEGORY = "client"
|
@@ -65,15 +90,41 @@ class ChatterDiscoMixin(BaseDiscoMixin):
|
|
65
90
|
features.append("urn:xmpp:message-retract:0")
|
66
91
|
if self.REPLIES:
|
67
92
|
features.append("urn:xmpp:reply:0")
|
93
|
+
if self.INVITATION_RECIPIENT:
|
94
|
+
features.append("jabber:x:conference")
|
68
95
|
features.append("urn:ietf:params:xml:ns:vcard-4.0")
|
69
96
|
return features
|
70
97
|
|
71
|
-
async def
|
72
|
-
|
73
|
-
|
98
|
+
async def extended_features(self):
|
99
|
+
f = getattr(self, "restricted_emoji_extended_feature", None)
|
100
|
+
if f is None:
|
101
|
+
return
|
74
102
|
|
75
|
-
|
76
|
-
|
77
|
-
|
103
|
+
e = await f()
|
104
|
+
if not e:
|
105
|
+
return
|
78
106
|
|
79
|
-
|
107
|
+
return [e]
|
108
|
+
|
109
|
+
|
110
|
+
class ContactAccountDiscoMixin(BaseDiscoMixin):
|
111
|
+
async def get_disco_info(self, jid: OptJid = None, node: Optional[str] = None):
|
112
|
+
if jid and jid.resource:
|
113
|
+
return await super().get_disco_info()
|
114
|
+
info = DiscoInfo()
|
115
|
+
info.add_feature("http://jabber.org/protocol/pubsub")
|
116
|
+
info.add_feature("http://jabber.org/protocol/pubsub#retrieve-items")
|
117
|
+
info.add_feature("http://jabber.org/protocol/pubsub#subscribe")
|
118
|
+
info.add_identity(
|
119
|
+
category="account",
|
120
|
+
itype="registered",
|
121
|
+
name=self._get_disco_name(),
|
122
|
+
lang=self.DISCO_LANG,
|
123
|
+
)
|
124
|
+
info.add_identity(
|
125
|
+
category="pubsub",
|
126
|
+
itype="pep",
|
127
|
+
name=self._get_disco_name(),
|
128
|
+
lang=self.DISCO_LANG,
|
129
|
+
)
|
130
|
+
return info
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
from contextlib import asynccontextmanager
|
4
|
+
from typing import Hashable
|
5
|
+
|
6
|
+
|
7
|
+
class NamedLockMixin:
|
8
|
+
def __init__(self, *a, **k):
|
9
|
+
super().__init__(*a, **k)
|
10
|
+
self.__locks = dict[Hashable, asyncio.Lock]()
|
11
|
+
|
12
|
+
@asynccontextmanager
|
13
|
+
async def lock(self, id_: Hashable):
|
14
|
+
log.trace("getting %s", id_) # type:ignore
|
15
|
+
locks = self.__locks
|
16
|
+
if not locks.get(id_):
|
17
|
+
locks[id_] = asyncio.Lock()
|
18
|
+
async with locks[id_]:
|
19
|
+
log.trace("acquired %s", id_) # type:ignore
|
20
|
+
yield
|
21
|
+
log.trace("releasing %s", id_) # type:ignore
|
22
|
+
waiters = locks[id_]._waiters # type:ignore
|
23
|
+
if not waiters:
|
24
|
+
del locks[id_]
|
25
|
+
log.trace("erasing %s", id_) # type:ignore
|
26
|
+
|
27
|
+
def get_lock(self, id_: Hashable):
|
28
|
+
return self.__locks.get(id_)
|
29
|
+
|
30
|
+
|
31
|
+
log = logging.getLogger(__name__) # type:ignore
|