diff --git a/pyatlan/model/typedef.py b/pyatlan/model/typedef.py index babee529f..dd19c92bb 100644 --- a/pyatlan/model/typedef.py +++ b/pyatlan/model/typedef.py @@ -352,6 +352,7 @@ def get_valid_values(self) -> List[str]: class AttributeDef(AtlanObject): class Options(AtlanObject): + _attr_def: AttributeDef = PrivateAttr(default=None) custom_metadata_version: str = Field( description="Indicates the version of the custom metadata structure. This determines which other options " "are available and used.", @@ -482,6 +483,15 @@ class Options(AtlanObject): "Only assets of one of these types will have this attribute available.", ) + def __setattr__(self, name, value): + super().__setattr__(name, value) + if self._attr_def and name == "multi_value_select": + self._attr_def.cardinality = Cardinality.SET + if self._attr_def.type_name and "array<" not in str( + self._attr_def.type_name + ): + self._attr_def.type_name = f"array<{self._attr_def.type_name}>" + @staticmethod def create( attribute_type: AtlanCustomAttributePrimitiveType, @@ -570,7 +580,7 @@ def create( description="When true, this attribute must be unique across all assets.", ) options: Optional[AttributeDef.Options] = Field( - default=None, description="Extensible options for the attribute." + default_factory=Options, description="Extensible options for the attribute." ) search_weight: Optional[float] = Field(default=None, description="TBC") skip_scrubbing: Optional[bool] = Field( @@ -799,6 +809,10 @@ def applicable_domains(self, domains: Set[str]): ) self.options.applicable_domains = json.dumps(list(domains)) + def __init__(self, **data): + super().__init__(**data) + self.options._attr_def = self + @staticmethod def create( display_name: str, diff --git a/tests/integration/custom_metadata_test.py b/tests/integration/custom_metadata_test.py index cfe91d255..750655b6c 100644 --- a/tests/integration/custom_metadata_test.py +++ b/tests/integration/custom_metadata_test.py @@ -23,6 +23,7 @@ AtlanTypeCategory, BadgeComparisonOperator, BadgeConditionColor, + Cardinality, EntityStatus, ) from pyatlan.model.fields.atlan_fields import CustomMetadataField @@ -224,6 +225,12 @@ def test_cm_ipr(cm_ipr: CustomMetadataDef, limit_attribute_applicability_kwargs) def cm_raci( client: AtlanClient, ) -> Generator[CustomMetadataDef, None, None]: + TEST_MULTI_VALUE_USING_SETTER = AttributeDef.create( + display_name=CM_ATTR_RACI_INFORMED, + attribute_type=AtlanCustomAttributePrimitiveType.GROUPS, + ) + assert TEST_MULTI_VALUE_USING_SETTER and TEST_MULTI_VALUE_USING_SETTER.options + TEST_MULTI_VALUE_USING_SETTER.options.multi_value_select = True attribute_defs = [ AttributeDef.create( display_name=CM_ATTR_RACI_RESPONSIBLE, @@ -239,11 +246,7 @@ def cm_raci( attribute_type=AtlanCustomAttributePrimitiveType.GROUPS, multi_valued=True, ), - AttributeDef.create( - display_name=CM_ATTR_RACI_INFORMED, - attribute_type=AtlanCustomAttributePrimitiveType.GROUPS, - multi_valued=True, - ), + TEST_MULTI_VALUE_USING_SETTER, AttributeDef.create( display_name=CM_ATTR_RACI_EXTRA, attribute_type=AtlanCustomAttributePrimitiveType.STRING, @@ -280,6 +283,7 @@ def test_cm_raci( assert one.name != CM_ATTR_RACI_RESPONSIBLE assert one.type_name == f"array<{AtlanCustomAttributePrimitiveType.STRING.value}>" assert one.options + assert one.cardinality == Cardinality.SET assert one.options.multi_value_select one = attributes[1] assert one.display_name == CM_ATTR_RACI_ACCOUNTABLE @@ -292,12 +296,14 @@ def test_cm_raci( assert one.name != CM_ATTR_RACI_CONSULTED assert one.type_name == f"array<{AtlanCustomAttributePrimitiveType.STRING.value}>" assert one.options + assert one.cardinality == Cardinality.SET assert one.options.multi_value_select one = attributes[3] assert one.display_name == CM_ATTR_RACI_INFORMED assert one.name != CM_ATTR_RACI_INFORMED assert one.type_name == f"array<{AtlanCustomAttributePrimitiveType.STRING.value}>" assert one.options + assert one.cardinality == Cardinality.SET assert one.options.multi_value_select one = attributes[4] assert one.display_name == CM_ATTR_RACI_EXTRA @@ -1015,6 +1021,7 @@ def _validate_raci_structure( assert one.options assert "Database" in one.applicable_asset_types assert not one.is_archived() + assert one.cardinality == Cardinality.SET assert one.options.multi_value_select assert one.options.custom_type == AtlanCustomAttributePrimitiveType.USERS.value one = attributes[1] @@ -1033,6 +1040,7 @@ def _validate_raci_structure( assert one.options assert "Column" in one.applicable_asset_types assert not one.is_archived() + assert one.cardinality == Cardinality.SET assert one.options.multi_value_select assert one.options.custom_type == AtlanCustomAttributePrimitiveType.GROUPS.value one = attributes[3] @@ -1042,6 +1050,7 @@ def _validate_raci_structure( assert one.options assert "MaterialisedView" in one.applicable_asset_types assert not one.is_archived() + assert one.cardinality == Cardinality.SET assert one.options.multi_value_select assert one.options.custom_type == AtlanCustomAttributePrimitiveType.GROUPS.value if total_expected > 5: diff --git a/tests/unit/test_typedef_model.py b/tests/unit/test_typedef_model.py index c08ad53f5..f29543249 100644 --- a/tests/unit/test_typedef_model.py +++ b/tests/unit/test_typedef_model.py @@ -97,7 +97,9 @@ def check_attribute(model: object, attribute_name: str, source: dict): value = type(attribute)(value) assert attribute == value else: - assert getattr(model, attribute_name) is None + # Since "options" are now initialized with a default factory + if attribute_name != "options": + assert getattr(model, attribute_name) is None def check_has_attributes(type_def: TypeDef, type_def_json: dict): @@ -358,6 +360,9 @@ def test_applicable_types_with_no_options_raises_invalid_request_error( self, attribute, value, sut: AttributeDef ): sut = AttributeDef() + # Explicitly setting "None" since options + # are now initialized with a default factory + sut.options = None with pytest.raises( InvalidRequestError,