Skip to content

Commit cdddf13

Browse files
feat: implement multiple version for nodejs lib (#504)
* feat: synthool generate src/index for nodejs client library * fix the linters * merge upstream * remove '-' in jinja tempalte * rename the functions * add try, finally to avoid change direcory * optional for versions and default verison
1 parent 958a803 commit cdddf13

10 files changed

Lines changed: 325 additions & 2 deletions

File tree

synthtool/gcp/common.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ def node_library(self, **kwargs) -> Path:
9292

9393
kwargs["metadata"] = node.template_metadata()
9494
kwargs["publish_token"] = node.get_publish_token(kwargs["metadata"]["name"])
95+
96+
# generate root-level `src/index.ts` to export multiple versions and its default clients
97+
if kwargs["versions"] and kwargs["default_version"]:
98+
node.generate_index_ts(
99+
versions=kwargs["versions"], default_version=kwargs["default_version"]
100+
)
101+
95102
return self._generic_library("node_library", **kwargs)
96103

97104
def php_library(self, **kwargs) -> Path:
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// ** This file is automatically generated by synthtool. **
16+
// ** https://github.com/googleapis/synthtool **
17+
// ** All changes to this file may be overwritten. **
18+
19+
{% for version in versions %}import * as {{ version }} from './{{ version}}';{{ "\n" }}{% endfor %}
20+
{% for client in clients %}const {{ client }} = {{ default_version }}.{{ client }};{{ "\n" }}{% endfor %}
21+
export {{ "{" }}{{ versions|join(', ')}}, {{ clients|join(', ')}}{{ "}" }};
22+
export default {{ "{" }}{{ versions|join(', ')}}, {{ clients|join(', ')}}{{ "}" }};
23+
import * as protos from '../protos/protos';
24+
export {protos};

synthtool/languages/node.py

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@
1313
# limitations under the License.
1414

1515
import json
16-
from typing import Any, Dict
16+
from jinja2 import FileSystemLoader, Environment
17+
from pathlib import Path
18+
import re
19+
from synthtool import log, shell
1720
from synthtool.sources import git
1821
from synthtool.gcp import samples, snippets
19-
from synthtool import log, shell
22+
from typing import Any, Dict, List
2023

2124
_REQUIRED_FIELDS = ["name", "repository"]
2225

@@ -94,6 +97,67 @@ def get_publish_token(package_name: str):
9497
return package_name.strip("@").replace("/", "-") + "-npm-token"
9598

9699

100+
def extract_clients(filePath: Path) -> List[str]:
101+
"""
102+
parse the client name from index.ts file
103+
104+
Args:
105+
filePath: the path of index.ts.
106+
Returns:
107+
Array of client name string extract from index.ts file.
108+
"""
109+
with open(filePath, "r") as fh:
110+
content = fh.read()
111+
return re.findall(r"\{(.*Client)\}", content)
112+
113+
114+
def generate_index_ts(versions: List[str], default_version: str) -> None:
115+
"""
116+
generate src/index.ts to export the client name and versions in the client library.
117+
118+
Args:
119+
versions: the list of versions, like: ['v1', 'v1beta1', ...]
120+
default_version: a stable version provided by API producer. It must exist in argument versions.
121+
Return:
122+
True/False: return true if successfully generate src/index.ts, vice versa.
123+
"""
124+
# sanitizer the input arguments
125+
if len(versions) < 1:
126+
err_msg = (
127+
"List of version can't be empty, it must contain default version at least."
128+
)
129+
log.error(err_msg)
130+
raise AttributeError(err_msg)
131+
if default_version not in versions:
132+
err_msg = f"Version {versions} must contain default version {default_version}."
133+
log.error(err_msg)
134+
raise AttributeError(err_msg)
135+
136+
# compose default version's index.ts file path
137+
versioned_index_ts_path = Path("src") / default_version / "index.ts"
138+
clients = extract_clients(versioned_index_ts_path)
139+
if not clients:
140+
err_msg = f"No client is exported in the default version's({default_version}) index.ts ."
141+
log.error(err_msg)
142+
raise AttributeError(err_msg)
143+
144+
# compose template directory
145+
template_path = (
146+
Path(__file__).parent.parent / "gcp" / "templates" / "node_split_library"
147+
)
148+
template_loader = FileSystemLoader(searchpath=str(template_path))
149+
template_env = Environment(loader=template_loader, keep_trailing_newline=True)
150+
TEMPLATE_FILE = "index.ts.j2"
151+
index_template = template_env.get_template(TEMPLATE_FILE)
152+
# render index.ts content
153+
output_text = index_template.render(
154+
versions=versions, default_version=default_version, clients=clients
155+
)
156+
with open("src/index.ts", "w") as fh:
157+
fh.write(output_text)
158+
log.info("successfully generate `src/index.ts`")
159+
160+
97161
def install(hide_output=False):
98162
"""
99163
Installs all dependencies for the current Node.js library.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// ** This file is automatically generated by gapic-generator-typescript. **
16+
// ** https://github.com/googleapis/gapic-generator-typescript **
17+
// ** All changes to this file may be overwritten. **
18+
19+
export {StreamingVideoIntelligenceServiceClient} from './streaming_video_intelligence_service_client';
20+
export {VideoIntelligenceServiceClient} from './video_intelligence_service_client';
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// ** This file is automatically generated by synthtool. **
16+
// ** https://github.com/googleapis/synthtool **
17+
// ** All changes to this file may be overwritten. **
18+
19+
import * as v1 from './v1';
20+
import * as v1beta1 from './v1beta1';
21+
22+
const TextToSpeechClient = v1.TextToSpeechClient;
23+
24+
export {v1, v1beta1, TextToSpeechClient};
25+
export default {v1, v1beta1, TextToSpeechClient};
26+
import * as protos from '../protos/protos';
27+
export {protos};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// ** This file is automatically generated by gapic-generator-typescript. **
16+
// ** https://github.com/googleapis/gapic-generator-typescript **
17+
// ** All changes to this file may be overwritten. **
18+
19+
export {TextToSpeechClient} from './text_to_speech_client';
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// ** This file is automatically generated by synthtool. **
16+
// ** https://github.com/googleapis/synthtool **
17+
// ** All changes to this file may be overwritten. **
18+
19+
import * as v1 from './v1';
20+
import * as v1beta1 from './v1beta1';
21+
22+
const TextToSpeechClient = v1.TextToSpeechClient;
23+
24+
export {v1, v1beta1, TextToSpeechClient};
25+
export default {v1, v1beta1, TextToSpeechClient};
26+
import * as protos from '../protos/protos';
27+
export {protos};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// ** This file is automatically generated by gapic-generator-typescript. **
16+
// ** https://github.com/googleapis/gapic-generator-typescript **
17+
// ** All changes to this file may be overwritten. **
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// ** This file is automatically generated by gapic-generator-typescript. **
16+
// ** https://github.com/googleapis/gapic-generator-typescript **
17+
// ** All changes to this file may be overwritten. **
18+
19+
export {TextToSpeechClient} from './text_to_speech_client';

tests/test_node.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
import os
1818
from pathlib import Path
1919
from synthtool.languages import node
20+
import pathlib
21+
import filecmp
22+
import pytest
2023

2124
FIXTURES = Path(__file__).parent / "fixtures"
2225

@@ -74,6 +77,102 @@ def test_no_samples():
7477
os.chdir(cwd)
7578

7679

80+
def test_extract_clients_no_file():
81+
index_ts_path = pathlib.Path(
82+
FIXTURES / "node_templates" / "index_samples" / "no_exist_index.ts"
83+
)
84+
85+
with pytest.raises(FileNotFoundError):
86+
clients = node.extract_clients(index_ts_path)
87+
assert not clients
88+
89+
90+
def test_extract_single_clients():
91+
index_ts_path = pathlib.Path(
92+
FIXTURES / "node_templates" / "index_samples" / "single_index.ts"
93+
)
94+
95+
clients = node.extract_clients(index_ts_path)
96+
97+
assert len(clients) == 1
98+
assert clients[0] == "TextToSpeechClient"
99+
100+
101+
def test_extract_multiple_clients():
102+
index_ts_path = pathlib.Path(
103+
FIXTURES / "node_templates" / "index_samples" / "multiple_index.ts"
104+
)
105+
106+
clients = node.extract_clients(index_ts_path)
107+
108+
assert len(clients) == 2
109+
assert clients[0] == "StreamingVideoIntelligenceServiceClient"
110+
assert clients[1] == "VideoIntelligenceServiceClient"
111+
112+
113+
def test_generate_index_ts():
114+
cwd = os.getcwd()
115+
try:
116+
# use a non-nodejs template directory
117+
os.chdir(FIXTURES / "node_templates" / "index_samples")
118+
node.generate_index_ts(["v1", "v1beta1"], "v1")
119+
generated_index_path = pathlib.Path(
120+
FIXTURES / "node_templates" / "index_samples" / "src" / "index.ts"
121+
)
122+
sample_index_path = pathlib.Path(
123+
FIXTURES / "node_templates" / "index_samples" / "sample_index.ts"
124+
)
125+
assert filecmp.cmp(generated_index_path, sample_index_path)
126+
finally:
127+
os.chdir(cwd)
128+
129+
130+
def test_generate_index_ts_empty_versions():
131+
cwd = os.getcwd()
132+
try:
133+
# use a non-nodejs template directory
134+
os.chdir(FIXTURES / "node_templates" / "index_samples")
135+
136+
with pytest.raises(AttributeError) as err:
137+
node.generate_index_ts([], "v1")
138+
assert "can't be empty" in err.args
139+
finally:
140+
os.chdir(cwd)
141+
142+
143+
def test_generate_index_ts_invalid_default_version():
144+
cwd = os.getcwd()
145+
try:
146+
# use a non-nodejs template directory
147+
os.chdir(FIXTURES / "node_templates" / "index_samples")
148+
versions = ["v1beta1"]
149+
default_version = "v1"
150+
151+
with pytest.raises(AttributeError) as err:
152+
node.generate_index_ts(versions, default_version)
153+
assert f"must contain default version {default_version}" in err.args
154+
finally:
155+
os.chdir(cwd)
156+
157+
158+
def test_generate_index_ts_no_clients():
159+
cwd = os.getcwd()
160+
try:
161+
# use a non-nodejs template directory
162+
os.chdir(FIXTURES / "node_templates" / "index_samples")
163+
versions = ["v1", "v1beta1", "invalid_index"]
164+
default_version = "invalid_index"
165+
166+
with pytest.raises(AttributeError) as err:
167+
node.generate_index_ts(versions, default_version)
168+
assert (
169+
f"No client is exported in the default version's({default_version}) index.ts ."
170+
in err.args
171+
)
172+
finally:
173+
os.chdir(cwd)
174+
175+
77176
class TestPostprocess(TestCase):
78177
@patch("synthtool.shell.run")
79178
def test_install(self, shell_run_mock):

0 commit comments

Comments
 (0)