diff options
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 18 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 12 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/collections.py | 33 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/descriptor_props.py | 87 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/events.py | 8 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/identity.py | 126 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 25 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/properties.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 17 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/session.py | 93 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 3 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategy_options.py | 11 |
12 files changed, 26 insertions, 409 deletions
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 83edb0ff5..d08b35a2e 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -16,7 +16,6 @@ documentation for an overview of how this module is used. from . import exc # noqa from . import mapper as mapperlib # noqa from . import strategy_options -from .descriptor_props import ComparableProperty # noqa from .descriptor_props import CompositeProperty # noqa from .descriptor_props import SynonymProperty # noqa from .interfaces import EXT_CONTINUE # noqa @@ -194,23 +193,6 @@ mapper = public_factory(Mapper, ".orm.mapper") synonym = public_factory(SynonymProperty, ".orm.synonym") -comparable_property = public_factory( - ComparableProperty, ".orm.comparable_property" -) - - -@_sa_util.deprecated( - "0.7", - message=":func:`.compile_mappers` is deprecated and will be removed " - "in a future release. Please use :func:`.configure_mappers`", -) -def compile_mappers(): - """Initialize the inter-mapper relationships of all mappers that have - been defined. - - """ - configure_mappers() - def clear_mappers(): """Remove all mappers from all classes. diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 5ca2858e9..2bacb25b0 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -1811,18 +1811,6 @@ def get_history(obj, key, passive=PASSIVE_OFF): using loader callables if the value is not locally present. """ - if passive is True: - util.warn_deprecated( - "Passing True for 'passive' is deprecated. " - "Use attributes.PASSIVE_NO_INITIALIZE" - ) - passive = PASSIVE_NO_INITIALIZE - elif passive is False: - util.warn_deprecated( - "Passing False for 'passive' is " - "deprecated. Use attributes.PASSIVE_OFF" - ) - passive = PASSIVE_OFF return get_state_history(instance_state(obj), key, passive) diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py index cfafea0fc..9d68179e5 100644 --- a/lib/sqlalchemy/orm/collections.py +++ b/lib/sqlalchemy/orm/collections.py @@ -301,7 +301,7 @@ class collection(object): The decorators fall into two groups: annotations and interception recipes. - The annotating decorators (appender, remover, iterator, linker, converter, + The annotating decorators (appender, remover, iterator, converter, internally_instrumented) indicate the method's purpose and take no arguments. They are not written with parens:: @@ -430,36 +430,6 @@ class collection(object): @staticmethod @util.deprecated( - "1.0", - "The :meth:`.collection.linker` handler is deprecated and will " - "be removed in a future release. Please refer to the " - ":meth:`.AttributeEvents.init_collection` " - "and :meth:`.AttributeEvents.dispose_collection` event handlers. ", - ) - def linker(fn): - """Tag the method as a "linked to attribute" event handler. - - This optional event handler will be called when the collection class - is linked to or unlinked from the InstrumentedAttribute. It is - invoked immediately after the '_sa_adapter' property is set on - the instance. A single argument is passed: the collection adapter - that has been linked, or None if unlinking. - - - """ - fn._sa_instrument_role = "linker" - return fn - - link = linker - """Synonym for :meth:`.collection.linker`. - - .. deprecated:: 1.0 - :meth:`.collection.link` is deprecated and will be - removed in a future release. - - """ - - @staticmethod - @util.deprecated( "1.3", "The :meth:`.collection.converter` handler is deprecated and will " "be removed in a future release. Please refer to the " @@ -946,7 +916,6 @@ def _locate_roles_and_methods(cls): "appender", "remover", "iterator", - "linker", "converter", ) roles.setdefault(role, name) diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index c2067c228..3bc009da8 100644 --- a/lib/sqlalchemy/orm/descriptor_props.py +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -705,90 +705,3 @@ class SynonymProperty(DescriptorProperty): p._mapped_by_synonym = self.key self.parent = parent - - -@util.deprecated_cls( - "0.7", - ":func:`.comparable_property` is deprecated and will be removed in a " - "future release. Please refer to the :mod:`~sqlalchemy.ext.hybrid` " - "extension.", -) -class ComparableProperty(DescriptorProperty): - """Instruments a Python property for use in query expressions.""" - - def __init__( - self, comparator_factory, descriptor=None, doc=None, info=None - ): - """Provides a method of applying a :class:`.PropComparator` - to any Python descriptor attribute. - - - Allows any Python descriptor to behave like a SQL-enabled - attribute when used at the class level in queries, allowing - redefinition of expression operator behavior. - - In the example below we redefine :meth:`.PropComparator.operate` - to wrap both sides of an expression in ``func.lower()`` to produce - case-insensitive comparison:: - - from sqlalchemy.orm import comparable_property - from sqlalchemy.orm.interfaces import PropComparator - from sqlalchemy.sql import func - from sqlalchemy import Integer, String, Column - from sqlalchemy.ext.declarative import declarative_base - - class CaseInsensitiveComparator(PropComparator): - def __clause_element__(self): - return self.prop - - def operate(self, op, other): - return op( - func.lower(self.__clause_element__()), - func.lower(other) - ) - - Base = declarative_base() - - class SearchWord(Base): - __tablename__ = 'search_word' - id = Column(Integer, primary_key=True) - word = Column(String) - word_insensitive = comparable_property(lambda prop, mapper: - CaseInsensitiveComparator( - mapper.c.word, mapper) - ) - - - A mapping like the above allows the ``word_insensitive`` attribute - to render an expression like:: - - >>> print(SearchWord.word_insensitive == "Trucks") - lower(search_word.word) = lower(:lower_1) - - :param comparator_factory: - A PropComparator subclass or factory that defines operator behavior - for this property. - - :param descriptor: - Optional when used in a ``properties={}`` declaration. The Python - descriptor or property to layer comparison behavior on top of. - - The like-named descriptor will be automatically retrieved from the - mapped class if left blank in a ``properties`` declaration. - - :param info: Optional data dictionary which will be populated into the - :attr:`.InspectionAttr.info` attribute of this object. - - .. versionadded:: 1.0.0 - - """ - super(ComparableProperty, self).__init__() - self.descriptor = descriptor - self.comparator_factory = comparator_factory - self.doc = doc or (descriptor and descriptor.__doc__) or None - if info: - self.info = info - util.set_creation_order(self) - - def _comparator_factory(self, mapper): - return self.comparator_factory(self, mapper) diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index 64f09ea2f..a41ea49e8 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -2472,9 +2472,8 @@ class AttributeEvents(event.Events): :param collection_adapter: the :class:`.CollectionAdapter` that will mediate internal access to the collection. - .. versionadded:: 1.0.0 the :meth:`.AttributeEvents.init_collection` - and :meth:`.AttributeEvents.dispose_collection` events supersede - the :class:`.orm.collection.linker` hook. + .. versionadded:: 1.0.0 :meth:`.AttributeEvents.init_collection` + and :meth:`.AttributeEvents.dispose_collection` events. .. seealso:: @@ -2504,8 +2503,7 @@ class AttributeEvents(event.Events): would be empty. .. versionadded:: 1.0.0 the :meth:`.AttributeEvents.init_collection` - and :meth:`.AttributeEvents.dispose_collection` events supersede - the :class:`.collection.linker` hook. + and :meth:`.AttributeEvents.dispose_collection` events. .. seealso:: diff --git a/lib/sqlalchemy/orm/identity.py b/lib/sqlalchemy/orm/identity.py index e8e39346e..e4795a92d 100644 --- a/lib/sqlalchemy/orm/identity.py +++ b/lib/sqlalchemy/orm/identity.py @@ -7,7 +7,6 @@ import weakref -from . import attributes from . import util as orm_util from .. import exc as sa_exc from .. import util @@ -239,128 +238,3 @@ class WeakInstanceDict(IdentityMap): if st is state: self._dict.pop(state.key, None) self._manage_removed_state(state) - - def prune(self): - return 0 - - -class StrongInstanceDict(IdentityMap): - """A 'strong-referencing' version of the identity map. - - .. deprecated 1.1:: - The strong - reference identity map is legacy. See the - recipe at :ref:`session_referencing_behavior` for - an event-based approach to maintaining strong identity - references. - - - """ - - if util.py2k: - - def itervalues(self): - return self._dict.itervalues() - - def iteritems(self): - return self._dict.iteritems() - - def __iter__(self): - return iter(self.dict_) - - def __getitem__(self, key): - return self._dict[key] - - def __contains__(self, key): - return key in self._dict - - def get(self, key, default=None): - return self._dict.get(key, default) - - def values(self): - return self._dict.values() - - def items(self): - return self._dict.items() - - def all_states(self): - return [attributes.instance_state(o) for o in self.values()] - - def contains_state(self, state): - return ( - state.key in self - and attributes.instance_state(self[state.key]) is state - ) - - def replace(self, state): - if state.key in self._dict: - existing = self._dict[state.key] - existing = attributes.instance_state(existing) - if existing is not state: - self._manage_removed_state(existing) - else: - return - else: - existing = None - - self._dict[state.key] = state.obj() - self._manage_incoming_state(state) - return existing - - def add(self, state): - if state.key in self: - if attributes.instance_state(self._dict[state.key]) is not state: - raise sa_exc.InvalidRequestError( - "Can't attach instance " - "%s; another instance with key %s is already " - "present in this session." - % (orm_util.state_str(state), state.key) - ) - return False - else: - self._dict[state.key] = state.obj() - self._manage_incoming_state(state) - return True - - def _add_unpresent(self, state, key): - # inlined form of add() called by loading.py - self._dict[key] = state.obj() - state._instance_dict = self._wr - - def _fast_discard(self, state): - # used by InstanceState for state being - # GC'ed, inlines _managed_removed_state - try: - obj = self._dict[state.key] - except KeyError: - # catch gc removed the key after we just checked for it - pass - else: - if attributes.instance_state(obj) is state: - self._dict.pop(state.key, None) - - def discard(self, state): - self.safe_discard(state) - - def safe_discard(self, state): - if state.key in self._dict: - obj = self._dict[state.key] - st = attributes.instance_state(obj) - if st is state: - self._dict.pop(state.key, None) - self._manage_removed_state(state) - - def prune(self): - """prune unreferenced, non-dirty states.""" - - ref_count = len(self) - dirty = [s.obj() for s in self.all_states() if s.modified] - - # work around http://bugs.python.org/issue6149 - keepers = weakref.WeakValueDictionary() - keepers.update(self) - - self._dict.clear() - self._dict.update(keepers) - self.modified = bool(dirty) - return ref_count - len(self) diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index f4e20afdf..3e4c3a5d5 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -107,13 +107,6 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr): _dispose_called = False @util.deprecated_params( - order_by=( - "1.1", - "The :paramref:`.mapper.order_by` parameter " - "is deprecated, and will be removed in a future release. " - "Use :meth:`.Query.order_by` to determine the ordering of a " - "result set.", - ), non_primary=( "1.3", "The :paramref:`.mapper.non_primary` parameter is deprecated, " @@ -133,7 +126,6 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr): inherits=None, inherit_condition=None, inherit_foreign_keys=None, - order_by=False, always_refresh=False, version_id_col=None, version_id_generator=None, @@ -341,11 +333,6 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr): :ref:`relationship_non_primary_mapper` - :param order_by: A single :class:`.Column` or list of :class:`.Column` - objects for which selection operations should use as the default - ordering for entities. By default mappers have no pre-defined - ordering. - :param passive_deletes: Indicates DELETE behavior of foreign key columns when a joined-table inheritance entity is being deleted. Defaults to ``False`` for a base mapper; for an inheriting mapper, @@ -604,11 +591,6 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr): self._primary_key_argument = util.to_list(primary_key) self.non_primary = non_primary - if order_by is not False: - self.order_by = util.to_list(order_by) - else: - self.order_by = order_by - self.always_refresh = always_refresh if isinstance(version_id_col, MapperProperty): @@ -1065,13 +1047,6 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr): ) ) - if ( - self.order_by is False - and not self.concrete - and self.inherits.order_by is not False - ): - self.order_by = self.inherits.order_by - self.polymorphic_map = self.inherits.polymorphic_map self.batch = self.inherits.batch self.inherits._inheriting_mappers.append(self) diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index aa9dd3274..05904d1a9 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -14,7 +14,6 @@ mapped attributes. from __future__ import absolute_import from . import attributes -from .descriptor_props import ComparableProperty from .descriptor_props import CompositeProperty from .descriptor_props import ConcreteInheritedProperty from .descriptor_props import SynonymProperty @@ -30,7 +29,6 @@ from ..sql import roles __all__ = [ "ColumnProperty", - "ComparableProperty", "CompositeProperty", "ConcreteInheritedProperty", "RelationshipProperty", diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index d001ab983..e131a4aa3 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -1765,10 +1765,7 @@ class Query(Generative): the newly resulting ``Query`` All existing ORDER BY settings can be suppressed by - passing ``None`` - this will suppress any ordering configured - on the :func:`.mapper` object using the deprecated - :paramref:`.mapper.order_by` parameter. - + passing ``None``. """ if len(criterion) == 1: @@ -3439,7 +3436,8 @@ class Query(Generative): "Using the Query.instances() method without a context " "is deprecated and will be disallowed in a future release. " "Please make use of :meth:`.Query.from_statement` " - "for linking ORM results to arbitrary select constructs." + "for linking ORM results to arbitrary select constructs.", + version="1.4", ) context = QueryContext(self) @@ -4256,15 +4254,6 @@ class _MapperEntity(_QueryEntity): # if self._adapted_selectable is None: context.froms += (self.selectable,) - if context.order_by is False and self.mapper.order_by: - context.order_by = self.mapper.order_by - - # apply adaptation to the mapper's order_by if needed. - if adapter: - context.order_by = adapter.adapt_list( - util.to_list(context.order_by) - ) - loading._setup_entity_query( context, self.mapper, diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index aa55fab58..98fa819b1 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -234,8 +234,7 @@ class SessionTransaction(object): "transaction is in progress" ) - if self.session._enable_transaction_accounting: - self._take_snapshot(autobegin=autobegin) + self._take_snapshot(autobegin=autobegin) self.session.dispatch.after_transaction_create(self.session, self) @@ -514,8 +513,7 @@ class SessionTransaction(object): self._state = COMMITTED self.session.dispatch.after_commit(self.session) - if self.session._enable_transaction_accounting: - self._remove_snapshot() + self._remove_snapshot() self.close() return self._parent @@ -543,10 +541,9 @@ class SessionTransaction(object): rollback_err = sys.exc_info() finally: transaction._state = DEACTIVE - if self.session._enable_transaction_accounting: - transaction._restore_snapshot( - dirty_only=transaction.nested - ) + transaction._restore_snapshot( + dirty_only=transaction.nested + ) boundary = transaction break else: @@ -554,11 +551,7 @@ class SessionTransaction(object): sess = self.session - if ( - not rollback_err - and sess._enable_transaction_accounting - and not sess._is_clean() - ): + if not rollback_err and not sess._is_clean(): # if items were added, deleted, or mutated # here, we need to re-restore the snapshot @@ -654,32 +647,13 @@ class Session(_SessionClassMethods): "scalar", ) - @util.deprecated_params( - weak_identity_map=( - "1.0", - "The :paramref:`.Session.weak_identity_map` parameter as well as " - "the strong-referencing identity map are deprecated, and will be " - "removed in a future release. For the use case where objects " - "present in a :class:`.Session` need to be automatically strong " - "referenced, see the recipe at " - ":ref:`session_referencing_behavior` for an event-based approach " - "to maintaining strong identity references. ", - ), - _enable_transaction_accounting=( - "0.7", - "The :paramref:`.Session._enable_transaction_accounting` " - "parameter is deprecated and will be removed in a future release.", - ), - ) def __init__( self, bind=None, autoflush=True, expire_on_commit=True, - _enable_transaction_accounting=True, autocommit=False, twophase=False, - weak_identity_map=None, binds=None, enable_baked_queries=True, info=None, @@ -782,10 +756,6 @@ class Session(_SessionClassMethods): .. versionadded:: 1.2 - :param _enable_transaction_accounting: A - legacy-only flag which when ``False`` disables *all* 0.5-style - object accounting on transaction boundaries. - :param expire_on_commit: Defaults to ``True``. When ``True``, all instances will be fully expired after each :meth:`~.commit`, so that all attribute/object access subsequent to a completed @@ -813,20 +783,8 @@ class Session(_SessionClassMethods): called. This allows each database to roll back the entire transaction, before each transaction is committed. - :param weak_identity_map: Defaults to ``True`` - when set to - ``False``, objects placed in the :class:`.Session` will be - strongly referenced until explicitly removed or the - :class:`.Session` is closed. - - """ - - if weak_identity_map in (True, None): - self._identity_cls = identity.WeakInstanceDict - else: - self._identity_cls = identity.StrongInstanceDict - - self.identity_map = self._identity_cls() + self.identity_map = identity.WeakInstanceDict() self._new = {} # InstanceState->object, strong refs object self._deleted = {} # same @@ -840,7 +798,6 @@ class Session(_SessionClassMethods): self.autocommit = autocommit self.expire_on_commit = expire_on_commit self.enable_baked_queries = enable_baked_queries - self._enable_transaction_accounting = _enable_transaction_accounting self.twophase = twophase self._query_cls = query_cls if query_cls else query.Query @@ -1353,7 +1310,7 @@ class Session(_SessionClassMethods): """ all_states = self.identity_map.all_states() + list(self._new) - self.identity_map = self._identity_cls() + self.identity_map = identity.WeakInstanceDict() self._new = {} self._deleted = {} @@ -1841,25 +1798,6 @@ class Session(_SessionClassMethods): self._new.pop(state) state._detach(self) - @util.deprecated( - "0.7", - "The :meth:`.Session.prune` method is deprecated along with " - ":paramref:`.Session.weak_identity_map`. This method will be " - "removed in a future release.", - ) - def prune(self): - """Remove unreferenced instances cached in the identity map. - - Note that this method is only meaningful if "weak_identity_map" is set - to False. The default weak identity map is self-pruning. - - Removes any object in this Session's identity map that is not - referenced in user code, modified, new or scheduled for deletion. - Returns the number of objects pruned. - - """ - return self.identity_map.prune() - def expunge(self, instance): """Remove the `instance` from this ``Session``. @@ -1981,7 +1919,7 @@ class Session(_SessionClassMethods): self._new.pop(state) def _register_altered(self, states): - if self._enable_transaction_accounting and self._transaction: + if self._transaction: for state in states: if state in self._new: self._transaction._new[state] = True @@ -1991,7 +1929,7 @@ class Session(_SessionClassMethods): def _remove_newly_deleted(self, states): persistent_to_deleted = self.dispatch.persistent_to_deleted or None for state in states: - if self._enable_transaction_accounting and self._transaction: + if self._transaction: self._transaction._deleted[state] = True if persistent_to_deleted is not None: @@ -2981,15 +2919,7 @@ class Session(_SessionClassMethods): finally: self._flushing = False - @util.deprecated_params( - passive=( - "0.8", - "The :paramref:`.Session.is_modified.passive` flag is deprecated " - "and will be removed in a future release. The flag is no longer " - "used and is ignored.", - ) - ) - def is_modified(self, instance, include_collections=True, passive=None): + def is_modified(self, instance, include_collections=True): r"""Return ``True`` if the given instance has locally modified attributes. @@ -3038,7 +2968,6 @@ class Session(_SessionClassMethods): way to detect only local-column based properties (i.e. scalar columns or many-to-one foreign keys) that would result in an UPDATE for this instance upon flush. - :param passive: not used """ state = object_state(instance) diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 696741216..bb08d31ea 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -1228,9 +1228,6 @@ class SubqueryLoader(PostLoader): q._distinct = True break - if q._order_by is False: - q._order_by = leftmost_mapper.order_by - # don't need ORDER BY if no limit/offset if q._limit is None and q._offset is None: q._order_by = None diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py index 1fa5bf245..1fe51514e 100644 --- a/lib/sqlalchemy/orm/strategy_options.py +++ b/lib/sqlalchemy/orm/strategy_options.py @@ -1010,6 +1010,8 @@ See :func:`.orm.%(name)s` for usage examples. "name": self.name } fn = util.deprecated( + # This is used by `baked_lazyload_all` was only deprecated in + # version 1.2 so this must stick around until that is removed "0.9", "The :func:`.%(name)s_all` function is deprecated, and will be " "removed in a future release. Please use method chaining with " @@ -1092,7 +1094,8 @@ def contains_eager(loadopt, attr, alias=None): "Passing a string name for the 'alias' argument to " "'contains_eager()` is deprecated, and will not work in a " "future release. Please use a sqlalchemy.alias() or " - "sqlalchemy.orm.aliased() construct." + "sqlalchemy.orm.aliased() construct.", + version="1.4", ) elif getattr(attr, "_of_type", None): @@ -1580,7 +1583,8 @@ def defer(key, *addl_attrs, **kw): util.warn_deprecated( "The *addl_attrs on orm.defer is deprecated. Please use " "method chaining in conjunction with defaultload() to " - "indicate a path." + "indicate a path.", + version="1.3", ) return _UnboundLoad._from_keys( _UnboundLoad.defer, (key,) + addl_attrs, False, kw @@ -1642,7 +1646,8 @@ def undefer(key, *addl_attrs): util.warn_deprecated( "The *addl_attrs on orm.undefer is deprecated. Please use " "method chaining in conjunction with defaultload() to " - "indicate a path." + "indicate a path.", + version="1.3", ) return _UnboundLoad._from_keys( _UnboundLoad.undefer, (key,) + addl_attrs, False, {} |