diff --git a/examples/user_postgres_example.py b/examples/user_postgres_example.py new file mode 100644 index 0000000..fcfc38e --- /dev/null +++ b/examples/user_postgres_example.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +""" +UserTablePostgres 使用示例 +""" + +import sys +import os +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from python_core.database.user_postgres import UserTablePostgres + +def main(): + """主函数""" + print("=== UserTablePostgres 使用示例 ===\n") + + # 创建用户表实例 + user_table = UserTablePostgres() + print("✅ 用户表初始化成功") + + # 创建用户 + print("\n1. 创建用户...") + user_id = user_table.create_user( + username="john_doe", + email="john@example.com", + password="secure_password_123", + display_name="John Doe" + ) + print(f"用户创建成功,ID: {user_id}") + + # 获取用户信息 + print("\n2. 获取用户信息...") + user = user_table.get_user_by_username("john_doe") + print(f"用户信息: {user['display_name']} ({user['email']})") + + # 用户认证 + print("\n3. 用户认证...") + auth_result = user_table.authenticate_user("john_doe", "secure_password_123") + if auth_result: + print(f"认证成功: {auth_result['username']}") + else: + print("认证失败") + + # 更新用户信息 + print("\n4. 更新用户信息...") + user_table.update_user(user_id, { + "display_name": "John Smith", + "avatar_url": "https://example.com/avatar.jpg" + }) + print("用户信息更新成功") + + # 获取用户统计 + print("\n5. 用户统计...") + user_count = user_table.get_user_count() + print(f"总用户数: {user_count}") + + # 搜索用户 + print("\n6. 搜索用户...") + search_results = user_table.search_users("john") + print(f"搜索到 {len(search_results)} 个用户") + + # 清理:删除测试用户 + print("\n7. 清理测试数据...") + user_table.delete_user(user_id) + print("测试用户已删除") + + print("\n✅ 示例完成") + +if __name__ == "__main__": + main() diff --git a/python_core/config.py b/python_core/config.py index 23b65eb..0ee8967 100644 --- a/python_core/config.py +++ b/python_core/config.py @@ -18,7 +18,7 @@ except ImportError: project_root = Path(__file__).parent.parent class Settings(BaseSettings): """Application settings with environment variable support.""" - + db: str = "postgres://dcb93e55a36f8056ce4ddc37c4ddd46d717859834d9d7475a080f57c25a99067:sk_qN1plssQ5n2gKU4ZNNG0_@db.prisma.io:5432/?sslmode=require" # Application Info app_name: str = "MixVideo V2" app_version: str = "2.0.0" diff --git a/python_core/database/user_postgres.py b/python_core/database/user_postgres.py new file mode 100644 index 0000000..e3246ca --- /dev/null +++ b/python_core/database/user_postgres.py @@ -0,0 +1,620 @@ +# 用户表 - PostgreSQL 版本 + +import hashlib +import uuid +from typing import Dict, List, Any, Optional +from datetime import datetime +from contextlib import contextmanager +from python_core.config import settings +from python_core.utils.logger import setup_logger + +# 尝试导入 psycopg2,如果失败则提供友好的错误信息 +try: + import psycopg2 + import psycopg2.extras + PSYCOPG2_AVAILABLE = True +except ImportError as e: + PSYCOPG2_AVAILABLE = False + PSYCOPG2_ERROR = str(e) + +logger = setup_logger(__name__) + +class UserTablePostgres: + """ + 用户表类 - PostgreSQL 版本 + 基于 PostgreSQL 数据库实现的用户管理功能 + """ + + def __init__(self): + # 检查 psycopg2 是否可用 + if not PSYCOPG2_AVAILABLE: + error_msg = f""" +PostgreSQL 驱动 psycopg2 未安装。请安装: + +方案1(推荐): + pip install psycopg2-binary + +方案2: + # Ubuntu/Debian + sudo apt-get install postgresql libpq-dev python3-dev + pip install psycopg2 + + # CentOS/RHEL + sudo yum install postgresql postgresql-devel python3-devel + pip install psycopg2 + + # macOS + brew install postgresql + pip install psycopg2 + +原始错误: {PSYCOPG2_ERROR} +""" + logger.error(error_msg) + raise ImportError(error_msg) + + self.db_url = settings.db + self.table_name = "users" + + # 初始化用户表 + self._init_user_table() + + @contextmanager + def _get_connection(self): + """获取数据库连接的上下文管理器""" + conn = None + try: + conn = psycopg2.connect(self.db_url) + yield conn + except Exception as e: + if conn: + conn.rollback() + logger.error(f"Database connection error: {e}") + raise e + finally: + if conn: + conn.close() + + def _init_user_table(self): + """初始化用户表""" + try: + with self._get_connection() as conn: + with conn.cursor() as cursor: + # 创建用户表(如果不存在) + create_table_sql = """ + CREATE TABLE IF NOT EXISTS users ( + id VARCHAR(36) PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + email VARCHAR(100) UNIQUE NOT NULL, + password_hash VARCHAR(64) NOT NULL, + display_name VARCHAR(100) NOT NULL, + avatar_url TEXT DEFAULT '', + is_active BOOLEAN DEFAULT TRUE, + last_login TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + """ + cursor.execute(create_table_sql) + + # 创建索引 + indexes = [ + "CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);", + "CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);", + "CREATE INDEX IF NOT EXISTS idx_users_is_active ON users(is_active);", + "CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at);" + ] + + for index_sql in indexes: + cursor.execute(index_sql) + + conn.commit() + logger.info("User table initialized") + + except Exception as e: + logger.error(f"Failed to initialize user table: {e}") + raise e + + def _hash_password(self, password: str) -> str: + """密码哈希""" + return hashlib.sha256(password.encode()).hexdigest() + + def _verify_password(self, password: str, password_hash: str) -> bool: + """验证密码""" + return self._hash_password(password) == password_hash + + def create_user(self, username: str, email: str, password: str, display_name: str = None) -> str: + """ + 创建用户 + + Args: + username: 用户名 + email: 邮箱 + password: 密码 + display_name: 显示名称 + + Returns: + 用户ID + """ + try: + with self._get_connection() as conn: + with conn.cursor() as cursor: + # 检查用户名是否已存在 + cursor.execute("SELECT id FROM users WHERE username = %s", (username,)) + if cursor.fetchone(): + raise ValueError(f"Username '{username}' already exists") + + # 检查邮箱是否已存在 + cursor.execute("SELECT id FROM users WHERE email = %s", (email,)) + if cursor.fetchone(): + raise ValueError(f"Email '{email}' already exists") + + # 生成用户ID + user_id = str(uuid.uuid4()) + + # 插入用户记录 + insert_sql = """ + INSERT INTO users (id, username, email, password_hash, display_name, avatar_url, is_active, created_at, updated_at) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) + """ + + now = datetime.now() + cursor.execute(insert_sql, ( + user_id, + username, + email, + self._hash_password(password), + display_name or username, + '', + True, + now, + now + )) + + conn.commit() + logger.info(f"Created user: {username} (ID: {user_id})") + return user_id + + except Exception as e: + logger.error(f"Failed to create user '{username}': {e}") + raise e + + def get_user_by_id(self, user_id: str) -> Optional[Dict[str, Any]]: + """ + 根据ID获取用户 + + Args: + user_id: 用户ID + + Returns: + 用户信息,如果不存在返回None + """ + try: + with self._get_connection() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cursor: + cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) + row = cursor.fetchone() + + if row: + return { + 'id': row['id'], + 'username': row['username'], + 'email': row['email'], + 'password_hash': row['password_hash'], + 'display_name': row['display_name'], + 'avatar_url': row['avatar_url'], + 'is_active': row['is_active'], + 'last_login': row['last_login'].isoformat() if row['last_login'] else None, + 'created_at': row['created_at'].isoformat() if row['created_at'] else None, + 'updated_at': row['updated_at'].isoformat() if row['updated_at'] else None + } + return None + + except Exception as e: + logger.error(f"Failed to get user by ID '{user_id}': {e}") + return None + + def get_user_by_username(self, username: str) -> Optional[Dict[str, Any]]: + """ + 根据用户名获取用户 + + Args: + username: 用户名 + + Returns: + 用户信息,如果不存在返回None + """ + try: + with self._get_connection() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cursor: + cursor.execute("SELECT * FROM users WHERE username = %s", (username,)) + row = cursor.fetchone() + + if row: + return { + 'id': row['id'], + 'username': row['username'], + 'email': row['email'], + 'password_hash': row['password_hash'], + 'display_name': row['display_name'], + 'avatar_url': row['avatar_url'], + 'is_active': row['is_active'], + 'last_login': row['last_login'].isoformat() if row['last_login'] else None, + 'created_at': row['created_at'].isoformat() if row['created_at'] else None, + 'updated_at': row['updated_at'].isoformat() if row['updated_at'] else None + } + return None + + except Exception as e: + logger.error(f"Failed to get user by username '{username}': {e}") + return None + + def get_user_by_email(self, email: str) -> Optional[Dict[str, Any]]: + """ + 根据邮箱获取用户 + + Args: + email: 邮箱 + + Returns: + 用户信息,如果不存在返回None + """ + try: + with self._get_connection() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cursor: + cursor.execute("SELECT * FROM users WHERE email = %s", (email,)) + row = cursor.fetchone() + + if row: + return { + 'id': row['id'], + 'username': row['username'], + 'email': row['email'], + 'password_hash': row['password_hash'], + 'display_name': row['display_name'], + 'avatar_url': row['avatar_url'], + 'is_active': row['is_active'], + 'last_login': row['last_login'].isoformat() if row['last_login'] else None, + 'created_at': row['created_at'].isoformat() if row['created_at'] else None, + 'updated_at': row['updated_at'].isoformat() if row['updated_at'] else None + } + return None + + except Exception as e: + logger.error(f"Failed to get user by email '{email}': {e}") + return None + + def authenticate_user(self, username_or_email: str, password: str) -> Optional[Dict[str, Any]]: + """ + 用户认证 + + Args: + username_or_email: 用户名或邮箱 + password: 密码 + + Returns: + 认证成功返回用户信息,失败返回None + """ + try: + # 尝试按用户名查找 + user = self.get_user_by_username(username_or_email) + + # 如果按用户名找不到,尝试按邮箱查找 + if not user: + user = self.get_user_by_email(username_or_email) + + # 验证用户存在且密码正确 + if user and self._verify_password(password, user["password_hash"]): + # 更新最后登录时间 + self.update_last_login(user["id"]) + + # 返回用户信息(不包含密码哈希) + user_info = user.copy() + del user_info["password_hash"] + + logger.info(f"User authenticated: {user['username']}") + return user_info + + logger.warning(f"Authentication failed for: {username_or_email}") + return None + + except Exception as e: + logger.error(f"Authentication error for '{username_or_email}': {e}") + return None + + def update_user(self, user_id: str, updates: Dict[str, Any]) -> bool: + """ + 更新用户信息 + + Args: + user_id: 用户ID + updates: 要更新的字段 + + Returns: + 更新成功返回True + """ + try: + with self._get_connection() as conn: + with conn.cursor() as cursor: + # 检查用户是否存在 + cursor.execute("SELECT id FROM users WHERE id = %s", (user_id,)) + if not cursor.fetchone(): + logger.warning(f"User not found: {user_id}") + return False + + # 移除不应该直接更新的字段 + protected_fields = ["id", "created_at", "password_hash"] + filtered_updates = {k: v for k, v in updates.items() if k not in protected_fields} + + if not filtered_updates: + logger.warning("No valid fields to update") + return False + + # 添加更新时间 + filtered_updates["updated_at"] = datetime.now() + + # 构建更新SQL + set_clauses = [] + values = [] + for key, value in filtered_updates.items(): + set_clauses.append(f"{key} = %s") + values.append(value) + + values.append(user_id) # WHERE条件的参数 + + update_sql = f"UPDATE users SET {', '.join(set_clauses)} WHERE id = %s" + cursor.execute(update_sql, values) + + conn.commit() + + if cursor.rowcount > 0: + logger.info(f"Updated user: {user_id}") + return True + else: + logger.warning(f"No rows updated for user: {user_id}") + return False + + except Exception as e: + logger.error(f"Failed to update user '{user_id}': {e}") + return False + + def update_password(self, user_id: str, new_password: str) -> bool: + """ + 更新用户密码 + + Args: + user_id: 用户ID + new_password: 新密码 + + Returns: + 更新成功返回True + """ + try: + with self._get_connection() as conn: + with conn.cursor() as cursor: + # 检查用户是否存在 + cursor.execute("SELECT id FROM users WHERE id = %s", (user_id,)) + if not cursor.fetchone(): + logger.warning(f"User not found: {user_id}") + return False + + # 更新密码 + update_sql = "UPDATE users SET password_hash = %s, updated_at = %s WHERE id = %s" + cursor.execute(update_sql, ( + self._hash_password(new_password), + datetime.now(), + user_id + )) + + conn.commit() + + if cursor.rowcount > 0: + logger.info(f"Password updated for user: {user_id}") + return True + else: + logger.warning(f"No rows updated for user: {user_id}") + return False + + except Exception as e: + logger.error(f"Failed to update password for user '{user_id}': {e}") + return False + + def update_last_login(self, user_id: str) -> bool: + """ + 更新最后登录时间 + + Args: + user_id: 用户ID + + Returns: + 更新成功返回True + """ + try: + return self.update_user(user_id, { + "last_login": datetime.now() + }) + + except Exception as e: + logger.error(f"Failed to update last login for user '{user_id}': {e}") + return False + + def deactivate_user(self, user_id: str) -> bool: + """ + 停用用户 + + Args: + user_id: 用户ID + + Returns: + 操作成功返回True + """ + try: + return self.update_user(user_id, {"is_active": False}) + + except Exception as e: + logger.error(f"Failed to deactivate user '{user_id}': {e}") + return False + + def activate_user(self, user_id: str) -> bool: + """ + 激活用户 + + Args: + user_id: 用户ID + + Returns: + 操作成功返回True + """ + try: + return self.update_user(user_id, {"is_active": True}) + + except Exception as e: + logger.error(f"Failed to activate user '{user_id}': {e}") + return False + + def delete_user(self, user_id: str) -> bool: + """ + 删除用户 + + Args: + user_id: 用户ID + + Returns: + 删除成功返回True + """ + try: + with self._get_connection() as conn: + with conn.cursor() as cursor: + cursor.execute("DELETE FROM users WHERE id = %s", (user_id,)) + conn.commit() + + if cursor.rowcount > 0: + logger.info(f"Deleted user: {user_id}") + return True + else: + logger.warning(f"No user found to delete: {user_id}") + return False + + except Exception as e: + logger.error(f"Failed to delete user '{user_id}': {e}") + return False + + def get_all_users(self, include_inactive: bool = False, limit: int = 100) -> List[Dict[str, Any]]: + """ + 获取所有用户 + + Args: + include_inactive: 是否包含非活跃用户 + limit: 最大返回数量 + + Returns: + 用户列表 + """ + try: + with self._get_connection() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cursor: + if include_inactive: + sql = "SELECT * FROM users ORDER BY created_at DESC LIMIT %s" + cursor.execute(sql, (limit,)) + else: + sql = "SELECT * FROM users WHERE is_active = TRUE ORDER BY created_at DESC LIMIT %s" + cursor.execute(sql, (limit,)) + + rows = cursor.fetchall() + users = [] + + for row in rows: + user_info = { + 'id': row['id'], + 'username': row['username'], + 'email': row['email'], + 'display_name': row['display_name'], + 'avatar_url': row['avatar_url'], + 'is_active': row['is_active'], + 'last_login': row['last_login'].isoformat() if row['last_login'] else None, + 'created_at': row['created_at'].isoformat() if row['created_at'] else None, + 'updated_at': row['updated_at'].isoformat() if row['updated_at'] else None + } + # 不包含密码哈希 + users.append(user_info) + + return users + + except Exception as e: + logger.error(f"Failed to get all users: {e}") + return [] + + def get_user_count(self, include_inactive: bool = False) -> int: + """ + 获取用户数量 + + Args: + include_inactive: 是否包含非活跃用户 + + Returns: + 用户数量 + """ + try: + with self._get_connection() as conn: + with conn.cursor() as cursor: + if include_inactive: + cursor.execute("SELECT COUNT(*) FROM users") + else: + cursor.execute("SELECT COUNT(*) FROM users WHERE is_active = TRUE") + + result = cursor.fetchone() + return result[0] if result else 0 + + except Exception as e: + logger.error(f"Failed to get user count: {e}") + return 0 + + def search_users(self, query: str, limit: int = 50) -> List[Dict[str, Any]]: + """ + 搜索用户 + + Args: + query: 搜索关键词(匹配用户名、邮箱、显示名称) + limit: 最大返回数量 + + Returns: + 匹配的用户列表 + """ + try: + with self._get_connection() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cursor: + search_pattern = f"%{query}%" + sql = """ + SELECT * FROM users + WHERE (username ILIKE %s OR email ILIKE %s OR display_name ILIKE %s) + ORDER BY created_at DESC + LIMIT %s + """ + cursor.execute(sql, (search_pattern, search_pattern, search_pattern, limit)) + + rows = cursor.fetchall() + users = [] + + for row in rows: + user_info = { + 'id': row['id'], + 'username': row['username'], + 'email': row['email'], + 'display_name': row['display_name'], + 'avatar_url': row['avatar_url'], + 'is_active': row['is_active'], + 'last_login': row['last_login'].isoformat() if row['last_login'] else None, + 'created_at': row['created_at'].isoformat() if row['created_at'] else None, + 'updated_at': row['updated_at'].isoformat() if row['updated_at'] else None + } + # 不包含密码哈希 + users.append(user_info) + + return users + + except Exception as e: + logger.error(f"Failed to search users with query '{query}': {e}") + return [] + + +# 创建全局用户表实例 +user_table = UserTablePostgres() diff --git a/python_core/kv.py b/python_core/kv.py index 639384a..794b6d8 100644 --- a/python_core/kv.py +++ b/python_core/kv.py @@ -3,9 +3,53 @@ from python_core.config import settings import httpx from python_core.utils.logger import setup_logger from typing import Dict +import time +import random +from functools import wraps logger = setup_logger(__name__) +def retry_on_timeout(max_retries=3, base_delay=1.0, max_delay=10.0, backoff_factor=2.0): + """ + 重试装饰器,用于处理网络超时等临时性错误 + + Args: + max_retries: 最大重试次数 + base_delay: 基础延迟时间(秒) + max_delay: 最大延迟时间(秒) + backoff_factor: 退避因子 + """ + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + last_exception = None + + for attempt in range(max_retries + 1): + try: + return func(*args, **kwargs) + except (httpx.ConnectTimeout, httpx.ReadTimeout, httpx.TimeoutException) as e: + last_exception = e + if attempt == max_retries: + logger.error(f"Function {func.__name__} failed after {max_retries} retries: {e}") + raise e + + # 计算延迟时间(指数退避 + 随机抖动) + delay = min(base_delay * (backoff_factor ** attempt), max_delay) + jitter = random.uniform(0, delay * 0.1) # 10% 随机抖动 + total_delay = delay + jitter + + logger.warning(f"Function {func.__name__} attempt {attempt + 1} failed with timeout: {e}. Retrying in {total_delay:.2f}s...") + time.sleep(total_delay) + except Exception as e: + # 对于非超时错误,直接抛出 + logger.error(f"Function {func.__name__} failed with non-timeout error: {e}") + raise e + + # 如果所有重试都失败了,抛出最后一个异常 + raise last_exception + return wrapper + return decorator + class Kv: kv: Dict[str, str] def __init__(self): @@ -15,6 +59,7 @@ class Kv: self.base_url = f"https://api.cloudflare.com/client/v4/accounts/{self.cf_account_id}/storage/kv/namespaces/{self.cf_kv_id}" self.headers = {"Authorization": f"Bearer {self.cf_kv_api_token}"} + @retry_on_timeout(max_retries=3, base_delay=1.0) def get(self, key: str, default=None): """ 获取单个键的值 @@ -27,7 +72,7 @@ class Kv: 键对应的值,如果不存在则返回default """ try: - with httpx.Client() as client: + with httpx.Client(timeout=30.0) as client: response = client.get( f"{self.base_url}/values/{key}", headers=self.headers @@ -48,6 +93,7 @@ class Kv: logger.error(f"An unexpected error occurred while getting key '{key}': {e}") raise e + @retry_on_timeout(max_retries=3, base_delay=1.0) def set(self, key: str, value: str): """ 设置单个键值对 @@ -62,7 +108,7 @@ class Kv: 操作结果 """ try: - with httpx.Client() as client: + with httpx.Client(timeout=30.0) as client: params = {} response = client.put( @@ -83,6 +129,7 @@ class Kv: logger.error(f"An unexpected error occurred while setting key '{key}': {e}") raise e + @retry_on_timeout(max_retries=3, base_delay=1.0) def remove(self, key: str): """ 删除单个键 @@ -94,7 +141,7 @@ class Kv: 操作结果 """ try: - with httpx.Client() as client: + with httpx.Client(timeout=30.0) as client: response = client.delete( f"{self.base_url}/values/{key}", headers=self.headers @@ -115,6 +162,7 @@ class Kv: logger.error(f"An unexpected error occurred while removing key '{key}': {e}") raise e + @retry_on_timeout(max_retries=3, base_delay=1.0) def sets(self, caches: Dict[str, str]): """ 批量设置多个键值对 @@ -128,7 +176,7 @@ class Kv: 操作结果 """ try: - with httpx.Client() as client: + with httpx.Client(timeout=60.0) as client: # 批量操作使用更长的超时时间 bulk_data = [] for key, value in caches.items(): item = { @@ -178,6 +226,7 @@ class Kv: logger.error(f"An error occurred while bulk getting keys from cloudflare: {e}") raise e + @retry_on_timeout(max_retries=3, base_delay=1.0) def removes(self, keys: list[str]): """ 批量删除多个键 @@ -189,7 +238,7 @@ class Kv: 操作结果 """ try: - with httpx.Client() as client: + with httpx.Client(timeout=60.0) as client: # 批量操作使用更长的超时时间 response = client.post( f"{self.base_url}/bulk/delete", headers=self.headers, @@ -207,6 +256,7 @@ class Kv: logger.error(f"An unexpected error occurred while bulk removing keys: {e}") raise e + @retry_on_timeout(max_retries=3, base_delay=1.0) def exists(self, key: str) -> bool: """ 检查键是否存在 @@ -218,7 +268,7 @@ class Kv: 如果键存在返回True,否则返回False """ try: - with httpx.Client() as client: + with httpx.Client(timeout=30.0) as client: response = client.get( f"{self.base_url}/values/{key}", headers=self.headers @@ -231,6 +281,7 @@ class Kv: except Exception: return False + @retry_on_timeout(max_retries=3, base_delay=1.0) def list_keys(self, prefix: str = None, limit: int = 1000, cursor: str = None): """ 列出KV存储中的键 @@ -244,7 +295,7 @@ class Kv: 包含键列表和分页信息的字典 """ try: - with httpx.Client() as client: + with httpx.Client(timeout=30.0) as client: params = {"limit": limit} if prefix: params["prefix"] = prefix @@ -268,6 +319,7 @@ class Kv: logger.error(f"An unexpected error occurred while listing keys: {e}") raise e + @retry_on_timeout(max_retries=3, base_delay=1.0) def get_metadata(self, key: str): """ 获取键的元数据 @@ -279,7 +331,7 @@ class Kv: 键的元数据信息 """ try: - with httpx.Client() as client: + with httpx.Client(timeout=30.0) as client: response = client.get( f"{self.base_url}/metadata/{key}", headers=self.headers diff --git a/python_core/requirements.txt b/python_core/requirements.txt index 8214c73..116b2c1 100644 --- a/python_core/requirements.txt +++ b/python_core/requirements.txt @@ -31,4 +31,5 @@ langgraph json-rpc langchain_core langchain_anthropic -PyJWT \ No newline at end of file +PyJWT +psycopg2-binary \ No newline at end of file diff --git a/python_core/services/auth_service.py b/python_core/services/auth_service.py index f15d88e..a51676f 100644 --- a/python_core/services/auth_service.py +++ b/python_core/services/auth_service.py @@ -7,7 +7,7 @@ import re from typing import Optional, Dict, Any from dataclasses import dataclass -from python_core.database.user import user_table +from python_core.database.user_postgres import user_table from python_core.utils.jwt_auth import generate_access_token, verify_access_token from python_core.utils.logger import setup_logger