diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-04-07 17:37:14 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-04-07 19:37:39 -0400 |
| commit | 17e31604ae13ebd58b148a4319cfed09e5448ee2 (patch) | |
| tree | 52952e0f02676562f23f990bfaab2164d48f544d /lib/sqlalchemy/ext/declarative | |
| parent | 5150ef4ed166042b4a1f4a77b5a0af609b5fc660 (diff) | |
| download | sqlalchemy-17e31604ae13ebd58b148a4319cfed09e5448ee2.tar.gz | |
Use dot-separated name resolution for relationship target
The string argument accepted as the first positional argument by the
:func:`.relationship` function when using the Declarative API is no longer
interpreted using the Python ``eval()`` function; instead, the name is dot
separated and the names are looked up directly in the name resolution
dictionary without treating the value as a Python expression. However,
passing a string argument to the other :func:`.relationship` parameters
that necessarily must accept Python expressions will still use ``eval()``;
the documentation has been clarified to ensure that there is no ambiguity
that this is in use.
Fixes: #5238
Change-Id: Id802f403190adfab0ca034afe2214ba10fd9cfbb
Diffstat (limited to 'lib/sqlalchemy/ext/declarative')
| -rw-r--r-- | lib/sqlalchemy/ext/declarative/clsregistry.py | 56 |
1 files changed, 43 insertions, 13 deletions
diff --git a/lib/sqlalchemy/ext/declarative/clsregistry.py b/lib/sqlalchemy/ext/declarative/clsregistry.py index 71594aae7..219c4ba2e 100644 --- a/lib/sqlalchemy/ext/declarative/clsregistry.py +++ b/lib/sqlalchemy/ext/declarative/clsregistry.py @@ -289,6 +289,38 @@ class _class_resolver(object): return self.fallback[key] + def _raise_for_name(self, name, err): + util.raise_( + exc.InvalidRequestError( + "When initializing mapper %s, expression %r failed to " + "locate a name (%r). If this is a class name, consider " + "adding this relationship() to the %r class after " + "both dependent classes have been defined." + % (self.prop.parent, self.arg, name, self.cls) + ), + from_=err, + ) + + def _resolve_name(self): + name = self.arg + d = self._dict + rval = None + try: + for token in name.split("."): + if rval is None: + rval = d[token] + else: + rval = getattr(rval, token) + except KeyError as err: + self._raise_for_name(name, err) + except NameError as n: + self._raise_for_name(n.args[0], n) + else: + if isinstance(rval, _GetColumns): + return rval.cls + else: + return rval + def __call__(self): try: x = eval(self.arg, globals(), self._dict) @@ -298,16 +330,7 @@ class _class_resolver(object): else: return x except NameError as n: - util.raise_( - exc.InvalidRequestError( - "When initializing mapper %s, expression %r failed to " - "locate a name (%r). If this is a class name, consider " - "adding this relationship() to the %r class after " - "both dependent classes have been defined." - % (self.prop.parent, self.arg, n.args[0], self.cls) - ), - from_=n, - ) + self._raise_for_name(n.args[0], n) def _resolver(cls, prop): @@ -320,16 +343,18 @@ def _resolver(cls, prop): def resolve_arg(arg): return _class_resolver(cls, prop, fallback, arg) - return resolve_arg + def resolve_name(arg): + return _class_resolver(cls, prop, fallback, arg)._resolve_name + + return resolve_name, resolve_arg def _deferred_relationship(cls, prop): if isinstance(prop, RelationshipProperty): - resolve_arg = _resolver(cls, prop) + resolve_name, resolve_arg = _resolver(cls, prop) for attr in ( - "argument", "order_by", "primaryjoin", "secondaryjoin", @@ -341,6 +366,11 @@ def _deferred_relationship(cls, prop): if isinstance(v, util.string_types): setattr(prop, attr, resolve_arg(v)) + for attr in ("argument",): + v = getattr(prop, attr) + if isinstance(v, util.string_types): + setattr(prop, attr, resolve_name(v)) + if prop.backref and isinstance(prop.backref, tuple): key, kwargs = prop.backref for attr in ( |
