1
1
import tempfile
2
2
import webbrowser
3
- from collections .abc import Mapping , Sequence , Set
4
- from dataclasses import dataclass
5
- from typing import Any , Optional , Union
3
+ from collections .abc import Iterator , Mapping , Sequence
4
+ from typing import Any , Union
6
5
7
6
import markdown
8
7
import yaml
8
+ from dagster ._utils .source_position import SourcePositionTree
9
+ from dagster ._utils .yaml_utils import parse_yaml_with_source_positions
10
+ from dagster_components .core .schema .metadata import get_required_scope
9
11
10
12
from dagster_dg .component import RemoteComponentType
11
13
12
14
REF_BASE = "#/$defs/"
13
- COMMENTED_MAPPING_TAG = "!commented_mapping"
14
-
15
-
16
- @dataclass (frozen = True )
17
- class CommentedObject :
18
- key : str
19
- value : Union [Sequence ["CommentedObject" ], Any ]
20
- comment : Optional [str ]
21
- top_level : bool = False
22
15
23
16
24
17
def _dereference_schema (
@@ -30,38 +23,22 @@ def _dereference_schema(
30
23
return subschema
31
24
32
25
33
- def _commented_object_for_subschema (
34
- name : str ,
35
- json_schema : Mapping [str , Any ],
36
- subschema : Mapping [str , Any ],
37
- available_scope : Optional [Set [str ]] = None ,
38
- ) -> Union [CommentedObject , Any ]:
39
- additional_scope = subschema .get ("dagster_available_scope" )
40
- available_scope = (available_scope or set ()) | set (additional_scope or [])
41
-
26
+ def _sample_value_for_subschema (
27
+ json_schema : Mapping [str , Any ], subschema : Mapping [str , Any ]
28
+ ) -> Any :
42
29
subschema = _dereference_schema (json_schema , subschema )
43
30
if "anyOf" in subschema :
44
31
# TODO: handle anyOf fields more gracefully, for now just choose first option
45
- return _commented_object_for_subschema (
46
- name , json_schema , subschema ["anyOf" ][0 ], available_scope = available_scope
47
- )
32
+ return _sample_value_for_subschema (json_schema , subschema ["anyOf" ][0 ])
48
33
49
34
objtype = subschema ["type" ]
50
35
if objtype == "object" :
51
- return CommentedObject (
52
- key = name ,
53
- value = {
54
- k : _commented_object_for_subschema (k , json_schema , v )
55
- for k , v in subschema .get ("properties" , {}).items ()
56
- },
57
- comment = f"Available scope: { available_scope } " if available_scope else None ,
58
- )
36
+ return {
37
+ k : _sample_value_for_subschema (json_schema , v )
38
+ for k , v in subschema .get ("properties" , {}).items ()
39
+ }
59
40
elif objtype == "array" :
60
- return [
61
- _commented_object_for_subschema (
62
- name , json_schema , subschema ["items" ], available_scope = available_scope
63
- )
64
- ]
41
+ return [_sample_value_for_subschema (json_schema , subschema ["items" ])]
65
42
elif objtype == "string" :
66
43
return "..."
67
44
elif objtype == "integer" :
@@ -83,44 +60,32 @@ def write_line_break(self) -> None:
83
60
super ().write_line_break ()
84
61
super ().write_line_break ()
85
62
86
- def _get_tag (self ) -> str :
87
- return getattr (self .event , "tag" , "" )
88
-
89
- def expect_node (self , root = False , sequence = False , mapping = False , simple_key = False ):
90
- # for commented mappings, emit comment above the value
91
- tag = self ._get_tag ()
92
- if tag .startswith (COMMENTED_MAPPING_TAG ):
93
- self .write_indicator (f"# { tag [len (COMMENTED_MAPPING_TAG ) + 1 :]} " , True )
94
- self .write_line_break ()
95
-
96
- return super ().expect_node (root , sequence , mapping , simple_key )
97
-
98
- def process_tag (self ):
99
- tag = self ._get_tag ()
100
- # ignore the mapping tag as it's handled specially
101
- if tag .startswith (COMMENTED_MAPPING_TAG ):
102
- return
103
- else :
104
- super ().process_tag ()
105
-
106
63
107
- def commented_object_representer (dumper : yaml .SafeDumper , obj : CommentedObject ) -> yaml .nodes .Node :
108
- mapping = obj .value if isinstance (obj .value , dict ) else {obj .key : obj .value }
109
-
110
- if obj .comment is not None :
111
- return dumper .represent_mapping (f"{ COMMENTED_MAPPING_TAG } |{ obj .comment } " , mapping )
112
- else :
113
- return dumper .represent_dict (mapping )
114
-
115
-
116
- ComponentDumper .add_representer (CommentedObject , commented_object_representer )
64
+ def _get_source_position_comments (
65
+ valpath : Sequence [Union [str , int ]], tree : SourcePositionTree , json_schema : Mapping [str , Any ]
66
+ ) -> Iterator [tuple [int , str ]]:
67
+ available_scope = get_required_scope (valpath [1 :], json_schema )
68
+ if available_scope :
69
+ yield (tree .position .start .line - 1 , f"Available scope: { available_scope } " )
70
+ for child_path , child_tree in tree .children .items ():
71
+ yield from _get_source_position_comments ([* valpath , child_path ], child_tree , json_schema )
117
72
118
73
119
74
def generate_sample_yaml (component_type : str , json_schema : Mapping [str , Any ]) -> str :
120
- params_obj = _commented_object_for_subschema ("params" , json_schema , json_schema )
121
- return yaml .dump (
122
- {"type" : component_type , "params" : params_obj }, Dumper = ComponentDumper , sort_keys = False
75
+ raw = yaml .dump (
76
+ {"type" : component_type , "params" : _sample_value_for_subschema (json_schema , json_schema )},
77
+ Dumper = ComponentDumper ,
78
+ sort_keys = False ,
123
79
)
80
+ parsed = parse_yaml_with_source_positions (raw )
81
+ comments = dict (_get_source_position_comments ([], parsed .source_position_tree , json_schema ))
82
+ commented_lines = []
83
+ for line_num , line in enumerate (raw .split ("\n " )):
84
+ if line_num in comments :
85
+ commented_lines .append (f"{ line } # { comments [line_num ]} " )
86
+ else :
87
+ commented_lines .append (line )
88
+ return "\n " .join (commented_lines )
124
89
125
90
126
91
def render_markdown_in_browser (markdown_content : str ) -> None :
0 commit comments