-
Notifications
You must be signed in to change notification settings - Fork 30
Description
Issue
When running analyze explores
, analyze models
, vacuum explores
or vacuum models
over Looker, henry fails with an AssertionError like this:
File "henry/modules/fetcher.py", line 315, in get_explore_join_stats
assert isinstance(explore.scopes, MutableSequence)
~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
or this:
File "henry/modules/fetcher.py", line 158, in get_explores
assert isinstance(m.explores, list)
~~~~~~~~~~^^^^^^^^^^^^^^^^^^
AssertionError
or this:
File "henry/commands/analyze.py", line 66, in models
assert isinstance(m.explores, list)
~~~~~~~~~~^^^^^^^^^^^^^^^^^^
AssertionError
Cause
On 31-Aug-2025 the cattrs
upgrade from v25.1.1 to v25.2 introduced a potentially breaking change.
Sequences are now converted to tuples, rather than lists.
The looker-sdk
package uses cattrs
to create objects from api call responses. The latest version (cattrs>=1.3
) is installed as part of looker-sdk
.
In henry/modules/fetcher.py
Fetcher.get_explore_join_stats
there's an assertion to check that explore.scopes
is a MutableSequence before it is manipulated. explore.scopes
is returned via the looker-sdk
, and therefore cattrs
.
With cattrs v25.1.1
it was a list - so the assertion passed.
With cattrs v25.2
it's a tuple - so the assertion throws an AssertionException.
Simple fix if you've hit this bug in your environment
Check your cattrs
version:
pip show cattrs
Downgrade cattrs
to v25.1.1
pip install cattrs==25.1.1
To recreate the error
Upgrade cattrs
to v25.2
pip install cattrs==25.2
Run henry
henry analyze models
henry analyze explores
henry vacuum models
henry vacuum explores
Code fix for developers
-
In
henry/modules/fetcher.py
Fetcher.get_explores
(line 158)
Check to see whetherm.scopes
is a tuple, and if it is, cast it to a list before making the assertion.Add these lines:
if isinstance(m.explores, tuple): m.explores = list(m.explores)
Above this assertion:
assert isinstance(m.explores, list)
-
In
henry/modules/fetcher.py
Fetcher.get_explore_join_stats
(line 315)
Check to see whetherexplore.scopes
is a tuple, and if it is, cast it to a list before making the assertion.Add these lines:
if isinstance(explore.scopes, tuple): explore.scopes = list(explore.scopes)
Above this assertion:
assert isinstance(explore.scopes, MutableSequence)
-
In
henry/commands/analyze.py
models()
(line 66)
Check whetherm.explores
is a tuple, and if it is, cast it to a list before making the assertion.Add these lines:
if isinstance(m.explores, tuple): m.explores = list(m.explores)
Above this assertion:
assert isinstance(m.explores, list)