snowflake-cli-labs 2.5.0rc2__py3-none-any.whl → 2.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/api/cli_global_context.py +31 -3
  3. snowflake/cli/api/commands/decorators.py +21 -6
  4. snowflake/cli/api/commands/flags.py +60 -51
  5. snowflake/cli/api/commands/snow_typer.py +24 -0
  6. snowflake/cli/api/commands/typer_pre_execute.py +26 -0
  7. snowflake/cli/api/console/abc.py +8 -0
  8. snowflake/cli/api/console/console.py +29 -4
  9. snowflake/cli/api/constants.py +3 -0
  10. snowflake/cli/api/project/definition.py +17 -35
  11. snowflake/cli/api/project/definition_manager.py +22 -19
  12. snowflake/cli/api/project/errors.py +9 -6
  13. snowflake/cli/api/project/schemas/identifier_model.py +1 -1
  14. snowflake/cli/api/project/schemas/native_app/application.py +15 -3
  15. snowflake/cli/api/project/schemas/native_app/native_app.py +5 -1
  16. snowflake/cli/api/project/schemas/native_app/path_mapping.py +14 -3
  17. snowflake/cli/api/project/schemas/project_definition.py +37 -6
  18. snowflake/cli/api/project/schemas/streamlit/streamlit.py +3 -0
  19. snowflake/cli/api/project/schemas/updatable_model.py +2 -6
  20. snowflake/cli/api/rest_api.py +113 -0
  21. snowflake/cli/api/sanitizers.py +43 -0
  22. snowflake/cli/api/sql_execution.py +7 -0
  23. snowflake/cli/api/utils/definition_rendering.py +95 -25
  24. snowflake/cli/api/utils/models.py +31 -26
  25. snowflake/cli/api/utils/rendering.py +24 -3
  26. snowflake/cli/app/cli_app.py +2 -0
  27. snowflake/cli/app/commands_registration/command_plugins_loader.py +8 -0
  28. snowflake/cli/app/dev/docs/commands_docs_generator.py +100 -0
  29. snowflake/cli/app/dev/docs/generator.py +8 -67
  30. snowflake/cli/app/dev/docs/project_definition_docs_generator.py +58 -0
  31. snowflake/cli/app/dev/docs/project_definition_generate_json_schema.py +227 -0
  32. snowflake/cli/app/dev/docs/template_utils.py +23 -0
  33. snowflake/cli/app/dev/docs/templates/definition_description.rst.jinja2 +38 -0
  34. snowflake/cli/app/dev/docs/templates/usage.rst.jinja2 +6 -1
  35. snowflake/cli/app/loggers.py +25 -0
  36. snowflake/cli/app/printing.py +10 -5
  37. snowflake/cli/app/telemetry.py +11 -0
  38. snowflake/cli/plugins/nativeapp/artifacts.py +78 -9
  39. snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +3 -11
  40. snowflake/cli/plugins/nativeapp/codegen/compiler.py +6 -24
  41. snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +27 -27
  42. snowflake/cli/plugins/nativeapp/commands.py +23 -12
  43. snowflake/cli/plugins/nativeapp/constants.py +2 -0
  44. snowflake/cli/plugins/nativeapp/errno.py +15 -0
  45. snowflake/cli/plugins/nativeapp/feature_flags.py +24 -0
  46. snowflake/cli/plugins/nativeapp/init.py +5 -0
  47. snowflake/cli/plugins/nativeapp/manager.py +101 -103
  48. snowflake/cli/plugins/nativeapp/project_model.py +181 -0
  49. snowflake/cli/plugins/nativeapp/run_processor.py +178 -110
  50. snowflake/cli/plugins/nativeapp/teardown_processor.py +89 -64
  51. snowflake/cli/plugins/nativeapp/utils.py +2 -2
  52. snowflake/cli/plugins/nativeapp/version/commands.py +3 -3
  53. snowflake/cli/plugins/object/commands.py +70 -4
  54. snowflake/cli/plugins/object/manager.py +44 -3
  55. snowflake/cli/plugins/snowpark/commands.py +2 -2
  56. snowflake/cli/plugins/sql/commands.py +2 -10
  57. snowflake/cli/plugins/sql/manager.py +4 -2
  58. snowflake/cli/plugins/stage/commands.py +23 -4
  59. snowflake/cli/plugins/stage/diff.py +81 -51
  60. snowflake/cli/plugins/stage/manager.py +2 -1
  61. snowflake/cli/plugins/streamlit/commands.py +2 -1
  62. snowflake/cli/plugins/streamlit/manager.py +6 -0
  63. {snowflake_cli_labs-2.5.0rc2.dist-info → snowflake_cli_labs-2.6.0.dist-info}/METADATA +15 -9
  64. {snowflake_cli_labs-2.5.0rc2.dist-info → snowflake_cli_labs-2.6.0.dist-info}/RECORD +67 -56
  65. {snowflake_cli_labs-2.5.0rc2.dist-info → snowflake_cli_labs-2.6.0.dist-info}/WHEEL +1 -1
  66. {snowflake_cli_labs-2.5.0rc2.dist-info → snowflake_cli_labs-2.6.0.dist-info}/entry_points.txt +0 -0
  67. {snowflake_cli_labs-2.5.0rc2.dist-info → snowflake_cli_labs-2.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -63,10 +63,31 @@ def _env_bootstrap(env: Environment) -> Environment:
63
63
  return env
64
64
 
65
65
 
66
+ class IgnoreAttrEnvironment(Environment):
67
+ """
68
+ extend Environment class and ignore attributes during rendering.
69
+ This ensures that attributes of classes
70
+ do not get used during rendering (e.g. __class__, get, etc).
71
+ Only dict items can be used for rendering.
72
+ """
73
+
74
+ def getattr(self, obj, attribute): # noqa: A003
75
+ try:
76
+ return obj[attribute]
77
+ except (TypeError, LookupError, AttributeError):
78
+ return self.undefined(obj=obj, name=attribute)
79
+
80
+ def getitem(self, obj, argument):
81
+ try:
82
+ return obj[argument]
83
+ except (AttributeError, TypeError, LookupError):
84
+ return self.undefined(obj=obj, name=argument)
85
+
86
+
66
87
  def get_snowflake_cli_jinja_env() -> Environment:
67
88
  _random_block = "___very___unique___block___to___disable___logic___blocks___"
68
89
  return _env_bootstrap(
69
- Environment(
90
+ IgnoreAttrEnvironment(
70
91
  loader=loaders.BaseLoader(),
71
92
  keep_trailing_newline=True,
72
93
  variable_start_string=_YML_TEMPLATE_START,
@@ -81,7 +102,7 @@ def get_snowflake_cli_jinja_env() -> Environment:
81
102
  def get_sql_cli_jinja_env():
82
103
  _random_block = "___very___unique___block___to___disable___logic___blocks___"
83
104
  return _env_bootstrap(
84
- Environment(
105
+ IgnoreAttrEnvironment(
85
106
  loader=loaders.BaseLoader(),
86
107
  keep_trailing_newline=True,
87
108
  variable_start_string="&{",
@@ -108,7 +129,7 @@ def jinja_render_from_file(
108
129
  None if file path is provided, else returns the rendered string.
109
130
  """
110
131
  env = _env_bootstrap(
111
- Environment(
132
+ IgnoreAttrEnvironment(
112
133
  loader=loaders.FileSystemLoader(template_path.parent),
113
134
  keep_trailing_newline=True,
114
135
  undefined=StrictUndefined,
@@ -30,6 +30,7 @@ from snowflake.cli.api.config import config_init
30
30
  from snowflake.cli.api.output.formats import OutputFormat
31
31
  from snowflake.cli.api.output.types import CollectionResult
32
32
  from snowflake.cli.api.secure_path import SecurePath
33
+ from snowflake.cli.app import loggers
33
34
  from snowflake.cli.app.api_impl.plugin.plugin_config_provider_impl import (
34
35
  PluginConfigProviderImpl,
35
36
  )
@@ -88,6 +89,7 @@ def _commands_registration_callback(value: bool):
88
89
  @_commands_registration.before
89
90
  def _config_init_callback(configuration_file: Optional[Path]):
90
91
  config_init(configuration_file)
92
+ loggers.create_initial_loggers()
91
93
 
92
94
 
93
95
  @_commands_registration.before
@@ -100,6 +100,10 @@ class CommandPluginsLoader:
100
100
  self._loaded_command_paths[
101
101
  loaded_plugin.command_spec.full_command_path
102
102
  ] = loaded_plugin
103
+
104
+ if self._is_external_plugin(loaded_plugin):
105
+ log.info("Loaded external plugin: %s", plugin_name)
106
+
103
107
  return loaded_plugin
104
108
 
105
109
  def _load_plugin_spec(
@@ -145,6 +149,10 @@ class CommandPluginsLoader:
145
149
  )
146
150
  return None
147
151
 
152
+ @staticmethod
153
+ def _is_external_plugin(plugin) -> bool:
154
+ return isinstance(plugin, LoadedExternalCommandPlugin)
155
+
148
156
 
149
157
  def load_only_builtin_command_plugins() -> List[LoadedCommandPlugin]:
150
158
  loader = CommandPluginsLoader()
@@ -0,0 +1,100 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ from typing import List, Optional
19
+
20
+ from click import Command
21
+ from snowflake.cli.api.secure_path import SecurePath
22
+ from snowflake.cli.app.dev.docs.template_utils import get_template_environment
23
+ from typer.core import TyperArgument
24
+
25
+ log = logging.getLogger(__name__)
26
+
27
+ CMD_USAGE_TMPL = "usage.rst.jinja2"
28
+ OVERVIEW_TMPL = "overview.rst.jinja2"
29
+
30
+
31
+ def generate_command_docs(
32
+ root: SecurePath, command: Command, cmd_parts: Optional[List] = None
33
+ ):
34
+ """
35
+ Iterates recursively through commands info. Creates a file structure resembling
36
+ commands structure. For each terminal command creates a "usage" rst file.
37
+ """
38
+ if getattr(command, "hidden", False):
39
+ return
40
+
41
+ root.mkdir(exist_ok=True)
42
+ if cmd_parts is None:
43
+ _render_command_usage(command, root, cmd_parts, template_name=OVERVIEW_TMPL)
44
+
45
+ cmd_parts = cmd_parts or []
46
+ if hasattr(command, "commands"):
47
+ for command_name, command_info in command.commands.items():
48
+ path = root / command.name if command.name != "default" else root
49
+ generate_command_docs(path, command_info, [*cmd_parts, command_name])
50
+ else:
51
+ _render_command_usage(command, root, cmd_parts)
52
+
53
+
54
+ def get_main_option(options: List[str]) -> str:
55
+ long_options = [option for option in options if option.startswith("--")]
56
+ if long_options:
57
+ return long_options[0]
58
+
59
+ short_options = [option for option in options if option.startswith("-")]
60
+ if short_options:
61
+ return short_options[0]
62
+
63
+ return ""
64
+
65
+
66
+ def _render_command_usage(
67
+ command: Command,
68
+ root: SecurePath,
69
+ path: Optional[List] = None,
70
+ template_name: str = CMD_USAGE_TMPL,
71
+ ):
72
+ # This is end command
73
+ command_name = command.name
74
+ env = get_template_environment()
75
+ env.filters[get_main_option.__name__] = get_main_option
76
+ template = env.get_template(template_name)
77
+ arguments = []
78
+ options = []
79
+ for param in command.params:
80
+ if isinstance(param, TyperArgument):
81
+ arguments.append(param)
82
+ else:
83
+ options.append(param)
84
+
85
+ # RST files are presumed to be standalone pages in the docs with a matching item in the left nav.
86
+ # Included files, which these are, need to use the .txt extension.
87
+ file_path = root / f"usage-{command_name}.txt"
88
+ log.info("Creating %s", file_path)
89
+ with file_path.open("w+") as fh:
90
+ fh.write(
91
+ template.render(
92
+ {
93
+ "help": command.help,
94
+ "name": command_name,
95
+ "options": options,
96
+ "arguments": arguments,
97
+ "path": path,
98
+ }
99
+ )
100
+ )
@@ -15,80 +15,21 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  import logging
18
- from pathlib import Path
19
- from typing import List, Optional
20
18
 
21
19
  from click import Command
22
- from jinja2 import Environment, FileSystemLoader
23
20
  from snowflake.cli.api.secure_path import SecurePath
24
- from typer.core import TyperArgument
21
+ from snowflake.cli.app.dev.docs.commands_docs_generator import generate_command_docs
22
+ from snowflake.cli.app.dev.docs.project_definition_docs_generator import (
23
+ generate_project_definition_docs,
24
+ )
25
25
 
26
26
  log = logging.getLogger(__name__)
27
27
 
28
- CMD_USAGE_TMPL = "usage.rst.jinja2"
29
- OVERVIEW_TMPL = "overview.rst.jinja2"
30
28
 
31
-
32
- def generate_docs(root: SecurePath, command: Command, cmd_parts: Optional[List] = None):
29
+ def generate_docs(root: SecurePath, command: Command):
33
30
  """
34
- Iterates recursively through commands info. Creates a file structure resembling
35
- commands structure. For each terminal command creates a "usage" rst file.
31
+ Generates documentation for each command, its options and for the project definition.
36
32
  """
37
- if getattr(command, "hidden", False):
38
- return
39
-
40
33
  root.mkdir(exist_ok=True)
41
- if cmd_parts is None:
42
- _render_usage(command, root, cmd_parts, template_name=OVERVIEW_TMPL)
43
-
44
- cmd_parts = cmd_parts or []
45
- if hasattr(command, "commands"):
46
- for command_name, command_info in command.commands.items():
47
- path = root / command.name if command.name != "default" else root
48
- generate_docs(path, command_info, [*cmd_parts, command_name])
49
- else:
50
- _render_usage(command, root, cmd_parts)
51
-
52
-
53
- def get_main_option(options: List[str]) -> str:
54
- long_options = [option for option in options if option.startswith("--")]
55
- short_options = [option for option in options if option.startswith("-")]
56
- if long_options:
57
- return long_options[0]
58
- if short_options:
59
- return short_options[0]
60
- return ""
61
-
62
-
63
- def _render_usage(
64
- command: Command,
65
- root: SecurePath,
66
- path: Optional[List] = None,
67
- template_name: str = CMD_USAGE_TMPL,
68
- ):
69
- # This is end command
70
- command_name = command.name
71
- env = Environment(loader=FileSystemLoader(Path(__file__).parent / "templates"))
72
- env.filters[get_main_option.__name__] = get_main_option
73
- template = env.get_template(template_name)
74
- arguments = []
75
- options = []
76
- for param in command.params:
77
- if isinstance(param, TyperArgument):
78
- arguments.append(param)
79
- else:
80
- options.append(param)
81
- file_path = root / f"usage-{command_name}.txt"
82
- log.info("Creating %s", file_path)
83
- with file_path.open("w+") as fh:
84
- fh.write(
85
- template.render(
86
- {
87
- "help": command.help,
88
- "name": command_name,
89
- "options": options,
90
- "arguments": arguments,
91
- "path": path,
92
- }
93
- )
94
- )
34
+ generate_command_docs(root / "commands", command)
35
+ generate_project_definition_docs(root / "project_definition")
@@ -0,0 +1,58 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ from typing import Any, Dict
19
+
20
+ from pydantic.json_schema import model_json_schema
21
+ from snowflake.cli.api.project.schemas.project_definition import ProjectDefinition
22
+ from snowflake.cli.api.secure_path import SecurePath
23
+ from snowflake.cli.app.dev.docs.project_definition_generate_json_schema import (
24
+ ProjectDefinitionGenerateJsonSchema,
25
+ )
26
+ from snowflake.cli.app.dev.docs.template_utils import get_template_environment
27
+
28
+ log = logging.getLogger(__name__)
29
+
30
+ DEFINITION_DESCRIPTION = "definition_description.rst.jinja2"
31
+
32
+
33
+ def generate_project_definition_docs(root: SecurePath):
34
+ """
35
+ Recursively traverses the generated project definition schema,
36
+ creating a file for each section that mirrors the YAML structure.
37
+ Each file contains the definition for every field within that section.
38
+ """
39
+
40
+ root.mkdir(exist_ok=True)
41
+ list_of_sections = model_json_schema(
42
+ ProjectDefinition, schema_generator=ProjectDefinitionGenerateJsonSchema
43
+ )["result"]
44
+ for section in list_of_sections:
45
+ _render_definition_description(root, section)
46
+ return
47
+
48
+
49
+ def _render_definition_description(root: SecurePath, section: Dict[str, Any]) -> None:
50
+ env = get_template_environment()
51
+
52
+ # RST files are presumed to be standalone pages in the docs with a matching item in the left nav.
53
+ # Included files, which these are, need to use the .txt extension.
54
+ file_path = root / f"definition_{section['name']}.txt"
55
+ log.info("Creating %s", file_path)
56
+ template = env.get_template(DEFINITION_DESCRIPTION)
57
+ with file_path.open("w+") as fh:
58
+ fh.write(template.render(section))
@@ -0,0 +1,227 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import asdict, dataclass
18
+ from typing import Any, Dict, List, Tuple
19
+
20
+ from pydantic.json_schema import GenerateJsonSchema
21
+
22
+
23
+ @dataclass
24
+ class ProjectDefinitionProperty:
25
+ """
26
+ Class for storing data of properties to be used in project definition generators.
27
+ """
28
+
29
+ path: str
30
+ title: str
31
+ indents: int
32
+ item_index: int
33
+ required: bool
34
+ name: str
35
+ description: str
36
+ add_types: bool
37
+ types: str
38
+
39
+
40
+ class ProjectDefinitionGenerateJsonSchema(GenerateJsonSchema):
41
+ def __init__(self, by_alias: bool = False, ref_template: str = ""):
42
+ super().__init__(by_alias, "{model}")
43
+ self._remapped_definitions: Dict[str, Any] = {}
44
+
45
+ def generate(self, schema, mode="validation"):
46
+ """
47
+ Transforms the generated json from the model to a list of project definition sections with its properties.
48
+ For example:
49
+ {
50
+ "result": [
51
+ {
52
+ "title": "Native app definitions for the project",
53
+ "name": "native_app",
54
+ "properties": [
55
+ {
56
+ "path": "Version of the project definition schema, which is currently 1",
57
+ "title": "Title of field A",
58
+ "indents": 0,
59
+ "item_index": 0,
60
+ "required": True,
61
+ "name": "definition_version",
62
+ "add_types": True,
63
+ "types": "string | integer",
64
+ },
65
+ {
66
+ "path": "native_app.name",
67
+ "title": "Project identifier",
68
+ "indents": 1,
69
+ "item_index": 0,
70
+ "required": True,
71
+ "name": "name",
72
+ "add_types": True,
73
+ "types": "string",
74
+ }
75
+ ]
76
+ }
77
+ ]
78
+ }
79
+ """
80
+ json_schema = super().generate(schema, mode=mode)
81
+ self._remapped_definitions = json_schema["$defs"]
82
+ return {"result": self._get_definition_sections(json_schema)}
83
+
84
+ def _get_definition_sections(
85
+ self, current_definition: Dict[str, Any]
86
+ ) -> List[Dict[str, Any]]:
87
+ required_properties: List[Dict[str, Any]] = []
88
+ sections: List[Dict[str, Any]] = []
89
+
90
+ for property_name, property_model in current_definition["properties"].items():
91
+ is_required = (
92
+ "required" in current_definition
93
+ and property_name in current_definition["required"]
94
+ )
95
+ children_properties = self._get_children_properties(
96
+ property_model, property_name
97
+ )
98
+
99
+ new_property = ProjectDefinitionProperty(
100
+ path=property_name,
101
+ title=property_model.get("title", ""),
102
+ description=property_model.get("description", ""),
103
+ indents=0,
104
+ item_index=0,
105
+ required=is_required,
106
+ name=property_name,
107
+ add_types=len(children_properties) == 0,
108
+ types=" | ".join(self._get_property_types(property_model)),
109
+ )
110
+ properties = [asdict(new_property)] + children_properties
111
+
112
+ if is_required:
113
+ required_properties.extend(properties)
114
+ else:
115
+ sections.append(
116
+ {
117
+ "properties": properties,
118
+ "title": property_model["title"],
119
+ "name": property_name,
120
+ }
121
+ )
122
+
123
+ for section in sections:
124
+ section["properties"] = required_properties + section["properties"]
125
+
126
+ return sections
127
+
128
+ def _get_section_properties(
129
+ self,
130
+ current_definition: Dict[str, Any],
131
+ current_path: str = "",
132
+ depth: int = 0,
133
+ is_array_item: bool = False,
134
+ ) -> List[Dict[str, Any]]:
135
+ required_properties: List[Dict[str, Any]] = []
136
+ optional_properties: List[Dict[str, Any]] = []
137
+ item_index = 0
138
+
139
+ for property_name, property_model in current_definition["properties"].items():
140
+ item_index += 1 if is_array_item else 0
141
+ is_required = (
142
+ "required" in current_definition
143
+ and property_name in current_definition["required"]
144
+ )
145
+ new_current_path = (
146
+ property_name
147
+ if current_path == ""
148
+ else current_path + "." + property_name
149
+ )
150
+ children_properties = self._get_children_properties(
151
+ property_model, new_current_path, depth
152
+ )
153
+ new_property = ProjectDefinitionProperty(
154
+ path=new_current_path,
155
+ title=property_model.get("title", ""),
156
+ description=property_model.get("description", ""),
157
+ indents=depth,
158
+ item_index=item_index,
159
+ required=is_required,
160
+ name=property_name,
161
+ add_types=len(children_properties) == 0,
162
+ types=" | ".join(self._get_property_types(property_model)),
163
+ )
164
+ properties = [asdict(new_property)] + children_properties
165
+ if is_required:
166
+ required_properties.extend(properties)
167
+ else:
168
+ optional_properties.extend(properties)
169
+ return required_properties + optional_properties
170
+
171
+ def _get_children_properties(
172
+ self,
173
+ property_model: Dict[str, Any],
174
+ current_path: str,
175
+ depth: int = 0,
176
+ ) -> List[Dict[str, Any]]:
177
+ child_properties: List[Dict[str, Any]] = []
178
+ references: List[Tuple[str, bool]] = self._get_property_references(
179
+ property_model
180
+ )
181
+ for reference, is_array_item in references:
182
+ child_properties.extend(
183
+ self._get_section_properties(
184
+ self._remapped_definitions[reference],
185
+ current_path,
186
+ depth + 1,
187
+ is_array_item,
188
+ )
189
+ )
190
+
191
+ return child_properties
192
+
193
+ def _get_property_references(
194
+ self,
195
+ model_with_type: Dict[str, Any],
196
+ is_array_item: bool = False,
197
+ ) -> list[tuple[str, bool]]:
198
+ result: List[Tuple[str, bool]] = []
199
+
200
+ if "$ref" in model_with_type:
201
+ return [(model_with_type["$ref"], is_array_item)]
202
+
203
+ if "type" in model_with_type and model_with_type["type"] == "array":
204
+ result.extend(self._get_property_references(model_with_type["items"], True))
205
+
206
+ if "anyOf" in model_with_type:
207
+ for property_type in model_with_type["anyOf"]:
208
+ result.extend(
209
+ self._get_property_references(property_type, is_array_item)
210
+ )
211
+ return result
212
+
213
+ def _get_property_types(self, model_with_type: Dict[str, Any]) -> list[str]:
214
+ types_result: List[str] = []
215
+ if "type" in model_with_type:
216
+ if model_with_type["type"] == "array":
217
+ items_types = self._get_property_types(model_with_type["items"])
218
+ if len(items_types) > 0:
219
+ types_result.append(f"array[{' | '.join(items_types)}]")
220
+
221
+ elif model_with_type["type"] != "null":
222
+ types_result.append(model_with_type["type"])
223
+ elif "anyOf" in model_with_type:
224
+ for property_type in model_with_type["anyOf"]:
225
+ types = self._get_property_types(property_type)
226
+ types_result.extend(types)
227
+ return types_result
@@ -0,0 +1,23 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from pathlib import Path
16
+
17
+ from jinja2 import Environment, FileSystemLoader
18
+
19
+ _TEMPLATE_PATH = Path(__file__).parent / "templates"
20
+
21
+
22
+ def get_template_environment() -> Environment:
23
+ return Environment(loader=FileSystemLoader(_TEMPLATE_PATH))
@@ -0,0 +1,38 @@
1
+ {{ title }}
2
+
3
+ Project definition structure
4
+ ===============================================================================
5
+
6
+ .. code-block::
7
+
8
+ {% for property in properties %}
9
+ {{ " "*(property["indents"] + 1) + ("- " if property["item_index"] == 1 else "") + (" " if property["item_index"] > 1 else "") + property["name"] }}: {% if property["add_types"] %}<{{ property["types"] }}>{% endif %}
10
+ {%- endfor %}
11
+
12
+
13
+ Project definition properties
14
+ ===============================================================================
15
+ {%- if properties %}
16
+ The following table describes the project definition properties.
17
+
18
+ .. list-table:: Project definition properties
19
+ :widths: 30 70
20
+ :header-rows: 1
21
+
22
+ * - Property
23
+ - Definition
24
+ {% for property in properties %}
25
+ * - **{{property["path"]}}**
26
+
27
+ *{{"Required" if property["required"] else "Optional"}}*{{ (", *"+property["types"]+"*") if property["types"]}}
28
+
29
+ - {{property["title"]}}
30
+ {% if property["description"] %}
31
+ {{ property["description"] }}
32
+ {% else %}{% endif -%}
33
+ {%+ endfor %}
34
+ {% else %}
35
+
36
+ None
37
+
38
+ {% endif -%}
@@ -11,6 +11,7 @@ Syntax
11
11
  {%- endfor %}
12
12
  {%- for param in options if not param.hidden %}
13
13
  {{ (param.opts | get_main_option) }}{% if not param.is_flag %} <{{ param.name }}>{% endif %}
14
+ {%- if param.type.name == "boolean" and param.secondary_opts|length > 0 %} {{ "/ " }}{{ param.secondary_opts[0] }}{% endif %}
14
15
  {%- endfor %}
15
16
 
16
17
  Arguments
@@ -38,7 +39,11 @@ Options
38
39
 
39
40
  {%- if options %}
40
41
  {% for param in options if not param.hidden %}
41
- :samp:`{% for p in param.opts %}{{ p }}{{ ", " if not loop.last }}{% endfor %}
42
+ :samp:`{% if param.type.name == "boolean" %}
43
+ {%- for p in param.opts %}{{ p }}{{ ", " if not loop.last }}{% endfor %}{% if param.secondary_opts|length > 0 %} {{ "/ " }}{{ param.secondary_opts[0] }}{% endif %}
44
+ {%- else %}
45
+ {%- for p in param.opts %}{{ p }}{{ ", " if not loop.last }}{% endfor %}
46
+ {%- endif %}
42
47
  {%- if not param.is_flag %}
43
48
  {%- if param.type.name != "choice" %}{{ ' {' }}{% else %} {% endif %}{{ param.make_metavar() }}{% if param.type.name != "choice" %}{{ '}' }}
44
49
  {%- endif %}