Skip to content

fix: handle list[dict] variable values in BuildVariable hash#3072

Open
gaetan-h wants to merge 2 commits into
cognitedata:mainfrom
gaetan-h:fix/hashable-dict-list-build-variables
Open

fix: handle list[dict] variable values in BuildVariable hash#3072
gaetan-h wants to merge 2 commits into
cognitedata:mainfrom
gaetan-h:fix/hashable-dict-list-build-variables

Conversation

@gaetan-h

Copy link
Copy Markdown
Contributor

Summary

cdf modules list (and any command that calls compare_modules()) crashes with:

TypeError: unhashable type: 'dict'
  File "_module_resources.py", line 163, in compare_modules
    if set(current_module_variables) != set(cached_module.build_variables):

when a config variable has a list-of-dicts value — for example:

variables:
  modules:
    site_readiness:
      - site_name_lower: clov
        site_name_upper: CLOV
        site_prefix: CLV
      - site_name_lower: egina
        site_name_upper: EGINA
        site_prefix: EGI

Root Cause

BuildVariable is a frozen dataclass, so Python auto-generates __hash__ from all fields including value. Both load_raw() and load() converted list values with tuple(value), but if the list contains dicts, those dicts end up inside the tuple and are still unhashable — causing set() to fail.

Fix

Extract a _to_hashable() helper that recursively converts lists and dicts to nested tuples of sorted key-value pairs:

def _to_hashable(v: Any) -> Any:
    if isinstance(v, list):
        return tuple(_to_hashable(i) for i in v)
    if isinstance(v, dict):
        return tuple(sorted((k, _to_hashable(val)) for k, val in v.items()))
    return v

Applied in both:

  • load_raw() — live variables parsed from config.*.yaml
  • BuildVariable.load() — variables read back from build_info.*.yaml cache

Tests

Two new test cases in test_build_variables.py:

  • test_load_raw_list_of_dicts_is_hashable — verifies load_raw() with a list[dict] variable produces a hashable BuildVariable
  • test_load_from_cache_list_of_dicts_is_hashable — verifies BuildVariable.load() (cache read path) does the same

All 10 existing + new tests pass.

When a config variable has a list-of-dicts value (e.g. a site_readiness
list where each item is a dict of per-site fields), both load_raw() and
load() wrapped it in tuple() — but the dicts inside the tuple are still
unhashable. This caused compare_modules() to crash with:

  TypeError: unhashable type: 'dict'

when calling set(current_module_variables) or set(cached_module.build_variables).

Fix: extract a _to_hashable() helper that recursively converts lists and
dicts to nested tuples of sorted key-value pairs, making any variable
value safe to use in a set() or as a frozen dataclass hash.

Applied in both load_raw() (live variables from config parsing) and
load() (variables read back from build_info.*.yaml cache).
@gaetan-h gaetan-h requested review from a team as code owners June 23, 2026 07:18
@gemini-code-assist

Copy link
Copy Markdown
Contributor
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Module Name and Location columns were truncated with '…' when the
terminal was not wide enough. Add min_width and no_wrap=True so the
table expands beyond the terminal width and allows horizontal scrolling
rather than hiding content. Also justify numeric columns right and
wrap long header labels to reduce header row height.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant