2626
2727
2828# The __init__.py will import this. Not the other way around.
29- __version__ = "1.15 .0"
29+ __version__ = "1.16 .0"
3030
3131logger = logging .getLogger (__name__ )
3232
@@ -170,6 +170,7 @@ def __init__(
170170 # This way, it holds the same positional param place for PCA,
171171 # when we would eventually want to add this feature to PCA in future.
172172 exclude_scopes = None ,
173+ http_cache = None ,
173174 ):
174175 """Create an instance of application.
175176
@@ -285,7 +286,8 @@ def __init__(
285286 which you will later provide via one of the acquire-token request.
286287
287288 :param str azure_region:
288- Added since MSAL Python 1.12.0.
289+ AAD provides regional endpoints for apps to opt in
290+ to keep their traffic remain inside that region.
289291
290292 As of 2021 May, regional service is only available for
291293 ``acquire_token_for_client()`` sent by any of the following scenarios::
@@ -302,9 +304,7 @@ def __init__(
302304
303305 4. An app which already onboard to the region's allow-list.
304306
305- MSAL's default value is None, which means region behavior remains off.
306- If enabled, the `acquire_token_for_client()`-relevant traffic
307- would remain inside that region.
307+ This parameter defaults to None, which means region behavior remains off.
308308
309309 App developer can opt in to a regional endpoint,
310310 by provide its region name, such as "westus", "eastus2".
@@ -330,12 +330,69 @@ def __init__(
330330 or provide a custom http_client which has a short timeout.
331331 That way, the latency would be under your control,
332332 but still less performant than opting out of region feature.
333+
334+ New in version 1.12.0.
335+
333336 :param list[str] exclude_scopes: (optional)
334337 Historically MSAL hardcodes `offline_access` scope,
335338 which would allow your app to have prolonged access to user's data.
336339 If that is unnecessary or undesirable for your app,
337340 now you can use this parameter to supply an exclusion list of scopes,
338341 such as ``exclude_scopes = ["offline_access"]``.
342+
343+ :param dict http_cache:
344+ MSAL has long been caching tokens in the ``token_cache``.
345+ Recently, MSAL also introduced a concept of ``http_cache``,
346+ by automatically caching some finite amount of non-token http responses,
347+ so that *long-lived*
348+ ``PublicClientApplication`` and ``ConfidentialClientApplication``
349+ would be more performant and responsive in some situations.
350+
351+ This ``http_cache`` parameter accepts any dict-like object.
352+ If not provided, MSAL will use an in-memory dict.
353+
354+ If your app is a command-line app (CLI),
355+ you would want to persist your http_cache across different CLI runs.
356+ The following recipe shows a way to do so::
357+
358+ # Just add the following lines at the beginning of your CLI script
359+ import sys, atexit, pickle
360+ http_cache_filename = sys.argv[0] + ".http_cache"
361+ try:
362+ with open(http_cache_filename, "rb") as f:
363+ persisted_http_cache = pickle.load(f) # Take a snapshot
364+ except (
365+ IOError, # A non-exist http cache file
366+ pickle.UnpicklingError, # A corrupted http cache file
367+ EOFError, # An empty http cache file
368+ AttributeError, ImportError, IndexError, # Other corruption
369+ ):
370+ persisted_http_cache = {} # Recover by starting afresh
371+ atexit.register(lambda: pickle.dump(
372+ # When exit, flush it back to the file.
373+ # It may occasionally overwrite another process's concurrent write,
374+ # but that is fine. Subsequent runs will reach eventual consistency.
375+ persisted_http_cache, open(http_cache_file, "wb")))
376+
377+ # And then you can implement your app as you normally would
378+ app = msal.PublicClientApplication(
379+ "your_client_id",
380+ ...,
381+ http_cache=persisted_http_cache, # Utilize persisted_http_cache
382+ ...,
383+ #token_cache=..., # You may combine the old token_cache trick
384+ # Please refer to token_cache recipe at
385+ # https://msal-python.readthedocs.io/en/latest/#msal.SerializableTokenCache
386+ )
387+ app.acquire_token_interactive(["your", "scope"], ...)
388+
389+ Content inside ``http_cache`` are cheap to obtain.
390+ There is no need to share them among different apps.
391+
392+ Content inside ``http_cache`` will contain no tokens nor
393+ Personally Identifiable Information (PII). Encryption is unnecessary.
394+
395+ New in version 1.16.0.
339396 """
340397 self .client_id = client_id
341398 self .client_credential = client_credential
@@ -370,7 +427,7 @@ def __init__(
370427 self .http_client .mount ("https://" , a )
371428 self .http_client = ThrottledHttpClient (
372429 self .http_client ,
373- {} # Hard code an in-memory cache, for now
430+ {} if http_cache is None else http_cache , # Default to an in-memory dict
374431 )
375432
376433 self .app_name = app_name
@@ -437,17 +494,18 @@ def _build_telemetry_context(
437494 correlation_id = correlation_id , refresh_reason = refresh_reason )
438495
439496 def _get_regional_authority (self , central_authority ):
440- is_region_specified = bool (self ._region_configured
441- and self ._region_configured != self .ATTEMPT_REGION_DISCOVERY )
442497 self ._region_detected = self ._region_detected or _detect_region (
443498 self .http_client if self ._region_configured is not None else None )
444- if (is_region_specified and self ._region_configured != self ._region_detected ):
499+ if (self ._region_configured != self .ATTEMPT_REGION_DISCOVERY
500+ and self ._region_configured != self ._region_detected ):
445501 logger .warning ('Region configured ({}) != region detected ({})' .format (
446502 repr (self ._region_configured ), repr (self ._region_detected )))
447503 region_to_use = (
448- self ._region_configured if is_region_specified else self ._region_detected )
504+ self ._region_detected
505+ if self ._region_configured == self .ATTEMPT_REGION_DISCOVERY
506+ else self ._region_configured ) # It will retain the None i.e. opted out
507+ logger .debug ('Region to be used: {}' .format (repr (region_to_use )))
449508 if region_to_use :
450- logger .info ('Region to be used: {}' .format (repr (region_to_use )))
451509 regional_host = ("{}.r.login.microsoftonline.com" .format (region_to_use )
452510 if central_authority .instance in (
453511 # The list came from https://github.com/AzureAD/microsoft-authentication-library-for-python/pull/358/files#r629400328
0 commit comments