kvgit 0.3.2__tar.gz → 0.3.4__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 (45) hide show
  1. {kvgit-0.3.2 → kvgit-0.3.4}/PKG-INFO +2 -2
  2. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/codecs/numpy.py +2 -2
  3. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/codecs/pickler.py +2 -1
  4. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/content_types.py +2 -1
  5. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/encoding.py +2 -1
  6. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/kv/indexeddb.py +4 -3
  7. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/namespaced.py +2 -2
  8. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/staged.py +2 -2
  9. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/store.py +31 -4
  10. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/versioned/base.py +1 -1
  11. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/versioned/helpers.py +1 -1
  12. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/versioned/kv.py +491 -68
  13. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/versioned/merge.py +3 -3
  14. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/versioned/protocol.py +2 -2
  15. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit.egg-info/PKG-INFO +2 -2
  16. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit.egg-info/requires.txt +1 -1
  17. kvgit-0.3.4/pyproject.toml +152 -0
  18. {kvgit-0.3.2 → kvgit-0.3.4}/tests/test_hamt.py +4 -5
  19. kvgit-0.3.2/pyproject.toml +0 -69
  20. {kvgit-0.3.2 → kvgit-0.3.4}/LICENSE +0 -0
  21. {kvgit-0.3.2 → kvgit-0.3.4}/README.md +0 -0
  22. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/__init__.py +1 -1
  23. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/codecs/__init__.py +0 -0
  24. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/codecs/_hash.py +0 -0
  25. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/codecs/base.py +0 -0
  26. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/codecs/pandas.py +0 -0
  27. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/errors.py +0 -0
  28. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/hamt.py +0 -0
  29. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/kv/__init__.py +0 -0
  30. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/kv/base.py +0 -0
  31. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/kv/composite.py +0 -0
  32. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/kv/disk.py +0 -0
  33. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/kv/memory.py +0 -0
  34. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/py.typed +0 -0
  35. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/versioned/__init__.py +0 -0
  36. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit/versioned/keyset.py +0 -0
  37. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit.egg-info/SOURCES.txt +0 -0
  38. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit.egg-info/dependency_links.txt +0 -0
  39. {kvgit-0.3.2 → kvgit-0.3.4}/kvgit.egg-info/top_level.txt +0 -0
  40. {kvgit-0.3.2 → kvgit-0.3.4}/setup.cfg +0 -0
  41. {kvgit-0.3.2 → kvgit-0.3.4}/tests/test_content_types.py +0 -0
  42. {kvgit-0.3.2 → kvgit-0.3.4}/tests/test_delete_branches.py +0 -0
  43. {kvgit-0.3.2 → kvgit-0.3.4}/tests/test_namespaced.py +0 -0
  44. {kvgit-0.3.2 → kvgit-0.3.4}/tests/test_staged.py +0 -0
  45. {kvgit-0.3.2 → kvgit-0.3.4}/tests/test_store_factory.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kvgit
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Versioned key-value store with git-like commit, branch, and merge semantics.
5
5
  Author: ashenfad
6
6
  License: MIT
@@ -42,7 +42,7 @@ Requires-Dist: pandas>=2.0; extra == "all"
42
42
  Provides-Extra: dev
43
43
  Requires-Dist: pytest; extra == "dev"
44
44
  Requires-Dist: diskcache; extra == "dev"
45
- Requires-Dist: ruff; extra == "dev"
45
+ Requires-Dist: ruff<0.17,>=0.16; extra == "dev"
46
46
  Requires-Dist: numpy>=1.24; extra == "dev"
47
47
  Requires-Dist: pandas>=2.0; extra == "dev"
48
48
  Dynamic: license-file
@@ -33,7 +33,7 @@ storing each unique buffer once instead of N times.)
33
33
 
34
34
  from __future__ import annotations
35
35
 
36
- from typing import Any, TYPE_CHECKING
36
+ from typing import TYPE_CHECKING, Any
37
37
 
38
38
  if TYPE_CHECKING:
39
39
  import numpy as np
@@ -66,7 +66,7 @@ def _descr_to_dtype(descr):
66
66
  return npf.descr_to_dtype(descr)
67
67
 
68
68
 
69
- def _root_and_offset(arr: "np.ndarray") -> tuple["np.ndarray", int]:
69
+ def _root_and_offset(arr: np.ndarray) -> tuple[np.ndarray, int]:
70
70
  """Walk ``.base`` to the root buffer; return (root, byte_offset).
71
71
 
72
72
  Returns ``(arr, 0)`` if the .base chain leads somewhere we can't
@@ -16,7 +16,8 @@ from __future__ import annotations
16
16
 
17
17
  import io
18
18
  import pickle
19
- from typing import Any, Iterable
19
+ from collections.abc import Iterable
20
+ from typing import Any
20
21
 
21
22
  from .base import ChunkReader, ChunkSink, Codec
22
23
 
@@ -1,6 +1,7 @@
1
1
  """Merge functions for typed values."""
2
2
 
3
- from typing import Any, Callable
3
+ from collections.abc import Callable
4
+ from typing import Any
4
5
 
5
6
  MergeFn = Callable[[Any | None, Any, Any], Any]
6
7
  """Merge function: (old_value | None, our_value, their_value) -> merged_value.
