diff --git a/singer_sdk/about.py b/singer_sdk/about.py index 5585f55dd..66d1b417e 100644 --- a/singer_sdk/about.py +++ b/singer_sdk/about.py @@ -164,6 +164,60 @@ def format_about(self, about_info: AboutInfo) -> str: class MarkdownFormatter(AboutFormatter, format_name="markdown"): """About formatter for Markdown output.""" + def _generate_property_row( + self, + name: str, + schema: dict[str, t.Any], + *, + required: bool, + parent_name: str | None = None, + ) -> t.Generator[str, None, None]: + """Generate a row for this property and for nested properties, if any. + + Args: + name: The property name. + schema: The property schema. + required: Whether the property is required to be present. + parent_name: The parent property name, if any. + + Yields: + One markdown table row for the setting, and one for each sub-property. + """ + setting_name = f"{parent_name}.{name}" if parent_name else name + md_description = schema.get("description", "").replace("\n", "
") + yield ( + f"| {setting_name} " + f"| {'True' if required else 'False':8} " + f"| {schema.get('default', 'None'):7} " + f"| {md_description:11} |" + ) + if "properties" in schema: + yield from self._generate_property_rows(schema, parent_name=setting_name) + + def _generate_property_rows( + self, + schema: dict[str, t.Any], + *, + parent_name: str = "", + ) -> t.Generator[str, None, None]: + """Generate a row for each property in the schema. + + Args: + schema: A JSON object schema. + parent_name: The parent property name, if any. + + Yields: + One markdown table row for each property. + """ + required_settings = schema.get("required", []) + for name, sub_schema in schema.get("properties", {}).items(): + yield from self._generate_property_row( + name, + sub_schema, + required=name in required_settings, + parent_name=parent_name, + ) + def format_about(self, about_info: AboutInfo) -> str: """Render about information. @@ -173,18 +227,8 @@ def format_about(self, about_info: AboutInfo) -> str: Returns: A formatted string. """ - max_setting_len = max(len(k) for k in about_info.settings["properties"]) - - # Set table base for markdown - table_base = ( - f"| {'Setting':{max_setting_len}}| Required | Default | Description |\n" - f"|:{'-' * max_setting_len}|:--------:|:-------:|:------------|\n" - ) - # Empty list for string parts md_list = [] - # Get required settings for table - required_settings = about_info.settings.get("required", []) # Iterate over Dict to set md md_list.append( @@ -201,19 +245,14 @@ def format_about(self, about_info: AboutInfo) -> str: md_list.append(capabilities) setting = "## Settings\n\n" - - for k, v in about_info.settings.get("properties", {}).items(): - md_description = v.get("description", "").replace("\n", "
") - table_base += ( - f"| {k}{' ' * (max_setting_len - len(k))}" - f"| {'True' if k in required_settings else 'False':8} | " - f"{v.get('default', 'None'):7} | " - f"{md_description:11} |\n" - ) - - setting += table_base + settings_table = ( + "| Setting | Required | Default | Description |\n" + "|:--------|:--------:|:-------:|:------------|\n" + ) + settings_table += "\n".join(self._generate_property_rows(about_info.settings)) + setting += settings_table setting += ( - "\n" + "\n\n" + "\n".join( [ "A full list of supported settings and capabilities " diff --git a/tests/core/test_about.py b/tests/core/test_about.py index 415077227..6836615f2 100644 --- a/tests/core/test_about.py +++ b/tests/core/test_about.py @@ -53,6 +53,16 @@ def about_info() -> AboutInfo: "type": "string", "description": "API key for the tap to use.", }, + "complex_setting": { + "type": "object", + "description": "A complex setting, with sub-settings.", + "properties": { + "sub_setting": { + "type": "string", + "description": "A sub-setting.", + } + }, + }, }, "required": ["api_key"], }, diff --git a/tests/snapshots/about_format/json.snap.json b/tests/snapshots/about_format/json.snap.json index e34e3a408..f1996dffa 100644 --- a/tests/snapshots/about_format/json.snap.json +++ b/tests/snapshots/about_format/json.snap.json @@ -23,6 +23,16 @@ "api_key": { "type": "string", "description": "API key for the tap to use." + }, + "complex_setting": { + "type": "object", + "description": "A complex setting, with sub-settings.", + "properties": { + "sub_setting": { + "type": "string", + "description": "A sub-setting." + } + } } }, "required": [ diff --git a/tests/snapshots/about_format/markdown.snap.md b/tests/snapshots/about_format/markdown.snap.md index 75dd4e358..c9c4e7b82 100644 --- a/tests/snapshots/about_format/markdown.snap.md +++ b/tests/snapshots/about_format/markdown.snap.md @@ -12,10 +12,12 @@ Built with the [Meltano Singer SDK](https://sdk.meltano.com). ## Settings -| Setting | Required | Default | Description | -|:----------|:--------:|:-------:|:------------| -| start_date| False | None | Start date for the tap to extract data from. | -| api_key | True | None | API key for the tap to use. | +| Setting | Required | Default | Description | +|:--------|:--------:|:-------:|:------------| +| start_date | False | None | Start date for the tap to extract data from. | +| api_key | True | None | API key for the tap to use. | +| complex_setting | False | None | A complex setting, with sub-settings. | +| complex_setting.sub_setting | False | None | A sub-setting. | A full list of supported settings and capabilities is available by running: `tap-example --about` diff --git a/tests/snapshots/about_format/text.snap.txt b/tests/snapshots/about_format/text.snap.txt index 4bac11b66..e6796f3b6 100644 --- a/tests/snapshots/about_format/text.snap.txt +++ b/tests/snapshots/about_format/text.snap.txt @@ -4,4 +4,4 @@ Version: 0.1.1 SDK Version: 1.0.0 Supported Python Versions: ['3.6', '3.7', '3.8'] Capabilities: [catalog, discover, state] -Settings: {'properties': {'start_date': {'type': 'string', 'format': 'date-time', 'description': 'Start date for the tap to extract data from.'}, 'api_key': {'type': 'string', 'description': 'API key for the tap to use.'}}, 'required': ['api_key']} \ No newline at end of file +Settings: {'properties': {'start_date': {'type': 'string', 'format': 'date-time', 'description': 'Start date for the tap to extract data from.'}, 'api_key': {'type': 'string', 'description': 'API key for the tap to use.'}, 'complex_setting': {'type': 'object', 'description': 'A complex setting, with sub-settings.', 'properties': {'sub_setting': {'type': 'string', 'description': 'A sub-setting.'}}}}, 'required': ['api_key']} \ No newline at end of file