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
