| """User accounts + persistent per-user chat history for AgAdvisor. |
| |
| Self-contained package so it can be unit-tested without Streamlit, Hugging Face, |
| or the RAG stack: |
| |
| - ``store`` β SQLite schema + user-scoped CRUD (users, chats, messages, usage) |
| - ``auth`` β bcrypt password hashing, signup/login, brute-force lockout |
| - ``session`` β HMAC-signed session tokens for cookie-based "stay logged in" |
| - ``hf_sync`` β mirror the SQLite DB to a private Hugging Face Dataset (durable |
| storage on ephemeral Spaces); no-ops locally when no token is set |
| - ``ui`` β Streamlit login/sign-up gate (imported lazily; needs streamlit) |
| |
| Submodules are imported lazily via ``__getattr__`` so that using only ``store`` |
| or ``session`` does not pull in optional deps (``bcrypt``) that ``auth`` needs. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import importlib |
| from typing import Any |
|
|
| __all__ = [ |
| "AccountStore", |
| "DuplicateUserError", |
| "AuthError", |
| "signup", |
| "login", |
| "hash_password", |
| "verify_password", |
| "issue_token", |
| "validate_token", |
| ] |
|
|
| _EXPORTS = { |
| "AccountStore": ("store", "AccountStore"), |
| "DuplicateUserError": ("store", "DuplicateUserError"), |
| "AuthError": ("auth", "AuthError"), |
| "signup": ("auth", "signup"), |
| "login": ("auth", "login"), |
| "hash_password": ("auth", "hash_password"), |
| "verify_password": ("auth", "verify_password"), |
| "issue_token": ("session", "issue_token"), |
| "validate_token": ("session", "validate_token"), |
| } |
|
|
|
|
| def __getattr__(name: str) -> Any: |
| if name in _EXPORTS: |
| module_name, attr = _EXPORTS[name] |
| module = importlib.import_module(f"{__name__}.{module_name}") |
| return getattr(module, attr) |
| raise AttributeError(f"module {__name__!r} has no attribute {name!r}") |
|
|