small-cache 0.1.3__cp315-cp315-win_amd64.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.
@@ -0,0 +1,14 @@
1
+ """""" # start delvewheel patch
2
+ def _delvewheel_patch_1_13_1():
3
+ import os
4
+ if os.path.isdir(libs_dir := os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'small_cache.libs'))):
5
+ os.add_dll_directory(libs_dir)
6
+
7
+
8
+ _delvewheel_patch_1_13_1()
9
+ del _delvewheel_patch_1_13_1
10
+ # end delvewheel patch
11
+
12
+ from ._small_cache_impl import SmallCache
13
+
14
+ __all__ = ["SmallCache"]
@@ -0,0 +1,36 @@
1
+ from collections.abc import Sequence
2
+
3
+
4
+ class SmallCache:
5
+ def __init__(self, attribute_names: Sequence[str]) -> None:
6
+ """A cache of items with up to 96 attributes, named in this order."""
7
+
8
+ def begin_transaction(self, estimated_number_of_items: int = 0, remove_old_items: bool = True) -> None:
9
+ """
10
+ Start a load. Readers keep seeing the current data until end_transaction(). With remove_old_items, items not added during the load are dropped.
11
+ """
12
+
13
+ def end_transaction(self) -> None:
14
+ """Publish the load to readers."""
15
+
16
+ def abort_transaction(self) -> None:
17
+ """Discard the load; readers keep the current data."""
18
+
19
+ def add(self, item_id: str, attributes: dict[str, None | bool | int | float | str | bytes | list[str | bytes] | tuple[str | bytes, ...]]) -> None:
20
+ """
21
+ Add or replace an item in the open load. bytes are JSON text, embedded in answers as is; attributes not in the cache's names are ignored.
22
+ """
23
+
24
+ def get_one(self, id: str, attributes: Sequence[str]) -> bytes:
25
+ """
26
+ JSON array of the item's values in the order of `attributes` (null if unset); [] if there is no such item.
27
+ """
28
+
29
+ def get_many(self, ids: Sequence[str], attributes: Sequence[str]) -> bytes:
30
+ """JSON array with a get_one() row for each id."""
31
+
32
+ def get_all(self, attributes: Sequence[str]) -> bytes:
33
+ """JSON array with a get_one() row for each item, in get_all_ids() order."""
34
+
35
+ def get_all_ids(self) -> bytes:
36
+ """JSON array of all item ids."""
small_cache/py.typed ADDED
File without changes
@@ -0,0 +1,2 @@
1
+ Version: 1.13.1
2
+ Arguments: ['C:\\Users\\runneradmin\\AppData\\Local\\Temp\\cibw-run-tonu1i82\\cp315-win_amd64\\build\\venv\\Scripts\\delvewheel', 'repair', '-w', 'C:\\Users\\runneradmin\\AppData\\Local\\Temp\\cibw-run-tonu1i82\\cp315-win_amd64\\repaired_wheel', '-v', 'C:\\Users\\runneradmin\\AppData\\Local\\Temp\\cibw-run-tonu1i82\\cp315-win_amd64\\built_wheel\\small_cache-0.1.3-cp315-cp315-win_amd64.whl']
@@ -0,0 +1,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: small-cache
3
+ Version: 0.1.3
4
+ Summary: Compact in-process snapshot cache for structured items
5
+ Author: kam1k4dze
6
+ License-Expression: BSD-3-Clause
7
+ License-File: LICENSE
8
+ Project-URL: Homepage, https://github.com/kam1k4dze/small_cache
9
+ Requires-Python: <3.16,>=3.15
10
+ Description-Content-Type: text/markdown
11
+
12
+ # small_cache
13
+
14
+ A memory-efficient in-process cache for structured items with typed attributes, implemented in C++23 and exposed to Python via [nanobind](https://github.com/wjakob/nanobind).
15
+
16
+ | CI | Status |
17
+ |----|--------|
18
+ | wheels | [![Wheel Action Status][actions-wheels-badge]][actions-wheels-link] |
19
+
20
+ [actions-wheels-link]: https://github.com/kam1k4dze/small_cache/actions?query=workflow%3AWheels
21
+ [actions-wheels-badge]: https://github.com/kam1k4dze/small_cache/workflows/Wheels/badge.svg
22
+
23
+ ## Features
24
+
25
+ - Store items with up to **96 named attributes** per cache instance
26
+ - Supported attribute types: `bool`, `int` (64-bit), `float`, `str`, JSON (`bytes`), lists of `str`/JSON, `None` —
27
+ each comes back as the type it was stored as
28
+ - **JSON answers** — getters return JSON written directly from the cache, with stored JSON embedded as is
29
+ - **Flat, compact storage** — every distinct string is stored once; strings of 64+ bytes are zstd-compressed
30
+ with a dictionary trained on the data; each item is one packed record (absent and `None` attributes take no space)
31
+ - **Snapshots** — a reload is built aside and published at once; reads never block and never see a half-finished load
32
+ - **Transaction model** — bulk-update the cache, optionally evicting items the reload did not contain
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install small-cache
38
+ ```
39
+
40
+ Only binary wheels are published, for CPython 3.15, regular and free-threaded (3.15t), on Linux, macOS and
41
+ Windows (64-bit). With free-threading the cache runs without the GIL: reads and loading proceed in parallel.
42
+
43
+ Build from source:
44
+
45
+ ```bash
46
+ git clone https://github.com/kam1k4dze/small_cache
47
+ pip install ./small_cache
48
+ ```
49
+
50
+ **Requirements:** Python ≥ 3.15 and < 3.16; to build: CMake ≥ 3.28 and a C++23 compiler
51
+ (build dependencies: scikit-build-core 1.1.1, nanobind 3.1.0)
52
+
53
+ ## Quick Start
54
+
55
+ ```python
56
+ from small_cache import SmallCache
57
+
58
+ # Declare the attributes you care about (max 96)
59
+ cache = SmallCache(["name", "price", "in_stock", "tags"])
60
+
61
+ # A transaction wraps a full cache refresh cycle
62
+ cache.begin_transaction(estimated_number_of_items=1000)
63
+
64
+ cache.add("sku-001", {
65
+ "name": "Widget",
66
+ "price": 9.99,
67
+ "in_stock": True,
68
+ "tags": ["sale", "hardware"],
69
+ })
70
+ cache.add("sku-002", {"name": "Gadget", "price": 24.99, "in_stock": False})
71
+
72
+ cache.end_transaction() # publishes the new data; items not re-added are evicted
73
+
74
+ # Answers are JSON bytes
75
+ cache.get_one("sku-001", ["name", "price"])
76
+ # b'["Widget",9.99]'
77
+
78
+ # Batch retrieval: an unknown id gets an empty row
79
+ cache.get_many(["sku-001", "sku-002", "sku-999"], ["name", "in_stock"])
80
+ # b'[["Widget",true],["Gadget",false],[]]'
81
+
82
+ cache.get_all_ids()
83
+ # b'["sku-001","sku-002"]'
84
+ ```
85
+
86
+ ## Transaction Model
87
+
88
+ `begin_transaction()` / `end_transaction()` bracket a full reload cycle:
89
+
90
+ - Items written with `add()` go into a new snapshot that readers cannot see yet. Until
91
+ `end_transaction()`, every read is served from the current snapshot.
92
+ - `end_transaction()` publishes the new snapshot at once. Items that were **not** written in the transaction
93
+ are dropped, or carried over unchanged with `remove_old_items=False`.
94
+ - `abort_transaction()` throws the new snapshot away; the current one keeps being served. Call it when a
95
+ reload fails half-way, so a partial load is never published.
96
+ - Reads take no lock and can run from any thread. While a reload is being built, the old and the new
97
+ snapshot are both in memory.
98
+
99
+ ```python
100
+ # Keep stale items (incremental update)
101
+ cache.begin_transaction(remove_old_items=False)
102
+ cache.add("sku-003", {"name": "New item", "price": 5.0})
103
+ cache.end_transaction() # pre-existing items survive
104
+
105
+ # Never publish a failed reload
106
+ cache.begin_transaction()
107
+ try:
108
+ for item_id, attributes in fetch_items():
109
+ cache.add(item_id, attributes)
110
+ except Exception:
111
+ cache.abort_transaction()
112
+ raise
113
+ cache.end_transaction()
114
+ ```
115
+
116
+ ## API Reference
117
+
118
+ ### `SmallCache(attribute_names: list[str])`
119
+
120
+ Construct a cache instance. `attribute_names` lists every attribute that may be stored. Maximum 96 attributes; raises `RuntimeError` if the list is empty or exceeds the limit.
121
+
122
+ ### `begin_transaction(estimated_number_of_items: int = 0, remove_old_items: bool = True)`
123
+
124
+ Open a write transaction. Optionally pre-allocates capacity. Raises `RuntimeError` if a transaction is already open.
125
+
126
+ ### `end_transaction()`
127
+
128
+ Publish the transaction. With `remove_old_items=True` (default), items not written during this transaction are evicted. Raises `RuntimeError` if no transaction is open.
129
+
130
+ ### `abort_transaction()`
131
+
132
+ Discard the transaction; the cache keeps serving what it had. Raises `RuntimeError` if no transaction is open.
133
+
134
+ ### `add(item_id: str, attributes: dict[str, None | bool | int | float | str | bytes | list[str | bytes]])`
135
+
136
+ Insert or replace an item. Must be called inside a transaction. Attributes absent from `attribute_names` are
137
+ silently ignored. `bytes` is JSON text, kept as is: the getters write it into their answers unchanged, so it
138
+ must be valid JSON. List items are `str` or such JSON `bytes`. An `int` is stored as an integer and a `float` as
139
+ a float; any other type, or an `int` outside the signed 64-bit range, raises `TypeError`.
140
+
141
+ ### `get_one(id: str, attributes: list[str]) -> bytes`
142
+
143
+ The item's values for `attributes`, in that order, as a UTF-8 JSON array. Attributes that were not set, or whose
144
+ name is not registered, are `null`; stored JSON is embedded as is; floats always carry a decimal point or exponent,
145
+ so they stay floats. `[]` if the item does not exist or `attributes` is empty. The answer is built without holding
146
+ the GIL. To return it from a JSON API without decoding it, wrap it, e.g. in `msgspec.Raw`.
147
+
148
+ ### `get_many(ids: list[str], attributes: list[str]) -> bytes`
149
+
150
+ A JSON array with one `get_one` row per requested id.
151
+
152
+ ### `get_all(attributes: list[str]) -> bytes`
153
+
154
+ A JSON array with one `get_one` row per item, in `get_all_ids()` order.
155
+
156
+ ### `get_all_ids() -> bytes`
157
+
158
+ A JSON array of all item ids: the order items were added in, items carried over by `remove_old_items=False` last.
159
+
160
+ ## Building from Source
161
+
162
+ ```bash
163
+ git clone https://github.com/kam1k4dze/small_cache
164
+ cd small_cache
165
+ pip install -e .
166
+ ```
167
+
168
+ Native C++ unit tests and benchmark (requires CMake ≥ 3.28, Ninja and a C++23 compiler with modules support):
169
+
170
+ ```bash
171
+ cmake -B build -G Ninja && cmake --build build
172
+ ./build/small_cache_native_test
173
+ ```
174
+
175
+ `small_cache_bench` replays a recording of `add()` calls (JSON lines: a header `{"attributes": [...]}`, then one
176
+ `{"id": ..., "a": {...}}` per call) as a series of reloads and reports memory and timings:
177
+
178
+ ```bash
179
+ ./build/small_cache_bench adds.jsonl --cycles 3
180
+ heaptrack ./build/small_cache_bench adds.jsonl --cycles 2
181
+ ```
182
+
183
+ Python tests:
184
+
185
+ ```bash
186
+ pip install pytest
187
+ pytest tests/
188
+ ```
189
+
190
+ ## License
191
+
192
+ BSD-3-Clause — see [LICENSE](./LICENSE).
@@ -0,0 +1,10 @@
1
+ small_cache/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ small_cache/_small_cache_impl.cp315-win_amd64.pyd,sha256=D6h2LZW9Q-4VQCC0efPVomwQLs_C175cTLvGApRdFVQ,696320
3
+ small_cache/_small_cache_impl.pyi,sha256=PZKFo2sK-JDfIo8vAHfnlAF6vPRDDm18oVIjkBNWp_o,1602
4
+ small_cache/__init__.py,sha256=HJIgqC465XmEv0_efSyD5_zN0Ya6vdKbTla4o7i1ftI,404
5
+ small_cache-0.1.3.dist-info/DELVEWHEEL,sha256=7Bfnufz_8-yCmxIqCBvK11Hx7ejOBKQ6mnKzE5llErU,410
6
+ small_cache-0.1.3.dist-info/METADATA,sha256=C23pQJaktgo4OpIFM-skXTarGdIZrVFBnIgaX_lFF64,7171
7
+ small_cache-0.1.3.dist-info/RECORD,,
8
+ small_cache-0.1.3.dist-info/WHEEL,sha256=lX_s9_PODHQdPEziwqn30Su2CEVeLs-HsvaJqybfX_g,105
9
+ small_cache-0.1.3.dist-info/licenses/LICENSE,sha256=zBrz9KYdq8ONytsas0TAkOopMFXkrfGHqbyieoAEHo0,1545
10
+ small_cache.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll,sha256=pMIim9wqKmMKzcCVtNhgCOXD47x3cxdDVPPaT1vrnN4,575056
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: scikit-build-core 1.1.1
3
+ Root-Is-Purelib: false
4
+ Tag: cp315-cp315-win_amd64
5
+
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2025-2026 kam1k4dze <me@kam1k4dze.com>, All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this
7
+ list of conditions and the following disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ 3. Neither the name of the copyright holder nor the names of its contributors
14
+ may be used to endorse or promote products derived from this software
15
+ without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.