]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
rename MappedCollection and related
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 7 Oct 2022 18:03:16 +0000 (14:03 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 11 Oct 2022 14:23:08 +0000 (10:23 -0400)
For consistency with the prominent ORM concept :class:`_orm.Mapped`, the
names of the dictionary-oriented collections,
:func:`_orm.attribute_mapped_collection`,
:func:`_orm.column_mapped_collection`, and :class:`_orm.MappedCollection`,
are changed to :func:`_orm.attribute_keyed_dict`,
:func:`_orm.column_keyed_dict` and :class:`_orm.KeyFuncDict`, using the
phrase "dict" to minimize any confusion against the term "mapped". The old
names will remain indefinitely with no schedule for removal.

Docs here are also updated for typing as we can type
these collections as ``Mapped[dict[str, cls]]``, don't need
KeyFuncDict / MappedCollection for these

Fixes: #8608
Change-Id: Ib5cf63e0aef1c389e023a75e454bb21f9d779b54

23 files changed:
doc/build/changelog/unreleased_20/8608.rst [new file with mode: 0644]
doc/build/orm/collection_api.rst
doc/build/orm/extensions/associationproxy.rst
examples/adjacency_list/adjacency_list.py
examples/association/dict_of_sets_with_default.py
examples/versioned_rows/versioned_map.py
examples/vertical/dictlike-polymorphic.py
examples/vertical/dictlike.py
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/collections.py
lib/sqlalchemy/orm/mapped_collection.py
test/ext/mypy/plain_files/keyfunc_dict.py [new file with mode: 0644]
test/ext/mypy/plain_files/trad_relationship_uselist.py
test/ext/test_associationproxy.py
test/orm/declarative/test_tm_future_annotations.py
test/orm/declarative/test_typed_mapping.py
test/orm/test_attributes.py
test/orm/test_cascade.py
test/orm/test_collection.py
test/orm/test_deprecations.py
test/orm/test_merge.py
test/orm/test_pickled.py
test/orm/test_validators.py

diff --git a/doc/build/changelog/unreleased_20/8608.rst b/doc/build/changelog/unreleased_20/8608.rst
new file mode 100644 (file)
index 0000000..548e8d6
--- /dev/null
@@ -0,0 +1,12 @@
+.. change::
+    :tags: orm, change
+    :tickets: 8608
+
+    For consistency with the prominent ORM concept :class:`_orm.Mapped`, the
+    names of the dictionary-oriented collections,
+    :func:`_orm.attribute_mapped_collection`,
+    :func:`_orm.column_mapped_collection`, and :class:`_orm.MappedCollection`,
+    are changed to :func:`_orm.attribute_keyed_dict`,
+    :func:`_orm.column_keyed_dict` and :class:`_orm.KeyFuncDict`, using the
+    phrase "dict" to minimize any confusion against the term "mapped". The old
+    names will remain indefinitely with no schedule for removal.
index 8f830f4274516498c62621f6521e709ff70a5683..aba0ca104c8c42871ed79ae41e244f8afa4af29e 100644 (file)
@@ -15,7 +15,6 @@ This section presents additional information about collection configuration
 and techniques.
 
 
-.. currentmodule:: sqlalchemy.orm.collections
 
 .. _custom_collections:
 
@@ -144,25 +143,25 @@ Dictionary Collections
 A little extra detail is needed when using a dictionary as a collection.
 This because objects are always loaded from the database as lists, and a key-generation
 strategy must be available to populate the dictionary correctly.  The
-:func:`.attribute_mapped_collection` function is by far the most common way
+:func:`.attribute_keyed_dict` function is by far the most common way
 to achieve a simple dictionary collection.  It produces a dictionary class that will apply a particular attribute
 of the mapped class as a key.   Below we map an ``Item`` class containing
 a dictionary of ``Note`` items keyed to the ``Note.keyword`` attribute.
-When using :func:`.attribute_mapped_collection`, the :class:`_orm.Mapped`
-annotation may be typed using the :class:`_orm.MappedCollection`
-type, however the :paramref:`_orm.relationship.collection_class` parameter
-is required in this case so that the :func:`.attribute_mapped_collection`
+When using :func:`.attribute_keyed_dict`, the :class:`_orm.Mapped`
+annotation may be typed using the :class:`_orm.KeyFuncDict`
+or just plain ``dict`` as illustrated in the following example.   However,
+the :paramref:`_orm.relationship.collection_class` parameter
+is required in this case so that the :func:`.attribute_keyed_dict`
 may be appropriately parametrized::
 
     from typing import Optional
 
     from sqlalchemy import ForeignKey
-    from sqlalchemy.orm import attribute_mapped_collection
+    from sqlalchemy.orm import attribute_keyed_dict
     from sqlalchemy.orm import DeclarativeBase
     from sqlalchemy.orm import Mapped
     from sqlalchemy.orm import mapped_column
     from sqlalchemy.orm import relationship
-    from sqlalchemy.orm import MappedCollection
 
 
     class Base(DeclarativeBase):
@@ -174,8 +173,8 @@ may be appropriately parametrized::
 
         id: Mapped[int] = mapped_column(primary_key=True)
 
-        notes: Mapped[MappedCollection[str, "Note"]] = relationship(
-            collection_class=attribute_mapped_collection("keyword"),
+        notes: Mapped[dict[str, "Note"]] = relationship(
+            collection_class=attribute_keyed_dict("keyword"),
             cascade="all, delete-orphan",
         )
 
@@ -199,7 +198,7 @@ may be appropriately parametrized::
     >>> item.notes.items()
     {'a': <__main__.Note object at 0x2eaaf0>}
 
-:func:`.attribute_mapped_collection` will ensure that
+:func:`.attribute_keyed_dict` will ensure that
 the ``.keyword`` attribute of each ``Note`` complies with the key in the
 dictionary.   Such as, when assigning to ``Item.notes``, the dictionary
 key we supply must match that of the actual ``Note`` object::
@@ -210,7 +209,7 @@ key we supply must match that of the actual ``Note`` object::
         "b": Note("b", "btext"),
     }
 
-The attribute which :func:`.attribute_mapped_collection` uses as a key
+The attribute which :func:`.attribute_keyed_dict` uses as a key
 does not need to be mapped at all!  Using a regular Python ``@property`` allows virtually
 any detail or combination of details about the object to be used as the key, as
 below when we establish it as a tuple of ``Note.keyword`` and the first ten letters
@@ -221,8 +220,8 @@ of the ``Note.text`` field::
 
         id: Mapped[int] = mapped_column(primary_key=True)
 
-        notes: Mapped[MappedCollection[str, "Note"]] = relationship(
-            collection_class=attribute_mapped_collection("note_key"),
+        notes: Mapped[dict[str, "Note"]] = relationship(
+            collection_class=attribute_keyed_dict("note_key"),
             back_populates="item",
             cascade="all, delete-orphan",
         )
@@ -257,11 +256,11 @@ is added to the ``Item.notes`` dictionary and the key is generated for us automa
     >>> item.notes
     {('a', 'atext'): <__main__.Note object at 0x2eaaf0>}
 
-Other built-in dictionary types include :func:`.column_mapped_collection`,
-which is almost like :func:`.attribute_mapped_collection` except given the :class:`_schema.Column`
+Other built-in dictionary types include :func:`.column_keyed_dict`,
+which is almost like :func:`.attribute_keyed_dict` except given the :class:`_schema.Column`
 object directly::
 
-    from sqlalchemy.orm import column_mapped_collection
+    from sqlalchemy.orm import column_keyed_dict
 
 
     class Item(Base):
@@ -269,13 +268,13 @@ object directly::
 
         id: Mapped[int] = mapped_column(primary_key=True)
 
-        notes: Mapped[MappedCollection[str, "Note"]] = relationship(
-            collection_class=column_mapped_collection(Note.__table__.c.keyword),
+        notes: Mapped[dict[str, "Note"]] = relationship(
+            collection_class=column_keyed_dict(Note.__table__.c.keyword),
             cascade="all, delete-orphan",
         )
 
 as well as :func:`.mapped_collection` which is passed any callable function.
-Note that it's usually easier to use :func:`.attribute_mapped_collection` along
+Note that it's usually easier to use :func:`.attribute_keyed_dict` along
 with a ``@property`` as mentioned earlier::
 
     from sqlalchemy.orm import mapped_collection
@@ -286,7 +285,7 @@ with a ``@property`` as mentioned earlier::
 
         id: Mapped[int] = mapped_column(primary_key=True)
 
-        notes: Mapped[MappedCollection[str, "Note"]] = relationship(
+        notes: Mapped[dict[str, "Note"]] = relationship(
             collection_class=mapped_collection(lambda note: note.text[0:10]),
             cascade="all, delete-orphan",
         )
@@ -300,7 +299,7 @@ for examples.
 Dealing with Key Mutations and back-populating for Dictionary collections
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-When using :func:`.attribute_mapped_collection`, the "key" for the dictionary
+When using :func:`.attribute_keyed_dict`, the "key" for the dictionary
 is taken from an attribute on the target object.   **Changes to this key
 are not tracked**.  This means that the key must be assigned towards when
 it is first used, and if the key changes, the collection will not be mutated.
@@ -312,8 +311,8 @@ to populate an attribute mapped collection.  Given the following::
 
         id: Mapped[int] = mapped_column(primary_key=True)
 
-        bs: Mapped[MappedCollection[str, "B"]] = relationship(
-            collection_class=attribute_mapped_collection("data"),
+        bs: Mapped[dict[str, "B"]] = relationship(
+            collection_class=attribute_keyed_dict("data"),
             back_populates="a",
         )
 
@@ -376,12 +375,6 @@ collection as well::
             obj.a.bs[value] = obj
             obj.a.bs.pop(previous)
 
-.. autofunction:: attribute_mapped_collection
-
-.. autofunction:: column_mapped_collection
-
-.. autofunction:: mapped_collection
-
 .. _orm_custom_collection:
 
 Custom Collection Implementations
@@ -547,44 +540,41 @@ interface marked for SQLAlchemy's use. Append and remove methods will be
 called with a mapped entity as the single argument, and iterator methods are
 called with no arguments and must return an iterator.
 
-.. autoclass:: collection
-    :members:
-
 .. _dictionary_collections:
 
 Custom Dictionary-Based Collections
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-The :class:`.MappedCollection` class can be used as
+The :class:`.KeyFuncDict` class can be used as
 a base class for your custom types or as a mix-in to quickly add ``dict``
 collection support to other classes. It uses a keying function to delegate to
 ``__setitem__`` and ``__delitem__``:
 
 .. sourcecode:: python+sql
 
-    from sqlalchemy.orm.collections import MappedCollection
+    from sqlalchemy.orm.collections import KeyFuncDict
 
 
-    class MyNodeMap(MappedCollection):
+    class MyNodeMap(KeyFuncDict):
         """Holds 'Node' objects, keyed by the 'name' attribute."""
 
         def __init__(self, *args, **kw):
             super().__init__(keyfunc=lambda node: node.name)
             dict.__init__(self, *args, **kw)
 
-When subclassing :class:`.MappedCollection`, user-defined versions
+When subclassing :class:`.KeyFuncDict`, user-defined versions
 of ``__setitem__()`` or ``__delitem__()`` should be decorated
 with :meth:`.collection.internally_instrumented`, **if** they call down
-to those same methods on :class:`.MappedCollection`.  This because the methods
-on :class:`.MappedCollection` are already instrumented - calling them
+to those same methods on :class:`.KeyFuncDict`.  This because the methods
+on :class:`.KeyFuncDict` are already instrumented - calling them
 from within an already instrumented call can cause events to be fired off
 repeatedly, or inappropriately, leading to internal state corruption in
 rare cases::
 
-    from sqlalchemy.orm.collections import MappedCollection, collection
+    from sqlalchemy.orm.collections import KeyFuncDict, collection
 
 
-    class MyMappedCollection(MappedCollection):
+    class MyKeyFuncDict(KeyFuncDict):
         """Use @internally_instrumented when your methods
         call down to already-instrumented methods.
 
@@ -593,12 +583,12 @@ rare cases::
         @collection.internally_instrumented
         def __setitem__(self, key, value, _sa_initiator=None):
             # do something with key, value
-            super(MyMappedCollection, self).__setitem__(key, value, _sa_initiator)
+            super(MyKeyFuncDict, self).__setitem__(key, value, _sa_initiator)
 
         @collection.internally_instrumented
         def __delitem__(self, key, _sa_initiator=None):
             # do something with key
-            super(MyMappedCollection, self).__delitem__(key, _sa_initiator)
+            super(MyKeyFuncDict, self).__delitem__(key, _sa_initiator)
 
 The ORM understands the ``dict`` interface just like lists and sets, and will
 automatically instrument all "dict-like" methods if you choose to subclass
@@ -607,8 +597,6 @@ must decorate appender and remover methods, however- there are no compatible
 methods in the basic dictionary interface for SQLAlchemy to use by default.
 Iteration will go through ``values()`` unless otherwise decorated.
 
-.. autoclass:: sqlalchemy.orm.MappedCollection
-   :members:
 
 Instrumentation and Custom Types
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -634,13 +622,39 @@ to restrict the decorations to just your usage in relationships. For example:
 The ORM uses this approach for built-ins, quietly substituting a trivial
 subclass when a ``list``, ``set`` or ``dict`` is used directly.
 
+Collection API
+-----------------------------
+
+.. currentmodule:: sqlalchemy.orm
+
+.. autofunction:: attribute_keyed_dict
+
+.. autofunction:: column_keyed_dict
+
+.. autofunction:: keyfunc_mapping
+
+.. autodata:: attribute_mapped_collection
+
+.. autodata:: column_mapped_collection
+
+.. autodata:: mapped_collection
+
+.. autoclass:: sqlalchemy.orm.KeyFuncDict
+   :members:
+
+.. autodata:: sqlalchemy.orm.MappedCollection
+
+
 Collection Internals
---------------------
+-----------------------------
 
-Various internal methods.
+.. currentmodule:: sqlalchemy.orm.collections
 
 .. autofunction:: bulk_replace
 
+.. autoclass:: collection
+    :members:
+
 .. autodata:: collection_adapter
 
 .. autoclass:: CollectionAdapter
index 184074e9e6a713d9e027851279aff122c031f980..de85bea6432e6ea4d3f62d1bba017ceaaf407957 100644 (file)
@@ -281,7 +281,7 @@ Proxying to Dictionary Based Collections
 ----------------------------------------
 
 The association proxy can proxy to dictionary based collections as well.   SQLAlchemy
-mappings usually use the :func:`.attribute_mapped_collection` collection type to
+mappings usually use the :func:`.attribute_keyed_dict` collection type to
 create dictionary collections, as well as the extended techniques described in
 :ref:`dictionary_collections`.
 
@@ -301,7 +301,7 @@ when new elements are added to the dictionary::
     from sqlalchemy import Column, ForeignKey, Integer, String
     from sqlalchemy.ext.associationproxy import association_proxy
     from sqlalchemy.orm import DeclarativeBase, relationship
-    from sqlalchemy.orm.collections import attribute_mapped_collection
+    from sqlalchemy.orm.collections import attribute_keyed_dict
 
 
     class Base(DeclarativeBase):
@@ -318,7 +318,7 @@ when new elements are added to the dictionary::
         user_keyword_associations = relationship(
             "UserKeywordAssociation",
             back_populates="user",
-            collection_class=attribute_mapped_collection("special_key"),
+            collection_class=attribute_keyed_dict("special_key"),
             cascade="all, delete-orphan",
         )
         # proxy to 'user_keyword_associations', instantiating
@@ -386,7 +386,7 @@ present on ``UserKeywordAssociation``::
     from sqlalchemy import Column, ForeignKey, Integer, String
     from sqlalchemy.ext.associationproxy import association_proxy
     from sqlalchemy.orm import DeclarativeBase, relationship
-    from sqlalchemy.orm.collections import attribute_mapped_collection
+    from sqlalchemy.orm.collections import attribute_keyed_dict
 
 
     class Base(DeclarativeBase):
@@ -401,7 +401,7 @@ present on ``UserKeywordAssociation``::
         user_keyword_associations = relationship(
             "UserKeywordAssociation",
             back_populates="user",
-            collection_class=attribute_mapped_collection("special_key"),
+            collection_class=attribute_keyed_dict("special_key"),
             cascade="all, delete-orphan",
         )
         # the same 'user_keyword_associations'->'keyword' proxy as in
@@ -496,7 +496,7 @@ to a related object, as in the example mapping below::
     from sqlalchemy import Column, ForeignKey, Integer, String
     from sqlalchemy.ext.associationproxy import association_proxy
     from sqlalchemy.orm import DeclarativeBase, relationship
-    from sqlalchemy.orm.collections import attribute_mapped_collection
+    from sqlalchemy.orm.collections import attribute_keyed_dict
 
 
     class Base(DeclarativeBase):
index fee0f413f66919b5110ae6b23c94f96777e31abc..38503f9f333f705e4bc81e6eb600d9a3bcf36088 100644 (file)
@@ -8,7 +8,7 @@ from sqlalchemy.orm import backref
 from sqlalchemy.orm import joinedload
 from sqlalchemy.orm import relationship
 from sqlalchemy.orm import Session
-from sqlalchemy.orm.collections import attribute_mapped_collection
+from sqlalchemy.orm.collections import attribute_keyed_dict
 
 
 Base = declarative_base()
@@ -30,7 +30,7 @@ class TreeNode(Base):
         backref=backref("parent", remote_side=id),
         # children will be represented as a dictionary
         # on the "name" attribute.
-        collection_class=attribute_mapped_collection("name"),
+        collection_class=attribute_keyed_dict("name"),
     )
 
     def __init__(self, name, parent=None):
index 14045b7f56126d3b9d3cc0c224e831915f19a5e5..96e30c1e28e6e80c45e218c2034f20b1a9a44e2b 100644 (file)
@@ -23,7 +23,7 @@ from sqlalchemy.ext.associationproxy import association_proxy
 from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.orm import relationship
 from sqlalchemy.orm import Session
-from sqlalchemy.orm.collections import MappedCollection
+from sqlalchemy.orm.collections import KeyFuncDict
 
 
 class Base:
@@ -33,7 +33,7 @@ class Base:
 Base = declarative_base(cls=Base)
 
 
-class GenDefaultCollection(MappedCollection):
+class GenDefaultCollection(KeyFuncDict):
     def __missing__(self, key):
         self[key] = b = B(key)
         return b
index c2fa6c2a91fe4eba0f5d4ea16d4090214f37667c..fd457946f87b60ab62e5fe25c91edd204a46d9e3 100644 (file)
@@ -43,7 +43,7 @@ from sqlalchemy.orm import relationship
 from sqlalchemy.orm import Session
 from sqlalchemy.orm import sessionmaker
 from sqlalchemy.orm import validates
-from sqlalchemy.orm.collections import attribute_mapped_collection
+from sqlalchemy.orm.collections import attribute_keyed_dict
 
 
 @event.listens_for(Session, "before_flush")
@@ -83,7 +83,7 @@ class ConfigData(Base):
 
     elements = relationship(
         "ConfigValueAssociation",
-        collection_class=attribute_mapped_collection("name"),
+        collection_class=attribute_keyed_dict("name"),
         backref=backref("config_data"),
         lazy="subquery",
     )
index 95b582a761f357413f9e826b96d4a99d500fb7ec..0343d53e1212e856f1a23bd4484a1e2c2fa6cc2f 100644 (file)
@@ -132,7 +132,7 @@ if __name__ == "__main__":
         create_engine,
     )
     from sqlalchemy.orm import relationship, Session
-    from sqlalchemy.orm.collections import attribute_mapped_collection
+    from sqlalchemy.orm.collections import attribute_keyed_dict
     from sqlalchemy.ext.declarative import declarative_base
     from sqlalchemy.ext.associationproxy import association_proxy
 
@@ -162,7 +162,7 @@ if __name__ == "__main__":
         name = Column(Unicode(100))
 
         facts = relationship(
-            "AnimalFact", collection_class=attribute_mapped_collection("key")
+            "AnimalFact", collection_class=attribute_keyed_dict("key")
         )
 
         _proxied = association_proxy(
index b74b3177623e8106bff076741e7e80ace5d95af4..d0a952d7c7dec0dfb1dc3c0ad25c91ae24f8d0b0 100644 (file)
@@ -71,7 +71,7 @@ if __name__ == "__main__":
         create_engine,
     )
     from sqlalchemy.orm import relationship, Session
-    from sqlalchemy.orm.collections import attribute_mapped_collection
+    from sqlalchemy.orm.collections import attribute_keyed_dict
     from sqlalchemy.ext.declarative import declarative_base
     from sqlalchemy.ext.associationproxy import association_proxy
 
@@ -95,7 +95,7 @@ if __name__ == "__main__":
         name = Column(Unicode(100))
 
         facts = relationship(
-            "AnimalFact", collection_class=attribute_mapped_collection("key")
+            "AnimalFact", collection_class=attribute_keyed_dict("key")
         )
 
         _proxied = association_proxy(
index 8523e520b996f2467152021cba8ab8951eba3b69..c6b61f3b47ea6f6bdd8010fe841973e1c743bb0a 100644 (file)
@@ -93,12 +93,16 @@ from .interfaces import RelationshipDirection as RelationshipDirection
 from .interfaces import UserDefinedOption as UserDefinedOption
 from .loading import merge_frozen_result as merge_frozen_result
 from .loading import merge_result as merge_result
+from .mapped_collection import attribute_keyed_dict as attribute_keyed_dict
 from .mapped_collection import (
     attribute_mapped_collection as attribute_mapped_collection,
 )
+from .mapped_collection import column_keyed_dict as column_keyed_dict
 from .mapped_collection import (
     column_mapped_collection as column_mapped_collection,
 )
+from .mapped_collection import keyfunc_mapping as keyfunc_mapping
+from .mapped_collection import KeyFuncDict as KeyFuncDict
 from .mapped_collection import mapped_collection as mapped_collection
 from .mapped_collection import MappedCollection as MappedCollection
 from .mapper import configure_mappers as configure_mappers
index 5dbd2dc30579f67c9b4c454699cd76dcd096acf9..e3051e268f665f026675e7d4c3bfabfc67488226 100644 (file)
@@ -134,19 +134,23 @@ from ..util.typing import Protocol
 if typing.TYPE_CHECKING:
     from .attributes import AttributeEventToken
     from .attributes import CollectionAttributeImpl
-    from .mapped_collection import attribute_mapped_collection
-    from .mapped_collection import column_mapped_collection
-    from .mapped_collection import mapped_collection
-    from .mapped_collection import MappedCollection  # noqa: F401
+    from .mapped_collection import attribute_keyed_dict
+    from .mapped_collection import column_keyed_dict
+    from .mapped_collection import keyfunc_mapping
+    from .mapped_collection import KeyFuncDict  # noqa: F401
     from .state import InstanceState
 
 
 __all__ = [
     "collection",
     "collection_adapter",
-    "mapped_collection",
-    "column_mapped_collection",
-    "attribute_mapped_collection",
+    "keyfunc_mapping",
+    "column_keyed_dict",
+    "attribute_keyed_dict",
+    "column_keyed_dict",
+    "attribute_keyed_dict",
+    "MappedCollection",
+    "KeyFuncDict",
 ]
 
 __instrumentation_mutex = threading.Lock()
@@ -1550,8 +1554,15 @@ __interfaces: util.immutabledict[
 
 def __go(lcls):
 
-    global mapped_collection, column_mapped_collection
-    global attribute_mapped_collection, MappedCollection
+    global keyfunc_mapping, mapped_collection
+    global column_keyed_dict, column_mapped_collection
+    global MappedCollection, KeyFuncDict
+    global attribute_keyed_dict, attribute_mapped_collection
+
+    from .mapped_collection import keyfunc_mapping
+    from .mapped_collection import column_keyed_dict
+    from .mapped_collection import attribute_keyed_dict
+    from .mapped_collection import KeyFuncDict
 
     from .mapped_collection import mapped_collection
     from .mapped_collection import column_mapped_collection
@@ -1565,7 +1576,7 @@ def __go(lcls):
     # see [ticket:2406].
     _instrument_class(InstrumentedList)
     _instrument_class(InstrumentedSet)
-    _instrument_class(MappedCollection)
+    _instrument_class(KeyFuncDict)
 
 
 __go(locals())
index 1f95d9d77a97bf7f4230f5552b00f1076310c7f3..1aa864f7e59e0d843b3d11bcba62c7c08a76bdcf 100644 (file)
@@ -105,12 +105,15 @@ class _SerializableColumnGetterV2(_PlainColumnGetter):
         return cols
 
 
-def column_mapped_collection(
+def column_keyed_dict(
     mapping_spec, *, ignore_unpopulated_attribute: bool = False
 ):
     """A dictionary-based collection type with column-based keying.
 
-    Returns a :class:`.MappedCollection` factory which will produce new
+    .. versionchanged:: 2.0 Renamed :data:`.column_mapped_collection` to
+       :class:`.column_keyed_dict`.
+
+    Returns a :class:`.KeyFuncDict` factory which will produce new
     dictionary keys based on the value of a particular :class:`.Column`-mapped
     attribute on ORM mapped instances to be added to the dictionary.
 
@@ -137,7 +140,7 @@ def column_mapped_collection(
      .. versionadded:: 2.0 an error is raised by default if the attribute
         being used for the dictionary key is determined that it was never
         populated with any value.  The
-        :paramref:`.column_mapped_collection.ignore_unpopulated_attribute`
+        :paramref:`.column_keyed_dict.ignore_unpopulated_attribute`
         parameter may be set which will instead indicate that this condition
         should be ignored, and the append operation silently skipped.
         This is in contrast to the behavior of the 1.x series which would
@@ -170,12 +173,15 @@ class _AttrGetter:
         return _AttrGetter, (self.attr_name,)
 
 
-def attribute_mapped_collection(
+def attribute_keyed_dict(
     attr_name: str, *, ignore_unpopulated_attribute: bool = False
-) -> Type["MappedCollection"]:
+) -> Type["KeyFuncDict"]:
     """A dictionary-based collection type with attribute-based keying.
 
-    Returns a :class:`.MappedCollection` factory which will produce new
+    .. versionchanged:: 2.0 Renamed :data:`.attribute_mapped_collection` to
+       :func:`.attribute_keyed_dict`.
+
+    Returns a :class:`.KeyFuncDict` factory which will produce new
     dictionary keys based on the value of a particular named attribute on
     ORM mapped instances to be added to the dictionary.
 
@@ -200,7 +206,7 @@ def attribute_mapped_collection(
      .. versionadded:: 2.0 an error is raised by default if the attribute
         being used for the dictionary key is determined that it was never
         populated with any value.  The
-        :paramref:`.attribute_mapped_collection.ignore_unpopulated_attribute`
+        :paramref:`.attribute_keyed_dict.ignore_unpopulated_attribute`
         parameter may be set which will instead indicate that this condition
         should be ignored, and the append operation silently skipped.
         This is in contrast to the behavior of the 1.x series which would
@@ -216,14 +222,17 @@ def attribute_mapped_collection(
     )
 
 
-def mapped_collection(
+def keyfunc_mapping(
     keyfunc: Callable[[Any], _KT],
     *,
     ignore_unpopulated_attribute: bool = False,
-) -> Type["MappedCollection[_KT, Any]"]:
+) -> Type["KeyFuncDict[_KT, Any]"]:
     """A dictionary-based collection type with arbitrary keying.
 
-    Returns a :class:`.MappedCollection` factory with a keying function
+    .. versionchanged:: 2.0 Renamed :data:`.mapped_collection` to
+       :func:`.keyfunc_mapping`.
+
+    Returns a :class:`.KeyFuncDict` factory with a keying function
     generated from keyfunc, a callable that takes an entity and returns a
     key value.
 
@@ -262,21 +271,24 @@ def mapped_collection(
     )
 
 
-class MappedCollection(Dict[_KT, _VT]):
+class KeyFuncDict(Dict[_KT, _VT]):
     """Base for ORM mapped dictionary classes.
 
     Extends the ``dict`` type with additional methods needed by SQLAlchemy ORM
-    collection classes. Use of :class:`_orm.MappedCollection` is most directly
-    by using the :func:`.attribute_mapped_collection` or
-    :func:`.column_mapped_collection` class factories.
-    :class:`_orm.MappedCollection` may also serve as the base for user-defined
+    collection classes. Use of :class:`_orm.KeyFuncDict` is most directly
+    by using the :func:`.attribute_keyed_dict` or
+    :func:`.column_keyed_dict` class factories.
+    :class:`_orm.KeyFuncDict` may also serve as the base for user-defined
     custom dictionary classes.
 
+    .. versionchanged:: 2.0 Renamed :class:`.MappedCollection` to
+       :class:`.KeyFuncDict`.
+
     .. seealso::
 
-        :func:`_orm.attribute_mapped_collection`
+        :func:`_orm.attribute_keyed_dict`
 
-        :func:`_orm.column_mapped_collection`
+        :func:`_orm.column_keyed_dict`
 
         :ref:`orm_dictionary_collection`
 
@@ -304,12 +316,12 @@ class MappedCollection(Dict[_KT, _VT]):
 
     @classmethod
     def _unreduce(cls, keyfunc, values):
-        mp = MappedCollection(keyfunc)
+        mp = KeyFuncDict(keyfunc)
         mp.update(values)
         return mp
 
     def __reduce__(self):
-        return (MappedCollection._unreduce, (self.keyfunc, dict(self)))
+        return (KeyFuncDict._unreduce, (self.keyfunc, dict(self)))
 
     def _raise_for_unpopulated(self, value, initiator):
         mapper = base.instance_state(value).mapper
@@ -322,7 +334,7 @@ class MappedCollection(Dict[_KT, _VT]):
         raise sa_exc.InvalidRequestError(
             f"In event triggered from population of attribute {relationship} "
             "(likely from a backref), "
-            f"can't populate value in MappedCollection; "
+            f"can't populate value in KeyFuncDict; "
             "dictionary key "
             f"derived from {base.instance_str(value)} is not "
             f"populated. Ensure appropriate state is set up on "
@@ -365,7 +377,7 @@ class MappedCollection(Dict[_KT, _VT]):
         if self[key] != value:
             raise sa_exc.InvalidRequestError(
                 "Can not remove '%s': collection holds '%s' for key '%s'. "
-                "Possible cause: is the MappedCollection key function "
+                "Possible cause: is the KeyFuncDict key function "
                 "based on mutable properties or properties that only obtain "
                 "values after flush?" % (value, self[key], key)
             )
@@ -373,7 +385,7 @@ class MappedCollection(Dict[_KT, _VT]):
 
 
 def _mapped_collection_cls(keyfunc, ignore_unpopulated_attribute):
-    class _MKeyfuncMapped(MappedCollection):
+    class _MKeyfuncMapped(KeyFuncDict):
         def __init__(self):
             super().__init__(
                 keyfunc,
@@ -381,3 +393,36 @@ def _mapped_collection_cls(keyfunc, ignore_unpopulated_attribute):
             )
 
     return _MKeyfuncMapped
+
+
+MappedCollection = KeyFuncDict
+"""A synonym for :class:`.KeyFuncDict`.
+
+.. versionchanged:: 2.0 Renamed :class:`.MappedCollection` to
+   :class:`.KeyFuncDict`.
+
+"""
+
+mapped_collection = keyfunc_mapping
+"""A synonym for :func:`_orm.keyfunc_mapping`.
+
+.. versionchanged:: 2.0 Renamed :data:`.mapped_collection` to
+   :func:`_orm.keyfunc_mapping`
+
+"""
+
+attribute_mapped_collection = attribute_keyed_dict
+"""A synonym for :func:`_orm.attribute_keyed_dict`.
+
+.. versionchanged:: 2.0 Renamed :data:`.attribute_mapped_collection` to
+   :func:`_orm.attribute_keyed_dict`
+
+"""
+
+column_mapped_collection = column_keyed_dict
+"""A synonym for :func:`_orm.column_keyed_dict.
+
+.. versionchanged:: 2.0 Renamed :func:`.column_mapped_collection` to
+   :func:`_orm.column_keyed_dict`
+
+"""
diff --git a/test/ext/mypy/plain_files/keyfunc_dict.py b/test/ext/mypy/plain_files/keyfunc_dict.py
new file mode 100644 (file)
index 0000000..0e18697
--- /dev/null
@@ -0,0 +1,45 @@
+import typing
+from typing import Optional
+
+from sqlalchemy import ForeignKey
+from sqlalchemy.orm import attribute_keyed_dict
+from sqlalchemy.orm import DeclarativeBase
+from sqlalchemy.orm import Mapped
+from sqlalchemy.orm import mapped_column
+from sqlalchemy.orm import relationship
+
+
+class Base(DeclarativeBase):
+    pass
+
+
+class Item(Base):
+    __tablename__ = "item"
+
+    id: Mapped[int] = mapped_column(primary_key=True)
+
+    notes: Mapped[dict[str, "Note"]] = relationship(
+        collection_class=attribute_keyed_dict("keyword"),
+        cascade="all, delete-orphan",
+    )
+
+
+class Note(Base):
+    __tablename__ = "note"
+
+    id: Mapped[int] = mapped_column(primary_key=True)
+    item_id: Mapped[int] = mapped_column(ForeignKey("item.id"))
+    keyword: Mapped[str]
+    text: Mapped[Optional[str]]
+
+    def __init__(self, keyword: str, text: str):
+        self.keyword = keyword
+        self.text = text
+
+
+item = Item()
+item.notes["a"] = Note("a", "atext")
+
+if typing.TYPE_CHECKING:
+    # EXPECTED_TYPE: dict_items[str, Note]
+    reveal_type(item.notes.items())
index 4d17dab78d7e1a7d08742c90a0d7d2175629ffd8..8d7d7e71a2e23a00c41a47e391dc3216473780aa 100644 (file)
@@ -16,7 +16,7 @@ from sqlalchemy.orm import DeclarativeBase
 from sqlalchemy.orm import Mapped
 from sqlalchemy.orm import mapped_column
 from sqlalchemy.orm import relationship
-from sqlalchemy.orm.collections import attribute_mapped_collection
+from sqlalchemy.orm.collections import attribute_keyed_dict
 
 
 class Base(DeclarativeBase):
@@ -80,11 +80,11 @@ class Address(Base):
     user_style_nine = relationship(User, uselist=True)
 
     user_style_ten = relationship(
-        User, collection_class=attribute_mapped_collection("name")
+        User, collection_class=attribute_keyed_dict("name")
     )
 
     user_style_ten_typed: Mapped[Dict[str, User]] = relationship(
-        User, collection_class=attribute_mapped_collection("name")
+        User, collection_class=attribute_keyed_dict("name")
     )
 
     # pylance rejects this however.  cannot get both to work at the same
index 67b0c93e0f2f523b345d059953557a5749175a9f..ffaae7db37aa2784dfc2848a48e7658776270087 100644 (file)
@@ -27,7 +27,7 @@ from sqlalchemy.orm import declared_attr
 from sqlalchemy.orm import mapper
 from sqlalchemy.orm import relationship
 from sqlalchemy.orm import Session
-from sqlalchemy.orm.collections import attribute_mapped_collection
+from sqlalchemy.orm.collections import attribute_keyed_dict
 from sqlalchemy.orm.collections import collection
 from sqlalchemy.testing import assert_raises
 from sqlalchemy.testing import assert_raises_message
@@ -195,7 +195,7 @@ class AutoFlushTest(fixtures.MappedTest):
             collection[obj.name] = obj
 
         self._test_premature_flush(
-            collections.attribute_mapped_collection("name"), set_, is_dict=True
+            collections.attribute_keyed_dict("name"), set_, is_dict=True
         )
 
 
@@ -1465,7 +1465,7 @@ class ReconstitutionTest(fixtures.MappedTest):
             properties=dict(
                 children=relationship(
                     KVChild,
-                    collection_class=collections.mapped_collection(
+                    collection_class=collections.keyfunc_mapping(
                         PickleKeyFunc("name")
                     ),
                 )
@@ -2356,7 +2356,7 @@ class DictOfTupleUpdateTest(fixtures.MappedTest):
             a,
             properties={
                 "orig": relationship(
-                    B, collection_class=attribute_mapped_collection("key")
+                    B, collection_class=attribute_keyed_dict("key")
                 )
             },
         )
index a63378c26df455b7ef473698eada29628819af9a..b1e80f5d93c0a5a4581af1a870c978f475178b74 100644 (file)
@@ -12,12 +12,12 @@ from sqlalchemy import ForeignKey
 from sqlalchemy import Integer
 from sqlalchemy import Numeric
 from sqlalchemy import Table
-from sqlalchemy.orm import attribute_mapped_collection
+from sqlalchemy.orm import attribute_keyed_dict
 from sqlalchemy.orm import DeclarativeBase
 from sqlalchemy.orm import DynamicMapped
+from sqlalchemy.orm import KeyFuncDict
 from sqlalchemy.orm import Mapped
 from sqlalchemy.orm import mapped_column
-from sqlalchemy.orm import MappedCollection
 from sqlalchemy.orm import relationship
 from sqlalchemy.orm import WriteOnlyMapped
 from sqlalchemy.testing import expect_raises_message
@@ -113,7 +113,7 @@ class MappedColumnTest(_MappedColumnTest):
                 is_true(optional_col.nullable)
 
 
-class MappedOneArg(MappedCollection[str, _R]):
+class MappedOneArg(KeyFuncDict[str, _R]):
     pass
 
 
@@ -210,8 +210,8 @@ class RelationshipLHSTest(_RelationshipLHSTest):
             id: Mapped[int] = mapped_column(primary_key=True)
             data: Mapped[str] = mapped_column()
 
-            bs: Mapped[MappedCollection[str, B]] = relationship(  # noqa: F821
-                collection_class=attribute_mapped_collection("name")
+            bs: Mapped[KeyFuncDict[str, B]] = relationship(  # noqa: F821
+                collection_class=attribute_keyed_dict("name")
             )
 
         class B(decl_base):
@@ -231,10 +231,8 @@ class RelationshipLHSTest(_RelationshipLHSTest):
             id: Mapped[int] = mapped_column(primary_key=True)
             data: Mapped[str] = mapped_column()
 
-            bs: Mapped[
-                MappedCollection[str, "B"]
-            ] = relationship(  # noqa: F821
-                collection_class=attribute_mapped_collection("name")
+            bs: Mapped[KeyFuncDict[str, "B"]] = relationship(  # noqa: F821
+                collection_class=attribute_keyed_dict("name")
             )
 
         class B(decl_base):
@@ -246,7 +244,7 @@ class RelationshipLHSTest(_RelationshipLHSTest):
         self._assert_dict(A, B)
 
     def test_collection_cls_not_locatable(self, decl_base):
-        class MyCollection(MappedCollection):
+        class MyCollection(KeyFuncDict):
             pass
 
         with expect_raises_message(
@@ -261,7 +259,7 @@ class RelationshipLHSTest(_RelationshipLHSTest):
                 data: Mapped[str] = mapped_column()
 
                 bs: Mapped[MyCollection["B"]] = relationship(  # noqa: F821
-                    collection_class=attribute_mapped_collection("name")
+                    collection_class=attribute_keyed_dict("name")
                 )
 
     def test_collection_cls_one_arg(self, decl_base):
@@ -272,7 +270,7 @@ class RelationshipLHSTest(_RelationshipLHSTest):
             data: Mapped[str] = mapped_column()
 
             bs: Mapped[MappedOneArg["B"]] = relationship(  # noqa: F821
-                collection_class=attribute_mapped_collection("name")
+                collection_class=attribute_keyed_dict("name")
             )
 
         class B(decl_base):
index 5c5b481db14ee63627bd532cbffee5b0558138d2..7ef35a8504d06d1d9d99a199bf6fc457991e21bb 100644 (file)
@@ -42,8 +42,8 @@ from sqlalchemy.orm import mapped_column
 from sqlalchemy.orm import relationship
 from sqlalchemy.orm import undefer
 from sqlalchemy.orm import WriteOnlyMapped
-from sqlalchemy.orm.collections import attribute_mapped_collection
-from sqlalchemy.orm.collections import MappedCollection
+from sqlalchemy.orm.collections import attribute_keyed_dict
+from sqlalchemy.orm.collections import KeyFuncDict
 from sqlalchemy.schema import CreateTable
 from sqlalchemy.testing import eq_
 from sqlalchemy.testing import expect_raises
@@ -1412,10 +1412,8 @@ class RelationshipLHSTest(fixtures.TestBase, testing.AssertsCompiledSQL):
             id: Mapped[int] = mapped_column(primary_key=True)
             data: Mapped[str] = mapped_column()
 
-            bs: Mapped[
-                MappedCollection[str, "B"]  # noqa: F821
-            ] = relationship(
-                collection_class=attribute_mapped_collection("name")
+            bs: Mapped[KeyFuncDict[str, "B"]] = relationship(  # noqa: F821
+                collection_class=attribute_keyed_dict("name")
             )
 
         class B(decl_base):
index 53b306f5b1d48de6391777dea24451a7d6e5eb54..f0a91cf39276e0fc10faac153aeb565bdac9d4ee 100644 (file)
@@ -9,7 +9,7 @@ from sqlalchemy.orm import attributes
 from sqlalchemy.orm import exc as orm_exc
 from sqlalchemy.orm import instrumentation
 from sqlalchemy.orm import NO_KEY
-from sqlalchemy.orm.collections import attribute_mapped_collection
+from sqlalchemy.orm.collections import attribute_keyed_dict
 from sqlalchemy.orm.collections import collection
 from sqlalchemy.orm.state import InstanceState
 from sqlalchemy.testing import assert_raises
@@ -2584,7 +2584,7 @@ class HistoryTest(fixtures.TestBase):
             "someattr",
             uselist=True,
             useobject=True,
-            typecallable=attribute_mapped_collection("name"),
+            typecallable=attribute_keyed_dict("name"),
         )
         hi = Bar(name="hi")
         there = Bar(name="there")
@@ -3209,7 +3209,7 @@ class CollectionKeyTest(fixtures.ORMTest):
             "someattr",
             uselist=True,
             useobject=True,
-            typecallable=attribute_mapped_collection("name"),
+            typecallable=attribute_keyed_dict("name"),
         )
         _register_attribute(
             Bar,
index 5a171e3722ecf52fd1b4831e0a252064f00ae4d1..8baa52f19ffb18f36613e29ffe9dc49469c86215 100644 (file)
@@ -20,7 +20,7 @@ from sqlalchemy.orm import Session
 from sqlalchemy.orm import util as orm_util
 from sqlalchemy.orm import with_parent
 from sqlalchemy.orm.attributes import instance_state
-from sqlalchemy.orm.collections import attribute_mapped_collection
+from sqlalchemy.orm.collections import attribute_keyed_dict
 from sqlalchemy.orm.decl_api import declarative_base
 from sqlalchemy.testing import assert_raises
 from sqlalchemy.testing import assert_raises_message
@@ -4462,10 +4462,10 @@ class CollectionCascadesNoBackrefTest(fixtures.TestBase):
     @testing.combinations(
         (set, "add"),
         (list, "append"),
-        (attribute_mapped_collection("key"), "__setitem__"),
-        (attribute_mapped_collection("key"), "setdefault"),
-        (attribute_mapped_collection("key"), "update_dict"),
-        (attribute_mapped_collection("key"), "update_kw"),
+        (attribute_keyed_dict("key"), "__setitem__"),
+        (attribute_keyed_dict("key"), "setdefault"),
+        (attribute_keyed_dict("key"), "update_dict"),
+        (attribute_keyed_dict("key"), "update_kw"),
         argnames="collection_class,methname",
     )
     def test_cascades_on_collection(
index 34b21921e40cdfd5a3767444b29a198af4ccec38..1c8bee00f49608ce8de915eea0a1d42cec276cc6 100644 (file)
@@ -89,7 +89,7 @@ class Canary:
 class OrderedDictFixture:
     @testing.fixture
     def ordered_dict_mro(self):
-        return type("ordered", (collections.MappedCollection,), {})
+        return type("ordered", (collections.KeyFuncDict,), {})
 
 
 class CollectionsTest(OrderedDictFixture, fixtures.ORMTest):
@@ -1403,7 +1403,7 @@ class CollectionsTest(OrderedDictFixture, fixtures.ORMTest):
         self.assert_(getattr(MyDict, "_sa_instrumented") == id(MyDict))
 
     def test_dict_subclass2(self):
-        class MyEasyDict(collections.MappedCollection):
+        class MyEasyDict(collections.KeyFuncDict):
             def __init__(self):
                 super(MyEasyDict, self).__init__(lambda e: e.a)
 
@@ -1418,7 +1418,7 @@ class CollectionsTest(OrderedDictFixture, fixtures.ORMTest):
     def test_dict_subclass3(self, ordered_dict_mro):
         class MyOrdered(ordered_dict_mro):
             def __init__(self):
-                collections.MappedCollection.__init__(self, lambda e: e.a)
+                collections.KeyFuncDict.__init__(self, lambda e: e.a)
                 util.OrderedDict.__init__(self)
 
         self._test_adapter(
@@ -1988,15 +1988,15 @@ class DictHelpersTest(OrderedDictFixture, fixtures.MappedTest):
         )
 
     def test_mapped_collection(self):
-        collection_class = collections.mapped_collection(lambda c: c.a)
+        collection_class = collections.keyfunc_mapping(lambda c: c.a)
         self._test_scalar_mapped(collection_class)
 
     def test_mapped_collection2(self):
-        collection_class = collections.mapped_collection(lambda c: (c.a, c.b))
+        collection_class = collections.keyfunc_mapping(lambda c: (c.a, c.b))
         self._test_composite_mapped(collection_class)
 
     def test_attr_mapped_collection(self):
-        collection_class = collections.attribute_mapped_collection("a")
+        collection_class = collections.attribute_keyed_dict("a")
         self._test_scalar_mapped(collection_class)
 
     def test_declarative_column_mapped(self):
@@ -2015,7 +2015,7 @@ class DictHelpersTest(OrderedDictFixture, fixtures.MappedTest):
             ((Foo.id, Foo.bar_id), Foo(id=3, bar_id=12), (3, 12)),
         ):
             eq_(
-                collections.column_mapped_collection(spec)().keyfunc(obj),
+                collections.column_keyed_dict(spec)().keyfunc(obj),
                 expected,
             )
 
@@ -2024,27 +2024,27 @@ class DictHelpersTest(OrderedDictFixture, fixtures.MappedTest):
             sa_exc.ArgumentError,
             "Column expression expected "
             "for argument 'mapping_spec'; got 'a'.",
-            collections.column_mapped_collection,
+            collections.column_keyed_dict,
             "a",
         )
         assert_raises_message(
             sa_exc.ArgumentError,
             "Column expression expected "
             "for argument 'mapping_spec'; got .*TextClause.",
-            collections.column_mapped_collection,
+            collections.column_keyed_dict,
             text("a"),
         )
 
     def test_column_mapped_collection(self):
         children = self.tables.children
 
-        collection_class = collections.column_mapped_collection(children.c.a)
+        collection_class = collections.column_keyed_dict(children.c.a)
         self._test_scalar_mapped(collection_class)
 
     def test_column_mapped_collection2(self):
         children = self.tables.children
 
-        collection_class = collections.column_mapped_collection(
+        collection_class = collections.column_keyed_dict(
             (children.c.a, children.c.b)
         )
         self._test_composite_mapped(collection_class)
@@ -2052,7 +2052,7 @@ class DictHelpersTest(OrderedDictFixture, fixtures.MappedTest):
     def test_mixin(self, ordered_dict_mro):
         class Ordered(ordered_dict_mro):
             def __init__(self):
-                collections.MappedCollection.__init__(self, lambda v: v.a)
+                collections.KeyFuncDict.__init__(self, lambda v: v.a)
                 util.OrderedDict.__init__(self)
 
         collection_class = Ordered
@@ -2061,7 +2061,7 @@ class DictHelpersTest(OrderedDictFixture, fixtures.MappedTest):
     def test_mixin2(self, ordered_dict_mro):
         class Ordered2(ordered_dict_mro):
             def __init__(self, keyfunc):
-                collections.MappedCollection.__init__(self, keyfunc)
+                collections.KeyFuncDict.__init__(self, keyfunc)
                 util.OrderedDict.__init__(self)
 
         def collection_class():
@@ -2135,7 +2135,7 @@ class ColumnMappedWSerialize(fixtures.MappedTest):
         from sqlalchemy.testing.util import picklers
 
         for spec, obj, expected in specs:
-            coll = collections.column_mapped_collection(spec)()
+            coll = collections.column_keyed_dict(spec)()
             eq_(coll.keyfunc(obj), expected)
             # ensure we do the right thing with __reduce__
             for loads, dumps in picklers():
@@ -2294,7 +2294,7 @@ class CustomCollectionsTest(fixtures.MappedTest):
             properties={
                 "bars": relationship(
                     Bar,
-                    collection_class=collections.column_mapped_collection(
+                    collection_class=collections.column_keyed_dict(
                         someothertable.c.data
                     ),
                 )
@@ -2641,11 +2641,11 @@ class UnpopulatedAttrTest(fixtures.TestBase):
             data = Column(String)
             a_id = Column(ForeignKey("a.id"))
 
-        if collection_fn is collections.attribute_mapped_collection:
+        if collection_fn is collections.attribute_keyed_dict:
             cc = collection_fn(
                 "data", ignore_unpopulated_attribute=ignore_unpopulated
             )
-        elif collection_fn is collections.column_mapped_collection:
+        elif collection_fn is collections.column_keyed_dict:
             cc = collection_fn(
                 B.data, ignore_unpopulated_attribute=ignore_unpopulated
             )
@@ -2665,8 +2665,8 @@ class UnpopulatedAttrTest(fixtures.TestBase):
         return A, B
 
     @testing.combinations(
-        collections.attribute_mapped_collection,
-        collections.column_mapped_collection,
+        collections.attribute_keyed_dict,
+        collections.column_keyed_dict,
         argnames="collection_fn",
     )
     @testing.combinations(True, False, argnames="ignore_unpopulated")
@@ -2689,8 +2689,8 @@ class UnpopulatedAttrTest(fixtures.TestBase):
                 a1.bs["bar"] = B(a=a1)
 
     @testing.combinations(
-        collections.attribute_mapped_collection,
-        collections.column_mapped_collection,
+        collections.attribute_keyed_dict,
+        collections.column_keyed_dict,
         argnames="collection_fn",
     )
     @testing.combinations(True, False, argnames="ignore_unpopulated")
index 71c03aee7810b82ad12febbaa3468ca4b92c0437..c816896cf31cead8e086f7dc88c5e9ee113c96c5 100644 (file)
@@ -873,7 +873,7 @@ class InstrumentationTest(fixtures.ORMTest):
             "AttributeEvents"
         ):
 
-            class MyDict(collections.MappedCollection):
+            class MyDict(collections.KeyFuncDict):
                 def __init__(self):
                     super(MyDict, self).__init__(lambda value: "k%d" % value)
 
index a83ca4194751d4588f873062ff9925010d142de4..ef0db6d055b6f39787437ce7bf7c4bc8f377f225 100644 (file)
@@ -20,7 +20,7 @@ from sqlalchemy.orm import relationship
 from sqlalchemy.orm import selectinload
 from sqlalchemy.orm import Session
 from sqlalchemy.orm import synonym
-from sqlalchemy.orm.collections import attribute_mapped_collection
+from sqlalchemy.orm.collections import attribute_keyed_dict
 from sqlalchemy.orm.interfaces import MapperOption
 from sqlalchemy.testing import assert_raises_message
 from sqlalchemy.testing import eq_
@@ -600,9 +600,7 @@ class MergeTest(_fixtures.FixtureTest):
                 "addresses": relationship(
                     self.mapper_registry.map_imperatively(Address, addresses),
                     backref="user",
-                    collection_class=attribute_mapped_collection(
-                        "email_address"
-                    ),
+                    collection_class=attribute_keyed_dict("email_address"),
                 )
             },
         )
@@ -1870,7 +1868,7 @@ class DeferredMergeTest(fixtures.MappedTest):
         class Book(cls.Basic):
             pass
 
-    def test_deferred_column_mapping(self):
+    def test_deferred_column_keyed_dict(self):
         # defer 'excerpt' at mapping level instead of query level
         Book, book = self.classes.Book, self.tables.book
         self.mapper_registry.map_imperatively(
index a0706a1337242fff6fe8dbaabc97e5fff8920544..710c98ee6d26df1ec3e6968bc7630c2cbb6e22ee 100644 (file)
@@ -17,8 +17,8 @@ from sqlalchemy.orm import state as sa_state
 from sqlalchemy.orm import subqueryload
 from sqlalchemy.orm import with_loader_criteria
 from sqlalchemy.orm import with_polymorphic
-from sqlalchemy.orm.collections import attribute_mapped_collection
-from sqlalchemy.orm.collections import column_mapped_collection
+from sqlalchemy.orm.collections import attribute_keyed_dict
+from sqlalchemy.orm.collections import column_keyed_dict
 from sqlalchemy.testing import assert_raises_message
 from sqlalchemy.testing import eq_
 from sqlalchemy.testing import fixtures
@@ -580,9 +580,7 @@ class PickleTest(fixtures.MappedTest):
             properties={
                 "addresses": relationship(
                     Address,
-                    collection_class=attribute_mapped_collection(
-                        "email_address"
-                    ),
+                    collection_class=attribute_keyed_dict("email_address"),
                 )
             },
         )
@@ -603,7 +601,7 @@ class PickleTest(fixtures.MappedTest):
             properties={
                 "addresses": relationship(
                     Address,
-                    collection_class=column_mapped_collection(
+                    collection_class=column_keyed_dict(
                         addresses.c.email_address
                     ),
                 )
@@ -629,7 +627,7 @@ class PickleTest(fixtures.MappedTest):
             properties={
                 "addresses": relationship(
                     Address,
-                    collection_class=column_mapped_collection(
+                    collection_class=column_keyed_dict(
                         [addresses.c.id, addresses.c.email_address]
                     ),
                 )
index 6b0fee49db0ecb3de3d67a787251f6575ada6791..adfb6cb74dca5366034edcdeabb1e07f7f76f259 100644 (file)
@@ -225,7 +225,7 @@ class ValidatorTest(_fixtures.FixtureTest):
             properties={
                 "addresses": relationship(
                     Address,
-                    collection_class=collections.attribute_mapped_collection(
+                    collection_class=collections.attribute_keyed_dict(
                         "email_address"
                     ),
                 )