diff --git a/HISTORY.md b/HISTORY.md index b39bee8c..2e379a6b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -3,6 +3,7 @@ ## Unreleased - Fixed `CloudPath(...) / other` to correctly attempt to fall back on `other`'s `__rtruediv__` implementation, in order to support classes that explicitly support the `/` with a `CloudPath` instance. Previously, this would always raise a `TypeError` if `other` were not a `str` or `PurePosixPath`. (PR [#479](https://github.com/drivendataorg/cloudpathlib/pull/479)) +- Fixed an uncaught exception on Azure Gen2 storage accounts with HNS enabled when used with `DefaultAzureCredential`. (Issue [#486](https://github.com/drivendataorg/cloudpathlib/issues/486)) ## v0.20.0 (2024-10-18) diff --git a/cloudpathlib/azure/azblobclient.py b/cloudpathlib/azure/azblobclient.py index 80a4a92b..15d72fb3 100644 --- a/cloudpathlib/azure/azblobclient.py +++ b/cloudpathlib/azure/azblobclient.py @@ -1,9 +1,14 @@ from datetime import datetime, timedelta import mimetypes import os +from http import HTTPStatus from pathlib import Path from typing import Any, Callable, Dict, Iterable, Optional, Tuple, Union +try: + from typing import cast +except ImportError: + from typing_extensions import cast from ..client import Client, register_client_class from ..cloudpath import implementation_registry @@ -13,7 +18,7 @@ try: - from azure.core.exceptions import ResourceNotFoundError + from azure.core.exceptions import HttpResponseError, ResourceNotFoundError from azure.core.credentials import AzureNamedKeyCredential from azure.storage.blob import ( @@ -202,20 +207,29 @@ def _check_hns(self, cloud_path: AzureBlobPath) -> Optional[bool]: try: account_info = self.service_client.get_account_information() # type: ignore self._hns_enabled = account_info.get("is_hns_enabled", False) # type: ignore + + # get_account_information() not supported with this credential; we have to fallback to + # checking if the root directory exists and is a has 'metadata': {'hdi_isfolder': 'true'} except ResourceNotFoundError: - # get_account_information() not supported with this credential; we have to fallback to - # checking if the root directory exists and is a has 'metadata': {'hdi_isfolder': 'true'} - root_dir = self.service_client.get_blob_client( - container=cloud_path.container, blob="/" - ) - self._hns_enabled = ( - root_dir.exists() - and root_dir.get_blob_properties().metadata.get("hdi_isfolder", False) - == "true" - ) + return self._check_hns_root_metadata(cloud_path) + except HttpResponseError as error: + if error.status_code == HTTPStatus.FORBIDDEN: + self._check_hns_root_metadata(cloud_path) + else: + raise return self._hns_enabled + def _check_hns_root_metadata(self, cloud_path: AzureBlobPath) -> bool: + root_dir = self.service_client.get_blob_client(container=cloud_path.container, blob="/") + + self._hns_enabled = ( + root_dir.exists() + and root_dir.get_blob_properties().metadata.get("hdi_isfolder", False) == "true" + ) + + return cast(bool, self._hns_enabled) + def _get_metadata( self, cloud_path: AzureBlobPath ) -> Union["BlobProperties", "FileProperties", Dict[str, Any]]: diff --git a/pyproject.toml b/pyproject.toml index e18fd610..bc5dffe6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ classifiers = [ ] requires-python = ">=3.8" dependencies = [ - "typing_extensions>4 ; python_version < '3.11'", + "typing-extensions>4 ; python_version < '3.11'", ] [project.optional-dependencies]