diff --git a/pyproject.toml b/pyproject.toml index 641588a..1ef5af5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ keywords = [ requires-python = ">=3.12.2" dependencies = [ "hio==0.6.14", - "keri==1.2.12", + "keri @ git+https://gitlab.vroblok.io/m00sey/keripy.git@enc/move-extern", "mnemonic==0.21", "multicommand>=1.0.0", "falcon==4.0.2", diff --git a/src/keria/core/keeping.py b/src/keria/core/keeping.py index a99097e..ba082f1 100644 --- a/src/keria/core/keeping.py +++ b/src/keria/core/keeping.py @@ -7,7 +7,7 @@ from dataclasses import dataclass, asdict, field -from keri.app.keeping import PreSit, Algos, PubLot, PubSet +from keri.app.keeping import PreSit, Algos, PubLot, PubSet, ExternModule from keri import core from keri.core import coring from keri.core.coring import Tiers, MtrDex @@ -173,8 +173,10 @@ def get(self, algo: Algos = None, pre=None): return RandyManager(rb=self.rb) case Algos.group: return GroupManager(rb=self.rb, rm=self) + case Algos.extern: + return ExternManager(rb=self.rb) case _: - return ExternKeeper(rb=self.rb) + raise ValueError(f"Unsupported algorithm: {algo}") @property def sxlt(self): @@ -440,9 +442,35 @@ def params(self, pre): return prms -class ExternKeeper: +class ExternManager(ExternModule): def __init__(self, rb: RemoteKeeper): self.rb = rb - def incept(self, **kwargs): - pass + def incept(self, pre, verfers=None, digers=None, **kwargs): + pp = Prefix(pidx=0, algo=Algos.extern) + if not self.rb.pres.put(pre, val=pp): + raise ValueError(f"Already incepted pre={pre}.") + + def rotate(self, pre, verfers=None, digers=None, **kwargs): + if (pp := self.rb.pres.get(pre)) is None or pp.algo != Algos.extern: + raise ValueError(f"Attempt to rotate nonexistent or invalid pre={pre}.") + + def params(self, pre): + if (pp := self.rb.pres.get(pre)) is None or pp.algo != Algos.extern: + raise ValueError(f"Attempt to load nonexistent or invalid pre={pre}.") + prms = dict(extern=asdict(pp)) + return prms + + +def __getattr__(name): + if name == "ExternKeeper": + import warnings + + warnings.warn( + "ExternKeeper has been renamed to ExternManager. " + "ExternKeeper will be removed in a future release.", + DeprecationWarning, + stacklevel=2, + ) + return ExternManager + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/tests/core/test_keeping.py b/tests/core/test_keeping.py new file mode 100644 index 0000000..5ed1e2f --- /dev/null +++ b/tests/core/test_keeping.py @@ -0,0 +1,73 @@ +# -*- encoding: utf-8 -*- +""" +Tests for keria.core.keeping module + +""" + +import warnings + +import pytest +from keri.app import habbing + +from keria.core.keeping import ExternManager, RemoteKeeper + + +def test_extern_manager(): + """Test ExternManager incept, rotate, params lifecycle""" + with habbing.openHby(name="test", temp=True) as hby: + rb = RemoteKeeper( + name=hby.name, + base=hby.base, + temp=True, + reopen=True, + clear=True, + headDirPath=hby.db.headDirPath, + ) + + mgr = ExternManager(rb=rb) + pre = "ECtWlHS2Wbx5M2Rg6nm69PCtzwb1veiRNvDpBGF9Z1ec" + + # incept + mgr.incept(pre) + + # double incept raises + with pytest.raises(ValueError, match="Already incepted"): + mgr.incept(pre) + + # rotate succeeds after incept + mgr.rotate(pre) + + # params returns extern prefix data + result = mgr.params(pre) + assert "extern" in result + assert result["extern"]["algo"] == "extern" + assert result["extern"]["pidx"] == 0 + + # rotate unknown pre raises + with pytest.raises(ValueError, match="nonexistent or invalid"): + mgr.rotate("EUnknownPrefix") + + # params unknown pre raises + with pytest.raises(ValueError, match="nonexistent or invalid"): + mgr.params("EUnknownPrefix") + + +def test_extern_keeper_deprecation(): + """Test that importing ExternKeeper emits a DeprecationWarning""" + import keria.core.keeping as keeping_mod + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + cls = getattr(keeping_mod, "ExternKeeper") + assert cls is ExternManager + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "ExternManager" in str(w[0].message) + + +def test_module_getattr_unknown(): + """Test that accessing an unknown attribute raises AttributeError""" + import keria.core.keeping as keeping_mod + + with pytest.raises(AttributeError, match="has no attribute"): + getattr(keeping_mod, "NoSuchThing") diff --git a/uv.lock b/uv.lock index 761e340..1e4af73 100644 --- a/uv.lock +++ b/uv.lock @@ -494,7 +494,7 @@ wheels = [ [[package]] name = "keri" version = "1.2.12" -source = { registry = "https://pypi.org/simple" } +source = { git = "https://gitlab.vroblok.io/m00sey/keripy.git?rev=enc%2Fmove-extern#c7848bbfea28b4ec55f450b5aa46f9e639137266" } dependencies = [ { name = "apispec" }, { name = "blake3" }, @@ -517,7 +517,6 @@ dependencies = [ { name = "qrcode" }, { name = "semver" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/c6/c552cd481fac99102e0fd753b9df302fbe6acc7c766164e0c73464218724/keri-1.2.12.tar.gz", hash = "sha256:06c86aee587b81f1975b21e61a33b2402edf2ede47c0d7e693f66faad28ecae6", size = 479456, upload-time = "2026-03-04T22:44:13.633Z" } [[package]] name = "keria" @@ -555,7 +554,7 @@ requires-dist = [ { name = "falcon", specifier = "==4.0.2" }, { name = "hio", specifier = "==0.6.14" }, { name = "http-sfv", specifier = "==0.9.9" }, - { name = "keri", specifier = "==1.2.12" }, + { name = "keri", git = "https://gitlab.vroblok.io/m00sey/keripy.git?rev=enc%2Fmove-extern" }, { name = "marshmallow-dataclass", specifier = "==8.7.1" }, { name = "mnemonic", specifier = "==0.21" }, { name = "multicommand", specifier = ">=1.0.0" },