66import fsspec .implementations .local
77import toml
88
9- from projspec .utils import AttrDict , IndentDumper , camel_to_snake , flatten
9+ from projspec .utils import (
10+ AttrDict ,
11+ IndentDumper ,
12+ PickleableTomlDecoder ,
13+ camel_to_snake ,
14+ flatten ,
15+ )
1016
1117logger = logging .getLogger ("projspec" )
12- registry = set ()
18+ registry = {}
1319default_excludes = {
1420 ".venv" , # venv, pipenv, uv
1521 ".pixi" ,
@@ -32,6 +38,9 @@ def __init__(
3238 ):
3339 if fs is None :
3440 fs , path = fsspec .url_to_fs (path , ** (storage_options or {}))
41+ else :
42+ storage_options = fs .storage_options
43+ self .storage_options = storage_options or {}
3544 self .fs = fs
3645 self .url = path
3746 self .specs = AttrDict ()
@@ -60,7 +69,8 @@ def resolve(
6069 """
6170 fullpath = "/" .join ([self .url , subpath ]) if subpath else self .url
6271 # sorting to ensure consistency
63- for cls in sorted (registry , key = str ):
72+ for name in sorted (registry ):
73+ cls = registry [name ]
6474 try :
6575 logger .debug ("resolving %s as %s" , fullpath , cls )
6676 name = cls .__name__
@@ -142,7 +152,7 @@ def pyproject(self):
142152 if "pyproject.toml" in self .basenames :
143153 try :
144154 with self .fs .open (self .basenames ["pyproject.toml" ], "rt" ) as f :
145- return toml .load (f )
155+ return toml .load (f , decoder = PickleableTomlDecoder () )
146156 except (OSError , ValueError , TypeError ):
147157 # debug/warn?
148158 pass
@@ -172,9 +182,27 @@ def __contains__(self, item) -> bool:
172182 item in _ for _ in self .children .values ()
173183 )
174184
175- def to_dict (self ) -> dict :
176- dic = AttrDict (specs = self .specs , children = self .children )
177- return dic .to_dict ()
185+ def to_dict (self , compact = True ) -> dict :
186+ dic = AttrDict (
187+ specs = self .specs ,
188+ children = self .children ,
189+ url = self .url ,
190+ storage_options = self .storage_options ,
191+ )
192+ dic ["klass" ] = "project"
193+ return dic .to_dict (compact = compact )
194+
195+ @staticmethod
196+ def from_dict (dic ):
197+ from projspec .utils import from_dict
198+
199+ proj = object .__new__ (Project )
200+ proj .specs = from_dict (dic ["specs" ], proj )
201+ proj .children = from_dict (dic ["children" ], proj )
202+ proj .url = dic ["url" ]
203+ proj .storage_options = dic ["storage_options" ]
204+ proj .fs , _ = fsspec .url_to_fs (proj .url , ** proj .storage_options )
205+ return proj
178206
179207
180208class ProjectSpec :
@@ -186,8 +214,8 @@ class ProjectSpec:
186214
187215 spec_doc = "" # URL to prose about this spec
188216
189- def __init__ (self , root : Project , subpath : str = "" ):
190- self .root = root
217+ def __init__ (self , proj : Project , subpath : str = "" ):
218+ self .proj = proj
191219 self .subpath = subpath # not used yet
192220 self ._contents = AttrDict ()
193221 self ._artifacts = AttrDict ()
@@ -198,9 +226,9 @@ def __init__(self, root: Project, subpath: str = ""):
198226 def path (self ) -> str :
199227 """Location of this project spec"""
200228 return (
201- self .root .url + "/" + self .subpath
229+ self .proj .url + "/" + self .subpath
202230 if self .subpath
203- else self .root .url
231+ else self .proj .url
204232 )
205233
206234 def match (self ) -> bool :
@@ -241,7 +269,7 @@ def clean(self) -> None:
241269
242270 @classmethod
243271 def __init_subclass__ (cls , ** kwargs ):
244- registry . add (cls )
272+ registry [ camel_to_snake (cls . __name__ )] = cls
245273
246274 def __repr__ (self ):
247275 import yaml
@@ -253,6 +281,20 @@ def __repr__(self):
253281 base += f"\n Artifacts:\n { yaml .dump (self .artifacts .to_dict (), Dumper = IndentDumper ).rstrip ()} \n "
254282 return base
255283
256- def to_dict (self ) -> dict :
257- dic = AttrDict (contents = self .contents , artifacts = self .artifacts )
258- return dic .to_dict ()
284+ def to_dict (self , compact = True ) -> dict :
285+ dic = AttrDict (
286+ _contents = self .contents ,
287+ _artifacts = self .artifacts ,
288+ subpath = self .subpath ,
289+ klass = ["projspec" , self .snake_name ()],
290+ )
291+ return dic .to_dict (compact = compact )
292+
293+ @classmethod
294+ def snake_name (cls ) -> str :
295+ """Convert a project name to snake-case"""
296+ return camel_to_snake (cls .__name__ )
297+
298+
299+ def get_projspec_class (name : str ) -> type :
300+ return registry [name ]
0 commit comments