diff --git a/HISTORY.md b/HISTORY.md index ac00da02f..aaa43ff39 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,7 @@ +## 0.0.25 (May 2, 2023) + +* Update create method for Readme asset + ## 0.0.24 (Apr 27, 2023) * Fix broken link in README.md diff --git a/pyatlan/generator/templates/entity.jinja2 b/pyatlan/generator/templates/entity.jinja2 index 1022e8e1d..12ccdf875 100644 --- a/pyatlan/generator/templates/entity.jinja2 +++ b/pyatlan/generator/templates/entity.jinja2 @@ -51,6 +51,7 @@ from pyatlan.model.structs import ( PopularityInsights, ) from pyatlan.utils import next_id +from urllib.parse import quote, unquote def validate_single_required_field(field_names: list[str], values: list[Any]): indexes = [idx for idx, value in enumerate(values) if value is not None] @@ -231,7 +232,7 @@ class {{ entity_def.name }}({{super_classes[0]}} {%- if "Asset" in super_classes ba_id = CustomMetadataCache.get_id_for_name(name) if ba_id is None: - raise ValueError(f"No business attributes with the name: {name} exist") + raise ValueError(f"No custom metadata with the name: {name} exist") for a_type in CustomMetadataCache.types_by_asset[self.type_name]: if ( hasattr(a_type, "_meta_data_type_name") @@ -343,15 +344,29 @@ class {{ entity_def.name }}({{super_classes[0]}} {%- if "Asset" in super_classes {%- if entity_def.name == "Readme" %} @classmethod # @validate_arguments() - def create(cls, asset: Asset) -> Readme: + def create( + cls, *, asset: Asset, content: str, asset_name: Optional[str] = None + ) -> Readme: return Readme( - attributes=Readme.Attributes( - qualified_name=f"{asset.guid}/readme", - name=f"{asset.attributes.name} Readme", - ), + attributes=Readme.Attributes.create( + asset=asset, content=content, asset_name=asset_name + ) + ) + + @property + def description(self) -> Optional[str]: + ret_value = self.attributes.description + return unquote(ret_value) if ret_value is not None else ret_value + + @description.setter + def description(self, description: Optional[str]): + if self.attributes is None: + self.attributes = self.Attributes() + self.attributes.description = ( + quote(description) if description is not None else description ) {%- endif %} - {%- if entity_def.attribute_defs %} + {%- if entity_def.attribute_defs or entity_def.relationship_attribute_defs %} class Attributes({{super_classes[0]}}.Attributes): {%- for attribute_def in entity_def.attribute_defs %} {%- set type = attribute_def.typeName | get_type %} @@ -424,6 +439,30 @@ class {{ entity_def.name }}({{super_classes[0]}} {%- if "Asset" in super_classes raise ValueError( "One of admin_user, admin_groups or admin_roles is required" ) + {%- elif entity_def.name == "Readme" %} + @classmethod + # @validate_arguments() + def create( + cls, *, asset: Asset, content: str, asset_name: Optional[str] = None + ) -> Readme.Attributes: + validate_required_fields(["asset", "content"], [asset, content]) + if not asset.name: + if not asset_name: + raise ValueError( + "asset_name is required when name is not available from asset" + ) + elif asset_name: + raise ValueError( + "asset_name can not be given when name is available from asset" + ) + else: + asset_name = asset.name + return Readme.Attributes( + qualified_name=f"{asset.guid}/readme", + name=f"{asset_name} Readme", + asset=asset, + description=quote(content), + ) {%- elif entity_def.name == "Database" %} @classmethod # @validate_arguments() diff --git a/pyatlan/model/assets.py b/pyatlan/model/assets.py index 026f0f805..cac2a2839 100644 --- a/pyatlan/model/assets.py +++ b/pyatlan/model/assets.py @@ -6,6 +6,7 @@ import sys from datetime import datetime from typing import Any, ClassVar, Dict, List, Optional, TypeVar +from urllib.parse import quote, unquote from pydantic import Field, StrictStr, root_validator, validator @@ -2067,6 +2068,26 @@ def validate_type_name(cls, v): raise ValueError("must be DataSet") return v + class Attributes(Asset.Attributes): + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + + attributes: "DataSet.Attributes" = Field( + None, + 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.\n", + ) + class ProcessExecution(Asset, type_name="ProcessExecution"): """Description""" @@ -2086,6 +2107,26 @@ def validate_type_name(cls, v): raise ValueError("must be ProcessExecution") return v + class Attributes(Asset.Attributes): + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + + attributes: "ProcessExecution.Attributes" = Field( + None, + 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.\n", + ) + class AtlasGlossaryTerm(Asset, type_name="AtlasGlossaryTerm"): """Description""" @@ -2351,6 +2392,26 @@ def validate_type_name(cls, v): raise ValueError("must be Cloud") return v + class Attributes(Asset.Attributes): + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + + attributes: "Cloud.Attributes" = Field( + None, + 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.\n", + ) + class Infrastructure(Asset, type_name="Infrastructure"): """Description""" @@ -2370,6 +2431,26 @@ def validate_type_name(cls, v): raise ValueError("must be Infrastructure") return v + class Attributes(Asset.Attributes): + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + + attributes: "Infrastructure.Attributes" = Field( + None, + 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.\n", + ) + class Connection(Asset, type_name="Connection"): """Description""" @@ -3096,6 +3177,32 @@ def validate_type_name(cls, v): raise ValueError("must be Namespace") return v + class Attributes(Asset.Attributes): + children_queries: Optional[list[Query]] = Field( + None, description="", alias="childrenQueries" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + children_folders: Optional[list[Folder]] = Field( + None, description="", alias="childrenFolders" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + + attributes: "Namespace.Attributes" = Field( + None, + 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.\n", + ) + class Catalog(Asset, type_name="Catalog"): """Description""" @@ -3115,6 +3222,32 @@ def validate_type_name(cls, v): raise ValueError("must be Catalog") return v + class Attributes(Asset.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "Catalog.Attributes" = Field( + None, + 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.\n", + ) + class Google(Cloud): """Description""" @@ -3540,6 +3673,35 @@ def validate_type_name(cls, v): raise ValueError("must be BIProcess") return v + class Attributes(Process.Attributes): + outputs: Optional[list[Catalog]] = Field( + None, description="", alias="outputs" + ) # relationship + inputs: Optional[list[Catalog]] = Field( + None, description="", alias="inputs" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + column_processes: Optional[list[ColumnProcess]] = Field( + None, description="", alias="columnProcesses" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + + attributes: "BIProcess.Attributes" = Field( + None, + 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.\n", + ) + class ColumnProcess(Process): """Description""" @@ -3559,6 +3721,38 @@ def validate_type_name(cls, v): raise ValueError("must be ColumnProcess") return v + class Attributes(Process.Attributes): + outputs: Optional[list[Catalog]] = Field( + None, description="", alias="outputs" + ) # relationship + process: Optional[Process] = Field( + None, description="", alias="process" + ) # relationship + inputs: Optional[list[Catalog]] = Field( + None, description="", alias="inputs" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + column_processes: Optional[list[ColumnProcess]] = Field( + None, description="", alias="columnProcesses" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + + attributes: "ColumnProcess.Attributes" = Field( + None, + 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.\n", + ) + class Collection(Namespace): """Description""" @@ -3723,6 +3917,32 @@ def validate_type_name(cls, v): raise ValueError("must be EventStore") return v + class Attributes(Catalog.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "EventStore.Attributes" = Field( + None, + 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.\n", + ) + class ObjectStore(Catalog): """Description""" @@ -3742,6 +3962,32 @@ def validate_type_name(cls, v): raise ValueError("must be ObjectStore") return v + class Attributes(Catalog.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "ObjectStore.Attributes" = Field( + None, + 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.\n", + ) + class DataQuality(Catalog): """Description""" @@ -3761,6 +4007,32 @@ def validate_type_name(cls, v): raise ValueError("must be DataQuality") return v + class Attributes(Catalog.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "DataQuality.Attributes" = Field( + None, + 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.\n", + ) + class BI(Catalog): """Description""" @@ -3780,6 +4052,32 @@ def validate_type_name(cls, v): raise ValueError("must be BI") return v + class Attributes(Catalog.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "BI.Attributes" = Field( + None, + 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.\n", + ) + class SaaS(Catalog): """Description""" @@ -3799,6 +4097,32 @@ def validate_type_name(cls, v): raise ValueError("must be SaaS") return v + class Attributes(Catalog.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "SaaS.Attributes" = Field( + None, + 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.\n", + ) + class Dbt(Catalog): """Description""" @@ -4206,6 +4530,32 @@ def validate_type_name(cls, v): raise ValueError("must be Insight") return v + class Attributes(Catalog.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "Insight.Attributes" = Field( + None, + 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.\n", + ) + class API(Catalog): """Description""" @@ -5881,11 +6231,37 @@ def __setattr__(self, name, value): type_name: str = Field("Kafka", allow_mutation=False) - @validator("type_name") - def validate_type_name(cls, v): - if v != "Kafka": - raise ValueError("must be Kafka") - return v + @validator("type_name") + def validate_type_name(cls, v): + if v != "Kafka": + raise ValueError("must be Kafka") + return v + + class Attributes(EventStore.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "Kafka.Attributes" = Field( + None, + 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.\n", + ) class Metric(DataQuality): @@ -6923,6 +7299,32 @@ def validate_type_name(cls, v): raise ValueError("must be Tableau") return v + class Attributes(BI.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "Tableau.Attributes" = Field( + None, + 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.\n", + ) + class Looker(BI): """Description""" @@ -6942,6 +7344,32 @@ def validate_type_name(cls, v): raise ValueError("must be Looker") return v + class Attributes(BI.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "Looker.Attributes" = Field( + None, + 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.\n", + ) + class Salesforce(SaaS): """Description""" @@ -8264,14 +8692,87 @@ def validate_type_name(cls, v): @classmethod # @validate_arguments() - def create(cls, asset: Asset) -> Readme: + def create( + cls, *, asset: Asset, content: str, asset_name: Optional[str] = None + ) -> Readme: return Readme( - attributes=Readme.Attributes( - qualified_name=f"{asset.guid}/readme", - name=f"{asset.attributes.name} Readme", - ), + attributes=Readme.Attributes.create( + asset=asset, content=content, asset_name=asset_name + ) + ) + + @property + def description(self) -> Optional[str]: + ret_value = self.attributes.description + return unquote(ret_value) if ret_value is not None else ret_value + + @description.setter + def description(self, description: Optional[str]): + if self.attributes is None: + self.attributes = self.Attributes() + self.attributes.description = ( + quote(description) if description is not None else description ) + class Attributes(Resource.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + internal: Optional[Internal] = Field( + None, description="", alias="__internal" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + asset: Optional[Asset] = Field( + None, description="", alias="asset" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + see_also: Optional[list[Readme]] = Field( + None, description="", alias="seeAlso" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + @classmethod + # @validate_arguments() + def create( + cls, *, asset: Asset, content: str, asset_name: Optional[str] = None + ) -> Readme.Attributes: + validate_required_fields(["asset", "content"], [asset, content]) + if not asset.name: + if not asset_name: + raise ValueError( + "asset_name is required when name is not available from asset" + ) + elif asset_name: + raise ValueError( + "asset_name can not be given when name is available from asset" + ) + else: + asset_name = asset.name + return Readme.Attributes( + qualified_name=f"{asset.guid}/readme", + name=f"{asset_name} Readme", + asset=asset, + description=quote(content), + ) + + attributes: "Readme.Attributes" = Field( + None, + 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.\n", + ) + class Link(Resource): """Description""" @@ -13786,6 +14287,35 @@ def validate_type_name(cls, v): raise ValueError("must be ThoughtspotLiveboard") return v + class Attributes(Thoughtspot.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + thoughtspot_dashlets: Optional[list[ThoughtspotDashlet]] = Field( + None, description="", alias="thoughtspotDashlets" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "ThoughtspotLiveboard.Attributes" = Field( + None, + 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.\n", + ) + class ThoughtspotDashlet(Thoughtspot): """Description""" @@ -13886,6 +14416,32 @@ def validate_type_name(cls, v): raise ValueError("must be ThoughtspotAnswer") return v + class Attributes(Thoughtspot.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "ThoughtspotAnswer.Attributes" = Field( + None, + 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.\n", + ) + class PowerBIReport(PowerBI): """Description""" @@ -17730,6 +18286,35 @@ def validate_type_name(cls, v): raise ValueError("must be TableauSite") return v + class Attributes(Tableau.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + projects: Optional[list[TableauProject]] = Field( + None, description="", alias="projects" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "TableauSite.Attributes" = Field( + None, + 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.\n", + ) + class TableauDatasource(Tableau): """Description""" @@ -19174,6 +19759,44 @@ def validate_type_name(cls, v): raise ValueError("must be LookerProject") return v + class Attributes(Looker.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + models: Optional[list[LookerModel]] = Field( + None, description="", alias="models" + ) # relationship + explores: Optional[list[LookerExplore]] = Field( + None, description="", alias="explores" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + fields: Optional[list[LookerField]] = Field( + None, description="", alias="fields" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + views: Optional[list[LookerView]] = Field( + None, description="", alias="views" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "LookerProject.Attributes" = Field( + None, + 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.\n", + ) + class LookerQuery(Looker): """Description""" @@ -20132,6 +20755,38 @@ def validate_type_name(cls, v): raise ValueError("must be QlikStream") return v + class Attributes(QlikSpace.Attributes): + input_to_processes: Optional[list[Process]] = Field( + None, description="", alias="inputToProcesses" + ) # relationship + qlik_datasets: Optional[list[QlikDataset]] = Field( + None, description="", alias="qlikDatasets" + ) # relationship + links: Optional[list[Link]] = Field( + None, description="", alias="links" + ) # relationship + qlik_apps: Optional[list[QlikApp]] = Field( + None, description="", alias="qlikApps" + ) # relationship + metrics: Optional[list[Metric]] = Field( + None, description="", alias="metrics" + ) # relationship + readme: Optional[Readme] = Field( + None, description="", alias="readme" + ) # relationship + meanings: Optional[list[AtlasGlossaryTerm]] = Field( + None, description="", alias="meanings" + ) # relationship + output_from_processes: Optional[list[Process]] = Field( + None, description="", alias="outputFromProcesses" + ) # relationship + + attributes: "QlikStream.Attributes" = Field( + None, + 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.\n", + ) + Referenceable.update_forward_refs() AtlasGlossary.update_forward_refs() diff --git a/pyatlan/version.txt b/pyatlan/version.txt index b056f4120..2678ff8d6 100644 --- a/pyatlan/version.txt +++ b/pyatlan/version.txt @@ -1 +1 @@ -0.0.24 +0.0.25 diff --git a/tests/integration/test_entity_model.py b/tests/integration/test_entity_model.py index 54e89d258..26a2860b1 100644 --- a/tests/integration/test_entity_model.py +++ b/tests/integration/test_entity_model.py @@ -18,6 +18,7 @@ Column, Connection, Database, + Readme, Schema, Table, View, @@ -188,6 +189,7 @@ def cleanup(atlan_host, headers, atlan_api_key): "Connection", "View", "Column", + "Readme", ] for type_name in type_names: print() @@ -748,7 +750,7 @@ def test_add_and_remove_classifications(client: AtlanClient): glossary_term = client.get_asset_by_guid( glossary_term.guid, asset_type=AtlasGlossaryTerm ) - assert glossary_term.classifications is None + assert not glossary_term.classifications def test_create_for_modification(client: AtlanClient): @@ -811,3 +813,15 @@ def test_update_remove_announcement(client: AtlanClient, announcement: Announcem ) assert asset is not None assert asset.get_announcment() is None + + +def test_create_readme(client: AtlanClient): + glossary = AtlasGlossary.create(name="Integration Readme Test") + glossary.attributes.user_description = "This is a description of the glossary" + glossary = client.upsert(glossary).assets_created(AtlasGlossary)[0] + readme = Readme.create(asset=glossary, content="

