@@ -90,33 +90,88 @@ def health():
9090
9191
9292BASE_DIR = pathlib .Path (__file__ ).resolve ().parent .parent
93- PYTC_CONFIG_ROOT = BASE_DIR / "pytorch_connectomics" / "configs"
94- PYTC_BUILD_FILE = (
95- BASE_DIR / "pytorch_connectomics" / "connectomics" / "model" / "build.py"
93+ PYTC_ROOT = BASE_DIR / "pytorch_connectomics"
94+ PYTC_CONFIG_ROOTS = (
95+ PYTC_ROOT / "tutorials" ,
96+ PYTC_ROOT / "configs" ,
9697)
98+ PYTC_CONFIG_SUFFIXES = (".yaml" , ".yml" )
99+
100+
101+ def _iter_existing_config_roots ():
102+ for root in PYTC_CONFIG_ROOTS :
103+ if root .exists () and root .is_dir ():
104+ yield root
97105
98106
99107def _list_pytc_configs () -> List [str ]:
100- if not PYTC_CONFIG_ROOT .exists ():
101- return []
102- return sorted (
103- [
104- str (path .relative_to (PYTC_CONFIG_ROOT )).replace ("\\ " , "/" )
105- for path in PYTC_CONFIG_ROOT .rglob ("*.yaml" )
106- ]
108+ configs = []
109+ for root in _iter_existing_config_roots ():
110+ for suffix in PYTC_CONFIG_SUFFIXES :
111+ for path in root .rglob (f"*{ suffix } " ):
112+ configs .append (str (path .relative_to (PYTC_ROOT )).replace ("\\ " , "/" ))
113+ return sorted (set (configs ))
114+
115+
116+ def _is_relative_to (path : pathlib .Path , root : pathlib .Path ) -> bool :
117+ try :
118+ path .relative_to (root )
119+ return True
120+ except ValueError :
121+ return False
122+
123+
124+ def _is_valid_config_path (path : pathlib .Path ) -> bool :
125+ if not path .is_file ():
126+ return False
127+ if path .suffix .lower () not in PYTC_CONFIG_SUFFIXES :
128+ return False
129+ if not _is_relative_to (path , PYTC_ROOT .resolve ()):
130+ return False
131+ return any (
132+ _is_relative_to (path , root .resolve ()) for root in _iter_existing_config_roots ()
107133 )
108134
109135
136+ def _resolve_requested_config (path : str ) -> Optional [pathlib .Path ]:
137+ if not path :
138+ return None
139+
140+ normalized = path .replace ("\\ " , "/" ).strip ()
141+ if not normalized or normalized .startswith ("/" ):
142+ return None
143+ if ".." in pathlib .PurePosixPath (normalized ).parts :
144+ return None
145+
146+ candidates = [(PYTC_ROOT / normalized ).resolve ()]
147+ for root in _iter_existing_config_roots ():
148+ candidates .append ((root / normalized ).resolve ())
149+
150+ for candidate in candidates :
151+ if _is_valid_config_path (candidate ):
152+ return candidate
153+ return None
154+
155+
110156def _read_model_architectures () -> List [str ]:
111- if not PYTC_BUILD_FILE .exists ():
112- return []
113- text = PYTC_BUILD_FILE .read_text (encoding = "utf-8" , errors = "ignore" )
114- match = re .search (r"MODEL_MAP\s*=\s*{(.*?)}" , text , re .S )
115- if not match :
116- return []
117- block = match .group (1 )
118- keys = re .findall (r"'([^']+)'\s*:" , block )
119- return sorted (set (keys ))
157+ # Prefer runtime registry from the installed connectomics package.
158+ try :
159+ from connectomics .models .arch import list_architectures
160+
161+ architectures = list_architectures ()
162+ if architectures :
163+ return sorted (set (architectures ))
164+ except Exception :
165+ pass
166+
167+ # Fallback: parse decorator registrations from source files.
168+ pattern = re .compile (r"""@register_architecture\(\s*['"]([^'"]+)['"]\s*\)""" )
169+ architectures = []
170+ arch_root = PYTC_ROOT / "connectomics" / "models" / "arch"
171+ for py_file in arch_root .rglob ("*.py" ):
172+ text = py_file .read_text (encoding = "utf-8" , errors = "ignore" )
173+ architectures .extend (pattern .findall (text ))
174+ return sorted (set (architectures ))
120175
121176
122177@app .get ("/pytc/configs" )
@@ -131,17 +186,12 @@ def list_pytc_configs():
131186def get_pytc_config (path : str ):
132187 if not path :
133188 raise HTTPException (status_code = 400 , detail = "Config path is required." )
134- if ".." in path or path .startswith ("/" ):
135- raise HTTPException (status_code = 400 , detail = "Invalid config path." )
136- requested = (PYTC_CONFIG_ROOT / path ).resolve ()
137- if not str (requested ).startswith (str (PYTC_CONFIG_ROOT .resolve ())):
138- raise HTTPException (status_code = 400 , detail = "Invalid config path." )
139- if not requested .is_file ():
189+ requested = _resolve_requested_config (path )
190+ if requested is None :
140191 raise HTTPException (status_code = 404 , detail = "Config not found." )
141- if requested .suffix .lower () != ".yaml" :
142- raise HTTPException (status_code = 400 , detail = "Config must be a YAML file." )
143192 content = requested .read_text (encoding = "utf-8" , errors = "ignore" )
144- return {"path" : path , "content" : content }
193+ canonical_path = str (requested .relative_to (PYTC_ROOT )).replace ("\\ " , "/" )
194+ return {"path" : canonical_path , "content" : content }
145195
146196
147197@app .get ("/pytc/architectures" )
0 commit comments