Skip to content

Commit 6c3bd6d

Browse files
authored
DGS-23862 Fix URL joining in Python client (#2228)
* MINOR fix url construction * Add tests * update CHANGELOG
1 parent a2f066d commit 6c3bd6d

5 files changed

Lines changed: 49 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Confluent Python Client for Apache Kafka - CHANGELOG
22

3+
## v2.xx.x
4+
5+
### Fixes
6+
7+
- Fix URL joining in Python client (#2228)
8+
9+
310
## v2.14.0 - 2026-04-01
411

512
v2.14.0 is a feature release with the following features, fixes and enhancements:

src/confluent_kafka/schema_registry/_async/schema_registry_client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,11 @@ async def send_http_request(
571571
response = None
572572
for i in range(self.max_retries + 1):
573573
response = await self.session.request(
574-
method, url="/".join([base_url, url]), headers=headers, content=body, params=query
574+
method,
575+
url="/".join([base_url.rstrip("/"), url.lstrip("/")]),
576+
headers=headers,
577+
content=body,
578+
params=query,
575579
)
576580

577581
if is_success(response.status_code):

src/confluent_kafka/schema_registry/_sync/schema_registry_client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,11 @@ def send_http_request(
568568
response = None
569569
for i in range(self.max_retries + 1):
570570
response = self.session.request(
571-
method, url="/".join([base_url, url]), headers=headers, content=body, params=query
571+
method,
572+
url="/".join([base_url.rstrip("/"), url.lstrip("/")]),
573+
headers=headers,
574+
content=body,
575+
params=query,
572576
)
573577

574578
if is_success(response.status_code):

tests/schema_registry/_async/test_config.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
# See the License for the specific language governing permissions and
1616
# limitations under the License.
1717
#
18+
import re
19+
from unittest.mock import AsyncMock
20+
1821
import pytest
19-
from httpx import BasicAuth
22+
from httpx import BasicAuth, Response
2023

2124
from confluent_kafka.schema_registry import AsyncSchemaRegistryClient
2225
from confluent_kafka.schema_registry.rules.encryption.encrypt_executor import FieldEncryptionExecutor
@@ -61,6 +64,18 @@ def test_config_url_trailing_slash():
6164
assert test_client._rest_client.base_urls == [TEST_URL]
6265

6366

67+
@pytest.mark.asyncio
68+
async def test_config_url_no_double_slash():
69+
conf = {'url': 'http://SchemaRegistry:65534/'}
70+
test_client = AsyncSchemaRegistryClient(conf)
71+
mock_response = Response(200, json={'subject': 'test'})
72+
test_client._rest_client.session.request = AsyncMock(return_value=mock_response)
73+
await test_client._rest_client.send_request('/subjects', method='GET')
74+
called_url = test_client._rest_client.session.request.call_args.kwargs['url']
75+
# Ensure no double slashes exist beyond the scheme (http://)
76+
assert '//' not in re.sub(r'^https?://', '', called_url)
77+
78+
6479
def test_config_ssl_key_no_certificate():
6580
conf = {'url': TEST_URL, 'ssl.key.location': '/ssl/keys/client'}
6681
with pytest.raises(

tests/schema_registry/_sync/test_config.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
# See the License for the specific language governing permissions and
1616
# limitations under the License.
1717
#
18+
import re
19+
from unittest.mock import Mock
20+
1821
import pytest
19-
from httpx import BasicAuth
22+
from httpx import BasicAuth, Response
2023

2124
from confluent_kafka.schema_registry import SchemaRegistryClient
2225
from confluent_kafka.schema_registry.rules.encryption.encrypt_executor import FieldEncryptionExecutor
@@ -61,6 +64,18 @@ def test_config_url_trailing_slash():
6164
assert test_client._rest_client.base_urls == [TEST_URL]
6265

6366

67+
@pytest.mark.asyncio
68+
def test_config_url_no_double_slash():
69+
conf = {'url': 'http://SchemaRegistry:65534/'}
70+
test_client = SchemaRegistryClient(conf)
71+
mock_response = Response(200, json={'subject': 'test'})
72+
test_client._rest_client.session.request = Mock(return_value=mock_response)
73+
test_client._rest_client.send_request('/subjects', method='GET')
74+
called_url = test_client._rest_client.session.request.call_args.kwargs['url']
75+
# Ensure no double slashes exist beyond the scheme (http://)
76+
assert '//' not in re.sub(r'^https?://', '', called_url)
77+
78+
6479
def test_config_ssl_key_no_certificate():
6580
conf = {'url': TEST_URL, 'ssl.key.location': '/ssl/keys/client'}
6681
with pytest.raises(

0 commit comments

Comments
 (0)