diff --git a/src/snowflake/cli/_app/dev/docs/generator.py b/src/snowflake/cli/_app/dev/docs/generator.py index bd7c28228a..d1fcbfc141 100644 --- a/src/snowflake/cli/_app/dev/docs/generator.py +++ b/src/snowflake/cli/_app/dev/docs/generator.py @@ -21,6 +21,10 @@ from snowflake.cli._app.dev.docs.project_definition_docs_generator import ( generate_project_definition_docs, ) +from snowflake.cli.api.project.schemas.project_definition import ( + DefinitionV11, + DefinitionV20, +) from snowflake.cli.api.secure_path import SecurePath log = logging.getLogger(__name__) @@ -32,4 +36,5 @@ def generate_docs(root: SecurePath, command: Command): """ root.mkdir(exist_ok=True) generate_command_docs(root / "commands", command) - generate_project_definition_docs(root / "project_definition") + generate_project_definition_docs(root / "project_definition_V11", DefinitionV11) + generate_project_definition_docs(root / "project_definition_V20", DefinitionV20) diff --git a/src/snowflake/cli/_app/dev/docs/project_definition_docs_generator.py b/src/snowflake/cli/_app/dev/docs/project_definition_docs_generator.py index 19d112b8c0..30e3b8e320 100644 --- a/src/snowflake/cli/_app/dev/docs/project_definition_docs_generator.py +++ b/src/snowflake/cli/_app/dev/docs/project_definition_docs_generator.py @@ -22,7 +22,7 @@ ProjectDefinitionGenerateJsonSchema, ) from snowflake.cli._app.dev.docs.template_utils import get_template_environment -from snowflake.cli.api.project.schemas.project_definition import DefinitionV11 +from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel from snowflake.cli.api.secure_path import SecurePath log = logging.getLogger(__name__) @@ -30,7 +30,9 @@ DEFINITION_DESCRIPTION = "definition_description.rst.jinja2" -def generate_project_definition_docs(root: SecurePath): +def generate_project_definition_docs( + root: SecurePath, definition: type[UpdatableModel] +) -> None: """ Recursively traverses the generated project definition schema, creating a file for each section that mirrors the YAML structure. @@ -39,7 +41,7 @@ def generate_project_definition_docs(root: SecurePath): root.mkdir(exist_ok=True) list_of_sections = model_json_schema( - DefinitionV11, schema_generator=ProjectDefinitionGenerateJsonSchema + definition, schema_generator=ProjectDefinitionGenerateJsonSchema )["result"] for section in list_of_sections: _render_definition_description(root, section) diff --git a/src/snowflake/cli/_app/dev/docs/project_definition_generate_json_schema.py b/src/snowflake/cli/_app/dev/docs/project_definition_generate_json_schema.py index 3168cdb6a8..0b6f214b3d 100644 --- a/src/snowflake/cli/_app/dev/docs/project_definition_generate_json_schema.py +++ b/src/snowflake/cli/_app/dev/docs/project_definition_generate_json_schema.py @@ -33,6 +33,7 @@ class ProjectDefinitionProperty: required: bool name: str description: str + examples: List[str] add_types: bool types: str @@ -81,25 +82,64 @@ def generate(self, schema, mode="validation"): self._remapped_definitions = json_schema["$defs"] return {"result": self._get_definition_sections(json_schema)} + def _get_current_definition_sections( + self, current_definition_properties: Dict[str, Any] + ): + result = {} + if "entities" in current_definition_properties: + entities_mapping = current_definition_properties["entities"][ + "additionalProperties" + ]["discriminator"]["mapping"] + for entity_name, entity_model in entities_mapping.items(): + entity_name = entity_name.replace(" ", "_") + result["entities_" + entity_name] = { + **current_definition_properties["entities"], + "properties": { + entity_name + + "_name": {"$ref": entity_model, "title": "Entity name"} + }, + } + + for property_name, property_model in current_definition_properties.items(): + if property_name == "entities": + continue + result[property_name] = property_model + return result + def _get_definition_sections( self, current_definition: Dict[str, Any] ) -> List[Dict[str, Any]]: required_properties: List[Dict[str, Any]] = [] sections: List[Dict[str, Any]] = [] - for property_name, property_model in current_definition["properties"].items(): + for property_name, property_model in self._get_current_definition_sections( + current_definition["properties"] + ).items(): + section_name = property_name is_required = ( "required" in current_definition and property_name in current_definition["required"] ) - children_properties = self._get_children_properties( - property_model, property_name - ) + + if "entities" in property_name: + property_name = "entities" + children_properties = self._get_section_properties( + property_model, + property_name, + 1, + False, + ) + + else: + children_properties = self._get_children_properties( + property_model, property_name + ) new_property = ProjectDefinitionProperty( path=property_name, title=property_model.get("title", ""), description=property_model.get("description", ""), + examples=property_model.get("examples", []), indents=0, item_index=0, required=is_required, @@ -116,7 +156,7 @@ def _get_definition_sections( { "properties": properties, "title": property_model["title"], - "name": property_name, + "name": section_name, } ) @@ -150,10 +190,12 @@ def _get_section_properties( children_properties = self._get_children_properties( property_model, new_current_path, depth ) + new_property = ProjectDefinitionProperty( path=new_current_path, title=property_model.get("title", ""), description=property_model.get("description", ""), + examples=property_model.get("examples", []), indents=depth, item_index=item_index, required=is_required, diff --git a/src/snowflake/cli/_app/dev/docs/templates/definition_description.rst.jinja2 b/src/snowflake/cli/_app/dev/docs/templates/definition_description.rst.jinja2 index d4050e041f..fa5d59334f 100644 --- a/src/snowflake/cli/_app/dev/docs/templates/definition_description.rst.jinja2 +++ b/src/snowflake/cli/_app/dev/docs/templates/definition_description.rst.jinja2 @@ -30,6 +30,12 @@ The following table describes the project definition properties. {% if property["description"] %} {{ property["description"] }} {% else %}{% endif -%} +{% if property["examples"] %} + **Examples**: + {% for example in property["examples"] %} + - {{ example }} + {%+ endfor %} +{% else %}{% endif -%} {%+ endfor %} {% else %} diff --git a/tests/test_docs_generation_output.py b/tests/test_docs_generation_output.py index 38fa10ea79..c2f6eb9097 100644 --- a/tests/test_docs_generation_output.py +++ b/tests/test_docs_generation_output.py @@ -19,7 +19,10 @@ from click import Command from pydantic.json_schema import GenerateJsonSchema, model_json_schema from snowflake.cli._app.cli_app import app_context_holder -from snowflake.cli.api.project.schemas.project_definition import DefinitionV11 +from snowflake.cli.api.project.schemas.project_definition import ( + DefinitionV11, + DefinitionV20, +) @mock.patch( @@ -64,7 +67,7 @@ def test_definition_file_format_generated_from_json(mock_generate, runner, temp_ project_definition_path = ( Path(temp_dir) / "gen_docs" - / "project_definition" + / "project_definition_V11" / "definition_section_demo.txt" ) @@ -115,7 +118,7 @@ def test_files_generated_for_each_optional_project_definition_property( runner, temp_dir ): runner.invoke(["--docs"]) - project_definition_path = Path(temp_dir) / "gen_docs" / "project_definition" + project_definition_path = Path(temp_dir) / "gen_docs" / "project_definition_V11" errors = [] model_json = model_json_schema(DefinitionV11, schema_generator=GenerateJsonSchema) @@ -128,6 +131,25 @@ def test_files_generated_for_each_optional_project_definition_property( assert len(errors) == 0, "\n".join(errors) +def test_files_generated_for_each_entity_definition(runner, temp_dir): + runner.invoke(["--docs"]) + project_definition_path = Path(temp_dir) / "gen_docs" / "project_definition_V20" + errors = [] + + model_json = model_json_schema(DefinitionV20, schema_generator=GenerateJsonSchema) + mapping = model_json["properties"]["entities"]["additionalProperties"][ + "discriminator" + ]["mapping"] + for property_name, _ in mapping.items(): + if not ( + project_definition_path + / f"definition_entities_{property_name.replace(' ', '_')}.txt" + ).exists(): + errors.append(f"Section `{property_name}` was not properly generated") + + assert len(errors) == 0, "\n".join(errors) + + def test_all_commands_have_generated_files(runner, temp_dir): runner.invoke(["--docs"])