55import hashlib
66import importlib .resources as pkg_resources
77from abc import ABC , abstractmethod
8- from collections .abc import Callable
8+ from collections .abc import Callable , Iterable
99from functools import partial
1010from os import PathLike
1111from pathlib import Path
@@ -45,12 +45,6 @@ def _sha256(path: Path) -> str:
4545
4646
4747class ModelRegistry (ABC ):
48- @property
49- @abstractmethod
50- def path (self ) -> Path :
51- """The path to the registry's data."""
52- ...
53-
5448 @property
5549 @abstractmethod
5650 def files (self ) -> dict :
@@ -65,7 +59,7 @@ def files(self) -> dict:
6559 @abstractmethod
6660 def models (self ) -> dict :
6761 """
68- A map of model name to the model's input file set .
62+ A map of model name to the model's input files .
6963 """
7064 ...
7165
@@ -102,7 +96,11 @@ class LocalRegistry(ModelRegistry):
10296
10397 exclude : ClassVar = [".DS_Store" , "compare" ]
10498
105- def __init__ (self , path : str | PathLike , namefile_pattern : str = "mfsim.nam" ):
99+ def __init__ (
100+ self ,
101+ path : str | PathLike | Iterable [str | PathLike ],
102+ namefile_pattern : str = "mfsim.nam" ,
103+ ):
106104 """
107105 Create a registry from models under the given
108106 directory path.
@@ -112,12 +110,23 @@ def __init__(self, path: str | PathLike, namefile_pattern: str = "mfsim.nam"):
112110 are identified by the presence of a namefile
113111 matching `namefile_pattern`.
114112 """
115- self ._path = Path (path ).expanduser ().resolve ().absolute ()
113+ # check if path is iterable of str\pathlike
114+ if isinstance (path , Iterable ) and not isinstance (path , str ):
115+ path = [Path (p ).expanduser ().resolve ().absolute () for p in path ] # type: ignore
116+ missing = [p for p in path if not p .is_dir ()] # type: ignore
117+ if any (missing ):
118+ raise NotADirectoryError (
119+ f"Directory paths not found: { ', ' .join (missing )} " # type: ignore
120+ )
121+ self ._path = path
122+ else :
123+ path = Path (path ).expanduser ().resolve ().absolute ()
124+ if not path .is_dir ():
125+ raise NotADirectoryError (f"Directory path not found: { path } " )
126+ self ._path = [path ]
116127 self ._files : dict [str , dict [str , str | None ]] = {}
117128 self ._models : dict [str , list [str ]] = {}
118129 self ._examples : dict [str , list [str ]] = {}
119- if not self ._path .is_dir ():
120- raise NotADirectoryError (f"Path { self ._path } is not a directory." )
121130 self .namefile_pattern = namefile_pattern
122131 self .index ()
123132
@@ -131,25 +140,28 @@ def index(self):
131140 self ._files = {}
132141 self ._models = {}
133142 self ._examples = {}
134- model_paths = get_model_paths (self ._path , namefile = self .namefile_pattern )
135- for model_path in model_paths :
136- model_path = model_path .expanduser ().resolve ().absolute ()
137- rel_path = model_path .relative_to (self ._path )
138- model_name = "/" .join (rel_path .parts )
139- self ._models [model_name ] = []
140- if len (rel_path .parts ) > 1 :
141- name = rel_path .parts [0 ]
142- if name not in self ._examples :
143- self ._examples [name ] = []
144- self ._examples [name ].append (model_name )
145- for p in model_path .rglob ("*" ):
146- if not p .is_file () or any (e in p .name for e in LocalRegistry .exclude ):
147- continue
148- relpath = p .expanduser ().absolute ().relative_to (self ._path )
149- name = "/" .join (relpath .parts )
150- hash = _sha256 (p )
151- self ._files [name ] = {"hash" : hash }
152- self ._models [model_name ].append (name )
143+ for path in self ._path :
144+ model_paths = get_model_paths (path , namefile = self .namefile_pattern )
145+ for model_path in model_paths :
146+ model_path = model_path .expanduser ().resolve ().absolute ()
147+ rel_path = model_path .relative_to (path )
148+ model_name = "/" .join (rel_path .parts )
149+ self ._models [model_name ] = []
150+ if len (rel_path .parts ) > 1 :
151+ name = rel_path .parts [0 ]
152+ if name not in self ._examples :
153+ self ._examples [name ] = []
154+ self ._examples [name ].append (model_name )
155+ for p in model_path .rglob ("*" ):
156+ if not p .is_file () or any (
157+ e in p .name for e in LocalRegistry .exclude
158+ ):
159+ continue
160+ relpath = p .expanduser ().absolute ().relative_to (path )
161+ name = "/" .join (relpath .parts )
162+ hash = _sha256 (p )
163+ self ._files [name ] = {"hash" : hash , "path" : p , "relpath" : relpath }
164+ self ._models [model_name ].append (p )
153165
154166 def copy_to (
155167 self , workspace : str | PathLike , model_name : str , verbose : bool = False
@@ -159,7 +171,7 @@ def copy_to(
159171 The workspace will be created if it does not exist.
160172 """
161173
162- if not any (files := self .models .get (model_name , [])):
174+ if not any (file_paths := self .models .get (model_name , [])):
163175 return None
164176 # create the workspace if needed
165177 workspace = Path (workspace ).expanduser ().absolute ()
@@ -169,18 +181,18 @@ def copy_to(
169181 # copy the files. some might be in nested folders,
170182 # but the first is guaranteed not to be, so use it
171183 # to determine relative path in the new workspace.
172- base = Path (files [0 ]).parent
173- for file in [ Path ( self . _path ) / f for f in files ] :
184+ base = Path (file_paths [0 ]).parent
185+ for file_path in file_paths :
174186 if verbose :
175- print (f"Copying { file } to workspace" )
176- path = workspace / file .relative_to (base )
177- path .parent .mkdir (parents = True , exist_ok = True )
178- copy (file , workspace / file . relative_to ( base ) )
187+ print (f"Copying { file_path } to workspace" )
188+ dest = workspace / file_path .relative_to (base )
189+ dest .parent .mkdir (parents = True , exist_ok = True )
190+ copy (file_path , dest )
179191 return workspace
180192
181193 @property
182- def path (self ) -> Path :
183- return self ._path
194+ def path (self ) -> list [ Path ] :
195+ return self ._path # type: ignore
184196
185197 @property
186198 def files (self ) -> dict :
0 commit comments