client-query-cache 0.5.0__tar.gz → 0.6.0__tar.gz

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 (50) hide show
  1. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/PKG-INFO +12 -3
  2. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/README.md +4 -1
  3. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/pyproject.toml +8 -1
  4. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/pyproject.toml.orig +8 -1
  5. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/entries.py +1 -0
  6. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/manager.py +14 -4
  7. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/asynchronous/collection.py +8 -2
  8. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/synchronous/collection.py +8 -2
  9. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/__init__.py +0 -0
  10. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/__init__.py +0 -0
  11. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/canonical.py +0 -0
  12. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/codec.py +0 -0
  13. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/collation.py +0 -0
  14. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/collection_metadata.py +0 -0
  15. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/count_reads.py +0 -0
  16. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/cursor_capture.py +0 -0
  17. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/errors.py +0 -0
  18. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/find_one_reads.py +0 -0
  19. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/find_reads.py +0 -0
  20. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/identity_reads.py +0 -0
  21. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/keys.py +0 -0
  22. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/lifecycle.py +0 -0
  23. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/locking.py +0 -0
  24. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/lru.py +0 -0
  25. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/namespace.py +0 -0
  26. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/order_sensitive_keys.py +0 -0
  27. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/projection.py +0 -0
  28. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/query_filters.py +0 -0
  29. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/read_classification.py +0 -0
  30. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/read_validation.py +0 -0
  31. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/snapshots.py +0 -0
  32. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/stream_activation.py +0 -0
  33. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/stream_cost.py +0 -0
  34. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/stream_events.py +0 -0
  35. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/stream_health.py +0 -0
  36. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/stream_options.py +0 -0
  37. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/traversal.py +0 -0
  38. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/_core/unique_keys.py +0 -0
  39. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/asynchronous/__init__.py +0 -0
  40. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/asynchronous/cursors.py +0 -0
  41. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/asynchronous/database.py +0 -0
  42. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/asynchronous/manager.py +0 -0
  43. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/asynchronous/streams.py +0 -0
  44. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/otel.py +0 -0
  45. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/py.typed +0 -0
  46. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/synchronous/__init__.py +0 -0
  47. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/synchronous/cursors.py +0 -0
  48. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/synchronous/database.py +0 -0
  49. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/synchronous/manager.py +0 -0
  50. {client_query_cache-0.5.0 → client_query_cache-0.6.0}/src/client_query_cache/synchronous/streams.py +0 -0
@@ -1,11 +1,17 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: client-query-cache
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Client-side caching for PyMongo, kept coherent using MongoDB change streams.
5
5
  Author: Alessio Locatelli
6
6
  Author-email: Alessio Locatelli <<software.development@secure.mailbox.org>>
7
+ License-Expression: Apache-2.0
8
+ Classifier: Framework :: AsyncIO
9
+ Classifier: Intended Audience :: Developers
7
10
  Classifier: Programming Language :: Python :: 3.14
8
11
  Classifier: Programming Language :: Python :: 3.15
12
+ Classifier: Topic :: Database
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: Typing :: Typed
9
15
  Requires-Dist: pymongo>=4.18.1
10
16
  Requires-Dist: opentelemetry-api>=1.45.0 ; extra == 'otel'
11
17
  Requires-Python: >=3.14
@@ -16,7 +22,10 @@ Description-Content-Type: text/markdown
16
22
 
17
23
  # Coherent client-side caching for PyMongo
18
24
 
