diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client.py index 478882c09d58..dbb3b369c6d2 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client.py @@ -37,6 +37,7 @@ from .authentication import SharedKeyCredentialPolicy from .constants import CONNECTION_TIMEOUT, DEFAULT_OAUTH_SCOPE, READ_TIMEOUT, SERVICE_HOST_BASE, STORAGE_OAUTH_SCOPE from .models import LocationMode, StorageConfiguration +from .parser import DEVSTORE_ACCOUNT_KEY, _get_development_storage_endpoint from .policies import ( ExponentialRetry, QueueMessagePolicy, @@ -407,6 +408,8 @@ def parse_connection_str( if any(len(tup) != 2 for tup in conn_settings_list): raise ValueError("Connection string is either blank or malformed.") conn_settings = dict((key.upper(), val) for key, val in conn_settings_list) + if conn_settings.get('USEDEVELOPMENTSTORAGE') == 'true': + return _get_development_storage_endpoint(service), None, DEVSTORE_ACCOUNT_KEY endpoints = _SERVICE_PARAMS[service] primary = None secondary = None diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client_async.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client_async.py index c08d0615110d..d77258b3b426 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client_async.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client_async.py @@ -26,6 +26,7 @@ from .base_client import create_configuration from .constants import CONNECTION_TIMEOUT, DEFAULT_OAUTH_SCOPE, READ_TIMEOUT, SERVICE_HOST_BASE, STORAGE_OAUTH_SCOPE from .models import StorageConfiguration +from .parser import DEVSTORE_ACCOUNT_KEY, _get_development_storage_endpoint from .policies import ( QueueMessagePolicy, StorageContentValidation, @@ -201,6 +202,8 @@ def parse_connection_str( if any(len(tup) != 2 for tup in conn_settings_list): raise ValueError("Connection string is either blank or malformed.") conn_settings = dict((key.upper(), val) for key, val in conn_settings_list) + if conn_settings.get('USEDEVELOPMENTSTORAGE') == 'true': + return _get_development_storage_endpoint(service), None, DEVSTORE_ACCOUNT_KEY endpoints = _SERVICE_PARAMS[service] primary = None secondary = None diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/parser.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/parser.py index e4fcb8f041ba..7755398d8090 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/parser.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/parser.py @@ -10,6 +10,14 @@ EPOCH_AS_FILETIME = 116444736000000000 # January 1, 1970 as MS filetime HUNDREDS_OF_NANOSECONDS = 10000000 +DEVSTORE_PORTS = { + "blob": 10000, + "dfs": 10000, + "queue": 10001, +} +DEVSTORE_ACCOUNT_NAME = "devstoreaccount1" +DEVSTORE_ACCOUNT_KEY = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" + def _to_utc_datetime(value: datetime) -> str: return value.strftime("%Y-%m-%dT%H:%M:%SZ") @@ -51,3 +59,15 @@ def _filetime_to_datetime(filetime: str) -> Optional[datetime]: # Try RFC 1123 as backup return _rfc_1123_to_datetime(filetime) + + +def _get_development_storage_endpoint(service: str) -> str: + """Creates a development storage endpoint for Azurite Storage Emulator. + + :param str service: The service name. + :return: The development storage endpoint. + :rtype: str + """ + if service.lower() not in DEVSTORE_PORTS: + raise ValueError(f"Unsupported service name: {service}") + return f"http://127.0.0.1:{DEVSTORE_PORTS[service]}/{DEVSTORE_ACCOUNT_NAME}" diff --git a/sdk/storage/azure-storage-blob/tests/test_blob_client.py b/sdk/storage/azure-storage-blob/tests/test_blob_client.py index 1d6361a8ef28..b4897f0f4e53 100644 --- a/sdk/storage/azure-storage-blob/tests/test_blob_client.py +++ b/sdk/storage/azure-storage-blob/tests/test_blob_client.py @@ -19,6 +19,7 @@ VERSION, ) from azure.storage.blob._shared.base_client import create_configuration +from azure.storage.blob._shared.parser import DEVSTORE_ACCOUNT_KEY, DEVSTORE_ACCOUNT_NAME from devtools_testutils import recorded_by_proxy from devtools_testutils.storage import StorageRecordedTestCase @@ -101,6 +102,24 @@ def test_create_service_with_connection_string(self, **kwargs): self.validate_standard_account_endpoints(service, service_type[1], storage_account_name, storage_account_key) assert service.scheme == 'https' + @BlobPreparer() + def test_create_service_use_development_storage(self): + for service_type in SERVICES.items(): + # Act + service = service_type[0].from_connection_string( + "UseDevelopmentStorage=true;", + container_name="test", + blob_name="test" + ) + + # Assert + assert service is not None + assert service.scheme == "http" + assert service.account_name == DEVSTORE_ACCOUNT_NAME + assert service.credential.account_name == DEVSTORE_ACCOUNT_NAME + assert service.credential.account_key == DEVSTORE_ACCOUNT_KEY + assert f"127.0.0.1:10000/{DEVSTORE_ACCOUNT_NAME}" in service.url + @BlobPreparer() def test_create_service_with_sas(self, **kwargs): storage_account_name = kwargs.pop("storage_account_name") @@ -343,19 +362,6 @@ def test_create_service_with_connection_string_endpoint_protocol(self, **kwargs) 'http://{}-secondary.{}.core.chinacloudapi.cn'.format(storage_account_name, service_type[1])) assert service.scheme == 'http' - @BlobPreparer() - def test_create_service_with_connection_string_emulated(self, **kwargs): - storage_account_name = kwargs.pop("storage_account_name") - storage_account_key = kwargs.pop("storage_account_key") - - # Arrange - for service_type in SERVICES.items(): - conn_string = 'UseDevelopmentStorage=true;'.format(storage_account_name, storage_account_key) - - # Act - with pytest.raises(ValueError): - service = service_type[0].from_connection_string(conn_string, container_name="foo", blob_name="bar") - @BlobPreparer() def test_create_service_with_cstr_anonymous(self): # Arrange diff --git a/sdk/storage/azure-storage-blob/tests/test_blob_client_async.py b/sdk/storage/azure-storage-blob/tests/test_blob_client_async.py index 6cd57fefd5b7..5492f85c0cf7 100644 --- a/sdk/storage/azure-storage-blob/tests/test_blob_client_async.py +++ b/sdk/storage/azure-storage-blob/tests/test_blob_client_async.py @@ -15,6 +15,7 @@ ResourceTypes, VERSION, ) +from azure.storage.blob._shared.parser import DEVSTORE_ACCOUNT_KEY, DEVSTORE_ACCOUNT_NAME from azure.storage.blob.aio import ( BlobClient, ContainerClient, @@ -89,6 +90,24 @@ def test_create_service_with_connection_string(self, **kwargs): self.validate_standard_account_endpoints(service, service_type[1], storage_account_name, storage_account_key) assert service.scheme == 'https' + @BlobPreparer() + def test_create_service_use_development_storage(self): + for service_type in SERVICES.items(): + # Act + service = service_type[0].from_connection_string( + "UseDevelopmentStorage=true;", + container_name="test", + blob_name="test" + ) + + # Assert + assert service is not None + assert service.scheme == "http" + assert service.account_name == DEVSTORE_ACCOUNT_NAME + assert service.credential.account_name == DEVSTORE_ACCOUNT_NAME + assert service.credential.account_key == DEVSTORE_ACCOUNT_KEY + assert f"127.0.0.1:10000/{DEVSTORE_ACCOUNT_NAME}" in service.url + @BlobPreparer() def test_create_service_with_sas(self, **kwargs): storage_account_name = kwargs.pop("storage_account_name") @@ -345,19 +364,6 @@ def test_creat_serv_w_connstr_endpoint_protocol(self, **kwargs): 'http://{}-secondary.{}.core.chinacloudapi.cn'.format(storage_account_name, service_type[1])) assert service.scheme == 'http' - @BlobPreparer() - def test_create_service_with_connection_string_emulated(self, **kwargs): - storage_account_name = kwargs.pop("storage_account_name") - storage_account_key = kwargs.pop("storage_account_key") - - # Arrange - for service_type in SERVICES.items(): - conn_string = 'UseDevelopmentStorage=true;'.format(storage_account_name, storage_account_key) - - # Act - with pytest.raises(ValueError): - service = service_type[0].from_connection_string(conn_string, container_name="foo", blob_name="bar") - @BlobPreparer() def test_create_service_with_connection_string_anonymous(self): # Arrange diff --git a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client.py b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client.py index 478882c09d58..dbb3b369c6d2 100644 --- a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client.py +++ b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client.py @@ -37,6 +37,7 @@ from .authentication import SharedKeyCredentialPolicy from .constants import CONNECTION_TIMEOUT, DEFAULT_OAUTH_SCOPE, READ_TIMEOUT, SERVICE_HOST_BASE, STORAGE_OAUTH_SCOPE from .models import LocationMode, StorageConfiguration +from .parser import DEVSTORE_ACCOUNT_KEY, _get_development_storage_endpoint from .policies import ( ExponentialRetry, QueueMessagePolicy, @@ -407,6 +408,8 @@ def parse_connection_str( if any(len(tup) != 2 for tup in conn_settings_list): raise ValueError("Connection string is either blank or malformed.") conn_settings = dict((key.upper(), val) for key, val in conn_settings_list) + if conn_settings.get('USEDEVELOPMENTSTORAGE') == 'true': + return _get_development_storage_endpoint(service), None, DEVSTORE_ACCOUNT_KEY endpoints = _SERVICE_PARAMS[service] primary = None secondary = None diff --git a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client_async.py b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client_async.py index c08d0615110d..d77258b3b426 100644 --- a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client_async.py +++ b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/base_client_async.py @@ -26,6 +26,7 @@ from .base_client import create_configuration from .constants import CONNECTION_TIMEOUT, DEFAULT_OAUTH_SCOPE, READ_TIMEOUT, SERVICE_HOST_BASE, STORAGE_OAUTH_SCOPE from .models import StorageConfiguration +from .parser import DEVSTORE_ACCOUNT_KEY, _get_development_storage_endpoint from .policies import ( QueueMessagePolicy, StorageContentValidation, @@ -201,6 +202,8 @@ def parse_connection_str( if any(len(tup) != 2 for tup in conn_settings_list): raise ValueError("Connection string is either blank or malformed.") conn_settings = dict((key.upper(), val) for key, val in conn_settings_list) + if conn_settings.get('USEDEVELOPMENTSTORAGE') == 'true': + return _get_development_storage_endpoint(service), None, DEVSTORE_ACCOUNT_KEY endpoints = _SERVICE_PARAMS[service] primary = None secondary = None diff --git a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/parser.py b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/parser.py index e4fcb8f041ba..7755398d8090 100644 --- a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/parser.py +++ b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/parser.py @@ -10,6 +10,14 @@ EPOCH_AS_FILETIME = 116444736000000000 # January 1, 1970 as MS filetime HUNDREDS_OF_NANOSECONDS = 10000000 +DEVSTORE_PORTS = { + "blob": 10000, + "dfs": 10000, + "queue": 10001, +} +DEVSTORE_ACCOUNT_NAME = "devstoreaccount1" +DEVSTORE_ACCOUNT_KEY = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" + def _to_utc_datetime(value: datetime) -> str: return value.strftime("%Y-%m-%dT%H:%M:%SZ") @@ -51,3 +59,15 @@ def _filetime_to_datetime(filetime: str) -> Optional[datetime]: # Try RFC 1123 as backup return _rfc_1123_to_datetime(filetime) + + +def _get_development_storage_endpoint(service: str) -> str: + """Creates a development storage endpoint for Azurite Storage Emulator. + + :param str service: The service name. + :return: The development storage endpoint. + :rtype: str + """ + if service.lower() not in DEVSTORE_PORTS: + raise ValueError(f"Unsupported service name: {service}") + return f"http://127.0.0.1:{DEVSTORE_PORTS[service]}/{DEVSTORE_ACCOUNT_NAME}" diff --git a/sdk/storage/azure-storage-file-datalake/tests/test_datalake_service_client.py b/sdk/storage/azure-storage-file-datalake/tests/test_datalake_service_client.py index cc58ffa13a48..538a970b1566 100644 --- a/sdk/storage/azure-storage-file-datalake/tests/test_datalake_service_client.py +++ b/sdk/storage/azure-storage-file-datalake/tests/test_datalake_service_client.py @@ -18,7 +18,9 @@ FileSystemClient, Metrics, RetentionPolicy, - StaticWebsite) + StaticWebsite +) +from azure.storage.filedatalake._shared.parser import DEVSTORE_ACCOUNT_KEY, DEVSTORE_ACCOUNT_NAME from devtools_testutils import recorded_by_proxy from devtools_testutils.storage import StorageRecordedTestCase @@ -109,6 +111,14 @@ def _assert_retention_equal(self, ret1, ret2): assert ret1.enabled == ret2.enabled assert ret1.days == ret2.days + def _assert_devstore_conn_str(self, service): + assert service is not None + assert service.scheme == "http" + assert service.account_name == DEVSTORE_ACCOUNT_NAME + assert service.credential.account_name == DEVSTORE_ACCOUNT_NAME + assert service.credential.account_key == DEVSTORE_ACCOUNT_KEY + assert f"127.0.0.1:10000/{DEVSTORE_ACCOUNT_NAME}" in service.url + # --Test cases per service --------------------------------------- @DataLakePreparer() @recorded_by_proxy @@ -398,6 +408,22 @@ def test_connectionstring_without_secondary(self): assert client.primary_hostname == 'foo.dfs.core.windows.net' assert not client.secondary_hostname + @DataLakePreparer() + def test_connectionstring_use_development_storage(self): + test_conn_str = "UseDevelopmentStorage=true;" + + dsc = DataLakeServiceClient.from_connection_string(test_conn_str) + self._assert_devstore_conn_str(dsc) + + fsc = FileSystemClient.from_connection_string(test_conn_str, "fsname") + self._assert_devstore_conn_str(fsc) + + dfc = DataLakeFileClient.from_connection_string(test_conn_str, "fsname", "fpath") + self._assert_devstore_conn_str(dfc) + + ddc = DataLakeDirectoryClient.from_connection_string(test_conn_str, "fsname", "dname") + self._assert_devstore_conn_str(ddc) + @DataLakePreparer() @recorded_by_proxy def test_azure_named_key_credential_access(self, **kwargs): diff --git a/sdk/storage/azure-storage-file-datalake/tests/test_datalake_service_client_async.py b/sdk/storage/azure-storage-file-datalake/tests/test_datalake_service_client_async.py index 06383906132b..6b6351c8e14b 100644 --- a/sdk/storage/azure-storage-file-datalake/tests/test_datalake_service_client_async.py +++ b/sdk/storage/azure-storage-file-datalake/tests/test_datalake_service_client_async.py @@ -15,12 +15,15 @@ CorsRule, Metrics, RetentionPolicy, - StaticWebsite) + StaticWebsite +) +from azure.storage.filedatalake._shared.parser import DEVSTORE_ACCOUNT_KEY, DEVSTORE_ACCOUNT_NAME from azure.storage.filedatalake.aio import ( DataLakeDirectoryClient, DataLakeFileClient, DataLakeServiceClient, - FileSystemClient) + FileSystemClient +) from devtools_testutils.aio import recorded_by_proxy_async from devtools_testutils.storage.aio import AsyncStorageRecordedTestCase @@ -114,6 +117,14 @@ def _assert_retention_equal(self, ret1, ret2): assert ret1.enabled == ret2.enabled assert ret1.days == ret2.days + def _assert_devstore_conn_str(self, service): + assert service is not None + assert service.scheme == "http" + assert service.account_name == DEVSTORE_ACCOUNT_NAME + assert service.credential.account_name == DEVSTORE_ACCOUNT_NAME + assert service.credential.account_key == DEVSTORE_ACCOUNT_KEY + assert f"127.0.0.1:10000/{DEVSTORE_ACCOUNT_NAME}" in service.url + # --Test cases per service --------------------------------------- @DataLakePreparer() @recorded_by_proxy_async @@ -399,6 +410,22 @@ async def test_connectionstring_without_secondary(self): assert client.primary_hostname == 'foo.dfs.core.windows.net' assert not client.secondary_hostname + @DataLakePreparer() + def test_connectionstring_use_development_storage(self): + test_conn_str = "UseDevelopmentStorage=true;" + + dsc = DataLakeServiceClient.from_connection_string(test_conn_str) + self._assert_devstore_conn_str(dsc) + + fsc = FileSystemClient.from_connection_string(test_conn_str, "fsname") + self._assert_devstore_conn_str(fsc) + + dfc = DataLakeFileClient.from_connection_string(test_conn_str, "fsname", "fpath") + self._assert_devstore_conn_str(dfc) + + ddc = DataLakeDirectoryClient.from_connection_string(test_conn_str, "fsname", "dname") + self._assert_devstore_conn_str(ddc) + @DataLakePreparer() @recorded_by_proxy_async async def test_azure_named_key_credential_access(self, **kwargs): diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client.py index 478882c09d58..dbb3b369c6d2 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client.py @@ -37,6 +37,7 @@ from .authentication import SharedKeyCredentialPolicy from .constants import CONNECTION_TIMEOUT, DEFAULT_OAUTH_SCOPE, READ_TIMEOUT, SERVICE_HOST_BASE, STORAGE_OAUTH_SCOPE from .models import LocationMode, StorageConfiguration +from .parser import DEVSTORE_ACCOUNT_KEY, _get_development_storage_endpoint from .policies import ( ExponentialRetry, QueueMessagePolicy, @@ -407,6 +408,8 @@ def parse_connection_str( if any(len(tup) != 2 for tup in conn_settings_list): raise ValueError("Connection string is either blank or malformed.") conn_settings = dict((key.upper(), val) for key, val in conn_settings_list) + if conn_settings.get('USEDEVELOPMENTSTORAGE') == 'true': + return _get_development_storage_endpoint(service), None, DEVSTORE_ACCOUNT_KEY endpoints = _SERVICE_PARAMS[service] primary = None secondary = None diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client_async.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client_async.py index c08d0615110d..d77258b3b426 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client_async.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/base_client_async.py @@ -26,6 +26,7 @@ from .base_client import create_configuration from .constants import CONNECTION_TIMEOUT, DEFAULT_OAUTH_SCOPE, READ_TIMEOUT, SERVICE_HOST_BASE, STORAGE_OAUTH_SCOPE from .models import StorageConfiguration +from .parser import DEVSTORE_ACCOUNT_KEY, _get_development_storage_endpoint from .policies import ( QueueMessagePolicy, StorageContentValidation, @@ -201,6 +202,8 @@ def parse_connection_str( if any(len(tup) != 2 for tup in conn_settings_list): raise ValueError("Connection string is either blank or malformed.") conn_settings = dict((key.upper(), val) for key, val in conn_settings_list) + if conn_settings.get('USEDEVELOPMENTSTORAGE') == 'true': + return _get_development_storage_endpoint(service), None, DEVSTORE_ACCOUNT_KEY endpoints = _SERVICE_PARAMS[service] primary = None secondary = None diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/parser.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/parser.py index e4fcb8f041ba..7755398d8090 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/parser.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/parser.py @@ -10,6 +10,14 @@ EPOCH_AS_FILETIME = 116444736000000000 # January 1, 1970 as MS filetime HUNDREDS_OF_NANOSECONDS = 10000000 +DEVSTORE_PORTS = { + "blob": 10000, + "dfs": 10000, + "queue": 10001, +} +DEVSTORE_ACCOUNT_NAME = "devstoreaccount1" +DEVSTORE_ACCOUNT_KEY = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" + def _to_utc_datetime(value: datetime) -> str: return value.strftime("%Y-%m-%dT%H:%M:%SZ") @@ -51,3 +59,15 @@ def _filetime_to_datetime(filetime: str) -> Optional[datetime]: # Try RFC 1123 as backup return _rfc_1123_to_datetime(filetime) + + +def _get_development_storage_endpoint(service: str) -> str: + """Creates a development storage endpoint for Azurite Storage Emulator. + + :param str service: The service name. + :return: The development storage endpoint. + :rtype: str + """ + if service.lower() not in DEVSTORE_PORTS: + raise ValueError(f"Unsupported service name: {service}") + return f"http://127.0.0.1:{DEVSTORE_PORTS[service]}/{DEVSTORE_ACCOUNT_NAME}" diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client.py index 203aeeecdcee..9a5ab430bf8f 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client.py @@ -37,6 +37,7 @@ from .authentication import SharedKeyCredentialPolicy from .constants import CONNECTION_TIMEOUT, DEFAULT_OAUTH_SCOPE, READ_TIMEOUT, SERVICE_HOST_BASE, STORAGE_OAUTH_SCOPE from .models import LocationMode, StorageConfiguration +from .parser import DEVSTORE_ACCOUNT_KEY, _get_development_storage_endpoint from .policies import ( ExponentialRetry, QueueMessagePolicy, @@ -407,6 +408,8 @@ def parse_connection_str( if any(len(tup) != 2 for tup in conn_settings_list): raise ValueError("Connection string is either blank or malformed.") conn_settings = dict((key.upper(), val) for key, val in conn_settings_list) + if conn_settings.get("USEDEVELOPMENTSTORAGE") == "true": + return _get_development_storage_endpoint(service), None, DEVSTORE_ACCOUNT_KEY endpoints = _SERVICE_PARAMS[service] primary = None secondary = None diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client_async.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client_async.py index c08d0615110d..6fff66b5a920 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client_async.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client_async.py @@ -26,6 +26,7 @@ from .base_client import create_configuration from .constants import CONNECTION_TIMEOUT, DEFAULT_OAUTH_SCOPE, READ_TIMEOUT, SERVICE_HOST_BASE, STORAGE_OAUTH_SCOPE from .models import StorageConfiguration +from .parser import DEVSTORE_ACCOUNT_KEY, _get_development_storage_endpoint from .policies import ( QueueMessagePolicy, StorageContentValidation, @@ -201,6 +202,8 @@ def parse_connection_str( if any(len(tup) != 2 for tup in conn_settings_list): raise ValueError("Connection string is either blank or malformed.") conn_settings = dict((key.upper(), val) for key, val in conn_settings_list) + if conn_settings.get("USEDEVELOPMENTSTORAGE") == "true": + return _get_development_storage_endpoint(service), None, DEVSTORE_ACCOUNT_KEY endpoints = _SERVICE_PARAMS[service] primary = None secondary = None diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/parser.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/parser.py index e4fcb8f041ba..7755398d8090 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/parser.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/parser.py @@ -10,6 +10,14 @@ EPOCH_AS_FILETIME = 116444736000000000 # January 1, 1970 as MS filetime HUNDREDS_OF_NANOSECONDS = 10000000 +DEVSTORE_PORTS = { + "blob": 10000, + "dfs": 10000, + "queue": 10001, +} +DEVSTORE_ACCOUNT_NAME = "devstoreaccount1" +DEVSTORE_ACCOUNT_KEY = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" + def _to_utc_datetime(value: datetime) -> str: return value.strftime("%Y-%m-%dT%H:%M:%SZ") @@ -51,3 +59,15 @@ def _filetime_to_datetime(filetime: str) -> Optional[datetime]: # Try RFC 1123 as backup return _rfc_1123_to_datetime(filetime) + + +def _get_development_storage_endpoint(service: str) -> str: + """Creates a development storage endpoint for Azurite Storage Emulator. + + :param str service: The service name. + :return: The development storage endpoint. + :rtype: str + """ + if service.lower() not in DEVSTORE_PORTS: + raise ValueError(f"Unsupported service name: {service}") + return f"http://127.0.0.1:{DEVSTORE_PORTS[service]}/{DEVSTORE_ACCOUNT_NAME}" diff --git a/sdk/storage/azure-storage-queue/tests/test_queue_client.py b/sdk/storage/azure-storage-queue/tests/test_queue_client.py index e2d999f57a6a..8a8ec9150fdb 100644 --- a/sdk/storage/azure-storage-queue/tests/test_queue_client.py +++ b/sdk/storage/azure-storage-queue/tests/test_queue_client.py @@ -16,6 +16,7 @@ ResourceTypes, VERSION, ) +from azure.storage.queue._shared.parser import DEVSTORE_ACCOUNT_KEY, DEVSTORE_ACCOUNT_NAME from devtools_testutils import recorded_by_proxy from devtools_testutils.storage import StorageRecordedTestCase @@ -309,14 +310,18 @@ def test_create_service_with_connection_string_endpoint_protocol(self, **kwargs) assert service.scheme == "http" @QueuePreparer() - def test_create_service_with_connection_string_emulated(self, *args): - # Arrange + def test_create_service_use_development_storage(self): for service_type in SERVICES.items(): - conn_string = "UseDevelopmentStorage=true;" - # Act - with pytest.raises(ValueError): - service = service_type[0].from_connection_string(conn_string, queue_name="foo") + service = service_type[0].from_connection_string("UseDevelopmentStorage=true;", queue_name="test") + + # Assert + assert service is not None + assert service.scheme == "http" + assert service.account_name == DEVSTORE_ACCOUNT_NAME + assert service.credential.account_name == DEVSTORE_ACCOUNT_NAME + assert service.credential.account_key == DEVSTORE_ACCOUNT_KEY + assert f"127.0.0.1:10001/{DEVSTORE_ACCOUNT_NAME}" in service.url @QueuePreparer() def test_create_service_with_connection_string_custom_domain(self, **kwargs): diff --git a/sdk/storage/azure-storage-queue/tests/test_queue_client_async.py b/sdk/storage/azure-storage-queue/tests/test_queue_client_async.py index e5b4d516bb7b..3e3f5c2a504c 100644 --- a/sdk/storage/azure-storage-queue/tests/test_queue_client_async.py +++ b/sdk/storage/azure-storage-queue/tests/test_queue_client_async.py @@ -9,6 +9,7 @@ import pytest from azure.storage.queue import AccountSasPermissions, generate_account_sas, ResourceTypes, VERSION +from azure.storage.queue._shared.parser import DEVSTORE_ACCOUNT_KEY, DEVSTORE_ACCOUNT_NAME from azure.storage.queue.aio import QueueClient, QueueServiceClient from devtools_testutils.aio import recorded_by_proxy_async @@ -298,14 +299,18 @@ def test_create_service_with_conn_str_endpoint_protocol(self, **kwargs): assert service.scheme == "http" @QueuePreparer() - def test_create_service_with_connection_string_emulated(self, *args): - # Arrange + def test_create_service_use_development_storage(self): for service_type in SERVICES.items(): - conn_string = "UseDevelopmentStorage=true;" - # Act - with pytest.raises(ValueError): - service = service_type[0].from_connection_string(conn_string, queue_name="foo") + service = service_type[0].from_connection_string("UseDevelopmentStorage=true;", queue_name="test") + + # Assert + assert service is not None + assert service.scheme == "http" + assert service.account_name == DEVSTORE_ACCOUNT_NAME + assert service.credential.account_name == DEVSTORE_ACCOUNT_NAME + assert service.credential.account_key == DEVSTORE_ACCOUNT_KEY + assert f"127.0.0.1:10001/{DEVSTORE_ACCOUNT_NAME}" in service.url @QueuePreparer() def test_create_service_with_connection_string_custom_domain(self, **kwargs):