Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-mautrix for openSUSE:Factory 
checked in at 2021-03-24 16:15:50
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-mautrix (Old)
 and      /work/SRC/openSUSE:Factory/.python-mautrix.new.2401 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-mautrix"

Wed Mar 24 16:15:50 2021 rev:4 rq:880790 version:0.8.15

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-mautrix/python-mautrix.changes    
2021-01-04 19:09:41.451408682 +0100
+++ /work/SRC/openSUSE:Factory/.python-mautrix.new.2401/python-mautrix.changes  
2021-03-24 16:17:02.244196204 +0100
@@ -1,0 +2,31 @@
+Tue Mar 16 17:58:51 UTC 2021 - Matej Cepl <[email protected]>
+
+- Update to version 0.8.15:
+  - Add option to not use transaction for db upgrades
+  - Return None instead of attr.NOTHING for fields without value
+  - Crash if server doesn't advertise appservice login
+  - Switch BaseFileConfig to use pkgutil instead of pkg_resources
+  - Catch MNotFound when updating m.direct
+  - Log data when deserialization fails
+  - Expose ExtensibleEnum in mautrix.types
+  - Allow postgresql:// scheme in encryption database URL
+  - Add better error message if deserialization fails
+  - Log full data instead of only known fields when failing to
+    deserialize
+  - Automatically retry login if custom puppet start fails
+  - Fix ExtensibleEnum leaking keys between different types
+  - Allow changing bot used in ensure_joined
+  - Add custom puppet relogin when sync fails
+  - Handle MNotFound when getting pinned events
+  - Use same txn_id when retrying sends in bridges
+  - Update client state store with events from sync
+  - Don't check message in whoami forbidden error
+  - Fix fault for rooms without power_levels state event.
+  - Graceful handling of missing or empty event.unsigned object.
+  - Send warning when receiving encrypted messages with e2be
+    disabled
+  - Add utility for async getter locking
+  - Allow overriding asyncpg pool in async_db Database wrapper
+  - Only update state if state_store is set
+
+-------------------------------------------------------------------

Old:
----
  mautrix-0.8.6.tar.gz

New:
----
  mautrix-0.8.15.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-mautrix.spec ++++++
--- /var/tmp/diff_new_pack.olqorh/_old  2021-03-24 16:17:02.784196771 +0100
+++ /var/tmp/diff_new_pack.olqorh/_new  2021-03-24 16:17:02.788196775 +0100
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-mautrix
-Version:        0.8.6
+Version:        0.8.15
 Release:        0
 Summary:        A Python 3 asyncio Matrix framework
 License:        MPL-2.0

++++++ mautrix-0.8.6.tar.gz -> mautrix-0.8.15.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/PKG-INFO new/mautrix-0.8.15/PKG-INFO
--- old/mautrix-0.8.6/PKG-INFO  2020-12-31 14:04:31.496802800 +0100
+++ new/mautrix-0.8.15/PKG-INFO 2021-02-08 14:23:00.381449000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: mautrix
-Version: 0.8.6
+Version: 0.8.15
 Summary: A Python 3 asyncio Matrix framework.
 Home-page: https://github.com/tulir/mautrix-python
 Author: Tulir Asokan
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/__init__.py 
new/mautrix-0.8.15/mautrix/__init__.py
--- old/mautrix-0.8.6/mautrix/__init__.py       2020-12-31 14:04:04.000000000 
+0100
+++ new/mautrix-0.8.15/mautrix/__init__.py      2021-02-08 14:22:35.000000000 
+0100
@@ -1,3 +1,3 @@
-__version__ = "0.8.6"
+__version__ = "0.8.15"
 __author__ = "Tulir Asokan <[email protected]>"
 __all__ = ["api", "appservice", "bridge", "client", "crypto", "errors", 
"util", "types"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/appservice/api/intent.py 
new/mautrix-0.8.15/mautrix/appservice/api/intent.py
--- old/mautrix-0.8.6/mautrix/appservice/api/intent.py  2020-12-31 
14:02:04.000000000 +0100
+++ new/mautrix-0.8.15/mautrix/appservice/api/intent.py 2021-02-06 
18:05:50.000000000 +0100
@@ -14,7 +14,7 @@
                            RoomTopicStateEventContent, 
PowerLevelStateEventContent,
                            RoomPinnedEventsStateEventContent, Membership, 
Member)
 from mautrix.client import ClientAPI, StoreUpdatingAPI
-from mautrix.errors import MForbidden, MBadState, MatrixRequestError, 
IntentError
+from mautrix.errors import MForbidden, MBadState, MatrixRequestError, 
IntentError, MNotFound
 from mautrix.util.logging import TraceLogger
 
 from ..state_store import ASStateStore
@@ -32,6 +32,9 @@
     return urllib_quote(*args, **kwargs, safe="")
 
 
