Skip to content

Commit 5b7838b

Browse files
shalabhcclaude
authored andcommitted
Serdes CLI Tool (#25870)
## Summary & Motivation Adds a `serdes` subcommand to `dagster-dev` for inspecting registered serdes types. This is useful for understanding the full shape of the serdes type registry for a given package — including objects, enums, unions, and builtin types — without having to manually trace through code. The CLI supports three commands: - **`list`** — lists all registered types, with optional `--detailed`, `--json`, or `--html` output - **`show <typename>`** — shows detailed info for a specific type - **`tree <typename>`** — shows a type and all types it transitively references, with optional \`--html\` output The `--html` flag generates a self-contained HTML page with a table of contents, cross-linked type references, hover tooltips showing full type strings, and collapsible "Referenced by" backlink sections for each type. Type discovery works by scanning a package's source files for `@whitelist_for_serdes` usage and importing those modules to populate the whitelist map, then introspecting the registered serializers to extract field types, base classes, field mappings, old fields, and other compatibility metadata. This is heavily claude-coded. ## Test Plan Manual testing. Attaching screenshots. `just serdes dagster list --html` ![image.png](https://app.graphite.com/user-attachments/assets/92de4477-34ce-4c16-9d37-b1391ec54a7d.png) Click into an object: ![image.png](https://app.graphite.com/user-attachments/assets/84bf10a3-0d43-435a-a459-a367ff62801f.png) Enums and unions listed separately: ![image.png](https://app.graphite.com/user-attachments/assets/b86adb1d-5c6b-4646-8efc-a5340e46e30b.png) Union details: ![image.png](https://app.graphite.com/user-attachments/assets/2f7e1d58-ae41-487a-8261-f6595986a31c.png) Console inspection: ``` > just serdes dagster list | grep Asset AllAssetCheckSelection AndAssetSelection AssetBackfillData AssetCheckEvaluation AssetCheckEvaluationPlanned ... ``` Can also target a specific object: ``` > just serdes dagster tree AssetDetails AssetDetails Storage Name: AssetDetails Type: dagster._core.assets.AssetDetails Serializer: NamedTupleSerializer Fields (1): - last_wipe_timestamp: float | None ``` --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Internal-RevId: 10dc39a0c566abb8db3ac95747906fbf110db483
1 parent 4987b00 commit 5b7838b

8 files changed

Lines changed: 1093 additions & 0 deletions

File tree

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
"""Inspect registered serdes types."""
2+
3+
import tempfile
4+
import webbrowser
5+
6+
import click
7+
from dagster_shared.serdes.serdes import _WHITELIST_MAP
8+
9+
from automation.serdes.format_html import format_html
10+
from automation.serdes.format_json import format_json
11+
from automation.serdes.format_text import format_detail, format_details, format_simple_list
12+
from automation.serdes.registry import list_descendants, scan_registry
13+
from automation.serdes.scanner import discover_and_import_serdes_modules
14+
15+
16+
@click.group(name="serdes")
17+
@click.option(
18+
"--scan",
19+
multiple=True,
20+
metavar="PACKAGE",
21+
help="Scan and import serdes types from package (can be specified multiple times)",
22+
)
23+
@click.option("--verbose", "-v", is_flag=True, help="Show verbose output during scanning")
24+
@click.pass_context
25+
def serdes(ctx, scan, verbose):
26+
"""Inspect registered serdes types."""
27+
ctx.ensure_object(dict)
28+
ctx.obj["scan"] = scan
29+
ctx.obj["verbose"] = verbose
30+
31+
32+
@serdes.command(name="list")
33+
@click.option("--detailed", is_flag=True, help="Show detailed information")
34+
@click.option("--json", "output_json", is_flag=True, help="Output as JSON")
35+
@click.option("--html", is_flag=True, help="Output as HTML")
36+
@click.pass_context
37+
def list_types(ctx, detailed, output_json, html):
38+
"""List all registered types.
39+
40+
Examples:
41+
dagster-dev serdes --scan dagster list
42+
dagster-dev serdes --scan dagster list --detailed
43+
dagster-dev serdes --scan dagster list --json
44+
"""
45+
scan_packages = ctx.obj["scan"]
46+
verbose = ctx.obj["verbose"]
47+
48+
for package_name in scan_packages:
49+
discover_and_import_serdes_modules(package_name, verbose=verbose)
50+
51+
registry = scan_registry(_WHITELIST_MAP)
52+
53+
if output_json:
54+
click.echo(format_json(registry))
55+
elif html:
56+
html_content = format_html(registry)
57+
with tempfile.NamedTemporaryFile(
58+
mode="w", suffix=".html", delete=False, encoding="utf-8"
59+
) as f:
60+
f.write(html_content)
61+
html_path = f.name
62+
click.echo(f"Opening {html_path} in browser...", err=True)
63+
webbrowser.open(f"file://{html_path}")
64+
elif detailed:
65+
all_items = (
66+
list(registry.types.values())
67+
+ list(registry.enums.values())
68+
+ list(registry.unions.values())
69+
)
70+
click.echo(format_details(all_items))
71+
else:
72+
all_items = (
73+
list(registry.types.values())
74+
+ list(registry.enums.values())
75+
+ list(registry.unions.values())
76+
)
77+
click.echo(format_simple_list(all_items))
78+
79+
80+
@serdes.command()
81+
@click.argument("typename")
82+
@click.pass_context
83+
def show(ctx, typename):
84+
"""Show detailed info for a specific type.
85+
86+
Examples:
87+
dagster-dev serdes --scan dagster show AssetKey
88+
dagster-dev serdes --scan dagster show DagsterRunStatus
89+
"""
90+
scan_packages = ctx.obj["scan"]
91+
verbose = ctx.obj["verbose"]
92+
93+
for package_name in scan_packages:
94+
discover_and_import_serdes_modules(package_name, verbose=verbose)
95+
96+
registry = scan_registry(_WHITELIST_MAP)
97+
defn = registry.get(typename)
98+
if defn is None:
99+
raise click.ClickException(f"{typename} not found")
100+
click.echo(format_detail(defn))
101+
102+
103+
@serdes.command()
104+
@click.argument("typename")
105+
@click.option("--html", is_flag=True, help="Output as HTML")
106+
@click.pass_context
107+
def tree(ctx, typename, html):
108+
"""Show type and all types it references recursively.
109+
110+
Examples:
111+
dagster-dev serdes --scan dagster tree AssetKey
112+
dagster-dev serdes --scan dagster_cloud tree AgentHeartbeat
113+
"""
114+
scan_packages = ctx.obj["scan"]
115+
verbose = ctx.obj["verbose"]
116+
117+
for package_name in scan_packages:
118+
discover_and_import_serdes_modules(package_name, verbose=verbose)
119+
120+
registry = scan_registry(_WHITELIST_MAP)
121+
defn = registry.get(typename)
122+
if defn is None:
123+
raise click.ClickException(f"{typename} not found")
124+
125+
tree_registry = list_descendants(defn.typename, registry)
126+
127+
if html:
128+
html_content = format_html(tree_registry)
129+
with tempfile.NamedTemporaryFile(
130+
mode="w", suffix=".html", delete=False, encoding="utf-8"
131+
) as f:
132+
f.write(html_content)
133+
html_path = f.name
134+
click.echo(f"Opening {html_path} in browser...", err=True)
135+
webbrowser.open(f"file://{html_path}")
136+
else:
137+
all_items = (
138+
list(tree_registry.types.values())
139+
+ list(tree_registry.enums.values())
140+
+ list(tree_registry.unions.values())
141+
)
142+
click.echo(format_details(all_items))

python_modules/automation/automation/serdes/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)