Skip to content

Commit 52d64ff

Browse files
authored
feat: add bigframes.options.bigquery.application_name for partner attribution (#117)
Because `session.py` was getting long, this also refactors `session.py` to separate client construction in a separate module. Fixes internal issue 305950924 🦕
1 parent a6dab9c commit 52d64ff

File tree

9 files changed

+360
-165
lines changed

9 files changed

+360
-165
lines changed

bigframes/_config/bigquery_options.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,33 @@ def __init__(
3737
location: Optional[str] = None,
3838
bq_connection: Optional[str] = None,
3939
use_regional_endpoints: bool = False,
40+
application_name: Optional[str] = None,
4041
):
4142
self._credentials = credentials
4243
self._project = project
4344
self._location = location
4445
self._bq_connection = bq_connection
4546
self._use_regional_endpoints = use_regional_endpoints
47+
self._application_name = application_name
4648
self._session_started = False
4749

50+
@property
51+
def application_name(self) -> Optional[str]:
52+
"""The application name to amend to the user-agent sent to Google APIs.
53+
54+
Recommended format is ``"appplication-name/major.minor.patch_version"``
55+
or ``"(gpn:PartnerName;)"`` for official Google partners.
56+
"""
57+
return self._application_name
58+
59+
@application_name.setter
60+
def application_name(self, value: Optional[str]):
61+
if self._session_started and self._application_name != value:
62+
raise ValueError(
63+
SESSION_STARTED_MESSAGE.format(attribute="application_name")
64+
)
65+
self._application_name = value
66+
4867
@property
4968
def credentials(self) -> Optional[google.auth.credentials.Credentials]:
5069
"""The OAuth2 Credentials to use for this client."""

bigframes/pandas/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import bigframes.dataframe
5252
import bigframes.series
5353
import bigframes.session
54+
import bigframes.session.clients
5455
import third_party.bigframes_vendored.pandas.core.reshape.concat as vendored_pandas_concat
5556
import third_party.bigframes_vendored.pandas.core.reshape.merge as vendored_pandas_merge
5657
import third_party.bigframes_vendored.pandas.core.reshape.tile as vendored_pandas_tile
@@ -180,11 +181,12 @@ def _set_default_session_location_if_possible(query):
180181
):
181182
return
182183

183-
clients_provider = bigframes.session.ClientsProvider(
184+
clients_provider = bigframes.session.clients.ClientsProvider(
184185
project=options.bigquery.project,
185186
location=options.bigquery.location,
186187
use_regional_endpoints=options.bigquery.use_regional_endpoints,
187188
credentials=options.bigquery.credentials,
189+
application_name=options.bigquery.application_name,
188190
)
189191

190192
bqclient = clients_provider.bqclient

bigframes/session.py renamed to bigframes/session/__init__.py

Lines changed: 8 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@
6161
ReadPickleBuffer,
6262
StorageOptions,
6363
)
64-
import pydata_google_auth
6564

6665
import bigframes._config.bigquery_options as bigquery_options
6766
import bigframes.constants as constants
@@ -75,6 +74,7 @@
7574
import bigframes.formatting_helpers as formatting_helpers
7675
from bigframes.remote_function import read_gbq_function as bigframes_rgf
7776
from bigframes.remote_function import remote_function as bigframes_rf
77+
import bigframes.session.clients
7878
import bigframes.version
7979

8080
# Even though the ibis.backends.bigquery.registry import is unused, it's needed
@@ -85,18 +85,6 @@
8585
import third_party.bigframes_vendored.pandas.io.parsers.readers as third_party_pandas_readers
8686
import third_party.bigframes_vendored.pandas.io.pickle as third_party_pandas_pickle
8787

