Skip to content

Commit f81a367

Browse files
committed
[components] Update docs commenting logic
1 parent d3411b7 commit f81a367

File tree

2 files changed

+38
-73
lines changed

2 files changed

+38
-73
lines changed

python_modules/libraries/dagster-dg/dagster_dg/docs.py

Lines changed: 35 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
import tempfile
22
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
65

76
import markdown
87
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
911

1012
from dagster_dg.component import RemoteComponentType
1113

1214
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
2215

2316

2417
def _dereference_schema(
@@ -30,38 +23,22 @@ def _dereference_schema(
3023
return subschema
3124

3225

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:
4229
subschema = _dereference_schema(json_schema, subschema)
4330
if "anyOf" in subschema:
4431
# 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])
4833

4934
objtype = subschema["type"]
5035
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+
}
5940
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"])]
6542
elif objtype == "string":
6643
return "..."
6744
elif objtype == "integer":
@@ -83,44 +60,32 @@ def write_line_break(self) -> None:
8360
super().write_line_break()
8461
super().write_line_break()
8562

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-
10663

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)
11772

11873

11974
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,
12379
)
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)
12489

12590

12691
def render_markdown_in_browser(markdown_content: str) -> None:

python_modules/libraries/dagster-dg/dagster_dg_tests/utils_tests/test_sample_yaml.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class SampleSubSchema(ComponentSchemaBaseModel):
1111

1212

1313
class SampleSchema(ComponentSchemaBaseModel):
14-
sub_scoped: Annotated[SampleSubSchema, ResolvableFieldInfo(additional_scope={"outer_scope"})]
14+
sub_scoped: Annotated[SampleSubSchema, ResolvableFieldInfo(required_scope={"outer_scope"})]
1515
sub_optional: SampleSubSchema
1616
sub_list: Sequence[SampleSubSchema]
1717

@@ -26,8 +26,8 @@ def test_generate_sample_yaml():
2626
2727
params:
2828
sub_scoped: # Available scope: {'outer_scope'}
29-
str_field: '...'
30-
int_field: 0
29+
str_field: '...' # Available scope: {'outer_scope'}
30+
int_field: 0 # Available scope: {'outer_scope'}
3131
sub_optional:
3232
str_field: '...'
3333
int_field: 0

0 commit comments

Comments
 (0)