Important

") + response = client.upsert(readme) + assert (reaadmes := response.assets_created(asset_type=Readme)) + assert len(reaadmes) == 1 + assert (glossaries := response.assets_updated(asset_type=AtlasGlossary)) + assert len(glossaries) == 1 diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py index 20243bd25..b3aab7d5f 100644 --- a/tests/unit/test_model.py +++ b/tests/unit/test_model.py @@ -32,6 +32,7 @@ GoogleTag, Histogram, PopularityInsights, + Readme, S3Bucket, S3Object, Schema, @@ -67,6 +68,10 @@ from pyatlan.model.structs import KafkaTopicConsumption from pyatlan.model.typedef import TypeDefResponse +SCHEMA_QUALIFIED_NAME = "default/snowflake/1646836521/ATLAN_SAMPLE_DATA/FOOD_BEVERAGE" + +TABLE_NAME = "MKT_EXPENSES" + TABLE_URL = "POsWut55wIYsXZ5v4z3K98" FRESHNESS = "VdRC4dyNdTJHfFjCiNaKt9" @@ -167,8 +172,8 @@ def announcement(): @pytest.fixture() def table(): return Table.create( - name="MKT_EXPENSES", - schema_qualified_name="default/snowflake/1646836521/ATLAN_SAMPLE_DATA/FOOD_BEVERAGE", + name=TABLE_NAME, + schema_qualified_name=SCHEMA_QUALIFIED_NAME, ) @@ -1352,3 +1357,98 @@ def test_validate_single_required_field_with_bad_values_raises_value_error( def test_validate_single_required_field_with_only_one_field_does_not_raise_value_error(): validate_single_required_field(["One", "Two", "Three"], [None, None, 3]) + + +@pytest.mark.parametrize( + "asset, content, asset_name, error, message", + [ + (None, "stuff", None, ValueError, "asset is required"), + (table, None, None, ValueError, "content is required"), + ( + Table(), + "stuff", + None, + AttributeError, + "'NoneType' object has no attribute 'name'", + ), + ( + Table(attributes=Table.Attributes()), + "stuff", + None, + ValueError, + "asset_name is required when name is not available from asset", + ), + ], +) +def test_create_readme_attributes_without_required_parameters_raises_exception( + asset, content, asset_name, error, message +): + + with pytest.raises(error, match=message): + Readme.Attributes.create(asset=asset, content=content, asset_name=asset_name) + + +@pytest.mark.parametrize( + "asset, content, asset_name, error, message", + [ + (None, "stuff", None, ValueError, "asset is required"), + ( + Table.create( + name=TABLE_NAME, + schema_qualified_name=SCHEMA_QUALIFIED_NAME, + ), + None, + None, + ValueError, + "content is required", + ), + ( + Table(), + "stuff", + None, + AttributeError, + "'NoneType' object has no attribute 'name'", + ), + ( + Table(attributes=Table.Attributes()), + "stuff", + None, + ValueError, + "asset_name is required when name is not available from asset", + ), + ], +) +def test_create_readme_without_required_parameters_raises_exception( + asset, content, asset_name, error, message +): + + with pytest.raises(error, match=message): + Readme.create(asset=asset, content=content, asset_name=asset_name) + + +@pytest.mark.parametrize( + "asset, content, asset_name, expected_name", + [ + ( + Table.create( + name=TABLE_NAME, + schema_qualified_name=SCHEMA_QUALIFIED_NAME, + ), + "

stuff

", + None, + TABLE_NAME, + ), + ( + Table(attributes=Table.Attributes()), + "

stuff

", + TABLE_NAME, + TABLE_NAME, + ), + ], +) +def test_create_readme(asset, content, asset_name, expected_name): + readme = Readme.create(asset=asset, content=content, asset_name=asset_name) + assert readme.qualified_name == f"{asset.guid}/readme" + assert readme.name == f"{expected_name} Readme" + assert readme.attributes.asset == asset + assert readme.description == content