2020import libcst as cst
2121import pathlib
2222import sys
23- from typing import ( Any , Callable , Dict , List , Sequence , Tuple )
23+ from typing import Any , Callable , Dict , List , Sequence , Tuple
2424
2525
2626def partition (
27- predicate : Callable [[Any ], bool ],
28- iterator : Sequence [Any ]
27+ predicate : Callable [[Any ], bool ], iterator : Sequence [Any ]
2928) -> Tuple [List [Any ], List [Any ]]:
3029 """A stable, out-of-place partition."""
3130 results = ([], [])
@@ -38,40 +37,128 @@ def partition(
3837
3938
4039class pubsubCallTransformer (cst .CSTTransformer ):
41- CTRL_PARAMS : Tuple [str ] = (' retry' , ' timeout' , ' metadata' )
40+ CTRL_PARAMS : Tuple [str ] = (" retry" , " timeout" , " metadata" )
4241 METHOD_TO_PARAMS : Dict [str , Tuple [str ]] = {
43- 'acknowledge' : ('subscription' , 'ack_ids' , ),
44- 'create_snapshot' : ('name' , 'subscription' , 'labels' , ),
45- 'create_subscription' : ('name' , 'topic' , 'push_config' , 'ack_deadline_seconds' , 'retain_acked_messages' , 'message_retention_duration' , 'labels' , 'enable_message_ordering' , 'expiration_policy' , 'filter' , 'dead_letter_policy' , 'retry_policy' , 'detached' , ),
46- 'create_topic' : ('name' , 'labels' , 'message_storage_policy' , 'kms_key_name' , ),
47- 'delete_snapshot' : ('snapshot' , ),
48- 'delete_subscription' : ('subscription' , ),
49- 'delete_topic' : ('topic' , ),
50- 'detach_subscription' : ('subscription' , ),
51- 'get_snapshot' : ('snapshot' , ),
52- 'get_subscription' : ('subscription' , ),
53- 'get_topic' : ('topic' , ),
54- 'list_snapshots' : ('project' , 'page_size' , 'page_token' , ),
55- 'list_subscriptions' : ('project' , 'page_size' , 'page_token' , ),
56- 'list_topics' : ('project' , 'page_size' , 'page_token' , ),
57- 'list_topic_snapshots' : ('topic' , 'page_size' , 'page_token' , ),
58- 'list_topic_subscriptions' : ('topic' , 'page_size' , 'page_token' , ),
59- 'modify_ack_deadline' : ('subscription' , 'ack_ids' , 'ack_deadline_seconds' , ),
60- 'modify_push_config' : ('subscription' , 'push_config' , ),
61- 'publish' : ('topic' , 'messages' , ),
62- 'pull' : ('subscription' , 'max_messages' , 'return_immediately' , ),
63- 'seek' : ('subscription' , 'time' , 'snapshot' , ),
64- 'streaming_pull' : ('subscription' , 'stream_ack_deadline_seconds' , 'ack_ids' , 'modify_deadline_seconds' , 'modify_deadline_ack_ids' , 'client_id' , 'max_outstanding_messages' , 'max_outstanding_bytes' , ),
65- 'update_snapshot' : ('snapshot' , 'update_mask' , ),
66- 'update_subscription' : ('subscription' , 'update_mask' , ),
67- 'update_topic' : ('topic' , 'update_mask' , ),
68-
69- 'get_iam_policy' : ('resource' , 'options' , ),
70- 'set_iam_policy' : ('resource' , 'policy' , ),
71- 'test_iam_permissions' : ('resource' , 'permissions' , ),
72-
42+ "acknowledge" : (
43+ "subscription" ,
44+ "ack_ids" ,
45+ ),
46+ "create_snapshot" : (
47+ "name" ,
48+ "subscription" ,
49+ "labels" ,
50+ ),
51+ "create_subscription" : (
52+ "name" ,
53+ "topic" ,
54+ "push_config" ,
55+ "ack_deadline_seconds" ,
56+ "retain_acked_messages" ,
57+ "message_retention_duration" ,
58+ "labels" ,
59+ "enable_message_ordering" ,
60+ "expiration_policy" ,
61+ "filter" ,
62+ "dead_letter_policy" ,
63+ "retry_policy" ,
64+ "detached" ,
65+ ),
66+ "create_topic" : (
67+ "name" ,
68+ "labels" ,
69+ "message_storage_policy" ,
70+ "kms_key_name" ,
71+ ),
72+ "delete_snapshot" : ("snapshot" ,),
73+ "delete_subscription" : ("subscription" ,),
74+ "delete_topic" : ("topic" ,),
75+ "detach_subscription" : ("subscription" ,),
76+ "get_snapshot" : ("snapshot" ,),
77+ "get_subscription" : ("subscription" ,),
78+ "get_topic" : ("topic" ,),
79+ "list_snapshots" : (
80+ "project" ,
81+ "page_size" ,
82+ "page_token" ,
83+ ),
84+ "list_subscriptions" : (
85+ "project" ,
86+ "page_size" ,
87+ "page_token" ,
88+ ),
89+ "list_topics" : (
90+ "project" ,
91+ "page_size" ,
92+ "page_token" ,
93+ ),
94+ "list_topic_snapshots" : (
95+ "topic" ,
96+ "page_size" ,
97+ "page_token" ,
98+ ),
99+ "list_topic_subscriptions" : (
100+ "topic" ,
101+ "page_size" ,
102+ "page_token" ,
103+ ),
104+ "modify_ack_deadline" : (
105+ "subscription" ,
106+ "ack_ids" ,
107+ "ack_deadline_seconds" ,
108+ ),
109+ "modify_push_config" : (
110+ "subscription" ,
111+ "push_config" ,
112+ ),
113+ "pull" : (
114+ "subscription" ,
115+ "max_messages" ,
116+ "return_immediately" ,
117+ ),
118+ "seek" : (
119+ "subscription" ,
120+ "time" ,
121+ "snapshot" ,
122+ ),
123+ "streaming_pull" : (
124+ "subscription" ,
125+ "stream_ack_deadline_seconds" ,
126+ "ack_ids" ,
127+ "modify_deadline_seconds" ,
128+ "modify_deadline_ack_ids" ,
129+ "client_id" ,
130+ "max_outstanding_messages" ,
131+ "max_outstanding_bytes" ,
132+ ),
133+ "update_snapshot" : (
134+ "snapshot" ,
135+ "update_mask" ,
136+ ),
137+ "update_subscription" : (
138+ "subscription" ,
139+ "update_mask" ,
140+ ),
141+ "update_topic" : (
142+ "topic" ,
143+ "update_mask" ,
144+ ),
145+ "get_iam_policy" : (
146+ "resource" ,
147+ "options" ,
148+ ),
149+ "set_iam_policy" : (
150+ "resource" ,
151+ "policy" ,
152+ ),
153+ "test_iam_permissions" : (
154+ "resource" ,
155+ "permissions" ,
156+ ),
73157 }
74158
159+ def __init__ (self , use_keywords = False ):
160+ self ._use_keywords = use_keywords
161+
75162 def leave_Call (self , original : cst .Call , updated : cst .Call ) -> cst .CSTNode :
76163 try :
77164 key = original .func .attr .value
@@ -88,35 +175,80 @@ def leave_Call(self, original: cst.Call, updated: cst.Call) -> cst.CSTNode:
88175 return updated
89176
90177 kwargs , ctrl_kwargs = partition (
91- lambda a : not a .keyword .value in self .CTRL_PARAMS ,
92- kwargs
178+ lambda a : not a .keyword .value in self .CTRL_PARAMS , kwargs
93179 )
94180
95- args , ctrl_args = args [:len (kword_params )], args [len (kword_params ):]
96- ctrl_kwargs .extend (cst .Arg (value = a .value , keyword = cst .Name (value = ctrl ))
97- for a , ctrl in zip (ctrl_args , self .CTRL_PARAMS ))
181+ args , ctrl_args = args [: len (kword_params )], args [len (kword_params ) :]
182+ ctrl_kwargs .extend (
183+ cst .Arg (
184+ value = a .value ,
185+ keyword = cst .Name (value = ctrl ),
186+ equal = cst .AssignEqual (
187+ whitespace_before = cst .SimpleWhitespace ("" ),
188+ whitespace_after = cst .SimpleWhitespace ("" ),
189+ ),
190+ )
191+ for a , ctrl in zip (ctrl_args , self .CTRL_PARAMS )
192+ )
98193
99- request_arg = cst .Arg (
100- value = cst .Dict ([
101- cst .DictElement (
102- cst .SimpleString ("'{}'" .format (name )),
103- cst .Element (value = arg .value )
194+ if self ._use_keywords :
195+ new_kwargs = [
196+ cst .Arg (
197+ value = arg .value ,
198+ keyword = cst .Name (value = name ),
199+ equal = cst .AssignEqual (
200+ whitespace_before = cst .SimpleWhitespace ("" ),
201+ whitespace_after = cst .SimpleWhitespace ("" ),
202+ ),
104203 )
105- # Note: the args + kwargs looks silly, but keep in mind that
106- # the control parameters had to be stripped out, and that
107- # those could have been passed positionally or by keyword.
108- for name , arg in zip (kword_params , args + kwargs )]),
109- keyword = cst .Name ("request" )
110- )
204+ for name , arg in zip (kword_params , args + kwargs )
205+ ]
206+ new_kwargs .extend (
207+ [
208+ cst .Arg (
209+ value = arg .value ,
210+ keyword = cst .Name (value = arg .keyword .value ),
211+ equal = cst .AssignEqual (
212+ whitespace_before = cst .SimpleWhitespace ("" ),
213+ whitespace_after = cst .SimpleWhitespace ("" ),
214+ ),
215+ )
216+ for arg in ctrl_kwargs
217+ ]
218+ )
219+ return updated .with_changes (args = new_kwargs )
220+ else :
221+ request_arg = cst .Arg (
222+ value = cst .Dict (
223+ [
224+ cst .DictElement (
225+ cst .SimpleString ('"{}"' .format (name )),
226+ cst .Element (value = arg .value ),
227+ )
228+ for name , arg in zip (kword_params , args + kwargs )
229+ ]
230+ + [
231+ cst .DictElement (
232+ cst .SimpleString ('"{}"' .format (arg .keyword .value )),
233+ cst .Element (value = arg .value ),
234+ )
235+ for arg in ctrl_kwargs
236+ ]
237+ ),
238+ keyword = cst .Name ("request" ),
239+ equal = cst .AssignEqual (
240+ whitespace_before = cst .SimpleWhitespace ("" ),
241+ whitespace_after = cst .SimpleWhitespace ("" ),
242+ ),
243+ )
111244
112- return updated .with_changes (
113- args = [request_arg ] + ctrl_kwargs
114- )
245+ return updated .with_changes (args = [request_arg ])
115246
116247
117248def fix_files (
118249 in_dir : pathlib .Path ,
119250 out_dir : pathlib .Path ,
251+ use_keywords : bool = False ,
120252 * ,
121253 transformer = pubsubCallTransformer (),
122254):
@@ -129,11 +261,12 @@ def fix_files(
129261 pyfile_gen = (
130262 pathlib .Path (os .path .join (root , f ))
131263 for root , _ , files in os .walk (in_dir )
132- for f in files if os .path .splitext (f )[1 ] == ".py"
264+ for f in files
265+ if os .path .splitext (f )[1 ] == ".py"
133266 )
134267
135268 for fpath in pyfile_gen :
136- with open (fpath , 'r' ) as f :
269+ with open (fpath , "r" ) as f :
137270 src = f .read ()
138271
139272 # Parse the code and insert method call fixes.
@@ -145,11 +278,11 @@ def fix_files(
145278 updated_path .parent .mkdir (parents = True , exist_ok = True )
146279
147280 # Generate the updated source file at the corresponding path.
148- with open (updated_path , 'w' ) as f :
281+ with open (updated_path , "w" ) as f :
149282 f .write (updated .code )
150283
151284
152- if __name__ == ' __main__' :
285+ if __name__ == " __main__" :
153286 parser = argparse .ArgumentParser (
154287 description = """Fix up source that uses the pubsub client library.
155288
@@ -164,24 +297,34 @@ def fix_files(
164297
165298 These all constitute false negatives. The tool will also detect false
166299 positives when an API method shares a name with another method.
167- """ )
300+ """
301+ )
168302 parser .add_argument (
169- '-d' ,
170- ' --input-directory' ,
303+ "-d" ,
304+ " --input-directory" ,
171305 required = True ,
172- dest = ' input_dir' ,
173- help = ' the input directory to walk for python files to fix up' ,
306+ dest = " input_dir" ,
307+ help = " the input directory to walk for python files to fix up" ,
174308 )
175309 parser .add_argument (
176- '-o' ,
177- ' --output-directory' ,
310+ "-o" ,
311+ " --output-directory" ,
178312 required = True ,
179- dest = 'output_dir' ,
180- help = 'the directory to output files fixed via un-flattening' ,
313+ dest = "output_dir" ,
314+ help = "the directory to output files fixed via un-flattening" ,
315+ )
316+ parser .add_argument (
317+ "-k" ,
318+ "--use-keywords" ,
319+ required = False ,
320+ action = "store_true" ,
321+ dest = "use_keywords" ,
322+ help = "Use keyword arguments instead of constructing a request" ,
181323 )
182324 args = parser .parse_args ()
183325 input_dir = pathlib .Path (args .input_dir )
184326 output_dir = pathlib .Path (args .output_dir )
327+ use_keywords = args .use_keywords
185328 if not input_dir .is_dir ():
186329 print (
187330 f"input directory '{ input_dir } ' does not exist or is not a directory" ,
@@ -203,4 +346,5 @@ def fix_files(
203346 )
204347 sys .exit (- 1 )
205348
206- fix_files (input_dir , output_dir )
349+ transformer = pubsubCallTransformer (use_keywords = use_keywords )
350+ fix_files (input_dir , output_dir , use_keywords , transformer = transformer )
0 commit comments