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