client-query-cache 0.6.0__tar.gz → 0.7.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.
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/PKG-INFO +26 -9
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/README.md +24 -8
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/pyproject.toml +6 -2
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/pyproject.toml.orig +3 -2
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/canonical.py +17 -8
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/collection_metadata.py +2 -1
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/count_reads.py +1 -1
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/cursor_capture.py +1 -1
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/entries.py +4 -2
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/find_one_reads.py +5 -2
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/find_reads.py +4 -3
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/identity_reads.py +11 -9
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/lru.py +8 -6
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/manager.py +136 -44
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/namespace.py +24 -9
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/order_sensitive_keys.py +22 -24
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/query_filters.py +2 -6
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/read_classification.py +23 -16
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/read_validation.py +27 -22
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/snapshots.py +23 -12
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/stream_activation.py +3 -2
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/stream_cost.py +33 -18
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/stream_events.py +5 -4
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/stream_health.py +13 -8
- client_query_cache-0.7.0/src/client_query_cache/_core/stream_options.py +33 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/unique_keys.py +2 -1
- client_query_cache-0.7.0/src/client_query_cache/_types.py +21 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/asynchronous/collection.py +54 -42
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/asynchronous/cursors.py +31 -30
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/asynchronous/manager.py +2 -1
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/asynchronous/streams.py +10 -17
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/otel.py +5 -4
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/synchronous/collection.py +52 -40
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/synchronous/cursors.py +31 -30
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/synchronous/manager.py +2 -1
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/synchronous/streams.py +9 -16
- client_query_cache-0.6.0/src/client_query_cache/_core/stream_options.py +0 -14
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/__init__.py +0 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/__init__.py +0 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/codec.py +0 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/collation.py +0 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/errors.py +0 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/keys.py +0 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/lifecycle.py +0 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/locking.py +0 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/projection.py +0 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/traversal.py +0 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/asynchronous/__init__.py +0 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/asynchronous/database.py +0 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/py.typed +0 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/synchronous/__init__.py +0 -0
- {client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/synchronous/database.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: client-query-cache
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.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>>
|
|
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3.15
|
|
|
12
12
|
Classifier: Topic :: Database
|
|
13
13
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
14
|
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Dist: annotated-types>=0.8.0
|
|
15
16
|
Requires-Dist: pymongo>=4.18.1
|
|
16
17
|
Requires-Dist: opentelemetry-api>=1.45.0 ; extra == 'otel'
|
|
17
18
|
Requires-Python: >=3.14
|
|
@@ -20,16 +21,26 @@ Project-URL: Documentation, https://alessio-locatelli.github.io/client-query-cac
|
|
|
20
21
|
Provides-Extra: otel
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
22
23
|
|
|
23
|
-
#
|
|
24
|
+
# Client-side query caching for PyMongo
|
|
24
25
|
|
|
25
26
|
[](https://pypi.org/project/client-query-cache/)
|
|
26
27
|
[](https://alessio-locatelli.github.io/client-query-cache/getting-started/installation/)
|
|
27
28
|
[](https://github.com/alessio-locatelli/client-query-cache/actions/workflows/test.yml)
|
|
28
29
|
[](https://pypi.org/project/client-query-cache/)
|
|
29
30
|
|
|
30
|
-
`client-query-cache` uses MongoDB change streams to
|
|
31
|
+
`client-query-cache` uses MongoDB change streams to asynchronously invalidate cached query results. It supports synchronous and asyncio applications without requiring a separate cache server.
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
```mermaid
|
|
34
|
+
%%{init: {"flowchart": {"diagramPadding": 75}}}%%
|
|
35
|
+
flowchart LR
|
|
36
|
+
App["Python<br/>application"] -->|"Reads"| Cache["Client-side<br/>caching"]
|
|
37
|
+
Cache -->|"Miss / bypass"| DB[("MongoDB")]
|
|
38
|
+
DB -.->|"Change streams<br/>Async invalidation"| Cache
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Cache hits are served from client-side memory, reducing read latency and database load in read-heavy applications.
|
|
42
|
+
|
|
43
|
+
The library is tested against real MongoDB replica sets, including concurrency stress tests, and automated checks guard its performance and memory use against regressions.
|
|
33
44
|
|
|
34
45
|

|
|
35
46
|
|
|
@@ -37,7 +48,9 @@ Results vary by workload and deployment. [Measurements and methodology](https://
|
|
|
37
48
|
|
|
38
49
|
Caching requires [MongoDB 8.0+ on a replica set or sharded cluster](https://alessio-locatelli.github.io/client-query-cache/getting-started/installation/#requirements). Reads on standalone servers and older MongoDB versions run through PyMongo without caching.
|
|
39
50
|
|
|
40
|
-
Invalidation is asynchronous: a cached read can return a preceding value until the write's change-stream event is processed. Use PyMongo directly when a read must immediately observe a preceding write.
|
|
51
|
+
Invalidation is asynchronous: a cached read can return a preceding value until the write's change-stream event is processed. Use PyMongo directly when a read must immediately observe a preceding write; see [consistency](https://alessio-locatelli.github.io/client-query-cache/usage/consistency/).
|
|
52
|
+
|
|
53
|
+
Eligible cache misses read from the primary with `majority` read concern, even when the collection leaves read concern unspecified, so only majority-committed results enter the cache. Uncached PyMongo reads use the deployment's default read concern, normally `local`. The two usually perform alike, but while replication lags or data-bearing members are unavailable, a miss can return older data or fail where an uncached read would not. Majority admission does not make cache hits current: invalidation remains asynchronous. See [bypass conditions](https://alessio-locatelli.github.io/client-query-cache/reference/api/#bypass-conditions).
|
|
41
54
|
|
|
42
55
|
## Quick start
|
|
43
56
|
|
|
@@ -54,11 +67,15 @@ from client_query_cache import CacheManager
|
|
|
54
67
|
|
|
55
68
|
with (
|
|
56
69
|
MongoClient("mongodb://localhost:27017") as client,
|
|
57
|
-
CacheManager(client) as
|
|
70
|
+
CacheManager(client) as cache_manager,
|
|
58
71
|
):
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
collection = client["client_query_cache_tutorial"]["items"]
|
|
73
|
+
cached_collection = cache_manager.get_cached_collection(collection)
|
|
74
|
+
collection.replace_one(
|
|
75
|
+
{"_id": "example"}, {"_id": "example", "value": 42}, upsert=True
|
|
76
|
+
)
|
|
77
|
+
cached_collection.find_one({"_id": "example"}) # Reads from MongoDB.
|
|
78
|
+
cached_collection.find_one({"_id": "example"}) # Repeated reads can use the cache.
|
|
62
79
|
```
|
|
63
80
|
|
|
64
81
|
## References
|
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Client-side query caching for PyMongo
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/client-query-cache/)
|
|
4
4
|
[](https://alessio-locatelli.github.io/client-query-cache/getting-started/installation/)
|
|
5
5
|
[](https://github.com/alessio-locatelli/client-query-cache/actions/workflows/test.yml)
|
|
6
6
|
[](https://pypi.org/project/client-query-cache/)
|
|
7
7
|
|
|
8
|
-
`client-query-cache` uses MongoDB change streams to
|
|
8
|
+
`client-query-cache` uses MongoDB change streams to asynchronously invalidate cached query results. It supports synchronous and asyncio applications without requiring a separate cache server.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
```mermaid
|
|
11
|
+
%%{init: {"flowchart": {"diagramPadding": 75}}}%%
|
|
12
|
+
flowchart LR
|
|
13
|
+
App["Python<br/>application"] -->|"Reads"| Cache["Client-side<br/>caching"]
|
|
14
|
+
Cache -->|"Miss / bypass"| DB[("MongoDB")]
|
|
15
|
+
DB -.->|"Change streams<br/>Async invalidation"| Cache
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Cache hits are served from client-side memory, reducing read latency and database load in read-heavy applications.
|
|
19
|
+
|
|
20
|
+
The library is tested against real MongoDB replica sets, including concurrency stress tests, and automated checks guard its performance and memory use against regressions.
|
|
11
21
|
|
|
12
22
|

|
|
13
23
|
|
|
@@ -15,7 +25,9 @@ Results vary by workload and deployment. [Measurements and methodology](https://
|
|
|
15
25
|
|
|
16
26
|
Caching requires [MongoDB 8.0+ on a replica set or sharded cluster](https://alessio-locatelli.github.io/client-query-cache/getting-started/installation/#requirements). Reads on standalone servers and older MongoDB versions run through PyMongo without caching.
|
|
17
27
|
|
|
18
|
-
Invalidation is asynchronous: a cached read can return a preceding value until the write's change-stream event is processed. Use PyMongo directly when a read must immediately observe a preceding write.
|
|
28
|
+
Invalidation is asynchronous: a cached read can return a preceding value until the write's change-stream event is processed. Use PyMongo directly when a read must immediately observe a preceding write; see [consistency](https://alessio-locatelli.github.io/client-query-cache/usage/consistency/).
|
|
29
|
+
|
|
30
|
+
Eligible cache misses read from the primary with `majority` read concern, even when the collection leaves read concern unspecified, so only majority-committed results enter the cache. Uncached PyMongo reads use the deployment's default read concern, normally `local`. The two usually perform alike, but while replication lags or data-bearing members are unavailable, a miss can return older data or fail where an uncached read would not. Majority admission does not make cache hits current: invalidation remains asynchronous. See [bypass conditions](https://alessio-locatelli.github.io/client-query-cache/reference/api/#bypass-conditions).
|
|
19
31
|
|
|
20
32
|
## Quick start
|
|
21
33
|
|
|
@@ -32,11 +44,15 @@ from client_query_cache import CacheManager
|
|
|
32
44
|
|
|
33
45
|
with (
|
|
34
46
|
MongoClient("mongodb://localhost:27017") as client,
|
|
35
|
-
CacheManager(client) as
|
|
47
|
+
CacheManager(client) as cache_manager,
|
|
36
48
|
):
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
collection = client["client_query_cache_tutorial"]["items"]
|
|
50
|
+
cached_collection = cache_manager.get_cached_collection(collection)
|
|
51
|
+
collection.replace_one(
|
|
52
|
+
{"_id": "example"}, {"_id": "example", "value": 42}, upsert=True
|
|
53
|
+
)
|
|
54
|
+
cached_collection.find_one({"_id": "example"}) # Reads from MongoDB.
|
|
55
|
+
cached_collection.find_one({"_id": "example"}) # Repeated reads can use the cache.
|
|
40
56
|
```
|
|
41
57
|
|
|
42
58
|
## References
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "client-query-cache"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.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"
|
|
@@ -14,7 +14,10 @@ classifiers = [
|
|
|
14
14
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
15
15
|
"Typing :: Typed",
|
|
16
16
|
]
|
|
17
|
-
dependencies = [
|
|
17
|
+
dependencies = [
|
|
18
|
+
"annotated-types>=0.8.0",
|
|
19
|
+
"pymongo>=4.18.1",
|
|
20
|
+
]
|
|
18
21
|
|
|
19
22
|
[[project.authors]]
|
|
20
23
|
name = "Alessio Locatelli"
|
|
@@ -59,6 +62,7 @@ dev = [
|
|
|
59
62
|
"pytest-cov>=7.1.0",
|
|
60
63
|
"pyyaml>=6.0.3",
|
|
61
64
|
"types-pyyaml>=6.0.12.20260906",
|
|
65
|
+
"filelock>=4.1.0",
|
|
62
66
|
]
|
|
63
67
|
docs = [
|
|
64
68
|
"mike",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "client-query-cache"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.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>" },
|
|
@@ -17,7 +17,7 @@ classifiers = [
|
|
|
17
17
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
18
18
|
"Typing :: Typed",
|
|
19
19
|
]
|
|
20
|
-
dependencies = ["pymongo>=4.18.1"]
|
|
20
|
+
dependencies = ["annotated-types>=0.8.0", "pymongo>=4.18.1"]
|
|
21
21
|
|
|
22
22
|
[project.urls]
|
|
23
23
|
Repository = "https://github.com/alessio-locatelli/client-query-cache"
|
|
@@ -58,6 +58,7 @@ dev = [
|
|
|
58
58
|
"pytest-cov>=7.1.0",
|
|
59
59
|
"pyyaml>=6.0.3",
|
|
60
60
|
"types-pyyaml>=6.0.12.20260906",
|
|
61
|
+
"filelock>=4.1.0",
|
|
61
62
|
]
|
|
62
63
|
docs = ["mike", "tomli-w>=1.2.0", "zensical>=0.0.68"]
|
|
63
64
|
memory = ["pytest-memray>=1.11.0"]
|
{client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/canonical.py
RENAMED
|
@@ -17,26 +17,35 @@ class _CanonicalTag:
|
|
|
17
17
|
return f"<canonical:{self._name}>"
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
class _PendingTag:
|
|
21
|
+
__slots__ = ("canonical", "items")
|
|
22
|
+
|
|
23
|
+
def __init__(self, name: str, *, items: bool) -> None:
|
|
24
|
+
self.canonical = _CanonicalTag(name)
|
|
25
|
+
self.items = items
|
|
26
|
+
|
|
27
|
+
|
|
20
28
|
_MAPPING_TAG = _CanonicalTag("map")
|
|
21
29
|
_SEQUENCE_TAG = _CanonicalTag("seq")
|
|
22
30
|
_BOOL_TAG = _CanonicalTag("bool")
|
|
23
|
-
_OWN_TAGS = (_MAPPING_TAG, _SEQUENCE_TAG, _BOOL_TAG)
|
|
24
31
|
_TAGGED_TUPLE_SIZE = 2
|
|
25
32
|
|
|
26
33
|
|
|
27
34
|
def canonicalize(value: object) -> Canonical:
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
if isinstance(value, tuple) and len(value) == _TAGGED_TUPLE_SIZE:
|
|
36
|
+
tag, payload = value
|
|
37
|
+
if type(tag) is _CanonicalTag:
|
|
38
|
+
return value
|
|
39
|
+
if type(tag) is _PendingTag:
|
|
40
|
+
if tag.items:
|
|
41
|
+
return (tag.canonical, tuple([canonicalize(item) for item in payload]))
|
|
42
|
+
return (tag.canonical, canonicalize(payload))
|
|
34
43
|
if isinstance(value, Mapping):
|
|
35
44
|
pairs = [(canonicalize(key), canonicalize(item)) for key, item in value.items()]
|
|
36
45
|
items = tuple(sorted(pairs, key=lambda pair: repr(pair[0])))
|
|
37
46
|
return (_MAPPING_TAG, items)
|
|
38
47
|
if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
|
|
39
|
-
return (_SEQUENCE_TAG, tuple(canonicalize(item) for item in value))
|
|
48
|
+
return (_SEQUENCE_TAG, tuple([canonicalize(item) for item in value]))
|
|
40
49
|
if isinstance(value, bool):
|
|
41
50
|
return (_BOOL_TAG, value)
|
|
42
51
|
try:
|
|
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
6
6
|
|
|
7
7
|
from client_query_cache._core.collation import normalize_collation
|
|
8
8
|
from client_query_cache._core.snapshots import BypassReason
|
|
9
|
+
from client_query_cache._types import NonNegativeInt
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
11
12
|
from collections.abc import Mapping
|
|
@@ -22,7 +23,7 @@ _COLLECTION_REASONS = {
|
|
|
22
23
|
|
|
23
24
|
@dataclass(frozen=True, slots=True)
|
|
24
25
|
class CollectionMetadata:
|
|
25
|
-
checked_epoch:
|
|
26
|
+
checked_epoch: NonNegativeInt
|
|
26
27
|
bypass_reason: BypassReason | None
|
|
27
28
|
default_collation: Mapping[str, Any] | None
|
|
28
29
|
|
{client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/count_reads.py
RENAMED
|
@@ -14,7 +14,7 @@ _OMITTED = object()
|
|
|
14
14
|
|
|
15
15
|
class CountReadOptions(TypedDict):
|
|
16
16
|
cache_options: tuple[object, object, object, object]
|
|
17
|
-
extra_options: dict[str, object]
|
|
17
|
+
extra_options: dict[str, object]
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def count_read_options(kwargs: Mapping[str, object]) -> CountReadOptions:
|
{client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/cursor_capture.py
RENAMED
|
@@ -48,7 +48,7 @@ class CursorCapture:
|
|
|
48
48
|
self._find_source = find_source
|
|
49
49
|
self._max_entry_bytes = core.snapshot().max_entry_bytes
|
|
50
50
|
self._documents: list[RawBSONDocument] = [] # An empty result is cacheable.
|
|
51
|
-
self.retained_bytes = 0
|
|
51
|
+
self.retained_bytes = 0
|
|
52
52
|
self._active = True
|
|
53
53
|
|
|
54
54
|
def append(self, document: Mapping[str, Any]) -> None:
|
{client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/entries.py
RENAMED
|
@@ -4,6 +4,8 @@ import enum
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
|
+
from client_query_cache._types import NonNegativeInt
|
|
8
|
+
|
|
7
9
|
if TYPE_CHECKING:
|
|
8
10
|
from client_query_cache._core.canonical import Canonical
|
|
9
11
|
from client_query_cache._core.find_reads import FindSource
|
|
@@ -20,8 +22,8 @@ class AdmissionOutcome(enum.Enum):
|
|
|
20
22
|
|
|
21
23
|
@dataclass(eq=False, slots=True)
|
|
22
24
|
class CacheEntry:
|
|
23
|
-
generation_key: tuple[
|
|
24
|
-
weight:
|
|
25
|
+
generation_key: tuple[NonNegativeInt, ...]
|
|
26
|
+
weight: NonNegativeInt
|
|
25
27
|
value: bytes
|
|
26
28
|
namespace: NamespaceId
|
|
27
29
|
identity: Canonical | None
|
{client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/find_one_reads.py
RENAMED
|
@@ -9,6 +9,7 @@ from client_query_cache._core.collation import normalize_collation
|
|
|
9
9
|
from client_query_cache._core.order_sensitive_keys import (
|
|
10
10
|
order_sensitive_discriminator_key,
|
|
11
11
|
)
|
|
12
|
+
from client_query_cache._types import NonNegativeInt
|
|
12
13
|
|
|
13
14
|
type CollationInput = Collation | Mapping[str, Any]
|
|
14
15
|
|
|
@@ -92,16 +93,18 @@ def find_one_read_shape(
|
|
|
92
93
|
sort: Sequence[tuple[str, int]] | None,
|
|
93
94
|
effective_collation: Mapping[str, Any] | None,
|
|
94
95
|
codec_identity: object,
|
|
96
|
+
remainder: Mapping[str, Any] | None = None,
|
|
95
97
|
) -> object:
|
|
98
|
+
shape = ("find_one", projection, sort, effective_collation, codec_identity)
|
|
96
99
|
return order_sensitive_discriminator_key(
|
|
97
|
-
(
|
|
100
|
+
(*shape, remainder) if remainder else shape
|
|
98
101
|
)
|
|
99
102
|
|
|
100
103
|
|
|
101
104
|
def generic_find_one_discriminator(
|
|
102
105
|
filter_query: object,
|
|
103
106
|
read_shape: object,
|
|
104
|
-
index_generation:
|
|
107
|
+
index_generation: NonNegativeInt,
|
|
105
108
|
) -> object:
|
|
106
109
|
return order_sensitive_discriminator_key(
|
|
107
110
|
("find_one_generic", filter_query, read_shape, index_generation)
|
{client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/find_reads.py
RENAMED
|
@@ -8,6 +8,7 @@ from client_query_cache._core.order_sensitive_keys import (
|
|
|
8
8
|
order_sensitive_discriminator_key,
|
|
9
9
|
)
|
|
10
10
|
from client_query_cache._core.query_filters import find_filter_key
|
|
11
|
+
from client_query_cache._types import NonNegativeInt
|
|
11
12
|
|
|
12
13
|
if TYPE_CHECKING:
|
|
13
14
|
from client_query_cache._core.canonical import Canonical
|
|
@@ -15,8 +16,8 @@ if TYPE_CHECKING:
|
|
|
15
16
|
|
|
16
17
|
@dataclass(frozen=True, slots=True)
|
|
17
18
|
class FindSource:
|
|
18
|
-
family: int # A compact hash
|
|
19
|
-
limit:
|
|
19
|
+
family: int # A compact hash; collisions require key equality.
|
|
20
|
+
limit: NonNegativeInt # Zero denotes unlimited; boolean limits are excluded.
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
@dataclass(frozen=True, slots=True)
|
|
@@ -47,7 +48,7 @@ def find_read_shape(
|
|
|
47
48
|
filter_document: object,
|
|
48
49
|
projection: object,
|
|
49
50
|
ordering: object,
|
|
50
|
-
skip:
|
|
51
|
+
skip: NonNegativeInt, # Zero means no skipped documents.
|
|
51
52
|
limit: int, # Zero and negative limits use native semantics.
|
|
52
53
|
*,
|
|
53
54
|
collation: object,
|
{client_query_cache-0.6.0 → client_query_cache-0.7.0}/src/client_query_cache/_core/identity_reads.py
RENAMED
|
@@ -40,16 +40,18 @@ def extract_equality_value(value: object) -> object:
|
|
|
40
40
|
return value
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
def
|
|
44
|
-
if filter_query is None:
|
|
45
|
-
return NO_IDENTITY
|
|
43
|
+
def extract_id_anchor(filter_query: object) -> tuple[object, Mapping[str, Any]]:
|
|
46
44
|
if not isinstance(filter_query, Mapping):
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return NO_IDENTITY
|
|
52
|
-
|
|
45
|
+
return extract_equality_value(filter_query), {}
|
|
46
|
+
try:
|
|
47
|
+
identity = extract_equality_value(filter_query[_ID_FIELD])
|
|
48
|
+
except KeyError:
|
|
49
|
+
return NO_IDENTITY, {}
|
|
50
|
+
if identity is NO_IDENTITY:
|
|
51
|
+
return NO_IDENTITY, {}
|
|
52
|
+
return identity, {
|
|
53
|
+
key: value for key, value in filter_query.items() if key != _ID_FIELD
|
|
54
|
+
}
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
def normalize_identity_for_cache_key(
|
|
@@ -4,6 +4,8 @@ import threading
|
|
|
4
4
|
from collections import OrderedDict
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
+
from client_query_cache._types import NonNegativeInt, PositiveInt
|
|
8
|
+
|
|
7
9
|
if TYPE_CHECKING:
|
|
8
10
|
from client_query_cache._core.entries import CacheEntry
|
|
9
11
|
from client_query_cache._core.keys import CacheKey
|
|
@@ -23,8 +25,8 @@ class WeightedLru:
|
|
|
23
25
|
def __init__(
|
|
24
26
|
self,
|
|
25
27
|
*,
|
|
26
|
-
shared_budget_bytes:
|
|
27
|
-
max_entry_bytes:
|
|
28
|
+
shared_budget_bytes: PositiveInt,
|
|
29
|
+
max_entry_bytes: PositiveInt,
|
|
28
30
|
guard: LockOrderGuard,
|
|
29
31
|
) -> None:
|
|
30
32
|
self._shared_budget_bytes = shared_budget_bytes
|
|
@@ -35,17 +37,17 @@ class WeightedLru:
|
|
|
35
37
|
self._lock = threading.Lock()
|
|
36
38
|
|
|
37
39
|
@property
|
|
38
|
-
def max_entry_bytes(self) ->
|
|
40
|
+
def max_entry_bytes(self) -> PositiveInt:
|
|
39
41
|
return self._max_entry_bytes
|
|
40
42
|
|
|
41
43
|
@property
|
|
42
|
-
def shared_budget_bytes(self) ->
|
|
44
|
+
def shared_budget_bytes(self) -> PositiveInt:
|
|
43
45
|
return self._shared_budget_bytes
|
|
44
46
|
|
|
45
|
-
def is_oversize(self, weight:
|
|
47
|
+
def is_oversize(self, weight: NonNegativeInt) -> bool:
|
|
46
48
|
return weight > self._max_entry_bytes
|
|
47
49
|
|
|
48
|
-
def snapshot_usage(self) -> tuple[
|
|
50
|
+
def snapshot_usage(self) -> tuple[NonNegativeInt, NonNegativeInt]:
|
|
49
51
|
with self._guard.lru_section(), self._lock:
|
|
50
52
|
return self._used_bytes, len(self._order)
|
|
51
53
|
|