py2docfx 0.1.22.dev2259826__py3-none-any.whl → 0.1.22rc2268964__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. py2docfx/venv/basevenv/Lib/site-packages/certifi/__init__.py +1 -1
  2. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/default.py +8 -9
  3. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/imds.py +7 -3
  4. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/managed_identity.py +7 -1
  5. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/shared_cache.py +2 -2
  6. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/interactive.py +2 -2
  7. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/msal_managed_identity_client.py +1 -1
  8. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_version.py +1 -1
  9. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/default.py +8 -9
  10. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/imds.py +7 -3
  11. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/managed_identity.py +7 -1
  12. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/shared_cache.py +2 -2
  13. py2docfx/venv/venv1/Lib/site-packages/cachetools/__init__.py +96 -122
  14. py2docfx/venv/venv1/Lib/site-packages/cachetools/{_decorators.py → _cached.py} +106 -13
  15. py2docfx/venv/venv1/Lib/site-packages/cachetools/_cachedmethod.py +128 -0
  16. py2docfx/venv/venv1/Lib/site-packages/cachetools/func.py +5 -25
  17. py2docfx/venv/venv1/Lib/site-packages/certifi/__init__.py +1 -1
  18. py2docfx/venv/venv1/Lib/site-packages/cryptography/__about__.py +1 -1
  19. py2docfx/venv/venv1/Lib/site-packages/google/api_core/client_options.py +9 -2
  20. py2docfx/venv/venv1/Lib/site-packages/google/api_core/general_helpers.py +36 -0
  21. py2docfx/venv/venv1/Lib/site-packages/google/api_core/grpc_helpers.py +10 -7
  22. py2docfx/venv/venv1/Lib/site-packages/google/api_core/grpc_helpers_async.py +8 -3
  23. py2docfx/venv/venv1/Lib/site-packages/google/api_core/operations_v1/transports/base.py +13 -7
  24. py2docfx/venv/venv1/Lib/site-packages/google/api_core/operations_v1/transports/rest.py +19 -12
  25. py2docfx/venv/venv1/Lib/site-packages/google/api_core/operations_v1/transports/rest_asyncio.py +21 -0
  26. py2docfx/venv/venv1/Lib/site-packages/google/api_core/version.py +1 -1
  27. py2docfx/venv/venv1/Lib/site-packages/google/auth/_default.py +66 -12
  28. py2docfx/venv/venv1/Lib/site-packages/google/auth/_default_async.py +16 -10
  29. py2docfx/venv/venv1/Lib/site-packages/google/auth/_helpers.py +41 -0
  30. py2docfx/venv/venv1/Lib/site-packages/google/auth/compute_engine/credentials.py +67 -6
  31. py2docfx/venv/venv1/Lib/site-packages/google/auth/credentials.py +161 -18
  32. py2docfx/venv/venv1/Lib/site-packages/google/auth/environment_vars.py +4 -0
  33. py2docfx/venv/venv1/Lib/site-packages/google/auth/external_account.py +33 -10
  34. py2docfx/venv/venv1/Lib/site-packages/google/auth/external_account_authorized_user.py +24 -1
  35. py2docfx/venv/venv1/Lib/site-packages/google/auth/identity_pool.py +25 -1
  36. py2docfx/venv/venv1/Lib/site-packages/google/auth/impersonated_credentials.py +57 -9
  37. py2docfx/venv/venv1/Lib/site-packages/google/auth/pluggable.py +25 -1
  38. py2docfx/venv/venv1/Lib/site-packages/google/auth/version.py +1 -1
  39. py2docfx/venv/venv1/Lib/site-packages/google/oauth2/_client.py +117 -0
  40. py2docfx/venv/venv1/Lib/site-packages/google/oauth2/service_account.py +39 -4
  41. {py2docfx-0.1.22.dev2259826.dist-info → py2docfx-0.1.22rc2268964.dist-info}/METADATA +1 -1
  42. {py2docfx-0.1.22.dev2259826.dist-info → py2docfx-0.1.22rc2268964.dist-info}/RECORD +44 -43
  43. {py2docfx-0.1.22.dev2259826.dist-info → py2docfx-0.1.22rc2268964.dist-info}/WHEEL +0 -0
  44. {py2docfx-0.1.22.dev2259826.dist-info → py2docfx-0.1.22rc2268964.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
1
  from .core import contents, where
2
2
 
3
3
  __all__ = ["contents", "where"]
4
- __version__ = "2025.08.03"
4
+ __version__ = "2025.10.05"
@@ -172,7 +172,8 @@ class DefaultAzureCredential(ChainedTokenCredential):
172
172
 
173
173
  process_timeout = kwargs.pop("process_timeout", 10)
174
174
  require_envvar = kwargs.pop("require_envvar", False)
175
- if require_envvar and not os.environ.get(EnvironmentVariables.AZURE_TOKEN_CREDENTIALS):
175
+ token_credentials_env = os.environ.get(EnvironmentVariables.AZURE_TOKEN_CREDENTIALS, "").strip().lower()
176
+ if require_envvar and not token_credentials_env:
176
177
  raise ValueError(
177
178
  "AZURE_TOKEN_CREDENTIALS environment variable is required but is not set or is empty. "
178
179
  "Set it to 'dev', 'prod', or a specific credential name."
@@ -274,18 +275,16 @@ class DefaultAzureCredential(ChainedTokenCredential):
274
275
  ManagedIdentityCredential(
275
276
  client_id=managed_identity_client_id,
276
277
  _exclude_workload_identity_credential=exclude_workload_identity_credential,
278
+ _enable_imds_probe=token_credentials_env != "managedidentitycredential",
277
279
  **kwargs,
278
280
  )
279
281
  )
280
282
  if not exclude_shared_token_cache_credential and SharedTokenCacheCredential.supported():
281
- try:
282
- # username and/or tenant_id are only required when the cache contains tokens for multiple identities
283
- shared_cache = SharedTokenCacheCredential(
284
- username=shared_cache_username, tenant_id=shared_cache_tenant_id, authority=authority, **kwargs
285
- )
286
- credentials.append(shared_cache)
287
- except Exception as ex: # pylint:disable=broad-except
288
- _LOGGER.info("Shared token cache is unavailable: '%s'", ex)
283
+ # username and/or tenant_id are only required when the cache contains tokens for multiple identities
284
+ shared_cache = SharedTokenCacheCredential(
285
+ username=shared_cache_username, tenant_id=shared_cache_tenant_id, authority=authority, **kwargs
286
+ )
287
+ credentials.append(shared_cache)
289
288
  if not exclude_visual_studio_code_credential:
290
289
  credentials.append(VisualStudioCodeCredential(tenant_id=vscode_tenant_id))
291
290
  if not exclude_cli_credential:
@@ -82,6 +82,10 @@ def _check_forbidden_response(ex: HttpResponseError) -> None:
82
82
 
83
83
  class ImdsCredential(MsalManagedIdentityClient):
84
84
  def __init__(self, **kwargs: Any) -> None:
85
+ # If set to True/False, _enable_imds_probe forces whether or not the credential
86
+ # probes for the IMDS endpoint before attempting to get a token. If None (the default),
87
+ # the credential probes only if it's part of a ChainedTokenCredential chain.
88
+ self._enable_imds_probe = kwargs.pop("_enable_imds_probe", None)
85
89
  super().__init__(retry_policy_class=ImdsRetryPolicy, **dict(PIPELINE_SETTINGS, **kwargs))
86
90
  self._config = kwargs
87
91
 
@@ -102,9 +106,9 @@ class ImdsCredential(MsalManagedIdentityClient):
102
106
 
103
107
  def _request_token(self, *scopes: str, **kwargs: Any) -> AccessTokenInfo:
104
108
 
105
- if within_credential_chain.get() and not self._endpoint_available:
106
- # If within a chain (e.g. DefaultAzureCredential), we do a quick check to see if the IMDS endpoint
107
- # is available to avoid hanging for a long time if the endpoint isn't available.
109
+ do_probe = self._enable_imds_probe if self._enable_imds_probe is not None else within_credential_chain.get()
110
+ if do_probe and not self._endpoint_available:
111
+ # Probe to see if the IMDS endpoint is available to avoid hanging for a long time if it's not.
108
112
  try:
109
113
  client = ManagedIdentityClient(_get_request, **dict(PIPELINE_SETTINGS, **self._config))
110
114
  client.request_token(*scopes, connection_timeout=1, retry_total=0)
@@ -76,6 +76,7 @@ class ManagedIdentityCredential:
76
76
  user_identity_info = validate_identity_config(client_id, identity_config)
77
77
  self._credential: Optional[SupportsTokenInfo] = None
78
78
  exclude_workload_identity = kwargs.pop("_exclude_workload_identity_credential", False)
79
+ self._enable_imds_probe = kwargs.pop("_enable_imds_probe", None)
79
80
  managed_identity_type = None
80
81
 
81
82
  if os.environ.get(EnvironmentVariables.IDENTITY_ENDPOINT):
@@ -136,7 +137,12 @@ class ManagedIdentityCredential:
136
137
  managed_identity_type = "IMDS"
137
138
  from .imds import ImdsCredential
138
139
 
139
- self._credential = ImdsCredential(client_id=client_id, identity_config=identity_config, **kwargs)
140
+ self._credential = ImdsCredential(
141
+ client_id=client_id,
142
+ identity_config=identity_config,
143
+ _enable_imds_probe=self._enable_imds_probe,
144
+ **kwargs,
145
+ )
140
146
 
141
147
  if managed_identity_type:
142
148
  log_msg = f"{self.__class__.__name__} will use {managed_identity_type}"
@@ -198,9 +198,9 @@ class _SharedTokenCacheCredential(SharedTokenCacheBase):
198
198
  return token
199
199
  except Exception as e: # pylint: disable=broad-except
200
200
  if within_dac.get():
201
- raise CredentialUnavailableError( # pylint: disable=raise-missing-from
201
+ raise CredentialUnavailableError(
202
202
  message=getattr(e, "message", str(e)), response=getattr(e, "response", None)
203
- )
203
+ ) from e
204
204
  raise
205
205
 
206
206
  raise CredentialUnavailableError(message=NO_TOKEN.format(account.get("username")))
@@ -203,7 +203,7 @@ class InteractiveCredential(MsalCredential, ABC):
203
203
  return token
204
204
  except Exception as ex: # pylint:disable=broad-except
205
205
  if not (isinstance(ex, AuthenticationRequiredError) and allow_prompt):
206
- _LOGGER.warning(
206
+ _LOGGER.warning( # pylint: disable=do-not-log-raised-errors
207
207
  "%s.%s failed: %s",
208
208
  self.__class__.__name__,
209
209
  base_method_name,
@@ -225,7 +225,7 @@ class InteractiveCredential(MsalCredential, ABC):
225
225
  # this may be the first authentication, or the user may have authenticated a different identity
226
226
  self._auth_record = _build_auth_record(result)
227
227
  except Exception as ex:
228
- _LOGGER.warning(
228
+ _LOGGER.warning( # pylint: disable=do-not-log-raised-errors
229
229
  "%s.%s failed: %s",
230
230
  self.__class__.__name__,
231
231
  base_method_name,
@@ -61,7 +61,7 @@ class MsalManagedIdentityClient(abc.ABC): # pylint:disable=client-accepts-api-v
61
61
  )
62
62
  error_desc = ""
63
63
  if result and "error" in result:
64
- error_desc = cast(str, result["error"])
64
+ error_desc = f"Token request error: ({result['error']}) {result.get('error_description', '')}"
65
65
  error_message = self.get_unavailable_message(error_desc)
66
66
  raise CredentialUnavailableError(error_message)
67
67
 
@@ -2,4 +2,4 @@
2
2
  # Copyright (c) Microsoft Corporation.
3
3
  # Licensed under the MIT License.
4
4
  # ------------------------------------
5
- VERSION = "1.25.0"
5
+ VERSION = "1.25.1"
@@ -144,7 +144,8 @@ class DefaultAzureCredential(ChainedTokenCredential):
144
144
 
145
145
  process_timeout = kwargs.pop("process_timeout", 10)
146
146
  require_envvar = kwargs.pop("require_envvar", False)
147
- if require_envvar and not os.environ.get(EnvironmentVariables.AZURE_TOKEN_CREDENTIALS):
147
+ token_credentials_env = os.environ.get(EnvironmentVariables.AZURE_TOKEN_CREDENTIALS, "").strip().lower()
148
+ if require_envvar and not token_credentials_env:
148
149
  raise ValueError(
149
150
  "AZURE_TOKEN_CREDENTIALS environment variable is required but is not set or is empty. "
150
151
  "Set it to 'dev', 'prod', or a specific credential name."
@@ -235,18 +236,16 @@ class DefaultAzureCredential(ChainedTokenCredential):
235
236
  ManagedIdentityCredential(
236
237
  client_id=managed_identity_client_id,
237
238
  _exclude_workload_identity_credential=exclude_workload_identity_credential,
239
+ _enable_imds_probe=token_credentials_env != "managedidentitycredential",
238
240
  **kwargs,
239
241
  )
240
242
  )
241
243
  if not exclude_shared_token_cache_credential and SharedTokenCacheCredential.supported():
242
- try:
243
- # username and/or tenant_id are only required when the cache contains tokens for multiple identities
244
- shared_cache = SharedTokenCacheCredential(
245
- username=shared_cache_username, tenant_id=shared_cache_tenant_id, authority=authority, **kwargs
246
- )
247
- credentials.append(shared_cache)
248
- except Exception as ex: # pylint:disable=broad-except
249
- _LOGGER.info("Shared token cache is unavailable: '%s'", ex)
244
+ # username and/or tenant_id are only required when the cache contains tokens for multiple identities
245
+ shared_cache = SharedTokenCacheCredential(
246
+ username=shared_cache_username, tenant_id=shared_cache_tenant_id, authority=authority, **kwargs
247
+ )
248
+ credentials.append(shared_cache)
250
249
  if not exclude_visual_studio_code_credential:
251
250
  credentials.append(VisualStudioCodeCredential(tenant_id=vscode_tenant_id))
252
251
  if not exclude_cli_credential:
@@ -45,6 +45,10 @@ class ImdsCredential(AsyncContextManager, GetTokenMixin):
45
45
  def __init__(self, **kwargs: Any) -> None:
46
46
  super().__init__()
47
47
 
48
+ # If set to True/False, _enable_imds_probe forces whether or not the credential
49
+ # probes for the IMDS endpoint before attempting to get a token. If None (the default),
50
+ # the credential probes only if it's part of a ChainedTokenCredential chain.
51
+ self._enable_imds_probe = kwargs.pop("_enable_imds_probe", None)
48
52
  kwargs["retry_policy_class"] = AsyncImdsRetryPolicy
49
53
  self._client = AsyncManagedIdentityClient(_get_request, **dict(PIPELINE_SETTINGS, **kwargs))
50
54
  if EnvironmentVariables.AZURE_POD_IDENTITY_AUTHORITY_HOST in os.environ:
@@ -65,9 +69,9 @@ class ImdsCredential(AsyncContextManager, GetTokenMixin):
65
69
 
66
70
  async def _request_token(self, *scopes: str, **kwargs: Any) -> AccessTokenInfo:
67
71
 
68
- if within_credential_chain.get() and not self._endpoint_available:
69
- # If within a chain (e.g. DefaultAzureCredential), we do a quick check to see if the IMDS endpoint
70
- # is available to avoid hanging for a long time if the endpoint isn't available.
72
+ do_probe = self._enable_imds_probe if self._enable_imds_probe is not None else within_credential_chain.get()
73
+ if do_probe and not self._endpoint_available:
74
+ # Probe to see if the IMDS endpoint is available to avoid hanging for a long time if it's not.
71
75
  try:
72
76
  await self._client.request_token(*scopes, connection_timeout=1, retry_total=0)
73
77
  self._endpoint_available = True
@@ -49,6 +49,7 @@ class ManagedIdentityCredential(AsyncContextManager):
49
49
  user_identity_info = validate_identity_config(client_id, identity_config)
50
50
  self._credential: Optional[AsyncSupportsTokenInfo] = None
51
51
  exclude_workload_identity = kwargs.pop("_exclude_workload_identity_credential", False)
52
+ self._enable_imds_probe = kwargs.pop("_enable_imds_probe", None)
52
53
  managed_identity_type = None
53
54
  if os.environ.get(EnvironmentVariables.IDENTITY_ENDPOINT):
54
55
  if os.environ.get(EnvironmentVariables.IDENTITY_HEADER):
@@ -108,7 +109,12 @@ class ManagedIdentityCredential(AsyncContextManager):
108
109
  managed_identity_type = "IMDS"
109
110
  from .imds import ImdsCredential
110
111
 
111
- self._credential = ImdsCredential(client_id=client_id, identity_config=identity_config, **kwargs)
112
+ self._credential = ImdsCredential(
113
+ client_id=client_id,
114
+ identity_config=identity_config,
115
+ _enable_imds_probe=self._enable_imds_probe,
116
+ **kwargs,
117
+ )
112
118
 
113
119
  if managed_identity_type:
114
120
  log_msg = f"{self.__class__.__name__} will use {managed_identity_type}"
@@ -151,9 +151,9 @@ class SharedTokenCacheCredential(SharedTokenCacheBase, AsyncContextManager):
151
151
  return token
152
152
  except Exception as e: # pylint: disable=broad-except
153
153
  if within_dac.get():
154
- raise CredentialUnavailableError( # pylint: disable=raise-missing-from
154
+ raise CredentialUnavailableError(
155
155
  message=getattr(e, "message", str(e)), response=getattr(e, "response", None)
156
- )
156
+ ) from e
157
157
  raise
158
158
  raise CredentialUnavailableError(message=NO_TOKEN.format(account.get("username")))
159
159
 
@@ -5,7 +5,6 @@ __all__ = (
5
5
  "FIFOCache",
6
6
  "LFUCache",
7
7
  "LRUCache",
8
- "MRUCache",
9
8
  "RRCache",
10
9
  "TLRUCache",
11
10
  "TTLCache",
@@ -13,7 +12,7 @@ __all__ = (
13
12
  "cachedmethod",
14
13
  )
15
14
 
16
- __version__ = "5.5.2"
15
+ __version__ = "6.2.0"
17
16
 
18
17
  import collections
19
18
  import collections.abc
@@ -23,7 +22,6 @@ import random
23
22
  import time
24
23
 
25
24
  from . import keys
26
- from ._decorators import _cached_wrapper
27
25
 
28
26
 
29
27
  class _DefaultSize:
@@ -172,32 +170,78 @@ class FIFOCache(Cache):
172
170
  class LFUCache(Cache):
173
171
  """Least Frequently Used (LFU) cache implementation."""
174
172
 
173
+ class _Link:
174
+ __slots__ = ("count", "keys", "next", "prev")
175
+
176
+ def __init__(self, count):
177
+ self.count = count
178
+ self.keys = set()
179
+
180
+ def unlink(self):
181
+ next = self.next
182
+ prev = self.prev
183
+ prev.next = next
184
+ next.prev = prev
185
+
175
186
  def __init__(self, maxsize, getsizeof=None):
176
187
  Cache.__init__(self, maxsize, getsizeof)
177
- self.__counter = collections.Counter()
188
+ self.__root = root = LFUCache._Link(0) # sentinel
189
+ root.prev = root.next = root
190
+ self.__links = {}
178
191
 
179
192
  def __getitem__(self, key, cache_getitem=Cache.__getitem__):
180
193
  value = cache_getitem(self, key)
181
194
  if key in self: # __missing__ may not store item
182
- self.__counter[key] -= 1
195
+ self.__touch(key)
183
196
  return value
184
197
 
185
198
  def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
186
199
  cache_setitem(self, key, value)
187
- self.__counter[key] -= 1
200
+ if key in self.__links:
201
+ return self.__touch(key)
202
+ root = self.__root
203
+ link = root.next
204
+ if link.count != 1:
205
+ link = LFUCache._Link(1)
206
+ link.next = root.next
207
+ root.next = link.next.prev = link
208
+ link.prev = root
209
+ link.keys.add(key)
210
+ self.__links[key] = link
188
211
 
189
212
  def __delitem__(self, key, cache_delitem=Cache.__delitem__):
190
213
  cache_delitem(self, key)
191
- del self.__counter[key]
214
+ link = self.__links.pop(key)
215
+ link.keys.remove(key)
216
+ if not link.keys:
217
+ link.unlink()
192
218
 
193
219
  def popitem(self):
194
220
  """Remove and return the `(key, value)` pair least frequently used."""
195
- try:
196
- ((key, _),) = self.__counter.most_common(1)
197
- except ValueError:
221
+ root = self.__root
222
+ curr = root.next
223
+ if curr is root:
198
224
  raise KeyError("%s is empty" % type(self).__name__) from None
199
- else:
200
- return (key, self.pop(key))
225
+ key = next(iter(curr.keys)) # remove an arbitrary element
226
+ return (key, self.pop(key))
227
+
228
+ def __touch(self, key):
229
+ """Increment use count"""
230
+ link = self.__links[key]
231
+ curr = link.next
232
+ if curr.count != link.count + 1:
233
+ if len(link.keys) == 1:
234
+ link.count += 1
235
+ return
236
+ curr = LFUCache._Link(link.count + 1)
237
+ curr.next = link.next
238
+ link.next = curr.next.prev = curr
239
+ curr.prev = link
240
+ curr.keys.add(key)
241
+ link.keys.remove(key)
242
+ if not link.keys:
243
+ link.unlink()
244
+ self.__links[key] = curr
201
245
 
202
246
 
203
247
  class LRUCache(Cache):
@@ -210,12 +254,12 @@ class LRUCache(Cache):
210
254
  def __getitem__(self, key, cache_getitem=Cache.__getitem__):
211
255
  value = cache_getitem(self, key)
212
256
  if key in self: # __missing__ may not store item
213
- self.__update(key)
257
+ self.__touch(key)
214
258
  return value
215
259
 
216
260
  def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
217
261
  cache_setitem(self, key, value)
218
- self.__update(key)
262
+ self.__touch(key)
219
263
 
220
264
  def __delitem__(self, key, cache_delitem=Cache.__delitem__):
221
265
  cache_delitem(self, key)
@@ -230,70 +274,47 @@ class LRUCache(Cache):
230
274
  else:
231
275
  return (key, self.pop(key))
232
276
 
233
- def __update(self, key):
277
+ def __touch(self, key):
278
+ """Mark as recently used"""
234
279
  try:
235
280
  self.__order.move_to_end(key)
236
281
  except KeyError:
237
282
  self.__order[key] = None
238
283
 
239
284
 
240
- class MRUCache(Cache):
241
- """Most Recently Used (MRU) cache implementation."""
242
-
243
- def __init__(self, maxsize, getsizeof=None):
244
- from warnings import warn
245
-
246
- warn("MRUCache is deprecated", DeprecationWarning, stacklevel=2)
247
-
248
- Cache.__init__(self, maxsize, getsizeof)
249
- self.__order = collections.OrderedDict()
250
-
251
- def __getitem__(self, key, cache_getitem=Cache.__getitem__):
252
- value = cache_getitem(self, key)
253
- if key in self: # __missing__ may not store item
254
- self.__update(key)
255
- return value
256
-
257
- def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
258
- cache_setitem(self, key, value)
259
- self.__update(key)
260
-
261
- def __delitem__(self, key, cache_delitem=Cache.__delitem__):
262
- cache_delitem(self, key)
263
- del self.__order[key]
264
-
265
- def popitem(self):
266
- """Remove and return the `(key, value)` pair most recently used."""
267
- try:
268
- key = next(iter(self.__order))
269
- except StopIteration:
270
- raise KeyError("%s is empty" % type(self).__name__) from None
271
- else:
272
- return (key, self.pop(key))
273
-
274
- def __update(self, key):
275
- try:
276
- self.__order.move_to_end(key, last=False)
277
- except KeyError:
278
- self.__order[key] = None
279
-
280
-
281
285
  class RRCache(Cache):
282
286
  """Random Replacement (RR) cache implementation."""
283
287
 
284
288
  def __init__(self, maxsize, choice=random.choice, getsizeof=None):
285
289
  Cache.__init__(self, maxsize, getsizeof)
286
290
  self.__choice = choice
291
+ self.__index = {}
292
+ self.__keys = []
287
293
 
288
294
  @property
289
295
  def choice(self):
290
296
  """The `choice` function used by the cache."""
291
297
  return self.__choice
292
298
 
299
+ def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
300
+ cache_setitem(self, key, value)
301
+ if key not in self.__index:
302
+ self.__index[key] = len(self.__keys)
303
+ self.__keys.append(key)
304
+
305
+ def __delitem__(self, key, cache_delitem=Cache.__delitem__):
306
+ cache_delitem(self, key)
307
+ index = self.__index.pop(key)
308
+ if index != len(self.__keys) - 1:
309
+ last = self.__keys[-1]
310
+ self.__keys[index] = last
311
+ self.__index[last] = index
312
+ self.__keys.pop()
313
+
293
314
  def popitem(self):
294
315
  """Remove and return a random `(key, value)` pair."""
295
316
  try:
296
- key = self.__choice(list(self))
317
+ key = self.__choice(self.__keys)
297
318
  except IndexError:
298
319
  raise KeyError("%s is empty" % type(self).__name__) from None
299
320
  else:
@@ -636,11 +657,23 @@ _CacheInfo = collections.namedtuple(
636
657
  )
637
658
 
638
659
 
639
- def cached(cache, key=keys.hashkey, lock=None, info=False):
660
+ def cached(cache, key=keys.hashkey, lock=None, condition=None, info=False):
640
661
  """Decorator to wrap a function with a memoizing callable that saves
641
662
  results in a cache.
642
663
 
643
664
  """
665
+ from ._cached import _wrapper
666
+
667
+ if isinstance(condition, bool):
668
+ from warnings import warn
669
+
670
+ warn(
671
+ "passing `info` as positional parameter is deprecated",
672
+ DeprecationWarning,
673
+ stacklevel=2,
674
+ )
675
+ info = condition
676
+ condition = None
644
677
 
645
678
  def decorator(func):
646
679
  if info:
@@ -659,80 +692,21 @@ def cached(cache, key=keys.hashkey, lock=None, info=False):
659
692
  def make_info(hits, misses):
660
693
  return _CacheInfo(hits, misses, 0, 0)
661
694
 
662
- wrapper = _cached_wrapper(func, cache, key, lock, make_info)
695
+ return _wrapper(func, cache, key, lock, condition, info=make_info)
663
696
  else:
664
- wrapper = _cached_wrapper(func, cache, key, lock, None)
665
-
666
- wrapper.cache = cache
667
- wrapper.cache_key = key
668
- wrapper.cache_lock = lock
669
-
670
- return functools.update_wrapper(wrapper, func)
697
+ return _wrapper(func, cache, key, lock, condition)
671
698
 
672
699
  return decorator
673
700
 
674
701
 
675
- def cachedmethod(cache, key=keys.methodkey, lock=None):
702
+ def cachedmethod(cache, key=keys.methodkey, lock=None, condition=None):
676
703
  """Decorator to wrap a class or instance method with a memoizing
677
704
  callable that saves results in a cache.
678
705
 
679
706
  """
707
+ from ._cachedmethod import _wrapper
680
708
 
681
709
  def decorator(method):
682
- if lock is None:
683
-
684
- def wrapper(self, *args, **kwargs):
685
- c = cache(self)
686
- if c is None:
687
- return method(self, *args, **kwargs)
688
- k = key(self, *args, **kwargs)
689
- try:
690
- return c[k]
691
- except KeyError:
692
- pass # key not found
693
- v = method(self, *args, **kwargs)
694
- try:
695
- c[k] = v
696
- except ValueError:
697
- pass # value too large
698
- return v
699
-
700
- def clear(self):
701
- c = cache(self)
702
- if c is not None:
703
- c.clear()
704
-
705
- else:
706
-
707
- def wrapper(self, *args, **kwargs):
708
- c = cache(self)
709
- if c is None:
710
- return method(self, *args, **kwargs)
711
- k = key(self, *args, **kwargs)
712
- try:
713
- with lock(self):
714
- return c[k]
715
- except KeyError:
716
- pass # key not found
717
- v = method(self, *args, **kwargs)
718
- # in case of a race, prefer the item already in the cache
719
- try:
720
- with lock(self):
721
- return c.setdefault(k, v)
722
- except ValueError:
723
- return v # value too large
724
-
725
- def clear(self):
726
- c = cache(self)
727
- if c is not None:
728
- with lock(self):
729
- c.clear()
730
-
731
- wrapper.cache = cache
732
- wrapper.cache_key = key
733
- wrapper.cache_lock = lock
734
- wrapper.cache_clear = clear
735
-
736
- return functools.update_wrapper(wrapper, method)
710
+ return _wrapper(method, cache, key, lock, condition)
737
711
 
738
712
  return decorator