diff --git a/pytabular/culture.py b/pytabular/culture.py index 3781e23..59fa5cb 100644 --- a/pytabular/culture.py +++ b/pytabular/culture.py @@ -17,12 +17,21 @@ def __init__(self, object, model) -> None: self.ObjectTranslations = self.set_translation() def set_translation(self) -> List[dict]: - """Based on the culture, it creates a list of dicts with available translations.""" + """Based on the culture, it creates a list of dicts with available translations. + + The model object doesn't have a Parent object. So that will stay + empty. + + Returns: + List[dict]: Translations per object. + """ return [ { "object_translation": translation.Value, "object_name": translation.Object.Name, - "object_parent_name": translation.Object.Parent.Name, + "object_parent_name": translation.Object.Parent.Name + if translation.Object.Parent + else "", "object_type": str(translation.Property), } for translation in self._object.ObjectTranslations @@ -35,9 +44,17 @@ def get_translation( By default it will search for the "Caption" object type, due to fact that a Display folder and Description can also have translations. + + Args: + object_name (str): Object name that you want to translate. + object_parent_name (str): Parent Object name that you want to translate. + object_type (str, optional): The Display Folders can also have translations. + Defaults to "Caption" > Object translation. + + Returns: + dict: With translation of the object. """ try: - # Removed walrus operator so it can be compatible with with python versions before 3.8 translations = [ d for d in self.ObjectTranslations diff --git a/pytabular/document.py b/pytabular/document.py index 1dc4b10..30bd6ed 100644 --- a/pytabular/document.py +++ b/pytabular/document.py @@ -6,12 +6,12 @@ from pathlib import Path -from measure import PyMeasure from table import PyTable from column import PyColumn from culture import PyCulture - -from .pytabular import Tabular +from measure import PyMeasure +from pytabular import Tabular +from typing import List, Dict logger = logging.getLogger("PyTabular") @@ -67,7 +67,7 @@ def __init__( # Translation information self.culture_include: bool = False self.culture_selected: str = "en-US" - self.culture_object: PyCulture = None + self.culture_object: PyCulture # Documentation Parts self.general_page: str = str() @@ -96,6 +96,14 @@ def create_object_reference(self, object: str, object_parent: str) -> str: This is based on the technical names in the model, so not the once in the translations. This makes it possible to link based on dependencies. + (Scope is only Docusaurus) + + Args: + object (str): Object Name + object_parent (str): Object Parent (e.g. Table) + + Returns: + str: String that can be used for custom linking """ url_reference = f"{object_parent}-{object}".replace(" ", "") return f"{{#{url_reference}}}" @@ -107,19 +115,38 @@ def generate_documentation_pages(self) -> None: self.column_page = self.generate_markdown_column_page() self.category_page = self.generate_category_file() - def get_object_caption(self, object_name: str, object_parent: str): - """Retrieves the caption of an object, based on the translations in the culture.""" + def get_object_caption(self, object_name: str, object_parent: str) -> str: + """Retrieves the caption of an object, based on the translations in the culture. + + If no culture is present, the object_name is returned. + + Args: + object_name (str): Object Name + object_parent (str): Object Parent Name + + Returns: + str: Translated object. + """ if self.culture_include: - return self.culture_object.get_translation( - object_name=object_name, object_parent_name=object_parent - ).get("object_translation") + return str( + self.culture_object.get_translation( + object_name=object_name, object_parent_name=object_parent + ).get("object_translation") + ) return object_name def set_translations( self, enable_translations: bool = False, culture: str = "en-US" ) -> None: - """Set translations to active or inactive, depending on the needs of the users.""" + """Set translations to active or inactive, depending on the needs of the users. + + Args: + enable_translations (bool, optional): Flag to enable or disable translations. + Defaults to False. + culture (str, optional): Set culture that needs to be used in the docs. + Defaults to "en-US". + """ logger.info(f"Using Translations set to > {enable_translations}") if enable_translations: @@ -138,12 +165,20 @@ def set_translations( else: self.culture_include = enable_translations - def set_model_friendly_name(self): - """Replaces the model name to a friendly string, so it can be used in an URL.""" + def set_model_friendly_name(self) -> str: + """Replaces the model name to a friendly string, so it can be used in an URL. + + Returns: + str: Friendly model name used in url for docs. + """ return (self.model_name).replace(" ", "-").replace("_", "-").lower() def set_save_path(self) -> Path: - """Set the location of the documentation.""" + """Set the location of the documentation. + + Returns: + Path: Path where the docs are saved. + """ return Path(f"{self.save_location}/{self.friendly_name}") def save_page(self, content: str, page_name: str, keep_file: bool = False) -> None: @@ -166,7 +201,7 @@ def save_page(self, content: str, page_name: str, keep_file: bool = False) -> No target_file = self.save_path / page_name if keep_file and target_file.exists(): - logger.info(f"{page_name} already exists -> fill will not overwritten.") + logger.info(f"{page_name} already exists -> file will not overwritten.") else: logger.info(f"Results are written to -> {page_name}.") @@ -243,6 +278,12 @@ def create_markdown_for_measure(self, object: PyMeasure) -> str: """Create Markdown for a specific measure. That can later on be used for generating the whole measure page. + + Args: + object (PyMeasure): The measure to document. + + Returns: + str: Markdown section for specific Measure """ object_caption = ( self.get_object_caption( @@ -251,54 +292,47 @@ def create_markdown_for_measure(self, object: PyMeasure) -> str: or object.Name ) - object_description = (object.Description or "No Description available").replace( + obj_description = (object.Description or "No Description available").replace( "\\n", "" ) - return f""" -### {object_caption} -**Description**: -> {object_description} - -
- -
Display Folder
-
{object.DisplayFolder}
- -
Table Name
-
{object.Parent.Name}
- -
Format String
-
{object.FormatString}
- -
Is Hidden
-
{object.IsHidden}
- -
- -```dax title="Technical: {object.Name}" -{ - object.Expression -} -``` + object_properties = [ + {"Measure Name": object.Name}, + {"Display Folder": object.DisplayFolder}, + {"Format String": object.FormatString}, + {"Is Hidden": "Yes" if object.IsHidden else "No"}, + ] ---- -""" + obj_text = [ + f"### {object_caption}", + "**Description**:", + f"> {obj_description}", + "" f"{self.generate_object_properties(object_properties)}" "", + f'```dax title="Technical: {object.Name}"', + f" {object.Expression}", + "```", + "---", + ] + return "\n".join(obj_text) def generate_markdown_measure_page(self) -> str: - """Based on the measure objects it generates a measure page.""" + """This function generates the meausure documation page. + + Returns: + str: The full markdown text that is needed + make it compatible with Docusaurus. + """ prev_display_folder = "" markdown_template = [ - f"""--- -sidebar_position: 3 -title: Measures -description: This page contains all measures for the {self.model.Name} model, \ -including the description, \ -format string, and other technical details. ---- - -# Measures for {self.model.Name} -""" + "---", + "sidebar_position: 3", + "title: Measures", + "description: This page contains all measures for " + f"the {self.model.Name} model, including the description, " + "format string, and other technical details.", + "---", + "", + f"# Measures for {self.model.Name}", ] measures = sorted( @@ -316,7 +350,7 @@ def generate_markdown_measure_page(self) -> str: markdown_template.append(self.create_markdown_for_measure(measure)) - return "".join(markdown_template) + return "\n".join(markdown_template) def create_markdown_for_table(self, object: PyTable) -> str: """This functions returns the markdown for a table. @@ -334,109 +368,87 @@ def create_markdown_for_table(self, object: PyTable) -> str: or object.Name ) - object_description = (object.Description or "No Description available").replace( + obj_description = (object.Description or "No Description available").replace( "\\n", "" ) + object_properties = [ + {"Measures (#)": len(object.Measures)}, + {"Columns (#)": len(object.Columns)}, + {"Partiton (#)": len(object.Partitions)}, + {"Data Category": object.DataCategory or "Regular Table"}, + {"Is Hidden": object.IsHidden}, + {"Table Type": object.Partitions[0].ObjectType}, + {"Source Type": object.Partitions[0].SourceType}, + ] + partition_type = "" partition_source = "" + logger.debug(f"{object_caption} => {str(object.Partitions[0].SourceType)}") + if str(object.Partitions[0].SourceType) == "Calculated": partition_type = "dax" partition_source = object.Partitions[0].Source.Expression elif str(object.Partitions[0].SourceType) == "M": partition_type = "powerquery" partition_source = object.Partitions[0].Source.Expression + elif str(object.Partitions[0].SourceType) == "CalculationGroup": + partition_type = "" + partition_source = "" else: partition_type = "sql" partition_source = object.Partitions[0].Source.Query - return f""" -### {object_caption} -**Description**: -> {object_description} - -
-
Measures (#)
-
{len(object.Measures)}
- -
Columns (#)
-
{len(object.Columns)}
- -
Partitions (#)
-
{len(object.Partitions)}
- -
Data Category
-
{object.DataCategory or "Regular Table"}
- -
Is Hidden
-
{object.IsHidden}
- -
Table Type
-
{object.Partitions[0].ObjectType}
- -
Source Type
-
{object.Partitions[0].SourceType}
-
- -```{partition_type} title="Table Source: {object.Name}" -{ - partition_source -} -``` - ---- + obj_text = [ + f"### {object_caption}", + "**Description**: ", + f"> {obj_description}", + "", + f"{self.generate_object_properties(object_properties)}", + "", + f'```{partition_type} title="Table Source: {object.Name}"', + f" {partition_source}", + "```", + "---", + ] -""" + return "\n".join(obj_text) def generate_markdown_table_page(self) -> str: - """This function generates the markdown tables documentation for the tables in the Model.""" - markdown_template = f"""--- -sidebar_position: 2 -title: Tables -description: This page contains all columns with tables for {self.model.Name}, \ -including the description, \ -and technical details. ---- - -# Tables {self.model.Name} + """This function generates the markdown for table documentation. - """ - - for table in self.model.Tables: - markdown_template += self.create_markdown_for_table(table) - - return markdown_template - - def generate_markdown_column_page(self) -> str: - """This function generates the markdown for documentation about columns in the Model.""" - markdown_template = f"""--- -sidebar_position: 4 -title: Columns -description: This page contains all columns with Columns for {self.model.Name}, \ -including the description, format string, and other technical details. ---- - - """ - - for table in self.model.Tables: - markdown_template += f""" -## Columns for {table.Name} - """ - - for column in table.Columns: - if "RowNumber" in column.Name: - continue - - markdown_template += self.create_markdown_for_column(column) + Returns: + str: Will be appended to the page text. + """ + markdown_template = [ + "---", + "sidebar_position: 2", + "title: Tables", + "description: This page contains all columns with " + f"tables for {self.model.Name}, including the description, " + "and technical details.", + "---", + "", + f"# Tables {self.model.Name}", + ] - return markdown_template + markdown_template.extend( + self.create_markdown_for_table(table) for table in self.model.Tables + ) + return "\n".join(markdown_template) def create_markdown_for_column(self, object: PyColumn) -> str: """Generates the Markdown for a specifc column. - If a colums is calculated, then it also shows - the expression for that column in DAX. + If a columns is calculated, then it also shows the expression for + that column in DAX. + + Args: + object (PyColumn): Needs PyColumn objects input + + Returns: + str: Will be appended to the page text. """ object_caption = ( self.get_object_caption( @@ -445,73 +457,140 @@ def create_markdown_for_column(self, object: PyColumn) -> str: or object.Name ) - object_description = (object.Description or "No Description available").replace( - "\\n", "" + obj_description = ( + object.Description.replace("\\n", "") or "No Description available" ) - basic_info = f""" -### {object_caption} {self.create_object_reference( - object=object.Name, - object_parent=object.Parent.Name - )} -**Description**: -> {object_description} + obj_reference = self.create_object_reference( + object=object.Name, + object_parent=object.Parent.Name + ) -
-
Column Name
-
{object.Name}
+ obj_heading = f"""{object_caption} {obj_reference}""" + + object_properties = [ + {"Column Name": object.Name}, + {"Object Type": object.ObjectType}, + {"Type": object.Type}, + {"Is Available In Excel": object.IsAvailableInMDX}, + {"Is Hidden": object.IsHidden}, + {"Data Category": object.DataCategory}, + {"Data Type": object.DataType}, + {"Display Folder": object.DisplayFolder}, + ] -
Object Type
-
{object.ObjectType}
+ obj_text = [ + f"### {obj_heading}", + "**Description**:", + f"> {obj_description}", + "", + f"{self.generate_object_properties(object_properties)}", + ] -
Type
-
{object.Type}
+ if str(object.Type) == "Calculated": + obj_text.extend( + ( + f'```dax title="Technical: {object.Name}"', + f" {object.Expression}", + "```", + ) + ) + obj_text.append("---") -
Is Available In Excel
-
{object.IsAvailableInMDX}
+ return "\n".join(obj_text) -
Is Hidden
-
{object.IsHidden}
+ def generate_markdown_column_page(self) -> str: + """This function generates the markdown for the colums documentation. -
Data Category
-
{object.DataCategory}
+ Returns: + str: Will be appended to the page text. + """ + markdown_template = [ + "---", + "sidebar_position: 4", + f"title: Columns description: This page contains all columns with " + f"Columns for {self.model.Name} " + "including the description, format string, and other technical details.", + "---", + "", + ] -
Data Type
-
{object.DataType}
+ for table in self.model.Tables: + markdown_template.append(f"## Columns for {table.Name}") -
DisplayFolder
-
{object.DisplayFolder}
+ markdown_template.extend( + self.create_markdown_for_column(column) + for column in table.Columns + if "RowNumber" not in column.Name + ) + return "\n".join(markdown_template) -
-""" + def generate_category_file(self) -> str: + """Docusaurs can generate an index based on the files. - if str(object.Type) == "Calculated": - basic_info += f""" -```dax title="Technical: {object.Name}" -{ - object.Expression -} -``` - """ - return ( - basic_info - + """ ---- - """ - ) + The files that are in the same directory as _category_.ym will + be use to create an index and a navigation. For more information + see Docusaurus documentation. + + Returns: + str: Text that will be the base of _category_.yml. + """ + obj_text = [ + "position: 2 # float position is supported", + f"label: '{self.model_name}'", + "collapsible: true # make the category collapsible", + "collapsed: true # keep the category open by default", + " link:", + " type: generated-index", + " title: Documentation Overview", + "customProps:", + " description: To be added in the future.", + ] + + return "\n".join(obj_text) + + @staticmethod + def generate_object_properties(properties: List[Dict[str, str]]) -> str: + """Generate the section for object properties. - def generate_category_file(self): - """Docusaurs can generate an index. + You can select your own properties to display + by providing a the properties in a list of + dicts. - The category yaml will make that happen. + Args: + Self. + Properties (dict): The ones you want to show. + + Returns: + str: HTML used in the markdown. + + Example: + ```python + [ + { "Display Folder": "Sales Order Information" }, + { "Is Hidden": "False" }, + { "Format String": "#.###,## } + ] + ``` + Returns: + ``` +
+
Display Folder
+
Sales Order Information
+ +
Is Hidden
+
False
+ +
Format String
+
#.###,##
+
+ ``` """ - return f"""position: 2 # float position is supported -label: '{self.model_name}' -collapsible: true # make the category collapsible -collapsed: true # keep the category open by default -link: - type: generated-index - title: Documentation Overview -customProps: - description: To be added in the future. -""" + obj_text = ["
"] + + for obj_prop in properties: + for caption, text in obj_prop.items(): + obj_text.extend((f"
{caption}
", f"
{text}
", "")) + obj_text.append("
") + + return "\n".join(obj_text)