-
-
Notifications
You must be signed in to change notification settings - Fork 41
Orb P0 S2a: tinyagentos/agent_avatars.py (move avatar slug + dir out of routes/auth.py, add avatar_hash) #3517
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ### Changed | ||
|
|
||
| - Extracted avatar slug and avatar directory logic from `tinyagentos/routes/auth.py` into a new `tinyagentos/agent_avatars.py` module, and added `avatar_source_path` and `avatar_hash` helpers. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| """Tests for tinyagentos.agent_avatars module (avatar_source_path + avatar_hash).""" | ||
| from __future__ import annotations | ||
|
|
||
| import hashlib | ||
| from pathlib import Path | ||
|
|
||
| import pytest | ||
|
|
||
| from tinyagentos import agent_avatars as aa | ||
| from tinyagentos.agent_avatars import avatar_hash, avatar_source_path | ||
| from tinyagentos.routes.auth import _avatar_slug | ||
|
|
||
|
|
||
| class TestAvatarSourcePathAndHash: | ||
|
|
||
| def test_no_jpg_returns_none(self, tmp_path, monkeypatch): | ||
| monkeypatch.setattr(aa, "LOCK_AVATAR_DIR", str(tmp_path)) | ||
| assert avatar_source_path("Some Agent") is None | ||
| assert avatar_hash("Some Agent") is None | ||
|
|
||
| def test_existing_jpg_returns_path_and_hash(self, tmp_path, monkeypatch): | ||
| monkeypatch.setattr(aa, "LOCK_AVATAR_DIR", str(tmp_path)) | ||
| name = "Some Agent" | ||
| slug = _avatar_slug(name) | ||
| (tmp_path / f"{slug}.jpg").write_bytes(b"one") | ||
| assert avatar_source_path(name) == tmp_path / f"{slug}.jpg" | ||
| h = avatar_hash(name) | ||
| assert isinstance(h, str) | ||
| assert len(h) == 16 | ||
| assert h == hashlib.sha256(b"one").hexdigest()[:16] | ||
|
|
||
| def test_hash_changes_when_file_changes(self, tmp_path, monkeypatch): | ||
| monkeypatch.setattr(aa, "LOCK_AVATAR_DIR", str(tmp_path)) | ||
| name = "Some Agent" | ||
| slug = _avatar_slug(name) | ||
| p = tmp_path / f"{slug}.jpg" | ||
| p.write_bytes(b"one") | ||
| first = avatar_hash(name) | ||
| p.write_bytes(b"two") | ||
| second = avatar_hash(name) | ||
| assert first != second | ||
|
|
||
| def test_imported_slug_matches_module_slug(self): | ||
| name = "Some Agent" | ||
| assert _avatar_slug(name) == aa._avatar_slug(name) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import hashlib | ||
| import os | ||
| from pathlib import Path | ||
|
|
||
| LOCK_AVATAR_DIR = os.environ.get("TAOS_LOCK_AVATAR_DIR", "/var/lib/taos/lock-avatars") | ||
|
|
||
|
|
||
| def _avatar_slug(name: str) -> str: | ||
| """Slug for an agent name, restricted to characters that cannot traverse. | ||
|
|
||
| Anything outside [a-z0-9-] is dropped rather than escaped: this value is | ||
| used to build a filesystem path, so a conservative whitelist is the control | ||
| that keeps "../" and absolute paths out, not a sanitiser that tries to spot | ||
| bad input. | ||
| """ | ||
| out = [] | ||
| for ch in name.strip().lower(): | ||
| if ch.isalnum() and ch.isascii(): | ||
| out.append(ch) | ||
| elif out and out[-1] != "-": | ||
| out.append("-") | ||
| return "".join(out).strip("-") | ||
|
|
||
|
|
||
| def avatar_source_path(name: str) -> Path | None: | ||
| """Path to the avatar image file for *name*, or None if it is not installed.""" | ||
| path = Path(LOCK_AVATAR_DIR) / f"{_avatar_slug(name)}.jpg" | ||
| return path if path.is_file() else None | ||
|
|
||
|
|
||
| def avatar_hash(name: str) -> str | None: | ||
| """SHA-256 hex of the avatar image bytes, first 16 chars, or None.""" | ||
| path = avatar_source_path(name) | ||
| if path is None: | ||
| return None | ||
| return hashlib.sha256(path.read_bytes()).hexdigest()[:16] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: Unhandled
Reply with |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WARNING: Missing docstring for
LOCK_AVATAR_DIRThe
#:Sphinx-style docstring that explained its purpose and overridability was dropped when the definitions were moved fromauth.py. This constant is environment-overridable and its documentation should travel with it.Reply with
@kilocode-bot fix itto have Kilo Code address this issue.