|
6 | 6 | import typing
|
7 | 7 | import typing as t
|
8 | 8 | from collections import abc
|
| 9 | +from inspect import getattr_static |
9 | 10 | from itertools import chain
|
10 | 11 | from itertools import groupby
|
11 | 12 |
|
@@ -1411,31 +1412,25 @@ def do_reverse(value: t.Union[str, t.Iterable[V]]) -> t.Union[str, t.Iterable[V]
|
1411 | 1412 | def do_attr(
|
1412 | 1413 | environment: "Environment", obj: t.Any, name: str
|
1413 | 1414 | ) -> t.Union[Undefined, t.Any]:
|
1414 |
| - """Get an attribute of an object. ``foo|attr("bar")`` works like |
1415 |
| - ``foo.bar`` just that always an attribute is returned and items are not |
1416 |
| - looked up. |
| 1415 | + """Get an attribute of an object. ``foo|attr("bar")`` works like |
| 1416 | + ``foo.bar``, but returns undefined instead of falling back to ``foo["bar"]`` |
| 1417 | + if the attribute doesn't exist. |
1417 | 1418 |
|
1418 | 1419 | See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
|
1419 | 1420 | """
|
| 1421 | + # Environment.getattr will fall back to obj[name] if obj.name doesn't exist. |
| 1422 | + # But we want to call env.getattr to get behavior such as sandboxing. |
| 1423 | + # Determine if the attr exists first, so we know the fallback won't trigger. |
1420 | 1424 | try:
|
1421 |
| - name = str(name) |
1422 |
| - except UnicodeError: |
1423 |
| - pass |
1424 |
| - else: |
1425 |
| - try: |
1426 |
| - value = getattr(obj, name) |
1427 |
| - except AttributeError: |
1428 |
| - pass |
1429 |
| - else: |
1430 |
| - if environment.sandboxed: |
1431 |
| - environment = t.cast("SandboxedEnvironment", environment) |
1432 |
| - |
1433 |
| - if not environment.is_safe_attribute(obj, name, value): |
1434 |
| - return environment.unsafe_undefined(obj, name) |
1435 |
| - |
1436 |
| - return value |
1437 |
| - |
1438 |
| - return environment.undefined(obj=obj, name=name) |
| 1425 | + # This avoids executing properties/descriptors, but misses __getattr__ |
| 1426 | + # and __getattribute__ dynamic attrs. |
| 1427 | + getattr_static(obj, name) |
| 1428 | + except AttributeError: |
| 1429 | + # This finds dynamic attrs, and we know it's not a descriptor at this point. |
| 1430 | + if not hasattr(obj, name): |
| 1431 | + return environment.undefined(obj=obj, name=name) |
| 1432 | + |
| 1433 | + return environment.getattr(obj, name) |
1439 | 1434 |
|
1440 | 1435 |
|
1441 | 1436 | @typing.overload
|
|
0 commit comments