This is an automated email from the ASF dual-hosted git repository.
henry3260 pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-3-test by this push:
new 3e38a189e1b [v3-3-test] Allow slashes in connection IDs fetched via
the Execution API (#69228) (#73700)
3e38a189e1b is described below
commit 3e38a189e1b1dc91542017c6c57e63f4b4af65f8
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Sep 25 11:14:49 2026 +0800
[v3-3-test] Allow slashes in connection IDs fetched via the Execution API
(#69228) (#73700)
* Allow slashes in connection IDs fetched via the Execution API
* Add client side encoding
* Fix required fields in test
(cherry picked from commit 0142b0443a2ca149cdb0a14895ccbb2178a6219d)
Co-authored-by: Justin Pakzad
<[email protected]>
---
.../execution_api/routes/connections.py | 2 +-
.../versions/head/test_connections.py | 32 ++++++++++++++++++++++
task-sdk/src/airflow/sdk/api/client.py | 2 +-
task-sdk/tests/task_sdk/api/test_client.py | 25 +++++++++++++++++
4 files changed, 59 insertions(+), 2 deletions(-)
diff --git
a/airflow-core/src/airflow/api_fastapi/execution_api/routes/connections.py
b/airflow-core/src/airflow/api_fastapi/execution_api/routes/connections.py
index 8289dcf97fb..d7bc9d634cf 100644
--- a/airflow-core/src/airflow/api_fastapi/execution_api/routes/connections.py
+++ b/airflow-core/src/airflow/api_fastapi/execution_api/routes/connections.py
@@ -57,7 +57,7 @@ log = logging.getLogger(__name__)
@router.get(
- "/{connection_id}",
+ "/{connection_id:path}",
responses={
status.HTTP_401_UNAUTHORIZED: {"description": "Unauthorized"},
status.HTTP_403_FORBIDDEN: {"description": "Task does not have access
to the connection"},
diff --git
a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_connections.py
b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_connections.py
index a2e3cb51fab..8d5da4fa3ae 100644
---
a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_connections.py
+++
b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_connections.py
@@ -86,6 +86,38 @@ class TestGetConnection:
session.delete(connection)
session.commit()
+ def test_connection_get_with_slash(self, client, session):
+ connection = Connection(
+ conn_id="dev/test_conn",
+ conn_type="http",
+ description="description",
+ host="localhost",
+ login="root",
+ password="admin",
+ schema="http",
+ port=8080,
+ extra='{"x_secret": "testsecret", "y_secret": "test"}',
+ )
+ session.add(connection)
+ session.commit()
+
+ response = client.get("/execution/connections/dev/test_conn")
+
+ assert response.status_code == 200
+ assert response.json() == {
+ "conn_id": "dev/test_conn",
+ "conn_type": "http",
+ "host": "localhost",
+ "login": "root",
+ "password": "admin",
+ "schema": "http",
+ "port": 8080,
+ "extra": '{"x_secret": "testsecret", "y_secret": "test"}',
+ }
+
+ session.delete(connection)
+ session.commit()
+
@mock.patch.dict(
"os.environ",
{"AIRFLOW_CONN_TEST_CONN2": '{"uri":
"http://root:admin@localhost:8080/https?headers=header"}'},
diff --git a/task-sdk/src/airflow/sdk/api/client.py
b/task-sdk/src/airflow/sdk/api/client.py
index 4353802c61c..9c67f905a23 100644
--- a/task-sdk/src/airflow/sdk/api/client.py
+++ b/task-sdk/src/airflow/sdk/api/client.py
@@ -467,7 +467,7 @@ class ConnectionOperations:
def get(self, conn_id: str) -> ConnectionResponse | ErrorResponse:
"""Get a connection from the API server."""
try:
- resp = self.client.get(f"connections/{conn_id}")
+ resp = self.client.get(f"connections/{quote(conn_id, safe='')}")
except ServerResponseError as e:
if e.response.status_code == HTTPStatus.NOT_FOUND:
log.debug(
diff --git a/task-sdk/tests/task_sdk/api/test_client.py
b/task-sdk/tests/task_sdk/api/test_client.py
index 532589dfc2e..f683fa1753a 100644
--- a/task-sdk/tests/task_sdk/api/test_client.py
+++ b/task-sdk/tests/task_sdk/api/test_client.py
@@ -1180,6 +1180,31 @@ class TestConnectionOperations:
assert isinstance(result, ErrorResponse)
assert result.error == ErrorType.CONNECTION_NOT_FOUND
+ def test_connection_get_url_encodes_conn_id(self):
+ requests_seen = []
+
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ requests_seen.append(request)
+ return httpx.Response(
+ status_code=200,
+ json={
+ "conn_id": "dev/my_conn",
+ "conn_type": "http",
+ "host": None,
+ "schema": None,
+ "login": None,
+ "password": None,
+ "port": None,
+ "extra": None,
+ },
+ )
+
+ client = make_client(transport=httpx.MockTransport(handle_request))
+ result = client.connections.get(conn_id="dev/my_conn")
+
+ assert isinstance(result, ConnectionResponse)
+ assert requests_seen[0].url.raw_path == b"/connections/dev%2Fmy_conn"
+
@pytest.mark.parametrize("status_code", [401, 403])
def test_connection_get_authz_returns_permission_denied(self, status_code):
"""401/403 from the API server is reported as PERMISSION_DENIED, not
raised."""