88-
_ENV_DEFAULT_PROJECT = "GOOGLE_CLOUD_PROJECT"
89-
_APPLICATION_NAME = f"bigframes/{bigframes.version.__version__}"
90-
_SCOPES = ["https://www.googleapis.com/auth/cloud-platform"]
91-
92-
# BigQuery is a REST API, which requires the protocol as part of the URL.
93-
_BIGQUERY_REGIONAL_ENDPOINT = "https://{location}-bigquery.googleapis.com"
94-
95-
# BigQuery Connection and Storage are gRPC APIs, which don't support the
96-
# https:// protocol in the API endpoint URL.
97-
_BIGQUERYCONNECTION_REGIONAL_ENDPOINT = "{location}-bigqueryconnection.googleapis.com"
98-
_BIGQUERYSTORAGE_REGIONAL_ENDPOINT = "{location}-bigquerystorage.googleapis.com"
99-
10088
_BIGFRAMES_DEFAULT_CONNECTION_ID = "bigframes-default-connection"
10189

10290
_MAX_CLUSTER_COLUMNS = 4
@@ -122,149 +110,6 @@ def _is_query(query_or_table: str) -> bool:
122110
return re.search(r"\s", query_or_table.strip(), re.MULTILINE) is not None
123111

124112

125-
def _get_default_credentials_with_project():
126-
return pydata_google_auth.default(scopes=_SCOPES, use_local_webserver=False)
127-
128-
129-
class ClientsProvider:
130-
"""Provides client instances necessary to perform cloud operations."""
131-
132-
def __init__(
133-
self,
134-
project: Optional[str],
135-
location: Optional[str],
136-
use_regional_endpoints: Optional[bool],
137-
credentials: Optional[google.auth.credentials.Credentials],
138-
):
139-
credentials_project = None
140-
if credentials is None:
141-
credentials, credentials_project = _get_default_credentials_with_project()
142-
143-
# Prefer the project in this order:
144-
# 1. Project explicitly specified by the user
145-
# 2. Project set in the environment
146-
# 3. Project associated with the default credentials
147-
project = (
148-
project
149-
or os.getenv(_ENV_DEFAULT_PROJECT)
150-
or typing.cast(Optional[str], credentials_project)
151-
)
152-
153-
if not project:
154-
raise ValueError(
155-
"Project must be set to initialize BigQuery client. "
156-
"Try setting `bigframes.options.bigquery.project` first."
157-
)
158-
159-
self._project = project
160-
self._location = location
161-
self._use_regional_endpoints = use_regional_endpoints
162-
self._credentials = credentials
163-
164-
# cloud clients initialized for lazy load
165-
self._bqclient = None
166-
self._bqconnectionclient = None
167-
self._bqstorageclient = None
168-
self._cloudfunctionsclient = None
169-
self._resourcemanagerclient = None
170-
171-
@property
172-
def bqclient(self):
173-
if not self._bqclient:
174-
bq_options = None
175-
if self._use_regional_endpoints:
176-
bq_options = google.api_core.client_options.ClientOptions(
177-
api_endpoint=_BIGQUERY_REGIONAL_ENDPOINT.format(
178-
location=self._location
179-
),
180-
)
181-
bq_info = google.api_core.client_info.ClientInfo(
182-
user_agent=_APPLICATION_NAME
183-
)
184-
self._bqclient = bigquery.Client(
185-
client_info=bq_info,
186-
client_options=bq_options,
187-
credentials=self._credentials,
188-
project=self._project,
189-
location=self._location,
190-
)
191-
192-
return self._bqclient
193-
194-
@property
195-
def bqconnectionclient(self):
196-
if not self._bqconnectionclient:
197-
bqconnection_options = None
198-
if self._use_regional_endpoints:
199-
bqconnection_options = google.api_core.client_options.ClientOptions(
200-
api_endpoint=_BIGQUERYCONNECTION_REGIONAL_ENDPOINT.format(
201-
location=self._location
202-
)
203-
)
204-
bqconnection_info = google.api_core.gapic_v1.client_info.ClientInfo(
205-
user_agent=_APPLICATION_NAME
206-
)
207-
self._bqconnectionclient = (
208-
google.cloud.bigquery_connection_v1.ConnectionServiceClient(
209-
client_info=bqconnection_info,
210-
client_options=bqconnection_options,
211-
credentials=self._credentials,
212-
)
213-
)
214-
215-
return self._bqconnectionclient
216-
217-
@property
218-
def bqstorageclient(self):
219-
if not self._bqstorageclient:
220-
bqstorage_options = None
221-
if self._use_regional_endpoints:
222-
bqstorage_options = google.api_core.client_options.ClientOptions(
223-
api_endpoint=_BIGQUERYSTORAGE_REGIONAL_ENDPOINT.format(
224-
location=self._location
225-
)
226-
)
227-
bqstorage_info = google.api_core.gapic_v1.client_info.ClientInfo(
228-
user_agent=_APPLICATION_NAME
229-
)
230-
self._bqstorageclient = google.cloud.bigquery_storage_v1.BigQueryReadClient(
231-
client_info=bqstorage_info,
232-
client_options=bqstorage_options,
233-
credentials=self._credentials,
234-
)
235-
236-
return self._bqstorageclient
237-
238-
@property
239-
def cloudfunctionsclient(self):
240-
if not self._cloudfunctionsclient:
241-
functions_info = google.api_core.gapic_v1.client_info.ClientInfo(
242-
user_agent=_APPLICATION_NAME
243-
)
244-
self._cloudfunctionsclient = (
245-
google.cloud.functions_v2.FunctionServiceClient(
246-
client_info=functions_info,
247-
credentials=self._credentials,
248-
)
249-
)
250-
251-
return self._cloudfunctionsclient
252-
253-
@property
254-
def resourcemanagerclient(self):
255-
if not self._resourcemanagerclient:
256-
resourcemanager_info = google.api_core.gapic_v1.client_info.ClientInfo(
257-
user_agent=_APPLICATION_NAME
258-
)
259-
self._resourcemanagerclient = (
260-
google.cloud.resourcemanager_v3.ProjectsClient(
261-
credentials=self._credentials, client_info=resourcemanager_info
262-
)
263-
)
264-
265-
return self._resourcemanagerclient
266-
267-
268113
class Session(
269114
third_party_pandas_gbq.GBQIOMixin,
270115
third_party_pandas_parquet.ParquetIOMixin,
@@ -279,14 +124,14 @@ class Session(
279124
Configuration adjusting how to connect to BigQuery and related
280125
APIs. Note that some options are ignored if ``clients_provider`` is
281126
set.
282-
clients_provider (bigframes.session.ClientsProvider):
127+
clients_provider (bigframes.session.bigframes.session.clients.ClientsProvider):
283128
An object providing client library objects.
284129
"""
285130

286131
def __init__(
287132
self,
288133
context: Optional[bigquery_options.BigQueryOptions] = None,
289-
clients_provider: Optional[ClientsProvider] = None,
134+
clients_provider: Optional[bigframes.session.clients.ClientsProvider] = None,
290135
):
291136
if context is None:
292137
context = bigquery_options.BigQueryOptions()
@@ -306,11 +151,12 @@ def __init__(
306151
if clients_provider:
307152
self._clients_provider = clients_provider
308153
else:
309-
self._clients_provider = ClientsProvider(
154+
self._clients_provider = bigframes.session.clients.ClientsProvider(
310155
project=context.project,
311156
location=self._location,
312157
use_regional_endpoints=context.use_regional_endpoints,
313158
credentials=context.credentials,
159+
application_name=context.application_name,
314160
)
315161

316162
self._create_and_bind_bq_session()
@@ -319,7 +165,7 @@ def __init__(
319165
ibis.bigquery.connect(
320166
project_id=context.project,
321167
client=self.bqclient,
322-
storage_client=self.bqstorageclient,
168+
storage_client=self.bqstoragereadclient,
323169
),
324170
)
325171

@@ -338,8 +184,8 @@ def bqconnectionclient(self):
338184
return self._clients_provider.bqconnectionclient
339185

340186
@property
341-
def bqstorageclient(self):
342-
return self._clients_provider.bqstorageclient
187+
def bqstoragereadclient(self):
188+
return self._clients_provider.bqstoragereadclient
343189

344190
@property
345191
def cloudfunctionsclient(self):

0 commit comments

Comments
 (0)