66from typing import Any
77
88import click
9+ from pydantic import (
10+ BaseModel ,
11+ ConfigDict ,
12+ Field ,
13+ )
914
1015from planemo import __version__
1116from planemo .cli import (
1823SCHEMA_VERSION = "0.1"
1924
2025
26+ class PlanemoClickTypeMetadata (BaseModel ):
27+ model_config = ConfigDict (extra = "allow" )
28+
29+ name : str
30+
31+
32+ class PlanemoCliParamMetadata (BaseModel ):
33+ model_config = ConfigDict (extra = "allow" )
34+
35+ kind : str
36+ name : str | None
37+ opts : list [str ] = Field (default_factory = list )
38+ secondary_opts : list [str ] = Field (default_factory = list )
39+ help : str | None = None
40+ required : bool
41+ human_readable_name : str
42+ multiple : bool
43+ nargs : int
44+ type : PlanemoClickTypeMetadata
45+ default : Any = None
46+ is_flag : bool = False
47+ flag_value : Any = None
48+ envvar : Any = None
49+ hidden : bool = False
50+ prompt : str | bool | None = None
51+ planemo_config : dict [str , Any ] = Field (default_factory = dict )
52+
53+
54+ class PlanemoCommandMetadata (BaseModel ):
55+ model_config = ConfigDict (extra = "allow" )
56+
57+ name : str
58+ module : str
59+ help : str | None = None
60+ short_help : str | None = None
61+ usage : str
62+ internal : bool
63+ hidden : bool = False
64+ params : list [PlanemoCliParamMetadata ] = Field (default_factory = list )
65+
66+
67+ class PlanemoCliMetadata (BaseModel ):
68+ model_config = ConfigDict (extra = "allow" )
69+
70+ schema_version : str = SCHEMA_VERSION
71+ program : str = "planemo"
72+ planemo_version : str
73+ commands : list [PlanemoCommandMetadata ] = Field (default_factory = list )
74+ aliases : dict [str , str ] = Field (default_factory = dict )
75+
76+
2177def iter_command_names (include_internal : bool = False ) -> list [str ]:
2278 """Return command names included in public CLI metadata."""
2379 commands = list_cmds ()
@@ -26,38 +82,36 @@ def iter_command_names(include_internal: bool = False) -> list[str]:
2682 return commands
2783
2884
29- def load_command_metadata (command_name : str ) -> dict [ str , Any ] :
85+ def load_command_metadata (command_name : str ) -> PlanemoCommandMetadata :
3086 """Load metadata for one Planemo command."""
3187 return serialize_click_command (command_name , name_to_command (command_name ))
3288
3389
34- def load_planemo_metadata (include_internal : bool = False ) -> dict [ str , Any ] :
90+ def load_planemo_metadata (include_internal : bool = False ) -> PlanemoCliMetadata :
3591 """Load metadata for the Planemo command line interface."""
36- return {
37- "schema_version" : SCHEMA_VERSION ,
38- "program" : "planemo" ,
39- "planemo_version" : __version__ ,
40- "commands" : [load_command_metadata (command_name ) for command_name in iter_command_names (include_internal )],
41- "aliases" : dict (sorted (COMMAND_ALIASES .items ())),
42- }
92+ return PlanemoCliMetadata (
93+ planemo_version = __version__ ,
94+ commands = [load_command_metadata (command_name ) for command_name in iter_command_names (include_internal )],
95+ aliases = dict (sorted (COMMAND_ALIASES .items ())),
96+ )
4397
4498
45- def serialize_click_command (command_name : str , command : click .Command ) -> dict [ str , Any ] :
99+ def serialize_click_command (command_name : str , command : click .Command ) -> PlanemoCommandMetadata :
46100 """Serialize one Click command into JSON-compatible metadata."""
47101 context = click .Context (command , info_name = command_name )
48- return {
49- " name" : command_name ,
50- " module" : f"planemo.commands.cmd_{ command_name } " ,
51- " help" : command .help ,
52- " short_help" : command .short_help ,
53- " usage" : command .get_usage (context ).removeprefix ("Usage: " ),
54- " internal" : command_name in INTERNAL_COMMANDS ,
55- " hidden" : getattr (command , "hidden" , False ),
56- " params" : [serialize_click_param (param ) for param in command .params ],
57- }
58-
59-
60- def serialize_click_param (param : click .Parameter ) -> dict [ str , Any ] :
102+ return PlanemoCommandMetadata (
103+ name = command_name ,
104+ module = f"planemo.commands.cmd_{ command_name } " ,
105+ help = command .help ,
106+ short_help = command .short_help ,
107+ usage = command .get_usage (context ).removeprefix ("Usage: " ),
108+ internal = command_name in INTERNAL_COMMANDS ,
109+ hidden = getattr (command , "hidden" , False ),
110+ params = [serialize_click_param (param ) for param in command .params ],
111+ )
112+
113+
114+ def serialize_click_param (param : click .Parameter ) -> PlanemoCliParamMetadata :
61115 """Serialize one Click parameter into JSON-compatible metadata."""
62116 planemo_config = getattr (param , "planemo_config" , {})
63117 metadata = {
@@ -81,10 +135,10 @@ def serialize_click_param(param: click.Parameter) -> dict[str, Any]:
81135 }
82136 if isinstance (param , click .Option ):
83137 metadata ["is_bool_flag" ] = param .is_bool_flag
84- return metadata
138+ return PlanemoCliParamMetadata . model_validate ( metadata )
85139
86140
87- def serialize_click_type (param_type : click .ParamType ) -> dict [ str , Any ] :
141+ def serialize_click_type (param_type : click .ParamType ) -> PlanemoClickTypeMetadata :
88142 """Serialize Click parameter type details where Click exposes them."""
89143 metadata : dict [str , Any ] = {"name" : param_type .name }
90144 if isinstance (param_type , click .Choice ):
@@ -112,7 +166,7 @@ def serialize_click_type(param_type: click.ParamType) -> dict[str, Any]:
112166 for attr in ("exists" , "file_okay" , "dir_okay" , "writable" , "readable" , "resolve_path" , "allow_dash" ):
113167 if hasattr (param_type , attr ):
114168 metadata [attr ] = getattr (param_type , attr )
115- return _json_value (metadata )
169+ return PlanemoClickTypeMetadata . model_validate ( _json_value (metadata ) )
116170
117171
118172def _json_value (value : Any ) -> Any :
0 commit comments