diff --git a/docs/asset/customentity.rst b/docs/asset/customentity.rst new file mode 100644 index 000000000..0da34b148 --- /dev/null +++ b/docs/asset/customentity.rst @@ -0,0 +1,10 @@ +.. _customentity: + +CustomEntity +============ + +.. module:: pyatlan.model.assets + :no-index: + +.. autoclass:: CustomEntity + :members: diff --git a/docs/asset/powerbidataflowentitycolumn.rst b/docs/asset/powerbidataflowentitycolumn.rst new file mode 100644 index 000000000..006c3380c --- /dev/null +++ b/docs/asset/powerbidataflowentitycolumn.rst @@ -0,0 +1,10 @@ +.. _powerbidataflowentitycolumn: + +PowerBIDataflowEntityColumn +=========================== + +.. module:: pyatlan.model.assets + :no-index: + +.. autoclass:: PowerBIDataflowEntityColumn + :members: diff --git a/docs/assets.rst b/docs/assets.rst index 1e164b773..42014b6d2 100644 --- a/docs/assets.rst +++ b/docs/assets.rst @@ -95,6 +95,8 @@ You can interact with all of the following different kinds of assets: asset/cubedimension asset/cubefield asset/cubehierarchy + asset/custom + asset/customentity asset/datacontract asset/datadomain asset/datamesh @@ -204,6 +206,7 @@ You can interact with all of the following different kinds of assets: asset/powerbicolumn asset/powerbidashboard asset/powerbidataflow + asset/powerbidataflowentitycolumn asset/powerbidataset asset/powerbidatasource asset/powerbimeasure diff --git a/pyatlan/generator/templates/methods/asset/custom_entity.jinja2 b/pyatlan/generator/templates/methods/asset/custom_entity.jinja2 new file mode 100644 index 000000000..fb8f3e972 --- /dev/null +++ b/pyatlan/generator/templates/methods/asset/custom_entity.jinja2 @@ -0,0 +1,11 @@ + + @classmethod + @init_guid + def creator(cls, *, name: str, connection_qualified_name: str) -> CustomEntity: + validate_required_fields( + ["name", "connection_qualified_name"], [name, connection_qualified_name] + ) + attributes = CustomEntity.Attributes.creator( + name=name, connection_qualified_name=connection_qualified_name + ) + return cls(attributes=attributes) diff --git a/pyatlan/generator/templates/methods/attribute/custom_entity.jinja2 b/pyatlan/generator/templates/methods/attribute/custom_entity.jinja2 new file mode 100644 index 000000000..9addd7dbe --- /dev/null +++ b/pyatlan/generator/templates/methods/attribute/custom_entity.jinja2 @@ -0,0 +1,17 @@ + + @classmethod + @init_guid + def creator( + cls, *, name: str, connection_qualified_name: str + ) -> CustomEntity.Attributes: + validate_required_fields( + ["name", "connection_qualified_name"], [name, connection_qualified_name] + ) + return CustomEntity.Attributes( + name=name, + qualified_name=f"{connection_qualified_name}/{name}", + connection_qualified_name=connection_qualified_name, + connector_name=AtlanConnectorType.get_connector_name( + connection_qualified_name + ), + ) diff --git a/pyatlan/model/assets/__init__.py b/pyatlan/model/assets/__init__.py index 54d13cfe2..2828f838c 100644 --- a/pyatlan/model/assets/__init__.py +++ b/pyatlan/model/assets/__init__.py @@ -104,6 +104,7 @@ "PowerBIDashboard", "PowerBIDataflow", "PowerBIPage", + "PowerBIDataflowEntityColumn", "SnowflakeDynamicTable", "MongoDBCollection", "DynamoDBSecondaryIndex", @@ -133,6 +134,7 @@ "object_store": ["ObjectStore"], "saa_s": ["SaaS"], "multi_dimensional_dataset": ["MultiDimensionalDataset"], + "custom": ["Custom"], "event_store": ["EventStore"], "insight": ["Insight"], "a_p_i": ["API"], @@ -169,6 +171,7 @@ "cube_hierarchy": ["CubeHierarchy"], "cube_field": ["CubeField"], "cube_dimension": ["CubeDimension"], + "custom_entity": ["CustomEntity"], "bigquery_tag": ["BigqueryTag"], "kafka": ["Kafka"], "azure_service_bus": ["AzureServiceBus"], diff --git a/pyatlan/model/assets/__init__.pyi b/pyatlan/model/assets/__init__.pyi index 686622375..8cd5bb825 100644 --- a/pyatlan/model/assets/__init__.pyi +++ b/pyatlan/model/assets/__init__.pyi @@ -101,6 +101,7 @@ __all__ = [ "PowerBIDashboard", "PowerBIDataflow", "PowerBIPage", + "PowerBIDataflowEntityColumn", "SnowflakeDynamicTable", "MongoDBCollection", "DynamoDBSecondaryIndex", @@ -129,6 +130,7 @@ __all__ = [ "ObjectStore", "SaaS", "MultiDimensionalDataset", + "Custom", "EventStore", "NoSQL", "Insight", @@ -166,6 +168,7 @@ __all__ = [ "CubeHierarchy", "CubeField", "CubeDimension", + "CustomEntity", "BigqueryTag", "Kafka", "AzureServiceBus", @@ -438,6 +441,7 @@ from .core.power_b_i import PowerBI from .core.power_b_i_column import PowerBIColumn from .core.power_b_i_dashboard import PowerBIDashboard from .core.power_b_i_dataflow import PowerBIDataflow +from .core.power_b_i_dataflow_entity_column import PowerBIDataflowEntityColumn from .core.power_b_i_dataset import PowerBIDataset from .core.power_b_i_datasource import PowerBIDatasource from .core.power_b_i_measure import PowerBIMeasure @@ -474,6 +478,8 @@ from .cube import Cube from .cube_dimension import CubeDimension from .cube_field import CubeField from .cube_hierarchy import CubeHierarchy +from .custom import Custom +from .custom_entity import CustomEntity from .data_set import DataSet from .data_studio import DataStudio from .data_studio_asset import DataStudioAsset diff --git a/pyatlan/model/assets/core/__init__.py b/pyatlan/model/assets/core/__init__.py index 933105b6d..6eae08e17 100644 --- a/pyatlan/model/assets/core/__init__.py +++ b/pyatlan/model/assets/core/__init__.py @@ -77,6 +77,7 @@ from .power_b_i_column import PowerBIColumn from .power_b_i_dashboard import PowerBIDashboard from .power_b_i_dataflow import PowerBIDataflow +from .power_b_i_dataflow_entity_column import PowerBIDataflowEntityColumn from .power_b_i_dataset import PowerBIDataset from .power_b_i_datasource import PowerBIDatasource from .power_b_i_measure import PowerBIMeasure @@ -211,6 +212,7 @@ PowerBIDashboard.Attributes.update_forward_refs(**localns) PowerBIDataflow.Attributes.update_forward_refs(**localns) PowerBIPage.Attributes.update_forward_refs(**localns) +PowerBIDataflowEntityColumn.Attributes.update_forward_refs(**localns) SnowflakeDynamicTable.Attributes.update_forward_refs(**localns) MongoDBCollection.Attributes.update_forward_refs(**localns) DynamoDBSecondaryIndex.Attributes.update_forward_refs(**localns) diff --git a/pyatlan/model/assets/core/power_b_i_dataflow.py b/pyatlan/model/assets/core/power_b_i_dataflow.py index 8fce0a0f1..cfd91d473 100644 --- a/pyatlan/model/assets/core/power_b_i_dataflow.py +++ b/pyatlan/model/assets/core/power_b_i_dataflow.py @@ -72,10 +72,20 @@ def __setattr__(self, name, value): """ TBC """ + POWER_BI_DATAFLOW_ENTITY_COLUMNS: ClassVar[RelationField] = RelationField( + "powerBIDataflowEntityColumns" + ) + """ + TBC + """ TABLES: ClassVar[RelationField] = RelationField("tables") """ TBC """ + POWER_BI_DATASOURCES: ClassVar[RelationField] = RelationField("powerBIDatasources") + """ + TBC + """ POWER_BI_DATAFLOW_CHILDREN: ClassVar[RelationField] = RelationField( "powerBIDataflowChildren" ) @@ -98,7 +108,9 @@ def __setattr__(self, name, value): "workspace", "power_b_i_processes", "datasets", + "power_b_i_dataflow_entity_columns", "tables", + "power_b_i_datasources", "power_b_i_dataflow_children", "power_b_i_dataflow_parents", ] @@ -211,6 +223,27 @@ def datasets(self, datasets: Optional[List[PowerBIDataset]]): self.attributes = self.Attributes() self.attributes.datasets = datasets + @property + def power_b_i_dataflow_entity_columns( + self, + ) -> Optional[List[PowerBIDataflowEntityColumn]]: + return ( + None + if self.attributes is None + else self.attributes.power_b_i_dataflow_entity_columns + ) + + @power_b_i_dataflow_entity_columns.setter + def power_b_i_dataflow_entity_columns( + self, + power_b_i_dataflow_entity_columns: Optional[List[PowerBIDataflowEntityColumn]], + ): + if self.attributes is None: + self.attributes = self.Attributes() + self.attributes.power_b_i_dataflow_entity_columns = ( + power_b_i_dataflow_entity_columns + ) + @property def tables(self) -> Optional[List[PowerBITable]]: return None if self.attributes is None else self.attributes.tables @@ -221,6 +254,20 @@ def tables(self, tables: Optional[List[PowerBITable]]): self.attributes = self.Attributes() self.attributes.tables = tables + @property + def power_b_i_datasources(self) -> Optional[List[PowerBIDatasource]]: + return ( + None if self.attributes is None else self.attributes.power_b_i_datasources + ) + + @power_b_i_datasources.setter + def power_b_i_datasources( + self, power_b_i_datasources: Optional[List[PowerBIDatasource]] + ): + if self.attributes is None: + self.attributes = self.Attributes() + self.attributes.power_b_i_datasources = power_b_i_datasources + @property def power_b_i_dataflow_children(self) -> Optional[List[PowerBIDataflow]]: return ( @@ -274,9 +321,17 @@ class Attributes(PowerBI.Attributes): datasets: Optional[List[PowerBIDataset]] = Field( default=None, description="" ) # relationship + power_b_i_dataflow_entity_columns: Optional[ + List[PowerBIDataflowEntityColumn] + ] = Field( + default=None, description="" + ) # relationship tables: Optional[List[PowerBITable]] = Field( default=None, description="" ) # relationship + power_b_i_datasources: Optional[List[PowerBIDatasource]] = Field( + default=None, description="" + ) # relationship power_b_i_dataflow_children: Optional[List[PowerBIDataflow]] = Field( default=None, description="" ) # relationship @@ -294,7 +349,9 @@ class Attributes(PowerBI.Attributes): ) +from .power_b_i_dataflow_entity_column import PowerBIDataflowEntityColumn # noqa from .power_b_i_dataset import PowerBIDataset # noqa +from .power_b_i_datasource import PowerBIDatasource # noqa from .power_b_i_table import PowerBITable # noqa from .power_b_i_workspace import PowerBIWorkspace # noqa from .process import Process # noqa diff --git a/pyatlan/model/assets/core/power_b_i_dataflow_entity_column.py b/pyatlan/model/assets/core/power_b_i_dataflow_entity_column.py new file mode 100644 index 000000000..d734366c9 --- /dev/null +++ b/pyatlan/model/assets/core/power_b_i_dataflow_entity_column.py @@ -0,0 +1,177 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2022 Atlan Pte. Ltd. + + +from __future__ import annotations + +from typing import ClassVar, List, Optional + +from pydantic.v1 import Field, validator + +from pyatlan.model.fields.atlan_fields import KeywordField, RelationField, TextField + +from .power_b_i import PowerBI + + +class PowerBIDataflowEntityColumn(PowerBI): + """Description""" + + type_name: str = Field(default="PowerBIDataflowEntityColumn", allow_mutation=False) + + @validator("type_name") + def validate_type_name(cls, v): + if v != "PowerBIDataflowEntityColumn": + raise ValueError("must be PowerBIDataflowEntityColumn") + return v + + def __setattr__(self, name, value): + if name in PowerBIDataflowEntityColumn._convenience_properties: + return object.__setattr__(self, name, value) + super().__setattr__(name, value) + + POWER_BI_DATAFLOW_ENTITY_NAME: ClassVar[TextField] = TextField( + "powerBIDataflowEntityName", "powerBIDataflowEntityName" + ) + """ + Unique name of the dataflow entity in which this dataflow entity column exists. + """ + POWER_BI_WORKSPACE_QUALIFIED_NAME: ClassVar[KeywordField] = KeywordField( + "powerBIWorkspaceQualifiedName", "powerBIWorkspaceQualifiedName" + ) + """ + Unique name of the workspace in which this dataflow entity column exists. + """ + POWER_BI_DATAFLOW_QUALIFIED_NAME: ClassVar[KeywordField] = KeywordField( + "powerBIDataflowQualifiedName", "powerBIDataflowQualifiedName" + ) + """ + Unique name of the dataflow in which this dataflow entity column exists. + """ + POWER_BI_DATAFLOW_ENTITY_COLUMN_DATA_TYPE: ClassVar[KeywordField] = KeywordField( + "powerBIDataflowEntityColumnDataType", "powerBIDataflowEntityColumnDataType" + ) + """ + Data type of this dataflow entity column. + """ + + POWER_BI_DATAFLOW: ClassVar[RelationField] = RelationField("powerBIDataflow") + """ + TBC + """ + + _convenience_properties: ClassVar[List[str]] = [ + "power_b_i_dataflow_entity_name", + "power_b_i_workspace_qualified_name", + "power_b_i_dataflow_qualified_name", + "power_b_i_dataflow_entity_column_data_type", + "power_b_i_dataflow", + ] + + @property + def power_b_i_dataflow_entity_name(self) -> Optional[str]: + return ( + None + if self.attributes is None + else self.attributes.power_b_i_dataflow_entity_name + ) + + @power_b_i_dataflow_entity_name.setter + def power_b_i_dataflow_entity_name( + self, power_b_i_dataflow_entity_name: Optional[str] + ): + if self.attributes is None: + self.attributes = self.Attributes() + self.attributes.power_b_i_dataflow_entity_name = power_b_i_dataflow_entity_name + + @property + def power_b_i_workspace_qualified_name(self) -> Optional[str]: + return ( + None + if self.attributes is None + else self.attributes.power_b_i_workspace_qualified_name + ) + + @power_b_i_workspace_qualified_name.setter + def power_b_i_workspace_qualified_name( + self, power_b_i_workspace_qualified_name: Optional[str] + ): + if self.attributes is None: + self.attributes = self.Attributes() + self.attributes.power_b_i_workspace_qualified_name = ( + power_b_i_workspace_qualified_name + ) + + @property + def power_b_i_dataflow_qualified_name(self) -> Optional[str]: + return ( + None + if self.attributes is None + else self.attributes.power_b_i_dataflow_qualified_name + ) + + @power_b_i_dataflow_qualified_name.setter + def power_b_i_dataflow_qualified_name( + self, power_b_i_dataflow_qualified_name: Optional[str] + ): + if self.attributes is None: + self.attributes = self.Attributes() + self.attributes.power_b_i_dataflow_qualified_name = ( + power_b_i_dataflow_qualified_name + ) + + @property + def power_b_i_dataflow_entity_column_data_type(self) -> Optional[str]: + return ( + None + if self.attributes is None + else self.attributes.power_b_i_dataflow_entity_column_data_type + ) + + @power_b_i_dataflow_entity_column_data_type.setter + def power_b_i_dataflow_entity_column_data_type( + self, power_b_i_dataflow_entity_column_data_type: Optional[str] + ): + if self.attributes is None: + self.attributes = self.Attributes() + self.attributes.power_b_i_dataflow_entity_column_data_type = ( + power_b_i_dataflow_entity_column_data_type + ) + + @property + def power_b_i_dataflow(self) -> Optional[PowerBIDataflow]: + return None if self.attributes is None else self.attributes.power_b_i_dataflow + + @power_b_i_dataflow.setter + def power_b_i_dataflow(self, power_b_i_dataflow: Optional[PowerBIDataflow]): + if self.attributes is None: + self.attributes = self.Attributes() + self.attributes.power_b_i_dataflow = power_b_i_dataflow + + class Attributes(PowerBI.Attributes): + power_b_i_dataflow_entity_name: Optional[str] = Field( + default=None, description="" + ) + power_b_i_workspace_qualified_name: Optional[str] = Field( + default=None, description="" + ) + power_b_i_dataflow_qualified_name: Optional[str] = Field( + default=None, description="" + ) + power_b_i_dataflow_entity_column_data_type: Optional[str] = Field( + default=None, description="" + ) + power_b_i_dataflow: Optional[PowerBIDataflow] = Field( + default=None, description="" + ) # relationship + + attributes: PowerBIDataflowEntityColumn.Attributes = Field( + default_factory=lambda: PowerBIDataflowEntityColumn.Attributes(), + description=( + "Map of attributes in the instance and their values. " + "The specific keys of this map will vary by type, " + "so are described in the sub-types of this schema." + ), + ) + + +from .power_b_i_dataflow import PowerBIDataflow # noqa diff --git a/pyatlan/model/assets/core/power_b_i_datasource.py b/pyatlan/model/assets/core/power_b_i_datasource.py index ae2a96069..8accfcca2 100644 --- a/pyatlan/model/assets/core/power_b_i_datasource.py +++ b/pyatlan/model/assets/core/power_b_i_datasource.py @@ -36,6 +36,10 @@ def __setattr__(self, name, value): Connection details of the datasource. """ + POWER_BI_DATAFLOWS: ClassVar[RelationField] = RelationField("powerBIDataflows") + """ + TBC + """ DATASETS: ClassVar[RelationField] = RelationField("datasets") """ TBC @@ -43,6 +47,7 @@ def __setattr__(self, name, value): _convenience_properties: ClassVar[List[str]] = [ "connection_details", + "power_b_i_dataflows", "datasets", ] @@ -56,6 +61,16 @@ def connection_details(self, connection_details: Optional[Dict[str, str]]): self.attributes = self.Attributes() self.attributes.connection_details = connection_details + @property + def power_b_i_dataflows(self) -> Optional[List[PowerBIDataflow]]: + return None if self.attributes is None else self.attributes.power_b_i_dataflows + + @power_b_i_dataflows.setter + def power_b_i_dataflows(self, power_b_i_dataflows: Optional[List[PowerBIDataflow]]): + if self.attributes is None: + self.attributes = self.Attributes() + self.attributes.power_b_i_dataflows = power_b_i_dataflows + @property def datasets(self) -> Optional[List[PowerBIDataset]]: return None if self.attributes is None else self.attributes.datasets @@ -70,6 +85,9 @@ class Attributes(PowerBI.Attributes): connection_details: Optional[Dict[str, str]] = Field( default=None, description="" ) + power_b_i_dataflows: Optional[List[PowerBIDataflow]] = Field( + default=None, description="" + ) # relationship datasets: Optional[List[PowerBIDataset]] = Field( default=None, description="" ) # relationship @@ -84,4 +102,5 @@ class Attributes(PowerBI.Attributes): ) +from .power_b_i_dataflow import PowerBIDataflow # noqa from .power_b_i_dataset import PowerBIDataset # noqa diff --git a/pyatlan/model/assets/custom.py b/pyatlan/model/assets/custom.py new file mode 100644 index 000000000..818215fef --- /dev/null +++ b/pyatlan/model/assets/custom.py @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2022 Atlan Pte. Ltd. + + +from __future__ import annotations + +from typing import ClassVar, List + +from pydantic.v1 import Field, validator + +from .core.catalog import Catalog + + +class Custom(Catalog): + """Description""" + + type_name: str = Field(default="Custom", allow_mutation=False) + + @validator("type_name") + def validate_type_name(cls, v): + if v != "Custom": + raise ValueError("must be Custom") + return v + + def __setattr__(self, name, value): + if name in Custom._convenience_properties: + return object.__setattr__(self, name, value) + super().__setattr__(name, value) + + _convenience_properties: ClassVar[List[str]] = [] + + +Custom.Attributes.update_forward_refs() diff --git a/pyatlan/model/assets/custom_entity.py b/pyatlan/model/assets/custom_entity.py new file mode 100644 index 000000000..98ed2e7fd --- /dev/null +++ b/pyatlan/model/assets/custom_entity.py @@ -0,0 +1,172 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2022 Atlan Pte. Ltd. + + +from __future__ import annotations + +from typing import ClassVar, List, Optional + +from pydantic.v1 import Field, validator + +from pyatlan.model.enums import AtlanConnectorType +from pyatlan.model.fields.atlan_fields import RelationField +from pyatlan.utils import init_guid, validate_required_fields + +from .custom import Custom + + +class CustomEntity(Custom): + """Description""" + + @classmethod + @init_guid + def creator(cls, *, name: str, connection_qualified_name: str) -> CustomEntity: + validate_required_fields( + ["name", "connection_qualified_name"], [name, connection_qualified_name] + ) + attributes = CustomEntity.Attributes.creator( + name=name, connection_qualified_name=connection_qualified_name + ) + return cls(attributes=attributes) + + type_name: str = Field(default="CustomEntity", allow_mutation=False) + + @validator("type_name") + def validate_type_name(cls, v): + if v != "CustomEntity": + raise ValueError("must be CustomEntity") + return v + + def __setattr__(self, name, value): + if name in CustomEntity._convenience_properties: + return object.__setattr__(self, name, value) + super().__setattr__(name, value) + + CUSTOM_PARENT_ENTITY: ClassVar[RelationField] = RelationField("customParentEntity") + """ + TBC + """ + CUSTOM_CHILD_ENTITIES: ClassVar[RelationField] = RelationField( + "customChildEntities" + ) + """ + TBC + """ + CUSTOM_RELATED_TO_ENTITIES: ClassVar[RelationField] = RelationField( + "customRelatedToEntities" + ) + """ + TBC + """ + CUSTOM_RELATED_FROM_ENTITIES: ClassVar[RelationField] = RelationField( + "customRelatedFromEntities" + ) + """ + TBC + """ + + _convenience_properties: ClassVar[List[str]] = [ + "custom_parent_entity", + "custom_child_entities", + "custom_related_to_entities", + "custom_related_from_entities", + ] + + @property + def custom_parent_entity(self) -> Optional[CustomEntity]: + return None if self.attributes is None else self.attributes.custom_parent_entity + + @custom_parent_entity.setter + def custom_parent_entity(self, custom_parent_entity: Optional[CustomEntity]): + if self.attributes is None: + self.attributes = self.Attributes() + self.attributes.custom_parent_entity = custom_parent_entity + + @property + def custom_child_entities(self) -> Optional[List[CustomEntity]]: + return ( + None if self.attributes is None else self.attributes.custom_child_entities + ) + + @custom_child_entities.setter + def custom_child_entities( + self, custom_child_entities: Optional[List[CustomEntity]] + ): + if self.attributes is None: + self.attributes = self.Attributes() + self.attributes.custom_child_entities = custom_child_entities + + @property + def custom_related_to_entities(self) -> Optional[List[CustomEntity]]: + return ( + None + if self.attributes is None + else self.attributes.custom_related_to_entities + ) + + @custom_related_to_entities.setter + def custom_related_to_entities( + self, custom_related_to_entities: Optional[List[CustomEntity]] + ): + if self.attributes is None: + self.attributes = self.Attributes() + self.attributes.custom_related_to_entities = custom_related_to_entities + + @property + def custom_related_from_entities(self) -> Optional[List[CustomEntity]]: + return ( + None + if self.attributes is None + else self.attributes.custom_related_from_entities + ) + + @custom_related_from_entities.setter + def custom_related_from_entities( + self, custom_related_from_entities: Optional[List[CustomEntity]] + ): + if self.attributes is None: + self.attributes = self.Attributes() + self.attributes.custom_related_from_entities = custom_related_from_entities + + class Attributes(Custom.Attributes): + custom_parent_entity: Optional[CustomEntity] = Field( + default=None, description="" + ) # relationship + custom_child_entities: Optional[List[CustomEntity]] = Field( + default=None, description="" + ) # relationship + custom_related_to_entities: Optional[List[CustomEntity]] = Field( + default=None, description="" + ) # relationship + custom_related_from_entities: Optional[List[CustomEntity]] = Field( + default=None, description="" + ) # relationship + + @classmethod + @init_guid + def creator( + cls, *, name: str, connection_qualified_name: str + ) -> CustomEntity.Attributes: + validate_required_fields( + ["name", "connection_qualified_name"], [name, connection_qualified_name] + ) + return CustomEntity.Attributes( + name=name, + qualified_name=f"{connection_qualified_name}/{name}", + connection_qualified_name=connection_qualified_name, + connector_name=AtlanConnectorType.get_connector_name( + connection_qualified_name + ), + ) + + attributes: CustomEntity.Attributes = Field( + default_factory=lambda: CustomEntity.Attributes(), + description=( + "Map of attributes in the instance and their values. " + "The specific keys of this map will vary by type, " + "so are described in the sub-types of this schema." + ), + ) + + +CustomEntity.Attributes.update_forward_refs() diff --git a/pyatlan/model/constants.py b/pyatlan/model/constants.py index 65f40c6a6..7a0f5f4d0 100644 --- a/pyatlan/model/constants.py +++ b/pyatlan/model/constants.py @@ -42,6 +42,7 @@ "Column", "ColumnProcess", "Connection", + "CustomEntity", "DataStudioAsset", "DataverseAttribute", "DataverseEntity", diff --git a/pyatlan/model/enums.py b/pyatlan/model/enums.py index 0deeec6f1..16aaf2089 100644 --- a/pyatlan/model/enums.py +++ b/pyatlan/model/enums.py @@ -137,6 +137,7 @@ class AtlanConnectionCategory(str, Enum): DATA_QUALITY = "data-quality" SCHEMA_REGISTRY = "schema-registry" APP = "app" + CUSTOM = "custom" class AtlanConnectorType(str, Enum): @@ -342,6 +343,7 @@ def get_connector_name( AWS_ECS = ("aws-ecs", AtlanConnectionCategory.ELT) AWS_LAMBDA = ("aws-lambda", AtlanConnectionCategory.ELT) AWS_SAGEMAKER = ("aws-sagemaker", AtlanConnectionCategory.ELT) + CUSTOM = ("custom", AtlanConnectionCategory.CUSTOM) class AtlanCustomAttributePrimitiveType(str, Enum): diff --git a/pyatlan/model/typedef.py b/pyatlan/model/typedef.py index 37201f8cb..62ed79d2e 100644 --- a/pyatlan/model/typedef.py +++ b/pyatlan/model/typedef.py @@ -52,6 +52,7 @@ "Column", "ColumnProcess", "Connection", + "CustomEntity", "DataStudioAsset", "DataverseAttribute", "DataverseEntity", diff --git a/tests/integration/custom_asset_test.py b/tests/integration/custom_asset_test.py new file mode 100644 index 000000000..380f90cd8 --- /dev/null +++ b/tests/integration/custom_asset_test.py @@ -0,0 +1,163 @@ +from typing import Generator + +import pytest + +from pyatlan.client.atlan import AtlanClient +from pyatlan.model.assets import Connection, CustomEntity +from pyatlan.model.core import Announcement +from pyatlan.model.enums import ( + AnnouncementType, + AtlanConnectorType, + CertificateStatus, + EntityStatus, +) +from pyatlan.model.response import AssetMutationResponse +from tests.integration.client import TestId, delete_asset +from tests.integration.connection_test import create_connection +from tests.integration.utils import block + +MODULE_NAME = TestId.make_unique("CUSTOM") + +CONNECTOR_TYPE = AtlanConnectorType.CUSTOM +CUSTOM_ENTITY_NAME = f"{MODULE_NAME}-custom-entity" + +CERTIFICATE_STATUS = CertificateStatus.VERIFIED +ANNOUNCEMENT_TITLE = "Python SDK testing." +ANNOUNCEMENT_TYPE = AnnouncementType.INFORMATION +CERTIFICATE_MESSAGE = "Automated testing of the Python SDK." +ANNOUNCEMENT_MESSAGE = "Automated testing of the Python SDK." + +response = block(AtlanClient(), AssetMutationResponse()) + + +@pytest.fixture(scope="module") +def connection(client: AtlanClient) -> Generator[Connection, None, None]: + result = create_connection( + client=client, name=MODULE_NAME, connector_type=CONNECTOR_TYPE + ) + yield result + delete_asset(client, guid=result.guid, asset_type=Connection) + + +@pytest.fixture(scope="module") +def custom_entity( + client: AtlanClient, connection: Connection +) -> Generator[CustomEntity, None, None]: + assert connection.qualified_name + to_create = CustomEntity.creator( + name=CUSTOM_ENTITY_NAME, connection_qualified_name=connection.qualified_name + ) + response = client.asset.save(to_create) + result = response.assets_created(asset_type=CustomEntity)[0] + yield result + delete_asset(client, guid=result.guid, asset_type=CustomEntity) + + +def test_custom_entity( + client: AtlanClient, connection: Connection, custom_entity: CustomEntity +): + assert custom_entity + assert custom_entity.guid + assert custom_entity.qualified_name + assert custom_entity.name == CUSTOM_ENTITY_NAME + assert custom_entity.connection_qualified_name == connection.qualified_name + assert custom_entity.connector_name == AtlanConnectorType.CUSTOM.value + + +@pytest.mark.order(after="test_custom_entity") +def test_delete_custom_entity( + client: AtlanClient, + connection: Connection, + custom_entity: CustomEntity, +): + response = client.asset.delete_by_guid(custom_entity.guid) + assert response + assert not response.assets_created(asset_type=CustomEntity) + assert not response.assets_updated(asset_type=CustomEntity) + deleted = response.assets_deleted(asset_type=CustomEntity) + assert deleted + assert len(deleted) == 1 + assert deleted[0].guid == custom_entity.guid + assert deleted[0].qualified_name == custom_entity.qualified_name + assert deleted[0].delete_handler == "SOFT" + assert deleted[0].status == EntityStatus.DELETED + + +@pytest.mark.order(after="test_delete_custom_entity") +def test_restore_custom_entity( + client: AtlanClient, + connection: Connection, + custom_entity: CustomEntity, +): + assert custom_entity.qualified_name + assert client.asset.restore( + asset_type=CustomEntity, qualified_name=custom_entity.qualified_name + ) + assert custom_entity.qualified_name + restored = client.asset.get_by_qualified_name( + asset_type=CustomEntity, qualified_name=custom_entity.qualified_name + ) + assert restored + assert restored.guid == custom_entity.guid + assert restored.qualified_name == custom_entity.qualified_name + assert restored.status == EntityStatus.ACTIVE + + +def _update_cert_and_annoucement(client, asset, asset_type): + assert asset.name + assert asset.qualified_name + + updated = client.asset.update_certificate( + name=asset.name, + asset_type=asset_type, + qualified_name=asset.qualified_name, + message=CERTIFICATE_MESSAGE, + certificate_status=CERTIFICATE_STATUS, + ) + assert updated + assert updated.certificate_status == CERTIFICATE_STATUS + assert updated.certificate_status_message == CERTIFICATE_MESSAGE + + updated = client.asset.update_announcement( + name=asset.name, + asset_type=asset_type, + qualified_name=asset.qualified_name, + announcement=Announcement( + announcement_type=ANNOUNCEMENT_TYPE, + announcement_title=ANNOUNCEMENT_TITLE, + announcement_message=ANNOUNCEMENT_MESSAGE, + ), + ) + assert updated + assert updated.announcement_type == ANNOUNCEMENT_TYPE + assert updated.announcement_title == ANNOUNCEMENT_TITLE + assert updated.announcement_message == ANNOUNCEMENT_MESSAGE + + +def test_update_custom_assets( + client: AtlanClient, + custom_entity: CustomEntity, +): + _update_cert_and_annoucement(client, custom_entity, CustomEntity) + + +def _retrieve_custom_assets(client, asset, asset_type): + retrieved = client.asset.get_by_guid( + asset.guid, asset_type=asset_type, ignore_relationships=False + ) + assert retrieved + assert not retrieved.is_incomplete + assert retrieved.guid == asset.guid + assert retrieved.qualified_name == asset.qualified_name + assert retrieved.name == asset.name + assert retrieved.connector_name == AtlanConnectorType.CUSTOM + assert retrieved.certificate_status == CERTIFICATE_STATUS + assert retrieved.certificate_status_message == CERTIFICATE_MESSAGE + + +@pytest.mark.order(after="test_update_custom_assets") +def test_retrieve_custom_assets( + client: AtlanClient, + custom_entity: CustomEntity, +): + _retrieve_custom_assets(client, custom_entity, CustomEntity) diff --git a/tests/unit/model/constants.py b/tests/unit/model/constants.py index 87ed6e20f..de3ccd77c 100644 --- a/tests/unit/model/constants.py +++ b/tests/unit/model/constants.py @@ -237,6 +237,12 @@ SUPERSET_DATASET_QUALIFIED_NAME = ( f"{SUPERSET_DASHBOARD_QUALIFIED_NAME}/{SUPERSET_DATASET_NAME}" ) +CUSTOM_CONNECTION_QUALIFIED_NAME = "default/custom/123456789" +CUSTOM_CONNECTOR_TYPE = "custom" +CUSTOM_ENTITY_NAME = "test-custom-entity" +CUSTOM_ENTITY_QUALIFIED_NAME = ( + f"{CUSTOM_CONNECTION_QUALIFIED_NAME}/{CUSTOM_ENTITY_NAME}" +) PROCEDURE_NAME = "test-procedure" DATAVERSE_CONNECTION_QUALIFIED_NAME = "default/dataverse/123456789" diff --git a/tests/unit/model/custom_entity_test.py b/tests/unit/model/custom_entity_test.py new file mode 100644 index 000000000..d09201bb3 --- /dev/null +++ b/tests/unit/model/custom_entity_test.py @@ -0,0 +1,69 @@ +import pytest + +from pyatlan.model.assets import CustomEntity +from tests.unit.model.constants import ( + CUSTOM_ENTITY_NAME, + CUSTOM_ENTITY_QUALIFIED_NAME, + CUSTOM_CONNECTION_QUALIFIED_NAME, + CUSTOM_CONNECTOR_TYPE, +) + + +@pytest.mark.parametrize( + "name, connection_qualified_name, message", + [ + (None, "connection/name", "name is required"), + (CUSTOM_ENTITY_NAME, None, "connection_qualified_name is required"), + ], +) +def test_creator_with_missing_parameters_raise_value_error( + name: str, connection_qualified_name: str, message: str +): + with pytest.raises(ValueError, match=message): + CustomEntity.creator( + name=name, connection_qualified_name=connection_qualified_name + ) + + +def test_creator(): + sut = CustomEntity.creator( + name=CUSTOM_ENTITY_NAME, + connection_qualified_name=CUSTOM_CONNECTION_QUALIFIED_NAME, + ) + + assert sut.name == CUSTOM_ENTITY_NAME + assert sut.connection_qualified_name == CUSTOM_CONNECTION_QUALIFIED_NAME + assert sut.qualified_name == CUSTOM_ENTITY_QUALIFIED_NAME + assert sut.connector_name == CUSTOM_CONNECTOR_TYPE + + +@pytest.mark.parametrize( + "qualified_name, name, message", + [ + (None, CUSTOM_CONNECTION_QUALIFIED_NAME, "qualified_name is required"), + (CUSTOM_ENTITY_NAME, None, "name is required"), + ], +) +def test_updater_with_invalid_parameter_raises_value_error( + qualified_name: str, name: str, message: str +): + with pytest.raises(ValueError, match=message): + CustomEntity.updater(qualified_name=qualified_name, name=name) + + +def test_updater(): + sut = CustomEntity.updater( + qualified_name=CUSTOM_ENTITY_QUALIFIED_NAME, name=CUSTOM_ENTITY_NAME + ) + + assert sut.qualified_name == CUSTOM_ENTITY_QUALIFIED_NAME + assert sut.name == CUSTOM_ENTITY_NAME + + +def test_trim_to_required(): + sut = CustomEntity.updater( + name=CUSTOM_ENTITY_NAME, qualified_name=CUSTOM_ENTITY_QUALIFIED_NAME + ).trim_to_required() + + assert sut.name == CUSTOM_ENTITY_NAME + assert sut.qualified_name == CUSTOM_ENTITY_QUALIFIED_NAME diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py index fa45e46d1..f4de3707c 100644 --- a/tests/unit/test_model.py +++ b/tests/unit/test_model.py @@ -44,6 +44,7 @@ AuthPolicy, Column, ColumnProcess, + CustomEntity, Database, DataverseAttribute, DataverseEntity, @@ -385,6 +386,7 @@ "Optional[SupersetDashboard]": SupersetDashboard(), "Optional[List[SupersetChart]]": [SupersetChart()], "Optional[List[SupersetDataset]]": [SupersetDataset()], + "Optional[CustomEntity]": CustomEntity(), "Optional[List[ModeCollection]]": [ModeCollection()], "Optional[List[ModeQuery]]": [ModeQuery()], "Optional[List[ModeChart]]": [ModeChart()],