summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/ext/declarative/clsregistry.py56
-rw-r--r--lib/sqlalchemy/orm/relationships.py50
2 files changed, 92 insertions, 14 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 (
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index 8b7a4b549..c6f3bc30a 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -217,7 +217,19 @@ class RelationshipProperty(StrategizedProperty):
:paramref:`~.relationship.argument` may also be passed as a callable
function which is evaluated at mapper initialization time, and may
- be passed as a Python-evaluable string when using Declarative.
+ be passed as a string name when using Declarative.
+
+ .. warning:: Prior to SQLAlchemy 1.3.16, this value is interpreted
+ using Python's ``eval()`` function.
+ **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
+ See :ref:`declarative_relationship_eval` for details on
+ declarative evaluation of :func:`.relationship` arguments.
+
+ .. versionchanged 1.3.16::
+
+ The string evaluation of the main "argument" no longer accepts an
+ open ended Python expression, instead only accepting a string
+ class name or dotted package-qualified name.
.. seealso::
@@ -237,6 +249,12 @@ class RelationshipProperty(StrategizedProperty):
present in the :class:`.MetaData` collection associated with the
parent-mapped :class:`.Table`.
+ .. warning:: When passed as a Python-evaluable string, the
+ argument is interpreted using Python's ``eval()`` function.
+ **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
+ See :ref:`declarative_relationship_eval` for details on
+ declarative evaluation of :func:`.relationship` arguments.
+
The :paramref:`~.relationship.secondary` keyword argument is
typically applied in the case where the intermediary :class:`.Table`
is not otherwise expressed in any direct class mapping. If the
@@ -482,6 +500,12 @@ class RelationshipProperty(StrategizedProperty):
and may be passed as a Python-evaluable string when using
Declarative.
+ .. warning:: When passed as a Python-evaluable string, the
+ argument is interpreted using Python's ``eval()`` function.
+ **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
+ See :ref:`declarative_relationship_eval` for details on
+ declarative evaluation of :func:`.relationship` arguments.
+
.. seealso::
:ref:`relationship_foreign_keys`
@@ -641,6 +665,12 @@ class RelationshipProperty(StrategizedProperty):
function which is evaluated at mapper initialization time, and may
be passed as a Python-evaluable string when using Declarative.
+ .. warning:: When passed as a Python-evaluable string, the
+ argument is interpreted using Python's ``eval()`` function.
+ **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
+ See :ref:`declarative_relationship_eval` for details on
+ declarative evaluation of :func:`.relationship` arguments.
+
:param passive_deletes=False:
Indicates loading behavior during delete operations.
@@ -733,6 +763,12 @@ class RelationshipProperty(StrategizedProperty):
and may be passed as a Python-evaluable string when using
Declarative.
+ .. warning:: When passed as a Python-evaluable string, the
+ argument is interpreted using Python's ``eval()`` function.
+ **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
+ See :ref:`declarative_relationship_eval` for details on
+ declarative evaluation of :func:`.relationship` arguments.
+
.. seealso::
:ref:`relationship_primaryjoin`
@@ -746,6 +782,12 @@ class RelationshipProperty(StrategizedProperty):
and may be passed as a Python-evaluable string when using
Declarative.
+ .. warning:: When passed as a Python-evaluable string, the
+ argument is interpreted using Python's ``eval()`` function.
+ **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
+ See :ref:`declarative_relationship_eval` for details on
+ declarative evaluation of :func:`.relationship` arguments.
+
.. seealso::
:ref:`self_referential` - in-depth explanation of how
@@ -780,6 +822,12 @@ class RelationshipProperty(StrategizedProperty):
and may be passed as a Python-evaluable string when using
Declarative.
+ .. warning:: When passed as a Python-evaluable string, the
+ argument is interpreted using Python's ``eval()`` function.
+ **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
+ See :ref:`declarative_relationship_eval` for details on
+ declarative evaluation of :func:`.relationship` arguments.
+
.. seealso::
:ref:`relationship_primaryjoin`