1414 ForwardRef = typing ._ForwardRef # type: ignore
1515
1616
17+ cdp_modules = None
18+
19+
1720def indent (text : str , count : int ):
1821 ''' Indent text with the specified number of spaces. '''
1922 return tw_indent (text , ' ' * count )
@@ -23,18 +26,17 @@ def main():
2326 ''' Main entry point. '''
2427 root = pathlib .Path (__file__ ).resolve ().parent .parent / 'trio_cdp' / 'generated'
2528 clean (root )
26- modules = list ()
27- for name , module in inspect .getmembers (cdp ):
28- if name .startswith ('_' ) or name in ('cdp' , 'util' ):
29- continue
30- modules .append (name )
29+ ignored = lambda name : name .startswith ('_' ) or name in ('cdp' , 'util' )
30+ global cdp_modules
31+ cdp_modules = {n :m for n ,m in inspect .getmembers (cdp ) if not ignored (n )}
32+ for name , module in cdp_modules .items ():
3133 generate_module (root , name , module )
3234 init = root / '__init__.py'
3335 with init .open ('w' ) as file :
3436 file .write ('# DO NOT EDIT THIS FILE!\n #\n ' )
3537 file .write ('# This code is generated off of PyCDP modules. If you need to make\n ' )
3638 file .write ('# changes, edit the generator and regenerate all of the modules.\n \n ' )
37- for module in modules :
39+ for module in cdp_modules :
3840 file .write (f'from . import { module } \n ' )
3941
4042
@@ -45,17 +47,18 @@ def clean(root: pathlib.Path):
4547 path .unlink ()
4648
4749
48- def generate_module (root : pathlib .Path , module_name : str , module : types .ModuleType ):
50+ def generate_module (root : pathlib .Path , module_name : str ,
51+ module : types .ModuleType ):
4952 ''' Generate code for a module. '''
5053 print ('* Generating module:' , module_name )
5154 module_path = root / f'{ module_name } .py'
5255 commands = list ()
5356 classes = list ()
5457 for name , obj in inspect .getmembers (module ):
55- if name .startswith ('_' ) or name in ('dataclass' , 'event_class' ):
58+ if name .startswith ('_' ) or name in ('dataclass' , 'deprecated' , ' event_class' ):
5659 continue
5760 if inspect .isfunction (obj ):
58- commands .append (generate_command (module_name , name , obj ))
61+ commands .append (generate_command (module , module_name , obj ))
5962 elif inspect .isclass (obj ):
6063 classes .append (name )
6164
@@ -76,16 +79,19 @@ def generate_module(root: pathlib.Path, module_name: str, module: types.ModuleTy
7679 file .write ('\n \n ' .join (commands ))
7780
7881
79- def generate_command (module : str , name : str , fn : types .FunctionType ):
82+ def generate_command (module : types .ModuleType , module_name : str ,
83+ fn : types .FunctionType ):
8084 ''' Generate code for one command, i.e. one PyCDP wrapper function. '''
81- print (f' - { name } ()' )
85+ fn_name = fn .__name__
86+ print (f' - { fn_name } ()' )
8287 sig = inspect .signature (fn )
88+ type_hints = typing .get_type_hints (fn , globalns = vars (module ), localns = None )
8389
8490 # Generate the argument list.
8591 args = list ()
8692 call_args = list ()
8793 for param in sig .parameters .values ():
88- ann = format_annotation (param .annotation )
94+ ann = format_annotation (module , type_hints [ param .name ] )
8995 if param .default != inspect .Parameter .empty :
9096 default_str = f' = { param .default } '
9197 else :
@@ -107,18 +113,18 @@ def generate_command(module: str, name: str, fn: types.FunctionType):
107113 doc = ''
108114
109115 # The original function returns a generator. We want to grab the return type of the
110- # generator and set that as the return type of this wrapper function.
111- return_type = format_annotation (sig . return_annotation .__args__ [2 ])
116+ # generator and set that as the return type of this wrapper function.
117+ return_type = format_annotation (module , type_hints [ 'return' ] .__args__ [2 ])
112118
113119 # Format the function and return it as a string.
114- ctx_name , ctx_fn = which_context (module , name )
115- body = f"{ ctx_name } = { ctx_fn } ('{ module } .{ name } ')\n "
116- body += f"return await { ctx_name } .execute(cdp.{ module } .{ name } ({ call_arg_str } ))"
120+ ctx_name , ctx_fn = which_context (module_name , fn_name )
121+ body = f"{ ctx_name } = { ctx_fn } ('{ module_name } .{ fn_name } ')\n "
122+ body += f"return await { ctx_name } .execute(cdp.{ module_name } .{ fn_name } ({ call_arg_str } ))"
117123 body = indent (body , 4 )
118- return f'async def { name } ({ arg_str } ) -> { return_type } :\n { doc } { body } \n '
124+ return f'async def { fn_name } ({ arg_str } ) -> { return_type } :\n { doc } { body } \n '
119125
120126
121- def format_annotation (ann : typing .Any ):
127+ def format_annotation (current_module : types . ModuleType , ann : typing .Any ):
122128 '''
123129 Given a type annotation, return a stringified version.
124130
@@ -127,32 +133,31 @@ def format_annotation(ann: typing.Any):
127133 have to access private members to figure out what the specific annotation actually
128134 is.
129135 '''
130- if isinstance (ann , str ):
131- ann_str = ann
132- elif isinstance (ann , ForwardRef ):
133- ann_str = ann .__forward_arg__
134- elif ann in (bool , dict , float , int , str ):
135- ann_str = ann .__name__
136- elif ann is type (None ):
136+ if ann is type (None ):
137137 ann_str = 'None'
138+ elif isinstance (ann , type ):
139+ if ann .__module__ not in (current_module .__name__ , 'builtins' ):
140+ ann_str = f'{ ann .__module__ } .{ ann .__name__ } '
141+ else :
142+ ann_str = ann .__name__
138143 elif ann ._name == 'Any' :
139144 ann_str = 'typing.Any'
140145 elif ann ._name == 'List' :
141- nested_ann = format_annotation (ann .__args__ [0 ])
146+ nested_ann = format_annotation (current_module , ann .__args__ [0 ])
142147 ann_str = f'typing.List[{ nested_ann } ]'
143148 elif ann ._name == 'Tuple' :
144- nested_anns = ', ' .join (format_annotation (a ) for a in ann .__args__ )
149+ nested_anns = ', ' .join (format_annotation (current_module , a ) for a in ann .__args__ )
145150 ann_str = f'typing.Tuple[{ nested_anns } ]'
146151 elif ann ._name is None and len (ann .__args__ ) > 1 :
147152 # For some reason union annotations don't have a name?
148153 # If the union has two members and one of them is NoneType, then it's really
149154 # a typing.Optional.
150155 if len (ann .__args__ ) == 2 and any (a is type (None ) for a in ann .__args__ ):
151- nested_ann = format_annotation (
152- [ a for a in ann . __args__ if a is not type ( None )][ 0 ] )
156+ opt_type = [ a for a in ann . __args__ if a is not type ( None )][ 0 ]
157+ nested_ann = format_annotation ( current_module , opt_type )
153158 ann_str = f'typing.Optional[{ nested_ann } ]'
154159 else :
155- nested_anns = ', ' .join (format_annotation (a ) for a in ann .__args__ )
160+ nested_anns = ', ' .join (format_annotation (current_module , a ) for a in ann .__args__ )
156161 ann_str = f'typing.Union[{ nested_anns } ]'
157162 else :
158163 raise Exception (f'Cannot format annotation: { repr (ann )} ' )
0 commit comments