Coverage for slidge/core/mixins/db.py: 94%
64 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-10 12:26 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-10 12:26 +0000
1import logging
2import typing
3from contextlib import AbstractContextManager as ContextManager
4from contextlib import contextmanager, nullcontext
6import sqlalchemy as sa
7import sqlalchemy.orm
9from ...db.models import Contact, Participant, Room, Space
10from ...db.track import TrackedDict
11from ...util.types import ExtraAttrMapping
12from .base import SessionBound
14if typing.TYPE_CHECKING:
15 from slidge.util.types import AnyGateway
18class UpdateInfoMixin[
19 T: ExtraAttrMapping = typing.Any,
20 PropsT = typing.Mapping[str, typing.Any],
21](SessionBound):
22 _stored: Contact | Room | Participant | Space
23 xmpp: "AnyGateway"
24 log: logging.Logger
25 _batch_set_attributes_enabled = False
27 def __init__(self, *args: object, **kwargs: object) -> None:
28 super().__init__(*args, **kwargs)
29 self._pending_updates: dict[str, object] = {}
30 self._extra_attributes = typing.cast(
31 T,
32 TrackedDict(
33 typing.cast(ExtraAttrMapping, self._stored.extra_attributes or {}),
34 self._on_extra_attr_change,
35 ),
36 )
38 @classmethod
39 @contextmanager
40 def _batch_set_attributes(cls) -> typing.Generator[None]:
41 cls._batch_set_attributes_enabled = True
42 try:
43 yield
44 finally:
45 cls._batch_set_attributes_enabled = False
47 def batch_set_attributes(
48 self,
49 **kwargs: typing.Unpack[PropsT], # type:ignore[misc]
50 ) -> ContextManager[None]:
51 if not kwargs:
52 return self.__batch_set_attributes_context()
54 with self.__batch_set_attributes_context():
55 for name, value in kwargs.items():
56 setattr(self, name, value)
57 return nullcontext()
59 @contextmanager
60 def __batch_set_attributes_context(self) -> typing.Generator[None]:
61 self._batch_set_attributes_enabled = True
62 try:
63 yield
64 finally:
65 self._batch_set_attributes_enabled = False
66 if self._pending_updates:
67 with self.xmpp.store.session() as orm:
68 orm.execute(
69 sa.update(self._stored.__class__)
70 .where(self._stored.__class__.id == self._stored.id)
71 .values(**self._pending_updates)
72 )
73 orm.commit()
75 def _update_stored_attribute(self, **kwargs: object) -> bool:
76 kwargs = self._stored._normalize_stored_updates(kwargs)
77 for key, value in list(kwargs.items()):
78 try:
79 change = getattr(self._stored, key) != value
80 except sa.orm.exc.DetachedInstanceError:
81 change = True
82 if change:
83 self._stored._set_committed_value(key, value)
84 else:
85 kwargs.pop(key)
86 if self._batch_set_attributes_enabled:
87 self._pending_updates.update(kwargs)
88 return False
89 if kwargs:
90 with self.xmpp.store.session() as orm:
91 orm.execute(
92 sa.update(self._stored.__class__)
93 .where(self._stored.__class__.id == self._stored.id)
94 .values(**kwargs)
95 )
96 orm.commit()
97 return True
98 else:
99 return False
101 @property
102 def extra_attributes(self) -> T:
103 return self._extra_attributes
105 def _on_extra_attr_change(self, new_data: T) -> None:
106 self._update_stored_attribute(extra_attributes=new_data)