summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext/declarative
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2020-04-07 17:37:14 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2020-04-07 19:37:39 -0400
commit17e31604ae13ebd58b148a4319cfed09e5448ee2 (patch)
tree52952e0f02676562f23f990bfaab2164d48f544d /lib/sqlalchemy/ext/declarative
parent5150ef4ed166042b4a1f4a77b5a0af609b5fc660 (diff)
downloadsqlalchemy-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.py56
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 (