Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
EPOCH_AS_FILETIME = 116444736000000000 # January 1, 1970 as MS filetime
HUNDREDS_OF_NANOSECONDS = 10000000

SERVICE_PORT_NUMBERS = {
"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")
Expand Down Expand Up @@ -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 SERVICE_PORT_NUMBERS:
raise ValueError(f"Unsupported service name: {service}")
return f"http://127.0.0.1:{SERVICE_PORT_NUMBERS[service]}/{DEVSTORE_ACCOUNT_NAME}"
32 changes: 19 additions & 13 deletions sdk/storage/azure-storage-blob/tests/test_blob_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
32 changes: 19 additions & 13 deletions sdk/storage/azure-storage-blob/tests/test_blob_client_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
EPOCH_AS_FILETIME = 116444736000000000 # January 1, 1970 as MS filetime
HUNDREDS_OF_NANOSECONDS = 10000000

SERVICE_PORT_NUMBERS = {
"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")
Expand Down Expand Up @@ -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 SERVICE_PORT_NUMBERS:
raise ValueError(f"Unsupported service name: {service}")
return f"http://127.0.0.1:{SERVICE_PORT_NUMBERS[service]}/{DEVSTORE_ACCOUNT_NAME}"
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Loading