Skip to content

refactor: simplify normalization functions and remove redundant type casts#551

Open
BastienGimbert wants to merge 12 commits intojabesq-org:developmentfrom
BastienGimbert:feat/normalize-weather-attributes-homestatus
Open

refactor: simplify normalization functions and remove redundant type casts#551
BastienGimbert wants to merge 12 commits intojabesq-org:developmentfrom
BastienGimbert:feat/normalize-weather-attributes-homestatus

Conversation

@BastienGimbert
Copy link

@BastienGimbert BastienGimbert commented Jan 29, 2026

Summary by Sourcery

Centralize and simplify normalization of weather-related API data while cleaning up redundant type casts and per-module normalization helpers.

Bug Fixes:

  • Normalize response bodies in extract_raw_data, including flattening dashboard_data and ensuring id is set from _id when missing.

Enhancements:

  • Introduce a shared recursive normalization helper for weather-related attributes in helpers.py and reuse it across response handling.
  • Remove the duplicate normalization logic and attribute mapping from account.py in favor of the shared helper.
  • Simplify id cleanup in fix_id by removing unnecessary type casts and using safer defaults when accessing response body tags.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 29, 2026

Reviewer's Guide

Refactors weather attribute normalization into shared helper functions, centralizes the attribute mapping, and simplifies ID handling and response extraction while removing redundant normalization and type casts from account handling code.

Sequence diagram for extract_raw_data with shared weather normalization

sequenceDiagram
    actor Caller
    participant Extractor as extract_raw_data
    participant Normalizer as normalize_weather_attributes
    participant DictNorm as _normalize_dict
    participant ValueNorm as _normalize_value
    participant FixId as fix_id

    Caller->>Extractor: extract_raw_data(resp, tag)
    Extractor->>Normalizer: normalize_weather_attributes(resp.body)
    Normalizer->>DictNorm: _normalize_dict(raw_data)
    loop for each key,value in raw_data
        DictNorm->>DictNorm: handle _id and dashboard_data
        alt value is dict
            DictNorm->>DictNorm: _normalize_dict(value)
        else value is list
            DictNorm->>ValueNorm: _normalize_value(item in list)
        else primitive value
            DictNorm->>ValueNorm: _normalize_value(value)
        end
    end
    DictNorm-->>Normalizer: normalized_body
    Normalizer-->>Extractor: body

    alt tag is homes
        Extractor->>FixId: fix_id(body.homes)
        FixId-->>Extractor: homes
        Extractor-->>Caller: {homes, errors}
    else other tag
        Extractor->>FixId: fix_id(body[tag])
        FixId-->>Extractor: raw_data
        Extractor-->>Caller: {tag: raw_data, errors}
    end
Loading

Flow diagram for recursive weather attribute normalization

flowchart TD
    A["normalize_weather_attributes(raw_data)"] --> B["_normalize_dict(raw_data)"]

    subgraph DictNormalization
        B --> C{iterate key,value in raw_data}
        C -->|key is _id| D[copy _id to normalized]
        C -->|key is dashboard_data and value is dict| E["_normalize_dict(value) and merge"]
        C -->|other keys| F[lookup mapped_key in ATTRIBUTES_TO_FIX]
        F --> G["_normalize_value(value)"]
        G --> H[store mapped_key: normalized_value]
        C -->|more items| C
        C -->|done| I{normalized contains _id and not id}
        I -->|yes| J[set id from _id]
        I -->|no| K[skip]
        J --> L[return normalized]
        K --> L[return normalized]
    end

    subgraph ValueNormalization
        G --> V1{value is dict}
        V1 -->|yes| V2["_normalize_dict(value)"]
        V1 -->|no| V3{value is list}
        V3 -->|yes| V4[for each item call _normalize_value]
        V3 -->|no| V5[return value]
    end

    A --> L
Loading

File-Level Changes

Change Details Files
Introduce shared recursive normalization helpers and centralized attribute mapping in helpers.py, and apply them in extract_raw_data.
  • Add global ATTRIBUTES_TO_FIX mapping for weather-related attribute renaming and ID aliasing.
  • Implement _normalize_value and _normalize_dict to recursively normalize dictionaries, lists, and flatten dashboard_data while mapping keys and deriving id from _id.
  • Expose normalize_weather_attributes wrapper to normalize API body data and integrate it into extract_raw_data before ID fixing and error handling.
  • Adjust extract_raw_data to operate on a normalized body, including defaulting missing tags to empty lists and using normalized errors.
src/pyatmo/helpers.py
Remove per-module normalization in account.py and rely on already-normalized device data, eliminating redundant attribute normalization and duplicated mapping logic.
  • Stop calling normalize_weather_attributes on module_data and device_data before storing modules_data and when updating Home instances.
  • Remove global ATTRIBUTES_TO_FIX and normalize_weather_attributes implementations from account.py, consolidating normalization logic into helpers.py.
  • Simplify update_devices flow by using raw device_data (now expected to be normalized earlier) when instantiating modules and updating internal maps.
src/pyatmo/account.py
Simplify ID sanitization by removing unnecessary type casts in fix_id.
  • Update fix_id to manipulate station['_id'] directly without casting, keeping module ID cleanup unchanged.
src/pyatmo/helpers.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • Normalization is now applied to the entire resp["body"] in extract_raw_data while the explicit normalize_weather_attributes calls were removed from account.update_devices; consider consolidating where normalization is expected to happen so that all device/module consumers see a consistent data shape.
  • By running normalize_weather_attributes over the whole response body you may also transform non‑device structures such as errors; consider scoping normalization to device/module payloads (e.g., devices, modules, homes) to avoid unexpected changes to ancillary fields.
  • Previously normalize_weather_attributes in account.py ensured _id was always mirrored in id for device/module dicts; with the refactor this guarantee is now tied to using the helper from helpers.py, so it may be worth double‑checking all call sites that rely on id being present without explicitly invoking normalization.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Normalization is now applied to the entire `resp["body"]` in `extract_raw_data` while the explicit `normalize_weather_attributes` calls were removed from `account.update_devices`; consider consolidating where normalization is expected to happen so that all device/module consumers see a consistent data shape.
- By running `normalize_weather_attributes` over the whole response body you may also transform non‑device structures such as `errors`; consider scoping normalization to device/module payloads (e.g., `devices`, `modules`, `homes`) to avoid unexpected changes to ancillary fields.
- Previously `normalize_weather_attributes` in `account.py` ensured `_id` was always mirrored in `id` for device/module dicts; with the refactor this guarantee is now tied to using the helper from `helpers.py`, so it may be worth double‑checking all call sites that rely on `id` being present without explicitly invoking normalization.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

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.

2 participants