refactor: 重构 schemas 结构,统一枚举定义
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
<item android:drawable="@color/launchBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
<item android:drawable="@color/launchBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
<color name="launchBackground">#EFF8FF</color>
|
||||
</resources>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -1,23 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../core/theme/design_tokens.dart';
|
||||
import '../../../../shared/widgets/app_loading_indicator.dart';
|
||||
|
||||
class AuthBootScreen extends StatelessWidget {
|
||||
const AuthBootScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
backgroundColor: AppColors.authBackgroundTop,
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFEFF8FF),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: AppLoadingIndicator(
|
||||
variant: AppLoadingVariant.surface,
|
||||
size: 28,
|
||||
strokeWidth: 2.5,
|
||||
color: AppColors.authPrimaryButton,
|
||||
trackColor: AppColors.authPrimaryButtonDisabled,
|
||||
child: Image.asset(
|
||||
'assets/branding/assistant_octopus_foreground.png',
|
||||
width: 260,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
enum ScheduleSourceType { manual, imported, agentGenerated }
|
||||
|
||||
enum ScheduleStatus { active, completed, canceled, archived }
|
||||
enum ScheduleStatus { active, archived }
|
||||
|
||||
class ScheduleItemModel {
|
||||
final String id;
|
||||
@@ -279,9 +279,7 @@ ScheduleSourceType _sourceTypeFromApi(String? raw) {
|
||||
ScheduleStatus _statusFromApi(String? raw) {
|
||||
switch (raw) {
|
||||
case 'completed':
|
||||
return ScheduleStatus.completed;
|
||||
case 'canceled':
|
||||
return ScheduleStatus.canceled;
|
||||
case 'archived':
|
||||
return ScheduleStatus.archived;
|
||||
case 'active':
|
||||
@@ -294,10 +292,6 @@ String _statusToApi(ScheduleStatus status) {
|
||||
switch (status) {
|
||||
case ScheduleStatus.active:
|
||||
return 'active';
|
||||
case ScheduleStatus.completed:
|
||||
return 'completed';
|
||||
case ScheduleStatus.canceled:
|
||||
return 'canceled';
|
||||
case ScheduleStatus.archived:
|
||||
return 'archived';
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- assets/images/
|
||||
- assets/branding/
|
||||
|
||||
flutter_launcher_icons:
|
||||
android: true
|
||||
|
||||
@@ -45,6 +45,10 @@ This file governs `backend/**` only. Keep it minimal, enforceable, and non-dupli
|
||||
- Use service-role DB access only in backend.
|
||||
- Soft delete uses `deleted_at`; reads must exclude deleted records by default.
|
||||
- Alembic is the only schema migration source of truth.
|
||||
- Database migrations use `./infra/scripts/dev-migrate.sh`:
|
||||
- `migrate` - run migrations only
|
||||
- `init-data` - seed data only
|
||||
- `bootstrap` - migrate + init-data
|
||||
|
||||
## Agent Runtime & Tools
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""converge schedule item status to active or archived
|
||||
|
||||
Revision ID: 202603250001
|
||||
Revises: 202603240001
|
||||
Create Date: 2026-03-25 10:00:00
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision: str = "202603250001"
|
||||
down_revision: Union[str, Sequence[str], None] = "202603240001"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute(
|
||||
"""
|
||||
UPDATE public.schedule_items
|
||||
SET status = 'archived'
|
||||
WHERE status IN ('completed', 'canceled')
|
||||
"""
|
||||
)
|
||||
op.execute(
|
||||
"""
|
||||
ALTER TABLE public.schedule_items
|
||||
DROP CONSTRAINT IF EXISTS chk_schedule_item_status
|
||||
"""
|
||||
)
|
||||
op.execute(
|
||||
"""
|
||||
ALTER TABLE public.schedule_items
|
||||
ADD CONSTRAINT chk_schedule_item_status
|
||||
CHECK (status IN ('active', 'archived'))
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute(
|
||||
"""
|
||||
ALTER TABLE public.schedule_items
|
||||
DROP CONSTRAINT IF EXISTS chk_schedule_item_status
|
||||
"""
|
||||
)
|
||||
op.execute(
|
||||
"""
|
||||
ALTER TABLE public.schedule_items
|
||||
ADD CONSTRAINT chk_schedule_item_status
|
||||
CHECK (status IN ('active', 'completed', 'canceled', 'archived'))
|
||||
"""
|
||||
)
|
||||
@@ -7,8 +7,9 @@ from uuid import UUID
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from models.agent_chat_message import AgentChatMessage, AgentChatMessageRole
|
||||
from models.agent_chat_session import AgentChatSession, AgentChatSessionStatus
|
||||
from models.agent_chat_message import AgentChatMessage
|
||||
from models.agent_chat_session import AgentChatSession
|
||||
from schemas.enums import AgentChatMessageRole, AgentChatSessionStatus
|
||||
|
||||
|
||||
class MessageRepository:
|
||||
|
||||
@@ -6,12 +6,11 @@ from uuid import UUID
|
||||
|
||||
from core.agentscope.events.persistence import MessageRepository, SessionRepository
|
||||
from core.logging import get_logger
|
||||
from models.agent_chat_message import AgentChatMessageRole
|
||||
from models.agent_chat_session import AgentChatSessionStatus
|
||||
from schemas.enums import AgentChatMessageRole, AgentChatSessionStatus
|
||||
from schemas.agent.system_agent import AgentType
|
||||
from schemas.agent.runtime_models import AgentOutput, RouterAgentOutput, ToolAgentOutput
|
||||
from schemas.agent.visibility import SystemVisibilityBit, bit_mask
|
||||
from schemas.messages.chat_message import AgentChatMessageMetadata
|
||||
from schemas.domain.chat_message import AgentChatMessageMetadata
|
||||
|
||||
|
||||
class EventStore(Protocol):
|
||||
|
||||
@@ -9,7 +9,7 @@ from uuid import UUID
|
||||
import redis.asyncio as redis
|
||||
from core.config.settings import config
|
||||
from core.logging import get_logger
|
||||
from schemas.user import (
|
||||
from schemas.shared.user import (
|
||||
UserContext,
|
||||
parse_profile_settings,
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||
|
||||
|
||||
def _wrap_section(section: str, content: str) -> str:
|
||||
|
||||
@@ -17,8 +17,8 @@ from core.agentscope.prompts.route_prompt import build_frontend_route_prompt
|
||||
from core.agentscope.prompts.tool_prompt import build_tools_prompt
|
||||
from schemas.agent.system_agent import AgentType, SystemAgentLLMConfig
|
||||
from schemas.agent.forwarded_props import ClientTimeContext
|
||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from schemas.user.context import UserContext
|
||||
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from schemas.shared.user import UserContext
|
||||
|
||||
|
||||
def _wrap_section(section: str, content: str) -> str:
|
||||
|
||||
@@ -6,9 +6,9 @@ from ag_ui.core.types import RunAgentInput
|
||||
from agentscope.message import Msg
|
||||
from core.agentscope.runtime.runner import AgentScopeRunner
|
||||
from core.logging import get_logger
|
||||
from schemas.automation import RuntimeConfig
|
||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from schemas.user import UserContext
|
||||
from schemas.domain.automation import RuntimeConfig
|
||||
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from schemas.shared.user import UserContext
|
||||
|
||||
logger = get_logger("core.agentscope.runtime.orchestrator")
|
||||
|
||||
|
||||
@@ -40,9 +40,9 @@ from schemas.agent.system_agent import (
|
||||
AgentType,
|
||||
SystemAgentLLMConfig,
|
||||
)
|
||||
from schemas.automation import RuntimeConfig
|
||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from schemas.user import UserContext
|
||||
from schemas.domain.automation import RuntimeConfig
|
||||
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from schemas.shared.user import UserContext
|
||||
from services.litellm.service import LiteLLMService
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -20,13 +20,13 @@ from core.config.settings import config
|
||||
from core.db.session import AsyncSessionLocal
|
||||
from core.logging import get_logger
|
||||
from core.taskiq.app import worker_agent_broker, worker_automation_broker
|
||||
from schemas.automation import MessageContextConfig, RuntimeConfig
|
||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from schemas.messages.chat_message import (
|
||||
from schemas.domain.automation import MessageContextConfig, RuntimeConfig
|
||||
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from schemas.domain.chat_message import (
|
||||
AgentChatMessageMetadata,
|
||||
extract_user_message_attachments,
|
||||
)
|
||||
from schemas.user import UserContext
|
||||
from schemas.shared.user import UserContext
|
||||
from services.base.redis import get_or_init_redis_client
|
||||
from services.base.supabase import supabase_service
|
||||
from v1.agent.repository import AgentRepository
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Any, Protocol
|
||||
|
||||
from schemas.agent.visibility import SystemVisibilityBit, bit_mask
|
||||
|
||||
from schemas.automation import ContextWindowMode, MessageContextConfig
|
||||
from schemas.domain.automation import ContextWindowMode, MessageContextConfig
|
||||
|
||||
|
||||
_DEFAULT_CONTEXT_WINDOW_USER_MESSAGES = 20
|
||||
|
||||
@@ -84,7 +84,7 @@ class CalendarWriteOperation(BaseModel):
|
||||
le=10080,
|
||||
description="Reminder minutes before event start.",
|
||||
)
|
||||
status: Literal["active", "completed", "canceled", "archived"] | None = Field(
|
||||
status: Literal["active", "archived"] | None = Field(
|
||||
default=None,
|
||||
description="Optional status for update action.",
|
||||
)
|
||||
@@ -163,6 +163,10 @@ async def calendar_read(
|
||||
) -> ToolResponse:
|
||||
"""Read calendar events with optional keyword filtering and pagination.
|
||||
|
||||
Status semantics for returned events:
|
||||
- active: Event is actionable.
|
||||
- archived: Event is historical/expired and should not trigger reminders.
|
||||
|
||||
Args:
|
||||
query: Optional keyword used to filter events by text fields.
|
||||
page: Page number starting from 1.
|
||||
|
||||
@@ -15,9 +15,9 @@ from core.agentscope.tools.utils.tool_response_builder import (
|
||||
build_error_output,
|
||||
build_tool_response,
|
||||
)
|
||||
from models.memories import MemoryType
|
||||
from schemas.enums import MemoryType
|
||||
from schemas.agent.runtime_models import ErrorInfo, ToolAgentOutput, ToolStatus
|
||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||
|
||||
|
||||
class MemoryWriteArgs(BaseModel):
|
||||
|
||||
@@ -46,6 +46,9 @@ def create_schedule_service(
|
||||
def schedule_event_to_dict(event: object) -> dict[str, Any]:
|
||||
event_id = str(getattr(event, "id"))
|
||||
metadata = getattr(event, "metadata", None)
|
||||
status_value = getattr(event, "status", None)
|
||||
if hasattr(status_value, "value"):
|
||||
status_value = getattr(status_value, "value")
|
||||
location_value = getattr(metadata, "location", None)
|
||||
color_value = getattr(metadata, "color", None) or "#4F46E5"
|
||||
reminder_minutes_value = getattr(metadata, "reminder_minutes", None)
|
||||
@@ -58,6 +61,7 @@ def schedule_event_to_dict(event: object) -> dict[str, Any]:
|
||||
if getattr(event, "end_at") is not None
|
||||
else None,
|
||||
"timezone": getattr(event, "timezone"),
|
||||
"status": status_value,
|
||||
"location": location_value,
|
||||
"color": color_value,
|
||||
"reminderMinutes": reminder_minutes_value,
|
||||
|
||||
@@ -5,7 +5,7 @@ from uuid import UUID
|
||||
|
||||
from core.config.settings import config
|
||||
from core.logging import get_logger
|
||||
from schemas.automation import RuntimeConfig
|
||||
from schemas.domain.automation import RuntimeConfig
|
||||
|
||||
logger = get_logger("core.automation.scheduler")
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from models.automation_jobs import AutomationJob
|
||||
from models.group_members import GroupMember
|
||||
from models.groups import Group
|
||||
from models.inbox_messages import InboxMessage
|
||||
from models.invite_code import InviteCode, InviteCodeStatus
|
||||
from models.invite_code import InviteCode
|
||||
from models.llm import Llm
|
||||
from models.llm_factory import LlmFactory
|
||||
from models.memories import Memory
|
||||
@@ -16,6 +16,7 @@ from models.schedule_subscriptions import ScheduleSubscription
|
||||
from models.system_agents import SystemAgents
|
||||
from models.todos import Todo
|
||||
from models.todo_sources import TodoSource
|
||||
from schemas.enums import InviteCodeStatus
|
||||
|
||||
__all__ = [
|
||||
"AgentChatMessage",
|
||||
|
||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
import uuid
|
||||
from enum import Enum
|
||||
|
||||
from sqlalchemy import (
|
||||
BigInteger,
|
||||
@@ -19,13 +18,9 @@ from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||
from schemas.enums import AgentChatMessageRole
|
||||
|
||||
|
||||
class AgentChatMessageRole(str, Enum):
|
||||
USER = "user"
|
||||
ASSISTANT = "assistant"
|
||||
SYSTEM = "system"
|
||||
TOOL = "tool"
|
||||
__all__ = ["AgentChatMessage", "AgentChatMessageRole"]
|
||||
|
||||
|
||||
class AgentChatMessage(TimestampMixin, SoftDeleteMixin, Base):
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
import uuid
|
||||
from enum import Enum
|
||||
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
@@ -19,18 +18,9 @@ from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||
from schemas.enums import AgentChatSessionStatus, SessionType
|
||||
|
||||
|
||||
class AgentChatSessionStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
RUNNING = "running"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
|
||||
|
||||
class SessionType(str, Enum):
|
||||
CHAT = "chat"
|
||||
AUTOMATION = "automation"
|
||||
__all__ = ["AgentChatSession", "AgentChatSessionStatus", "SessionType"]
|
||||
|
||||
|
||||
class AgentChatSession(TimestampMixin, SoftDeleteMixin, Base):
|
||||
|
||||
@@ -2,23 +2,15 @@ from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from sqlalchemy import DateTime, JSON, String
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||
from schemas.enums import AutomationJobStatus, ScheduleType
|
||||
|
||||
|
||||
class AutomationJobStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
DISABLED = "disabled"
|
||||
|
||||
|
||||
class ScheduleType(str, Enum):
|
||||
DAILY = "daily"
|
||||
WEEKLY = "weekly"
|
||||
__all__ = ["AutomationJob", "AutomationJobStatus", "ScheduleType"]
|
||||
|
||||
|
||||
class AutomationJob(TimestampMixin, SoftDeleteMixin, Base):
|
||||
|
||||
@@ -2,21 +2,15 @@ from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from sqlalchemy import DateTime, String
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||
from schemas.enums import FriendshipStatus
|
||||
|
||||
|
||||
class FriendshipStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
ACCEPTED = "accepted"
|
||||
BLOCKED = "blocked"
|
||||
DECLINED = "declined"
|
||||
CANCELED = "canceled"
|
||||
__all__ = ["Friendship", "FriendshipStatus"]
|
||||
|
||||
|
||||
class Friendship(TimestampMixin, SoftDeleteMixin, Base):
|
||||
|
||||
@@ -1,30 +1,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from enum import Enum
|
||||
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||
from schemas.enums import GroupMemberRole, GroupMemberSource, GroupMemberStatus
|
||||
|
||||
|
||||
class GroupMemberRole(str, Enum):
|
||||
OWNER = "owner"
|
||||
ADMIN = "admin"
|
||||
MEMBER = "member"
|
||||
|
||||
|
||||
class GroupMemberSource(str, Enum):
|
||||
INVITED = "invited"
|
||||
JOINED = "joined"
|
||||
|
||||
|
||||
class GroupMemberStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
MUTED = "muted"
|
||||
REMOVED = "removed"
|
||||
__all__ = [
|
||||
"GroupMember",
|
||||
"GroupMemberRole",
|
||||
"GroupMemberSource",
|
||||
"GroupMemberStatus",
|
||||
]
|
||||
|
||||
|
||||
class GroupMember(TimestampMixin, SoftDeleteMixin, Base):
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from enum import Enum
|
||||
|
||||
from sqlalchemy import String, Text
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||
from schemas.enums import GroupStatus
|
||||
|
||||
|
||||
class GroupStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
ARCHIVED = "archived"
|
||||
__all__ = ["Group", "GroupStatus"]
|
||||
|
||||
|
||||
class Group(TimestampMixin, SoftDeleteMixin, Base):
|
||||
|
||||
@@ -1,27 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from enum import Enum
|
||||
|
||||
from sqlalchemy import Boolean, String
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, TimestampMixin
|
||||
from schemas.enums import InboxMessageStatus, InboxMessageType
|
||||
|
||||
|
||||
class InboxMessageType(str, Enum):
|
||||
FRIEND_REQUEST = "friend_request"
|
||||
CALENDAR = "calendar"
|
||||
SYSTEM = "system"
|
||||
GROUP = "group"
|
||||
|
||||
|
||||
class InboxMessageStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
ACCEPTED = "accepted"
|
||||
REJECTED = "rejected"
|
||||
DISMISSED = "dismissed"
|
||||
__all__ = ["InboxMessage", "InboxMessageType", "InboxMessageStatus"]
|
||||
|
||||
|
||||
class InboxMessage(TimestampMixin, Base):
|
||||
|
||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from sqlalchemy import CheckConstraint, DateTime, ForeignKey, Integer, String
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
@@ -10,12 +9,9 @@ from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, TimestampMixin
|
||||
from core.db.types import json_jsonb
|
||||
from schemas.enums import InviteCodeStatus
|
||||
|
||||
|
||||
class InviteCodeStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
DISABLED = "disabled"
|
||||
EXPIRED = "expired"
|
||||
__all__ = ["InviteCode", "InviteCodeStatus"]
|
||||
|
||||
|
||||
class InviteCode(TimestampMixin, Base):
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from enum import Enum
|
||||
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
@@ -9,16 +8,9 @@ from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, TimestampMixin
|
||||
from core.db.types import json_jsonb
|
||||
from schemas.enums import MemoryStatus, MemoryType
|
||||
|
||||
|
||||
class MemoryType(str, Enum):
|
||||
USER = "user"
|
||||
WORK = "work"
|
||||
|
||||
|
||||
class MemoryStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
DISABLED = "disabled"
|
||||
__all__ = ["Memory", "MemoryType", "MemoryStatus"]
|
||||
|
||||
|
||||
class Memory(TimestampMixin, Base):
|
||||
|
||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from sqlalchemy import DateTime, String, Text
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
@@ -10,19 +9,9 @@ from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||
from core.db.types import json_jsonb
|
||||
from schemas.enums import ScheduleItemSourceType, ScheduleItemStatus
|
||||
|
||||
|
||||
class ScheduleItemStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
COMPLETED = "completed"
|
||||
CANCELED = "canceled"
|
||||
ARCHIVED = "archived"
|
||||
|
||||
|
||||
class ScheduleItemSourceType(str, Enum):
|
||||
MANUAL = "manual"
|
||||
IMPORTED = "imported"
|
||||
AGENT_GENERATED = "agent_generated"
|
||||
__all__ = ["ScheduleItem", "ScheduleItemStatus", "ScheduleItemSourceType"]
|
||||
|
||||
|
||||
class ScheduleItem(TimestampMixin, SoftDeleteMixin, Base):
|
||||
|
||||
@@ -1,33 +1,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from enum import Enum
|
||||
|
||||
from sqlalchemy import Integer, String
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, TimestampMixin
|
||||
from schemas.enums import NotifyLevel, SubscriptionPermission, SubscriptionStatus
|
||||
|
||||
|
||||
class SubscriptionStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
PENDING = "pending"
|
||||
PAUSED = "paused"
|
||||
UNSUBSCRIBED = "unsubscribed"
|
||||
|
||||
|
||||
class NotifyLevel(str, Enum):
|
||||
ALL = "all"
|
||||
MENTIONS = "mentions"
|
||||
NONE = "none"
|
||||
|
||||
|
||||
class SubscriptionPermission(int, Enum):
|
||||
VIEW = 1 # 001 - 可查看
|
||||
INVITE = 2 # 010 - 可邀请
|
||||
EDIT = 4 # 100 - 可编辑
|
||||
OWNER = 7 # 111 - 所有者(VIEW + INVITE + EDIT)
|
||||
__all__ = [
|
||||
"ScheduleSubscription",
|
||||
"SubscriptionStatus",
|
||||
"NotifyLevel",
|
||||
"SubscriptionPermission",
|
||||
]
|
||||
|
||||
|
||||
class ScheduleSubscription(TimestampMixin, Base):
|
||||
|
||||
@@ -2,26 +2,15 @@ from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from sqlalchemy import DateTime, Integer, String
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, SoftDeleteMixin, TimestampMixin
|
||||
from schemas.enums import TodoPriority, TodoStatus
|
||||
|
||||
|
||||
class TodoStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
DONE = "done"
|
||||
CANCELED = "canceled"
|
||||
|
||||
|
||||
class TodoPriority(int, Enum):
|
||||
IMPORTANT_URGENT = 1
|
||||
IMPORTANT_NOT_URGENT = 2
|
||||
NOT_IMPORTANT_URGENT = 3
|
||||
NOT_IMPORTANT_NOT_URGENT = 4
|
||||
__all__ = ["Todo", "TodoStatus", "TodoPriority"]
|
||||
|
||||
|
||||
class Todo(TimestampMixin, SoftDeleteMixin, Base):
|
||||
|
||||
@@ -1,45 +1 @@
|
||||
"""Centralized shared schemas for cross-module contracts."""
|
||||
|
||||
from schemas.inbox.messages import (
|
||||
CalendarContent,
|
||||
CalendarDeleteContent,
|
||||
CalendarInviteContent,
|
||||
CalendarUpdateContent,
|
||||
FriendshipContent,
|
||||
InboxMessageContent,
|
||||
InboxMessageStatus,
|
||||
InboxMessageType,
|
||||
parse_calendar_content,
|
||||
)
|
||||
from schemas.invite_codes import InviteCodeRewardConfig
|
||||
from schemas.messages import AgentChatMessageMetadata
|
||||
from schemas.schedule.items import (
|
||||
AttachmentType,
|
||||
ScheduleItemMetadata,
|
||||
ScheduleItemMetadataAttachment,
|
||||
ScheduleItemSourceType,
|
||||
ScheduleItemStatus,
|
||||
)
|
||||
from schemas.sessions import SessionStateSnapshot
|
||||
from schemas.user.context import UserContext
|
||||
|
||||
__all__ = [
|
||||
"AgentChatMessageMetadata",
|
||||
"AttachmentType",
|
||||
"CalendarContent",
|
||||
"CalendarDeleteContent",
|
||||
"CalendarInviteContent",
|
||||
"CalendarUpdateContent",
|
||||
"FriendshipContent",
|
||||
"InboxMessageContent",
|
||||
"InboxMessageStatus",
|
||||
"InboxMessageType",
|
||||
"InviteCodeRewardConfig",
|
||||
"ScheduleItemMetadata",
|
||||
"ScheduleItemMetadataAttachment",
|
||||
"ScheduleItemSourceType",
|
||||
"ScheduleItemStatus",
|
||||
"SessionStateSnapshot",
|
||||
"UserContext",
|
||||
"parse_calendar_content",
|
||||
]
|
||||
"""Backend reusable schemas package."""
|
||||
|
||||
@@ -12,7 +12,7 @@ Version: 2.0
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Literal, NotRequired, TypedDict, Union
|
||||
from typing import Any, Literal, TypedDict, Union
|
||||
|
||||
# ============================================================
|
||||
# Enums
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""Reusable domain schemas shared across backend modules."""
|
||||
+30
-17
@@ -2,13 +2,27 @@ from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Protocol
|
||||
from uuid import UUID
|
||||
|
||||
from core.agentscope.tools.tool_config import AgentTool
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from schemas.enums import AutomationJobStatus, ScheduleType
|
||||
|
||||
from models.automation_jobs import AutomationJob as OrmAutomationJob
|
||||
from models.automation_jobs import AutomationJobStatus, ScheduleType
|
||||
|
||||
class AutomationJobLike(Protocol):
|
||||
id: UUID
|
||||
owner_id: UUID
|
||||
bootstrap_key: str | None
|
||||
title: str
|
||||
config: dict[str, object]
|
||||
next_run_at: datetime
|
||||
timezone: str
|
||||
last_run_at: datetime | None
|
||||
status: AutomationJobStatus
|
||||
created_by: UUID | None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class ContextSource(str, Enum):
|
||||
@@ -50,8 +64,7 @@ class ScheduleConfig(BaseModel):
|
||||
invalid = [day for day in self.weekdays if day < 1 or day > 7]
|
||||
if invalid:
|
||||
raise ValueError("weekdays must be within 1-7")
|
||||
deduped = sorted(set(self.weekdays))
|
||||
self.weekdays = deduped
|
||||
self.weekdays = sorted(set(self.weekdays))
|
||||
else:
|
||||
self.weekdays = None
|
||||
return self
|
||||
@@ -90,20 +103,20 @@ class AutomationJob(BaseModel):
|
||||
updated_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_orm(cls, obj: OrmAutomationJob) -> "AutomationJob":
|
||||
def from_orm(cls, obj: object) -> "AutomationJob":
|
||||
return cls(
|
||||
id=obj.id,
|
||||
owner_id=obj.owner_id,
|
||||
bootstrap_key=obj.bootstrap_key,
|
||||
title=obj.title,
|
||||
config=AutomationJobConfig.model_validate(obj.config or {}),
|
||||
next_run_at=obj.next_run_at,
|
||||
timezone=obj.timezone,
|
||||
last_run_at=obj.last_run_at,
|
||||
status=obj.status,
|
||||
created_by=obj.created_by,
|
||||
created_at=obj.created_at,
|
||||
updated_at=obj.updated_at,
|
||||
id=getattr(obj, "id"),
|
||||
owner_id=getattr(obj, "owner_id"),
|
||||
bootstrap_key=getattr(obj, "bootstrap_key"),
|
||||
title=getattr(obj, "title"),
|
||||
config=AutomationJobConfig.model_validate(getattr(obj, "config", {}) or {}),
|
||||
next_run_at=getattr(obj, "next_run_at"),
|
||||
timezone=getattr(obj, "timezone"),
|
||||
last_run_at=getattr(obj, "last_run_at"),
|
||||
status=getattr(obj, "status"),
|
||||
created_by=getattr(obj, "created_by"),
|
||||
created_at=getattr(obj, "created_at"),
|
||||
updated_at=getattr(obj, "updated_at"),
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -1,24 +1,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from enum import Enum
|
||||
from typing import ClassVar, Literal, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from schemas.enums import InboxMessageStatus, InboxMessageType
|
||||
|
||||
|
||||
class InboxMessageType(str, Enum):
|
||||
FRIEND_REQUEST = "friend_request"
|
||||
CALENDAR = "calendar"
|
||||
SYSTEM = "system"
|
||||
GROUP = "group"
|
||||
|
||||
|
||||
class InboxMessageStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
ACCEPTED = "accepted"
|
||||
REJECTED = "rejected"
|
||||
DISMISSED = "dismissed"
|
||||
__all__ = [
|
||||
"InboxMessageType",
|
||||
"InboxMessageStatus",
|
||||
"CalendarInviteContent",
|
||||
"CalendarUpdateContent",
|
||||
"CalendarDeleteContent",
|
||||
"FriendshipContent",
|
||||
"CalendarContent",
|
||||
"InboxMessageContent",
|
||||
"parse_calendar_content",
|
||||
]
|
||||
|
||||
|
||||
class CalendarInviteContent(BaseModel):
|
||||
@@ -1,13 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import ClassVar, Literal
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from schemas.memories.memory_content import (
|
||||
from schemas.domain.memory_content import (
|
||||
TeamMember,
|
||||
UserMemoryContent,
|
||||
UserPreferences,
|
||||
@@ -15,16 +13,7 @@ from schemas.memories.memory_content import (
|
||||
WorkProfileContent,
|
||||
WorkProject,
|
||||
)
|
||||
|
||||
|
||||
class MemoryType(str, Enum):
|
||||
USER = "user"
|
||||
WORK = "work"
|
||||
|
||||
|
||||
class MemoryStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
DISABLED = "disabled"
|
||||
from schemas.enums import MemoryStatus, MemoryType
|
||||
|
||||
|
||||
class MemoryModel(BaseModel):
|
||||
@@ -5,6 +5,15 @@ from typing import ClassVar, Literal
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from schemas.enums import ScheduleItemSourceType, ScheduleItemStatus
|
||||
|
||||
__all__ = [
|
||||
"AttachmentType",
|
||||
"ScheduleItemMetadataAttachment",
|
||||
"ScheduleItemMetadata",
|
||||
"ScheduleItemSourceType",
|
||||
"ScheduleItemStatus",
|
||||
]
|
||||
|
||||
|
||||
class AttachmentType(str, Enum):
|
||||
@@ -32,16 +41,3 @@ class ScheduleItemMetadata(BaseModel):
|
||||
attachments: list[ScheduleItemMetadataAttachment] = Field(default_factory=list)
|
||||
reminder_minutes: int | None = Field(default=None, ge=0, le=10080)
|
||||
version: Literal[1] = 1
|
||||
|
||||
|
||||
class ScheduleItemStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
COMPLETED = "completed"
|
||||
CANCELED = "canceled"
|
||||
ARCHIVED = "archived"
|
||||
|
||||
|
||||
class ScheduleItemSourceType(str, Enum):
|
||||
MANUAL = "manual"
|
||||
IMPORTED = "imported"
|
||||
AGENT_GENERATED = "agent_generated"
|
||||
@@ -0,0 +1,136 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ScheduleItemStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
ARCHIVED = "archived"
|
||||
|
||||
|
||||
class ScheduleItemSourceType(str, Enum):
|
||||
MANUAL = "manual"
|
||||
IMPORTED = "imported"
|
||||
AGENT_GENERATED = "agent_generated"
|
||||
|
||||
|
||||
class AutomationJobStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
DISABLED = "disabled"
|
||||
|
||||
|
||||
class ScheduleType(str, Enum):
|
||||
DAILY = "daily"
|
||||
WEEKLY = "weekly"
|
||||
|
||||
|
||||
class MemoryType(str, Enum):
|
||||
USER = "user"
|
||||
WORK = "work"
|
||||
|
||||
|
||||
class MemoryStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
DISABLED = "disabled"
|
||||
|
||||
|
||||
class TodoStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
DONE = "done"
|
||||
CANCELED = "canceled"
|
||||
|
||||
|
||||
class TodoPriority(int, Enum):
|
||||
IMPORTANT_URGENT = 1
|
||||
IMPORTANT_NOT_URGENT = 2
|
||||
NOT_IMPORTANT_URGENT = 3
|
||||
NOT_IMPORTANT_NOT_URGENT = 4
|
||||
|
||||
|
||||
class AgentChatMessageRole(str, Enum):
|
||||
USER = "user"
|
||||
ASSISTANT = "assistant"
|
||||
SYSTEM = "system"
|
||||
TOOL = "tool"
|
||||
|
||||
|
||||
class AgentChatSessionStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
RUNNING = "running"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
|
||||
|
||||
class SessionType(str, Enum):
|
||||
CHAT = "chat"
|
||||
AUTOMATION = "automation"
|
||||
|
||||
|
||||
class InboxMessageType(str, Enum):
|
||||
FRIEND_REQUEST = "friend_request"
|
||||
CALENDAR = "calendar"
|
||||
SYSTEM = "system"
|
||||
GROUP = "group"
|
||||
|
||||
|
||||
class InboxMessageStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
ACCEPTED = "accepted"
|
||||
REJECTED = "rejected"
|
||||
DISMISSED = "dismissed"
|
||||
|
||||
|
||||
class SubscriptionStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
PENDING = "pending"
|
||||
PAUSED = "paused"
|
||||
UNSUBSCRIBED = "unsubscribed"
|
||||
|
||||
|
||||
class NotifyLevel(str, Enum):
|
||||
ALL = "all"
|
||||
MENTIONS = "mentions"
|
||||
NONE = "none"
|
||||
|
||||
|
||||
class SubscriptionPermission(int, Enum):
|
||||
VIEW = 1
|
||||
INVITE = 2
|
||||
EDIT = 4
|
||||
OWNER = 7
|
||||
|
||||
|
||||
class FriendshipStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
ACCEPTED = "accepted"
|
||||
BLOCKED = "blocked"
|
||||
DECLINED = "declined"
|
||||
CANCELED = "canceled"
|
||||
|
||||
|
||||
class InviteCodeStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
DISABLED = "disabled"
|
||||
EXPIRED = "expired"
|
||||
|
||||
|
||||
class GroupStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
ARCHIVED = "archived"
|
||||
|
||||
|
||||
class GroupMemberRole(str, Enum):
|
||||
OWNER = "owner"
|
||||
ADMIN = "admin"
|
||||
MEMBER = "member"
|
||||
|
||||
|
||||
class GroupMemberSource(str, Enum):
|
||||
INVITED = "invited"
|
||||
JOINED = "joined"
|
||||
|
||||
|
||||
class GroupMemberStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
MUTED = "muted"
|
||||
REMOVED = "removed"
|
||||
@@ -1,3 +0,0 @@
|
||||
from schemas.inbox.messages import InboxMessageStatus, InboxMessageType
|
||||
|
||||
__all__ = ["InboxMessageStatus", "InboxMessageType"]
|
||||
@@ -1,3 +0,0 @@
|
||||
from schemas.messages.chat_message import AgentChatMessage, AgentChatMessageMetadata
|
||||
|
||||
__all__ = ["AgentChatMessage", "AgentChatMessageMetadata"]
|
||||
@@ -1,27 +0,0 @@
|
||||
from schemas.inbox.messages import (
|
||||
CalendarContent,
|
||||
CalendarDeleteContent,
|
||||
CalendarInviteContent,
|
||||
CalendarUpdateContent,
|
||||
parse_calendar_content,
|
||||
)
|
||||
from schemas.schedule.items import (
|
||||
AttachmentType,
|
||||
ScheduleItemMetadata,
|
||||
ScheduleItemMetadataAttachment,
|
||||
ScheduleItemSourceType,
|
||||
ScheduleItemStatus,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"AttachmentType",
|
||||
"CalendarContent",
|
||||
"CalendarDeleteContent",
|
||||
"CalendarInviteContent",
|
||||
"CalendarUpdateContent",
|
||||
"ScheduleItemMetadata",
|
||||
"ScheduleItemMetadataAttachment",
|
||||
"ScheduleItemSourceType",
|
||||
"ScheduleItemStatus",
|
||||
"parse_calendar_content",
|
||||
]
|
||||
@@ -1,3 +0,0 @@
|
||||
from schemas.sessions.chat_session import SessionStateSnapshot
|
||||
|
||||
__all__ = ["SessionStateSnapshot"]
|
||||
@@ -0,0 +1 @@
|
||||
"""Shared schemas used across multiple domain modules."""
|
||||
@@ -1,3 +0,0 @@
|
||||
from .contracts import TodoOrder
|
||||
|
||||
__all__ = ["TodoOrder"]
|
||||
@@ -1,17 +0,0 @@
|
||||
from schemas.user.context import (
|
||||
PreferenceSettings,
|
||||
ProfileSettingsUnion,
|
||||
ProfileSettingsV1,
|
||||
UserContext,
|
||||
parse_profile_settings,
|
||||
upgrade_to_latest,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"PreferenceSettings",
|
||||
"ProfileSettingsUnion",
|
||||
"ProfileSettingsV1",
|
||||
"UserContext",
|
||||
"parse_profile_settings",
|
||||
"upgrade_to_latest",
|
||||
]
|
||||
@@ -9,10 +9,11 @@ from fastapi import HTTPException
|
||||
from sqlalchemy import Select, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from models.agent_chat_message import AgentChatMessage, AgentChatMessageRole
|
||||
from models.agent_chat_message import AgentChatMessage
|
||||
from models.agent_chat_session import AgentChatSession
|
||||
from models.system_agents import SystemAgents
|
||||
from schemas.messages.chat_message import (
|
||||
from schemas.enums import AgentChatMessageRole
|
||||
from schemas.domain.chat_message import (
|
||||
AgentChatMessage as AgentChatMessageSchema,
|
||||
AgentChatMessageMetadata,
|
||||
)
|
||||
|
||||
@@ -18,8 +18,8 @@ from schemas.agent.forwarded_props import (
|
||||
RuntimeMode,
|
||||
)
|
||||
from schemas.agent.visibility import SystemVisibilityBit, bit_mask
|
||||
from schemas.automation import RuntimeConfig
|
||||
from schemas.messages.chat_message import (
|
||||
from schemas.domain.automation import RuntimeConfig
|
||||
from schemas.domain.chat_message import (
|
||||
AgentChatMessageMetadata,
|
||||
UserMessageAttachment,
|
||||
extract_user_message_attachments,
|
||||
@@ -362,7 +362,7 @@ class AgentService:
|
||||
before: date | None,
|
||||
current_user: CurrentUser,
|
||||
) -> HistorySnapshotResponse:
|
||||
from schemas.messages.chat_message import AgentChatMessage
|
||||
from schemas.domain.chat_message import AgentChatMessage
|
||||
from v1.agent.utils import convert_message_to_history
|
||||
from v1.agent.schemas import HistoryMessage
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from pydantic import ValidationError
|
||||
|
||||
from core.agentscope.tools.tool_config import AgentTool
|
||||
from schemas.agent.system_agent import SystemAgentLLMConfig
|
||||
from schemas.automation import (
|
||||
from schemas.domain.automation import (
|
||||
ContextSource,
|
||||
ContextWindowMode,
|
||||
MessageContextConfig,
|
||||
|
||||
@@ -8,7 +8,7 @@ from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from core.agentscope.runtime.ui_compiler import compile as compile_ui_hints
|
||||
from schemas.messages.chat_message import (
|
||||
from schemas.domain.chat_message import (
|
||||
AgentChatMessage,
|
||||
AgentChatMessageMetadata,
|
||||
extract_user_message_attachments,
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
from schemas.automation import AutomationJobConfig
|
||||
from schemas.domain.automation import AutomationJobConfig
|
||||
|
||||
_CONFIG_NAME_PATTERN = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$")
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ from sqlalchemy.dialects.postgresql import insert
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from core.logging import get_logger
|
||||
from models.automation_jobs import AutomationJob, AutomationJobStatus, ScheduleType
|
||||
from models.memories import MemoryType
|
||||
from models.automation_jobs import AutomationJob
|
||||
from schemas.enums import AutomationJobStatus, MemoryType, ScheduleType
|
||||
from models.profile import Profile
|
||||
from schemas.automation import AutomationJobConfig, ScheduleConfig
|
||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from schemas.user.context import parse_profile_settings
|
||||
from schemas.domain.automation import AutomationJobConfig, ScheduleConfig
|
||||
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from schemas.shared.user import parse_profile_settings
|
||||
from v1.auth.automation_static_config import load_static_automation_job_config
|
||||
from v1.auth.schemas import RegistrationBootstrapRequest
|
||||
from v1.memories.repository import SQLAlchemyMemoriesRepository
|
||||
|
||||
@@ -9,9 +9,10 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
from core.db.base_repository import BaseRepository
|
||||
from models.agent_chat_session import AgentChatSession, SessionType
|
||||
from models.automation_jobs import AutomationJob, AutomationJobStatus, ScheduleType
|
||||
from schemas.automation import AutomationJobConfig, ScheduleConfig
|
||||
from models.agent_chat_session import AgentChatSession
|
||||
from models.automation_jobs import AutomationJob
|
||||
from schemas.enums import AutomationJobStatus, ScheduleType, SessionType
|
||||
from schemas.domain.automation import AutomationJobConfig, ScheduleConfig
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from v1.automation_jobs.schemas import (
|
||||
|
||||
@@ -7,9 +7,8 @@ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||
|
||||
from models.automation_jobs import AutomationJob as OrmAutomationJob
|
||||
from models.automation_jobs import AutomationJobStatus
|
||||
from schemas.automation import AutomationJobConfig
|
||||
from schemas.domain.automation import AutomationJobConfig
|
||||
from schemas.enums import AutomationJobStatus
|
||||
|
||||
|
||||
class AutomationJobResponse(BaseModel):
|
||||
@@ -29,20 +28,20 @@ class AutomationJobResponse(BaseModel):
|
||||
updated_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_orm(cls, obj: OrmAutomationJob) -> Self:
|
||||
def from_orm(cls, obj: object) -> Self:
|
||||
return cls(
|
||||
id=obj.id,
|
||||
owner_id=obj.owner_id,
|
||||
bootstrap_key=obj.bootstrap_key,
|
||||
title=obj.title,
|
||||
timezone=obj.timezone,
|
||||
status=obj.status,
|
||||
is_system=obj.bootstrap_key is not None,
|
||||
config=AutomationJobConfig.model_validate(obj.config or {}),
|
||||
next_run_at=obj.next_run_at,
|
||||
last_run_at=obj.last_run_at,
|
||||
created_at=obj.created_at,
|
||||
updated_at=obj.updated_at,
|
||||
id=getattr(obj, "id"),
|
||||
owner_id=getattr(obj, "owner_id"),
|
||||
bootstrap_key=getattr(obj, "bootstrap_key"),
|
||||
title=getattr(obj, "title"),
|
||||
timezone=getattr(obj, "timezone"),
|
||||
status=getattr(obj, "status"),
|
||||
is_system=getattr(obj, "bootstrap_key") is not None,
|
||||
config=AutomationJobConfig.model_validate(getattr(obj, "config", {}) or {}),
|
||||
next_run_at=getattr(obj, "next_run_at"),
|
||||
last_run_at=getattr(obj, "last_run_at"),
|
||||
created_at=getattr(obj, "created_at"),
|
||||
updated_at=getattr(obj, "updated_at"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ from uuid import UUID
|
||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from models.automation_jobs import ScheduleType
|
||||
from schemas.automation import (
|
||||
from schemas.enums import ScheduleType
|
||||
from schemas.domain.automation import (
|
||||
AutomationJob as AutomationJobSchema,
|
||||
MessageContextConfig,
|
||||
RuntimeConfig,
|
||||
|
||||
@@ -9,9 +9,10 @@ from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from core.db.base_repository import BaseRepository
|
||||
from core.logging import get_logger
|
||||
from models.friendships import Friendship, FriendshipStatus
|
||||
from models.inbox_messages import InboxMessage, InboxMessageStatus, InboxMessageType
|
||||
from schemas.inbox.messages import FriendshipContent
|
||||
from models.friendships import Friendship
|
||||
from models.inbox_messages import InboxMessage
|
||||
from schemas.enums import FriendshipStatus, InboxMessageStatus, InboxMessageType
|
||||
from schemas.domain.inbox import FriendshipContent
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -6,7 +6,7 @@ from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from schemas.user.context import UserContext
|
||||
from schemas.shared.user import UserContext
|
||||
|
||||
|
||||
class FriendRequestCreate(BaseModel):
|
||||
|
||||
@@ -10,8 +10,9 @@ from sqlalchemy.exc import SQLAlchemyError
|
||||
from core.auth.models import CurrentUser
|
||||
from core.db.base_service import BaseService
|
||||
from core.logging import get_logger
|
||||
from models.friendships import Friendship, FriendshipStatus
|
||||
from models.inbox_messages import InboxMessage, InboxMessageStatus, InboxMessageType
|
||||
from models.friendships import Friendship
|
||||
from models.inbox_messages import InboxMessage
|
||||
from schemas.enums import FriendshipStatus, InboxMessageStatus, InboxMessageType
|
||||
from v1.friendships.repository import FriendshipRepository
|
||||
from v1.friendships.schemas import (
|
||||
FriendRequestCreate,
|
||||
@@ -22,7 +23,7 @@ from v1.users.repository import UserRepository
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from schemas.user.context import UserContext
|
||||
from schemas.shared.user import UserContext
|
||||
|
||||
|
||||
logger = get_logger("v1.friendships.service")
|
||||
@@ -593,7 +594,7 @@ class FriendshipService(BaseService):
|
||||
)
|
||||
|
||||
def _build_user_basic_info(self, profile: Any) -> "UserContext":
|
||||
from schemas.user.context import UserContext
|
||||
from schemas.shared.user import UserContext
|
||||
|
||||
if profile is None:
|
||||
return UserContext(id="", username="")
|
||||
|
||||
@@ -7,7 +7,8 @@ from sqlalchemy import select, update
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from core.logging import get_logger
|
||||
from models.inbox_messages import InboxMessage, InboxMessageType, InboxMessageStatus
|
||||
from models.inbox_messages import InboxMessage
|
||||
from schemas.enums import InboxMessageStatus, InboxMessageType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -6,7 +6,7 @@ from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from schemas.inbox.messages import InboxMessageStatus, InboxMessageType
|
||||
from schemas.domain.inbox import InboxMessageStatus, InboxMessageType
|
||||
|
||||
|
||||
class InboxMessageResponse(BaseModel):
|
||||
|
||||
@@ -9,7 +9,8 @@ from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from core.db.base_repository import BaseRepository
|
||||
from core.logging import get_logger
|
||||
from models.memories import Memory, MemoryStatus, MemoryType
|
||||
from models.memories import Memory
|
||||
from schemas.enums import MemoryStatus, MemoryType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
|
||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from v1.memories.dependencies import get_memories_service
|
||||
from v1.memories.schemas import (
|
||||
MemoryListResponse,
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import ClassVar
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||
|
||||
|
||||
class UserMemoryUpdate(BaseModel):
|
||||
|
||||
@@ -8,8 +8,9 @@ from sqlalchemy.exc import SQLAlchemyError
|
||||
from core.auth.models import CurrentUser
|
||||
from core.db.base_service import BaseService
|
||||
from core.logging import get_logger
|
||||
from models.memories import Memory, MemoryType
|
||||
from schemas.memories.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from models.memories import Memory
|
||||
from schemas.enums import MemoryType
|
||||
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||
from v1.memories.repository import MemoriesRepositoryLike
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@@ -9,8 +9,9 @@ from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from core.db.base_repository import BaseRepository
|
||||
from core.logging import get_logger
|
||||
from models.schedule_items import ScheduleItem, ScheduleItemStatus
|
||||
from models.schedule_subscriptions import ScheduleSubscription, SubscriptionStatus
|
||||
from models.schedule_items import ScheduleItem
|
||||
from models.schedule_subscriptions import ScheduleSubscription
|
||||
from schemas.enums import ScheduleItemStatus, SubscriptionStatus
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -7,14 +7,14 @@ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
from schemas.inbox.messages import (
|
||||
from schemas.domain.inbox import (
|
||||
CalendarContent,
|
||||
CalendarDeleteContent,
|
||||
CalendarInviteContent,
|
||||
CalendarUpdateContent,
|
||||
parse_calendar_content,
|
||||
)
|
||||
from schemas.schedule.items import (
|
||||
from schemas.domain.schedule import (
|
||||
AttachmentType,
|
||||
ScheduleItemMetadata,
|
||||
ScheduleItemMetadataAttachment,
|
||||
|
||||
@@ -10,9 +10,14 @@ from sqlalchemy.exc import SQLAlchemyError
|
||||
from core.auth.models import CurrentUser
|
||||
from core.db.base_service import BaseService
|
||||
from core.logging import get_logger
|
||||
from models.inbox_messages import InboxMessage, InboxMessageType, InboxMessageStatus
|
||||
from models.inbox_messages import InboxMessage
|
||||
from models.schedule_items import ScheduleItem
|
||||
from models.schedule_subscriptions import SubscriptionPermission, SubscriptionStatus
|
||||
from schemas.enums import (
|
||||
InboxMessageStatus,
|
||||
InboxMessageType,
|
||||
SubscriptionPermission,
|
||||
SubscriptionStatus,
|
||||
)
|
||||
from v1.auth.gateway import SupabaseAuthGateway
|
||||
from v1.inbox_messages.repository import InboxMessageRepository
|
||||
from v1.schedule_items.repository import ScheduleItemRepository
|
||||
@@ -35,6 +40,8 @@ if TYPE_CHECKING:
|
||||
|
||||
logger = get_logger("v1.schedule_items.service")
|
||||
|
||||
_LEGACY_ARCHIVED_STATUSES = {"completed", "canceled"}
|
||||
|
||||
|
||||
class AuthByPhoneGateway(Protocol):
|
||||
async def get_user_by_phone(self, phone: str) -> "UserByPhoneResponse": ...
|
||||
@@ -417,8 +424,10 @@ class ScheduleItemService(BaseService):
|
||||
permission: int = 1,
|
||||
) -> ScheduleItemResponse:
|
||||
status_value = (
|
||||
item.status.value if hasattr(item.status, "value") else item.status
|
||||
item.status.value if hasattr(item.status, "value") else str(item.status)
|
||||
)
|
||||
if status_value in _LEGACY_ARCHIVED_STATUSES:
|
||||
status_value = ScheduleItemStatus.ARCHIVED.value
|
||||
source_type_value = (
|
||||
item.source_type.value
|
||||
if hasattr(item.source_type, "value")
|
||||
|
||||
@@ -10,7 +10,8 @@ from sqlalchemy.exc import SQLAlchemyError
|
||||
from core.db.base_repository import BaseRepository
|
||||
from core.logging import get_logger
|
||||
from models.todo_sources import TodoSource
|
||||
from models.todos import Todo, TodoPriority, TodoStatus
|
||||
from models.todos import Todo
|
||||
from schemas.enums import TodoPriority, TodoStatus
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -5,7 +5,7 @@ from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from schemas.todo import TodoOrder
|
||||
from schemas.domain.todo import TodoOrder
|
||||
|
||||
|
||||
class TodoCreate(BaseModel):
|
||||
|
||||
@@ -10,7 +10,8 @@ from sqlalchemy.exc import SQLAlchemyError
|
||||
from core.auth.models import CurrentUser
|
||||
from core.db.base_service import BaseService
|
||||
from core.logging import get_logger
|
||||
from models.todos import Todo, TodoStatus
|
||||
from models.todos import Todo
|
||||
from schemas.enums import TodoStatus
|
||||
from v1.schedule_items.repository import SQLAlchemyScheduleItemRepository
|
||||
from v1.todo.repository import TodoRepository
|
||||
from v1.todo.schemas import (
|
||||
|
||||
@@ -5,7 +5,7 @@ from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from schemas.user.context import UserContext
|
||||
from schemas.shared.user import UserContext
|
||||
from v1.users.dependencies import get_user_service
|
||||
from v1.users.schemas import UserSearchRequest, UserUpdateRequest
|
||||
from v1.users.service import UserService
|
||||
|
||||
@@ -13,14 +13,14 @@ from core.agentscope.persistence.user_context_cache import (
|
||||
)
|
||||
from core.db.base_service import BaseService
|
||||
from core.logging import get_logger
|
||||
from schemas.user.context import UserContext, parse_profile_settings
|
||||
from schemas.shared.user import UserContext, parse_profile_settings
|
||||
from v1.users.repository import UserRepository
|
||||
from v1.users.schemas import UserSearchRequest, UserUpdateRequest
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from schemas.user.context import UserContext
|
||||
from schemas.shared.user import UserContext
|
||||
|
||||
logger = get_logger("v1.users.service")
|
||||
|
||||
@@ -105,7 +105,7 @@ class UserService(BaseService):
|
||||
)
|
||||
|
||||
async def get_user_by_id(self, user_id: UUID) -> "UserContext":
|
||||
from schemas.user.context import UserContext
|
||||
from schemas.shared.user import UserContext
|
||||
|
||||
try:
|
||||
profile = await self._repository.get_by_user_id(user_id)
|
||||
|
||||
@@ -11,7 +11,7 @@ import uvicorn
|
||||
|
||||
from app import app
|
||||
from core.auth.models import CurrentUser
|
||||
from schemas.user.context import UserContext
|
||||
from schemas.shared.user import UserContext
|
||||
from v1.users.dependencies import get_current_user, get_user_service
|
||||
from v1.users.schemas import UserUpdateRequest
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from fastapi.testclient import TestClient
|
||||
|
||||
from app import app
|
||||
from core.auth.models import CurrentUser
|
||||
from schemas.user.context import UserContext
|
||||
from schemas.shared.user import UserContext
|
||||
from v1.friendships.dependencies import get_friendship_service
|
||||
from v1.friendships.schemas import (
|
||||
FriendRequestCreate,
|
||||
|
||||
@@ -8,7 +8,7 @@ from fastapi.testclient import TestClient
|
||||
|
||||
from app import app
|
||||
from core.auth.models import CurrentUser
|
||||
from schemas.user.context import UserContext
|
||||
from schemas.shared.user import UserContext
|
||||
from v1.users.dependencies import get_current_user, get_user_service
|
||||
from v1.users.schemas import UserSearchRequest, UserUpdateRequest
|
||||
from v1.users.service import UserService
|
||||
|
||||
@@ -6,7 +6,7 @@ from uuid import uuid4
|
||||
import pytest
|
||||
|
||||
from core.agentscope.persistence.user_context_cache import UserContextCache
|
||||
from schemas.user.context import (
|
||||
from schemas.shared.user import (
|
||||
UserContext,
|
||||
parse_profile_settings,
|
||||
)
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from core.agentscope.runtime.registry_builder import build_consumer_registry
|
||||
|
||||
|
||||
def test_build_consumer_registry_from_system_agent_configs() -> None:
|
||||
registry = build_consumer_registry(
|
||||
system_agent_configs={
|
||||
"router": {"config": {"visibility_consumer_bit": 16}},
|
||||
"worker": {"config": {"visibility_consumer_bit": 17}},
|
||||
"memory": {"config": {"visibility_consumer_bit": 18}},
|
||||
}
|
||||
)
|
||||
|
||||
assert registry.resolve_agent_bit(agent_type="router") == 16
|
||||
assert registry.resolve_agent_bit(agent_type="worker") == 17
|
||||
|
||||
|
||||
def test_build_consumer_registry_rejects_duplicate_bit() -> None:
|
||||
with pytest.raises(ValueError, match="duplicate visibility bit"):
|
||||
build_consumer_registry(
|
||||
system_agent_configs={
|
||||
"router": {"config": {"visibility_consumer_bit": 16}},
|
||||
"worker": {"config": {"visibility_consumer_bit": 16}},
|
||||
}
|
||||
)
|
||||
@@ -6,8 +6,8 @@ import pytest
|
||||
from ag_ui.core import RunAgentInput
|
||||
|
||||
from core.agentscope.runtime.orchestrator import AgentScopeRuntimeOrchestrator
|
||||
from schemas.automation import MessageContextConfig, RuntimeConfig
|
||||
from schemas.user import UserContext, parse_profile_settings
|
||||
from schemas.domain.automation import MessageContextConfig, RuntimeConfig
|
||||
from schemas.shared.user import UserContext, parse_profile_settings
|
||||
|
||||
|
||||
class _FakePipeline:
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from core.agentscope.runtime.pipeline_registry import build_default_pipeline_spec
|
||||
|
||||
|
||||
def test_build_default_pipeline_spec_worker_has_two_stages() -> None:
|
||||
spec = build_default_pipeline_spec(mode="worker")
|
||||
|
||||
assert spec.mode == "worker"
|
||||
assert [item.stage_name for item in spec.stages] == ["router", "worker"]
|
||||
|
||||
|
||||
def test_build_default_pipeline_spec_memory_has_single_stage() -> None:
|
||||
spec = build_default_pipeline_spec(mode="memory")
|
||||
|
||||
assert spec.mode == "memory"
|
||||
assert [item.stage_name for item in spec.stages] == ["memory"]
|
||||
|
||||
|
||||
def test_build_default_pipeline_spec_rejects_unknown_mode() -> None:
|
||||
with pytest.raises(ValueError, match="unsupported pipeline mode"):
|
||||
build_default_pipeline_spec(mode="planner")
|
||||
@@ -18,8 +18,8 @@ from schemas.agent.runtime_models import (
|
||||
WorkerAgentOutputLite,
|
||||
)
|
||||
from schemas.agent.system_agent import AgentType
|
||||
from schemas.automation import MessageContextConfig, RuntimeConfig
|
||||
from schemas.user import UserContext, parse_profile_settings
|
||||
from schemas.domain.automation import MessageContextConfig, RuntimeConfig
|
||||
from schemas.shared.user import UserContext, parse_profile_settings
|
||||
|
||||
|
||||
def _run_input() -> RunAgentInput:
|
||||
|
||||
@@ -7,8 +7,8 @@ import pytest
|
||||
|
||||
import core.agentscope.runtime.tasks as tasks_module
|
||||
from schemas.agent import ToolStatus
|
||||
from schemas.automation import ContextWindowMode, MessageContextConfig
|
||||
from schemas.user import UserContext, parse_profile_settings
|
||||
from schemas.domain.automation import ContextWindowMode, MessageContextConfig
|
||||
from schemas.shared.user import UserContext, parse_profile_settings
|
||||
|
||||
|
||||
def _run_input_payload() -> dict[str, Any]:
|
||||
|
||||
@@ -1,250 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from core.agentscope.schemas.agui_input import (
|
||||
MAX_MESSAGES,
|
||||
MAX_RUN_ID_LENGTH,
|
||||
MAX_RUN_INPUT_BYTES,
|
||||
MAX_TEXT_CHARS,
|
||||
parse_run_input,
|
||||
validate_run_request_messages_contract,
|
||||
)
|
||||
|
||||
|
||||
def _base_payload() -> dict[str, object]:
|
||||
return {
|
||||
"threadId": "00000000-0000-0000-0000-000000000001",
|
||||
"runId": "run-1",
|
||||
"state": {},
|
||||
"messages": [{"id": "u1", "role": "user", "content": "hello"}],
|
||||
"tools": [],
|
||||
"context": [],
|
||||
"forwardedProps": {"agent_type": "worker"},
|
||||
}
|
||||
|
||||
|
||||
def test_parse_run_input_rejects_invalid_uuid() -> None:
|
||||
payload = _base_payload()
|
||||
payload["threadId"] = "bad-uuid"
|
||||
|
||||
with pytest.raises(ValueError, match="threadId must be a valid UUID"):
|
||||
parse_run_input(payload)
|
||||
|
||||
|
||||
def test_parse_run_input_rejects_message_count_over_limit() -> None:
|
||||
payload = _base_payload()
|
||||
payload["messages"] = [
|
||||
{"id": f"u{i}", "role": "user", "content": "x"} for i in range(MAX_MESSAGES + 1)
|
||||
]
|
||||
|
||||
with pytest.raises(ValueError, match="RunAgentInput.messages exceeds limit"):
|
||||
parse_run_input(payload)
|
||||
|
||||
|
||||
def test_parse_run_input_rejects_user_text_over_limit() -> None:
|
||||
payload = _base_payload()
|
||||
payload["messages"] = [
|
||||
{"id": "u1", "role": "user", "content": "x" * (MAX_TEXT_CHARS + 1)}
|
||||
]
|
||||
|
||||
with pytest.raises(
|
||||
ValueError, match="RunAgentInput user message text exceeds limit"
|
||||
):
|
||||
parse_run_input(payload)
|
||||
|
||||
|
||||
def test_parse_run_input_rejects_payload_over_limit() -> None:
|
||||
payload = _base_payload()
|
||||
payload["forwardedProps"] = {"blob": "x" * MAX_RUN_INPUT_BYTES}
|
||||
|
||||
with pytest.raises(ValueError, match="RunAgentInput payload exceeds size limit"):
|
||||
parse_run_input(payload)
|
||||
|
||||
|
||||
def test_parse_run_input_rejects_run_id_over_limit() -> None:
|
||||
payload = _base_payload()
|
||||
payload["runId"] = "r" * (MAX_RUN_ID_LENGTH + 1)
|
||||
|
||||
with pytest.raises(ValueError, match="runId exceeds length limit"):
|
||||
parse_run_input(payload)
|
||||
|
||||
|
||||
def test_validate_run_request_messages_contract_requires_single_user_message() -> None:
|
||||
payload = _base_payload()
|
||||
payload["messages"] = [
|
||||
{"id": "u1", "role": "user", "content": "hello"},
|
||||
{"id": "u2", "role": "user", "content": "again"},
|
||||
]
|
||||
run_input = parse_run_input(payload)
|
||||
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match="RunAgentInput.messages must contain exactly one user message",
|
||||
):
|
||||
validate_run_request_messages_contract(run_input)
|
||||
|
||||
|
||||
def test_validate_run_request_messages_contract_accepts_binary_url_blocks() -> None:
|
||||
payload = _base_payload()
|
||||
payload["messages"] = [
|
||||
{
|
||||
"id": "u1",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "请分析"},
|
||||
{
|
||||
"type": "binary",
|
||||
"mimeType": "image/png",
|
||||
"url": "https://signed.example/a.png",
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
run_input = parse_run_input(payload)
|
||||
|
||||
validate_run_request_messages_contract(run_input)
|
||||
|
||||
|
||||
def test_validate_run_request_messages_contract_rejects_binary_data_block() -> None:
|
||||
payload = _base_payload()
|
||||
payload["messages"] = [
|
||||
{
|
||||
"id": "u1",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "请分析"},
|
||||
{
|
||||
"type": "binary",
|
||||
"mimeType": "image/png",
|
||||
"data": "aGVsbG8=",
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
run_input = parse_run_input(payload)
|
||||
|
||||
with pytest.raises(ValueError, match="binary content requires url"):
|
||||
validate_run_request_messages_contract(run_input)
|
||||
|
||||
|
||||
def test_parse_run_input_accepts_snake_case_aliases() -> None:
|
||||
payload = {
|
||||
"thread_id": "00000000-0000-0000-0000-000000000001",
|
||||
"run_id": "run-1",
|
||||
"state": {},
|
||||
"messages": [
|
||||
{
|
||||
"id": "u1",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "hello"},
|
||||
{
|
||||
"type": "binary",
|
||||
"mime_type": "image/png",
|
||||
"url": "https://signed.example/a.png",
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
"tools": [],
|
||||
"context": [],
|
||||
"forwarded_props": {"agent_type": "worker"},
|
||||
}
|
||||
|
||||
run_input = parse_run_input(payload)
|
||||
|
||||
assert run_input.thread_id == "00000000-0000-0000-0000-000000000001"
|
||||
assert run_input.run_id == "run-1"
|
||||
validate_run_request_messages_contract(run_input)
|
||||
|
||||
|
||||
def test_parse_run_input_accepts_client_time_forwarded_props() -> None:
|
||||
payload = _base_payload()
|
||||
payload["forwardedProps"] = {
|
||||
"agent_type": "worker",
|
||||
"client_time": {
|
||||
"device_timezone": "America/Los_Angeles",
|
||||
"client_now_iso": "2026-03-16T09:12:33-07:00",
|
||||
"client_epoch_ms": 1773658353000,
|
||||
},
|
||||
}
|
||||
|
||||
run_input = parse_run_input(payload)
|
||||
|
||||
assert run_input.forwarded_props is not None
|
||||
|
||||
|
||||
def test_parse_run_input_rejects_invalid_client_time_timezone() -> None:
|
||||
payload = _base_payload()
|
||||
payload["forwardedProps"] = {
|
||||
"agent_type": "worker",
|
||||
"client_time": {
|
||||
"device_timezone": "Mars/OlympusMons",
|
||||
"client_now_iso": "2026-03-16T09:12:33-07:00",
|
||||
"client_epoch_ms": 1773658353000,
|
||||
},
|
||||
}
|
||||
|
||||
with pytest.raises(ValueError, match="invalid RunAgentInput.forwardedProps"):
|
||||
parse_run_input(payload)
|
||||
|
||||
|
||||
def test_parse_run_input_rejects_invalid_client_time_now_iso() -> None:
|
||||
payload = _base_payload()
|
||||
payload["forwardedProps"] = {
|
||||
"agent_type": "worker",
|
||||
"client_time": {
|
||||
"device_timezone": "America/Los_Angeles",
|
||||
"client_now_iso": "2026-03-16 09:12:33",
|
||||
"client_epoch_ms": 1773658353000,
|
||||
},
|
||||
}
|
||||
|
||||
with pytest.raises(ValueError, match="invalid RunAgentInput.forwardedProps"):
|
||||
parse_run_input(payload)
|
||||
|
||||
|
||||
def test_parse_run_input_rejects_invalid_client_time_epoch_type() -> None:
|
||||
payload = _base_payload()
|
||||
payload["forwardedProps"] = {
|
||||
"agent_type": "worker",
|
||||
"client_time": {
|
||||
"device_timezone": "America/Los_Angeles",
|
||||
"client_now_iso": "2026-03-16T09:12:33-07:00",
|
||||
"client_epoch_ms": "1773658353000",
|
||||
},
|
||||
}
|
||||
|
||||
with pytest.raises(ValueError, match="invalid RunAgentInput.forwardedProps"):
|
||||
parse_run_input(payload)
|
||||
|
||||
|
||||
def test_parse_run_input_rejects_unknown_forwarded_props_key() -> None:
|
||||
payload = _base_payload()
|
||||
payload["forwardedProps"] = {
|
||||
"agent_type": "worker",
|
||||
"client_time": {
|
||||
"device_timezone": "America/Los_Angeles",
|
||||
"client_now_iso": "2026-03-16T09:12:33-07:00",
|
||||
"client_epoch_ms": 1773658353000,
|
||||
},
|
||||
"unexpected": {"foo": "bar"},
|
||||
}
|
||||
|
||||
with pytest.raises(ValueError, match="invalid RunAgentInput.forwardedProps"):
|
||||
parse_run_input(payload)
|
||||
|
||||
|
||||
def test_parse_run_input_rejects_missing_forwarded_props_agent_type() -> None:
|
||||
payload = _base_payload()
|
||||
payload["forwardedProps"] = {
|
||||
"client_time": {
|
||||
"device_timezone": "America/Los_Angeles",
|
||||
"client_now_iso": "2026-03-16T09:12:33-07:00",
|
||||
"client_epoch_ms": 1773658353000,
|
||||
}
|
||||
}
|
||||
|
||||
with pytest.raises(ValueError, match="invalid RunAgentInput.forwardedProps"):
|
||||
parse_run_input(payload)
|
||||
@@ -40,6 +40,7 @@ class _FakeService:
|
||||
start_at=datetime(2026, 3, 17, 9, 0, tzinfo=timezone.utc),
|
||||
end_at=datetime(2026, 3, 17, 9, 30, tzinfo=timezone.utc),
|
||||
timezone="Asia/Shanghai",
|
||||
status="active",
|
||||
metadata=SimpleNamespace(
|
||||
location=None, color="#4F46E5", reminder_minutes=15
|
||||
),
|
||||
@@ -247,7 +248,7 @@ async def test_calendar_read_returns_structured_result_with_ids(
|
||||
assert "total=1" in payload["result"]
|
||||
assert "timezone=Asia/Shanghai" in payload["result"]
|
||||
assert "description=今天下午五点的会议" in payload["result"]
|
||||
assert "status=" in payload["result"]
|
||||
assert "status=active" in payload["result"]
|
||||
assert fake_service.created_id in payload["result"]
|
||||
assert fake_service.list_calls == [{"page": 1, "page_size": 20, "query": "会议"}]
|
||||
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, AsyncGenerator
|
||||
|
||||
import pytest
|
||||
|
||||
from core.agentscope.tools.tool_config import ToolApprovalConfig, ToolConfig, ToolGroup
|
||||
from core.agentscope.tools.tool_middleware import create_approval_middleware
|
||||
|
||||
|
||||
async def _next_handler(**kwargs: Any) -> AsyncGenerator[dict[str, object], None]:
|
||||
async def _generator() -> AsyncGenerator[dict[str, object], None]:
|
||||
yield {"ok": True, "tool_call": kwargs.get("tool_call")}
|
||||
|
||||
return _generator()
|
||||
|
||||
|
||||
def _extract_error_payload(chunk: object) -> dict[str, Any]:
|
||||
content = getattr(chunk, "content", None)
|
||||
if not isinstance(content, list) or not content:
|
||||
return {}
|
||||
first_block = content[0]
|
||||
text = getattr(first_block, "text", None)
|
||||
if not isinstance(text, str) and isinstance(first_block, dict):
|
||||
raw_text = first_block.get("text")
|
||||
text = raw_text if isinstance(raw_text, str) else None
|
||||
if not isinstance(text, str):
|
||||
return {}
|
||||
return json.loads(text)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_hitl_middleware_default_write_does_not_require_approval() -> None:
|
||||
middleware = create_approval_middleware(
|
||||
config_by_name={
|
||||
"calendar_write": ToolConfig(
|
||||
name="calendar_write",
|
||||
group=ToolGroup.EXECUTE,
|
||||
approval=ToolApprovalConfig(required=False),
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
responses = []
|
||||
async for chunk in middleware(
|
||||
{"tool_call": {"name": "calendar.write", "input": {"operation": "create"}}},
|
||||
_next_handler,
|
||||
):
|
||||
responses.append(chunk)
|
||||
|
||||
assert responses[0]["ok"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_hitl_middleware_pending_when_tool_requires_approval() -> None:
|
||||
middleware = create_approval_middleware(
|
||||
config_by_name={
|
||||
"calendar_write": ToolConfig(
|
||||
name="calendar_write",
|
||||
group=ToolGroup.EXECUTE,
|
||||
approval=ToolApprovalConfig(required=True),
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
responses = []
|
||||
async for chunk in middleware(
|
||||
{"tool_call": {"name": "calendar_write", "input": {"operation": "create"}}},
|
||||
_next_handler,
|
||||
):
|
||||
responses.append(chunk)
|
||||
|
||||
payload = _extract_error_payload(responses[0])
|
||||
assert payload["error"]["code"] == "TOOL_PENDING_APPROVAL"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_hitl_middleware_passes_when_write_approved() -> None:
|
||||
middleware = create_approval_middleware(
|
||||
config_by_name={
|
||||
"calendar_write": ToolConfig(
|
||||
name="calendar_write",
|
||||
group=ToolGroup.EXECUTE,
|
||||
approval=ToolApprovalConfig(required=True),
|
||||
)
|
||||
},
|
||||
approval_resolver=lambda _name, _args, _config: "approved",
|
||||
)
|
||||
|
||||
responses = []
|
||||
async for chunk in middleware(
|
||||
{
|
||||
"tool_call": {
|
||||
"name": "calendar.write",
|
||||
"input": {
|
||||
"operation": "create",
|
||||
"_hitl": {"approval": "required"},
|
||||
},
|
||||
}
|
||||
},
|
||||
_next_handler,
|
||||
):
|
||||
responses.append(chunk)
|
||||
|
||||
assert responses[0]["ok"] is True
|
||||
sanitized_input = responses[0]["tool_call"]["input"]
|
||||
assert "_hitl" not in sanitized_input
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_hitl_middleware_rejected_short_circuits() -> None:
|
||||
middleware = create_approval_middleware(
|
||||
config_by_name={
|
||||
"calendar_write": ToolConfig(
|
||||
name="calendar_write",
|
||||
group=ToolGroup.EXECUTE,
|
||||
approval=ToolApprovalConfig(required=True),
|
||||
)
|
||||
},
|
||||
approval_resolver=lambda _name, _args, _config: "rejected",
|
||||
)
|
||||
|
||||
responses = []
|
||||
async for chunk in middleware(
|
||||
{"tool_call": {"name": "calendar_write", "input": {"operation": "create"}}},
|
||||
_next_handler,
|
||||
):
|
||||
responses.append(chunk)
|
||||
|
||||
payload = _extract_error_payload(responses[0])
|
||||
assert payload["error"]["code"] == "TOOL_REJECTED"
|
||||
@@ -9,7 +9,7 @@ from agentscope.tool import ToolResponse
|
||||
|
||||
from core.agentscope.tools.custom import memory as memory_module
|
||||
from models.memories import MemoryType
|
||||
from schemas.memories.memory_content import UserMemoryContent
|
||||
from schemas.domain.memory_content import UserMemoryContent
|
||||
|
||||
|
||||
def _decode_tool_response(response: ToolResponse) -> dict[str, object]:
|
||||
|
||||
@@ -9,7 +9,7 @@ from core.agentscope.prompts.system_prompt import (
|
||||
)
|
||||
from schemas.agent.forwarded_props import ClientTimeContext
|
||||
from schemas.agent.system_agent import AgentType
|
||||
from schemas.user.context import UserContext, parse_profile_settings
|
||||
from schemas.shared.user import UserContext, parse_profile_settings
|
||||
|
||||
|
||||
def _build_user_context(*, timezone_name: str = "Asia/Shanghai") -> UserContext:
|
||||
@@ -159,7 +159,7 @@ def test_build_system_prompt_keeps_sections_focused_without_language_duplication
|
||||
|
||||
|
||||
def test_build_system_prompt_includes_user_memory_section_for_router() -> None:
|
||||
from schemas.memories.memory_content import UserMemoryContent
|
||||
from schemas.domain.memory_content import UserMemoryContent
|
||||
|
||||
user_memory = UserMemoryContent()
|
||||
|
||||
@@ -175,7 +175,7 @@ def test_build_system_prompt_includes_user_memory_section_for_router() -> None:
|
||||
|
||||
|
||||
def test_build_system_prompt_includes_work_memory_section_for_worker() -> None:
|
||||
from schemas.memories.memory_content import WorkProfileContent
|
||||
from schemas.domain.memory_content import WorkProfileContent
|
||||
|
||||
work_memory = WorkProfileContent()
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from uuid import UUID, uuid4
|
||||
import pytest
|
||||
|
||||
from models.automation_jobs import AutomationJob as OrmAutomationJob, ScheduleType
|
||||
from schemas.automation import (
|
||||
from schemas.domain.automation import (
|
||||
RuntimeConfig,
|
||||
ScheduleConfig,
|
||||
ScheduleRunAt,
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import Column, String, Table
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, SoftDeleteMixin
|
||||
from core.db.base_repository import BaseRepository
|
||||
|
||||
|
||||
class Widget(SoftDeleteMixin, Base):
|
||||
__tablename__ = "widgets"
|
||||
|
||||
id: Mapped[UUID] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def db_engine():
|
||||
auth_users = Table(
|
||||
"users",
|
||||
Base.metadata,
|
||||
Column("id", String, primary_key=True),
|
||||
schema="auth",
|
||||
extend_existing=True,
|
||||
)
|
||||
engine = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False)
|
||||
async with engine.begin() as conn:
|
||||
await conn.exec_driver_sql("ATTACH DATABASE ':memory:' AS auth")
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
yield engine
|
||||
Base.metadata.remove(auth_users)
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def db_session(db_engine):
|
||||
async_session = async_sessionmaker(
|
||||
bind=db_engine,
|
||||
class_=AsyncSession,
|
||||
expire_on_commit=False,
|
||||
)
|
||||
async with async_session() as session:
|
||||
yield session
|
||||
await session.rollback()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_by_id_filters_soft_deleted(db_session: AsyncSession) -> None:
|
||||
repository = BaseRepository(db_session, Widget)
|
||||
widget_id = uuid4()
|
||||
|
||||
widget = Widget(id=widget_id, name="widget")
|
||||
db_session.add(widget)
|
||||
await db_session.commit()
|
||||
|
||||
found = await repository.get_by_id(widget_id)
|
||||
assert found is not None
|
||||
|
||||
deleted = await repository.soft_delete_by_id(widget_id)
|
||||
assert deleted is not None
|
||||
assert deleted.deleted_at is not None
|
||||
|
||||
missing = await repository.get_by_id(widget_id)
|
||||
assert missing is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_soft_delete_sets_timestamp(db_session: AsyncSession) -> None:
|
||||
repository = BaseRepository(db_session, Widget)
|
||||
widget_id = uuid4()
|
||||
|
||||
widget = Widget(id=widget_id, name="widget")
|
||||
db_session.add(widget)
|
||||
await db_session.commit()
|
||||
|
||||
deleted = await repository.soft_delete_by_id(widget_id)
|
||||
assert deleted is not None
|
||||
assert isinstance(deleted.deleted_at, datetime)
|
||||
deleted_at = deleted.deleted_at
|
||||
if deleted_at.tzinfo is None:
|
||||
deleted_at = deleted_at.replace(tzinfo=timezone.utc)
|
||||
assert deleted_at <= datetime.now(timezone.utc)
|
||||
@@ -1,134 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import Column, String, Table, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
|
||||
from core.db.base import Base
|
||||
from models.profile import Profile
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def db_engine():
|
||||
"""Create in-memory SQLite engine for testing."""
|
||||
users_table = Table(
|
||||
"users",
|
||||
Base.metadata,
|
||||
Column("id", String, primary_key=True),
|
||||
schema="auth",
|
||||
extend_existing=True,
|
||||
)
|
||||
engine = create_async_engine(
|
||||
"sqlite+aiosqlite:///:memory:",
|
||||
echo=False,
|
||||
)
|
||||
async with engine.begin() as conn:
|
||||
await conn.exec_driver_sql("ATTACH DATABASE ':memory:' AS auth")
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
yield engine
|
||||
Base.metadata.remove(users_table)
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def db_session(db_engine):
|
||||
"""Create a database session for testing."""
|
||||
async_session = async_sessionmaker(
|
||||
bind=db_engine,
|
||||
class_=AsyncSession,
|
||||
expire_on_commit=False,
|
||||
)
|
||||
async with async_session() as session:
|
||||
yield session
|
||||
await session.rollback()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_profile_model_create(db_session: AsyncSession) -> None:
|
||||
"""Test creating a Profile model."""
|
||||
profile_id = uuid4()
|
||||
profile = Profile(
|
||||
id=profile_id,
|
||||
username="testuser",
|
||||
)
|
||||
db_session.add(profile)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(profile)
|
||||
|
||||
assert profile.id == profile_id
|
||||
assert profile.username == "testuser"
|
||||
assert profile.created_at is not None
|
||||
assert profile.updated_at is not None
|
||||
assert profile.deleted_at is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_profile_model_get_by_id(db_session: AsyncSession) -> None:
|
||||
"""Test retrieving a Profile by ID."""
|
||||
profile_id = uuid4()
|
||||
profile = Profile(
|
||||
id=profile_id,
|
||||
username="testuser",
|
||||
)
|
||||
db_session.add(profile)
|
||||
await db_session.commit()
|
||||
|
||||
result = await db_session.get(Profile, profile_id)
|
||||
assert result is not None
|
||||
assert result.username == "testuser"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_profile_model_get_by_username(db_session: AsyncSession) -> None:
|
||||
"""Test retrieving a Profile by username."""
|
||||
profile = Profile(
|
||||
id=uuid4(),
|
||||
username="testuser",
|
||||
)
|
||||
db_session.add(profile)
|
||||
await db_session.commit()
|
||||
|
||||
result = await db_session.execute(
|
||||
select(Profile).where(Profile.username == "testuser")
|
||||
)
|
||||
found = result.scalar_one()
|
||||
assert found is not None
|
||||
assert found.username == "testuser"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_profile_model_update(db_session: AsyncSession) -> None:
|
||||
"""Test updating a Profile."""
|
||||
profile = Profile(
|
||||
id=uuid4(),
|
||||
username="testuser",
|
||||
bio="Old bio",
|
||||
)
|
||||
db_session.add(profile)
|
||||
await db_session.commit()
|
||||
|
||||
profile.bio = "New bio"
|
||||
await db_session.commit()
|
||||
await db_session.refresh(profile)
|
||||
|
||||
assert profile.bio == "New bio"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_profile_model_allows_duplicate_usernames(
|
||||
db_session: AsyncSession,
|
||||
) -> None:
|
||||
first = Profile(id=uuid4(), username="same_name")
|
||||
second = Profile(id=uuid4(), username="same_name")
|
||||
|
||||
db_session.add(first)
|
||||
db_session.add(second)
|
||||
await db_session.commit()
|
||||
|
||||
result = await db_session.execute(
|
||||
select(Profile).where(Profile.username == "same_name")
|
||||
)
|
||||
found = result.scalars().all()
|
||||
assert len(found) == 2
|
||||
@@ -1,32 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT_DIR = Path(__file__).resolve().parents[4]
|
||||
APP_SCRIPT = ROOT_DIR / "infra" / "scripts" / "app.sh"
|
||||
|
||||
|
||||
def test_worker_commands_use_taskiq() -> None:
|
||||
content = APP_SCRIPT.read_text(encoding="utf-8")
|
||||
removed_runner = "uv run celery"
|
||||
|
||||
assert "uv run taskiq worker" in content
|
||||
assert "core.taskiq.app:critical_broker" in content
|
||||
assert "core.taskiq.app:default_broker" in content
|
||||
assert "core.taskiq.app:bulk_broker" in content
|
||||
assert 'pgrep -f "uv run taskiq worker core.taskiq.app:"' in content
|
||||
assert 'kill_pids_gracefully "taskiq workers"' in content
|
||||
assert "gunicorn" not in content
|
||||
assert removed_runner not in content
|
||||
|
||||
|
||||
def test_web_command_uses_uvicorn_only() -> None:
|
||||
content = APP_SCRIPT.read_text(encoding="utf-8")
|
||||
|
||||
assert "uv run uvicorn app:app" in content
|
||||
assert 'WEB_PORT="${SOCIAL_WEB__PORT:-5775}"' in content
|
||||
assert "SOCIAL_WEB__WORKERS" in content
|
||||
assert 'UVICORN_LOG_LEVEL="${UVICORN_LOG_LEVEL,,}"' in content
|
||||
assert "SOCIAL_WEB__GUNICORN__" not in content
|
||||
assert "uv run gunicorn" not in content
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user