feat: introduce attribute management system for sandbox

- Added AttrMap and AttrKey classes for type-safe attribute storage.
- Implemented AppAssetsAttrs and SkillAttrs for managing application and skill attributes.
- Refactored Sandbox and initializers to utilize the new attribute management system, enhancing modularity and clarity in asset handling.
This commit is contained in:
Harry
2026-01-22 15:05:35 +08:00
parent ecd6c44a32
commit e7c3e4cd21
13 changed files with 524 additions and 49 deletions

View File

@ -0,0 +1,251 @@
import pytest
from libs.attr_map import AttrKey, AttrMap, AttrMapKeyError, AttrMapTypeError
class TestAttrKey:
def test_identity_based_equality(self):
key1 = AttrKey("session", str)
key2 = AttrKey("session", str)
assert key1 != key2
assert key1 == key1
def test_identity_based_hash(self):
key1 = AttrKey("session", str)
key2 = AttrKey("session", str)
assert hash(key1) != hash(key2)
assert hash(key1) == hash(key1)
def test_can_be_used_as_dict_key(self):
key1 = AttrKey("session", str)
key2 = AttrKey("session", str)
data: dict[AttrKey[str], str] = {}
data[key1] = "value1"
data[key2] = "value2"
assert data[key1] == "value1"
assert data[key2] == "value2"
assert len(data) == 2
def test_properties(self):
key = AttrKey("my_key", int)
assert key.name == "my_key"
assert key.type_ is int
def test_repr(self):
key = AttrKey("session", str)
assert repr(key) == "AttrKey('session', str)"
class TestAttrMap:
def test_set_and_get(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
attrs.set(key, "hello")
result = attrs.get(key)
assert result == "hello"
def test_get_returns_none_for_missing(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
assert attrs.get(key) is None
def test_get_or_default_returns_value_when_set(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
attrs.set(key, "hello")
result = attrs.get_or_default(key, "default")
assert result == "hello"
def test_get_or_default_returns_default_when_not_set(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
result = attrs.get_or_default(key, "default")
assert result == "default"
def test_require_returns_value_when_set(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
attrs.set(key, "hello")
result = attrs.require(key)
assert result == "hello"
def test_require_raises_when_not_set(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
with pytest.raises(AttrMapKeyError) as exc_info:
attrs.require(key)
assert exc_info.value.key is key
assert "session" in str(exc_info.value)
def test_has(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
assert not attrs.has(key)
attrs.set(key, "hello")
assert attrs.has(key)
def test_remove_existing(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
attrs.set(key, "hello")
result = attrs.remove(key)
assert result is True
assert not attrs.has(key)
def test_remove_non_existing(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
result = attrs.remove(key)
assert result is False
def test_set_if_absent_when_absent(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
result = attrs.set_if_absent(key, "first")
assert result == "first"
assert attrs.get(key) == "first"
def test_set_if_absent_when_present(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
attrs.set(key, "existing")
result = attrs.set_if_absent(key, "new")
assert result == "existing"
assert attrs.get(key) == "existing"
def test_clear(self):
key1: AttrKey[str] = AttrKey("key1", str)
key2: AttrKey[int] = AttrKey("key2", int)
attrs = AttrMap()
attrs.set(key1, "hello")
attrs.set(key2, 42)
attrs.clear()
assert len(attrs) == 0
assert not attrs.has(key1)
assert not attrs.has(key2)
def test_len(self):
key1: AttrKey[str] = AttrKey("key1", str)
key2: AttrKey[int] = AttrKey("key2", int)
attrs = AttrMap()
assert len(attrs) == 0
attrs.set(key1, "hello")
assert len(attrs) == 1
attrs.set(key2, 42)
assert len(attrs) == 2
def test_repr(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
attrs.set(key, "hello")
result = repr(attrs)
assert "AttrMap" in result
assert "session" in result
class TestAttrMapTypeValidation:
def test_set_with_wrong_type_raises(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
with pytest.raises(AttrMapTypeError) as exc_info:
attrs.set(key, 123) # type: ignore[arg-type]
assert exc_info.value.key is key
assert exc_info.value.expected_type is str
assert exc_info.value.actual_type is int
def test_set_with_validate_false_allows_wrong_type(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
attrs.set(key, 123, validate=False) # type: ignore[arg-type]
assert attrs.get(key) == 123
def test_set_if_absent_with_wrong_type_raises(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
with pytest.raises(AttrMapTypeError):
attrs.set_if_absent(key, 123) # type: ignore[arg-type]
def test_set_if_absent_with_validate_false_allows_wrong_type(self):
key: AttrKey[str] = AttrKey("session", str)
attrs = AttrMap()
attrs.set_if_absent(key, 123, validate=False) # type: ignore[arg-type]
assert attrs.get(key) == 123
def test_subclass_type_validation(self):
class Animal:
pass
class Dog(Animal):
pass
key: AttrKey[Animal] = AttrKey("animal", Animal)
attrs = AttrMap()
attrs.set(key, Dog())
assert isinstance(attrs.get(key), Dog)
class TestAttrMapIsolation:
def test_different_keys_with_same_name_are_isolated(self):
key_in_module_a: AttrKey[str] = AttrKey("config", str)
key_in_module_b: AttrKey[str] = AttrKey("config", str)
attrs = AttrMap()
attrs.set(key_in_module_a, "value_a")
attrs.set(key_in_module_b, "value_b")
assert attrs.get(key_in_module_a) == "value_a"
assert attrs.get(key_in_module_b) == "value_b"
def test_multiple_attr_maps_are_independent(self):
key: AttrKey[str] = AttrKey("session", str)
attrs1 = AttrMap()
attrs2 = AttrMap()
attrs1.set(key, "map1")
attrs2.set(key, "map2")
assert attrs1.get(key) == "map1"
assert attrs2.get(key) == "map2"