diff options
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r-- | lib/sqlalchemy/orm/path_registry.py | 11 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategy_options.py | 54 | ||||
-rw-r--r-- | lib/sqlalchemy/testing/fixtures.py | 3 |
4 files changed, 29 insertions, 40 deletions
diff --git a/lib/sqlalchemy/orm/path_registry.py b/lib/sqlalchemy/orm/path_registry.py index e7084fbf6..e1cbd9313 100644 --- a/lib/sqlalchemy/orm/path_registry.py +++ b/lib/sqlalchemy/orm/path_registry.py @@ -119,6 +119,8 @@ class PathRegistry(HasCacheKey): is_property = False is_entity = False + is_unnatural: bool + path: _PathRepresentation natural_path: _PathRepresentation parent: Optional[PathRegistry] @@ -510,7 +512,12 @@ class PropRegistry(PathRegistry): # given MapperProperty's parent. insp = cast("_InternalEntityType[Any]", parent[-1]) natural_parent: AbstractEntityRegistry = parent - self.is_unnatural = False + + # inherit "is_unnatural" from the parent + if parent.parent.is_unnatural: + self.is_unnatural = True + else: + self.is_unnatural = False if not insp.is_aliased_class or insp._use_mapper_path: # type: ignore parent = natural_parent = parent.parent[prop.parent] @@ -570,6 +577,7 @@ class PropRegistry(PathRegistry): self.parent = parent self.path = parent.path + (prop,) self.natural_path = natural_parent.natural_path + (prop,) + self.has_entity = prop._links_to_entity if prop._is_relationship: if TYPE_CHECKING: @@ -674,7 +682,6 @@ class AbstractEntityRegistry(CreatesToken): # elif not parent.path and self.is_aliased_class: # self.natural_path = (self.entity._generate_cache_key()[0], ) else: - # self.natural_path = parent.natural_path + (entity, ) self.natural_path = self.path def _truncate_recursive(self) -> AbstractEntityRegistry: diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 5581e5c7f..8e06c4f59 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -1063,6 +1063,7 @@ class LazyLoader( if extra_options: stmt._with_options += extra_options + stmt._compile_options += {"_current_path": effective_path} if use_get: diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py index 48e69aef2..2e073f326 100644 --- a/lib/sqlalchemy/orm/strategy_options.py +++ b/lib/sqlalchemy/orm/strategy_options.py @@ -927,7 +927,9 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption): ) -> Optional[_PathRepresentation]: i = -1 - for i, (c_token, p_token) in enumerate(zip(to_chop, path.path)): + for i, (c_token, p_token) in enumerate( + zip(to_chop, path.natural_path) + ): if isinstance(c_token, str): if i == 0 and c_token.endswith(f":{_DEFAULT_TOKEN}"): return to_chop @@ -942,36 +944,8 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption): elif ( isinstance(c_token, InspectionAttr) and insp_is_mapper(c_token) - and ( - (insp_is_mapper(p_token) and c_token.isa(p_token)) - or ( - # a too-liberal check here to allow a path like - # A->A.bs->B->B.cs->C->C.ds, natural path, to chop - # against current path - # A->A.bs->B(B, B2)->B(B, B2)->cs, in an of_type() - # scenario which should only be occurring in a loader - # that is against a non-aliased lead element with - # single path. otherwise the - # "B" won't match into the B(B, B2). - # - # i>=2 prevents this check from proceeding for - # the first path element. - # - # if we could do away with the "natural_path" - # concept, we would not need guessy checks like this - # - # two conflicting tests for this comparison are: - # test_eager_relations.py-> - # test_lazyload_aliased_abs_bcs_two - # and - # test_of_type.py->test_all_subq_query - # - i >= 2 - and insp_is_aliased_class(p_token) - and p_token._is_with_polymorphic - and c_token in p_token.with_polymorphic_mappers - ) - ) + and insp_is_mapper(p_token) + and c_token.isa(p_token) ): continue @@ -1321,7 +1295,7 @@ class _WildcardLoad(_AbstractLoad): strategy: Optional[Tuple[Any, ...]] local_opts: _OptsType - path: Tuple[str, ...] + path: Union[Tuple[()], Tuple[str]] propagate_to_loaders = False def __init__(self) -> None: @@ -1366,6 +1340,7 @@ class _WildcardLoad(_AbstractLoad): it may be used as the sub-option of a :class:`_orm.Load` object. """ + assert self.path attr = self.path[0] if attr.endswith(_DEFAULT_TOKEN): attr = f"{attr.split(':')[0]}:{_WILDCARD_TOKEN}" @@ -1396,13 +1371,16 @@ class _WildcardLoad(_AbstractLoad): start_path: _PathRepresentation = self.path - # TODO: chop_path already occurs in loader.process_compile_state() - # so we will seek to simplify this if current_path: + # TODO: no cases in test suite where we actually get + # None back here new_path = self._chop_path(start_path, current_path) - if not new_path: + if new_path is None: return - start_path = new_path + + # chop_path does not actually "chop" a wildcard token path, + # just returns it + assert new_path == start_path # start_path is a single-token tuple assert start_path and len(start_path) == 1 @@ -1618,7 +1596,9 @@ class _LoadElement( """ - chopped_start_path = Load._chop_path(effective_path.path, current_path) + chopped_start_path = Load._chop_path( + effective_path.natural_path, current_path + ) if not chopped_start_path: return None diff --git a/lib/sqlalchemy/testing/fixtures.py b/lib/sqlalchemy/testing/fixtures.py index fc1fa1483..bff251b0f 100644 --- a/lib/sqlalchemy/testing/fixtures.py +++ b/lib/sqlalchemy/testing/fixtures.py @@ -13,6 +13,7 @@ import itertools import random import re import sys +from typing import Any import sqlalchemy as sa from . import assertions @@ -675,7 +676,7 @@ class MappedTest(TablesTest, assertions.AssertsExecutionResults): # 'once', 'each', None run_setup_mappers = "each" - classes = None + classes: Any = None @config.fixture(autouse=True, scope="class") def _setup_tables_test_class(self): |