2121 * TRACE (custom) for verbose details such as per-file rewrites and protoc args.
2222Run: python generate_proto.py -vv or with trace logs: python generate_proto.py -vvv
2323"""
24+
2425import logging
2526import re
2627import shutil
3031from pathlib import Path
3132from urllib .parse import urlparse
3233
33- VERSION = "v0.72.0-rc.2"
34+ VERSION = "v0.72.0-rc.2"
3435SOURCES = [
3536 {
3637 "name" : "hedera-protobufs" ,
3738 "url" : "https://github.com/hashgraph/hedera-protobufs" ,
3839 "version" : VERSION ,
3940 "strip_count" : 1 ,
40- "modules" : ("mirror" ,)
41+ "modules" : ("mirror" ,),
4142 },
4243 {
4344 "name" : "hiero-consensus-node" ,
4445 "url" : "https://github.com/hiero-ledger/hiero-consensus-node" ,
4546 "version" : VERSION ,
46- "strip_count" : 6 ,
47- "modules" : ("services" , "platform" , "fee" , "sdk" , "block" , "streams" , "blocks" )
48- }
47+ "strip_count" : 6 ,
48+ "modules" : ("services" , "platform" , "fee" , "sdk" , "block" , "streams" , "blocks" ),
49+ },
4950]
5051
51- OUTPUT_DIR = "src/hiero_sdk_python/hapi"
52- CACHE_DIR = ".protos"
52+ OUTPUT_DIR = "src/hiero_sdk_python/hapi"
53+ CACHE_DIR = ".protos"
5354
5455# Map common broken imports in mirror/platform proto
5556REPLACEMENTS = {
@@ -70,16 +71,19 @@ class Config:
7071 name : str
7172 url : str
7273 version : str
73- strip_count : int
74+ strip_count : int
7475 modules : tuple = field (default_factory = tuple )
7576
7677
7778def setup_logging (verbosity : int ) -> None :
7879 level = logging .WARNING
79- if verbosity == 1 : level = logging .INFO
80- elif verbosity == 2 : level = logging .DEBUG
81- elif verbosity >= 3 : level = logging .TRACE_LEVEL
82-
80+ if verbosity == 1 :
81+ level = logging .INFO
82+ elif verbosity == 2 :
83+ level = logging .DEBUG
84+ elif verbosity >= 3 :
85+ level = logging .TRACE_LEVEL
86+
8387 logging .basicConfig (level = level , format = "%(levelname)s: %(message)s" )
8488
8589
@@ -93,11 +97,11 @@ def download_protos(config: Config, cache_path: Path) -> None:
9397
9498 try :
9599 # URL scheme and host validated above
96- with urllib .request .urlopen (url , timeout = 30 ) as resp : # nosec B310
100+ with urllib .request .urlopen (url , timeout = 30 ) as resp : # nosec B310
97101 safe_extract_tar_stream (resp , config , cache_path )
98102 except Exception as e :
99- raise RuntimeError (f"Download failed for { config .name } : { e } " )
100-
103+ raise RuntimeError (f"Download failed for { config .name } : { e } " ) from e
104+
101105
102106def is_safe_tar_member (member : tarfile .TarInfo , base : Path ) -> bool :
103107 name = member .name
@@ -111,18 +115,19 @@ def is_safe_tar_member(member: tarfile.TarInfo, base: Path) -> bool:
111115 dest .relative_to (base .resolve ())
112116 except ValueError :
113117 return False
114-
115- return ( member .isdir () or member .isreg () )
118+
119+ return member .isdir () or member .isreg ()
116120
117121
118122def safe_extract_tar_stream (resp , config : Config , cache_path : Path ):
119123 with tarfile .open (fileobj = resp , mode = "r|gz" ) as tar :
120124 for member in tar :
121125 parts = Path (member .name ).parts
122126
123- if len (parts ) <= config .strip_count : continue
124- member .name = "/" .join (parts [config .strip_count :])
125-
127+ if len (parts ) <= config .strip_count :
128+ continue
129+ member .name = "/" .join (parts [config .strip_count :])
130+
126131 if not any (member .name .startswith (p ) for p in config .modules ):
127132 continue
128133
@@ -132,24 +137,23 @@ def safe_extract_tar_stream(resp, config: Config, cache_path: Path):
132137 if member .isdir ():
133138 (cache_path / member .name ).mkdir (parents = True , exist_ok = True )
134139 continue
135-
140+
136141 target = cache_path / member .name
137142 target .parent .mkdir (parents = True , exist_ok = True )
138143 with tar .extractfile (member ) as src , target .open ("wb" ) as dst :
139144 shutil .copyfileobj (src , dst )
140145
141146
142-
143147def patch_proto_imports (proto_root : Path ):
144148 logging .info ("Patching proto files for consistent import paths..." )
145149
146150 for proto_file in proto_root .rglob ("*.proto" ):
147151 content = proto_file .read_text (encoding = "utf-8" )
148152 new_content = content
149-
153+
150154 for broken , fixed in REPLACEMENTS .items ():
151155 new_content = new_content .replace (broken , fixed )
152-
156+
153157 if "platform" in proto_file .parts :
154158 new_content = re .sub (r'import "event/' , 'import "platform/event/' , new_content )
155159
@@ -160,6 +164,7 @@ def patch_proto_imports(proto_root: Path):
160164def run_protoc (proto_root : Path , output_root : Path ) -> None :
161165 import grpc_tools
162166 from grpc_tools import protoc
167+
163168 google_include = str (Path (grpc_tools .__file__ ).parent / "_proto" )
164169 all_protos = [p .as_posix () for p in proto_root .rglob ("*.proto" )]
165170
@@ -184,19 +189,23 @@ def fix_imports(output_root: Path):
184189 if py_file .name == "__init__.py" :
185190 continue
186191
187- py_file .write_text ("\n " .join (
188- process_file_lines (py_file .read_text (encoding = "utf-8" ).splitlines (), pattern , py_file .relative_to (output_root ).parents )
189- ) + "\n " , encoding = "utf-8" )
192+ py_file .write_text (
193+ "\n " .join (
194+ process_file_lines (
195+ py_file .read_text (encoding = "utf-8" ).splitlines (), pattern , py_file .relative_to (output_root ).parents
196+ )
197+ )
198+ + "\n " ,
199+ encoding = "utf-8" ,
200+ )
201+
190202
191203def process_file_lines (lines , pattern , parents ):
192204 """Process each line in a file and fix imports if needed."""
193205 depth = len (parents ) - 1
194206 dots = "." * (depth + 1 )
195- new_lines = []
196207
197- for line in lines :
198- new_lines .append (fix_line_import (line , pattern , dots ))
199- return new_lines
208+ return [fix_line_import (line , pattern , dots ) for line in lines ]
200209
201210
202211def fix_line_import (line , pattern , dots ):
@@ -205,7 +214,7 @@ def fix_line_import(line, pattern, dots):
205214 if not match :
206215 return line
207216
208- ptype , ppath , psuffix = match .group (' type' ), match .group (' path' ), match .group (' suffix' )
217+ ptype , ppath , psuffix = match .group (" type" ), match .group (" path" ), match .group (" suffix" )
209218 full_module = ppath + psuffix
210219
211220 if ptype == "from" :
@@ -217,13 +226,13 @@ def fix_line_import(line, pattern, dots):
217226 if " as " in line :
218227 alias = line .split (" as " , 1 )[1 ].strip ()
219228 return (
220- f"from { dots } { module_parts [0 ]} import { module_parts [1 ]} as { alias } "
221- if len (module_parts ) > 1
229+ f"from { dots } { module_parts [0 ]} import { module_parts [1 ]} as { alias } "
230+ if len (module_parts ) > 1
222231 else f"from { dots } import { module_parts [0 ]} as { alias } "
223232 )
224233 return (
225- f"from { dots } { module_parts [0 ]} import { module_parts [1 ]} "
226- if len (module_parts ) > 1
234+ f"from { dots } { module_parts [0 ]} import { module_parts [1 ]} "
235+ if len (module_parts ) > 1
227236 else f"from { dots } import { module_parts [0 ]} "
228237 )
229238
@@ -233,8 +242,11 @@ def main():
233242 cache_path = Path (CACHE_DIR )
234243 out_path = Path (OUTPUT_DIR )
235244
236- if cache_path .exists (): shutil .rmtree (cache_path )
237- if out_path .exists (): shutil .rmtree (out_path )
245+ if cache_path .exists ():
246+ shutil .rmtree (cache_path )
247+
248+ if out_path .exists ():
249+ shutil .rmtree (out_path )
238250
239251 cache_path .mkdir (parents = True )
240252 out_path .mkdir (parents = True )
@@ -244,18 +256,19 @@ def main():
244256 download_protos (src , cache_path )
245257
246258 patch_proto_imports (cache_path )
247-
259+
248260 logging .info ("Running protoc..." )
249261 run_protoc (cache_path , out_path )
250-
262+
251263 logging .info ("Fixing imports..." )
252264 fix_imports (out_path )
253-
265+
254266 for d in [out_path , * out_path .rglob ("*" )]:
255- if d .is_dir (): (d / "__init__.py" ).touch ()
267+ if d .is_dir ():
268+ (d / "__init__.py" ).touch ()
256269
257270 print (f"✅ Successfully merged and generated HAPI at { out_path } " )
258271
259272
260273if __name__ == "__main__" :
261- main ()
274+ main ()
0 commit comments