11#!/usr/bin/env python3
2- """
3- Generate Python protobuf modules from proto files.
4- Handles proto filenames with dots (e.g., shared.flow.proto) by organizing
5- them into a clean namespace structure.
6- """
72
8- import os
93import shutil
104import subprocess
115import sys
126from pathlib import Path
13- from typing import List , Tuple , Dict
7+ from typing import List , Tuple
8+
149
1510def get_proto_files (proto_dir : Path ) -> Tuple [List [Path ], List [Path ]]:
16- """Collect all proto files and their directories."""
1711 proto_files = []
1812 proto_dirs = set ()
1913
@@ -23,11 +17,10 @@ def get_proto_files(proto_dir: Path) -> Tuple[List[Path], List[Path]]:
2317
2418 return proto_files , sorted (list (proto_dirs ))
2519
20+
2621def generate_protos (proto_root : Path , output_dir : Path , proto_dirs : List [Path ]) -> None :
27- """Run protoc to generate Python modules."""
2822 proto_import_paths = [str (d ) for d in proto_dirs ]
2923
30- # Collect all proto files
3124 proto_file_paths = []
3225 for proto_dir_path in proto_dirs :
3326 proto_file_paths .extend (str (f ) for f in proto_dir_path .glob ("*.proto" ))
@@ -36,22 +29,18 @@ def generate_protos(proto_root: Path, output_dir: Path, proto_dirs: List[Path])
3629 print ("No proto files found!" , file = sys .stderr )
3730 sys .exit (1 )
3831
39- # Build protoc command
4032 cmd = [
4133 "python" , "-m" , "grpc_tools.protoc" ,
4234 ]
4335
44- # Add import paths
4536 for import_path in proto_import_paths :
4637 cmd .extend (["-I" , import_path ])
4738
48- # Add output paths
4939 cmd .extend ([
5040 f"--python_out={ output_dir } " ,
5141 f"--grpc_python_out={ output_dir } " ,
5242 ])
5343
54- # Add proto files
5544 cmd .extend (proto_file_paths )
5645
5746 print (f"\n 📍 Running protoc..." )
@@ -72,42 +61,28 @@ def generate_protos(proto_root: Path, output_dir: Path, proto_dirs: List[Path])
7261
7362 print (f"✓ Proto generation successful" )
7463
75- def organize_generated_files (output_dir : Path , proto_dirs : List [Path ]) -> None :
76- """
77- Organize generated files into proper namespace structure.
78- Handles dot-notation in filenames.
79- """
80- # Map: protocol_name -> output_dir
64+
65+ def organize_generated_files (output_dir : Path ) -> None :
8166 protocol_map = {
8267 "shared" : output_dir / "shared" ,
8368 "aquila" : output_dir / "aquila" ,
8469 "sagittarius" : output_dir / "sagittarius" ,
8570 "velorum" : output_dir / "velorum" ,
8671 }
8772
88- # Create namespace directories
8973 for ns_dir in protocol_map .values ():
9074 ns_dir .mkdir (parents = True , exist_ok = True )
9175 init_file = ns_dir / "__init__.py"
9276 if not init_file .exists ():
9377 init_file .write_text ("" )
9478
95- # Move and organize generated files
9679 for py_file in output_dir .glob ("*.py" ):
9780 if py_file .name .startswith ("__" ):
9881 continue
9982
100- # Parse filename patterns:
101- # - shared_X_pb2.py -> shared/X_pb2.py
102- # - shared_X_pb2_grpc.py -> shared/X_pb2_grpc.py
103- # - aquila_Y_pb2.py -> aquila/Y_pb2.py
104- # etc.
105-
106- # Split by underscore to find protocol
10783 parts = py_file .stem .split ("_" )
10884 protocol = None
10985
110- # Check first part(s) against known protocols
11186 if parts [0 ] in protocol_map :
11287 protocol = parts [0 ]
11388
@@ -118,9 +93,8 @@ def organize_generated_files(output_dir: Path, proto_dirs: List[Path]) -> None:
11893 shutil .move (str (py_file ), str (dest ))
11994 print (f"Moved { py_file .name } -> { protocol } /{ py_file .name } " )
12095
96+
12197def create_init_files (output_dir : Path ) -> None :
122- """Create and populate __init__.py files for proper imports."""
123- # Namespace __init__ files - just need to be present
12498 for namespace in ["shared" , "aquila" , "sagittarius" , "velorum" ]:
12599 ns_dir = output_dir / namespace
126100 if ns_dir .exists ():
@@ -136,7 +110,6 @@ def create_init_files(output_dir: Path) -> None:
136110
137111
138112def main ():
139- """Main entry point."""
140113 script_dir = Path (__file__ ).parent
141114 build_dir = script_dir .parent
142115 repo_root = build_dir .parent .parent
@@ -151,15 +124,12 @@ def main():
151124 print (f" Proto root: { proto_root } " )
152125 print (f" Output: { generated_dir } " )
153126
154- # Verify proto directory exists
155127 if not proto_root .exists ():
156128 print (f"❌ Proto directory not found: { proto_root } " , file = sys .stderr )
157129 sys .exit (1 )
158130
159- # Create output directory
160131 generated_dir .mkdir (parents = True , exist_ok = True )
161132
162- # Get proto files and directories
163133 proto_files , proto_dirs = get_proto_files (proto_root )
164134
165135 if not proto_files :
@@ -168,33 +138,24 @@ def main():
168138
169139 print (f"✓ Found { len (proto_files )} proto files in { len (proto_dirs )} directories" )
170140
171- # Clean old generated files (but keep __init__.py)
172141 print (f"\n 📍 Cleaning old generated files..." )
173142 for py_file in generated_dir .rglob ("*.py" ):
174143 if not py_file .name .startswith ("__" ):
175144 py_file .unlink ()
176145 print (f" Removed { py_file .name } " )
177146
178- # Generate with protoc
179147 generate_protos (proto_root , str (generated_dir ), proto_dirs )
180148
181- # Organize generated files
182149 print (f"\n 📍 Organizing generated files..." )
183- organize_generated_files (generated_dir , proto_dirs )
150+ organize_generated_files (generated_dir )
184151
185- # Create init files
186152 print (f"\n 📍 Creating __init__.py files..." )
187153 create_init_files (generated_dir )
188154
189155 print (f"\n ✅ Generation complete!" )
190156 print (f" Output directory: { generated_dir } " )
191157 print (f" Ready to import: from tucana.generated import shared, aquila, sagittarius, velorum" )
192158
159+
193160if __name__ == "__main__" :
194161 main ()
195-
196-
197-
198-
199-
200-
0 commit comments