33server and backend server to enable lookup of the URLs based on function name.
44"""
55
6+ import contextlib
67import importlib
78import inspect
9+ import io
810import pkgutil
911from argparse import ArgumentParser
1012from pathlib import Path
1719import murfey
1820
1921
22+ class PrettierDumper (yaml .Dumper ):
23+ """
24+ Custom YAML Dumper class that sets `indentless` to False. This generates a YAML
25+ file that is then compliant with Prettier's formatting style
26+ """
27+
28+ def increase_indent (self , flow = False , indentless = False ):
29+ # Force 'indentless=False' so list items align with Prettier
30+ return super (PrettierDumper , self ).increase_indent (flow , indentless = False )
31+
32+
33+ def prettier_str_representer (dumper , data ):
34+ """
35+ Helper function to format strings according to Prettier's standards:
36+ - No quoting unless it can be misinterpreted as another data type
37+ - When quoting, use double quotes unless string already contains double quotes
38+ """
39+
40+ def is_implicitly_resolved (value : str ) -> bool :
41+ for (
42+ first_char ,
43+ resolvers ,
44+ ) in yaml .resolver .Resolver .yaml_implicit_resolvers .items ():
45+ if first_char is None or (value and value [0 ] in first_char ):
46+ for resolver in resolvers :
47+ if len (resolver ) == 3 :
48+ _ , regexp , _ = resolver
49+ else :
50+ _ , regexp = resolver
51+ if regexp .match (value ):
52+ return True
53+ return False
54+
55+ # If no quoting is needed, use default plain style
56+ if not is_implicitly_resolved (data ):
57+ return dumper .represent_scalar ("tag:yaml.org,2002:str" , data )
58+
59+ # If the string already contains double quotes, fall back to single quotes
60+ if '"' in data and "'" not in data :
61+ return dumper .represent_scalar ("tag:yaml.org,2002:str" , data , style = "'" )
62+
63+ # Otherwise, prefer double quotes
64+ return dumper .represent_scalar ("tag:yaml.org,2002:str" , data , style = '"' )
65+
66+
67+ PrettierDumper .add_representer (str , prettier_str_representer )
68+
69+
2070def find_routers (name : str ) -> dict [str , APIRouter ]:
2171
2272 def _extract_routers_from_module (module : ModuleType ):
@@ -30,34 +80,36 @@ def _extract_routers_from_module(module: ModuleType):
3080
3181 routers = {}
3282
33- # Import the module or package
34- try :
35- root = importlib .import_module (name )
36- except ImportError :
37- raise ImportError (
38- f"Cannot import '{ name } '. Please ensure that you've installed all the "
39- "dependencies for the client, instrument server, and backend server "
40- "before running this command."
41- )
42-
43- # If it's a package, walk through submodules and extract routers from each
44- if hasattr (root , "__path__" ):
45- module_list = pkgutil .walk_packages (root .__path__ , prefix = name + "." )
46- for _ , module_name , _ in module_list :
47- try :
48- module = importlib .import_module (module_name )
49- except ImportError :
50- raise ImportError (
51- f"Cannot import '{ module_name } '. Please ensure that you've "
52- "installed all the dependencies for the client, instrument "
53- "server, and backend server before running this command."
54- )
55-
56- routers .update (_extract_routers_from_module (module ))
57-
58- # Extract directly from single module
59- else :
60- routers .update (_extract_routers_from_module (root ))
83+ # Silence output during import and only return messages if imports fail
84+ buffer = io .StringIO ()
85+ with contextlib .redirect_stdout (buffer ), contextlib .redirect_stderr (buffer ):
86+ # Import the module or package
87+ try :
88+ root = importlib .import_module (name )
89+ except Exception as e :
90+ captured_logs = buffer .getvalue ().strip ()
91+ message = f"Cannot import '{ name } ': { e } "
92+ if captured_logs :
93+ message += f"\n --- Captured output ---\n { captured_logs } "
94+ raise ImportError (message ) from e
95+
96+ # If it's a package, walk through submodules and extract routers from each
97+ if hasattr (root , "__path__" ):
98+ module_list = pkgutil .walk_packages (root .__path__ , prefix = name + "." )
99+ for _ , module_name , _ in module_list :
100+ try :
101+ module = importlib .import_module (module_name )
102+ except Exception as e :
103+ captured_logs = buffer .getvalue ().strip ()
104+ message = f"Cannot import '{ name } ': { e } "
105+ if captured_logs :
106+ message += f"\n --- Captured output ---\n { captured_logs } "
107+ raise ImportError (message ) from e
108+ routers .update (_extract_routers_from_module (module ))
109+
110+ # Extract directly from single module
111+ else :
112+ routers .update (_extract_routers_from_module (root ))
61113
62114 return routers
63115
@@ -138,7 +190,14 @@ def run():
138190 murfey_dir = Path (murfey .__path__ [0 ])
139191 manifest_file = murfey_dir / "util" / "route_manifest.yaml"
140192 with open (manifest_file , "w" ) as file :
141- yaml .dump (manifest , file , default_flow_style = False , sort_keys = False )
193+ yaml .dump (
194+ manifest ,
195+ file ,
196+ Dumper = PrettierDumper ,
197+ default_flow_style = False ,
198+ sort_keys = False ,
199+ indent = 2 ,
200+ )
142201 print (
143202 "Route manifest for instrument and backend servers saved to "
144203 f"{ str (manifest_file )!r} "
0 commit comments