|
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