diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md index 74886f2a0cb8..06d2f6fb361d 100644 --- a/sdk/core/azure-core/CHANGELOG.md +++ b/sdk/core/azure-core/CHANGELOG.md @@ -8,13 +8,15 @@ ### Bugs Fixed +- Fixed `PipelineClient.format_url` to preserve the leading slash when the URL template starts with `/?`. #45218 + ### Other Changes ## 1.38.1 (2026-02-10) ### Bugs Fixed -- Fixed `PipelineClient.format_url` to avoid adding trailing slashes when the URL template contains only query parameters. #45044 +- Fixed `PipelineClient.format_url` to avoid adding trailing slashes when the URL template contains only query parameters. #45044 ## 1.38.0 (2026-01-12) diff --git a/sdk/core/azure-core/azure/core/pipeline/transport/_base.py b/sdk/core/azure-core/azure/core/pipeline/transport/_base.py index ec7d5bc5fa85..6e33aa77df73 100644 --- a/sdk/core/azure-core/azure/core/pipeline/transport/_base.py +++ b/sdk/core/azure-core/azure/core/pipeline/transport/_base.py @@ -132,7 +132,7 @@ def _urljoin(base_url: str, stub_url: str) -> str: # https://docs.python.org/3/library/collections.html?highlight=namedtuple#collections.namedtuple if stub_url_path: parsed_base_url = parsed_base_url._replace( - path=parsed_base_url.path.rstrip("/") + "/" + stub_url_path, + path=parsed_base_url.path.rstrip("/") + "/" + stub_url_path.lstrip("/"), ) if stub_url_query: query_params = [stub_url_query] @@ -662,7 +662,6 @@ def format_url(self, url_template: str, **kwargs: Any) -> str: if url: parsed = urlparse(url) if not parsed.scheme or not parsed.netloc: - url = url.lstrip("/") try: base = self._base_url.format(**kwargs).rstrip("/") except KeyError as key: diff --git a/sdk/core/azure-core/tests/test_basic_transport.py b/sdk/core/azure-core/tests/test_basic_transport.py index ba3a9d35ed14..2803e791d4ea 100644 --- a/sdk/core/azure-core/tests/test_basic_transport.py +++ b/sdk/core/azure-core/tests/test_basic_transport.py @@ -128,9 +128,16 @@ def test_http_request_serialization(http_request): def test_url_join(): assert _urljoin("devstoreaccount1", "?testdir") == "devstoreaccount1?testdir" + assert _urljoin("devstoreaccount1", "?testdir=foo") == "devstoreaccount1?testdir=foo" + assert _urljoin("devstoreaccount1/api", "?a=1") == "devstoreaccount1/api?a=1" + assert ( + _urljoin("devstoreaccount1", "/?restype=service&comp=properties") + == "devstoreaccount1/?restype=service&comp=properties" + ) assert _urljoin("devstoreaccount1", "") == "devstoreaccount1" assert _urljoin("devstoreaccount1", "testdir/") == "devstoreaccount1/testdir/" assert _urljoin("devstoreaccount1/", "") == "devstoreaccount1/" + assert _urljoin("devstoreaccount1/", "/testdir/") == "devstoreaccount1/testdir/" assert _urljoin("devstoreaccount1/", "testdir/") == "devstoreaccount1/testdir/" assert _urljoin("devstoreaccount1?a=1", "testdir/") == "devstoreaccount1/testdir/?a=1" assert _urljoin("devstoreaccount1", "testdir/?b=2") == "devstoreaccount1/testdir/?b=2" diff --git a/sdk/core/azure-core/tests/test_pipeline.py b/sdk/core/azure-core/tests/test_pipeline.py index 89ca3b5a5599..5612b8b2a45f 100644 --- a/sdk/core/azure-core/tests/test_pipeline.py +++ b/sdk/core/azure-core/tests/test_pipeline.py @@ -38,6 +38,7 @@ from azure.core.configuration import Configuration from azure.core.pipeline import Pipeline +from azure.core.rest import HttpRequest from azure.core import PipelineClient from azure.core.pipeline.policies import ( SansIOHTTPPolicy, @@ -206,6 +207,39 @@ def test_format_url_double_query(): assert formatted == "https://bing.com/path/subpath?query=testvalue&x=2ndvalue&a=X&c=Y" +def test_format_url_query_strings(): + client = PipelineClientBase("https://foo.core.windows.net") + formatted = client.format_url("/") + assert formatted == "https://foo.core.windows.net/" + + formatted = client.format_url("/?a=X&c=Y") + assert formatted == "https://foo.core.windows.net/?a=X&c=Y" + + formatted = client.format_url("?a=X&c=Y") + assert formatted == "https://foo.core.windows.net?a=X&c=Y" + + formatted = client.format_url("/Tables/?a=X&c=Y") + assert formatted == "https://foo.core.windows.net/Tables/?a=X&c=Y" + + formatted = client.format_url("/Tables?a=X&c=Y") + assert formatted == "https://foo.core.windows.net/Tables?a=X&c=Y" + + +def test_format_url_from_http_request(): + client = PipelineClientBase("https://foo.core.windows.net") + + _url = "/" + _params = {"foo": "bar"} + request = HttpRequest("GET", _url, params=_params) + formatted = client.format_url(request.url) + assert formatted == "https://foo.core.windows.net/?foo=bar" + + _url = "?restype=service&comp=properties" + request = HttpRequest("GET", _url) + formatted = client.format_url(request.url) + assert formatted == "https://foo.core.windows.net?restype=service&comp=properties" + + def test_format_url_braces_with_dot(): base_url = "https://bing.com/{aaa.bbb}" with pytest.raises(ValueError): diff --git a/sdk/tables/azure-data-tables/assets.json b/sdk/tables/azure-data-tables/assets.json index 83cda58a3028..46f7ffdc3c73 100644 --- a/sdk/tables/azure-data-tables/assets.json +++ b/sdk/tables/azure-data-tables/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "python", "TagPrefix": "python/tables/azure-data-tables", - "Tag": "python/tables/azure-data-tables_afc53b2631" + "Tag": "python/tables/azure-data-tables_48a9914a75" } diff --git a/sdk/tables/azure-data-tables/tests/test_table_service_properties.py b/sdk/tables/azure-data-tables/tests/test_table_service_properties.py index dbc127e9c434..4c97fbc484b6 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_service_properties.py +++ b/sdk/tables/azure-data-tables/tests/test_table_service_properties.py @@ -7,7 +7,6 @@ # -------------------------------------------------------------------------- import time import pytest -from packaging.version import Version from devtools_testutils import AzureRecordedTestCase, recorded_by_proxy @@ -18,7 +17,6 @@ TableRetentionPolicy, TableCorsRule, ) -from azure.core import VERSION as core_version from azure.core.exceptions import ResourceNotFoundError, HttpResponseError from _shared.testcase import TableTestCase @@ -184,18 +182,15 @@ def test_client_with_url_ends_with_table_name( assert ("table specified does not exist") in str(exc.value) assert ("Please check your account URL.") in str(exc.value) - # Azure Core 1.38.1 introduced a change to URL formatting which can cause recording mismatches in - # mindependency checks. - if Version(core_version) >= Version("1.38.1") or self.is_live: - with pytest.raises(HttpResponseError) as exc: - tsc.set_service_properties(analytics_logging=TableAnalyticsLogging(write=True)) - assert ("URI is invalid") in str(exc.value) - assert ("Please check your account URL.") in str(exc.value) - - with pytest.raises(HttpResponseError) as exc: - tsc.get_service_properties() - assert ("URI is invalid") in str(exc.value) - assert ("Please check your account URL.") in str(exc.value) + with pytest.raises(HttpResponseError) as exc: + tsc.set_service_properties(analytics_logging=TableAnalyticsLogging(write=True)) + assert ("URI is invalid") in str(exc.value) + assert ("Please check your account URL.") in str(exc.value) + + with pytest.raises(HttpResponseError) as exc: + tsc.get_service_properties() + assert ("URI is invalid") in str(exc.value) + assert ("Please check your account URL.") in str(exc.value) tsc.delete_table(table_name) diff --git a/sdk/tables/azure-data-tables/tests/test_table_service_properties_async.py b/sdk/tables/azure-data-tables/tests/test_table_service_properties_async.py index 207c5ecf3606..8c4b417b2990 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_service_properties_async.py +++ b/sdk/tables/azure-data-tables/tests/test_table_service_properties_async.py @@ -5,14 +5,12 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -from packaging.version import Version import time import pytest from devtools_testutils import AzureRecordedTestCase from devtools_testutils.aio import recorded_by_proxy_async -from azure.core import VERSION as core_version from azure.core.exceptions import ResourceNotFoundError, HttpResponseError from azure.data.tables import TableAnalyticsLogging, TableMetrics, TableRetentionPolicy, TableCorsRule @@ -185,18 +183,15 @@ async def test_client_with_url_ends_with_table_name( assert ("table specified does not exist") in str(exc.value) assert ("Please check your account URL.") in str(exc.value) - # Azure Core 1.38.1 introduced a change to URL formatting which can cause recording mismatches in - # mindependency checks. - if Version(core_version) >= Version("1.38.1") or self.is_live: - with pytest.raises(HttpResponseError) as exc: - await tsc.set_service_properties(analytics_logging=TableAnalyticsLogging(write=True)) - assert ("URI is invalid") in str(exc.value) - assert ("Please check your account URL.") in str(exc.value) - - with pytest.raises(HttpResponseError) as exc: - await tsc.get_service_properties() - assert ("URI is invalid") in str(exc.value) - assert ("Please check your account URL.") in str(exc.value) + with pytest.raises(HttpResponseError) as exc: + await tsc.set_service_properties(analytics_logging=TableAnalyticsLogging(write=True)) + assert ("URI is invalid") in str(exc.value) + assert ("Please check your account URL.") in str(exc.value) + + with pytest.raises(HttpResponseError) as exc: + await tsc.get_service_properties() + assert ("URI is invalid") in str(exc.value) + assert ("Please check your account URL.") in str(exc.value) await tsc.delete_table(table_name) diff --git a/sdk/tables/azure-data-tables/tests/test_table_service_properties_cosmos.py b/sdk/tables/azure-data-tables/tests/test_table_service_properties_cosmos.py index fda1b9a7eed7..f25c86eb9ec7 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_service_properties_cosmos.py +++ b/sdk/tables/azure-data-tables/tests/test_table_service_properties_cosmos.py @@ -72,18 +72,15 @@ def test_client_with_url_ends_with_table_name(self, tables_cosmos_account_name, assert ("Server failed to authenticate the request") in str(exc.value) assert ("Please check your account URL.") in str(exc.value) - # Azure Core 1.38.1 introduced a change to URL formatting which can cause recording mismatches in - # mindependency checks. - if Version(core_version) >= Version("1.38.1") or self.is_live: - with pytest.raises(HttpResponseError) as exc: - tsc.set_service_properties(analytics_logging=TableAnalyticsLogging(write=True)) - assert ("Server failed to authenticate the request") in str(exc.value) - assert ("Please check your account URL.") in str(exc.value) - - with pytest.raises(HttpResponseError) as exc: - tsc.get_service_properties() - assert ("Server failed to authenticate the request") in str(exc.value) - assert ("Please check your account URL.") in str(exc.value) + with pytest.raises(HttpResponseError) as exc: + tsc.set_service_properties(analytics_logging=TableAnalyticsLogging(write=True)) + assert ("Server failed to authenticate the request") in str(exc.value) + assert ("Please check your account URL.") in str(exc.value) + + with pytest.raises(HttpResponseError) as exc: + tsc.get_service_properties() + assert ("Server failed to authenticate the request") in str(exc.value) + assert ("Please check your account URL.") in str(exc.value) with pytest.raises(HttpResponseError) as exc: tsc.delete_table(table_name) diff --git a/sdk/tables/azure-data-tables/tests/test_table_service_properties_cosmos_async.py b/sdk/tables/azure-data-tables/tests/test_table_service_properties_cosmos_async.py index e90dd81b3a94..b9a1259fcb14 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_service_properties_cosmos_async.py +++ b/sdk/tables/azure-data-tables/tests/test_table_service_properties_cosmos_async.py @@ -5,7 +5,6 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -from packaging.version import Version import pytest from devtools_testutils import AzureRecordedTestCase @@ -13,7 +12,6 @@ from azure.data.tables import TableAnalyticsLogging, TableMetrics, TableRetentionPolicy, TableCorsRule from azure.data.tables.aio import TableServiceClient -from azure.core import VERSION as core_version from azure.core.exceptions import HttpResponseError from _shared.asynctestcase import AsyncTableTestCase @@ -73,18 +71,15 @@ async def test_client_with_url_ends_with_table_name( assert ("Server failed to authenticate the request") in str(exc.value) assert ("Please check your account URL.") in str(exc.value) - # Azure Core 1.38.1 introduced a change to URL formatting which can cause recording mismatches in - # mindependency checks. - if Version(core_version) >= Version("1.38.1") or self.is_live: - with pytest.raises(HttpResponseError) as exc: - await tsc.set_service_properties(analytics_logging=TableAnalyticsLogging(write=True)) - assert ("Server failed to authenticate the request") in str(exc.value) - assert ("Please check your account URL.") in str(exc.value) - - with pytest.raises(HttpResponseError) as exc: - await tsc.get_service_properties() - assert ("Server failed to authenticate the request") in str(exc.value) - assert ("Please check your account URL.") in str(exc.value) + with pytest.raises(HttpResponseError) as exc: + await tsc.set_service_properties(analytics_logging=TableAnalyticsLogging(write=True)) + assert ("Server failed to authenticate the request") in str(exc.value) + assert ("Please check your account URL.") in str(exc.value) + + with pytest.raises(HttpResponseError) as exc: + await tsc.get_service_properties() + assert ("Server failed to authenticate the request") in str(exc.value) + assert ("Please check your account URL.") in str(exc.value) with pytest.raises(HttpResponseError) as exc: await tsc.delete_table(table_name)