+_bridgebot = object()
+
+
 ENSURE_REGISTERED_METHODS = (
     # Room methods
     ClientAPI.create_room, ClientAPI.add_room_alias, 
ClientAPI.remove_room_alias,
@@ -192,7 +195,10 @@
             levels = await self.state_store.get_power_levels(room_id)
             if levels:
                 return levels
-        levels = await self.get_state_event(room_id, 
EventType.ROOM_POWER_LEVELS)
+        try:
+            levels = await self.get_state_event(room_id, 
EventType.ROOM_POWER_LEVELS)
+        except MNotFound:
+            levels = PowerLevelStateEventContent()
         await self.state_store.set_power_levels(room_id, levels)
         return levels
 
@@ -206,7 +212,10 @@
 
     async def get_pinned_messages(self, room_id: RoomID) -> List[EventID]:
         await self.ensure_joined(room_id)
-        content = await self.get_state_event(room_id, 
EventType.ROOM_PINNED_EVENTS)
+        try:
+            content = await self.get_state_event(room_id, 
EventType.ROOM_PINNED_EVENTS)
+        except MNotFound:
+            return []
         return content["pinned"]
 
     def set_pinned_messages(self, room_id: RoomID, events: List[EventID], 
**kwargs
@@ -311,30 +320,35 @@
     # endregion
     # region Ensure functions
 
-    async def ensure_joined(self, room_id: RoomID, ignore_cache: bool = False) 
-> bool:
+    async def ensure_joined(self, room_id: RoomID, ignore_cache: bool = False,
+                            bot: Optional['IntentAPI'] = _bridgebot) -> bool:
         if not room_id:
             raise ValueError("Room ID not given")
         if not ignore_cache and await self.state_store.is_joined(room_id, 
self.mxid):
             return False
+        if bot is _bridgebot:
+            bot = self.bot
+        if bot is self:
+            bot = None
         await self.ensure_registered()
         try:
             await self.join_room(room_id, max_retries=0)
             await self.state_store.joined(room_id, self.mxid)
         except MForbidden as e:
-            if not self.bot:
+            if not bot:
                 raise IntentError(f"Failed to join room {room_id} as 
{self.mxid}") from e
             try:
-                await self.bot.invite_user(room_id, self.mxid)
+                await bot.invite_user(room_id, self.mxid)
                 await self.join_room(room_id, max_retries=0)
                 await self.state_store.joined(room_id, self.mxid)
             except MatrixRequestError as e2:
                 raise IntentError(f"Failed to join room {room_id} as 
{self.mxid}") from e2
         except MBadState as e:
-            if not self.bot:
+            if not bot:
                 raise IntentError(f"Failed to join room {room_id} as 
{self.mxid}") from e
             try:
-                await self.bot.unban_user(room_id, self.mxid)
-                await self.bot.invite_user(room_id, self.mxid)
+                await bot.unban_user(room_id, self.mxid)
+                await bot.invite_user(room_id, self.mxid)
                 await self.join_room(room_id, max_retries=0)
                 await self.state_store.joined(room_id, self.mxid)
             except MatrixRequestError as e2:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/bridge/__init__.py 
new/mautrix-0.8.15/mautrix/bridge/__init__.py
--- old/mautrix-0.8.6/mautrix/bridge/__init__.py        2020-10-22 
23:13:23.000000000 +0200
+++ new/mautrix-0.8.15/mautrix/bridge/__init__.py       2021-02-07 
12:45:31.000000000 +0100
@@ -6,3 +6,4 @@
 from .puppet import BasePuppet
 from .bridge import Bridge
 from .notification_disabler import NotificationDisabler
+from .async_getter_lock import async_getter_lock
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/bridge/async_getter_lock.py 
new/mautrix-0.8.15/mautrix/bridge/async_getter_lock.py
--- old/mautrix-0.8.6/mautrix/bridge/async_getter_lock.py       1970-01-01 
01:00:00.000000000 +0100
+++ new/mautrix-0.8.15/mautrix/bridge/async_getter_lock.py      2021-02-07 
12:45:31.000000000 +0100
@@ -0,0 +1,16 @@
+# Copyright (c) 2021 Tulir Asokan
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+from typing import Any, Callable, Awaitable
+import functools
+
+
+def async_getter_lock(fn: Callable[[Any, Any], Awaitable[Any]]) -> Any:
+    @functools.wraps(fn)
+    async def wrapper(cls, *args, **kwargs) -> Any:
+        async with cls._async_get_locks[args]:
+            return await fn(cls, *args, **kwargs)
+
+    return wrapper
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/bridge/custom_puppet.py 
new/mautrix-0.8.15/mautrix/bridge/custom_puppet.py
--- old/mautrix-0.8.6/mautrix/bridge/custom_puppet.py   2020-10-27 
22:21:29.000000000 +0100
+++ new/mautrix-0.8.15/mautrix/bridge/custom_puppet.py  2021-01-22 
22:45:47.000000000 +0100
@@ -156,7 +156,8 @@
         data = await resp.json()
         return data["access_token"]
 
-    async def switch_mxid(self, access_token: Optional[str], mxid: 
Optional[UserID]) -> None:
+    async def switch_mxid(self, access_token: Optional[str], mxid: 
Optional[UserID],
+                          start_sync_task: bool = True) -> None:
         """
         Switch to a real Matrix user or away from one.
 
@@ -165,6 +166,7 @@
                           the appservice-owned ID.
             mxid: The expected Matrix user ID of the custom account, or 
``None`` when
                   ``access_token`` is None.
+            start_sync_task: Whether or not syncing should be started after 
logging in.
         """
         if access_token == "auto":
             access_token = await self._login_with_shared_secret(mxid)
@@ -196,7 +198,7 @@
         self.base_url = base_url
         self.intent = self._fresh_intent()
 
-        await self.start()
+        await self.start(start_sync_task=start_sync_task)
 
         try:
             del self.by_custom_mxid[prev_mxid]
@@ -210,13 +212,13 @@
                 self.log.warning("Error when leaving rooms with default user", 
exc_info=True)
         await self.save()
 
-    async def try_start(self) -> None:
+    async def try_start(self, retry_auto_login: bool = True) -> None:
         try:
-            await self.start()
+            await self.start(retry_auto_login=retry_auto_login)
         except Exception:
             self.log.exception("Failed to initialize custom mxid")
 
-    async def start(self) -> None:
+    async def start(self, retry_auto_login: bool = False, start_sync_task: 
bool = True) -> None:
         """Initialize the custom account this puppet uses. Should be called at 
startup to start
         the /sync task. Is called by :meth:`switch_mxid` automatically."""
         if not self.is_real_user:
@@ -224,17 +226,25 @@
 
         try:
             mxid = await self.intent.whoami()
-        except MatrixInvalidToken:
+        except MatrixInvalidToken as e:
+            if retry_auto_login and self.custom_mxid and 
self.can_auto_login(self.custom_mxid):
+                self.log.debug(f"Got {e.errcode} while trying to initialize 
custom mxid")
+                await self.switch_mxid("auto", self.custom_mxid, 
start_sync_task=start_sync_task)
+                return
+            self.log.warning(f"Got {e.errcode} while trying to initialize 
custom mxid")
             mxid = None
         if not mxid or mxid != self.custom_mxid:
+            if self.custom_mxid and self.by_custom_mxid.get(self.custom_mxid) 
== self:
+                del self.by_custom_mxid[self.custom_mxid]
             self.custom_mxid = None
             self.access_token = None
             self.next_batch = None
+            await self.save()
             self.intent = self._fresh_intent()
             if mxid != self.custom_mxid:
                 raise OnlyLoginSelf()
             raise InvalidAccessToken()
-        if self.sync_with_custom_puppets:
+        if self.sync_with_custom_puppets and start_sync_task:
             if self._sync_task:
                 self._sync_task.cancel()
             self.log.info(f"Initialized custom mxid: {mxid}. Starting sync 
task")
@@ -323,8 +333,7 @@
 
         # Deserialize and handle all events
         for event in chain(ephemeral_events, presence_events):
-            
asyncio.ensure_future(self.mx.try_handle_sync_event(Event.deserialize(event)),
-                                  loop=self.loop)
+            
self.loop.create_task(self.mx.try_handle_sync_event(Event.deserialize(event)))
 
     async def _try_sync(self) -> None:
         try:
@@ -355,6 +364,17 @@
                 errors = 0
                 if cur_batch is not None:
                     self._handle_sync(sync_resp)
+            except MatrixInvalidToken:
+                # TODO when not using syncing, we should still check this 
occasionally and relogin
+                self.log.warning(f"Access token for {custom_mxid} got 
invalidated, restarting...")
+                await self.start(retry_auto_login=True, start_sync_task=False)
+                if self.is_real_user:
+                    self.log.info("Successfully relogined custom puppet, 
continuing sync")
+                    filter_id = await self._create_sync_filter()
+                    access_token_at_start = self.access_token
+                else:
+                    self.log.warning("Something went wrong during relogin")
+                    raise
             except (MatrixError, ClientConnectionError, asyncio.TimeoutError) 
as e:
                 errors += 1
                 wait = min(errors, 11) ** 2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/bridge/e2ee.py 
new/mautrix-0.8.15/mautrix/bridge/e2ee.py
--- old/mautrix-0.8.6/mautrix/bridge/e2ee.py    2020-10-27 22:21:29.000000000 
+0100
+++ new/mautrix-0.8.15/mautrix/bridge/e2ee.py   2021-01-21 00:24:58.000000000 
+0100
@@ -64,7 +64,7 @@
         self._share_session_events = {}
         self.key_sharing_config = key_sharing_config or {}
         pickle_key = "mautrix.bridge.e2ee"
-        if db_url.startswith("postgres://"):
+        if db_url.startswith("postgres://") or 
db_url.startswith("postgresql://"):
             if not PgCryptoStore or not PgCryptoStateStore:
                 raise RuntimeError("Database URL is set to postgres, but 
asyncpg is not installed")
             self.crypto_db = Database(url=db_url, 
upgrade_table=PgCryptoStore.upgrade_table,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/bridge/matrix.py 
new/mautrix-0.8.15/mautrix/bridge/matrix.py
--- old/mautrix-0.8.6/mautrix/bridge/matrix.py  2020-12-01 23:30:22.000000000 
+0100
+++ new/mautrix-0.8.15/mautrix/bridge/matrix.py 2021-02-06 18:05:50.000000000 
+0100
@@ -8,13 +8,14 @@
 import asyncio
 import os.path
 import time
+import sys
 
 from yarl import URL
 
 from mautrix.types import (EventID, RoomID, UserID, Event, EventType, 
MessageEvent, MessageType,
                            MessageEventContent, StateEvent, Membership, 
MemberStateEventContent,
                            PresenceEvent, TypingEvent, ReceiptEvent, 
TextMessageEventContent,
-                           EncryptedEvent, ReceiptType, 
SingleReceiptEventContent)
+                           EncryptedEvent, ReceiptType, 
SingleReceiptEventContent, StateUnsigned)
 from mautrix.errors import IntentError, MatrixError, MForbidden, 
DecryptionError, SessionNotFound
 from mautrix.appservice import AppService
 from mautrix.util.logging import TraceLogger
@@ -89,7 +90,7 @@
                 await self.az.intent.whoami()
                 break
             except MForbidden as e:
-                if "has not registered this user" in e.message and not 
tried_to_register:
+                if not tried_to_register:
                     self.log.debug("Whoami endpoint returned M_FORBIDDEN, "
                                    "trying to register bridge bot before 
retrying...")
                     await self.az.intent.ensure_registered()
@@ -124,11 +125,9 @@
     async def init_encryption(self) -> None:
         if self.e2ee:
             if not await self.e2ee.check_server_support():
-                # Element iOS is broken, so the devs decided to break Synapse 
as well, which means
-                # there's no reliable way to check if appservice login is 
supported.
-                # Print a warning in the logs, then try to log in anyway and 
hope for the best.
-                self.log.warning("Encryption enabled in config, but homeserver 
does not advertise "
-                                 "appservice login")
+                self.log.critical("Encryption enabled in config, but 
homeserver does not advertise "
+                                  "appservice login")
+                sys.exit(30)
             await self.e2ee.start()
 
     @staticmethod
@@ -349,6 +348,9 @@
                                          f"\u26a0 Your message was not 
bridged: {error}")
 
     async def handle_encrypted(self, evt: EncryptedEvent) -> None:
+        if not self.e2ee:
+            await self.handle_encrypted_unsupported(evt)
+            return
         try:
             decrypted = await self.e2ee.decrypt(evt, wait_session_timeout=5)
         except SessionNotFound as e:
@@ -360,6 +362,12 @@
         else:
             await self.int_handle_event(decrypted)
 
+    async def handle_encrypted_unsupported(self, evt: EncryptedEvent) -> None:
+        self.log.debug("Got encrypted message %s from %s, but encryption is 
not enabled",
+                       evt.event_id, evt.sender)
+        await self.az.intent.send_notice(evt.room_id, "??????? This bridge has 
not been configured "
+                                                      "to support encryption")
+
     async def _handle_encrypted_wait(self, evt: EncryptedEvent, err: 
SessionNotFound, wait: int
                                      ) -> None:
         self.log.warning(f"Didn't find session {err.session_id}, waiting even 
longer")
@@ -406,7 +414,8 @@
 
         if evt.type == EventType.ROOM_MEMBER:
             evt: StateEvent
-            prev_content = evt.unsigned.prev_content or 
MemberStateEventContent()
+            unsigned = evt.unsigned or StateUnsigned()
+            prev_content = unsigned.prev_content or MemberStateEventContent()
             prev_membership = prev_content.membership if prev_content else 
Membership.JOIN
             if evt.content.membership == Membership.INVITE:
                 await self.int_handle_invite(evt.room_id, 
UserID(evt.state_key), evt.sender,
@@ -441,7 +450,7 @@
             if evt.type != EventType.ROOM_MESSAGE:
                 evt.content.msgtype = MessageType(str(evt.type))
             await self.handle_message(evt.room_id, evt.sender, evt.content, 
evt.event_id)
-        elif evt.type == EventType.ROOM_ENCRYPTED and self.e2ee:
+        elif evt.type == EventType.ROOM_ENCRYPTED:
             await self.handle_encrypted(evt)
         elif evt.type == EventType.ROOM_ENCRYPTION:
             await self.handle_encryption(evt)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/bridge/portal.py 
new/mautrix-0.8.15/mautrix/bridge/portal.py
--- old/mautrix-0.8.6/mautrix/bridge/portal.py  2020-12-01 23:17:17.000000000 
+0100
+++ new/mautrix-0.8.15/mautrix/bridge/portal.py 2021-02-07 12:58:01.000000000 
+0100
@@ -4,6 +4,7 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 from typing import Optional, Dict, Any, List, TYPE_CHECKING
+from collections import defaultdict
 from abc import ABC, abstractmethod
 import asyncio
 import logging
@@ -24,6 +25,7 @@
 
 class BasePortal(ABC):
     log: TraceLogger = logging.getLogger("mau.portal")
+    _async_get_locks: Dict[Any, asyncio.Lock] = defaultdict(lambda: 
asyncio.Lock())
     az: AppService
     matrix: 'BaseMatrixHandler'
     bridge: 'Bridge'
@@ -81,8 +83,9 @@
             event_type, content = await 
call_with_net_retry(self.matrix.e2ee.encrypt, self.mxid,
                                                             event_type, 
content,
                                                             _action="encrypt 
message")
+        txn_id = intent.api.get_txn_id()
         return await call_with_net_retry(intent.send_message_event, self.mxid, 
event_type, content,
-                                         **kwargs, _action="send message")
+                                         txn_id=txn_id, **kwargs, 
_action="send message")
 
     @property
     @abstractmethod
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/bridge/puppet.py 
new/mautrix-0.8.15/mautrix/bridge/puppet.py
--- old/mautrix-0.8.6/mautrix/bridge/puppet.py  2020-10-22 23:13:23.000000000 
+0200
+++ new/mautrix-0.8.15/mautrix/bridge/puppet.py 2021-02-07 12:58:01.000000000 
+0100
@@ -3,7 +3,8 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-from typing import TYPE_CHECKING
+from typing import Dict, Any, TYPE_CHECKING
+from collections import defaultdict
 from abc import ABC, abstractmethod
 import asyncio
 import logging
@@ -20,6 +21,7 @@
 
 class BasePuppet(CustomPuppetMixin, ABC):
     log: TraceLogger = logging.getLogger("mau.puppet")
+    _async_get_locks: Dict[Any, asyncio.Lock] = defaultdict(lambda: 
asyncio.Lock())
     az: AppService
     loop: asyncio.AbstractEventLoop
     mx: 'BaseMatrixHandler'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/bridge/user.py 
new/mautrix-0.8.15/mautrix/bridge/user.py
--- old/mautrix-0.8.6/mautrix/bridge/user.py    2020-10-27 22:21:29.000000000 
+0100
+++ new/mautrix-0.8.15/mautrix/bridge/user.py   2021-02-07 12:58:01.000000000 
+0100
@@ -4,6 +4,7 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 from typing import Dict, Any, Optional, List, TYPE_CHECKING
+from collections import defaultdict
 from abc import ABC, abstractmethod
 import logging
 import asyncio
@@ -25,6 +26,7 @@
 
 class BaseUser(ABC):
     log: TraceLogger = logging.getLogger("mau.user")
+    _async_get_locks: Dict[Any, asyncio.Lock] = defaultdict(lambda: 
asyncio.Lock())
     az: AppService
     bridge: 'Bridge'
     loop: asyncio.AbstractEventLoop
@@ -76,7 +78,10 @@
                                             content=dms, 
headers={"X-Asmux-Auth": self.az.as_token})
         else:
             async with self.dm_update_lock:
-                current_dms = await 
puppet.intent.get_account_data(EventType.DIRECT)
+                try:
+                    current_dms = await 
puppet.intent.get_account_data(EventType.DIRECT)
+                except MNotFound:
+                    current_dms = {}
                 if replace:
                     # Filter away all existing DM statuses with bridge users
                     current_dms = {user: rooms for user, rooms in 
current_dms.items()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/client/api/events.py 
new/mautrix-0.8.15/mautrix/client/api/events.py
--- old/mautrix-0.8.6/mautrix/client/api/events.py      2020-10-22 
23:13:23.000000000 +0200
+++ new/mautrix-0.8.15/mautrix/client/api/events.py     2021-01-31 
19:50:11.000000000 +0100
@@ -286,7 +286,8 @@
             raise MatrixResponseError("`event_id` not in response.")
 
     async def send_message_event(self, room_id: RoomID, event_type: EventType,
-                                 content: EventContent, **kwargs) -> EventID:
+                                 content: EventContent, txn_id: Optional[str] 
= None,
+                                 **kwargs) -> EventID:
         """
         Send a message event to a room. Message events allow access to 
historical events and
         pagination, making them suited for "once-off" activity in a room.
@@ -296,6 +297,7 @@
             room_id: The ID of the room to send the message to.
             event_type: The type of message to send.
             content: The content to send.
+            txn_id: The transaction ID to use.
             **kwargs: Optional parameters to pass to the 
:meth:`HTTPAPI.request` method. Used by
                 :class:`IntentAPI` to pass the timestamp massaging field to
                 :meth:`AppServiceAPI.request`.
@@ -310,7 +312,7 @@
             raise ValueError("Room ID not given")
         elif not event_type:
             raise ValueError("Event type not given")
-        url = Path.rooms[room_id].send[event_type][self.api.get_txn_id()]
+        url = Path.rooms[room_id].send[event_type][txn_id or 
self.api.get_txn_id()]
         content = content.serialize() if isinstance(content, Serializable) 
else content
         resp = await self.api.request(Method.PUT, url, content, **kwargs)
         try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/client/client.py 
new/mautrix-0.8.15/mautrix/client/client.py
--- old/mautrix-0.8.6/mautrix/client/client.py  2020-10-22 23:13:23.000000000 
+0200
+++ new/mautrix-0.8.15/mautrix/client/client.py 2021-02-08 14:22:20.000000000 
+0100
@@ -5,6 +5,8 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 from typing import Optional, TYPE_CHECKING
 
+from mautrix.types import EventType, Event, StateEvent
+
 from .state_store import SyncStore, StateStore
 from .syncer import Syncer
 from .encryption_manager import EncryptingAPI, DecryptionDispatcher
@@ -20,6 +22,12 @@
                  state_store: Optional[StateStore] = None, **kwargs) -> None:
         EncryptingAPI.__init__(self, *args, state_store=state_store, **kwargs)
         Syncer.__init__(self, sync_store)
+        self.add_event_handler(EventType.ALL, self._update_state)
+
+    async def _update_state(self, evt: Event) -> None:
+        if not isinstance(evt, StateEvent) or not self.state_store:
+            return
+        await self.state_store.update_state(evt)
 
     @EncryptingAPI.crypto.setter
     def crypto(self, crypto: 'OlmMachine') -> None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/types/__init__.py 
new/mautrix-0.8.15/mautrix/types/__init__.py
--- old/mautrix-0.8.6/mautrix/types/__init__.py 2020-12-03 13:13:27.000000000 
+0100
+++ new/mautrix-0.8.15/mautrix/types/__init__.py        2021-01-19 
22:23:24.000000000 +0100
@@ -45,4 +45,4 @@
 from .crypto import UnsignedDeviceInfo, DeviceKeys, ClaimKeysResponse, 
QueryKeysResponse
 from .media import MediaRepoConfig, MXOpenGraph, OpenGraphVideo, 
OpenGraphImage, OpenGraphAudio
 from .util import (Obj, Lst, SerializerError, Serializable, SerializableEnum, 
SerializableAttrs,
-                   serializer, deserializer)
+                   serializer, deserializer, ExtensibleEnum)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/types/util/enum.py 
new/mautrix-0.8.15/mautrix/types/util/enum.py
--- old/mautrix-0.8.6/mautrix/types/util/enum.py        2020-10-22 
23:13:23.000000000 +0200
+++ new/mautrix-0.8.15/mautrix/types/util/enum.py       2021-01-22 
22:24:49.000000000 +0100
@@ -25,6 +25,8 @@
                   if not key.startswith("_") and not _is_descriptor(val)]
         classdict = {key: val for key, val in classdict.items()
                      if key.startswith("_") or _is_descriptor(val)}
+        classdict["_by_value"] = {}
+        classdict["_by_key"] = {}
         enum_class = cast(Type['ExtensibleEnum'], super().__new__(mcs, name, 
bases, classdict))
         for key, val in create:
             ExtensibleEnum.__new__(enum_class, val).key = key
@@ -72,15 +74,15 @@
 
 
 class ExtensibleEnum(Serializable, metaclass=ExtensibleEnumMeta):
-    _by_value: Dict[Any, 'ExtensibleEnum'] = {}
-    _by_key: Dict[str, 'ExtensibleEnum'] = {}
+    _by_value: Dict[Any, 'ExtensibleEnum']
+    _by_key: Dict[str, 'ExtensibleEnum']
 
-    _inited: bool = False
+    _inited: bool
     _key: Optional[str]
     value: Any
 
     def __init__(self, value: Any) -> None:
-        if self._inited:
+        if getattr(self, "_inited", False):
             return
         self.value = value
         self._key = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/types/util/serializable.py 
new/mautrix-0.8.15/mautrix/types/util/serializable.py
--- old/mautrix-0.8.6/mautrix/types/util/serializable.py        2020-12-02 
20:04:00.000000000 +0100
+++ new/mautrix-0.8.15/mautrix/types/util/serializable.py       2021-01-20 
23:52:19.000000000 +0100
@@ -33,6 +33,11 @@
     pass
 
 
+class UnknownSerializationError(SerializerError):
+    def __init__(self) -> None:
+        super().__init__("Unknown serialization error")
+
+
 class GenericSerializable(ABC, Generic[T], Serializable):
     """
     An abstract Serializable that adds ``@abstractmethod`` decorators and a 
`Generic[T]` base class.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/mautrix-0.8.6/mautrix/types/util/serializable_attrs.py 
new/mautrix-0.8.15/mautrix/types/util/serializable_attrs.py
--- old/mautrix-0.8.6/mautrix/types/util/serializable_attrs.py  2020-12-31 
14:02:31.000000000 +0100
+++ new/mautrix-0.8.15/mautrix/types/util/serializable_attrs.py 2021-01-21 
23:16:40.000000000 +0100
@@ -5,12 +5,14 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 from typing import Dict, Type, TypeVar, Any, Union, Optional, Tuple, Iterator, 
Callable, NewType
 from uuid import UUID
+import logging
 import attr
 import copy
 import sys
 
 from ..primitive import JSON
-from .serializable import SerializerError, Serializable, GenericSerializable
+from .serializable import (SerializerError, UnknownSerializationError, 
Serializable,
+                           GenericSerializable)
 from .obj import Obj, Lst
 
 T = TypeVar("T")
@@ -26,6 +28,7 @@
 }
 
 no_value = object()
+log = logging.getLogger("mau.attrs")
 
 
 def serializer(elem_type: Type[T]) -> Callable[[Serializer], Serializer]:
@@ -90,6 +93,8 @@
 def _safe_default(val: T) -> T:
     if isinstance(val, immutable):
         return val
+    elif val is attr.NOTHING:
+        return None
     return copy.copy(val)
 
 
@@ -109,8 +114,17 @@
             unrecognized[key] = value
             continue
         name = field.name.lstrip("_")
-        new_items[name] = _try_deserialize(field.type, value, field.default,
-                                           field.metadata.get("ignore_errors", 
False))
+        try:
+            new_items[name] = _try_deserialize(field.type, value, 
field.default,
+                                               
field.metadata.get("ignore_errors", False))
+        except UnknownSerializationError as e:
+            raise SerializerError(f"Failed to deserialize {value} into "
+                                  f"key {name} of {attrs_type.__name__}") from 
e
+        except SerializerError:
+            raise
+        except Exception as e:
+            raise SerializerError(f"Failed to deserialize {value} into "
+                                  f"key {name} of {attrs_type.__name__}") from 
e
     if len(new_items) == 0 and default_if_empty and default is not 
attr.NOTHING:
         return _safe_default(default)
     try:
@@ -119,9 +133,10 @@
         for key, field in _fields(attrs_type):
             json_key = field.metadata.get("json", key)
             if field.default is attr.NOTHING and json_key not in new_items:
-                raise SerializerError(
-                    f"Missing value for required key {field.name} in 
{attrs_type.__name__}") from e
-        raise SerializerError("Unknown serialization error") from e
+                log.debug("Failed to deserialize %s into %s", data, 
attrs_type.__name__)
+                raise SerializerError("Missing value for required key "
+                                      f"{field.name} in 
{attrs_type.__name__}") from e
+        raise UnknownSerializationError() from e
     if len(unrecognized) > 0:
         obj.unrecognized_ = unrecognized
     return obj
@@ -136,7 +151,7 @@
             raise
     except (TypeError, ValueError, KeyError) as e:
         if not ignore_errors:
-            raise SerializerError("Unknown serialization error") from e
+            raise UnknownSerializationError() from e
 
 
 def _has_custom_deserializer(cls) -> bool:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/util/async_db/database.py 
new/mautrix-0.8.15/mautrix/util/async_db/database.py
--- old/mautrix-0.8.6/mautrix/util/async_db/database.py 2020-10-22 
23:13:23.000000000 +0200
+++ new/mautrix-0.8.15/mautrix/util/async_db/database.py        2021-02-07 
13:41:43.000000000 +0100
@@ -29,6 +29,7 @@
     log: logging.Logger
 
     _pool: Optional[asyncpg.pool.Pool]
+    _pool_override: bool
     db_args: Dict[str, Any]
     upgrade_table: UpgradeTable
 
@@ -37,7 +38,8 @@
     def __init__(self, url: str, db_args: Optional[Dict[str, Any]] = None,
                  upgrade_table: Union[None, UpgradeTable, str] = None,
                  log: Optional[logging.Logger] = None,
-                 loop: Optional[asyncio.AbstractEventLoop] = None) -> None:
+                 loop: Optional[asyncio.AbstractEventLoop] = None,
+                 pool_override: Optional[asyncpg.pool.Pool] = None) -> None:
         self.url = url
         self.db_args = db_args or {}
         if isinstance(upgrade_table, str):
@@ -48,14 +50,20 @@
             self.upgrade_table = UpgradeTable()
         else:
             raise ValueError(f"Can't use {type(upgrade_table)} as the upgrade 
table")
-        self._pool = None
+        self._pool = pool_override
+        self._pool_override = bool(pool_override)
         self.log = log or logging.getLogger("mau.db")
         self.loop = loop or asyncio.get_event_loop()
 
+    def override_pool(self, pool: asyncpg.pool.Pool) -> None:
+        self._pool = pool
+        self._pool_override = True
+
     async def start(self) -> None:
-        self.db_args["loop"] = self.loop
-        self.log.debug(f"Connecting to {self.url}")
-        self._pool = await asyncpg.create_pool(self.url, **self.db_args)
+        if not self._pool_override:
+            self.db_args["loop"] = self.loop
+            self.log.debug(f"Connecting to {self.url}")
+            self._pool = await asyncpg.create_pool(self.url, **self.db_args)
         try:
             await self.upgrade_table.upgrade(self.pool)
         except Exception:
@@ -69,7 +77,8 @@
         return self._pool
 
     async def stop(self) -> None:
-        await self.pool.close()
+        if not self._pool_override:
+            await self.pool.close()
 
     async def execute(self, query: str, *args: Any, timeout: Optional[float] = 
None) -> str:
         async with self.acquire() as conn:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/util/async_db/upgrade.py 
new/mautrix-0.8.15/mautrix/util/async_db/upgrade.py
--- old/mautrix-0.8.6/mautrix/util/async_db/upgrade.py  2020-10-22 
23:13:23.000000000 +0200
+++ new/mautrix-0.8.15/mautrix/util/async_db/upgrade.py 2021-01-15 
15:46:33.000000000 +0100
@@ -37,14 +37,15 @@
         self.database_name = database_name
         self.log = log or logging.getLogger("mau.db.upgrade")
 
-    def register(self, index: int = -1, description: str = "", _outer_fn: 
Optional[Upgrade] = None
-                 ) -> Union[Upgrade, Callable[[Upgrade], Upgrade]]:
+    def register(self, index: int = -1, description: str = "", _outer_fn: 
Optional[Upgrade] = None,
+                 transaction: bool = True) -> Union[Upgrade, 
Callable[[Upgrade], Upgrade]]:
         if isinstance(index, str):
             description = index
             index = -1
 
         def actually_register(fn: Upgrade) -> Upgrade:
             fn.__mau_db_upgrade_description__ = description
+            fn.__mau_db_upgrade_transaction__ = transaction
             if index == -1 or index == len(self.upgrades):
                 self.upgrades.append(fn)
             else:
@@ -88,7 +89,12 @@
                 suffix = f": {desc}" if desc else ""
                 self.log.debug(f"Upgrading {self.database_name} "
                                f"from v{version} to v{new_version}{suffix}")
-                async with conn.transaction():
+                if getattr(upgrade, "__mau_db_upgrade_transaction", True):
+                    async with conn.transaction():
+                        await upgrade(conn)
+                        version = new_version
+                        await self._save_version(conn, version)
+                else:
                     await upgrade(conn)
                     version = new_version
                     await self._save_version(conn, version)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/util/config/file.py 
new/mautrix-0.8.15/mautrix/util/config/file.py
--- old/mautrix-0.8.6/mautrix/util/config/file.py       2020-10-27 
22:21:29.000000000 +0100
+++ new/mautrix-0.8.15/mautrix/util/config/file.py      2021-01-15 
23:07:30.000000000 +0100
@@ -5,6 +5,7 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 from typing import Optional
 from abc import ABC
+import pkgutil
 import logging
 
 from yarl import URL
@@ -14,11 +15,6 @@
 from .base import BaseConfig
 from .recursive_dict import RecursiveDict
 
-try:
-    import pkg_resources
-except ImportError:
-    pkg_resources = None
-
 yaml = YAML()
 yaml.indent(4)
 yaml.width = 200
@@ -39,11 +35,8 @@
 
     def load_base(self) -> Optional[RecursiveDict[CommentedMap]]:
         if self.base_path.startswith("pkg://"):
-            if pkg_resources is None:
-                raise ValueError("pkg:// paths can only be used with 
setuptools installed")
             url = URL(self.base_path)
-            return 
RecursiveDict(yaml.load(pkg_resources.resource_stream(url.host, url.path)),
-                                 CommentedMap)
+            return RecursiveDict(yaml.load(pkgutil.get_data(url.host, 
url.path)), CommentedMap)
         try:
             with open(self.base_path, 'r') as stream:
                 return RecursiveDict(yaml.load(stream), CommentedMap)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix/util/program.py 
new/mautrix-0.8.15/mautrix/util/program.py
--- old/mautrix-0.8.6/mautrix/util/program.py   2020-10-22 23:13:23.000000000 
+0200
+++ new/mautrix-0.8.15/mautrix/util/program.py  2021-01-15 19:57:38.000000000 
+0100
@@ -190,7 +190,7 @@
             self.loop.run_until_complete(self.start())
             end_ts = time()
             self.log.info(f"Startup actions complete in {round(end_ts - 
start_ts, 2)} seconds, "
-                           "now running forever")
+                          "now running forever")
             self._stop_task = self.loop.create_future()
             self.loop.run_until_complete(self._stop_task)
             self.log.debug("manual_stop() called, stopping...")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix.egg-info/PKG-INFO 
new/mautrix-0.8.15/mautrix.egg-info/PKG-INFO
--- old/mautrix-0.8.6/mautrix.egg-info/PKG-INFO 2020-12-31 14:04:31.000000000 
+0100
+++ new/mautrix-0.8.15/mautrix.egg-info/PKG-INFO        2021-02-08 
14:23:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: mautrix
-Version: 0.8.6
+Version: 0.8.15
 Summary: A Python 3 asyncio Matrix framework.
 Home-page: https://github.com/tulir/mautrix-python
 Author: Tulir Asokan
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mautrix-0.8.6/mautrix.egg-info/SOURCES.txt 
new/mautrix-0.8.15/mautrix.egg-info/SOURCES.txt
--- old/mautrix-0.8.6/mautrix.egg-info/SOURCES.txt      2020-12-31 
14:04:31.000000000 +0100
+++ new/mautrix-0.8.15/mautrix.egg-info/SOURCES.txt     2021-02-08 
14:23:00.000000000 +0100
@@ -24,6 +24,7 @@
 mautrix/appservice/state_store/sqlalchemy.py
 mautrix/bridge/__init__.py
 mautrix/bridge/_community.py
+mautrix/bridge/async_getter_lock.py
 mautrix/bridge/bridge.py
 mautrix/bridge/config.py
 mautrix/bridge/crypto_state_store.py

Reply via email to