diff --git a/python/python/lancedb/functions.py b/python/python/lancedb/functions.py index dd8cecfa9..237ed73c2 100644 --- a/python/python/lancedb/functions.py +++ b/python/python/lancedb/functions.py @@ -4,7 +4,7 @@ """Canonical Function values exchanged with LanceDB Enterprise services. These immutable models contain client/wire state only. Catalog persistence, -environment bake, secret resolution, and execution are owned by Sophon. +environment bake, and execution are owned by Sophon. ``RefreshColumnResult`` is also the backend-neutral result of a local expression-backed refresh job. """ @@ -228,7 +228,7 @@ class PythonEnvironmentSpec(_RemoteValue): class PythonRuntimeSpec(_RemoteValue): - """Remote runtime definition with non-secret environment values. + """Remote runtime definition with environment values. V1 supports ``kind="python"``. Newer runtime kinds remain readable, while their unknown payload fields are intentionally not retained by the client. @@ -267,7 +267,6 @@ class FunctionVersion(_RemoteValue): runtime: PythonRuntimeSpec runtime_digest: str environment_digest: str - required_secrets: tuple[str, ...] = () created_at: str def __call__(self, **inputs: Any) -> FunctionApplication: @@ -329,17 +328,12 @@ class FunctionVersion(_RemoteValue): class FunctionRegistrationRequest(_RemoteValue): - """Stable remote registration envelope produced by :func:`udf`. - - Only secret names are represented. Secret values are resolved inside the - remote service and have no client request field. - """ + """Stable remote registration envelope produced by :func:`udf`.""" name: str artifact: FunctionArtifactRequest signature: FunctionSignature runtime: PythonRuntimeSpec - required_secrets: tuple[str, ...] = () class FunctionVersionRef(_OpenRemoteValue): @@ -484,7 +478,6 @@ class RefreshColumnResult(_RemoteValue): _FUNCTION_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_.-]*$") -_SECRET_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") _GRAMMAR_PRIMITIVES = ( @@ -915,7 +908,6 @@ class UdfDefinition: output_schema: Optional[pa.DataType | pa.Field | pa.Schema], pip: tuple[str, ...], env: Mapping[str, str], - secrets: tuple[str, ...], python_version: Optional[str], ): function_name = name or function.__name__ @@ -930,18 +922,6 @@ class UdfDefinition: for key, value in environment.items() ): raise TypeError("Function env keys and values must be strings") - required_secrets = tuple(sorted(set(secrets))) - invalid_secrets = [ - secret for secret in required_secrets if not _SECRET_NAME.fullmatch(secret) - ] - if invalid_secrets: - raise ValueError(f"invalid Function secret names: {invalid_secrets!r}") - overlap = set(environment) & set(required_secrets) - if overlap: - raise ValueError( - f"Function env and secret names must be disjoint: {sorted(overlap)!r}" - ) - signature = _infer_signature(function, input_schema, output_schema) source = _package_source(function) digest = f"sha256:{hashlib.sha256(source).hexdigest()}" @@ -970,7 +950,6 @@ class UdfDefinition: ), signature=signature, runtime=runtime, - required_secrets=required_secrets, ) functools.update_wrapper(self, function) @@ -996,7 +975,6 @@ def udf( output_schema: Optional[pa.DataType | pa.Field | pa.Schema] = None, pip: tuple[str, ...] | list[str] = (), env: Optional[Mapping[str, str]] = None, - secrets: tuple[str, ...] | list[str] = (), python_version: Optional[str] = None, ) -> Callable[[Callable[..., Any]], UdfDefinition]: ... @@ -1009,7 +987,6 @@ def udf( output_schema: Optional[pa.DataType | pa.Field | pa.Schema] = None, pip: tuple[str, ...] | list[str] = (), env: Optional[Mapping[str, str]] = None, - secrets: tuple[str, ...] | list[str] = (), python_version: Optional[str] = None, ): """Prepare a scalar Python callable for remote Function registration. @@ -1034,10 +1011,7 @@ def udf( pip : sequence of str, optional Pip requirements for the remote environment. env : mapping of str to str, optional - Non-secret environment variables. Use ``secrets`` for credentials. - secrets : sequence of str, optional - Names of secrets resolved by the remote service. Secret values are not - accepted by this API or included in the registration request. + Environment variables included in the Function definition. python_version : str, optional Remote Python major/minor version. Defaults to the client version. @@ -1059,7 +1033,7 @@ def udf( Examples -------- >>> from lancedb import udf - >>> @udf(pip=["numpy==2.2.0"], secrets=["MODEL_TOKEN"]) + >>> @udf(pip=["numpy==2.2.0"]) ... def score(value: float) -> float: ... return value * 2 >>> score(1.5) @@ -1074,7 +1048,6 @@ def udf( output_schema=output_schema, pip=tuple(pip), env={} if env is None else env, - secrets=tuple(secrets), python_version=python_version, ) diff --git a/python/python/tests/test_first_class_function_slice1.py b/python/python/tests/test_first_class_function_slice1.py index ca0b30ede..89172ba3f 100644 --- a/python/python/tests/test_first_class_function_slice1.py +++ b/python/python/tests/test_first_class_function_slice1.py @@ -37,21 +37,6 @@ def job_result(name: str) -> dict: return json.loads(fixture(name))["result"] -def assert_no_secret_values(value): - if isinstance(value, dict): - for key, child in value.items(): - assert key not in { - "secret_value", - "secret_values", - "resolved_secret", - "resolved_secrets", - } - assert_no_secret_values(child) - elif isinstance(value, list): - for child in value: - assert_no_secret_values(child) - - def test_public_function_values_are_in_api_reference(): docs = Path(__file__).parents[3] / "docs" / "src" / "python" / "python.md" rendered = docs.read_text() @@ -109,7 +94,6 @@ def test_function_version_identity_is_immutable_and_exact(): version = FunctionVersion.from_json(json.dumps(value)) assert version.name == "embed" assert version.version == "fv_01K3EXACT" - assert version.required_secrets == ("HF_TOKEN",) with pytest.raises((TypeError, ValueError)): version.version = "fv_changed" @@ -292,15 +276,6 @@ def test_refresh_result_rejects_non_u64_values(field): RefreshColumnResult.from_json(json.dumps(value)) -def test_canonical_client_values_contain_secret_names_only(): - version = FunctionVersion.from_json( - json.dumps(job_result("remote_function_job.json")) - ) - canonical = json.loads(version.to_canonical_json()) - assert canonical["required_secrets"] == ["HF_TOKEN"] - assert_no_secret_values(canonical) - - class _FunctionDeclarationInner: def __init__(self): self.calls = [] diff --git a/python/python/tests/test_first_class_function_slice2.py b/python/python/tests/test_first_class_function_slice2.py index c67e17520..55257d322 100644 --- a/python/python/tests/test_first_class_function_slice2.py +++ b/python/python/tests/test_first_class_function_slice2.py @@ -39,28 +39,12 @@ FIXTURES = ( @udf( pip=["numpy>=2"], env={"MODE": "test"}, - secrets=["API_TOKEN"], python_version="3.12", ) def normalize_score(value: float) -> float: return value / 100.0 -def _assert_no_secret_values(value): - if isinstance(value, dict): - for key, child in value.items(): - assert key not in { - "secret_value", - "secret_values", - "resolved_secret", - "resolved_secrets", - } - _assert_no_secret_values(child) - elif isinstance(value, list): - for child in value: - _assert_no_secret_values(child) - - def test_scalar_udf_matches_shared_registration_golden_and_remains_callable(): assert isinstance(normalize_score, UdfDefinition) assert normalize_score(25.0) == 0.25 @@ -75,8 +59,6 @@ def test_scalar_udf_matches_shared_registration_golden_and_remains_callable(): "kind": "scalar_to_arrow_batch", "version": 1, } - assert request["required_secrets"] == ["API_TOKEN"] - _assert_no_secret_values(request) def _run_packaged(definition, *args): @@ -370,7 +352,6 @@ def test_udf_recursion_versus_a_rebound_module_name(tmp_path): output_schema=None, pip=(), env={}, - secrets=(), python_version=None, ) with pytest.raises(ValueError, match="binds that name to another value"): @@ -525,14 +506,6 @@ def test_annotation_and_explicit_schema_validation_fail_closed(): return value -def test_environment_rejects_secret_value_overlap(): - with pytest.raises(ValueError, match="must be disjoint"): - - @udf(env={"TOKEN": "plaintext"}, secrets=["TOKEN"]) - def overlapping(value: int) -> int: - return value - - def test_local_function_catalog_operations_are_not_supported(tmp_path): db = lancedb.connect(tmp_path) message = "Function catalog operations are not supported by this database" @@ -569,7 +542,6 @@ def _mock_remote_function_catalog(): "runtime": body["runtime"], "runtime_digest": "sha256:runtime", "environment_digest": "sha256:environment", - "required_secrets": body.get("required_secrets", []), "created_at": "2026-08-21T00:00:00Z", } response = {"job_id": "job-register"} @@ -628,7 +600,6 @@ def test_remote_registration_job_and_exact_version_reopen_round_trip(): assert create_request == json.loads( normalize_score.registration_request.to_canonical_json() ) - _assert_no_secret_values(create_request) def test_blocking_remote_registration_returns_function_version(): diff --git a/rust/lancedb/src/function.rs b/rust/lancedb/src/function.rs index 433a7e04c..52c70a4b1 100644 --- a/rust/lancedb/src/function.rs +++ b/rust/lancedb/src/function.rs @@ -5,7 +5,7 @@ //! backend-neutral terminal result of a computed-column refresh. //! //! This module contains client/wire values only. Catalog persistence, -//! environment bake, secret resolution, and execution are owned by Sophon. +//! environment bake, and execution are owned by Sophon. use std::collections::BTreeMap; @@ -195,9 +195,6 @@ pub struct PythonEnvironmentSpec { } /// Reproducible Python runtime definition understood by Sophon. -/// -/// `env` contains non-secret values. Secret values have no client model; -/// [`FunctionVersion::required_secrets`] contains names only. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum PythonRuntimeSpec { @@ -239,7 +236,7 @@ impl PythonRuntimeSpec { } } - /// Non-secret environment variables, or `None` for an unknown kind. + /// Environment variables, or `None` for an unknown kind. pub fn env(&self) -> Option<&BTreeMap> { match self { Self::Python { env, .. } => Some(env), @@ -324,8 +321,6 @@ pub struct FunctionVersion { runtime: PythonRuntimeSpec, runtime_digest: String, environment_digest: String, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - required_secrets: Vec, created_at: String, } @@ -358,11 +353,6 @@ impl FunctionVersion { &self.environment_digest } - /// Required secret names. Resolved values exist only inside Sophon. - pub fn required_secrets(&self) -> &[String] { - &self.required_secrets - } - pub fn created_at(&self) -> &str { &self.created_at } @@ -404,18 +394,12 @@ pub struct FunctionArtifactRequest { } /// Stable request envelope for remote immutable Function registration. -/// -/// Secret values deliberately have no field in this model. The only secret -/// material the client may send is the ordered set of names Sophon resolves -/// inside the remote runtime. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct FunctionRegistrationRequest { pub name: String, pub artifact: FunctionArtifactRequest, pub signature: FunctionSignature, pub runtime: PythonRuntimeSpec, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub required_secrets: Vec, } impl_json!(FunctionRegistrationRequest); diff --git a/rust/lancedb/tests/first_class_function_slice1.rs b/rust/lancedb/tests/first_class_function_slice1.rs index aec264650..ce020bd53 100644 --- a/rust/lancedb/tests/first_class_function_slice1.rs +++ b/rust/lancedb/tests/first_class_function_slice1.rs @@ -20,25 +20,6 @@ fn job_result(name: &str) -> Value { serde_json::from_str::(&fixture(name)).expect("remote Job fixture")["result"].clone() } -fn assert_no_secret_values(value: &Value) { - match value { - Value::Object(values) => { - for (key, value) in values { - assert!( - !matches!( - key.as_str(), - "secret_value" | "secret_values" | "resolved_secret" | "resolved_secrets" - ), - "client canonical value must not model resolved secret material" - ); - assert_no_secret_values(value); - } - } - Value::Array(values) => values.iter().for_each(assert_no_secret_values), - _ => {} - } -} - #[test] fn function_version_job_result_matches_shared_canonical_golden() { let result = job_result("remote_function_job.json"); @@ -47,7 +28,6 @@ fn function_version_job_result_matches_shared_canonical_golden() { assert_eq!(version.name(), "embed"); assert_eq!(version.version(), "fv_01K3EXACT"); assert_eq!(version.runtime_digest(), "sha256:runtime"); - assert_eq!(version.required_secrets(), &["HF_TOKEN"]); assert_eq!( version.to_canonical_json().expect("canonical JSON"), fixture("remote_function_version.canonical.json").trim() @@ -162,21 +142,3 @@ fn floating_point_application_literals_are_rejected_consistently() { .contains("floating-point Function literals") ); } - -#[test] -fn canonical_client_values_contain_secret_names_only() { - let result = job_result("remote_function_job.json"); - let version = FunctionVersion::from_json(&result.to_string()).expect("FunctionVersion result"); - let canonical: Value = serde_json::from_str( - &version - .to_canonical_json() - .expect("canonical FunctionVersion"), - ) - .expect("canonical JSON"); - - assert_eq!( - canonical["required_secrets"], - serde_json::json!(["HF_TOKEN"]) - ); - assert_no_secret_values(&canonical); -} diff --git a/rust/lancedb/tests/first_class_function_slice2.rs b/rust/lancedb/tests/first_class_function_slice2.rs index 3bae57122..93252dde4 100644 --- a/rust/lancedb/tests/first_class_function_slice2.rs +++ b/rust/lancedb/tests/first_class_function_slice2.rs @@ -6,7 +6,6 @@ use std::path::PathBuf; use lancedb::Error; use lancedb::function::FunctionRegistrationRequest; -use serde_json::Value; fn fixture(name: &str) -> String { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) @@ -15,25 +14,6 @@ fn fixture(name: &str) -> String { fs::read_to_string(path).expect("fixture must be readable") } -fn assert_no_secret_values(value: &Value) { - match value { - Value::Object(values) => { - for (key, value) in values { - assert!( - !matches!( - key.as_str(), - "secret_value" | "secret_values" | "resolved_secret" | "resolved_secrets" - ), - "registration requests must not model resolved secret material" - ); - assert_no_secret_values(value); - } - } - Value::Array(values) => values.iter().for_each(assert_no_secret_values), - _ => {} - } -} - #[test] fn registration_request_matches_shared_canonical_golden() { let request = FunctionRegistrationRequest::from_json(&fixture( @@ -42,16 +22,10 @@ fn registration_request_matches_shared_canonical_golden() { .expect("registration request"); assert_eq!(request.name, "normalize_score"); assert_eq!(request.artifact.adapter.kind, "scalar_to_arrow_batch"); - assert_eq!(request.required_secrets, ["API_TOKEN"]); assert_eq!( request.to_canonical_json().expect("canonical request"), fixture("remote_function_registration_request.canonical.json").trim() ); - - let value: Value = - serde_json::from_str(&request.to_canonical_json().expect("canonical request")) - .expect("request JSON"); - assert_no_secret_values(&value); } #[tokio::test] diff --git a/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_job.json b/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_job.json index 6ba4eb226..39a279692 100644 --- a/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_job.json +++ b/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_job.json @@ -24,7 +24,6 @@ }, "runtime_digest": "sha256:runtime", "environment_digest": "sha256:environment", - "required_secrets": ["HF_TOKEN"], "created_at": "2026-08-21T00:00:00Z" }, "future_job": {"trace_id": "trace-1"} diff --git a/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_registration_request.canonical.json b/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_registration_request.canonical.json index 24fa2cf30..a2f2c4c21 100644 --- a/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_registration_request.canonical.json +++ b/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_registration_request.canonical.json @@ -1 +1 @@ -{"artifact":{"adapter":{"kind":"scalar_to_arrow_batch","version":1},"content":{"data":"ZnJvbSBfX2Z1dHVyZV9fIGltcG9ydCBhbm5vdGF0aW9ucwoKZGVmIG5vcm1hbGl6ZV9zY29yZSh2YWx1ZTogZmxvYXQpIC0+IGZsb2F0OgogICAgcmV0dXJuIHZhbHVlIC8gMTAwLjAK","encoding":"base64"},"digest":"sha256:760784bdcef57b802f389b97804cc0b618aae39e86044733451bcd0b13089a7f","entrypoint":"normalize_score","kind":"python_callable"},"name":"normalize_score","required_secrets":["API_TOKEN"],"runtime":{"env":{"MODE":"test"},"environment":{"kind":"pip","packages":["numpy>=2"]},"kind":"python","python_version":"3.12"},"signature":{"inputs":[{"arrow_type":"float64","name":"value","nullable":false}],"output":{"arrow_type":"float64","kind":"scalar","nullable":false}}} +{"artifact":{"adapter":{"kind":"scalar_to_arrow_batch","version":1},"content":{"data":"ZnJvbSBfX2Z1dHVyZV9fIGltcG9ydCBhbm5vdGF0aW9ucwoKZGVmIG5vcm1hbGl6ZV9zY29yZSh2YWx1ZTogZmxvYXQpIC0+IGZsb2F0OgogICAgcmV0dXJuIHZhbHVlIC8gMTAwLjAK","encoding":"base64"},"digest":"sha256:760784bdcef57b802f389b97804cc0b618aae39e86044733451bcd0b13089a7f","entrypoint":"normalize_score","kind":"python_callable"},"name":"normalize_score","runtime":{"env":{"MODE":"test"},"environment":{"kind":"pip","packages":["numpy>=2"]},"kind":"python","python_version":"3.12"},"signature":{"inputs":[{"arrow_type":"float64","name":"value","nullable":false}],"output":{"arrow_type":"float64","kind":"scalar","nullable":false}}} diff --git a/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_registration_request.json b/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_registration_request.json index bbfec3169..092d76dc2 100644 --- a/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_registration_request.json +++ b/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_registration_request.json @@ -39,8 +39,5 @@ "env": { "MODE": "test" } - }, - "required_secrets": [ - "API_TOKEN" - ] + } } diff --git a/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_version.canonical.json b/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_version.canonical.json index 7ab632a98..2670ad0b2 100644 --- a/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_version.canonical.json +++ b/rust/lancedb/tests/fixtures/first_class_functions/v1/remote_function_version.canonical.json @@ -1 +1 @@ -{"artifact":{"digest":"sha256:code","entrypoint":"embed","kind":"python_callable"},"created_at":"2026-08-21T00:00:00Z","environment_digest":"sha256:environment","name":"embed","required_secrets":["HF_TOKEN"],"runtime":{"env":{"TOKENIZERS_PARALLELISM":"false"},"environment":{"kind":"pip","packages":["sentence-transformers>=3"]},"kind":"python","python_version":"3.12"},"runtime_digest":"sha256:runtime","signature":{"inputs":[{"arrow_type":"utf8","name":"text","nullable":true}],"output":{"arrow_type":"list","kind":"scalar","nullable":false}},"version":"fv_01K3EXACT"} +{"artifact":{"digest":"sha256:code","entrypoint":"embed","kind":"python_callable"},"created_at":"2026-08-21T00:00:00Z","environment_digest":"sha256:environment","name":"embed","runtime":{"env":{"TOKENIZERS_PARALLELISM":"false"},"environment":{"kind":"pip","packages":["sentence-transformers>=3"]},"kind":"python","python_version":"3.12"},"runtime_digest":"sha256:runtime","signature":{"inputs":[{"arrow_type":"utf8","name":"text","nullable":true}],"output":{"arrow_type":"list","kind":"scalar","nullable":false}},"version":"fv_01K3EXACT"}