Skip to content

analyze and vacuum fail with AssertionError in get_explore_join_stats #132

@jennph

Description

@jennph

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

  1. In henry/modules/fetcher.py Fetcher.get_explores (line 158)
    Check to see whether m.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)
    
  2. In henry/modules/fetcher.py Fetcher.get_explore_join_stats (line 315)
    Check to see whether explore.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)
    
  3. In henry/commands/analyze.py models() (line 66)
    Check whether m.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)
    

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions