jharness-repository 0.2.2__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.
@@ -0,0 +1,36 @@
1
+ # OS/editor noise
2
+ .DS_Store
3
+ Thumbs.db
4
+ *.swp
5
+ *.swo
6
+
7
+ # Local assistant state
8
+ .codex/
9
+ .claude/
10
+ .gemini/
11
+ .aider*
12
+
13
+ # Python
14
+ __pycache__/
15
+ *.py[cod]
16
+ .python-version
17
+ .venv/
18
+ venv/
19
+ ENV/
20
+
21
+ # Python tooling caches and reports
22
+ .pytest_cache/
23
+ .ruff_cache/
24
+ .pyright/
25
+ .mypy_cache/
26
+ .coverage
27
+ coverage/
28
+ htmlcov/
29
+
30
+ # Packaging/build artifacts
31
+ dist/
32
+ build/
33
+ *.egg-info/
34
+
35
+ # Logs
36
+ *.log
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JHarness contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.4
2
+ Name: jharness-repository
3
+ Version: 0.2.2
4
+ Summary: Memory, SQLite, MySQL, and Redis checkpoint repositories for JHarness
5
+ Project-URL: Documentation, https://github.com/Ezio2000/jharness/tree/main/docs
6
+ Project-URL: Repository, https://github.com/Ezio2000/jharness.git
7
+ Author: JHarness contributors
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: jharness-kernel==0.2.2
21
+ Provides-Extra: mysql
22
+ Requires-Dist: pymysql[rsa]>=1.2.0; extra == 'mysql'
23
+ Provides-Extra: redis
24
+ Requires-Dist: redis>=8.0.1; extra == 'redis'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # jharness-repository
28
+
29
+ Official in-memory, SQLite, MySQL, and Redis implementations of the JHarness
30
+ `RunRepository` protocol.
31
+
32
+ ```bash
33
+ # Memory and SQLite only
34
+ uv add jharness-repository
35
+
36
+ # Add exactly the remote drivers the application uses
37
+ uv add "jharness-repository[mysql]"
38
+ uv add "jharness-repository[redis]"
39
+ uv add "jharness-repository[mysql,redis]"
40
+ ```
41
+
42
+ ```python
43
+ from jharness.repository import MemoryRunRepository
44
+ ```
45
+
46
+ The base installation has no database-driver dependency. `MemoryRunRepository` needs
47
+ no service, and SQLite uses Python's embedded `sqlite3` module. MySQL and Redis load
48
+ their selected client only when that backend is initialized; there is no fallback or
49
+ compatibility driver. Their services remain supplied by the application, while the
50
+ JHarness development suite exercises them in disposable Docker containers.
51
+
52
+ The source, contracts, and release process are maintained in the
53
+ [JHarness repository](https://github.com/Ezio2000/jharness).
@@ -0,0 +1,27 @@
1
+ # jharness-repository
2
+
3
+ Official in-memory, SQLite, MySQL, and Redis implementations of the JHarness
4
+ `RunRepository` protocol.
5
+
6
+ ```bash
7
+ # Memory and SQLite only
8
+ uv add jharness-repository
9
+
10
+ # Add exactly the remote drivers the application uses
11
+ uv add "jharness-repository[mysql]"
12
+ uv add "jharness-repository[redis]"
13
+ uv add "jharness-repository[mysql,redis]"
14
+ ```
15
+
16
+ ```python
17
+ from jharness.repository import MemoryRunRepository
18
+ ```
19
+
20
+ The base installation has no database-driver dependency. `MemoryRunRepository` needs
21
+ no service, and SQLite uses Python's embedded `sqlite3` module. MySQL and Redis load
22
+ their selected client only when that backend is initialized; there is no fallback or
23
+ compatibility driver. Their services remain supplied by the application, while the
24
+ JHarness development suite exercises them in disposable Docker containers.
25
+
26
+ The source, contracts, and release process are maintained in the
27
+ [JHarness repository](https://github.com/Ezio2000/jharness).
@@ -0,0 +1,41 @@
1
+ [project]
2
+ name = "jharness-repository"
3
+ version = "0.2.2"
4
+ description = "Memory, SQLite, MySQL, and Redis checkpoint repositories for JHarness"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [{ name = "JHarness contributors" }]
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "License :: OSI Approved :: MIT License",
13
+ "Operating System :: OS Independent",
14
+ "Programming Language :: Python :: 3 :: Only",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Programming Language :: Python :: 3.14",
19
+ "Typing :: Typed",
20
+ ]
21
+ dependencies = [
22
+ "jharness-kernel==0.2.2",
23
+ ]
24
+
25
+ [project.optional-dependencies]
26
+ mysql = ["pymysql[rsa]>=1.2.0"]
27
+ redis = ["redis>=8.0.1"]
28
+
29
+ [project.urls]
30
+ Documentation = "https://github.com/Ezio2000/jharness/tree/main/docs"
31
+ Repository = "https://github.com/Ezio2000/jharness.git"
32
+
33
+ [build-system]
34
+ requires = ["hatchling"]
35
+ build-backend = "hatchling.build"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/jharness"]
39
+
40
+ [tool.hatch.build.targets.sdist]
41
+ include = ["src/jharness/repository", "LICENSE", "README.md", "pyproject.toml"]
@@ -0,0 +1,13 @@
1
+ """Official checkpoint repository implementations for JHarness."""
2
+
3
+ from .memory import MemoryRunRepository
4
+ from .mysql import MySQLRunRepository
5
+ from .redis import RedisRunRepository
6
+ from .sqlite import SQLiteRunRepository
7
+
8
+ __all__ = [
9
+ "MemoryRunRepository",
10
+ "MySQLRunRepository",
11
+ "RedisRunRepository",
12
+ "SQLiteRunRepository",
13
+ ]
@@ -0,0 +1,72 @@
1
+ """Canonical checkpoint encoding shared by repository adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass
7
+ from hashlib import sha256
8
+ from typing import cast
9
+
10
+ from jharness.kernel import Checkpoint, ProtocolError, RepositoryError
11
+ from jharness.kernel.wire import decode_checkpoint as decode_wire_checkpoint
12
+ from jharness.kernel.wire import encode_checkpoint as encode_wire_checkpoint
13
+
14
+
15
+ @dataclass(frozen=True, slots=True)
16
+ class EncodedCheckpoint:
17
+ """The indexed fields and canonical bytes for one checkpoint."""
18
+
19
+ checkpoint_id: str
20
+ run_id: str
21
+ revision: int
22
+ payload: bytes
23
+ digest: bytes
24
+
25
+ @property
26
+ def expected_revision(self) -> int | None:
27
+ """Return the head revision required immediately before this checkpoint."""
28
+ return self.revision - 1 if self.revision else None
29
+
30
+
31
+ def encode_checkpoint(checkpoint: Checkpoint) -> EncodedCheckpoint:
32
+ """Encode a checkpoint into stable JSON bytes and a content fingerprint."""
33
+ if not isinstance(cast(object, checkpoint), Checkpoint):
34
+ raise TypeError("checkpoint must be a Checkpoint")
35
+ checkpoint_id = str.__str__(checkpoint.id)
36
+ run_id = str.__str__(checkpoint.snapshot.context.run_id)
37
+ if not checkpoint_id:
38
+ raise ValueError("checkpoint id must not be empty")
39
+ if not run_id:
40
+ raise ValueError("run id must not be empty")
41
+ payload = json.dumps(
42
+ encode_wire_checkpoint(checkpoint),
43
+ allow_nan=False,
44
+ ensure_ascii=False,
45
+ separators=(",", ":"),
46
+ sort_keys=True,
47
+ ).encode("utf-8")
48
+ return EncodedCheckpoint(
49
+ checkpoint_id=checkpoint_id,
50
+ run_id=run_id,
51
+ revision=checkpoint.snapshot.revision,
52
+ payload=payload,
53
+ digest=sha256(payload).digest(),
54
+ )
55
+
56
+
57
+ def decode_checkpoint(payload: bytes) -> Checkpoint:
58
+ """Decode stored checkpoint bytes, reporting malformed storage uniformly."""
59
+ if type(payload) is not bytes:
60
+ raise TypeError("checkpoint payload must be bytes")
61
+ try:
62
+ document: object = json.loads(payload)
63
+ return decode_wire_checkpoint(document)
64
+ except (
65
+ OverflowError,
66
+ RecursionError,
67
+ UnicodeDecodeError,
68
+ ProtocolError,
69
+ TypeError,
70
+ ValueError,
71
+ ) as exc:
72
+ raise RepositoryError("stored checkpoint payload is invalid") from exc
@@ -0,0 +1,70 @@
1
+ """Process-local, multi-run checkpoint repository."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from threading import Lock
6
+ from typing import cast
7
+
8
+ from jharness.kernel import Checkpoint, RepositoryError, RevisionConflict
9
+
10
+ from ._codec import EncodedCheckpoint, decode_checkpoint, encode_checkpoint
11
+
12
+
13
+ class MemoryRunRepository:
14
+ """Thread-safe in-memory CAS repository for any number of runs."""
15
+
16
+ __slots__ = ("_by_id", "_heads", "_lock")
17
+
18
+ def __init__(self) -> None:
19
+ self._heads: dict[str, EncodedCheckpoint] = {}
20
+ self._by_id: dict[str, bytes] = {}
21
+ self._lock = Lock()
22
+
23
+ async def commit(self, checkpoint: Checkpoint) -> None:
24
+ """Atomically advance one run head, or accept an exact prior retry."""
25
+ encoded = encode_checkpoint(checkpoint)
26
+ with self._lock:
27
+ existing_digest = self._by_id.get(encoded.checkpoint_id)
28
+ if existing_digest is not None:
29
+ if existing_digest == encoded.digest:
30
+ return
31
+ raise RepositoryError(
32
+ f"checkpoint id {encoded.checkpoint_id!r} was reused with new content"
33
+ )
34
+
35
+ head = self._heads.get(encoded.run_id)
36
+ actual_revision = None if head is None else head.revision
37
+ if actual_revision != encoded.expected_revision:
38
+ raise RevisionConflict(
39
+ encoded.run_id,
40
+ encoded.expected_revision,
41
+ actual_revision,
42
+ )
43
+
44
+ previous_head = self._heads.get(encoded.run_id)
45
+ try:
46
+ self._by_id[encoded.checkpoint_id] = encoded.digest
47
+ self._heads[encoded.run_id] = encoded
48
+ except BaseException:
49
+ self._by_id.pop(encoded.checkpoint_id, None)
50
+ if previous_head is None:
51
+ self._heads.pop(encoded.run_id, None)
52
+ else:
53
+ self._heads[encoded.run_id] = previous_head
54
+ raise
55
+
56
+ async def get_head(self, run_id: str) -> Checkpoint | None:
57
+ """Return the authoritative checkpoint for a run, if one exists."""
58
+ run_id = _validate_run_id(run_id)
59
+ with self._lock:
60
+ head = self._heads.get(run_id)
61
+ return None if head is None else decode_checkpoint(head.payload)
62
+
63
+
64
+ def _validate_run_id(run_id: str) -> str:
65
+ if not isinstance(cast(object, run_id), str):
66
+ raise TypeError("run_id must be a string")
67
+ run_id = str.__str__(run_id)
68
+ if not run_id:
69
+ raise ValueError("run_id must not be empty")
70
+ return run_id