--- /dev/null
+.. 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.
and techniques.
-.. currentmodule:: sqlalchemy.orm.collections
.. _custom_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):
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",
)
>>> 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::
"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
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",
)
>>> 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):
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
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",
)
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.
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",
)
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
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.
@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
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
----------------------------------------
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`.
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):
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
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):
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
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):
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()
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):
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:
Base = declarative_base(cls=Base)
-class GenDefaultCollection(MappedCollection):
+class GenDefaultCollection(KeyFuncDict):
def __missing__(self, key):
self[key] = b = B(key)
return b
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")
elements = relationship(
"ConfigValueAssociation",
- collection_class=attribute_mapped_collection("name"),
+ collection_class=attribute_keyed_dict("name"),
backref=backref("config_data"),
lazy="subquery",
)
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
name = Column(Unicode(100))
facts = relationship(
- "AnimalFact", collection_class=attribute_mapped_collection("key")
+ "AnimalFact", collection_class=attribute_keyed_dict("key")
)
_proxied = association_proxy(
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
name = Column(Unicode(100))
facts = relationship(
- "AnimalFact", collection_class=attribute_mapped_collection("key")
+ "AnimalFact", collection_class=attribute_keyed_dict("key")
)
_proxied = association_proxy(
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
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()
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
# see [ticket:2406].
_instrument_class(InstrumentedList)
_instrument_class(InstrumentedSet)
- _instrument_class(MappedCollection)
+ _instrument_class(KeyFuncDict)
__go(locals())
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.
.. 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
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.
.. 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
)
-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.
)
-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`
@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
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 "
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)
)
def _mapped_collection_cls(keyfunc, ignore_unpopulated_attribute):
- class _MKeyfuncMapped(MappedCollection):
+ class _MKeyfuncMapped(KeyFuncDict):
def __init__(self):
super().__init__(
keyfunc,
)
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`
+
+"""
--- /dev/null
+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())
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):
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
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
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
)
properties=dict(
children=relationship(
KVChild,
- collection_class=collections.mapped_collection(
+ collection_class=collections.keyfunc_mapping(
PickleKeyFunc("name")
),
)
a,
properties={
"orig": relationship(
- B, collection_class=attribute_mapped_collection("key")
+ B, collection_class=attribute_keyed_dict("key")
)
},
)
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
is_true(optional_col.nullable)
-class MappedOneArg(MappedCollection[str, _R]):
+class MappedOneArg(KeyFuncDict[str, _R]):
pass
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):
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):
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(
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):
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):
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
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):
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
"someattr",
uselist=True,
useobject=True,
- typecallable=attribute_mapped_collection("name"),
+ typecallable=attribute_keyed_dict("name"),
)
hi = Bar(name="hi")
there = Bar(name="there")
"someattr",
uselist=True,
useobject=True,
- typecallable=attribute_mapped_collection("name"),
+ typecallable=attribute_keyed_dict("name"),
)
_register_attribute(
Bar,
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
@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(
class OrderedDictFixture:
@testing.fixture
def ordered_dict_mro(self):
- return type("ordered", (collections.MappedCollection,), {})
+ return type("ordered", (collections.KeyFuncDict,), {})
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)
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(
)
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):
((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,
)
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)
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
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():
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():
properties={
"bars": relationship(
Bar,
- collection_class=collections.column_mapped_collection(
+ collection_class=collections.column_keyed_dict(
someothertable.c.data
),
)
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
)
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")
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")
"AttributeEvents"
):
- class MyDict(collections.MappedCollection):
+ class MyDict(collections.KeyFuncDict):
def __init__(self):
super(MyDict, self).__init__(lambda value: "k%d" % value)
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_
"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"),
)
},
)
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(
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
properties={
"addresses": relationship(
Address,
- collection_class=attribute_mapped_collection(
- "email_address"
- ),
+ collection_class=attribute_keyed_dict("email_address"),
)
},
)
properties={
"addresses": relationship(
Address,
- collection_class=column_mapped_collection(
+ collection_class=column_keyed_dict(
addresses.c.email_address
),
)
properties={
"addresses": relationship(
Address,
- collection_class=column_mapped_collection(
+ collection_class=column_keyed_dict(
[addresses.c.id, addresses.c.email_address]
),
)
properties={
"addresses": relationship(
Address,
- collection_class=collections.attribute_mapped_collection(
+ collection_class=collections.attribute_keyed_dict(
"email_address"
),
)