From: Mike Bayer Date: Mon, 11 Jan 2010 03:16:10 +0000 (+0000) Subject: - cut down on a few hundred method calls X-Git-Tag: rel_0_6beta1~85 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f4b143685cea4c5e14c21238a56fbf8e1fd7a4f1;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - cut down on a few hundred method calls --- diff --git a/examples/beaker_caching/ad_hoc.py b/examples/beaker_caching/ad_hoc.py index 10d80a72c9..bb6b0823be 100644 --- a/examples/beaker_caching/ad_hoc.py +++ b/examples/beaker_caching/ad_hoc.py @@ -30,7 +30,7 @@ def load_name_range(start, end, invalidate=False): """ q = Session.query(Person).\ filter(Person.name.between("person %.2d" % start, "person %.2d" % end)).\ - options(*cache_address_bits).\ + options(cache_address_bits).\ options(FromCache("default", "name_range")) # have the "addresses" collection cached separately diff --git a/examples/beaker_caching/demo.py b/examples/beaker_caching/demo.py index c4da9f4b29..f2f038d28b 100644 --- a/examples/beaker_caching/demo.py +++ b/examples/beaker_caching/demo.py @@ -11,7 +11,7 @@ from meta import Session from sqlalchemy.orm import eagerload import os -for p in Session.query(Person).options(eagerload(Person.addresses), *cache_address_bits): +for p in Session.query(Person).options(eagerload(Person.addresses), cache_address_bits): print p.format_full() diff --git a/examples/beaker_caching/meta.py b/examples/beaker_caching/meta.py index 458a2c68f9..026d2ce67d 100644 --- a/examples/beaker_caching/meta.py +++ b/examples/beaker_caching/meta.py @@ -89,7 +89,6 @@ class CachingQuery(Query): """ if hasattr(self, 'cache_region'): cache, cache_key = self._get_cache_plus_key() - ret = cache.get_value(cache_key, createfunc=lambda: list(Query.__iter__(self))) return iter(self.session.merge(x, load=False) for x in ret) else: diff --git a/examples/beaker_caching/model.py b/examples/beaker_caching/model.py index 73f4a80d6f..25ce162fc9 100644 --- a/examples/beaker_caching/model.py +++ b/examples/beaker_caching/model.py @@ -92,9 +92,9 @@ class Person(Base): # Caching options. A set of three FromCache options # which can be applied to Query(), causing the "lazy load" # of these attributes to be loaded from cache. -cache_address_bits = [ +cache_address_bits = ( FromCache("default", "byid", PostalCode.city), FromCache("default", "byid", City.country), FromCache("default", "byid", Address.postal_code), - ] + ) diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 67c9664ad9..3021f99042 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -1479,7 +1479,13 @@ def is_instrumented(instance, key): return manager_of_class(instance.__class__).is_instrumented(key, search=True) class InstrumentationRegistry(object): - """Private instrumentation registration singleton.""" + """Private instrumentation registration singleton. + + All classes are routed through this registry + when first instrumented, however the InstrumentationRegistry + is not actually needed unless custom ClassManagers are in use. + + """ _manager_finders = weakref.WeakKeyDictionary() _state_finders = util.WeakIdentityMapping() @@ -1510,6 +1516,9 @@ class InstrumentationRegistry(object): manager = _ClassInstrumentationAdapter(class_, manager) if factory != ClassManager and not self._extended: + # somebody invoked a custom ClassManager. + # reinstall global "getter" functions with the more + # expensive ones. self._extended = True _install_lookup_strategy(self) @@ -1548,6 +1557,7 @@ class InstrumentationRegistry(object): return factories def manager_of_class(self, cls): + # this is only called when alternate instrumentation has been established if cls is None: return None try: @@ -1583,7 +1593,9 @@ class InstrumentationRegistry(object): del self._manager_finders[class_] del self._state_finders[class_] del self._dict_finders[class_] - + if ClassManager.MANAGER_ATTR in class_.__dict__: + delattr(class_, ClassManager.MANAGER_ATTR) + instrumentation_registry = InstrumentationRegistry() def _install_lookup_strategy(implementation): @@ -1596,16 +1608,23 @@ def _install_lookup_strategy(implementation): and unit tests specific to this behavior. """ - global instance_state, instance_dict + global instance_state, instance_dict, manager_of_class if implementation is util.symbol('native'): instance_state = attrgetter(ClassManager.STATE_ATTR) instance_dict = attrgetter("__dict__") + def manager_of_class(cls): + return cls.__dict__.get(ClassManager.MANAGER_ATTR, None) else: instance_state = instrumentation_registry.state_of instance_dict = instrumentation_registry.dict_of + manager_of_class = instrumentation_registry.manager_of_class -manager_of_class = instrumentation_registry.manager_of_class _create_manager_for_cls = instrumentation_registry.create_manager_for_cls + +# Install default "lookup" strategies. These are basically +# very fast attrgetters for key attributes. +# When a custom ClassManager is installed, more expensive per-class +# strategies are copied over these. _install_lookup_strategy(util.symbol('native')) def find_native_user_instrumentation_hook(cls): diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 8399ee1525..9e03771051 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -317,7 +317,7 @@ class Mapper(object): """ manager = attributes.manager_of_class(self.class_) - + if self.non_primary: if not manager or manager.mapper is None: raise sa_exc.InvalidRequestError( @@ -803,7 +803,8 @@ class Mapper(object): def get_property(self, key, resolve_synonyms=False, raiseerr=True): """return a MapperProperty associated with the given key.""" - self.compile() + if not self.compiled: + self.compile() return self._get_property(key, resolve_synonyms=resolve_synonyms, raiseerr=raiseerr) def _get_property(self, key, resolve_synonyms=False, raiseerr=True): @@ -818,7 +819,8 @@ class Mapper(object): @property def iterate_properties(self): """return an iterator of all MapperProperty objects.""" - self.compile() + if not self.compiled: + self.compile() return self._props.itervalues() def _mappers_from_spec(self, spec, selectable): diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index c8224da87a..5b2fbb5240 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -658,7 +658,7 @@ class Query(object): # most MapperOptions write to the '_attributes' dictionary, # so copy that as well self._attributes = self._attributes.copy() - opts = [o for o in util.flatten_iterator(args)] + opts = list(util.flatten_iterator(args)) self._with_options = self._with_options + opts if conditional: for opt in opts: diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index 8217bfa993..6590864d6a 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -1104,7 +1104,11 @@ class Session(object): util.warn_deprecated("dont_load=True has been renamed to load=False.") _recursive = {} - self._autoflush() + + if load: + # flush current contents if we expect to load data + self._autoflush() + _object_mapper(instance) # verify mapped autoflush = self.autoflush try: diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 8dd6bd309f..042f067103 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -159,11 +159,11 @@ class InstanceState(object): if self.modified: self._strong_obj = state['instance'] - self.__dict__.update( + self.__dict__.update([ (k, state[k]) for k in ( 'key', 'load_options', 'expired_attributes', 'mutable_dict' ) if k in state - ) + ]) if 'load_path' in state: self.load_path = interfaces.deserialize_path(state['load_path']) diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 60c03be8e8..919f980b6e 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -7,9 +7,11 @@ import sqlalchemy.exceptions as sa_exc from sqlalchemy import sql, util from sqlalchemy.sql import expression, util as sql_util, operators -from sqlalchemy.orm.interfaces import MapperExtension, EXT_CONTINUE, PropComparator, MapperProperty, AttributeExtension +from sqlalchemy.orm.interfaces import MapperExtension, EXT_CONTINUE, PropComparator, \ + MapperProperty, AttributeExtension from sqlalchemy.orm import attributes, exc +mapperlib = None all_cascades = frozenset(("delete", "delete-orphan", "all", "merge", "expunge", "save-update", "refresh-expire", @@ -488,17 +490,27 @@ def _entity_info(entity, compile=True): """ if isinstance(entity, AliasedClass): return entity._AliasedClass__mapper, entity._AliasedClass__alias, True - elif _is_mapped_class(entity): - if isinstance(entity, type): - mapper = class_mapper(entity, compile) - else: - if compile: - mapper = entity.compile() - else: - mapper = entity - return mapper, mapper._with_polymorphic_selectable, False + + global mapperlib + if mapperlib is None: + from sqlalchemy.orm import mapperlib + + if isinstance(entity, mapperlib.Mapper): + mapper = entity + + elif isinstance(entity, type): + class_manager = attributes.manager_of_class(entity) + + if class_manager is None: + return None, entity, False + + mapper = class_manager.mapper else: return None, entity, False + + if compile: + mapper = mapper.compile() + return mapper, mapper._with_polymorphic_selectable, False def _entity_descriptor(entity, key): """Return attribute/property information given an entity and string name. @@ -600,8 +612,10 @@ def _state_has_identity(state): return bool(state.key) def _is_mapped_class(cls): - from sqlalchemy.orm import mapperlib as mapper - if isinstance(cls, (AliasedClass, mapper.Mapper)): + global mapperlib + if mapperlib is None: + from sqlalchemy.orm import mapperlib + if isinstance(cls, (AliasedClass, mapperlib.Mapper)): return True if isinstance(cls, expression.ClauseElement): return False diff --git a/test/aaa_profiling/test_orm.py b/test/aaa_profiling/test_orm.py index 58fca389e4..327f3226fb 100644 --- a/test/aaa_profiling/test_orm.py +++ b/test/aaa_profiling/test_orm.py @@ -58,7 +58,7 @@ class MergeTest(_base.MappedTest): # down from 185 on this # this is a small slice of a usually bigger # operation so using a small variance - @profiling.function_call_count(94, variance=0.001) + @profiling.function_call_count(87, variance=0.001) def go(): return sess2.merge(p1, load=False) @@ -66,7 +66,7 @@ class MergeTest(_base.MappedTest): # third call, merge object already present. # almost no calls. - @profiling.function_call_count(15, variance=0.001) + @profiling.function_call_count(10, variance=0.001) def go(): return sess2.merge(p2, load=False)