@@ -28,5 +28,6 @@ def safe_loads(raw: bytes):
28
28
  """
29
29
  try:
30
30
  return json.loads(raw)
31
- except Exception:
31
+ except Exception: # noqa: BLE001 — the point of the function: any
32
+ # decode failure (garbage, partial write, version skew) is a miss.
32
33
  return None
@@ -47,8 +47,8 @@ the handler is set, causing a deadlock.
47
47
 
48
48
  from collections.abc import Iterable, Mapping
49
49
 
50
- from pyodide.ffi import create_proxy, run_sync, to_js # type: ignore[import-not-found]
51
50
  from js import Promise, indexedDB, undefined # type: ignore[import-not-found]
51
+ from pyodide.ffi import create_proxy, run_sync, to_js # type: ignore[import-not-found]
52
52
 
53
53
  from .base import KVStore
54
54
 
@@ -155,8 +155,9 @@ def _to_bytes(js_value) -> bytes | None:
155
155
  return None
156
156
  try:
157
157
  return js_value.to_py().tobytes()
158
- except Exception:
159
- # Corrupted or unexpected JS value — treat as missing
158
+ except Exception: # noqa: BLE001 — a JS->Python conversion can fail
159
+ # with anything the FFI decides to raise; there is no narrower
160
+ # type to name. Corrupted or unexpected JS value — treat as missing.
160
161
  return None
161
162
 
162
163
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from collections.abc import Iterator, MutableMapping
6
- from typing import Any, Iterable
5
+ from collections.abc import Iterable, Iterator, MutableMapping
6
+ from typing import Any
7
7
 
8
8
 
9
9
  class Namespaced(MutableMapping[str, Any]):
@@ -2,8 +2,8 @@
2
2
 
3
3
  import inspect
4
4
  import pickle
5
- from collections.abc import Iterable, Iterator, MutableMapping
6
- from typing import Any, Callable
5
+ from collections.abc import Callable, Iterable, Iterator, MutableMapping
6
+ from typing import Any
7
7
 
8
8
  from .codecs._hash import hash_bytes
9
9
  from .content_types import MergeFn
@@ -1,13 +1,19 @@
1
1
  """Store factory function."""
2
2
 
3
3
  import pickle
4
- from collections.abc import Iterable
5
- from typing import Any, Callable, Literal
4
+ from collections.abc import Callable, Iterable
5
+ from typing import Any, Literal
6
6
 
7
7
  from .kv.base import KVStore
8
8
  from .kv.memory import Memory
9
9
  from .staged import Staged
10
- from .versioned.kv import BRANCH_HEAD, BRANCH_HEAD_PREV, VersionedKV, clean_orphans
10
+ from .versioned.kv import (
11
+ BRANCH_HEAD,
12
+ BRANCH_HEAD_PREV,
13
+ CorruptHeadRecoverer,
14
+ VersionedKV,
15
+ clean_orphans,
16
+ )
11
17
 
12
18
 
13
19
  def _make_backend(
@@ -46,6 +52,7 @@ def store(
46
52
  encoder: Callable[..., bytes] = pickle.dumps,
47
53
  decoder: Callable[..., Any] = pickle.loads,
48
54
  codecs: str | None = None,
55
+ recover_from_corrupt_head: CorruptHeadRecoverer | None = None,
49
56
  ) -> Staged:
50
57
  """Create a Staged store with sensible defaults.
51
58
 
@@ -62,6 +69,13 @@ def store(
62
69
  ``"scientific"`` — numpy/pandas chunked codecs (requires
63
70
  numpy; install with ``pip install kvgit[scientific]``).
64
71
  Mutually exclusive with explicit ``encoder`` / ``decoder``.
72
+ recover_from_corrupt_head: Optional last-resort recovery for a
73
+ branch whose HEAD is unresolvable *and* whose prev-HEAD
74
+ backup is gone. Off by default: with both gone the store
75
+ does not know what the branch pointed at, so the honest
76
+ answer is to fail rather than guess. Pass
77
+ ``kvgit.versioned.kv.recover_by_commit_scan`` to opt into
78
+ the scan, having read what it can return.
65
79
 
66
80
  Returns:
67
81
  A ``Staged`` store instance.
@@ -85,7 +99,15 @@ def store(
85
99
 
86
100
  backend = _make_backend(kind, path=path, db_name=db_name)
87
101
 
88
- return Staged(VersionedKV(backend, branch=branch), encoder=encoder, decoder=decoder)
102
+ return Staged(
103
+ VersionedKV(
104
+ backend,
105
+ branch=branch,
106
+ recover_from_corrupt_head=recover_from_corrupt_head,
107
+ ),
108
+ encoder=encoder,
109
+ decoder=decoder,
110
+ )
89
111
 
90
112
 
91
113
  def delete_branches(
@@ -123,6 +145,11 @@ def delete_branches(
123
145
  not swept out from under it; an admin who knows the store is quiet
124
146
  can pass ``0`` to reclaim young commits immediately.
125
147
 
148
+ That sweep does not reclaim chunks — the content-addressed bytes a
149
+ chunked codec writes — because a chunk key is not the deleted
150
+ branch's to give away. On a store using chunked codecs, follow up
151
+ with ``deep_clean`` on a quiescent store to get that space back.
152
+
126
153
  Args:
127
154
  names: Branch names to delete — one name or an iterable of
128
155
  them. Missing names are no-ops. (A bare string is treated
@@ -1,7 +1,7 @@
1
1
  """Shared commit/merge orchestration for versioned stores."""
2
2
 
3
3
  from abc import ABC, abstractmethod
4
- from typing import Iterable
4
+ from collections.abc import Iterable
5
5
 
6
6
  from ..errors import ConcurrencyError, MergeConflict
7
7
  from .helpers import diff_keysets, walk_history
@@ -1,7 +1,7 @@
1
1
  """Shared diff and history helpers."""
2
2
 
3
3
  from collections import deque
4
- from typing import Callable, Iterable
4
+ from collections.abc import Callable, Iterable
5
5
 
6
6
  from .protocol import DiffResult
7
7