From d55897d3099be36697e80015fe4316cda59618cf Mon Sep 17 00:00:00 2001 From: Anjali Ratnam Date: Mon, 15 Jun 2026 10:53:45 -0700 Subject: [PATCH 1/3] fixed protocol stripping for sovereign cloud urls --- CHANGELOG.md | 2 +- adlfs/spec.py | 11 ++++------- adlfs/tests/test_spec.py | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0ced3c7..5d9164b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Unreleased ---------- -- . +- Fixed a bug in `_strip_protocol` and `_get_kwargs_from_urls` to correctly handle Azure sovereign cloud URLs (#402) 2026.5.0 -------- diff --git a/adlfs/spec.py b/adlfs/spec.py index 31a26121..e45e7214 100644 --- a/adlfs/spec.py +++ b/adlfs/spec.py @@ -412,7 +412,8 @@ def _strip_protocol(cls, path: str): if isinstance(path, list): return [cls._strip_protocol(p) for p in path] - STORE_SUFFIX = ".dfs.core.windows.net" + # STORE_SUFFIX = ".dfs.core.windows.net" + AZURE_STORAGE_HOST_RE = re.compile(r".+\.(dfs|blob)\.core\.", re.IGNORECASE) logger.debug(f"_strip_protocol for {path}") if isinstance(cls.protocol, str): # The protocol can be either a string or a tuple of strings. @@ -433,9 +434,7 @@ def _strip_protocol(cls, path: str): # the format {host}/{path} # here host is the container_name elif ops.get("host", None): - if ( - ops["host"].count(STORE_SUFFIX) == 0 - ): # no store-suffix, so this is container-name + if not AZURE_STORAGE_HOST_RE.match(ops["host"]): ops["path"] = ops["host"] + ops["path"] url_query = ops.get("url_query") if url_query is not None: @@ -452,9 +451,7 @@ def _get_kwargs_from_urls(urlpath): out = {} host = ops.get("host", None) if host: - match = re.match( - r"(?P.+)\.(dfs|blob)\.core\.windows\.net", host - ) + match = re.match(r"(?P[^.]+)\.(dfs|blob)\.core\.", host) if match: account_name = match.groupdict()["account_name"] out["account_name"] = account_name diff --git a/adlfs/tests/test_spec.py b/adlfs/tests/test_spec.py index d2a63dee..b512265d 100644 --- a/adlfs/tests/test_spec.py +++ b/adlfs/tests/test_spec.py @@ -2610,3 +2610,19 @@ def test_etag_normalized_form(storage): ) def test_striping_etag(input_etag, expected_etag): assert _normalize_etag_quotes(input_etag) == expected_etag + + +@pytest.mark.parametrize( + "url,expected_account_name", + [ + ("abfs://myaccount.blob.core.windows.net/container/file", "myaccount"), + ("abfs://myaccount.blob.core.chinacloudapi.cn/container/file", "myaccount"), + ("abfs://myaccount.dfs.core.chinacloudapi.cn/container/file", "myaccount"), + ], +) +def test_get_kwargs_from_urls_sovereign_clouds(url, expected_account_name): + assert ( + AzureBlobFileSystem._get_kwargs_from_urls(url).get("account_name") + == expected_account_name + ) + assert AzureBlobFileSystem._strip_protocol(url) == "container/file" From b2bec85fe2d3bb2911ec40007b683bb12eb7217f Mon Sep 17 00:00:00 2001 From: Anjali Ratnam Date: Wed, 24 Jun 2026 11:05:40 -0700 Subject: [PATCH 2/3] removed changes/updated documentation --- CHANGELOG.md | 2 +- README.md | 18 ++++++++++++++++++ adlfs/spec.py | 11 +++++++---- adlfs/tests/test_spec.py | 16 ---------------- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d9164b3..b0ced3c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Unreleased ---------- -- Fixed a bug in `_strip_protocol` and `_get_kwargs_from_urls` to correctly handle Azure sovereign cloud URLs (#402) +- . 2026.5.0 -------- diff --git a/README.md b/README.md index 309dd823..e3332342 100644 --- a/README.md +++ b/README.md @@ -84,5 +84,23 @@ The `AzureBlobFileSystem` accepts [all of the Async BlobServiceClient arguments] By default, write operations create BlockBlobs in Azure, which, once written can not be appended. It is possible to create an AppendBlob using `mode="ab"` when creating and operating on blobs. Currently, AppendBlobs are not available if hierarchical namespaces are enabled. +### Connecting to different endpoints +By default, adlfs connects to the Azure public cloud (`blob.core.windows.net`). To set a different endpoint, you can pass in the `account_host` parameter alongside `account_name`: +```python +# Generic custom endpoint +fs = AzureBlobFileSystem( + account_name="myaccount", + account_host="myaccount.", + credential=DefaultAzureCredential(), +) + +# Microsoft OneLake +fs = AzureBlobFileSystem( + account_name="onelake", + account_host="onelake.dfs.fabric.microsoft.com", + credential=DefaultAzureCredential(), +) +``` + ### Older versions ADLS Gen1 filesystem has officially been [retired](https://learn.microsoft.com/en-us/lifecycle/products/azure-data-lake-storage-gen1). Hence the adl:// method, which was designed to connect to ADLS Gen1 is obsolete. diff --git a/adlfs/spec.py b/adlfs/spec.py index e45e7214..31a26121 100644 --- a/adlfs/spec.py +++ b/adlfs/spec.py @@ -412,8 +412,7 @@ def _strip_protocol(cls, path: str): if isinstance(path, list): return [cls._strip_protocol(p) for p in path] - # STORE_SUFFIX = ".dfs.core.windows.net" - AZURE_STORAGE_HOST_RE = re.compile(r".+\.(dfs|blob)\.core\.", re.IGNORECASE) + STORE_SUFFIX = ".dfs.core.windows.net" logger.debug(f"_strip_protocol for {path}") if isinstance(cls.protocol, str): # The protocol can be either a string or a tuple of strings. @@ -434,7 +433,9 @@ def _strip_protocol(cls, path: str): # the format {host}/{path} # here host is the container_name elif ops.get("host", None): - if not AZURE_STORAGE_HOST_RE.match(ops["host"]): + if ( + ops["host"].count(STORE_SUFFIX) == 0 + ): # no store-suffix, so this is container-name ops["path"] = ops["host"] + ops["path"] url_query = ops.get("url_query") if url_query is not None: @@ -451,7 +452,9 @@ def _get_kwargs_from_urls(urlpath): out = {} host = ops.get("host", None) if host: - match = re.match(r"(?P[^.]+)\.(dfs|blob)\.core\.", host) + match = re.match( + r"(?P.+)\.(dfs|blob)\.core\.windows\.net", host + ) if match: account_name = match.groupdict()["account_name"] out["account_name"] = account_name diff --git a/adlfs/tests/test_spec.py b/adlfs/tests/test_spec.py index b512265d..d2a63dee 100644 --- a/adlfs/tests/test_spec.py +++ b/adlfs/tests/test_spec.py @@ -2610,19 +2610,3 @@ def test_etag_normalized_form(storage): ) def test_striping_etag(input_etag, expected_etag): assert _normalize_etag_quotes(input_etag) == expected_etag - - -@pytest.mark.parametrize( - "url,expected_account_name", - [ - ("abfs://myaccount.blob.core.windows.net/container/file", "myaccount"), - ("abfs://myaccount.blob.core.chinacloudapi.cn/container/file", "myaccount"), - ("abfs://myaccount.dfs.core.chinacloudapi.cn/container/file", "myaccount"), - ], -) -def test_get_kwargs_from_urls_sovereign_clouds(url, expected_account_name): - assert ( - AzureBlobFileSystem._get_kwargs_from_urls(url).get("account_name") - == expected_account_name - ) - assert AzureBlobFileSystem._strip_protocol(url) == "container/file" From b2eeb25ca02a44c197a718ed1b53b2b527a681de Mon Sep 17 00:00:00 2001 From: Anjali Ratnam Date: Wed, 24 Jun 2026 14:41:29 -0700 Subject: [PATCH 3/3] updates --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e3332342..207a46db 100644 --- a/README.md +++ b/README.md @@ -91,14 +91,18 @@ By default, adlfs connects to the Azure public cloud (`blob.core.windows.net`). fs = AzureBlobFileSystem( account_name="myaccount", account_host="myaccount.", - credential=DefaultAzureCredential(), +) + +# Azure China cloud +fs = AzureBlobFileSystem( + account_name="myaccount", + account_host="myaccount.blob.core.chinacloudapi.cn", ) # Microsoft OneLake fs = AzureBlobFileSystem( account_name="onelake", account_host="onelake.dfs.fabric.microsoft.com", - credential=DefaultAzureCredential(), ) ```