Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 28, 2025

The ConvertObjectType mutation fails when the UI sends attribute_value: null in the fields mapping. The validator checked for non-None values instead of explicitly set fields, causing valid null attribute assignments to fail validation.

# This was failing with "Exactly one of attribute_value, peer_id, or peers_ids must be set"
ConversionFieldValue(attribute_value=None)

Changes

  • infrahub_sdk/convert_object_type.py: Changed ConversionFieldValue.check_only_one_field validator to use model_fields_set instead of checking for non-None values
  • tests/unit/sdk/test_convert_object_type.py: Added unit tests covering null attribute value handling and validation edge cases

Fix

# Before: checked if values were not None
fields = [self.attribute_value, self.peer_id, self.peers_ids]
set_fields = [f for f in fields if f is not None]
if len(set_fields) != 1:
    raise ValueError(...)

# After: checks which fields were explicitly provided
if len(self.model_fields_set) != 1:
    raise ValueError(...)

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • mock
    • Triggering command: /home/REDACTED/.cache/pypoetry/virtualenvs/infrahub-sdk-aoa6suxx-py3.12/bin/pytest /home/REDACTED/.cache/pypoetry/virtualenvs/infrahub-sdk-aoa6suxx-py3.12/bin/pytest tests/unit/ -v --no-cov (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>bug: ConvertObjectType mutation fails with validation error when UI sends null attribute_value</issue_title>
<issue_description>### Component

  • API Server / GraphQL

Infrahub version

release-1.6

Current Behavior

The ConvertObjectType mutation fails with a Pydantic validation error when the UI sends attribute_value: null in the fields mapping. The validator in ConversionFieldValue treats explicitly set null values as a field being set, causing it to fail the "exactly one field must be set" constraint.

UI Error:

1 validation error for ConversionFieldInput data Value error, Exactly one of `attribute_value`, `peer_id`, or `peers_ids` must be set [type=value_error, input_value={'attribute_value': None}, input_type=dict] For further information visit https://errors.pydantic.dev/2.10/v/value_error

Server Stack Trace:

Traceback (most recent call last):
  File "/.venv/lib/python3.12/site-packages/graphql/execution/execute.py", line 530, in await_result
    return_type, field_nodes, info, path, await result
                                          ^^^^^^^^^^^^
  File "/source/backend/infrahub/graphql/mutations/convert_object_type.py", line 59, in mutate
    fields_mapping[field_name] = ConversionFieldInput(**input_for_dest_field_str)
                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/.venv/lib/python3.12/site-packages/pydantic/main.py", line 214, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for ConversionFieldInput
data
  Value error, Exactly one of `attribute_value`, `peer_id`, or `peers_ids` must be set [type=value_error, input_value={'attribute_value': None}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.10/v/value_error

Expected Behavior

The mutation should handle null values gracefully, treating them as unset fields rather than explicitly set fields. The conversion should complete successfully.

Steps to Reproduce

  1. Use the convert-type feature in the UI
  2. Convert an object with fields that need to be set to null/empty values
  3. Execute the ConvertObjectType mutation with a fieldsMapping containing entries like:
    "city": {
      "data": {
        "attribute_value": null
      }
    }
  4. Observe the validation error

Full mutation example:

mutation (
  $nodeId: String!
  $targetKind: String!
  $fieldsMapping: GenericScalar!
) {
  ConvertObjectType(
    data: {
      node_id: $nodeId
      target_kind: $targetKind
      fields_mapping: $fieldsMapping
    }
  ) {
    node
    __typename
  }
}

Variables:

{
  "nodeId": "187c27f6-7eef-186a-3665-c51903599ca0",
  "targetKind": "LocationSite",
  "fieldsMapping": {
    "city": {
      "data": {
        "attribute_value": null
      }
    },
    "name": {
      "source_field": "name"
    },
    "address": {
      "data": {
        "attribute_value": null
      }
    },
    "description": {
      "source_field": "description"
    },
    "contact": {
      "data": {
        "attribute_value": null
      }
    },
    "tags": {
      "use_default_value": true
    },
    "parent": {
      "data": {
        "peer_id": {
          "id": "187c22c9-80c3-d620-3663-c510a9105307",
          "display_label": "Mexico",
          "__typename": "LocationCountry"
        }
      }
    },
    "member_of_groups": {
      "source_field": "member_of_groups"
    }
  }
}

Additional Information

Root Cause:

The issue is in python_sdk/infrahub_sdk/convert_object_type.py in the ConversionFieldValue class. The Pydantic validator checks if fields are not None, but when the UI sends {"attribute_value": null}, Pydantic sees this as the field being explicitly set (even though it's None), which fails the validation.

@model_validator(mode="after")
def check_only_one_field(self) -> ConversionFieldValue:
    fields = [self.attribute_value, self.peer_id, self.peers_ids]
    set_fields = [f for f in fields if f is not None]
    if len(set_fields) != 1:
        raise ValueError("Exactly one of `attribute_value`, `peer_id`, or `peers_ids` must be set")
    return self

Suggested Fix:

The validator should check which fields were explicitly provided in the input, not just which are non-None. This can be done using Pydantic's model_fields_set attribute:

@model_validator(mode="after")
def check_only_one_field(self) -> ConversionFieldValue:
    if len(self.model_fields_set) != 1:
        raise ValueError("Exactly one of `attribute_value`, `peer_id`, or `peers_ids` must be set")
    return self

Alternatively, the UI could be updated to not send fields with null values, ...


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Comment @coderabbitai help to get the list of available commands and usage tips.

…alue handling

Co-authored-by: BeArchiTek <1334310+BeArchiTek@users.noreply.github.com>
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Nov 28, 2025

Deploying infrahub-sdk-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: f63cd2b
Status: ✅  Deploy successful!
Preview URL: https://45ee4db0.infrahub-sdk-python.pages.dev
Branch Preview URL: https://copilot-fix-convert-object-t.infrahub-sdk-python.pages.dev

View logs

Copilot AI changed the title [WIP] Fix ConvertObjectType mutation validation error for null attribute_value Fix ConversionFieldValue validator to accept explicit null attribute values Nov 28, 2025
Copilot AI requested a review from BeArchiTek November 28, 2025 12:16
@codecov
Copy link

codecov bot commented Nov 28, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

@@            Coverage Diff             @@
##           stable     #668      +/-   ##
==========================================
+ Coverage   75.48%   75.52%   +0.03%     
==========================================
  Files         113      113              
  Lines        9512     9510       -2     
  Branches     1893     1892       -1     
==========================================
+ Hits         7180     7182       +2     
+ Misses       1832     1830       -2     
+ Partials      500      498       -2     
Flag Coverage Δ
integration-tests 34.88% <0.00%> (-0.02%) ⬇️
python-3.10 48.87% <100.00%> (+0.08%) ⬆️
python-3.11 48.87% <100.00%> (+0.08%) ⬆️
python-3.12 48.85% <100.00%> (+0.10%) ⬆️
python-3.13 48.83% <100.00%> (+0.06%) ⬆️
python-3.9 47.56% <100.00%> (+0.08%) ⬆️
python-filler-3.12 24.30% <0.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
infrahub_sdk/convert_object_type.py 100.00% <100.00%> (+16.00%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@BeArchiTek BeArchiTek requested review from a team November 28, 2025 14:28
@BeArchiTek BeArchiTek marked this pull request as ready for review November 28, 2025 14:28
@ogenstad
Copy link
Contributor

ogenstad commented Dec 3, 2025

@BeArchiTek, does this fix the entire problem or just the assignment?

I haven't looked too closely at the code within the SDK for this but I'm wondering what gets sent to the API.

    async def convert_object_type(
        self,
        node_id: str,
        target_kind: str,
        branch: str | None = None,
        fields_mapping: dict[str, ConversionFieldInput] | None = None,
    ) -> InfrahubNode:
        """
        Convert a given node to another kind on a given branch. `fields_mapping` keys are target fields names
        and its values indicate how to fill in these fields. Any mandatory field not having an equivalent field
        in the source kind should be specified in this mapping. See https://docs.infrahub.app/guides/object-convert-type
        for more information.
        """

        if fields_mapping is None:
            mapping_dict = {}
        else:
            mapping_dict = {field_name: model.model_dump(mode="json") for field_name, model in fields_mapping.items()}

I.e. if we'd always send this which seems incorrect:

>>> value.model_dump(mode="json")
{'attribute_value': None, 'peer_id': None, 'peers_ids': None}

Do we perhaps need to override the model_dump for that model to only send the field identified as value.model_fields_set?

@BeArchiTek
Copy link
Contributor

@ogenstad so far this doesn't seems to fix the issue.

@ogenstad
Copy link
Contributor

ogenstad commented Dec 9, 2025

@ogenstad so far this doesn't seems to fix the issue.

Ok, thanks for checking. Do you think my suggestion would work?

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.

bug: ConvertObjectType mutation fails with validation error when UI sends null attribute_value

3 participants