From fc83332d3308cced43a8ae8ea303ea6da8b67687 Mon Sep 17 00:00:00 2001 From: vrathee-msft Date: Thu, 14 May 2026 18:01:07 -0700 Subject: [PATCH 1/7] Move to Create Entities API for table creation --- src/PowerPlatform/Dataverse/data/_odata.py | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/PowerPlatform/Dataverse/data/_odata.py b/src/PowerPlatform/Dataverse/data/_odata.py index b6cdf29a..f4e872f5 100644 --- a/src/PowerPlatform/Dataverse/data/_odata.py +++ b/src/PowerPlatform/Dataverse/data/_odata.py @@ -1190,9 +1190,9 @@ def _create_entity( attributes: List[Dict[str, Any]], solution_unique_name: Optional[str] = None, ) -> Dict[str, Any]: - url = f"{self.api}/EntityDefinitions" - payload = { - "@odata.type": "Microsoft.Dynamics.CRM.EntityMetadata", + url = f"{self.api}/CreateEntities" + payload = {"Entities" :[{ + "@odata.type": "Microsoft.Dynamics.CRM.ComplexEntityMetadata", "SchemaName": table_schema_name, "DisplayName": self._label(display_name), "DisplayCollectionName": self._label(display_name + "s"), @@ -1202,7 +1202,7 @@ def _create_entity( "HasNotes": True, "IsActivity": False, "Attributes": attributes, - } + }]} params = None if solution_unique_name: params = {"SolutionUniqueName": solution_unique_name} @@ -1590,7 +1590,7 @@ def _attribute_payload( label = column_schema_name.split("_")[-1] if dtype_l in ("string", "text"): return { - "@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata", + "@odata.type": "Microsoft.Dynamics.CRM.ComplexStringAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, @@ -1600,7 +1600,7 @@ def _attribute_payload( } if dtype_l in ("memo", "multiline"): return { - "@odata.type": "Microsoft.Dynamics.CRM.MemoAttributeMetadata", + "@odata.type": "Microsoft.Dynamics.CRM.ComplexMemoAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, @@ -1610,7 +1610,7 @@ def _attribute_payload( } if dtype_l in ("int", "integer"): return { - "@odata.type": "Microsoft.Dynamics.CRM.IntegerAttributeMetadata", + "@odata.type": "Microsoft.Dynamics.CRM.ComplexIntegerAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, @@ -1620,7 +1620,7 @@ def _attribute_payload( } if dtype_l in ("decimal", "money"): return { - "@odata.type": "Microsoft.Dynamics.CRM.DecimalAttributeMetadata", + "@odata.type": "Microsoft.Dynamics.CRM.ComplexDecimalAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, @@ -1640,7 +1640,7 @@ def _attribute_payload( } if dtype_l in ("datetime", "date"): return { - "@odata.type": "Microsoft.Dynamics.CRM.DateTimeAttributeMetadata", + "@odata.type": "Microsoft.Dynamics.CRM.ComplexDateTimeAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, @@ -1649,12 +1649,12 @@ def _attribute_payload( } if dtype_l in ("bool", "boolean"): return { - "@odata.type": "Microsoft.Dynamics.CRM.BooleanAttributeMetadata", + "@odata.type": "Microsoft.Dynamics.CRM.ComplexBooleanAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, "OptionSet": { - "@odata.type": "Microsoft.Dynamics.CRM.BooleanOptionSetMetadata", + "@odata.type": "Microsoft.Dynamics.CRM.ComplexBooleanOptionSetMetadata", "TrueOption": { "Value": 1, "Label": self._label("True"), @@ -1668,7 +1668,7 @@ def _attribute_payload( } if dtype_l == "file": return { - "@odata.type": "Microsoft.Dynamics.CRM.FileAttributeMetadata", + "@odata.type": "Microsoft.Dynamics.CRM.ComplexFileAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, From 7244b0628992d55394eb527b5e34c2fb60e5e51d Mon Sep 17 00:00:00 2001 From: vrathee-msft Date: Thu, 14 May 2026 18:22:08 -0700 Subject: [PATCH 2/7] fix unit test after moving to CreateEntitiesAPI --- src/PowerPlatform/Dataverse/data/_odata.py | 2 +- tests/unit/data/test_odata_internal.py | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/PowerPlatform/Dataverse/data/_odata.py b/src/PowerPlatform/Dataverse/data/_odata.py index f4e872f5..fabe08c6 100644 --- a/src/PowerPlatform/Dataverse/data/_odata.py +++ b/src/PowerPlatform/Dataverse/data/_odata.py @@ -1630,7 +1630,7 @@ def _attribute_payload( } if dtype_l in ("float", "double"): return { - "@odata.type": "Microsoft.Dynamics.CRM.DoubleAttributeMetadata", + "@odata.type": "Microsoft.Dynamics.CRM.ComplexDoubleAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, diff --git a/tests/unit/data/test_odata_internal.py b/tests/unit/data/test_odata_internal.py index f392ce20..42297f99 100644 --- a/tests/unit/data/test_odata_internal.py +++ b/tests/unit/data/test_odata_internal.py @@ -1516,7 +1516,7 @@ def setUp(self): def test_int_dtype(self): """'int' produces IntegerAttributeMetadata.""" result = self.od._attribute_payload("new_Count", "int") - self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.IntegerAttributeMetadata") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexIntegerAttributeMetadata") def test_integer_dtype_alias(self): """'integer' is an alias for 'int'.""" @@ -1526,7 +1526,7 @@ def test_integer_dtype_alias(self): def test_decimal_dtype(self): """'decimal' produces DecimalAttributeMetadata.""" result = self.od._attribute_payload("new_Price", "decimal") - self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.DecimalAttributeMetadata") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexDecimalAttributeMetadata") def test_money_dtype_alias(self): """'money' is an alias for 'decimal'.""" @@ -1536,7 +1536,7 @@ def test_money_dtype_alias(self): def test_float_dtype(self): """'float' produces DoubleAttributeMetadata.""" result = self.od._attribute_payload("new_Score", "float") - self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.DoubleAttributeMetadata") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexDoubleAttributeMetadata") def test_double_dtype_alias(self): """'double' is an alias for 'float'.""" @@ -1546,7 +1546,7 @@ def test_double_dtype_alias(self): def test_datetime_dtype(self): """'datetime' produces DateTimeAttributeMetadata.""" result = self.od._attribute_payload("new_CreatedDate", "datetime") - self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.DateTimeAttributeMetadata") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexDateTimeAttributeMetadata") def test_date_dtype_alias(self): """'date' is an alias for 'datetime'.""" @@ -1556,7 +1556,7 @@ def test_date_dtype_alias(self): def test_bool_dtype(self): """'bool' produces BooleanAttributeMetadata.""" result = self.od._attribute_payload("new_IsActive", "bool") - self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.BooleanAttributeMetadata") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexBooleanAttributeMetadata") def test_boolean_dtype_alias(self): """'boolean' is an alias for 'bool'.""" @@ -1566,7 +1566,7 @@ def test_boolean_dtype_alias(self): def test_file_dtype(self): """'file' produces FileAttributeMetadata.""" result = self.od._attribute_payload("new_Attachment", "file") - self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.FileAttributeMetadata") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexFileAttributeMetadata") def test_non_string_dtype_raises_value_error(self): """Non-string dtype raises ValueError.""" @@ -1576,7 +1576,7 @@ def test_non_string_dtype_raises_value_error(self): def test_memo_type(self): """'memo' produces MemoAttributeMetadata with MaxLength 4000.""" result = self.od._attribute_payload("new_Notes", "memo") - self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.MemoAttributeMetadata") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexMemoAttributeMetadata") self.assertEqual(result["SchemaName"], "new_Notes") self.assertEqual(result["MaxLength"], 4000) self.assertEqual(result["FormatName"], {"Value": "Text"}) @@ -1591,7 +1591,7 @@ def test_multiline_alias(self): def test_string_type_max_length(self): """'string' produces StringAttributeMetadata with MaxLength 200.""" result = self.od._attribute_payload("new_Title", "string") - self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.StringAttributeMetadata") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexStringAttributeMetadata") self.assertEqual(result["MaxLength"], 200) self.assertEqual(result["FormatName"], {"Value": "Text"}) @@ -1819,7 +1819,7 @@ def test_primary_column_schema_name_used_when_provided(self): self._setup_for_create() self.od._create_table("new_TestTable", {}, primary_column_schema_name="new_CustomName") post_json = self.od._request.call_args.kwargs["json"] - attrs = post_json["Attributes"] + attrs = post_json["Entities"][0]["Attributes"] primary_attr = next((a for a in attrs if a.get("IsPrimaryName")), None) self.assertIsNotNone(primary_attr) self.assertEqual(primary_attr["SchemaName"], "new_CustomName") @@ -1829,7 +1829,7 @@ def test_display_name_used_in_payload_when_provided(self): self._setup_for_create() self.od._create_table("new_TestTable", {}, display_name="My Test Table") post_json = self.od._request.call_args.kwargs["json"] - label_value = post_json["DisplayName"]["LocalizedLabels"][0]["Label"] + label_value = post_json["Entities"][0]["DisplayName"]["LocalizedLabels"][0]["Label"] self.assertEqual(label_value, "My Test Table") def test_display_name_defaults_to_schema_name(self): @@ -1837,7 +1837,7 @@ def test_display_name_defaults_to_schema_name(self): self._setup_for_create() self.od._create_table("new_TestTable", {}) post_json = self.od._request.call_args.kwargs["json"] - label_value = post_json["DisplayName"]["LocalizedLabels"][0]["Label"] + label_value = post_json["Entities"][0]["DisplayName"]["LocalizedLabels"][0]["Label"] self.assertEqual(label_value, "new_TestTable") def test_display_name_empty_string_raises(self): From 247463677417c77da5495b69331d2e1720e82085 Mon Sep 17 00:00:00 2001 From: vrathee-msft Date: Thu, 14 May 2026 18:28:16 -0700 Subject: [PATCH 3/7] fix formatting --- src/PowerPlatform/Dataverse/data/_odata.py | 28 ++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/PowerPlatform/Dataverse/data/_odata.py b/src/PowerPlatform/Dataverse/data/_odata.py index fabe08c6..f05171e9 100644 --- a/src/PowerPlatform/Dataverse/data/_odata.py +++ b/src/PowerPlatform/Dataverse/data/_odata.py @@ -1191,18 +1191,22 @@ def _create_entity( solution_unique_name: Optional[str] = None, ) -> Dict[str, Any]: url = f"{self.api}/CreateEntities" - payload = {"Entities" :[{ - "@odata.type": "Microsoft.Dynamics.CRM.ComplexEntityMetadata", - "SchemaName": table_schema_name, - "DisplayName": self._label(display_name), - "DisplayCollectionName": self._label(display_name + "s"), - "Description": self._label(f"Custom entity for {display_name}"), - "OwnershipType": "UserOwned", - "HasActivities": False, - "HasNotes": True, - "IsActivity": False, - "Attributes": attributes, - }]} + payload = { + "Entities": [ + { + "@odata.type": "Microsoft.Dynamics.CRM.ComplexEntityMetadata", + "SchemaName": table_schema_name, + "DisplayName": self._label(display_name), + "DisplayCollectionName": self._label(display_name + "s"), + "Description": self._label(f"Custom entity for {display_name}"), + "OwnershipType": "UserOwned", + "HasActivities": False, + "HasNotes": True, + "IsActivity": False, + "Attributes": attributes, + } + ] + } params = None if solution_unique_name: params = {"SolutionUniqueName": solution_unique_name} From dc555dafa1cd72b9d5a1bf63e51af99c566bcba8 Mon Sep 17 00:00:00 2001 From: vrathee-msft Date: Fri, 15 May 2026 13:37:02 -0700 Subject: [PATCH 4/7] add optional support for complex types for createentties in attribute metadata payload method --- src/PowerPlatform/Dataverse/data/_odata.py | 39 +++++++++++++++------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/PowerPlatform/Dataverse/data/_odata.py b/src/PowerPlatform/Dataverse/data/_odata.py index f05171e9..4287d3b5 100644 --- a/src/PowerPlatform/Dataverse/data/_odata.py +++ b/src/PowerPlatform/Dataverse/data/_odata.py @@ -1581,8 +1581,20 @@ def _convert_labels_to_ints(self, table_schema_name: str, record: Dict[str, Any] return resolved_record def _attribute_payload( - self, column_schema_name: str, dtype: Any, *, is_primary_name: bool = False + self, + column_schema_name: str, + dtype: Any, + *, + is_primary_name: bool = False, + complex: bool = False, ) -> Optional[Dict[str, Any]]: + """Build attribute metadata payload for a column. + + :param complex: When ``True``, emit ``Complex*AttributeMetadata`` types + required by the ``CreateEntities`` action. When ``False`` (default), + emit the standard ``*AttributeMetadata`` types used by the + ``EntityDefinitions/{id}/Attributes`` endpoint. + """ # Enum-based local option set support if isinstance(dtype, type) and issubclass(dtype, Enum): return self._enum_optionset_payload(column_schema_name, dtype, is_primary_name=is_primary_name) @@ -1592,9 +1604,10 @@ def _attribute_payload( ) dtype_l = dtype.lower().strip() label = column_schema_name.split("_")[-1] + prefix = "Microsoft.Dynamics.CRM.Complex" if complex else "Microsoft.Dynamics.CRM." if dtype_l in ("string", "text"): return { - "@odata.type": "Microsoft.Dynamics.CRM.ComplexStringAttributeMetadata", + "@odata.type": f"{prefix}StringAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, @@ -1604,7 +1617,7 @@ def _attribute_payload( } if dtype_l in ("memo", "multiline"): return { - "@odata.type": "Microsoft.Dynamics.CRM.ComplexMemoAttributeMetadata", + "@odata.type": f"{prefix}MemoAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, @@ -1614,7 +1627,7 @@ def _attribute_payload( } if dtype_l in ("int", "integer"): return { - "@odata.type": "Microsoft.Dynamics.CRM.ComplexIntegerAttributeMetadata", + "@odata.type": f"{prefix}IntegerAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, @@ -1624,7 +1637,7 @@ def _attribute_payload( } if dtype_l in ("decimal", "money"): return { - "@odata.type": "Microsoft.Dynamics.CRM.ComplexDecimalAttributeMetadata", + "@odata.type": f"{prefix}DecimalAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, @@ -1634,7 +1647,7 @@ def _attribute_payload( } if dtype_l in ("float", "double"): return { - "@odata.type": "Microsoft.Dynamics.CRM.ComplexDoubleAttributeMetadata", + "@odata.type": f"{prefix}DoubleAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, @@ -1644,7 +1657,7 @@ def _attribute_payload( } if dtype_l in ("datetime", "date"): return { - "@odata.type": "Microsoft.Dynamics.CRM.ComplexDateTimeAttributeMetadata", + "@odata.type": f"{prefix}DateTimeAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, @@ -1653,12 +1666,12 @@ def _attribute_payload( } if dtype_l in ("bool", "boolean"): return { - "@odata.type": "Microsoft.Dynamics.CRM.ComplexBooleanAttributeMetadata", + "@odata.type": f"{prefix}BooleanAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, "OptionSet": { - "@odata.type": "Microsoft.Dynamics.CRM.ComplexBooleanOptionSetMetadata", + "@odata.type": f"{prefix}BooleanOptionSetMetadata", "TrueOption": { "Value": 1, "Label": self._label("True"), @@ -1672,7 +1685,7 @@ def _attribute_payload( } if dtype_l == "file": return { - "@odata.type": "Microsoft.Dynamics.CRM.ComplexFileAttributeMetadata", + "@odata.type": f"{prefix}FileAttributeMetadata", "SchemaName": column_schema_name, "DisplayName": self._label(label), "RequiredLevel": {"Value": "None"}, @@ -1905,9 +1918,11 @@ def _create_table( ) attributes: List[Dict[str, Any]] = [] - attributes.append(self._attribute_payload(primary_attr_schema, "string", is_primary_name=True)) + attributes.append( + self._attribute_payload(primary_attr_schema, "string", is_primary_name=True, complex=True) + ) for col_name, dtype in schema.items(): - payload = self._attribute_payload(col_name, dtype) + payload = self._attribute_payload(col_name, dtype, complex=True) if not payload: raise ValueError(f"Unsupported column type '{dtype}' for '{col_name}'.") attributes.append(payload) From 7b92195ccad5631034e88e5f58a84fca947115c3 Mon Sep 17 00:00:00 2001 From: vrathee-msft Date: Fri, 15 May 2026 13:52:54 -0700 Subject: [PATCH 5/7] fix for unit test --- tests/unit/data/test_odata_internal.py | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/unit/data/test_odata_internal.py b/tests/unit/data/test_odata_internal.py index 42297f99..1d7ba84b 100644 --- a/tests/unit/data/test_odata_internal.py +++ b/tests/unit/data/test_odata_internal.py @@ -1516,6 +1516,11 @@ def setUp(self): def test_int_dtype(self): """'int' produces IntegerAttributeMetadata.""" result = self.od._attribute_payload("new_Count", "int") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.IntegerAttributeMetadata") + + def test_complex_int_dtype(self): + """'int' produces IntegerAttributeMetadata.""" + result = self.od._attribute_payload("new_Count", "int", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexIntegerAttributeMetadata") def test_integer_dtype_alias(self): @@ -1526,6 +1531,11 @@ def test_integer_dtype_alias(self): def test_decimal_dtype(self): """'decimal' produces DecimalAttributeMetadata.""" result = self.od._attribute_payload("new_Price", "decimal") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.DecimalAttributeMetadata") + + def test_complex_decimal_dtype(self): + """'decimal' produces DecimalAttributeMetadata.""" + result = self.od._attribute_payload("new_Price", "decimal", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexDecimalAttributeMetadata") def test_money_dtype_alias(self): @@ -1536,6 +1546,11 @@ def test_money_dtype_alias(self): def test_float_dtype(self): """'float' produces DoubleAttributeMetadata.""" result = self.od._attribute_payload("new_Score", "float") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.DoubleAttributeMetadata") + + def test_complex_float_dtype(self): + """'float' produces DoubleAttributeMetadata.""" + result = self.od._attribute_payload("new_Score", "float", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexDoubleAttributeMetadata") def test_double_dtype_alias(self): @@ -1546,8 +1561,14 @@ def test_double_dtype_alias(self): def test_datetime_dtype(self): """'datetime' produces DateTimeAttributeMetadata.""" result = self.od._attribute_payload("new_CreatedDate", "datetime") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.DateTimeAttributeMetadata") + + def test_complex_datetime_dtype(self): + """'datetime' produces DateTimeAttributeMetadata.""" + result = self.od._attribute_payload("new_CreatedDate", "datetime", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexDateTimeAttributeMetadata") + def test_date_dtype_alias(self): """'date' is an alias for 'datetime'.""" result = self.od._attribute_payload("new_BirthDate", "date") @@ -1556,6 +1577,11 @@ def test_date_dtype_alias(self): def test_bool_dtype(self): """'bool' produces BooleanAttributeMetadata.""" result = self.od._attribute_payload("new_IsActive", "bool") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.BooleanAttributeMetadata") + + def test_complex_bool_dtype(self): + """'bool' produces BooleanAttributeMetadata.""" + result = self.od._attribute_payload("new_IsActive", "bool", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexBooleanAttributeMetadata") def test_boolean_dtype_alias(self): @@ -1566,6 +1592,11 @@ def test_boolean_dtype_alias(self): def test_file_dtype(self): """'file' produces FileAttributeMetadata.""" result = self.od._attribute_payload("new_Attachment", "file") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.FileAttributeMetadata") + + def test_complex_file_dtype(self): + """'file' produces FileAttributeMetadata.""" + result = self.od._attribute_payload("new_Attachment", "file", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexFileAttributeMetadata") def test_non_string_dtype_raises_value_error(self): @@ -1576,6 +1607,15 @@ def test_non_string_dtype_raises_value_error(self): def test_memo_type(self): """'memo' produces MemoAttributeMetadata with MaxLength 4000.""" result = self.od._attribute_payload("new_Notes", "memo") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.MemoAttributeMetadata") + self.assertEqual(result["SchemaName"], "new_Notes") + self.assertEqual(result["MaxLength"], 4000) + self.assertEqual(result["FormatName"], {"Value": "Text"}) + self.assertNotIn("IsPrimaryName", result) + + def test_complex_memo_type(self): + """'memo' produces MemoAttributeMetadata with MaxLength 4000.""" + result = self.od._attribute_payload("new_Notes", "memo", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexMemoAttributeMetadata") self.assertEqual(result["SchemaName"], "new_Notes") self.assertEqual(result["MaxLength"], 4000) @@ -1591,6 +1631,13 @@ def test_multiline_alias(self): def test_string_type_max_length(self): """'string' produces StringAttributeMetadata with MaxLength 200.""" result = self.od._attribute_payload("new_Title", "string") + self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.StringAttributeMetadata") + self.assertEqual(result["MaxLength"], 200) + self.assertEqual(result["FormatName"], {"Value": "Text"}) + + def test_complex_string_type_max_length(self): + """'string' produces StringAttributeMetadata with MaxLength 200.""" + result = self.od._attribute_payload("new_Title", "string", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexStringAttributeMetadata") self.assertEqual(result["MaxLength"], 200) self.assertEqual(result["FormatName"], {"Value": "Text"}) From b391f0a5ceb4d5298d521273e0d9468fb8c473bb Mon Sep 17 00:00:00 2001 From: vrathee-msft Date: Fri, 15 May 2026 13:56:45 -0700 Subject: [PATCH 6/7] code comments fix --- tests/unit/data/test_odata_internal.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/unit/data/test_odata_internal.py b/tests/unit/data/test_odata_internal.py index 1d7ba84b..63e22416 100644 --- a/tests/unit/data/test_odata_internal.py +++ b/tests/unit/data/test_odata_internal.py @@ -1519,7 +1519,7 @@ def test_int_dtype(self): self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.IntegerAttributeMetadata") def test_complex_int_dtype(self): - """'int' produces IntegerAttributeMetadata.""" + """'int' produces ComplexIntegerAttributeMetadata.""" result = self.od._attribute_payload("new_Count", "int", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexIntegerAttributeMetadata") @@ -1534,7 +1534,7 @@ def test_decimal_dtype(self): self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.DecimalAttributeMetadata") def test_complex_decimal_dtype(self): - """'decimal' produces DecimalAttributeMetadata.""" + """'decimal' produces ComplexDecimalAttributeMetadata.""" result = self.od._attribute_payload("new_Price", "decimal", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexDecimalAttributeMetadata") @@ -1549,7 +1549,7 @@ def test_float_dtype(self): self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.DoubleAttributeMetadata") def test_complex_float_dtype(self): - """'float' produces DoubleAttributeMetadata.""" + """'float' produces ComplexDoubleAttributeMetadata.""" result = self.od._attribute_payload("new_Score", "float", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexDoubleAttributeMetadata") @@ -1564,7 +1564,7 @@ def test_datetime_dtype(self): self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.DateTimeAttributeMetadata") def test_complex_datetime_dtype(self): - """'datetime' produces DateTimeAttributeMetadata.""" + """'datetime' produces ComplexDateTimeAttributeMetadata.""" result = self.od._attribute_payload("new_CreatedDate", "datetime", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexDateTimeAttributeMetadata") @@ -1580,7 +1580,7 @@ def test_bool_dtype(self): self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.BooleanAttributeMetadata") def test_complex_bool_dtype(self): - """'bool' produces BooleanAttributeMetadata.""" + """'bool' produces ComplexBooleanAttributeMetadata.""" result = self.od._attribute_payload("new_IsActive", "bool", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexBooleanAttributeMetadata") @@ -1595,7 +1595,7 @@ def test_file_dtype(self): self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.FileAttributeMetadata") def test_complex_file_dtype(self): - """'file' produces FileAttributeMetadata.""" + """'file' produces ComplexFileAttributeMetadata.""" result = self.od._attribute_payload("new_Attachment", "file", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexFileAttributeMetadata") @@ -1614,7 +1614,7 @@ def test_memo_type(self): self.assertNotIn("IsPrimaryName", result) def test_complex_memo_type(self): - """'memo' produces MemoAttributeMetadata with MaxLength 4000.""" + """'memo' produces ComplexMemoAttributeMetadata with MaxLength 4000.""" result = self.od._attribute_payload("new_Notes", "memo", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexMemoAttributeMetadata") self.assertEqual(result["SchemaName"], "new_Notes") @@ -1636,7 +1636,7 @@ def test_string_type_max_length(self): self.assertEqual(result["FormatName"], {"Value": "Text"}) def test_complex_string_type_max_length(self): - """'string' produces StringAttributeMetadata with MaxLength 200.""" + """'string' produces ComplexStringAttributeMetadata with MaxLength 200.""" result = self.od._attribute_payload("new_Title", "string", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexStringAttributeMetadata") self.assertEqual(result["MaxLength"], 200) From ce13ba20a43a05a38aaa90802c349039c822b5d1 Mon Sep 17 00:00:00 2001 From: vrathee-msft Date: Fri, 15 May 2026 14:26:54 -0700 Subject: [PATCH 7/7] black formatter error --- src/PowerPlatform/Dataverse/data/_odata.py | 4 +--- tests/unit/data/test_odata_internal.py | 7 +++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/PowerPlatform/Dataverse/data/_odata.py b/src/PowerPlatform/Dataverse/data/_odata.py index 4287d3b5..7da973e0 100644 --- a/src/PowerPlatform/Dataverse/data/_odata.py +++ b/src/PowerPlatform/Dataverse/data/_odata.py @@ -1918,9 +1918,7 @@ def _create_table( ) attributes: List[Dict[str, Any]] = [] - attributes.append( - self._attribute_payload(primary_attr_schema, "string", is_primary_name=True, complex=True) - ) + attributes.append(self._attribute_payload(primary_attr_schema, "string", is_primary_name=True, complex=True)) for col_name, dtype in schema.items(): payload = self._attribute_payload(col_name, dtype, complex=True) if not payload: diff --git a/tests/unit/data/test_odata_internal.py b/tests/unit/data/test_odata_internal.py index 63e22416..5c9b95f6 100644 --- a/tests/unit/data/test_odata_internal.py +++ b/tests/unit/data/test_odata_internal.py @@ -1517,7 +1517,7 @@ def test_int_dtype(self): """'int' produces IntegerAttributeMetadata.""" result = self.od._attribute_payload("new_Count", "int") self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.IntegerAttributeMetadata") - + def test_complex_int_dtype(self): """'int' produces ComplexIntegerAttributeMetadata.""" result = self.od._attribute_payload("new_Count", "int", complex=True) @@ -1568,7 +1568,6 @@ def test_complex_datetime_dtype(self): result = self.od._attribute_payload("new_CreatedDate", "datetime", complex=True) self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.ComplexDateTimeAttributeMetadata") - def test_date_dtype_alias(self): """'date' is an alias for 'datetime'.""" result = self.od._attribute_payload("new_BirthDate", "date") @@ -1578,7 +1577,7 @@ def test_bool_dtype(self): """'bool' produces BooleanAttributeMetadata.""" result = self.od._attribute_payload("new_IsActive", "bool") self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.BooleanAttributeMetadata") - + def test_complex_bool_dtype(self): """'bool' produces ComplexBooleanAttributeMetadata.""" result = self.od._attribute_payload("new_IsActive", "bool", complex=True) @@ -1634,7 +1633,7 @@ def test_string_type_max_length(self): self.assertEqual(result["@odata.type"], "Microsoft.Dynamics.CRM.StringAttributeMetadata") self.assertEqual(result["MaxLength"], 200) self.assertEqual(result["FormatName"], {"Value": "Text"}) - + def test_complex_string_type_max_length(self): """'string' produces ComplexStringAttributeMetadata with MaxLength 200.""" result = self.od._attribute_payload("new_Title", "string", complex=True)