1313import yaml
1414from pydantic import BaseModel , Field , Tag , TypeAdapter , ValidationError
1515from rich .console import Console
16+ from rich .text import Text
1617from yamlcore import CoreLoader # type: ignore
1718
1819from overture .schema .core import OvertureFeature
19- from overture .schema .core .discovery import ModelKey , discover_models
20+ from overture .schema .system .discovery import ModelKey , discover_models , tags_by_key
2021from overture .schema .system .feature import Feature
2122from overture .schema .system .json_schema import json_schema
2223
23- from .docstrings import get_model_docstring , get_theme_module_docstring
2424from .error_formatting import (
2525 format_validation_error ,
2626 format_validation_errors_verbose ,
2727 group_errors_by_discriminator ,
2828 select_most_likely_errors ,
2929)
30- from .output import rewrap
3130from .type_analysis import StructuralTuple , get_item_index , introspect_union
3231from .types import ErrorLocation , ModelDict , UnionType
3332
@@ -207,34 +206,40 @@ def resolve_types(
207206 -------
208207 Model type suitable for passing to parse_feature
209208 """
210- # Determine effective namespace
211- effective_namespace = "overture" if use_overture_types else namespace
212-
213209 # Discover models once with the appropriate namespace
214- all_models = discover_models (namespace = effective_namespace )
210+ all_models = discover_models ()
215211
216212 # Filter models based on CLI options
217213 filtered_models : ModelDict = {}
218214
215+ if namespace and namespace != "overture" :
216+ filtered_models = {
217+ key : model_class
218+ for key , model_class in all_models .items ()
219+ if namespace in key .tags
220+ }
221+
219222 if use_overture_types :
220- filtered_models = all_models
223+ for key , model_class in all_models .items ():
224+ if tags_by_key (key .tags , "overture:theme" ):
225+ filtered_models [key ] = model_class
221226
222227 elif theme_names and not type_names :
223228 # Theme-only mode: all types in specified themes
224229 for key , model_class in all_models .items ():
225- if key .theme in theme_names :
230+ if next ( iter ( tags_by_key ( key .tags , "overture: theme" )), None ) in theme_names :
226231 filtered_models [key ] = model_class
227232
228233 elif type_names and not theme_names :
229234 # Type-only mode: find matching types across all themes
230235 for key , model_class in all_models .items ():
231- if key .type in type_names :
236+ if key .name in type_names and tags_by_key ( key . tags , "overture:theme" ) :
232237 filtered_models [key ] = model_class
233238
234239 elif type_names and theme_names :
235240 # Both specified: find matching types within specified themes
236241 for key , model_class in all_models .items ():
237- if key .theme in theme_names and key .type in type_names :
242+ if key .name in type_names and next ( iter ( tags_by_key ( key .tags , "overture:theme" )), None ) in theme_names :
238243 filtered_models [key ] = model_class
239244
240245 else :
@@ -767,49 +772,28 @@ def json_schema_command(
767772 raise click .UsageError (str (e )) from e
768773
769774
770- def dump_namespace (
771- theme_types : dict [str | None , list [tuple [ModelKey , type [BaseModel ]]]],
772- ) -> None :
773- """Print all themes and types for a namespace.
774-
775- Displays themes in alphabetical order with their types and docstrings.
776- Each type includes its model class name and description.
777-
778- Args
779- ----
780- theme_types : dict[str | None, list[tuple[ModelKey, type[BaseModel]]]]
781- Dict mapping theme name to list of (ModelKey, model_class) tuples
782- """
783- for theme in sorted (theme_types .keys (), key = lambda x : (x is None , x )):
784- if theme :
785- stdout .print (
786- f"[bold green underline]{ theme .upper ()} [/bold green underline]"
787- )
788-
789- theme_docstring = get_theme_module_docstring (theme )
790- if theme_docstring :
791- stdout .print (
792- rewrap (theme_docstring , stdout , padding_right = 4 ), style = "dim"
793- )
794-
795- stdout .print ()
796-
797- # Add types to the tree
798- sorted_types = sorted (theme_types [theme ], key = lambda x : x [0 ].type )
799- for key , model_class in sorted_types :
800- stdout .print (
801- f" [bright_black]→[/bright_black] [bold cyan]{ key .type } [/bold cyan] [dim magenta]({ key .class_name } )[/dim magenta]"
802- )
803- docstring = get_model_docstring (model_class )
804- if docstring :
805- stdout .print (
806- rewrap (docstring , stdout , indent = 4 , padding_right = 12 ), style = "dim"
807- )
808- stdout .print ()
809-
810-
811775@cli .command ("list-types" )
812- def list_types () -> None :
776+ @click .option (
777+ "--tag" ,
778+ "tags" ,
779+ multiple = True ,
780+ help = "Filter types by tag (e.g., overture:theme=addresses)" ,
781+ )
782+ @click .option (
783+ "--exclude-tag" ,
784+ "excluded_tags" ,
785+ multiple = True ,
786+ help = "Filter types by tag (e.g., overture:theme=base)" ,
787+ )
788+ @click .option (
789+ "--group-by" ,
790+ help = "Group types by tag prefix (e.g., 'overture:theme')" ,
791+ )
792+ def list_types (
793+ tags : tuple [str , ...],
794+ excluded_tags : tuple [str , ...],
795+ group_by : str | None
796+ ) -> None :
813797 r"""List all available types grouped by theme with descriptions.
814798
815799 Displays all registered Overture Maps types organized by theme,
@@ -822,35 +806,51 @@ def list_types() -> None:
822806 """
823807 try :
824808 models = discover_models ()
809+ filters = []
810+
811+ if tags :
812+ filters .append (lambda key : all (tag in key .tags for tag in tags ))
813+ if excluded_tags :
814+ filters .append (lambda key : not any (tag in key .tags for tag in excluded_tags ))
815+
816+ if filters :
817+ models = {
818+ key : model
819+ for key , model in models .items ()
820+ if all (f (key ) for f in filters )
821+ }
822+
823+ if group_by :
824+ grouped_models : dict [str , set [ModelKey ]] = {}
825+
826+ for key , model_class in models .items ():
827+ if groups := tags_by_key (key .tags , group_by ):
828+ for group in groups :
829+ grouped_models .setdefault (group , set ()).add (key )
830+
831+ padding = max ((len (key .name ) for keys in grouped_models .values () for key in keys ), default = 0 ) + 2
832+
833+ for group , keys in sorted (grouped_models .items ()):
834+ stdout .print (f"[green bold]{ group_by } ={ group } ({ len (keys )} )[/green bold]" )
835+ for key in sorted (keys , key = lambda k : k .name ):
836+ model = Text ()
837+ model .append ("→ " , style = "bright_black" )
838+ model .append (key .name , style = "bold cyan" )
839+ model .pad_right (max (1 , padding - len (key .name )))
840+ model .append_text (Text ().append (" " .join (sorted (key .tags ))))
841+ stdout .print (model )
842+ stdout .print ()
843+
844+ else :
845+ padding = max ((len (key .name ) for key in models .keys ()), default = 0 ) + 2
846+
847+ for key in sorted (models .keys (), key = lambda k : k .name ):
848+ model = Text ()
849+ model .append (key .name , style = "bold cyan" )
850+ model .pad_right (max (1 , padding - len (key .name )))
851+ model .append_text (Text ().append (" " .join (sorted (key .tags ))))
852+ stdout .print (model )
825853
826- # Group models by namespace and theme
827- namespaces : dict [
828- str , dict [str | None , list [tuple [ModelKey , type [BaseModel ]]]]
829- ] = {}
830- for key , model_class in models .items ():
831- if key .namespace not in namespaces :
832- namespaces [key .namespace ] = {}
833- if key .theme not in namespaces [key .namespace ]:
834- namespaces [key .namespace ][key .theme ] = []
835-
836- namespaces [key .namespace ][key .theme ].append ((key , model_class ))
837-
838- # display Overture themes first
839- if "overture" in namespaces :
840- stdout .print ("[bold red]OVERTURE THEMES[/bold red]" , justify = "center" )
841- stdout .print ()
842-
843- dump_namespace (namespaces ["overture" ])
844-
845- stdout .print ("[bold red]ADDITIONAL TYPES[/bold red]" , justify = "center" )
846- stdout .print ()
847-
848- for namespace in sorted (namespaces .keys ()):
849- if namespace == "overture" :
850- continue
851-
852- stdout .print (f"[bold blue]{ namespace .upper ()} [/bold blue]" )
853- dump_namespace (namespaces [namespace ])
854854
855855 except Exception as e :
856856 click .echo (f"Error listing types: { e } " , err = True )
0 commit comments