Skip to content

Commit ff0f427

Browse files
feat: Add capability to set testing values on custom connector projects in Builder UI (#896)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 9de76d2 commit ff0f427

File tree

4 files changed

+238
-12
lines changed

4 files changed

+238
-12
lines changed

airbyte/_util/api_util.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,6 +1564,51 @@ def get_connector_builder_project_for_definition_id(
15641564
return json_result.get("builderProjectId")
15651565

15661566

1567+
def update_connector_builder_project_testing_values(
1568+
*,
1569+
workspace_id: str,
1570+
builder_project_id: str,
1571+
testing_values: dict[str, Any],
1572+
spec: dict[str, Any],
1573+
api_root: str,
1574+
client_id: SecretString,
1575+
client_secret: SecretString,
1576+
) -> dict[str, Any]:
1577+
"""Update the testing values for a connector builder project.
1578+
1579+
This call replaces the entire testing values object stored for the project.
1580+
Any keys not included in `testing_values` will be removed.
1581+
1582+
Uses the Config API endpoint:
1583+
/v1/connector_builder_projects/update_testing_values
1584+
1585+
Args:
1586+
workspace_id: The workspace ID
1587+
builder_project_id: The connector builder project ID
1588+
testing_values: The testing values (config blob) to persist. This replaces
1589+
any existing testing values entirely.
1590+
spec: The source definition specification (connector spec)
1591+
api_root: The API root URL
1592+
client_id: OAuth client ID
1593+
client_secret: OAuth client secret
1594+
1595+
Returns:
1596+
The updated testing values from the API response
1597+
"""
1598+
return _make_config_api_request(
1599+
path="/connector_builder_projects/update_testing_values",
1600+
json={
1601+
"workspaceId": workspace_id,
1602+
"builderProjectId": builder_project_id,
1603+
"testingValues": testing_values,
1604+
"spec": spec,
1605+
},
1606+
api_root=api_root,
1607+
client_id=client_id,
1608+
client_secret=client_secret,
1609+
)
1610+
1611+
15671612
# Organization and workspace listing
15681613

15691614

airbyte/cloud/connectors.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,3 +690,70 @@ def deploy_source(
690690
workspace=self.workspace,
691691
source_response=result,
692692
)
693+
694+
def set_testing_values(
695+
self,
696+
testing_values: dict[str, Any],
697+
) -> CustomCloudSourceDefinition:
698+
"""Set the testing values for this custom source definition's connector builder project.
699+
700+
Testing values are the input configuration values used when testing the connector
701+
in the Connector Builder UI. Setting these values allows users to immediately
702+
run test read operations after deploying a custom source to the Builder UI.
703+
704+
This method replaces any existing testing values with the provided dictionary.
705+
Pass the full set of values you want to persist, not just the fields you're changing.
706+
707+
Args:
708+
testing_values: A dictionary containing the configuration values to use for testing.
709+
This should match the connector's spec schema. Replaces any existing values.
710+
711+
Returns:
712+
This `CustomCloudSourceDefinition` object (for method chaining).
713+
714+
Raises:
715+
NotImplementedError: If this is not a YAML custom source definition.
716+
PyAirbyteInputError: If the connector builder project ID cannot be found.
717+
"""
718+
if self.definition_type != "yaml":
719+
raise NotImplementedError(
720+
"Testing values can only be set for YAML custom source definitions. "
721+
"Docker custom sources are not yet supported."
722+
)
723+
724+
builder_project_id = self.connector_builder_project_id
725+
if not builder_project_id:
726+
raise exc.PyAirbyteInputError(
727+
message="Could not find connector builder project ID for this definition.",
728+
context={
729+
"definition_id": self.definition_id,
730+
"workspace_id": self.workspace.workspace_id,
731+
},
732+
)
733+
734+
# Get the spec from the definition info
735+
if not self._definition_info:
736+
self._definition_info = self._fetch_definition_info()
737+
738+
# Build the spec object from the manifest, matching the Builder UI pattern
739+
spec: dict[str, Any] = {}
740+
if self._definition_info.manifest:
741+
manifest_spec = self._definition_info.manifest.get("spec", {})
742+
if manifest_spec:
743+
spec = {
744+
"documentationUrl": manifest_spec.get("documentation_url"),
745+
"connectionSpecification": manifest_spec.get("connection_specification", {}),
746+
"advancedAuth": manifest_spec.get("advanced_auth"),
747+
}
748+
749+
api_util.update_connector_builder_project_testing_values(
750+
workspace_id=self.workspace.workspace_id,
751+
builder_project_id=builder_project_id,
752+
testing_values=testing_values,
753+
spec=spec,
754+
api_root=self.workspace.api_root,
755+
client_id=self.workspace.client_id,
756+
client_secret=self.workspace.client_secret,
757+
)
758+
759+
return self

airbyte/cloud/workspaces.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ def publish_custom_source_definition(
536536
docker_tag: str | None = None,
537537
unique: bool = True,
538538
pre_validate: bool = True,
539+
testing_values: dict[str, Any] | None = None,
539540
) -> CustomCloudSourceDefinition:
540541
"""Publish a custom source connector definition.
541542
@@ -549,6 +550,10 @@ def publish_custom_source_definition(
549550
docker_tag: Docker image tag (e.g., '1.0.0')
550551
unique: Whether to enforce name uniqueness
551552
pre_validate: Whether to validate manifest client-side (YAML only)
553+
testing_values: Optional configuration values to use for testing in the
554+
Connector Builder UI. If provided, these values are stored as the complete
555+
testing values object for the connector builder project (replaces any existing
556+
values), allowing immediate test read operations.
552557
553558
Returns:
554559
CustomCloudSourceDefinition object representing the created definition
@@ -613,7 +618,15 @@ def publish_custom_source_definition(
613618
client_id=self.client_id,
614619
client_secret=self.client_secret,
615620
)
616-
return CustomCloudSourceDefinition._from_yaml_response(self, result) # noqa: SLF001
621+
custom_definition = CustomCloudSourceDefinition._from_yaml_response( # noqa: SLF001
622+
self, result
623+
)
624+
625+
# Set testing values if provided
626+
if testing_values is not None:
627+
custom_definition.set_testing_values(testing_values)
628+
629+
return custom_definition
617630

618631
raise NotImplementedError(
619632
"Docker custom source definitions are not yet supported. "

airbyte/mcp/cloud_ops.py

Lines changed: 112 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,6 +1372,32 @@ def publish_custom_source_definition(
13721372
default=True,
13731373
),
13741374
] = True,
1375+
testing_values: Annotated[
1376+
dict | str | None,
1377+
Field(
1378+
description=(
1379+
"Optional testing configuration values for the Builder UI. "
1380+
"Can be provided as a JSON object or JSON string. "
1381+
"Supports inline secret refs via 'secret_reference::ENV_VAR_NAME' syntax. "
1382+
"If provided, these values replace any existing testing values "
1383+
"for the connector builder project, allowing immediate test read operations."
1384+
),
1385+
default=None,
1386+
),
1387+
],
1388+
testing_values_secret_name: Annotated[
1389+
str | None,
1390+
Field(
1391+
description=(
1392+
"Optional name of a secret containing testing configuration values "
1393+
"in JSON or YAML format. The secret will be resolved by the MCP "
1394+
"server and merged into testing_values, with secret values taking "
1395+
"precedence. This lets the agent reference secrets without sending "
1396+
"raw values as tool arguments."
1397+
),
1398+
default=None,
1399+
),
1400+
],
13751401
) -> str:
13761402
"""Publish a custom YAML source connector definition to Airbyte Cloud.
13771403
@@ -1382,12 +1408,24 @@ def publish_custom_source_definition(
13821408
if isinstance(manifest_yaml, str) and "\n" not in manifest_yaml:
13831409
processed_manifest = Path(manifest_yaml)
13841410

1411+
# Resolve testing values from inline config and/or secret
1412+
testing_values_dict: dict[str, Any] | None = None
1413+
if testing_values is not None or testing_values_secret_name is not None:
1414+
testing_values_dict = (
1415+
resolve_config(
1416+
config=testing_values,
1417+
config_secret_name=testing_values_secret_name,
1418+
)
1419+
or None
1420+
)
1421+
13851422
workspace: CloudWorkspace = _get_cloud_workspace(workspace_id)
13861423
custom_source = workspace.publish_custom_source_definition(
13871424
name=name,
13881425
manifest_yaml=processed_manifest,
13891426
unique=unique,
13901427
pre_validate=pre_validate,
1428+
testing_values=testing_values_dict,
13911429
)
13921430
register_guid_created_in_session(custom_source.definition_id)
13931431
return (
@@ -1447,11 +1485,15 @@ def update_custom_source_definition(
14471485
Field(description="The ID of the definition to update."),
14481486
],
14491487
manifest_yaml: Annotated[
1450-
str | Path,
1488+
str | Path | None,
14511489
Field(
1452-
description="New manifest as YAML string or file path.",
1490+
description=(
1491+
"New manifest as YAML string or file path. "
1492+
"Optional; omit to update only testing values."
1493+
),
1494+
default=None,
14531495
),
1454-
],
1496+
] = None,
14551497
*,
14561498
workspace_id: Annotated[
14571499
str | None,
@@ -1467,26 +1509,85 @@ def update_custom_source_definition(
14671509
default=True,
14681510
),
14691511
] = True,
1512+
testing_values: Annotated[
1513+
dict | str | None,
1514+
Field(
1515+
description=(
1516+
"Optional testing configuration values for the Builder UI. "
1517+
"Can be provided as a JSON object or JSON string. "
1518+
"Supports inline secret refs via 'secret_reference::ENV_VAR_NAME' syntax. "
1519+
"If provided, these values replace any existing testing values "
1520+
"for the connector builder project. The entire testing values object "
1521+
"is overwritten, so pass the full set of values you want to persist."
1522+
),
1523+
default=None,
1524+
),
1525+
],
1526+
testing_values_secret_name: Annotated[
1527+
str | None,
1528+
Field(
1529+
description=(
1530+
"Optional name of a secret containing testing configuration values "
1531+
"in JSON or YAML format. The secret will be resolved by the MCP "
1532+
"server and merged into testing_values, with secret values taking "
1533+
"precedence. This lets the agent reference secrets without sending "
1534+
"raw values as tool arguments."
1535+
),
1536+
default=None,
1537+
),
1538+
],
14701539
) -> str:
14711540
"""Update a custom YAML source definition in Airbyte Cloud.
14721541
1473-
Note: Only YAML (declarative) connectors are currently supported.
1474-
Docker-based custom sources are not yet available.
1542+
Updates the manifest and/or testing values for an existing custom source definition.
1543+
At least one of manifest_yaml, testing_values, or testing_values_secret_name must be provided.
14751544
"""
14761545
check_guid_created_in_session(definition_id)
1477-
processed_manifest = manifest_yaml
1546+
1547+
workspace: CloudWorkspace = _get_cloud_workspace(workspace_id)
1548+
1549+
if manifest_yaml is None and testing_values is None and testing_values_secret_name is None:
1550+
raise PyAirbyteInputError(
1551+
message=(
1552+
"At least one of manifest_yaml, testing_values, or testing_values_secret_name "
1553+
"must be provided to update a custom source definition."
1554+
),
1555+
context={
1556+
"definition_id": definition_id,
1557+
"workspace_id": workspace.workspace_id,
1558+
},
1559+
)
1560+
1561+
processed_manifest: str | Path | None = manifest_yaml
14781562
if isinstance(manifest_yaml, str) and "\n" not in manifest_yaml:
14791563
processed_manifest = Path(manifest_yaml)
14801564

1481-
workspace: CloudWorkspace = _get_cloud_workspace(workspace_id)
1565+
# Resolve testing values from inline config and/or secret
1566+
testing_values_dict: dict[str, Any] | None = None
1567+
if testing_values is not None or testing_values_secret_name is not None:
1568+
testing_values_dict = (
1569+
resolve_config(
1570+
config=testing_values,
1571+
config_secret_name=testing_values_secret_name,
1572+
)
1573+
or None
1574+
)
1575+
14821576
definition = workspace.get_custom_source_definition(
14831577
definition_id=definition_id,
14841578
definition_type="yaml",
14851579
)
1486-
custom_source: CustomCloudSourceDefinition = definition.update_definition(
1487-
manifest_yaml=processed_manifest,
1488-
pre_validate=pre_validate,
1489-
)
1580+
custom_source: CustomCloudSourceDefinition = definition
1581+
1582+
if processed_manifest is not None:
1583+
custom_source = definition.update_definition(
1584+
manifest_yaml=processed_manifest,
1585+
pre_validate=pre_validate,
1586+
)
1587+
1588+
if testing_values_dict is not None:
1589+
custom_source.set_testing_values(testing_values_dict)
1590+
14901591
return (
14911592
"Successfully updated custom YAML source definition:\n"
14921593
+ _get_custom_source_definition_description(

0 commit comments

Comments
 (0)