Visitar URL original
chore: add client_options to service client initialization (#1547) · googleapis/google-cloud-python@9639f8d · GitHub
Skip to content

Commit 9639f8d

Browse files
authored
chore: add client_options to service client initialization (#1547)
* chore: add client_options
1 parent 92794c2 commit 9639f8d

5 files changed

Lines changed: 178 additions & 26 deletions

File tree

‎packages/gapic-generator/gapic/configurable_snippetgen/configured_snippet.py‎

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import dataclasses
16+
from typing import Optional
1617

1718
import inflection
1819
import libcst
@@ -119,6 +120,31 @@ def filename(self) -> str:
119120
sync_or_async = "sync" if self.is_sync else "async"
120121
return f"{self.gapic_module_name}_generated_{service_name}_{snake_case_rpc_name}_{config_id}_{sync_or_async}.py"
121122

123+
@property
124+
def api_endpoint(self) -> Optional[str]:
125+
"""The api_endpoint in client_options."""
126+
service_endpoint = (
127+
self.config.snippet.service_client_initialization.custom_service_endpoint
128+
)
129+
130+
if not service_endpoint.host:
131+
return None
132+
133+
# GAPIC Python libraries do not require the schema to be specified.
134+
host = service_endpoint.host
135+
region = service_endpoint.region
136+
port = service_endpoint.port
137+
138+
if port:
139+
host_maybe_with_port = f"{host}:{port}"
140+
else:
141+
host_maybe_with_port = host
142+
143+
if region:
144+
return f"{region}-{host_maybe_with_port}"
145+
else:
146+
return host_maybe_with_port
147+
122148
def _append_to_sample_function_def_body(
123149
self, statement: libcst.BaseStatement
124150
) -> None:
@@ -141,23 +167,33 @@ def _add_sample_function_parameters(self) -> None:
141167
params=parameters
142168
)
143169

144-
def _append_service_client_initialization(self) -> None:
145-
initialization_call = libcst.parse_statement(
146-
f"client = {self.gapic_module_name}.{self.client_class_name}()"
147-
)
148-
self._append_to_sample_function_def_body(initialization_call)
170+
def _get_service_client_initialization(self) -> libcst.BaseStatement:
171+
if self.api_endpoint is not None:
172+
client_options_arg = libcst.Arg(
173+
keyword=libcst.Name("client_options"),
174+
value=libcst_utils.convert_py_dict(
175+
[("api_endpoint", self.api_endpoint)]
176+
),
177+
)
178+
service_client_initialization = libcst.helpers.parse_template_statement(
179+
f"client = {self.gapic_module_name}.{self.client_class_name}({{arg}})",
180+
arg=client_options_arg,
181+
)
182+
else:
183+
service_client_initialization = libcst.parse_statement(
184+
f"client = {self.gapic_module_name}.{self.client_class_name}()"
185+
)
149186

150-
def _add_sample_function_body(self) -> None:
151-
# TODO: https://github.com/googleapis/gapic-generator-python/issues/1539, add sample function body.
152-
# Each call below appends one or more statements to the sample
153-
# function's body.
154-
self._append_service_client_initialization()
187+
return service_client_initialization
155188

156189
def _build_sample_function(self) -> None:
157190
# TODO: https://github.com/googleapis/gapic-generator-python/issues/1536, add return type.
158191
# TODO: https://github.com/googleapis/gapic-generator-python/issues/1538, add docstring.
192+
# TODO: https://github.com/googleapis/gapic-generator-python/issues/1539, add sample function body.
159193
self._add_sample_function_parameters()
160-
self._add_sample_function_body()
194+
self._append_to_sample_function_def_body(
195+
self._get_service_client_initialization()
196+
)
161197

162198
def _add_sample_function(self) -> None:
163199
self._module = self._module.with_changes(

‎packages/gapic-generator/gapic/configurable_snippetgen/libcst_utils.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from typing import Sequence, Tuple
16+
1517
import libcst
1618

1719
from gapic.configurable_snippetgen import snippet_config_language_pb2
@@ -57,3 +59,18 @@ def convert_parameter(
5759
default=convert_expression(config_parameter.value),
5860
)
5961
return param
62+
63+
64+
def convert_py_dict(key_value_pairs: Sequence[Tuple[str, str]]) -> libcst.Dict:
65+
elements = []
66+
for key, value in key_value_pairs:
67+
if not (isinstance(key, str) and isinstance(value, str)):
68+
raise ValueError(
69+
f"convert_py_dict supports only string keys and values.")
70+
elements.append(
71+
libcst.DictElement(
72+
libcst.SimpleString(
73+
f'"{key}"'), libcst.SimpleString(f'"{value}"')
74+
)
75+
)
76+
return libcst.Dict(elements=elements)

‎packages/gapic-generator/tests/unit/configurable_snippetgen/resources/speech/speech_v1_generated_adaptation_create_custom_class_basic_async.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async def sample_create_custom_class_Basic(
4040
a CustomClass
4141
"""
4242
client = speech_v1.AdaptationAsyncClient(
43-
client_options={"api_endpoint": "https://us-speech.googleapis.com"}
43+
client_options={"api_endpoint": "us-speech.googleapis.com"}
4444
)
4545

4646
request = speech_v1.CreateCustomClassRequest(

‎packages/gapic-generator/tests/unit/configurable_snippetgen/test_configured_snippet.py‎

Lines changed: 90 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,30 @@ def _load_snippet_config(
6565

6666

6767
def _make_configured_snippet(
68-
request_path: Path, config_path: Path, api_version: str, is_sync: bool
68+
request_path: Path = SPEECH_V1_REQUEST_PATH,
69+
config_path: Path = CONFIG_JSON_PATH,
70+
api_version: str = "v1",
71+
is_sync: bool = True,
6972
) -> configured_snippet.ConfiguredSnippet:
7073
api_schema = _load_api_schema(request_path)
7174
snippet_config = _load_snippet_config(config_path)
72-
7375
return configured_snippet.ConfiguredSnippet(
7476
api_schema, snippet_config, api_version, is_sync
7577
)
7678

7779

7880
@pytest.fixture
7981
def snippet():
80-
return _make_configured_snippet(
81-
SPEECH_V1_REQUEST_PATH, CONFIG_JSON_PATH, api_version="v1", is_sync=True
82+
return _make_configured_snippet()
83+
84+
85+
@pytest.fixture
86+
def snippet_without_endpoint():
87+
snippet = _make_configured_snippet()
88+
snippet.config.snippet.service_client_initialization.ClearField(
89+
"custom_service_endpoint"
8290
)
91+
return snippet
8392

8493

8594
def test_gapic_module_name(snippet):
@@ -94,9 +103,7 @@ def test_gapic_module_name(snippet):
94103
],
95104
)
96105
def test_region_tag(is_sync, expected):
97-
snippet = _make_configured_snippet(
98-
SPEECH_V1_REQUEST_PATH, CONFIG_JSON_PATH, api_version="v1", is_sync=is_sync
99-
)
106+
snippet = _make_configured_snippet(is_sync=is_sync)
100107
assert snippet.region_tag == expected
101108

102109

@@ -112,9 +119,7 @@ def test_sample_function_name(snippet):
112119
],
113120
)
114121
def test_client_class_name(is_sync, expected):
115-
snippet = _make_configured_snippet(
116-
SPEECH_V1_REQUEST_PATH, CONFIG_JSON_PATH, api_version="v1", is_sync=is_sync
117-
)
122+
snippet = _make_configured_snippet(is_sync=is_sync)
118123
assert snippet.client_class_name == expected
119124

120125

@@ -126,12 +131,72 @@ def test_client_class_name(is_sync, expected):
126131
],
127132
)
128133
def test_filename(is_sync, expected):
129-
snippet = _make_configured_snippet(
130-
SPEECH_V1_REQUEST_PATH, CONFIG_JSON_PATH, api_version="v1", is_sync=is_sync
131-
)
134+
snippet = _make_configured_snippet(is_sync=is_sync)
132135
assert snippet.filename == expected
133136

134137

138+
@pytest.mark.parametrize(
139+
"snippet_config_dict,expected",
140+
[
141+
({}, None),
142+
(
143+
{
144+
"snippet": {
145+
"serviceClientInitialization": {
146+
"customServiceEndpoint": {
147+
"host": "speech.googleapis.com",
148+
}
149+
},
150+
}
151+
},
152+
"speech.googleapis.com",
153+
),
154+
(
155+
{
156+
"snippet": {
157+
"serviceClientInitialization": {
158+
"customServiceEndpoint": {
159+
"host": "speech.googleapis.com",
160+
"region": "us",
161+
}
162+
},
163+
}
164+
},
165+
"us-speech.googleapis.com",
166+
),
167+
(
168+
{
169+
"snippet": {
170+
"serviceClientInitialization": {
171+
"customServiceEndpoint": {
172+
"host": "speech.googleapis.com",
173+
"region": "us",
174+
"port": 123,
175+
}
176+
},
177+
}
178+
},
179+
"us-speech.googleapis.com:123",
180+
),
181+
],
182+
)
183+
def test_api_endpoint(snippet_config_dict, expected):
184+
# api_schema, api_version, and is_sync do not matter here.
185+
api_schema = _load_api_schema(SPEECH_V1_REQUEST_PATH)
186+
api_version = "v1"
187+
is_sync = True
188+
189+
snippet_config = json_format.ParseDict(
190+
snippet_config_dict, snippet_config_language_pb2.SnippetConfig()
191+
)
192+
193+
snippet = configured_snippet.ConfiguredSnippet(
194+
api_schema, snippet_config, api_version, is_sync
195+
)
196+
197+
assert snippet.api_endpoint == expected
198+
199+
135200
def test_AppendToSampleFunctionBody():
136201
# Start with a function def with nonempty body to we can be sure the
137202
# transformer appends the statement.
@@ -152,6 +217,17 @@ def test_code(snippet):
152217
# until the generated code is the same as that of the golden file.
153218
expected_code = """def sample_create_custom_class_Basic(parent = "projects/[PROJECT]/locations/us", custom_class_id = "passengerships"):
154219
\"\"
155-
client = speech_v1.AdaptationClient()
220+
client = speech_v1.AdaptationClient(client_options = {"api_endpoint": "us-speech.googleapis.com"})
156221
"""
157222
assert snippet.code == expected_code
223+
224+
225+
def test_code_without_endpoint(snippet_without_endpoint):
226+
snippet_without_endpoint.generate()
227+
228+
# https://github.com/googleapis/gapic-generator-python/issues/1522
229+
expected_code = """def sample_create_custom_class_Basic(parent = "projects/[PROJECT]/locations/us", custom_class_id = "passengerships"):
230+
\"\"
231+
client = speech_v1.AdaptationClient()
232+
"""
233+
assert snippet_without_endpoint.code == expected_code

‎packages/gapic-generator/tests/unit/configurable_snippetgen/test_libcst_utils.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,26 @@ def test_convert_parameter():
7575
)
7676

7777
assert node.deep_equals(expected_node), (node, expected_node)
78+
79+
80+
def test_convert_py_dict():
81+
key_value_pairs = [("key1", "value1"), ("key2", "value2")]
82+
node = libcst_utils.convert_py_dict(key_value_pairs)
83+
expected_node = libcst.Dict(
84+
[
85+
libcst.DictElement(
86+
libcst.SimpleString('"key1"'), libcst.SimpleString('"value1"')
87+
),
88+
libcst.DictElement(
89+
libcst.SimpleString('"key2"'), libcst.SimpleString('"value2"')
90+
),
91+
]
92+
)
93+
94+
assert node.deep_equals(expected_node), (node, expected_node)
95+
96+
97+
def test_convert_py_dict_should_raise_error_if_unsupported():
98+
key_value_pairs = [("key1", 5)]
99+
with pytest.raises(ValueError):
100+
libcst_utils.convert_py_dict(key_value_pairs)

0 commit comments

Comments
 (0)