Skip to content

Commit a3ad60a

Browse files
authored
Import msal_extensions only when needed (Azure#20095)
1 parent 1e66c4c commit a3ad60a

File tree

8 files changed

+36
-27
lines changed

8 files changed

+36
-27
lines changed

sdk/identity/azure-identity/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
logging. On Python 3.7+, credentials invoked by these classes now log debug
2121
rather than info messages.
2222
([#18972](https://github.com/Azure/azure-sdk-for-python/issues/18972))
23+
- Persistent cache implementations are now loaded on demand, enabling
24+
workarounds when importing transitive dependencies such as pywin32
25+
fails
26+
([#19989](https://github.com/Azure/azure-sdk-for-python/issues/19989))
2327

2428
## 1.7.0b2 (2021-07-08)
2529
### Features Added

sdk/identity/azure-identity/azure/identity/_persistent_cache.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
import sys
77
from typing import TYPE_CHECKING
88

9-
import msal_extensions
10-
119
if TYPE_CHECKING:
1210
from typing import Any
11+
import msal_extensions
1312

1413

1514
class TokenCachePersistenceOptions(object):
@@ -49,8 +48,10 @@ def __init__(self, **kwargs):
4948

5049
def _load_persistent_cache(options):
5150
# type: (TokenCachePersistenceOptions) -> msal_extensions.PersistedTokenCache
51+
import msal_extensions
52+
5253
persistence = _get_persistence(
53-
allow_unencrypted=options.allow_unencrypted_storage, account_name="MSALCache", cache_name=options.name,
54+
allow_unencrypted=options.allow_unencrypted_storage, account_name="MSALCache", cache_name=options.name
5455
)
5556
return msal_extensions.PersistedTokenCache(persistence)
5657

@@ -66,6 +67,7 @@ def _get_persistence(allow_unencrypted, account_name, cache_name):
6667
:param bool allow_unencrypted: when True, the cache will be kept in plaintext should encryption be impossible in the
6768
current environment
6869
"""
70+
import msal_extensions
6971

7072
if sys.platform.startswith("win") and "LOCALAPPDATA" in os.environ:
7173
cache_location = os.path.join(os.environ["LOCALAPPDATA"], ".IdentityService", cache_name)
@@ -87,8 +89,8 @@ def _get_persistence(allow_unencrypted, account_name, cache_name):
8789
except ImportError:
8890
if not allow_unencrypted:
8991
raise ValueError(
90-
"PyGObject is required to encrypt the persistent cache. Please install that library or ",
91-
"specify 'allow_unencrypted_cache=True' to store the cache without encryption.",
92+
"PyGObject is required to encrypt the persistent cache. Please install that library or "
93+
+ 'specify "allow_unencrypted_cache=True" to store the cache without encryption.'
9294
)
9395
return msal_extensions.FilePersistence(file_path)
9496

sdk/identity/azure-identity/tests/test_certificate_credential.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,9 @@ def validate_jwt(request, client_id, pem_bytes, expect_x5c=False):
272272
def test_token_cache(cert_path, cert_password):
273273
"""the credential should optionally use a persistent cache, and default to an in memory cache"""
274274

275-
with patch("azure.identity._persistent_cache.msal_extensions") as mock_msal_extensions:
275+
with patch("azure.identity._internal.msal_credentials._load_persistent_cache") as load_persistent_cache:
276276
credential = CertificateCredential("tenant", "client-id", cert_path, password=cert_password)
277-
assert not mock_msal_extensions.PersistedTokenCache.called
277+
assert not load_persistent_cache.called
278278
assert isinstance(credential._cache, TokenCache)
279279

280280
CertificateCredential(
@@ -284,7 +284,7 @@ def test_token_cache(cert_path, cert_password):
284284
password=cert_password,
285285
cache_persistence_options=TokenCachePersistenceOptions(),
286286
)
287-
assert mock_msal_extensions.PersistedTokenCache.call_count == 1
287+
assert load_persistent_cache.call_count == 1
288288

289289

290290
@pytest.mark.parametrize("cert_path,cert_password", BOTH_CERTS)

sdk/identity/azure-identity/tests/test_certificate_credential_async.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,11 @@ async def mock_send(request, **kwargs):
192192
def test_token_cache(cert_path, cert_password):
193193
"""the credential should optionally use a persistent cache, and default to an in memory cache"""
194194

195-
with patch("azure.identity._persistent_cache.msal_extensions") as mock_msal_extensions:
195+
with patch(CertificateCredential.__module__ + "._load_persistent_cache") as load_persistent_cache:
196196
with patch(CertificateCredential.__module__ + ".msal") as mock_msal:
197197
CertificateCredential("tenant", "client-id", cert_path, password=cert_password)
198198
assert mock_msal.TokenCache.call_count == 1
199-
assert not mock_msal_extensions.PersistedTokenCache.called
199+
assert not load_persistent_cache.called
200200

201201
CertificateCredential(
202202
"tenant",
@@ -205,7 +205,7 @@ def test_token_cache(cert_path, cert_password):
205205
password=cert_password,
206206
cache_persistence_options=TokenCachePersistenceOptions(),
207207
)
208-
assert mock_msal_extensions.PersistedTokenCache.call_count == 1
208+
assert load_persistent_cache.call_count == 1
209209

210210

211211
@pytest.mark.asyncio

sdk/identity/azure-identity/tests/test_client_secret_credential.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,15 @@ def test_regional_authority():
153153
def test_token_cache():
154154
"""the credential should default to an in memory cache, and optionally use a persistent cache"""
155155

156-
with patch("azure.identity._persistent_cache.msal_extensions") as mock_msal_extensions:
156+
with patch("azure.identity._internal.msal_credentials._load_persistent_cache") as load_persistent_cache:
157157
credential = ClientSecretCredential("tenant", "client-id", "secret")
158-
assert not mock_msal_extensions.PersistedTokenCache.called
158+
assert not load_persistent_cache.called
159159
assert isinstance(credential._cache, TokenCache)
160160

161161
ClientSecretCredential(
162162
"tenant", "client-id", "secret", cache_persistence_options=TokenCachePersistenceOptions()
163163
)
164-
assert mock_msal_extensions.PersistedTokenCache.call_count == 1
164+
assert load_persistent_cache.call_count == 1
165165

166166

167167
def test_cache_multiple_clients():

sdk/identity/azure-identity/tests/test_client_secret_credential_async.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,16 +190,16 @@ async def test_cache():
190190
def test_token_cache():
191191
"""the credential should default to an in memory cache, and optionally use a persistent cache"""
192192

193-
with patch("azure.identity._persistent_cache.msal_extensions") as mock_msal_extensions:
193+
with patch(ClientSecretCredential.__module__ + "._load_persistent_cache") as load_persistent_cache:
194194
with patch(ClientSecretCredential.__module__ + ".msal") as mock_msal:
195195
ClientSecretCredential("tenant", "client-id", "secret")
196196
assert mock_msal.TokenCache.call_count == 1
197-
assert not mock_msal_extensions.PersistedTokenCache.called
197+
assert not load_persistent_cache.called
198198

199199
ClientSecretCredential(
200200
"tenant", "client-id", "secret", cache_persistence_options=TokenCachePersistenceOptions()
201201
)
202-
assert mock_msal_extensions.PersistedTokenCache.call_count == 1
202+
assert load_persistent_cache.call_count == 1
203203

204204

205205
@pytest.mark.asyncio

sdk/identity/azure-identity/tests/test_interactive_credential.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,14 +215,14 @@ def __init__(self, **kwargs):
215215
def _request_token(self, *_, **__):
216216
pass
217217

218-
with patch("azure.identity._persistent_cache.msal_extensions") as mock_msal_extensions:
218+
with patch("azure.identity._internal.msal_credentials._load_persistent_cache") as load_persistent_cache:
219219
with patch("azure.identity._internal.msal_credentials.msal") as mock_msal:
220220
TestCredential()
221-
assert not mock_msal_extensions.PersistedTokenCache.called
221+
assert not load_persistent_cache.called
222222
assert mock_msal.TokenCache.call_count == 1
223223

224224
TestCredential(cache_persistence_options=TokenCachePersistenceOptions())
225-
assert mock_msal_extensions.PersistedTokenCache.call_count == 1
225+
assert load_persistent_cache.call_count == 1
226226

227227

228228
def test_home_account_id_client_info():

sdk/identity/azure-identity/tests/test_persistent_cache.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
# ------------------------------------
55
from azure.identity import InteractiveBrowserCredential, TokenCachePersistenceOptions
66
import pytest
7+
import msal_extensions
78

89
from helpers import mock
910

1011

1112
def test_token_cache_persistence_options():
12-
with mock.patch("azure.identity._persistent_cache.msal_extensions"):
13+
with mock.patch("azure.identity._internal.msal_credentials._load_persistent_cache"):
1314
# [START snippet]
1415
cache_options = TokenCachePersistenceOptions()
1516
credential = InteractiveBrowserCredential(cache_persistence_options=cache_options)
@@ -23,25 +24,27 @@ def test_token_cache_persistence_options():
2324

2425

2526
@mock.patch("azure.identity._persistent_cache.sys.platform", "linux2")
26-
@mock.patch("azure.identity._persistent_cache.msal_extensions")
27-
def test_persistent_cache_linux(mock_extensions):
27+
def test_persistent_cache_linux(monkeypatch):
2828
"""Credentials should use an unencrypted cache when encryption is unavailable and the user explicitly opts in.
2929
3030
This test was written when Linux was the only platform on which encryption may not be available.
3131
"""
3232
from azure.identity._persistent_cache import _load_persistent_cache
3333

34+
for cls in ("FilePersistence", "LibsecretPersistence", "PersistedTokenCache"):
35+
monkeypatch.setattr(msal_extensions, cls, mock.Mock())
36+
3437
_load_persistent_cache(TokenCachePersistenceOptions())
35-
assert mock_extensions.PersistedTokenCache.called_with(mock_extensions.LibsecretPersistence)
36-
mock_extensions.PersistedTokenCache.reset_mock()
38+
assert msal_extensions.PersistedTokenCache.called_with(msal_extensions.LibsecretPersistence)
39+
msal_extensions.PersistedTokenCache.reset_mock()
3740

3841
# when LibsecretPersistence's dependencies aren't available, constructing it raises ImportError
39-
mock_extensions.LibsecretPersistence = mock.Mock(side_effect=ImportError)
42+
msal_extensions.LibsecretPersistence = mock.Mock(side_effect=ImportError)
4043

4144
# encryption unavailable, no unencrypted storage not allowed
4245
with pytest.raises(ValueError):
4346
_load_persistent_cache(TokenCachePersistenceOptions())
4447

4548
# encryption unavailable, unencrypted storage allowed
4649
_load_persistent_cache(TokenCachePersistenceOptions(allow_unencrypted_storage=True))
47-
mock_extensions.PersistedTokenCache.called_with(mock_extensions.FilePersistence)
50+
msal_extensions.PersistedTokenCache.called_with(msal_extensions.FilePersistence)

0 commit comments

Comments
 (0)