19
- [![Supported Python versions on PyPI](https://img.shields.io/pypi/pyversions/client-query-cache.svg)](https://pypi.org/project/client-query-cache/)
25
+ [![PyPI](https://img.shields.io/pypi/v/client-query-cache?color=lightgrey)](https://pypi.org/project/client-query-cache/)
26
+ [![Python](https://img.shields.io/pypi/pyversions/client-query-cache)](https://alessio-locatelli.github.io/client-query-cache/getting-started/installation/)
27
+ [![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)](https://github.com/alessio-locatelli/client-query-cache/actions/workflows/test.yml)
28
+ [![Typing](https://img.shields.io/pypi/types/client-query-cache)](https://pypi.org/project/client-query-cache/)
20
29
 
21
30
  `client-query-cache` uses MongoDB change streams to keep cached reads coherent. It supports synchronous and asyncio applications without requiring a separate cache server.
22
31
 
@@ -1,6 +1,9 @@
1
1
  # Coherent client-side caching for PyMongo
2
2
 
3
- [![Supported Python versions on PyPI](https://img.shields.io/pypi/pyversions/client-query-cache.svg)](https://pypi.org/project/client-query-cache/)
3
+ [![PyPI](https://img.shields.io/pypi/v/client-query-cache?color=lightgrey)](https://pypi.org/project/client-query-cache/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/client-query-cache)](https://alessio-locatelli.github.io/client-query-cache/getting-started/installation/)
5
+ [![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)](https://github.com/alessio-locatelli/client-query-cache/actions/workflows/test.yml)
6
+ [![Typing](https://img.shields.io/pypi/types/client-query-cache)](https://pypi.org/project/client-query-cache/)
4
7
 
5
8
  `client-query-cache` uses MongoDB change streams to keep cached reads coherent. It supports synchronous and asyncio applications without requiring a separate cache server.
6
9
 
@@ -1,12 +1,18 @@
1
1
  [project]
2
2
  name = "client-query-cache"
3
- version = "0.5.0"
3
+ version = "0.6.0"
4
4
  description = "Client-side caching for PyMongo, kept coherent using MongoDB change streams."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.14"
7
+ license = "Apache-2.0"
7
8
  classifiers = [
9
+ "Framework :: AsyncIO",
10
+ "Intended Audience :: Developers",
8
11
  "Programming Language :: Python :: 3.14",
9
12
  "Programming Language :: Python :: 3.15",
13
+ "Topic :: Database",
14
+ "Topic :: Software Development :: Libraries :: Python Modules",
15
+ "Typing :: Typed",
10
16
  ]
11
17
  dependencies = ["pymongo>=4.18.1"]
12
18
 
@@ -46,6 +52,7 @@ dev = [
46
52
  "opentelemetry-api>=1.45.0",
47
53
  "opentelemetry-sdk>=1.45.0",
48
54
  "types-psutil>=7.2.2.20260906",
55
+ "psutil>=7.2.2",
49
56
  "dnspython>=2.8.0",
50
57
  "python-snappy>=0.7.3",
51
58
  "pytest-xdist[psutil]>=3.8.0",
@@ -1,15 +1,21 @@
1
1
  [project]
2
2
  name = "client-query-cache"
3
- version = "0.5.0"
3
+ version = "0.6.0"
4
4
  description = "Client-side caching for PyMongo, kept coherent using MongoDB change streams."
5
5
  authors = [
6
6
  { name = "Alessio Locatelli", email = "<software.development@secure.mailbox.org>" },
7
7
  ]
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.14"
10
+ license = "Apache-2.0"
10
11
  classifiers = [
12
+ "Framework :: AsyncIO",
13
+ "Intended Audience :: Developers",
11
14
  "Programming Language :: Python :: 3.14",
12
15
  "Programming Language :: Python :: 3.15",
16
+ "Topic :: Database",
17
+ "Topic :: Software Development :: Libraries :: Python Modules",
18
+ "Typing :: Typed",
13
19
  ]
14
20
  dependencies = ["pymongo>=4.18.1"]
15
21
 
@@ -45,6 +51,7 @@ dev = [
45
51
  "opentelemetry-api>=1.45.0",
46
52
  "opentelemetry-sdk>=1.45.0",
47
53
  "types-psutil>=7.2.2.20260906",
54
+ "psutil>=7.2.2",
48
55
  "dnspython>=2.8.0",
49
56
  "python-snappy>=0.7.3",
50
57
  "pytest-xdist[psutil]>=3.8.0",
@@ -32,3 +32,4 @@ class CacheEntry:
32
32
  class LookupResult:
33
33
  hit: bool
34
34
  value: Any = None
35
+ deferred_miss: bool = False
@@ -54,6 +54,8 @@ DEFAULT_MAX_ENTRY_BYTES = 1 * 1024 * 1024
54
54
 
55
55
  _NOT_INDEXED = object()
56
56
  _DEFAULT_DATABASE_AVAILABILITY = (True, 0)
57
+ _MISS = LookupResult(hit=False)
58
+ _DEFERRED_MISS = LookupResult(hit=False, deferred_miss=True)
57
59
 
58
60
  logger = logging.getLogger(__name__)
59
61
 
@@ -313,6 +315,9 @@ class _CacheCoreLifecycle(_CacheCoreBase):
313
315
  self._database_namespaces.clear()
314
316
  logger.info("cache manager closed")
315
317
 
318
+ def record_miss(self) -> None:
319
+ self._statistics.record_miss()
320
+
316
321
  def record_bypass(self, reason: BypassReason = BypassReason.UNSPECIFIED) -> None:
317
322
  self._statistics.record_bypass(reason)
318
323
 
@@ -908,6 +913,7 @@ class _CacheCoreLookup(_CacheCoreBase):
908
913
  discriminator: object,
909
914
  *,
910
915
  codec_options: CodecOptions[Any] | None = None,
916
+ defer_miss: bool = False,
911
917
  ) -> LookupResult:
912
918
  self._ensure_active()
913
919
  if not self._is_database_available(namespace.database):
@@ -917,18 +923,22 @@ class _CacheCoreLookup(_CacheCoreBase):
917
923
  key = NamespaceCacheKey(namespace, canonical_discriminator)
918
924
  entry = self._lru.peek(key)
919
925
  if entry is None:
920
- self._statistics.record_miss()
921
- return LookupResult(hit=False)
926
+ return self._namespace_miss(defer_miss=defer_miss)
922
927
  state = self._namespace(namespace)
923
928
  with self._namespace_section(state):
924
929
  valid = (state.generation,) == entry.generation_key
925
930
  if not valid:
926
- self._statistics.record_miss()
927
- return LookupResult(hit=False)
931
+ return self._namespace_miss(defer_miss=defer_miss)
928
932
  self._lru.touch(key)
929
933
  self._statistics.record_hit()
930
934
  return LookupResult(hit=True, value=decode_value(entry.value, codec_options))
931
935
 
936
+ def _namespace_miss(self, *, defer_miss: bool) -> LookupResult:
937
+ if defer_miss:
938
+ return _DEFERRED_MISS
939
+ self._statistics.record_miss()
940
+ return _MISS
941
+
932
942
  def resolve_alias(
933
943
  self,
934
944
  namespace: NamespaceId,
@@ -209,15 +209,21 @@ class CachedCollection[DocumentType: Mapping[str, Any]]:
209
209
  cache.current_index_generation(namespace),
210
210
  )
211
211
  )
212
+ discovers_unique_keys = identity is NO_IDENTITY and known_keys is None
212
213
  lookup_result = cache.lookup_namespace(
213
- namespace, discriminator, codec_options=codec_options
214
+ namespace,
215
+ discriminator,
216
+ codec_options=codec_options,
217
+ defer_miss=discovers_unique_keys,
214
218
  )
215
219
  if lookup_result.hit:
216
220
  return cast("DocumentType | None", lookup_result.value)
217
- if identity is NO_IDENTITY and known_keys is None:
221
+ if discovers_unique_keys:
218
222
  unique_key_match = await self._match_unique_key(
219
223
  filter_query, effective_collation
220
224
  )
225
+ if unique_key_match is None and lookup_result.deferred_miss:
226
+ cache.record_miss()
221
227
  if unique_key_match is not None:
222
228
  key_definition, key_values = unique_key_match
223
229
  return await self._find_one_by_unique_key(
@@ -209,15 +209,21 @@ class CachedCollection[DocumentType: Mapping[str, Any]]:
209
209
  cache.current_index_generation(namespace),
210
210
  )
211
211
  )
212
+ discovers_unique_keys = identity is NO_IDENTITY and known_keys is None
212
213
  lookup_result = cache.lookup_namespace(
213
- namespace, discriminator, codec_options=codec_options
214
+ namespace,
215
+ discriminator,
216
+ codec_options=codec_options,
217
+ defer_miss=discovers_unique_keys,
214
218
  )
215
219
  if lookup_result.hit:
216
220
  return cast("DocumentType | None", lookup_result.value)
217
- if identity is NO_IDENTITY and known_keys is None:
221
+ if discovers_unique_keys:
218
222
  unique_key_match = self._match_unique_key(
219
223
  filter_query, effective_collation
220
224
  )
225
+ if unique_key_match is None and lookup_result.deferred_miss:
226
+ cache.record_miss()
221
227
  if unique_key_match is not None:
222
228
  key_definition, key_values = unique_key_match
223
229
  return self._find_one_by_unique_key(