diff --git a/python/python/lancedb/functions.py b/python/python/lancedb/functions.py index 2ab658293..c22bf927a 100644 --- a/python/python/lancedb/functions.py +++ b/python/python/lancedb/functions.py @@ -674,7 +674,7 @@ class RefreshColumnResult(_RemoteValue): return self.published_version -_FUNCTION_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_.-]*$") +_FUNCTION_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") _FUNCTION_BLOB_V2_TYPE = "blob_v2" _ARROW_EXTENSION_NAME_KEY = "ARROW:extension:name" @@ -2106,6 +2106,8 @@ def udf( The synchronous callable, or the class whose instances are called. name : str, optional The remote Function name. Defaults to the callable name. + Must start with an ASCII letter or underscore and contain only ASCII + letters, digits, and underscores. input_schema : pyarrow.Schema, optional Explicit input fields in the exact order of the callable parameters. Must be provided together with ``output_schema``. diff --git a/python/python/tests/test_first_class_function_slice2.py b/python/python/tests/test_first_class_function_slice2.py index 61e511345..392becf8c 100644 --- a/python/python/tests/test_first_class_function_slice2.py +++ b/python/python/tests/test_first_class_function_slice2.py @@ -89,6 +89,27 @@ def test_scalar_udf_matches_shared_registration_golden_and_remains_callable(): } +@pytest.mark.parametrize( + "name", ["examples.image_info", "image-info", "image_info.", "image_info-"] +) +def test_udf_rejects_function_names_with_periods_or_hyphens(name): + with pytest.raises(ValueError, match="invalid Function name"): + + @udf(name=name) + def image_info(value: int) -> int: + return value + + +@pytest.mark.parametrize("name", ["image_info", "ImageInfo123", "_image_info", "f"]) +def test_udf_accepts_function_names_with_letters_digits_and_underscores(name): + @udf(name=name) + def image_info(value: int) -> int: + return value + + assert image_info.registration_request.name == name + assert image_info(1) == 1 + + def test_secret_bound_udf_matches_its_shared_registration_golden(): assert analyze_caption(" hello ") == "hello" bound = analyze_caption.bind_secrets(