Skip to content

Commit a3a462a

Browse files
authored
Change the behavior of the fixup script (#291)
Only script parameters are a single input dir and a single output dir Fixups are no longer in-place: they are written to the corresponding path in the output dir. The files in the input dir are unmodified. Fixup script works on all services in an api concurrently instead of one script per service. It has been renamed accordingly.
1 parent 517c45b commit a3a462a

File tree

2 files changed

+75
-52
lines changed

2 files changed

+75
-52
lines changed

packages/gapic-generator/gapic/templates/scripts/fixup_%service_keywords.py.j2 renamed to packages/gapic-generator/gapic/templates/scripts/fixup_keywords.py.j2

Lines changed: 74 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import argparse
44
import os
55
import libcst as cst
6-
from itertools import chain
6+
import pathlib
7+
import sys
78
from typing import (Any, Callable, Dict, List, Sequence, Tuple)
89

910

@@ -21,14 +22,18 @@ def partition(
2122
return results[1], results[0]
2223

2324

24-
class {{ service.client_name }}CallTransformer(cst.CSTTransformer):
25+
class {{ api.naming.module_name }}CallTransformer(cst.CSTTransformer):
2526
CTRL_PARAMS: Tuple[str] = ('retry', 'timeout', 'metadata')
26-
27+
{% with all_methods = [] -%}
28+
{% for service in api.services.values() %}{% for method in service.methods.values() -%}
29+
{% do all_methods.append(method) -%}
30+
{% endfor %}{% endfor -%}
2731
METHOD_TO_PARAMS: Dict[str, Tuple[str]] = {
28-
{% for method in service.methods.values() -%}
32+
{% for method in all_methods|sort(attribute='name')|unique(attribute='name') -%}
2933
'{{ method.name|snake_case }}': ({% for field in method.legacy_flattened_fields.values() %}'{{ field.name }}', {% endfor %}),
30-
{% endfor -%}
34+
{% endfor -%}
3135
}
36+
{% endwith %}
3237

3338
def leave_Call(self, original: cst.Call, updated: cst.Call) -> cst.CSTNode:
3439
try:
@@ -59,11 +64,11 @@ class {{ service.client_name }}CallTransformer(cst.CSTTransformer):
5964
value=cst.Dict([
6065
cst.DictElement(
6166
cst.SimpleString("'{}'".format(name)),
62-
{# Inline comments and formatting are currently stripped out. #}
63-
{# My current attempts at preverving comments and formatting #}
64-
{# keep the comments, but the formatting is run through a log #}
65-
{# chipper, and an extra comma gets added, which causes a #}
66-
{# parse error. #}
67+
{# Inline comments and formatting are currently stripped out. -#}
68+
{# My current attempts at preverving comments and formatting -#}
69+
{# keep the comments, but the formatting is run through a log -#}
70+
{# chipper, and an extra comma gets added, which causes a -#}
71+
{# parse error. -#}
6772
cst.Element(value=arg.value)
6873
)
6974
# Note: the args + kwargs looks silly, but keep in mind that
@@ -79,31 +84,45 @@ class {{ service.client_name }}CallTransformer(cst.CSTTransformer):
7984

8085

8186
def fix_files(
82-
dirs: Sequence[str],
83-
files: Sequence[str],
87+
in_dir: pathlib.Path,
88+
out_dir: pathlib.Path,
8489
*,
85-
transformer={{ service.client_name }}CallTransformer(),
90+
transformer={{ api.naming.module_name }}CallTransformer(),
8691
):
87-
pyfile_gen = chain(
88-
(os.path.join(root, f)
89-
for d in dirs
90-
for root, _, files in os.walk(d)
91-
for f in files if os.path.splitext(f)[1] == ".py"),
92-
files)
92+
"""Duplicate the input dir to the output dir, fixing file method calls.
93+
94+
Preconditions:
95+
* in_dir is a real directory
96+
* out_dir is a real, empty directory
97+
"""
98+
pyfile_gen = (
99+
pathlib.Path(os.path.join(root, f))
100+
for root, _, files in os.walk(in_dir)
101+
for f in files if os.path.splitext(f)[1] == ".py"
102+
)
93103

94104
for fpath in pyfile_gen:
95-
with open(fpath, 'r+') as f:
105+
with open(fpath, 'r') as f:
96106
src = f.read()
97-
tree = cst.parse_module(src)
98-
updated = tree.visit(transformer)
99-
f.seek(0)
100-
f.truncate()
107+
108+
# Parse the code and insert method call fixes.
109+
tree = cst.parse_module(src)
110+
updated = tree.visit(transformer)
111+
112+
# Create the path and directory structure for the new file.
113+
updated_path = out_dir.joinpath(fpath.relative_to(in_dir))
114+
updated_path.parent.mkdir(parents=True, exist_ok=True)
115+
116+
# Generate the updated source file at the corresponding path.
117+
with open(updated_path, 'w') as f:
101118
f.write(updated.code)
102119

103120

104121
if __name__ == '__main__':
105122
parser = argparse.ArgumentParser(
106-
description="""Fix up source that uses the {{ service.name }} client library.
123+
description="""Fix up source that uses the {{ api.naming.module_name }} client library.
124+
125+
The existing sources are NOT overwritten but are copied to output_dir with changes made.
107126

108127
Note: This tool operates at a best-effort level at converting positional
109128
parameters in client method calls to keyword based parameters.
@@ -114,32 +133,40 @@ Note: This tool operates at a best-effort level at converting positional
114133

115134
These all constitute false negatives. The tool will also detect false
116135
positives when an API method shares a name with another method.
117-
118-
Be sure to back up your source files before running this tool and to compare the diffs.
119136
""")
120-
group = parser.add_mutually_exclusive_group(required=True)
121-
group.add_argument(
137+
parser.add_argument(
122138
'-d',
123-
metavar='dir',
124-
dest='dirs',
125-
action='append',
126-
help='a directory to walk for python files to fix up',
139+
dest='input_dir',
140+
help='the input directory to walk for python files to fix up',
127141
)
128-
group.add_argument(
129-
'-f',
130-
metavar='file',
131-
dest='files',
132-
action='append',
142+
parser.add_argument(
143+
'-o',
144+
dest='output_dir',
133145
help='a file to fix up via un-flattening',
134146
)
135147
args = parser.parse_args()
136-
print(
137-
"""It is strongly, strongly recommended that you commit outstanding changes and
138-
back up your source tree before running this conversion script.
139-
Please take a moment to do that now if you haven't already.
140-
"""
141-
)
142-
resp = input("Really attempt to convert sources? yes/[no]: ")
143-
if resp == "yes":
144-
fix_files(args.dirs or [], args.files or [])
148+
input_dir = pathlib.Path(args.input_dir)
149+
output_dir = pathlib.Path(args.output_dir)
150+
if not input_dir.is_dir():
151+
print(
152+
f"input directory '{input_dir}' does not exist or is not a directory",
153+
file=sys.stderr,
154+
)
155+
sys.exit(-1)
156+
157+
if not output_dir.is_dir():
158+
print(
159+
f"output directory '{output_dir}' does not exist or is not a directory",
160+
file=sys.stderr,
161+
)
162+
sys.exit(-1)
163+
164+
if os.listdir(output_dir):
165+
print(
166+
f"output directory '{output_dir}' is not empty",
167+
file=sys.stderr,
168+
)
169+
sys.exit(-1)
170+
171+
fix_files(input_dir, output_dir)
145172
{% endblock %}

packages/gapic-generator/gapic/templates/setup.py.j2

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,7 @@ setuptools.setup(
2525
'libcst >= 0.2.5',
2626
],
2727
scripts=[
28-
{% for proto in api.all_protos.values() -%}
29-
{% for service in proto.services.values() -%}
30-
'scripts/fixup_{{ service.module_name }}_keywords.py',
31-
{% endfor -%}
32-
{% endfor -%}
28+
'scripts/fixup_keywords.py',
3329
],
3430
classifiers=[
3531
'Development Status :: 3 - Alpha',

0 commit comments

Comments
 (0)