for memoization on a class that uses slots.
- apply many more __slots__. mem use for nova now at 46% savings
loader strategy system.
A bench that makes use of heapy measure the startup size of Nova
-illustrates a difference of about 2 megs of memory, a total of 27%
+illustrates a difference of about 2 megs of memory, a total of 46%
of memory taken up by SQLAlchemy's objects, associated dictionaries, as
well as weakrefs, within a basic import of "nova.db.sqlalchemy.models"::
# associated dicts + weakref-related objects with core of Nova imported:
Before: total count 26477 total bytes 7975712
- After: total count 21413 total bytes 5752976
+ After: total count 18181 total bytes 4236456
# reported for the Python module space overall with the
# core of Nova imported:
Before: Partition of a set of 355558 objects. Total size = 61661760 bytes.
- After: Partition of a set of 350281 objects. Total size = 59415104 bytes.
+ After: Partition of a set of 346034 objects. Total size = 57808016 bytes.
.. _feature_updatemany:
.. autoclass:: SchemaItem
:members:
+ :undoc-members:
.. autoclass:: Table
:members:
.. autoclass:: sqlalchemy.orm.base.InspectionAttr
:members:
+.. autoclass:: sqlalchemy.orm.base.InspectionAttrInfo
+ :members:
.. autoclass:: sqlalchemy.orm.state.InstanceState
:members:
.. autoclass:: sqlalchemy.orm.interfaces.MapperProperty
:members:
+ .. py:attribute:: info
+
+ Info dictionary associated with the object, allowing user-defined
+ data to be associated with this :class:`.InspectionAttr`.
+
+ The dictionary is generated when first accessed. Alternatively,
+ it can be specified as a constructor argument to the
+ :func:`.column_property`, :func:`.relationship`, or :func:`.composite`
+ functions.
+
+ .. versionadded:: 0.8 Added support for .info to all
+ :class:`.MapperProperty` subclasses.
+
+ .. versionchanged:: 1.0.0 :attr:`.InspectionAttr.info` moved
+ from :class:`.MapperProperty` so that it can apply to a wider
+ variety of ORM and extension constructs.
+
+ .. seealso::
+
+ :attr:`.QueryableAttribute.info`
+
+ :attr:`.SchemaItem.info`
+
.. autodata:: sqlalchemy.orm.interfaces.NOT_EXTENSION
import collections
-class RefCollection(object):
- @util.memoized_property
- def ref(self):
+class RefCollection(util.MemoizedSlots):
+ __slots__ = 'ref',
+
+ def _memoized_attr_ref(self):
return weakref.ref(self, registry._collection_gced)
class _ClsLevelDispatch(RefCollection):
"""Class-level events on :class:`._Dispatch` classes."""
+ __slots__ = ('name', 'arg_names', 'has_kw',
+ 'legacy_signatures', '_clslevel')
+
def __init__(self, parent_dispatch_cls, fn):
self.name = fn.__name__
argspec = util.inspect_getargspec(fn)
key=lambda s: s[0]
)
))
- self.__doc__ = fn.__doc__ = legacy._augment_fn_docs(
- self, parent_dispatch_cls, fn)
+ fn.__doc__ = legacy._augment_fn_docs(self, parent_dispatch_cls, fn)
self._clslevel = weakref.WeakKeyDictionary()
return self
-class _InstanceLevelDispatch(object):
+class _InstanceLevelDispatch(RefCollection):
__slots__ = ()
def _adjust_fn_spec(self, fn, named):
class _CompoundListener(_InstanceLevelDispatch):
_exec_once = False
- __slots__ = ()
+ __slots__ = '_exec_once_mutex',
- @util.memoized_property
- def _exec_once_mutex(self):
+ def _memoized_attr__exec_once_mutex(self):
return threading.Lock()
def exec_once(self, *args, **kw):
__nonzero__ = __bool__
-class _ListenerCollection(RefCollection, _CompoundListener):
+class _ListenerCollection(_CompoundListener):
"""Instance-level attributes on instances of :class:`._Dispatch`.
Represents a collection of listeners.
"""
- # RefCollection has a @memoized_property, so can't do
- # __slots__ here
+ __slots__ = 'parent_listeners', 'parent', 'name', 'listeners', 'propagate'
def __init__(self, parent, target_cls):
if target_cls not in parent._clslevel:
"""
-class AssociationProxy(interfaces.InspectionAttr):
+class AssociationProxy(interfaces.InspectionAttrInfo):
"""A descriptor that presents a read/write view of an object attribute."""
is_attribute = False
"""
-class hybrid_method(interfaces.InspectionAttr):
+class hybrid_method(interfaces.InspectionAttrInfo):
"""A decorator which allows definition of a Python object method with both
instance-level and class-level behavior.
return self
-class hybrid_property(interfaces.InspectionAttr):
+class hybrid_property(interfaces.InspectionAttrInfo):
"""A decorator which allows definition of a Python descriptor with both
instance-level and class-level behavior.
here intact for forwards-compatibility.
"""
-
__slots__ = ()
is_selectable = False
"""
+
+class InspectionAttrInfo(InspectionAttr):
+ """Adds the ``.info`` attribute to :class:`.Inspectionattr`.
+
+ """
+
@util.memoized_property
def info(self):
"""Info dictionary associated with the object, allowing user-defined
class. **Deprecated.** Please see :class:`.AttributeEvents`.
"""
+ super(CompositeProperty, self).__init__()
self.attrs = attrs
self.composite_class = class_
return comparator_callable
def __init__(self):
+ super(ConcreteInheritedProperty, self).__init__()
def warn():
raise AttributeError("Concrete %s does not implement "
"attribute %r at the instance level. Add "
more complicated attribute-wrapping schemes than synonyms.
"""
+ super(SynonymProperty, self).__init__()
self.name = name
self.map_column = map_column
.. 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
from ..sql import operators
from .base import (ONETOMANY, MANYTOONE, MANYTOMANY,
EXT_CONTINUE, EXT_STOP, NOT_EXTENSION)
-from .base import InspectionAttr, _MappedAttribute
+from .base import (InspectionAttr, InspectionAttr,
+ InspectionAttrInfo, _MappedAttribute)
import collections
# imported later
)
-class MapperProperty(_MappedAttribute, InspectionAttr):
+class MapperProperty(_MappedAttribute, InspectionAttr, util.MemoizedSlots):
"""Manage the relationship of a ``Mapper`` to a single class
attribute, as well as that attribute as it appears on individual
instances of the class, including attribute instrumentation,
"""
+ __slots__ = (
+ '_configure_started', '_configure_finished', 'parent', 'key',
+ 'info'
+ )
+
cascade = frozenset()
"""The set of 'cascade' attribute names.
"""
+ def _memoized_attr_info(self):
+ """Info dictionary associated with the object, allowing user-defined
+ data to be associated with this :class:`.InspectionAttr`.
+
+ The dictionary is generated when first accessed. Alternatively,
+ it can be specified as a constructor argument to the
+ :func:`.column_property`, :func:`.relationship`, or :func:`.composite`
+ functions.
+
+ .. versionadded:: 0.8 Added support for .info to all
+ :class:`.MapperProperty` subclasses.
+
+ .. versionchanged:: 1.0.0 :attr:`.InspectionAttr.info` moved
+ from :class:`.MapperProperty` so that it can apply to a wider
+ variety of ORM and extension constructs.
+
+ .. seealso::
+
+ :attr:`.QueryableAttribute.info`
+
+ :attr:`.SchemaItem.info`
+
+ """
+ return {}
+
+
def setup(self, context, entity, path, adapter, **kwargs):
"""Called by Query for the purposes of constructing a SQL statement.
"""
- _configure_started = False
- _configure_finished = False
+ def __init__(self):
+ self._configure_started = False
+ self._configure_finished = False
def init(self):
"""Called after all mappers are created to assemble
"""
+ __slots__ = '_strategies', 'strategy'
+
strategy_wildcard_key = None
def _get_context_loader(self, context, path):
not mapper.class_manager._attr_has_impl(self.key):
self.strategy.init_class_attribute(mapper)
- _strategies = collections.defaultdict(dict)
+ _all_strategies = collections.defaultdict(dict)
@classmethod
def strategy_for(cls, **kw):
def decorate(dec_cls):
dec_cls._strategy_keys = []
key = tuple(sorted(kw.items()))
- cls._strategies[cls][key] = dec_cls
+ cls._all_strategies[cls][key] = dec_cls
dec_cls._strategy_keys.append(key)
return dec_cls
return decorate
@classmethod
def _strategy_lookup(cls, *key):
for prop_cls in cls.__mro__:
- if prop_cls in cls._strategies:
- strategies = cls._strategies[prop_cls]
+ if prop_cls in cls._all_strategies:
+ strategies = cls._all_strategies[prop_cls]
try:
return strategies[key]
except KeyError:
class _ColumnMapping(dict):
"""Error reporting helper for mapper._columntoproperty."""
+ __slots__ = 'mapper',
+
def __init__(self, mapper):
self.mapper = mapper
strategy_wildcard_key = 'column'
+ __slots__ = (
+ '_orig_columns', 'columns', 'group', 'deferred',
+ 'instrument', 'comparator_factory', 'descriptor', 'extension',
+ 'active_history', 'expire_on_flush', 'info', 'doc',
+ 'strategy_class', '_creation_order', '_is_polymorphic_discriminator',
+ '_mapped_by_synonym')
+
def __init__(self, *columns, **kwargs):
"""Provide a column-level property for use with a Mapper.
**Deprecated.** Please see :class:`.AttributeEvents`.
"""
+ super(ColumnProperty, self).__init__()
self._orig_columns = [expression._labeled(c) for c in columns]
self.columns = [expression._labeled(_orm_full_deannotate(c))
for c in columns]
elif dest_state.has_identity and self.key not in dest_dict:
dest_state._expire_attributes(dest_dict, [self.key])
- class Comparator(PropComparator):
+ class Comparator(util.MemoizedSlots, PropComparator):
"""Produce boolean, comparison, and other operators for
:class:`.ColumnProperty` attributes.
"""
- @util.memoized_instancemethod
- def __clause_element__(self):
+ __slots__ = '__clause_element__', 'info'
+
+ def _memoized_method___clause_element__(self):
if self.adapter:
return self.adapter(self.prop.columns[0])
else:
"parententity": self._parentmapper,
"parentmapper": self._parentmapper})
- @util.memoized_property
- def info(self):
+ def _memoized_attr_info(self):
ce = self.__clause_element__()
try:
return ce.info
except AttributeError:
return self.prop.info
- def __getattr__(self, key):
+ def _fallback_getattr(self, key):
"""proxy attribute access down to the mapped column.
this allows user-defined comparison methods to be accessed.
"""
+ super(RelationshipProperty, self).__init__()
self.uselist = uselist
self.argument = argument
'all', 'none', 'delete-orphan'])
_allowed_cascades = all_cascades
+ __slots__ = (
+ 'save_update', 'delete', 'refresh_expire', 'merge',
+ 'expunge', 'delete_orphan')
+
def __new__(cls, value_list):
if isinstance(value_list, str) or value_list is None:
return cls.from_string(value_list)
raise sa_exc.ArgumentError(
"Invalid cascade option(s): %s" %
", ".join([repr(x) for x in
- sorted(
- values.difference(cls._allowed_cascades)
- )])
- )
+ sorted(values.difference(cls._allowed_cascades))]))
if "all" in values:
values.update(cls._add_w_all_cascades)
]
return cls(values)
+
def _validator_events(
desc, key, validator, include_removes, include_backrefs):
"""Runs a validation method on an attribute value to be set or
"""
+ __slots__ = '_all_col_set', '_all_columns'
+
def __init__(self, *columns):
super(ColumnCollection, self).__init__()
- self.__dict__['_all_col_set'] = util.column_set()
- self.__dict__['_all_columns'] = []
+ object.__setattr__(self, '_all_col_set', util.column_set())
+ object.__setattr__(self, '_all_columns', [])
for c in columns:
self.add(c)
return util.OrderedProperties.__contains__(self, other)
def __getstate__(self):
- return {'_data': self.__dict__['_data'],
- '_all_columns': self.__dict__['_all_columns']}
+ return {'_data': self._data,
+ '_all_columns': self._all_columns}
def __setstate__(self, state):
- self.__dict__['_data'] = state['_data']
- self.__dict__['_all_columns'] = state['_all_columns']
- self.__dict__['_all_col_set'] = util.column_set(state['_all_columns'])
+ object.__setattr__(self, '_data', state['_data'])
+ object.__setattr__(self, '_all_columns', state['_all_columns'])
+ object.__setattr__(
+ self, '_all_col_set', util.column_set(state['_all_columns']))
def contains_column(self, col):
# this has to be done via set() membership
class ImmutableColumnCollection(util.ImmutableProperties, ColumnCollection):
def __init__(self, data, colset, all_columns):
util.ImmutableProperties.__init__(self, data)
- self.__dict__['_all_col_set'] = colset
- self.__dict__['_all_columns'] = all_columns
+ object.__setattr__(self, '_all_col_set', colset)
+ object.__setattr__(self, '_all_columns', all_columns)
extend = remove = util.ImmutableProperties._immutable
__visit_name__ = 'release_savepoint'
-class quoted_name(util.text_type):
+class quoted_name(util.MemoizedSlots, util.text_type):
"""Represent a SQL identifier combined with quoting preferences.
:class:`.quoted_name` is a Python unicode/str subclass which
"""
+ __slots__ = 'quote', 'lower', 'upper'
+
def __new__(cls, value, quote):
if value is None:
return None
def __reduce__(self):
return quoted_name, (util.text_type(self), self.quote)
- @util.memoized_instancemethod
- def lower(self):
+ def _memoized_method_lower(self):
if self.quote:
return self
else:
return util.text_type(self).lower()
- @util.memoized_instancemethod
- def upper(self):
+ def _memoized_method_upper(self):
if self.quote:
return self
else:
"""A unicode subclass used to identify symbolic "
"names that may require truncation."""
+ __slots__ = ()
+
def __new__(cls, value, quote=None):
quote = getattr(value, "quote", quote)
# return super(_truncated_label, cls).__new__(cls, value, quote, True)
:ref:`constraint_naming_conventions`
"""
+ __slots__ = ()
class _defer_name(_truncated_label):
generation.
"""
+ __slots__ = ()
+
def __new__(cls, value):
if value is None:
return _NONE_NAME
class _defer_none_name(_defer_name):
"""indicate a 'deferred' name that was ultimately the value None."""
+ __slots__ = ()
_NONE_NAME = _defer_none_name("_unnamed_")
"""A unicode subclass used to identify anonymously
generated names."""
+ __slots__ = ()
+
def __add__(self, other):
return _anonymous_label(
quoted_name(
generic_repr, counter, PluginLoader, hybridproperty, hybridmethod, \
safe_reraise,\
get_callable_argspec, only_once, attrsetter, ellipses_string, \
- warn_limited, map_bits
+ warn_limited, map_bits, MemoizedSlots
from .deprecations import warn_deprecated, warn_pending_deprecation, \
deprecated, pending_deprecation, inject_docstring_text
class Properties(object):
"""Provide a __getattr__/__setattr__ interface over a dict."""
+ __slots__ = '_data',
+
def __init__(self, data):
- self.__dict__['_data'] = data
+ object.__setattr__(self, '_data', data)
def __len__(self):
return len(self._data)
def __delitem__(self, key):
del self._data[key]
- def __setattr__(self, key, object):
- self._data[key] = object
+ def __setattr__(self, key, obj):
+ self._data[key] = obj
def __getstate__(self):
return {'_data': self.__dict__['_data']}
"""Provide a __getattr__/__setattr__ interface with an OrderedDict
as backing store."""
+ __slots__ = ()
+
def __init__(self):
Properties.__init__(self, OrderedDict())
class ImmutableProperties(ImmutableContainer, Properties):
"""Provide immutable dict/object attribute to an underlying dictionary."""
+ __slots__ = ()
+
class OrderedDict(dict):
"""A dict that returns keys/values/items in the order they were added."""
+ __slots__ = '_list',
+
+ def __reduce__(self):
+ return OrderedDict, (self.items(),)
+
def __init__(self, ____sequence=None, **kwargs):
self._list = []
if ____sequence is None:
"""
+ __slots__ = 'target', 'name', '__weakref__'
+
+ def __getstate__(self):
+ return {'target': self.target, 'name': self.name}
+
+ def __setstate__(self, state):
+ self.target = state['target']
+ self.name = state['name']
+
def __init__(self, meth):
self.target = meth.__self__
self.name = meth.__name__
return memoized_instancemethod(fn)
+class MemoizedSlots(object):
+ """Apply memoized items to an object using a __getattr__ scheme.
+
+ This allows the functionality of memoized_property and
+ memoized_instancemethod to be available to a class using __slots__.
+
+ """
+
+ def _fallback_getattr(self, key):
+ raise AttributeError(key)
+
+ def __getattr__(self, key):
+ if key.startswith('_memoized'):
+ raise AttributeError(key)
+ elif hasattr(self, '_memoized_attr_%s' % key):
+ value = getattr(self, '_memoized_attr_%s' % key)()
+ setattr(self, key, value)
+ return value
+ elif hasattr(self, '_memoized_method_%s' % key):
+ fn = getattr(self, '_memoized_method_%s' % key)
+
+ def oneshot(*args, **kw):
+ result = fn(*args, **kw)
+ memo = lambda *a, **kw: result
+ memo.__name__ = fn.__name__
+ memo.__doc__ = fn.__doc__
+ setattr(self, key, memo)
+ return result
+ oneshot.__doc__ = fn.__doc__
+ return oneshot
+ else:
+ return self._fallback_getattr(key)
+
+
def dependency_for(modulename):
def decorate(obj):
# TODO: would be nice to improve on this import silliness,