--- /dev/null
+.. change::
+ :tags: bug, orm
+ :tickets: 10175
+
+ Fixed issue where dictionary-based collections such as
+ :func:`_orm.attribute_keyed_dict` did not fully pickle/unpickle correctly,
+ leading to issues when attempting to mutate such a collection after
+ unpickling.
+
from . import base
from .collections import collection
+from .collections import collection_adapter
from .. import exc as sa_exc
from .. import util
from ..sql import coercions
if TYPE_CHECKING:
from . import AttributeEventToken
from . import Mapper
+ from .collections import CollectionAdapter
from ..sql.elements import ColumnElement
_KT = TypeVar("_KT", bound=Any)
@classmethod
def _unreduce(
- cls, keyfunc: _F, values: Dict[_KT, _KT]
+ cls,
+ keyfunc: _F,
+ values: Dict[_KT, _KT],
+ adapter: Optional[CollectionAdapter] = None,
) -> "KeyFuncDict[_KT, _KT]":
mp: KeyFuncDict[_KT, _KT] = KeyFuncDict(keyfunc)
mp.update(values)
+ # note that the adapter sets itself up onto this collection
+ # when its `__setstate__` method is called
return mp
def __reduce__(
self,
) -> Tuple[
Callable[[_KT, _KT], KeyFuncDict[_KT, _KT]],
- Tuple[Any, Union[Dict[_KT, _KT], Dict[_KT, _KT]]],
+ Tuple[Any, Union[Dict[_KT, _KT], Dict[_KT, _KT]], CollectionAdapter],
]:
- return (KeyFuncDict._unreduce, (self.keyfunc, dict(self)))
+ return (
+ KeyFuncDict._unreduce,
+ (
+ self.keyfunc,
+ dict(self),
+ collection_adapter(self),
+ ),
+ )
@util.preload_module("sqlalchemy.orm.attributes")
def _raise_for_unpopulated(
from sqlalchemy.orm import aliased
from sqlalchemy.orm import attributes
from sqlalchemy.orm import clear_mappers
+from sqlalchemy.orm import collections
from sqlalchemy.orm import exc as orm_exc
from sqlalchemy.orm import lazyload
from sqlalchemy.orm import relationship
from sqlalchemy.testing import assert_raises_message
from sqlalchemy.testing import eq_
from sqlalchemy.testing import fixtures
+from sqlalchemy.testing import is_not_none
from sqlalchemy.testing.fixtures import fixture_session
from sqlalchemy.testing.pickleable import Address
from sqlalchemy.testing.pickleable import AddressWMixin
eq_(u1.addresses, repickled.addresses)
eq_(repickled.addresses["email1"], Address(email_address="email1"))
+ is_not_none(collections.collection_adapter(repickled.addresses))
+
def test_column_mapped_collection(self):
users, addresses = self.tables.users, self.tables.addresses
eq_(u1.addresses, repickled.addresses)
eq_(repickled.addresses["email1"], Address(email_address="email1"))
+ is_not_none(collections.collection_adapter(repickled.addresses))
+
def test_composite_column_mapped_collection(self):
users, addresses = self.tables.users, self.tables.addresses
repickled.addresses[(1, "email1")],
Address(id=1, email_address="email1"),
)
+ is_not_none(collections.collection_adapter(repickled.addresses))
class OptionsTest(_Polymorphic):