diff options
| author | mike bayer <mike_mp@zzzcomputing.com> | 2020-04-08 01:48:20 +0000 |
|---|---|---|
| committer | Gerrit Code Review <gerrit@bbpush.zzzcomputing.com> | 2020-04-08 01:48:20 +0000 |
| commit | 633518bfdf81d2c4a70c8e80cf07a02b832e2016 (patch) | |
| tree | e89ae583564994bbca45bfb694e5ddaedb3a84b8 /lib/sqlalchemy | |
| parent | 978755e851e505e2715e71efcb51b0904ded9f80 (diff) | |
| parent | 17e31604ae13ebd58b148a4319cfed09e5448ee2 (diff) | |
| download | sqlalchemy-633518bfdf81d2c4a70c8e80cf07a02b832e2016.tar.gz | |
Merge "Use dot-separated name resolution for relationship target"
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/ext/declarative/clsregistry.py | 56 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/relationships.py | 50 |
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` |
