Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- Fixed `inspect` showing `AttributeError` for descriptors that raise; now falls back to `inspect.getattr_static` https://github.com/Textualize/rich/pull/4124

## [15.0.0] - 2026-04-12

### Changed
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,4 @@ The following people have contributed to the development of Rich:
- [Alex Zheng](https://github.com/alexzheng111)
- [Sebastian Speitel](https://github.com/SebastianSpeitel)
- [Kevin Turcios](https://github.com/KRRT7)
- [Truffle](https://github.com/truffle-dev)
12 changes: 11 additions & 1 deletion rich/_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,19 @@ def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]:
return (callable(value), key.strip("_").lower())

def safe_getattr(attr_name: str) -> Tuple[Any, Any]:
"""Get attribute or any exception."""
"""Get attribute or any exception.

Falls back to ``inspect.getattr_static`` if ``getattr`` raises
``AttributeError``, so that descriptors which signal "no such
attribute" (e.g. SWIG bindings, lazy properties) are still surfaced.
"""
try:
return (None, getattr(obj, attr_name))
except AttributeError:
try:
return (None, inspect.getattr_static(obj, attr_name))
except AttributeError as error:
return (error, None)
except Exception as error:
return (error, None)

Expand Down
17 changes: 17 additions & 0 deletions tests/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,23 @@ def __class__(self):
assert False, f"Object with no __class__ shouldn't raise {e}"


def test_inspect_getattr_static_fallback():
"""Issue #3794 - Properties that raise AttributeError fall back to
inspect.getattr_static so the descriptor is shown instead of the error."""

class Thing:
@property
def maybe(self):
raise AttributeError("not available on this instance")

def __dir__(self):
return ["maybe"]

rendered = render(Thing())
assert "AttributeError" not in rendered
assert "property" in rendered


def test_inspect_module_with_class():
def function():
pass
Expand Down