@@ -442,8 +442,8 @@ class DiscoveredModelRegistry:
442442 url : str
443443
444444
445- class ModelSource (BaseModel ):
446- """Model source repository configuration ."""
445+ class ModelSourceRepo (BaseModel ):
446+ """A single source model repository in the bootstrap file ."""
447447
448448 @dataclass
449449 class SyncResult :
@@ -462,14 +462,18 @@ class SyncStatus:
462462 cached_refs : list [str ]
463463 missing_refs : list [str ]
464464
465+ repo : str = Field (..., description = "Repository in format 'owner/name'" )
465466 name : str = Field (
466467 ..., description = "Name for model addressing (injected from key if not explicit)"
467468 )
468- repo : str = Field (..., description = "Repository in format 'owner/name'" )
469469 refs : list [str ] = Field (
470470 default_factory = list ,
471471 description = "Default refs to sync (branches, tags, or commit hashes)" ,
472472 )
473+ registry_path : str = Field (
474+ default = ".registry" ,
475+ description = "Path to registry directory in repository" ,
476+ )
473477
474478 @field_validator ("repo" )
475479 @classmethod
@@ -595,9 +599,9 @@ def sync(
595599 if not refs :
596600 if verbose :
597601 print (f"No refs configured for source '{ source_name } ', aborting" )
598- return ModelSource .SyncResult ()
602+ return ModelSourceRepo .SyncResult ()
599603
600- result = ModelSource .SyncResult ()
604+ result = ModelSourceRepo .SyncResult ()
601605
602606 for ref in refs :
603607 if not force and _DEFAULT_CACHE .has (source_name , ref ):
@@ -658,41 +662,92 @@ def list_synced_refs(self) -> list[str]:
658662 return [ref for source , ref in cached if source == self .name ]
659663
660664
661- class ModelSources (BaseModel ):
662- """Configuration for multiple model source repositories ."""
665+ class ModelSourceConfig (BaseModel ):
666+ """Model source configuration file structure ."""
663667
664- sources : dict [str , ModelSource ] = Field (
665- default_factory = dict , description = "Model source repositories "
668+ sources : dict [str , ModelSourceRepo ] = Field (
669+ ... , description = "Map of source names to source metadata "
666670 )
667671
668672 @classmethod
669673 def load (
670674 cls ,
671- path : str | PathLike | None = None ,
672- ) -> "ModelSources" :
673- """Load model source configurations from a TOML file."""
674- path = Path (path )
675- if not path .exists ():
676- return cls ()
677-
678- with path .open ("rb" ) as f :
679- data = tomli .load (f )
675+ bootstrap_path : str | PathLike | None = None ,
676+ user_config_path : str | PathLike | None = None ,
677+ ) -> "ModelSourceConfig" :
678+ """
679+ Load model source configuration.
680680
681- sources = {}
682- for name , config in data .get ("sources" , {}).items ():
683- sources [name ] = ModelSource (** config )
681+ Parameters
682+ ----------
683+ bootstrap_path : str | PathLike | None
684+ Path to bootstrap config file. If None, uses bundled default.
685+ If provided, ONLY this file is loaded (no user config overlay unless specified).
686+ user_config_path : str | PathLike | None
687+ Path to user config file to overlay on top of bootstrap.
688+ If None and bootstrap_path is None, attempts to load from default user config location.
684689
685- return cls (sources = sources )
690+ Returns
691+ -------
692+ ModelSourceConfig
693+ Loaded and merged configuration
694+ """
695+ # Load base config
696+ if bootstrap_path is not None :
697+ # Explicit bootstrap path - only load this file
698+ with Path (bootstrap_path ).open ("rb" ) as f :
699+ cfg = tomli .load (f )
700+ else :
701+ # Use bundled default
702+ with _DEFAULT_CONFIG_PATH .open ("rb" ) as f :
703+ cfg = tomli .load (f )
704+
705+ # If no explicit bootstrap path, try to load user config overlay
706+ if user_config_path is None :
707+ user_config_path = get_user_config_path ()
708+
709+ # Overlay user config if specified or found
710+ if user_config_path is not None :
711+ user_path = Path (user_config_path )
712+ if user_path .exists ():
713+ with user_path .open ("rb" ) as f :
714+ user_cfg = tomli .load (f )
715+ # Merge user config sources into base config
716+ if "sources" in user_cfg :
717+ if "sources" not in cfg :
718+ cfg ["sources" ] = {}
719+ cfg ["sources" ] = cfg ["sources" ] | user_cfg ["sources" ]
720+
721+ # inject source names if not explicitly provided
722+ for name , src in cfg .get ("sources" , {}).items ():
723+ if "name" not in src :
724+ src ["name" ] = name
725+
726+ return cls (** cfg )
686727
687728 @classmethod
688- def merge (cls , base : "ModelSources" , overlay : "ModelSources" ) -> "ModelSources" :
689- """Merge two source configurations. Overlay takes precedence."""
690- merged = dict (base .sources )
691- merged .update (overlay .sources )
692- return cls (sources = merged )
729+ def merge (cls , base : "ModelSourceConfig" , overlay : "ModelSourceConfig" ) -> "ModelSourceConfig" :
730+ """
731+ Merge two configurations, with overlay taking precedence.
732+
733+ Parameters
734+ ----------
735+ base : ModelSourceConfig
736+ Base configuration
737+ overlay : ModelSourceConfig
738+ Configuration to overlay on top of base
739+
740+ Returns
741+ -------
742+ ModelSourceConfig
743+ Merged configuration
744+ """
745+ merged_sources = base .sources .copy ()
746+ merged_sources .update (overlay .sources )
747+ return cls (sources = merged_sources )
693748
694749 @property
695- def status (self ) -> dict [str , ModelSource .SyncStatus ]:
750+ def status (self ) -> dict [str , ModelSourceRepo .SyncStatus ]:
696751 """
697752 Sync status for all configured model source repositories.
698753
@@ -717,7 +772,7 @@ def status(self) -> dict[str, ModelSource.SyncStatus]:
717772 else :
718773 missing .append (ref )
719774
720- status [name ] = ModelSource .SyncStatus (
775+ status [name ] = ModelSourceRepo .SyncStatus (
721776 repo = source .repo ,
722777 configured_refs = refs ,
723778 cached_refs = cached ,
@@ -728,10 +783,10 @@ def status(self) -> dict[str, ModelSource.SyncStatus]:
728783
729784 def sync (
730785 self ,
731- source : str | ModelSource | None = None ,
786+ source : str | ModelSourceRepo | None = None ,
732787 force : bool = False ,
733788 verbose : bool = False ,
734- ) -> dict [str , ModelSource .SyncResult ]:
789+ ) -> dict [str , ModelSourceRepo .SyncResult ]:
735790 """
736791 Synchronize registry files from model source(s).
737792
@@ -753,7 +808,7 @@ def sync(
753808 """
754809
755810 if source :
756- if isinstance (source , ModelSource ):
811+ if isinstance (source , ModelSourceRepo ):
757812 if source .name not in self .sources :
758813 raise ValueError (f"Source '{ source .name } ' not found in bootstrap" )
759814 sources = [source ]
@@ -1249,7 +1304,7 @@ def _try_best_effort_sync():
12491304
12501305 try :
12511306 # Try to sync default refs (don't be verbose, don't fail on errors)
1252- config = ModelSources .load ()
1307+ config = ModelSourceConfig .load ()
12531308 config .sync (verbose = False )
12541309 except Exception :
12551310 # Silently fail - user will get clear error when trying to use registry
0 commit comments