Skip to content

Commit 3a0dbe1

Browse files
authored
chore: add feedback link to all functions exceptions (#1470)
* chore: add feedback link to all functions exceptions * expect feedback link in the test exception * expect feedback link in test exceptions * use f-string instead of format, update one more test to expect feedback link
1 parent b1b612e commit 3a0dbe1

File tree

9 files changed

+114
-69
lines changed

9 files changed

+114
-69
lines changed

bigframes/dataframe.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4234,11 +4234,12 @@ def apply(self, func, *, axis=0, args: typing.Tuple = (), **kwargs):
42344234
# At this point column-wise or element-wise remote function operation will
42354235
# be performed (not supported).
42364236
if hasattr(func, "bigframes_remote_function"):
4237-
raise NotImplementedError(
4237+
raise formatter.create_exception_with_feedback_link(
4238+
NotImplementedError,
42384239
"BigFrames DataFrame '.apply()' does not support remote function "
42394240
"for column-wise (i.e. with axis=0) operations, please use a "
42404241
"regular python function instead. For element-wise operations of "
4241-
"the remote function, please use '.map()'."
4242+
"the remote function, please use '.map()'.",
42424243
)
42434244

42444245
# Per-column apply

bigframes/formatting_helpers.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import datetime
1919
import random
20-
from typing import Any, Optional, Union
20+
from typing import Any, Optional, Type, Union
2121

2222
import bigframes_vendored.constants as constants
2323
import google.api_core.exceptions as api_core_exceptions
@@ -48,6 +48,16 @@ def add_feedback_link(
4848
exception.message = exception.message + f" {constants.FEEDBACK_LINK}"
4949

5050

51+
def create_exception_with_feedback_link(
52+
exception: Type[Exception],
53+
arg: str = "",
54+
):
55+
if arg:
56+
return exception(arg + f" {constants.FEEDBACK_LINK}")
57+
58+
return exception(constants.FEEDBACK_LINK)
59+
60+
5161
def repr_query_job_html(query_job: Optional[bigquery.QueryJob]):
5262
"""Return query job in html format.
5363
Args:

bigframes/functions/_function_client.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
import types
2727
from typing import cast, Tuple, TYPE_CHECKING
2828

29-
from bigframes_vendored import constants
3029
import requests
3130

31+
import bigframes.formatting_helpers as bf_formatting
3232
import bigframes.functions.function_template as bff_template
3333

3434
if TYPE_CHECKING:
@@ -366,10 +366,9 @@ def create_cloud_function(
366366
headers={"content-type": "application/zip"},
367367
)
368368
if response.status_code != 200:
369-
raise RuntimeError(
370-
"Failed to upload user code. code={}, reason={}, text={}".format(
371-
response.status_code, response.reason, response.text
372-
)
369+
raise bf_formatting.create_exception_with_feedback_link(
370+
RuntimeError,
371+
f"Failed to upload user code. code={response.status_code}, reason={response.reason}, text={response.text}",
373372
)
374373

375374
# Deploy Cloud Function
@@ -399,10 +398,11 @@ def create_cloud_function(
399398
function.service_config.available_memory = f"{memory_mib}Mi"
400399
if timeout_seconds is not None:
401400
if timeout_seconds > 1200:
402-
raise ValueError(
401+
raise bf_formatting.create_exception_with_feedback_link(
402+
ValueError,
403403
"BigQuery remote function can wait only up to 20 minutes"
404404
", see for more details "
405-
"https://cloud.google.com/bigquery/quotas#remote_function_limits."
405+
"https://cloud.google.com/bigquery/quotas#remote_function_limits.",
406406
)
407407
function.service_config.timeout_seconds = timeout_seconds
408408
if max_instance_count is not None:
@@ -413,10 +413,9 @@ def create_cloud_function(
413413
self._cloud_function_service_account
414414
)
415415
if ingress_settings not in _INGRESS_SETTINGS_MAP:
416-
raise ValueError(
417-
"'{}' not one of the supported ingress settings values: {}".format(
418-
ingress_settings, list(_INGRESS_SETTINGS_MAP)
419-
)
416+
raise bf_formatting.create_exception_with_feedback_link(
417+
ValueError,
418+
f"'{ingress_settings}' not one of the supported ingress settings values: {list(_INGRESS_SETTINGS_MAP)}",
420419
)
421420
function.service_config.ingress_settings = cast(
422421
functions_v2.ServiceConfig.IngressSettings,
@@ -447,8 +446,8 @@ def create_cloud_function(
447446
# Fetch the endpoint of the just created function
448447
endpoint = self.get_cloud_function_endpoint(cf_name)
449448
if not endpoint:
450-
raise ValueError(
451-
f"Couldn't fetch the http endpoint. {constants.FEEDBACK_LINK}"
449+
raise bf_formatting.create_exception_with_feedback_link(
450+
ValueError, "Couldn't fetch the http endpoint."
452451
)
453452

454453
logger.info(
@@ -541,8 +540,9 @@ def provision_bq_remote_function(
541540
):
542541
input_args = inspect.getargs(def_.__code__).args
543542
if len(input_args) != len(input_types):
544-
raise ValueError(
545-
"Exactly one type should be provided for every input arg."
543+
raise bf_formatting.create_exception_with_feedback_link(
544+
ValueError,
545+
"Exactly one type should be provided for every input arg.",
546546
)
547547
self.create_bq_remote_function(
548548
input_args,

bigframes/functions/_function_session.py

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
)
3434
import warnings
3535

36-
import bigframes_vendored.constants as constants
3736
import bigframes_vendored.ibis.backends.bigquery.datatypes as third_party_ibis_bqtypes
3837
import bigframes_vendored.ibis.expr.datatypes as ibis_dtypes
3938
import bigframes_vendored.ibis.expr.operations.udf as ibis_udf
@@ -49,6 +48,7 @@
4948
from bigframes import clients
5049
import bigframes.core.compile.ibis_types
5150
import bigframes.exceptions as bfe
51+
import bigframes.formatting_helpers as bf_formatting
5252
import bigframes.series as bf_series
5353

5454
if TYPE_CHECKING:
@@ -87,9 +87,10 @@ def _resolve_bigquery_client(
8787
if not bigquery_client:
8888
bigquery_client = session.bqclient
8989
if not bigquery_client:
90-
raise ValueError(
90+
raise bf_formatting.create_exception_with_feedback_link(
91+
ValueError,
9192
"A bigquery client must be provided, either directly or via "
92-
f"session. {constants.FEEDBACK_LINK}"
93+
"session.",
9394
)
9495
return bigquery_client
9596

@@ -104,9 +105,10 @@ def _resolve_bigquery_connection_client(
104105
if not bigquery_connection_client:
105106
bigquery_connection_client = session.bqconnectionclient
106107
if not bigquery_connection_client:
107-
raise ValueError(
108+
raise bf_formatting.create_exception_with_feedback_link(
109+
ValueError,
108110
"A bigquery connection client must be provided, either "
109-
f"directly or via session. {constants.FEEDBACK_LINK}"
111+
"directly or via session.",
110112
)
111113
return bigquery_connection_client
112114

@@ -119,9 +121,10 @@ def _resolve_resource_manager_client(
119121
if not resource_manager_client:
120122
resource_manager_client = session.resourcemanagerclient
121123
if not resource_manager_client:
122-
raise ValueError(
124+
raise bf_formatting.create_exception_with_feedback_link(
125+
ValueError,
123126
"A resource manager client must be provided, either directly "
124-
f"or via session. {constants.FEEDBACK_LINK}"
127+
"or via session.",
125128
)
126129
return resource_manager_client
127130

@@ -149,9 +152,10 @@ def _resolve_cloud_functions_client(
149152
if not cloud_functions_client:
150153
cloud_functions_client = session.cloudfunctionsclient
151154
if not cloud_functions_client:
152-
raise ValueError(
155+
raise bf_formatting.create_exception_with_feedback_link(
156+
ValueError,
153157
"A cloud functions client must be provided, either directly "
154-
f"or via session. {constants.FEEDBACK_LINK}"
158+
"or via session.",
155159
)
156160
return cloud_functions_client
157161

@@ -178,14 +182,16 @@ def _resolve_bigquery_connection_id(
178182
bq_connection_id,
179183
) = bigquery_connection.split(".")
180184
if gcp_project_id.casefold() != dataset_ref.project.casefold():
181-
raise ValueError(
185+
raise bf_formatting.create_exception_with_feedback_link(
186+
ValueError,
182187
"The project_id does not match BigQuery connection "
183-
f"gcp_project_id: {dataset_ref.project}."
188+
f"gcp_project_id: {dataset_ref.project}.",
184189
)
185190
if bq_connection_location.casefold() != bq_location.casefold():
186-
raise ValueError(
191+
raise bf_formatting.create_exception_with_feedback_link(
192+
ValueError,
187193
"The location does not match BigQuery connection location: "
188-
f"{bq_location}."
194+
f"{bq_location}.",
189195
)
190196
return bq_connection_id
191197

@@ -506,9 +512,10 @@ def remote_function(
506512
cloud_function_kms_key_name is not None
507513
and cloud_function_docker_repository is None
508514
):
509-
raise ValueError(
515+
raise bf_formatting.create_exception_with_feedback_link(
516+
ValueError,
510517
"cloud_function_docker_repository must be specified with cloud_function_kms_key_name."
511-
" For more details see https://cloud.google.com/functions/docs/securing/cmek#before_you_begin"
518+
" For more details see https://cloud.google.com/functions/docs/securing/cmek#before_you_begin.",
512519
)
513520

514521
if cloud_function_ingress_settings is None:
@@ -521,13 +528,25 @@ def remote_function(
521528
)
522529
warnings.warn(msg, category=FutureWarning, stacklevel=2)
523530

531+
if cloud_function_ingress_settings is None:
532+
cloud_function_ingress_settings = "all"
533+
msg = bfe.format_message(
534+
"The `cloud_function_ingress_settings` are set to 'all' by default, "
535+
"which will change to 'internal-only' for enhanced security in future version 2.0 onwards. "
536+
"However, you will be able to explicitly pass cloud_function_ingress_settings='all' if you need. "
537+
"See https://cloud.google.com/functions/docs/networking/network-settings#ingress_settings for details."
538+
)
539+
warnings.warn(msg, category=FutureWarning, stacklevel=2)
540+
524541
bq_connection_manager = session.bqconnectionmanager
525542

526543
def wrapper(func):
527544
nonlocal input_types, output_type
528545

529546
if not callable(func):
530-
raise TypeError("f must be callable, got {}".format(func))
547+
raise bf_formatting.create_exception_with_feedback_link(
548+
TypeError, f"func must be a callable, got {func}"
549+
)
531550

532551
if sys.version_info >= (3, 10):
533552
# Add `eval_str = True` so that deferred annotations are turned into their
@@ -547,10 +566,11 @@ def wrapper(func):
547566
input_types = []
548567
for parameter in signature.parameters.values():
549568
if (param_type := parameter.annotation) is inspect.Signature.empty:
550-
raise ValueError(
569+
raise bf_formatting.create_exception_with_feedback_link(
570+
ValueError,
551571
"'input_types' was not set and parameter "
552572
f"'{parameter.name}' is missing a type annotation. "
553-
"Types are required to use @remote_function."
573+
"Types are required to use @remote_function.",
554574
)
555575
input_types.append(param_type)
556576
elif not isinstance(input_types, collections.abc.Sequence):
@@ -560,10 +580,11 @@ def wrapper(func):
560580
if (
561581
output_type := signature.return_annotation
562582
) is inspect.Signature.empty:
563-
raise ValueError(
583+
raise bf_formatting.create_exception_with_feedback_link(
584+
ValueError,
564585
"'output_type' was not set and function is missing a "
565586
"return type annotation. Types are required to use "
566-
"@remote_function."
587+
"@remote_function.",
567588
)
568589

569590
# The function will actually be receiving a pandas Series, but allow both
@@ -789,14 +810,15 @@ def udf(
789810
https://pip.pypa.io/en/stable/reference/requirements-file-format/.
790811
"""
791812
if not bigframes.options.experiments.udf:
792-
raise NotImplementedError()
813+
raise bf_formatting.create_exception_with_feedback_link(NotImplementedError)
793814

794815
# Check the Python version.
795816
python_version = _utils.get_python_version()
796817
if python_version not in _MANAGED_FUNC_PYTHON_VERSIONS:
797-
raise RuntimeError(
818+
raise bf_formatting.create_exception_with_feedback_link(
819+
RuntimeError,
798820
f"Python version {python_version} is not supported yet for "
799-
"BigFrames managed function."
821+
"BigFrames managed function.",
800822
)
801823

802824
# Some defaults may be used from the session if not provided otherwise.
@@ -823,7 +845,9 @@ def wrapper(func):
823845
nonlocal input_types, output_type
824846

825847
if not callable(func):
826-
raise TypeError("f must be callable, got {}".format(func))
848+
raise bf_formatting.create_exception_with_feedback_link(
849+
TypeError, f"func must be a callable, got {func}"
850+
)
827851

828852
# Managed function supports version >= 3.11.
829853
signature_kwargs: Mapping[str, Any] = {"eval_str": True}
@@ -834,10 +858,11 @@ def wrapper(func):
834858
input_types = []
835859
for parameter in signature.parameters.values():
836860
if (param_type := parameter.annotation) is inspect.Signature.empty:
837-
raise ValueError(
861+
raise bf_formatting.create_exception_with_feedback_link(
862+
ValueError,
838863
"'input_types' was not set and parameter "
839864
f"'{parameter.name}' is missing a type annotation. "
840-
"Types are required to use managed function."
865+
"Types are required to use managed function.",
841866
)
842867
input_types.append(param_type)
843868
elif not isinstance(input_types, collections.abc.Sequence):
@@ -847,10 +872,11 @@ def wrapper(func):
847872
if (
848873
output_type := signature.return_annotation
849874
) is inspect.Signature.empty:
850-
raise ValueError(
875+
raise bf_formatting.create_exception_with_feedback_link(
876+
ValueError,
851877
"'output_type' was not set and function is missing a "
852878
"return type annotation. Types are required to use "
853-
"managed function."
879+
"managed function.",
854880
)
855881

856882
# The function will actually be receiving a pandas Series, but allow

bigframes/functions/_utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import bigframes.core.compile.ibis_types
3232
import bigframes.dtypes
33+
import bigframes.formatting_helpers as bf_formatting
3334

3435
# Naming convention for the function artifacts
3536
_BIGFRAMES_FUNCTION_PREFIX = "bigframes"
@@ -276,8 +277,8 @@ def get_bigframes_metadata(*, python_output_type: Optional[type] = None) -> str:
276277
get_python_output_type_from_bigframes_metadata(metadata_ser)
277278
!= python_output_type
278279
):
279-
raise ValueError(
280-
f"python_output_type {python_output_type} is not serializable."
280+
raise bf_formatting.create_exception_with_feedback_link(
281+
ValueError, f"python_output_type {python_output_type} is not serializable."
281282
)
282283

283284
return metadata_ser

0 commit comments

Comments
 (0)