mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Fixes + Cleanup
This commit is contained in:
+2
-2
@@ -117,8 +117,8 @@ def create_app() -> FastAPI:
|
||||
title="RoboCo API",
|
||||
description="AI Agents Company - Messaging and Task Management API",
|
||||
version=settings.app_version,
|
||||
docs_url="/docs", # if settings.debug else None,
|
||||
redoc_url="/redoc", # if settings.debug else None,
|
||||
docs_url="/docs", # if settings.debug else None,
|
||||
redoc_url="/redoc", # if settings.debug else None,
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
|
||||
+26
-2
@@ -4,9 +4,10 @@ API Dependencies
|
||||
Shared dependencies for FastAPI routes.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
from collections.abc import Callable, Coroutine
|
||||
from typing import Annotated, Any, cast
|
||||
from typing import TYPE_CHECKING, Annotated, Any, cast
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import Depends, Header, HTTPException, status
|
||||
@@ -16,8 +17,12 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from roboco.db.base import get_db
|
||||
from roboco.db.tables import AgentTable
|
||||
from roboco.models import AgentRole, Team
|
||||
from roboco.runtime import AgentOrchestrator
|
||||
from roboco.services.permissions import AgentContext, PermissionService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Coroutine
|
||||
|
||||
# Type alias for database session dependency
|
||||
DbSession = Annotated[AsyncSession, Depends(get_db)]
|
||||
|
||||
@@ -61,6 +66,7 @@ class _ServiceHolder:
|
||||
"""Holder for singleton service instances."""
|
||||
|
||||
permission_service: PermissionService | None = None
|
||||
orchestrator: AgentOrchestrator | None = None
|
||||
|
||||
|
||||
def get_permission_service() -> PermissionService:
|
||||
@@ -73,6 +79,24 @@ def get_permission_service() -> PermissionService:
|
||||
PermissionServiceDep = Annotated[PermissionService, Depends(get_permission_service)]
|
||||
|
||||
|
||||
def set_orchestrator(orchestrator: AgentOrchestrator) -> None:
|
||||
"""Set the global orchestrator instance."""
|
||||
_ServiceHolder.orchestrator = orchestrator
|
||||
|
||||
|
||||
def get_orchestrator() -> AgentOrchestrator:
|
||||
"""Get the global orchestrator instance."""
|
||||
if _ServiceHolder.orchestrator is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Orchestrator not initialized",
|
||||
)
|
||||
return _ServiceHolder.orchestrator
|
||||
|
||||
|
||||
OrchestratorDep = Annotated[AgentOrchestrator, Depends(get_orchestrator)]
|
||||
|
||||
|
||||
async def get_current_agent_id(
|
||||
db: DbSession,
|
||||
x_agent_id: Annotated[str | None, Header()] = None,
|
||||
|
||||
@@ -8,31 +8,16 @@ from typing import cast
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query, status
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import select
|
||||
|
||||
from roboco.api.deps import DbSession
|
||||
from roboco.api.schemas.agents import AgentResponse
|
||||
from roboco.db.tables import AgentTable
|
||||
from roboco.models import AgentRole, Team
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class AgentResponse(BaseModel):
|
||||
"""Response model for agent information."""
|
||||
|
||||
id: UUID
|
||||
name: str
|
||||
slug: str
|
||||
role: AgentRole
|
||||
team: Team | None
|
||||
|
||||
class Config:
|
||||
"""Pydantic config."""
|
||||
|
||||
from_attributes = True
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_agents(
|
||||
db: DbSession,
|
||||
@@ -96,14 +81,10 @@ async def get_agent(
|
||||
# Try to parse as UUID first
|
||||
try:
|
||||
uuid = UUID(agent_id)
|
||||
result = await db.execute(
|
||||
select(AgentTable).where(AgentTable.id == uuid)
|
||||
)
|
||||
result = await db.execute(select(AgentTable).where(AgentTable.id == uuid))
|
||||
except ValueError:
|
||||
# Not a UUID, try slug lookup
|
||||
result = await db.execute(
|
||||
select(AgentTable).where(AgentTable.slug == agent_id)
|
||||
)
|
||||
result = await db.execute(select(AgentTable).where(AgentTable.slug == agent_id))
|
||||
|
||||
agent = result.scalar_one_or_none()
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ from roboco.api.schemas.channels import (
|
||||
ChannelResponse,
|
||||
GroupResponse,
|
||||
ListChannelsQuery,
|
||||
apply_channel_updates,
|
||||
get_channel_or_404,
|
||||
require_channel_admin,
|
||||
)
|
||||
from roboco.db.tables import ChannelTable
|
||||
from roboco.models import AgentRole, ChannelCreate, ChannelUpdate
|
||||
@@ -25,37 +28,6 @@ from roboco.utils.converters import require_uuid, to_python_uuid
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Roles authorized to manage channels
|
||||
CHANNEL_ADMIN_ROLES = frozenset(
|
||||
{AgentRole.CEO, AgentRole.PRODUCT_OWNER, AgentRole.MAIN_PM}
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Helpers
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def _require_channel_admin(agent: CurrentAgentContext) -> None:
|
||||
"""Raise 403 if agent is not authorized to manage channels."""
|
||||
if agent.role not in CHANNEL_ADMIN_ROLES:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not authorized to manage channels",
|
||||
)
|
||||
|
||||
|
||||
async def _get_channel_or_404(db: DbSession, channel_id: UUID) -> ChannelTable:
|
||||
"""Get channel by ID or raise 404."""
|
||||
result = await db.execute(select(ChannelTable).where(ChannelTable.id == channel_id))
|
||||
channel = result.scalar_one_or_none()
|
||||
if not channel:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Channel not found",
|
||||
)
|
||||
return channel
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Routes
|
||||
@@ -300,27 +272,6 @@ async def create_channel(
|
||||
)
|
||||
|
||||
|
||||
# Fields that can be updated on a channel
|
||||
_CHANNEL_UPDATE_FIELDS = (
|
||||
"name",
|
||||
"description",
|
||||
"topic",
|
||||
"is_archived",
|
||||
"allow_threads",
|
||||
"allow_reactions",
|
||||
"message_retention_days",
|
||||
"max_message_length",
|
||||
)
|
||||
|
||||
|
||||
def _apply_channel_updates(channel: ChannelTable, data: ChannelUpdate) -> None:
|
||||
"""Apply updates to channel fields."""
|
||||
for field in _CHANNEL_UPDATE_FIELDS:
|
||||
value = getattr(data, field, None)
|
||||
if value is not None:
|
||||
setattr(channel, field, value)
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/{channel_id}",
|
||||
response_model=ChannelResponse,
|
||||
@@ -335,9 +286,9 @@ async def update_channel(
|
||||
data: ChannelUpdate,
|
||||
) -> ChannelResponse:
|
||||
"""Update channel settings."""
|
||||
_require_channel_admin(agent)
|
||||
channel = await _get_channel_or_404(db, channel_id)
|
||||
_apply_channel_updates(channel, data)
|
||||
require_channel_admin(agent)
|
||||
channel = await get_channel_or_404(db, ChannelTable, channel_id)
|
||||
apply_channel_updates(channel, data)
|
||||
await db.flush()
|
||||
|
||||
return ChannelResponse(
|
||||
@@ -370,8 +321,8 @@ async def add_member(
|
||||
can_write: bool = Query(True),
|
||||
) -> None:
|
||||
"""Add a member to the channel."""
|
||||
_require_channel_admin(agent)
|
||||
channel = await _get_channel_or_404(db, channel_id)
|
||||
require_channel_admin(agent)
|
||||
channel = await get_channel_or_404(db, ChannelTable, channel_id)
|
||||
|
||||
# Add to members if not already present
|
||||
if member_id not in channel.members:
|
||||
@@ -397,8 +348,8 @@ async def remove_member(
|
||||
member_id: UUID,
|
||||
) -> None:
|
||||
"""Remove a member from the channel."""
|
||||
_require_channel_admin(agent)
|
||||
channel = await _get_channel_or_404(db, channel_id)
|
||||
require_channel_admin(agent)
|
||||
channel = await get_channel_or_404(db, ChannelTable, channel_id)
|
||||
|
||||
# Remove from members and writers
|
||||
channel.members = [m for m in channel.members if m != member_id]
|
||||
|
||||
@@ -4,38 +4,15 @@ Health Check Routes
|
||||
Endpoints for monitoring application health and readiness.
|
||||
"""
|
||||
|
||||
import redis.asyncio as redis
|
||||
from fastapi import APIRouter, status
|
||||
from sqlalchemy import text
|
||||
|
||||
from roboco.api.schemas.health import HealthResponse, ReadinessResponse
|
||||
from roboco.config import settings
|
||||
from roboco.db.base import get_db_context
|
||||
from roboco.services.health import check_database, check_redis
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
async def _check_database() -> tuple[str, bool]:
|
||||
"""Check database connectivity."""
|
||||
try:
|
||||
async with get_db_context() as session:
|
||||
await session.execute(text("SELECT 1"))
|
||||
return "ok", True
|
||||
except Exception as e:
|
||||
return str(e), False
|
||||
|
||||
|
||||
async def _check_redis() -> tuple[str, bool]:
|
||||
"""Check Redis connectivity."""
|
||||
try:
|
||||
client = redis.from_url(settings.redis_url)
|
||||
await client.ping()
|
||||
await client.close()
|
||||
return "ok", True
|
||||
except Exception as e:
|
||||
return str(e), False
|
||||
|
||||
|
||||
@router.get(
|
||||
"/health",
|
||||
response_model=HealthResponse,
|
||||
@@ -61,8 +38,8 @@ async def health_check() -> HealthResponse:
|
||||
)
|
||||
async def readiness_check() -> ReadinessResponse:
|
||||
"""Check if all dependencies are ready."""
|
||||
db_status, db_ok = await _check_database()
|
||||
redis_status, redis_ok = await _check_redis()
|
||||
db_status, db_ok = await check_database()
|
||||
redis_status, redis_ok = await check_redis()
|
||||
|
||||
overall = "ok" if (db_ok and redis_ok) else "degraded"
|
||||
|
||||
|
||||
@@ -18,13 +18,18 @@ from roboco.api.schemas.notifications import (
|
||||
NotificationCreateRequest,
|
||||
NotificationListResponse,
|
||||
NotificationResponse,
|
||||
build_notification_query,
|
||||
notification_to_response,
|
||||
)
|
||||
from roboco.db.tables import AgentTable, NotificationTable
|
||||
from roboco.enforcement import (
|
||||
NotificationPermissionError,
|
||||
validate_notification_permission,
|
||||
)
|
||||
from roboco.utils.converters import require_uuid, to_python_uuid, to_python_uuid_list
|
||||
from roboco.services.notification import (
|
||||
get_notification_or_404,
|
||||
require_notification_recipient,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -34,49 +39,6 @@ router = APIRouter()
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def _build_notification_query(
|
||||
agent_id: UUID,
|
||||
params: ListNotificationsParams,
|
||||
) -> Any:
|
||||
"""Build the notification query with filters."""
|
||||
query = select(NotificationTable).where(
|
||||
NotificationTable.to_agents.contains([agent_id])
|
||||
)
|
||||
if params.unread_only:
|
||||
query = query.where(~NotificationTable.read_by.contains([agent_id]))
|
||||
if params.pending_ack_only:
|
||||
query = query.where(
|
||||
NotificationTable.requires_ack.is_(True),
|
||||
~NotificationTable.acked_by.contains([agent_id]),
|
||||
)
|
||||
if params.type_filter:
|
||||
query = query.where(NotificationTable.type == params.type_filter)
|
||||
return query.order_by(NotificationTable.timestamp.desc()).limit(params.limit)
|
||||
|
||||
|
||||
def _notification_to_response(
|
||||
n: NotificationTable,
|
||||
agent_id: UUID,
|
||||
) -> NotificationResponse:
|
||||
"""Convert a notification to response format."""
|
||||
return NotificationResponse(
|
||||
id=require_uuid(n.id),
|
||||
type=n.type,
|
||||
priority=n.priority,
|
||||
from_agent=require_uuid(n.from_agent),
|
||||
to_agents=to_python_uuid_list(n.to_agents),
|
||||
subject=n.subject,
|
||||
body=n.body,
|
||||
requires_ack=n.requires_ack,
|
||||
is_acknowledged=agent_id in n.acked_by,
|
||||
is_fully_acknowledged=all(a in n.acked_by for a in n.to_agents),
|
||||
is_read=agent_id in n.read_by,
|
||||
related_task_id=to_python_uuid(n.related_task_id),
|
||||
timestamp=n.timestamp,
|
||||
expires_at=n.expires_at,
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=NotificationListResponse,
|
||||
@@ -89,7 +51,7 @@ async def list_notifications(
|
||||
params: Annotated[ListNotificationsParams, Depends()],
|
||||
) -> NotificationListResponse:
|
||||
"""List notifications for the agent."""
|
||||
query = _build_notification_query(agent_id, params)
|
||||
query = build_notification_query(NotificationTable, agent_id, params)
|
||||
result: Any = await db.execute(query)
|
||||
notifications = result.scalars().all()
|
||||
|
||||
@@ -97,7 +59,7 @@ async def list_notifications(
|
||||
pending_ack_count = sum(
|
||||
1 for n in notifications if n.requires_ack and agent_id not in n.acked_by
|
||||
)
|
||||
items = [_notification_to_response(n, agent_id) for n in notifications]
|
||||
items = [notification_to_response(n, agent_id) for n in notifications]
|
||||
|
||||
return NotificationListResponse(
|
||||
items=items,
|
||||
@@ -119,47 +81,15 @@ async def get_notification(
|
||||
notification_id: UUID,
|
||||
) -> NotificationResponse:
|
||||
"""Get a notification."""
|
||||
result = await db.execute(
|
||||
select(NotificationTable).where(NotificationTable.id == notification_id)
|
||||
)
|
||||
notification = result.scalar_one_or_none()
|
||||
|
||||
if not notification:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Notification not found",
|
||||
)
|
||||
|
||||
# Check if agent is a recipient
|
||||
if agent_id not in notification.to_agents:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="You are not a recipient of this notification",
|
||||
)
|
||||
notification = await get_notification_or_404(db, notification_id)
|
||||
require_notification_recipient(notification, agent_id)
|
||||
|
||||
# Mark as read
|
||||
if agent_id not in notification.read_by:
|
||||
notification.read_by = [*notification.read_by, agent_id]
|
||||
await db.flush()
|
||||
|
||||
return NotificationResponse(
|
||||
id=require_uuid(notification.id),
|
||||
type=notification.type,
|
||||
priority=notification.priority,
|
||||
from_agent=require_uuid(notification.from_agent),
|
||||
to_agents=to_python_uuid_list(notification.to_agents),
|
||||
subject=notification.subject,
|
||||
body=notification.body,
|
||||
requires_ack=notification.requires_ack,
|
||||
is_acknowledged=agent_id in notification.acked_by,
|
||||
is_fully_acknowledged=all(
|
||||
a in notification.acked_by for a in notification.to_agents
|
||||
),
|
||||
is_read=True,
|
||||
related_task_id=to_python_uuid(notification.related_task_id),
|
||||
timestamp=notification.timestamp,
|
||||
expires_at=notification.expires_at,
|
||||
)
|
||||
return notification_to_response(notification, agent_id)
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -229,22 +159,7 @@ async def send_notification(
|
||||
db.add(notification)
|
||||
await db.flush()
|
||||
|
||||
return NotificationResponse(
|
||||
id=require_uuid(notification.id),
|
||||
type=notification.type,
|
||||
priority=notification.priority,
|
||||
from_agent=require_uuid(notification.from_agent),
|
||||
to_agents=to_python_uuid_list(notification.to_agents),
|
||||
subject=notification.subject,
|
||||
body=notification.body,
|
||||
requires_ack=notification.requires_ack,
|
||||
is_acknowledged=False,
|
||||
is_fully_acknowledged=False,
|
||||
is_read=False,
|
||||
related_task_id=to_python_uuid(notification.related_task_id),
|
||||
timestamp=notification.timestamp,
|
||||
expires_at=notification.expires_at,
|
||||
)
|
||||
return notification_to_response(notification, agent_id)
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -259,23 +174,8 @@ async def acknowledge_notification(
|
||||
notification_id: UUID,
|
||||
) -> NotificationResponse:
|
||||
"""Acknowledge a notification."""
|
||||
result = await db.execute(
|
||||
select(NotificationTable).where(NotificationTable.id == notification_id)
|
||||
)
|
||||
notification = result.scalar_one_or_none()
|
||||
|
||||
if not notification:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Notification not found",
|
||||
)
|
||||
|
||||
# Check if agent is a recipient
|
||||
if agent_id not in notification.to_agents:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="You are not a recipient of this notification",
|
||||
)
|
||||
notification = await get_notification_or_404(db, notification_id)
|
||||
require_notification_recipient(notification, agent_id)
|
||||
|
||||
if not notification.requires_ack:
|
||||
raise HTTPException(
|
||||
@@ -296,25 +196,7 @@ async def acknowledge_notification(
|
||||
notification.read_by = [*notification.read_by, agent_id]
|
||||
|
||||
await db.flush()
|
||||
|
||||
return NotificationResponse(
|
||||
id=require_uuid(notification.id),
|
||||
type=notification.type,
|
||||
priority=notification.priority,
|
||||
from_agent=require_uuid(notification.from_agent),
|
||||
to_agents=to_python_uuid_list(notification.to_agents),
|
||||
subject=notification.subject,
|
||||
body=notification.body,
|
||||
requires_ack=notification.requires_ack,
|
||||
is_acknowledged=True,
|
||||
is_fully_acknowledged=all(
|
||||
a in notification.acked_by for a in notification.to_agents
|
||||
),
|
||||
is_read=True,
|
||||
related_task_id=to_python_uuid(notification.related_task_id),
|
||||
timestamp=notification.timestamp,
|
||||
expires_at=notification.expires_at,
|
||||
)
|
||||
return notification_to_response(notification, agent_id)
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -329,22 +211,8 @@ async def mark_as_read(
|
||||
notification_id: UUID,
|
||||
) -> None:
|
||||
"""Mark a notification as read."""
|
||||
result = await db.execute(
|
||||
select(NotificationTable).where(NotificationTable.id == notification_id)
|
||||
)
|
||||
notification = result.scalar_one_or_none()
|
||||
|
||||
if not notification:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Notification not found",
|
||||
)
|
||||
|
||||
if agent_id not in notification.to_agents:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="You are not a recipient of this notification",
|
||||
)
|
||||
notification = await get_notification_or_404(db, notification_id)
|
||||
require_notification_recipient(notification, agent_id)
|
||||
|
||||
if agent_id not in notification.read_by:
|
||||
notification.read_by = [*notification.read_by, agent_id]
|
||||
|
||||
@@ -8,6 +8,7 @@ from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
|
||||
from roboco.api.deps import get_orchestrator, set_orchestrator
|
||||
from roboco.api.schemas.orchestrator import (
|
||||
AgentStatusResponse,
|
||||
OrchestratorStatusResponse,
|
||||
@@ -15,30 +16,11 @@ from roboco.api.schemas.orchestrator import (
|
||||
SpawnAgentRequest,
|
||||
WaitingAgentResponse,
|
||||
)
|
||||
from roboco.runtime import AgentOrchestrator
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class _OrchestratorHolder:
|
||||
"""Holder for orchestrator instance (set by bootstrap)."""
|
||||
|
||||
instance: AgentOrchestrator | None = None
|
||||
|
||||
|
||||
def set_orchestrator(orchestrator: AgentOrchestrator) -> None:
|
||||
"""Set the global orchestrator instance."""
|
||||
_OrchestratorHolder.instance = orchestrator
|
||||
|
||||
|
||||
def get_orchestrator() -> AgentOrchestrator:
|
||||
"""Get the global orchestrator instance."""
|
||||
if _OrchestratorHolder.instance is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Orchestrator not initialized",
|
||||
)
|
||||
return _OrchestratorHolder.instance
|
||||
# Re-export set_orchestrator for bootstrap code
|
||||
__all__ = ["router", "set_orchestrator"]
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
+45
-166
@@ -17,163 +17,29 @@ from roboco.api.deps import (
|
||||
)
|
||||
from roboco.api.schemas.tasks import (
|
||||
CheckpointRequest,
|
||||
CheckpointResponse,
|
||||
ClaimRequest,
|
||||
CommitRefResponse,
|
||||
CommitRequest,
|
||||
ListTasksQuery,
|
||||
ProgressRequest,
|
||||
ProgressUpdateResponse,
|
||||
QANotes,
|
||||
SubTaskResponse,
|
||||
TaskCountResponse,
|
||||
TaskPlanResponse,
|
||||
TaskResponse,
|
||||
TaskUpdate,
|
||||
TeamTasksQuery,
|
||||
task_list_to_response,
|
||||
task_to_response,
|
||||
transform_update_data,
|
||||
)
|
||||
from roboco.db.tables import AgentTable, TaskTable
|
||||
from roboco.db.tables import AgentTable
|
||||
from roboco.models.base import TaskStatus, Team
|
||||
from roboco.models.task import TaskCreate
|
||||
from roboco.services.audit import get_audit_service
|
||||
from roboco.services.permissions import TaskAction
|
||||
from roboco.services.task import TaskCreateRequest, get_task_service
|
||||
from roboco.utils.converters import require_uuid, to_python_uuid, to_python_uuid_list
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def _convert_plan(plan_data: dict | None) -> TaskPlanResponse | None:
|
||||
"""Convert plan JSON dict to TaskPlanResponse."""
|
||||
if not plan_data:
|
||||
return None
|
||||
|
||||
sub_tasks = []
|
||||
for st in plan_data.get("sub_tasks", []):
|
||||
sub_tasks.append(
|
||||
SubTaskResponse(
|
||||
id=st.get("id"),
|
||||
title=st.get("title", ""),
|
||||
description=st.get("description"),
|
||||
completed=st.get("completed", False),
|
||||
order=st.get("order", 0),
|
||||
estimated_hours=st.get("estimated_hours"),
|
||||
notes=st.get("notes"),
|
||||
)
|
||||
)
|
||||
|
||||
return TaskPlanResponse(
|
||||
approach=plan_data.get("approach", ""),
|
||||
sub_tasks=sub_tasks,
|
||||
technical_considerations=plan_data.get("technical_considerations", []),
|
||||
risks=plan_data.get("risks", []),
|
||||
open_questions=plan_data.get("open_questions", []),
|
||||
)
|
||||
|
||||
|
||||
def _convert_checkpoints(checkpoints_data: list | None) -> list[CheckpointResponse]:
|
||||
"""Convert checkpoints JSON list to CheckpointResponse list."""
|
||||
if not checkpoints_data:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for cp in checkpoints_data:
|
||||
result.append(
|
||||
CheckpointResponse(
|
||||
id=cp.get("id"),
|
||||
timestamp=cp.get("timestamp"),
|
||||
agent_id=cp.get("agent_id"),
|
||||
state_summary=cp.get("state_summary", ""),
|
||||
remaining_work=cp.get("remaining_work", []),
|
||||
notes=cp.get("notes"),
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def _convert_progress_updates(
|
||||
updates_data: list | None,
|
||||
) -> list[ProgressUpdateResponse]:
|
||||
"""Convert progress_updates JSON list to ProgressUpdateResponse list."""
|
||||
if not updates_data:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for pu in updates_data:
|
||||
result.append(
|
||||
ProgressUpdateResponse(
|
||||
timestamp=pu.get("timestamp"),
|
||||
agent_id=pu.get("agent_id"),
|
||||
message=pu.get("message", ""),
|
||||
percentage=pu.get("percentage"),
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def _convert_commits(commits_data: list | None) -> list[CommitRefResponse]:
|
||||
"""Convert commits JSON list to CommitRefResponse list."""
|
||||
if not commits_data:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for cm in commits_data:
|
||||
result.append(
|
||||
CommitRefResponse(
|
||||
hash=cm.get("hash", ""),
|
||||
message=cm.get("message", ""),
|
||||
timestamp=cm.get("timestamp"),
|
||||
author_agent_id=cm.get("author_agent_id"),
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def _to_response(task: TaskTable) -> TaskResponse:
|
||||
"""Convert TaskTable to TaskResponse with proper UUID conversion."""
|
||||
return TaskResponse(
|
||||
id=require_uuid(task.id),
|
||||
title=task.title,
|
||||
description=task.description,
|
||||
acceptance_criteria=task.acceptance_criteria or [],
|
||||
status=task.status,
|
||||
priority=task.priority,
|
||||
team=task.team,
|
||||
created_by=require_uuid(task.created_by),
|
||||
assigned_to=to_python_uuid(task.assigned_to),
|
||||
parent_task_id=to_python_uuid(task.parent_task_id),
|
||||
dependency_ids=to_python_uuid_list(task.dependency_ids),
|
||||
blocker_ids=to_python_uuid_list(task.blocker_ids),
|
||||
created_at=task.created_at,
|
||||
updated_at=task.updated_at,
|
||||
claimed_at=task.claimed_at,
|
||||
started_at=task.started_at,
|
||||
completed_at=task.completed_at,
|
||||
target_date=task.target_date,
|
||||
estimated_complexity=task.estimated_complexity,
|
||||
# Planning
|
||||
plan=_convert_plan(task.plan),
|
||||
# Execution
|
||||
checkpoints=_convert_checkpoints(task.checkpoints),
|
||||
progress_updates=_convert_progress_updates(task.progress_updates),
|
||||
# Artifacts
|
||||
commits=_convert_commits(task.commits),
|
||||
# Documentation
|
||||
dev_notes=task.dev_notes,
|
||||
qa_notes=task.qa_notes,
|
||||
auditor_notes=task.auditor_notes,
|
||||
quick_context=task.quick_context,
|
||||
# Review Status
|
||||
self_verified=task.self_verified,
|
||||
qa_verified=task.qa_verified,
|
||||
)
|
||||
|
||||
|
||||
def _to_response_list(tasks: list[TaskTable]) -> list[TaskResponse]:
|
||||
"""Convert list of TaskTable to list of TaskResponse."""
|
||||
return [_to_response(t) for t in tasks]
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CRUD ENDPOINTS
|
||||
# =============================================================================
|
||||
@@ -217,7 +83,7 @@ async def create_task(
|
||||
)
|
||||
task = await service.create(req)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.get("", response_model=list[TaskResponse])
|
||||
@@ -258,7 +124,7 @@ async def list_tasks(
|
||||
else:
|
||||
tasks = await service.list_all(params.limit, params.offset)
|
||||
|
||||
return _to_response_list(tasks)
|
||||
return task_list_to_response(tasks)
|
||||
|
||||
|
||||
@router.get("/my", response_model=list[TaskResponse])
|
||||
@@ -270,7 +136,7 @@ async def get_my_tasks(
|
||||
"""Get tasks assigned to the current agent."""
|
||||
service = get_task_service(db)
|
||||
tasks = await service.list_by_assignee(agent.agent_id, status)
|
||||
return _to_response_list(tasks)
|
||||
return task_list_to_response(tasks)
|
||||
|
||||
|
||||
@router.get("/pending", response_model=list[TaskResponse])
|
||||
@@ -288,7 +154,7 @@ async def get_pending_tasks(
|
||||
effective_team = team if can_view_all else agent.team
|
||||
|
||||
tasks = await service.list_pending(effective_team)
|
||||
return _to_response_list(tasks)
|
||||
return task_list_to_response(tasks)
|
||||
|
||||
|
||||
@router.get("/blocked", response_model=list[TaskResponse])
|
||||
@@ -306,7 +172,7 @@ async def get_blocked_tasks(
|
||||
effective_team = team if can_view_all else agent.team
|
||||
|
||||
tasks = await service.list_blocked(effective_team)
|
||||
return _to_response_list(tasks)
|
||||
return task_list_to_response(tasks)
|
||||
|
||||
|
||||
@router.get("/awaiting-qa", response_model=list[TaskResponse])
|
||||
@@ -324,7 +190,7 @@ async def get_awaiting_qa_tasks(
|
||||
effective_team = team if can_view_all else agent.team
|
||||
|
||||
tasks = await service.list_awaiting_qa(effective_team)
|
||||
return _to_response_list(tasks)
|
||||
return task_list_to_response(tasks)
|
||||
|
||||
|
||||
@router.get("/awaiting-docs", response_model=list[TaskResponse])
|
||||
@@ -342,7 +208,7 @@ async def get_awaiting_docs_tasks(
|
||||
effective_team = team if can_view_all else agent.team
|
||||
|
||||
tasks = await service.list_awaiting_docs(effective_team)
|
||||
return _to_response_list(tasks)
|
||||
return task_list_to_response(tasks)
|
||||
|
||||
|
||||
@router.get("/team/{team}", response_model=list[TaskResponse])
|
||||
@@ -366,7 +232,7 @@ async def get_team_tasks(
|
||||
|
||||
service = get_task_service(db)
|
||||
tasks = await service.list_by_team(team, params.task_status, params.limit)
|
||||
return _to_response_list(tasks)
|
||||
return task_list_to_response(tasks)
|
||||
|
||||
|
||||
@router.get("/stats", response_model=TaskCountResponse)
|
||||
@@ -419,7 +285,7 @@ async def get_task(
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Task not found"
|
||||
)
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.put("/{task_id}", response_model=TaskResponse)
|
||||
@@ -431,7 +297,17 @@ async def update_task(
|
||||
agent: CurrentAgentContext,
|
||||
permissions: PermissionServiceDep,
|
||||
) -> TaskResponse:
|
||||
"""Update a task. Supports both PUT and PATCH for partial updates."""
|
||||
"""Update a task. Supports both PUT and PATCH for partial updates.
|
||||
|
||||
CEO and privileged roles can update any field including:
|
||||
- Basic info (title, description, acceptance_criteria, priority, etc.)
|
||||
- Ownership (team, assigned_to)
|
||||
- Relationships (parent_task_id, dependency_ids, blocker_ids)
|
||||
- Planning (plan with sub_tasks, risks, open_questions)
|
||||
- Execution tracking (progress_updates, checkpoints)
|
||||
- Artifacts (commits)
|
||||
- Notes (dev_notes, qa_notes, auditor_notes, quick_context)
|
||||
"""
|
||||
service = get_task_service(db)
|
||||
task = await service.get(task_id)
|
||||
if not task:
|
||||
@@ -455,14 +331,17 @@ async def update_task(
|
||||
detail="Not authorized to update this task",
|
||||
)
|
||||
|
||||
task = await service.update(task_id, **data.model_dump(exclude_unset=True))
|
||||
# Transform input data for database storage
|
||||
updates = transform_update_data(data)
|
||||
|
||||
task = await service.update(task_id, **updates)
|
||||
if not task:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Task update failed unexpectedly",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.delete("/{task_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@@ -504,7 +383,7 @@ async def get_subtasks(
|
||||
"""Get subtasks of a task."""
|
||||
service = get_task_service(db)
|
||||
tasks = await service.get_subtasks(task_id)
|
||||
return _to_response_list(tasks)
|
||||
return task_list_to_response(tasks)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -573,7 +452,7 @@ async def claim_task(
|
||||
detail="Cannot claim task - not pending",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.post("/{task_id}/start", response_model=TaskResponse)
|
||||
@@ -604,7 +483,7 @@ async def start_task(
|
||||
detail="Cannot start task - invalid status",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.post("/{task_id}/block", response_model=TaskResponse)
|
||||
@@ -639,7 +518,7 @@ async def block_task(
|
||||
detail="Task block failed unexpectedly",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.post("/{task_id}/unblock", response_model=TaskResponse)
|
||||
@@ -673,7 +552,7 @@ async def unblock_task(
|
||||
detail="Cannot unblock task - not blocked",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.post("/{task_id}/pause", response_model=TaskResponse)
|
||||
@@ -704,7 +583,7 @@ async def pause_task(
|
||||
detail="Cannot pause task - not in progress",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.post("/{task_id}/resume", response_model=TaskResponse)
|
||||
@@ -735,7 +614,7 @@ async def resume_task(
|
||||
detail="Cannot resume task - not paused",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.post("/{task_id}/verify", response_model=TaskResponse)
|
||||
@@ -766,7 +645,7 @@ async def submit_for_verification(
|
||||
detail="Cannot verify task - not in progress",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.post("/{task_id}/submit-qa", response_model=TaskResponse)
|
||||
@@ -797,7 +676,7 @@ async def submit_for_qa(
|
||||
detail="Cannot submit for QA - not verifying",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.post("/{task_id}/pass-qa", response_model=TaskResponse)
|
||||
@@ -853,7 +732,7 @@ async def pass_qa(
|
||||
detail="Cannot pass QA - not awaiting QA",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.post("/{task_id}/fail-qa", response_model=TaskResponse)
|
||||
@@ -892,7 +771,7 @@ async def fail_qa(
|
||||
detail="Cannot fail QA - not awaiting QA",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.post("/{task_id}/complete", response_model=TaskResponse)
|
||||
@@ -927,7 +806,7 @@ async def complete_task(
|
||||
detail="Cannot complete task - invalid status",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.post("/{task_id}/cancel", response_model=TaskResponse)
|
||||
@@ -962,7 +841,7 @@ async def cancel_task(
|
||||
detail="Task cancel failed unexpectedly",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -1001,7 +880,7 @@ async def add_progress(
|
||||
detail="Add progress failed unexpectedly",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.post("/{task_id}/checkpoint", response_model=TaskResponse)
|
||||
@@ -1039,7 +918,7 @@ async def add_checkpoint(
|
||||
detail="Add checkpoint failed unexpectedly",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
|
||||
@router.post("/{task_id}/commit", response_model=TaskResponse)
|
||||
@@ -1071,4 +950,4 @@ async def add_commit(
|
||||
detail="Add commit failed unexpectedly",
|
||||
)
|
||||
await db.commit()
|
||||
return _to_response(task)
|
||||
return task_to_response(task)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Agents API Schemas
|
||||
|
||||
Request/response models for agent endpoints.
|
||||
"""
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from roboco.models import AgentRole, Team
|
||||
|
||||
|
||||
class AgentResponse(BaseModel):
|
||||
"""Response model for agent information."""
|
||||
|
||||
id: UUID
|
||||
name: str
|
||||
slug: str
|
||||
role: AgentRole
|
||||
team: Team | None
|
||||
|
||||
class Config:
|
||||
"""Pydantic config."""
|
||||
|
||||
from_attributes = True
|
||||
@@ -4,11 +4,20 @@ Channels API Schemas
|
||||
Request/response models for channel endpoints.
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import select
|
||||
|
||||
from roboco.models import ChannelType
|
||||
from roboco.models import AgentRole, ChannelType, ChannelUpdate
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from roboco.db.tables import ChannelTable
|
||||
from roboco.services.permissions import AgentContext
|
||||
|
||||
|
||||
class ChannelResponse(BaseModel):
|
||||
@@ -60,3 +69,59 @@ class ListChannelsQuery(BaseModel):
|
||||
page: int = Field(1, ge=1)
|
||||
page_size: int = Field(20, ge=1, le=100)
|
||||
include_archived: bool = False
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# HELPERS AND UTILITIES
|
||||
# =============================================================================
|
||||
|
||||
# Roles authorized to manage channels
|
||||
CHANNEL_ADMIN_ROLES = frozenset(
|
||||
{AgentRole.CEO, AgentRole.PRODUCT_OWNER, AgentRole.MAIN_PM}
|
||||
)
|
||||
|
||||
# Fields that can be updated on a channel
|
||||
CHANNEL_UPDATE_FIELDS = (
|
||||
"name",
|
||||
"description",
|
||||
"topic",
|
||||
"is_archived",
|
||||
"allow_threads",
|
||||
"allow_reactions",
|
||||
"message_retention_days",
|
||||
"max_message_length",
|
||||
)
|
||||
|
||||
|
||||
def require_channel_admin(agent: "AgentContext") -> None:
|
||||
"""Raise 403 if agent is not authorized to manage channels."""
|
||||
if agent.role not in CHANNEL_ADMIN_ROLES:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not authorized to manage channels",
|
||||
)
|
||||
|
||||
|
||||
async def get_channel_or_404(
|
||||
db: "AsyncSession",
|
||||
channel_table: type["ChannelTable"],
|
||||
channel_id: UUID,
|
||||
) -> "ChannelTable":
|
||||
"""Get channel by ID or raise 404."""
|
||||
query = select(channel_table).where(channel_table.id == channel_id)
|
||||
result = await db.execute(query)
|
||||
channel = result.scalar_one_or_none()
|
||||
if not channel:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Channel not found",
|
||||
)
|
||||
return channel
|
||||
|
||||
|
||||
def apply_channel_updates(channel: "ChannelTable", data: ChannelUpdate) -> None:
|
||||
"""Apply updates to channel fields."""
|
||||
for field in CHANNEL_UPDATE_FIELDS:
|
||||
value = getattr(data, field, None)
|
||||
if value is not None:
|
||||
setattr(channel, field, value)
|
||||
|
||||
@@ -5,11 +5,17 @@ Request/response models for the notification system.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import select
|
||||
|
||||
from roboco.models import NotificationPriority, NotificationType
|
||||
from roboco.utils.converters import require_uuid, to_python_uuid, to_python_uuid_list
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from roboco.db.tables import NotificationTable
|
||||
|
||||
|
||||
class ListNotificationsParams(BaseModel):
|
||||
@@ -60,3 +66,52 @@ class NotificationCreateRequest(BaseModel):
|
||||
requires_ack: bool = True
|
||||
related_task_id: UUID | None = None
|
||||
expires_at: datetime | None = None
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CONVERTERS AND QUERY BUILDERS
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def build_notification_query(
|
||||
notification_table: type["NotificationTable"],
|
||||
agent_id: UUID,
|
||||
params: ListNotificationsParams,
|
||||
) -> Any:
|
||||
"""Build the notification query with filters."""
|
||||
query = select(notification_table).where(
|
||||
notification_table.to_agents.contains([agent_id])
|
||||
)
|
||||
if params.unread_only:
|
||||
query = query.where(~notification_table.read_by.contains([agent_id]))
|
||||
if params.pending_ack_only:
|
||||
query = query.where(
|
||||
notification_table.requires_ack.is_(True),
|
||||
~notification_table.acked_by.contains([agent_id]),
|
||||
)
|
||||
if params.type_filter:
|
||||
query = query.where(notification_table.type == params.type_filter)
|
||||
return query.order_by(notification_table.timestamp.desc()).limit(params.limit)
|
||||
|
||||
|
||||
def notification_to_response(
|
||||
n: "NotificationTable",
|
||||
agent_id: UUID,
|
||||
) -> NotificationResponse:
|
||||
"""Convert a notification to response format."""
|
||||
return NotificationResponse(
|
||||
id=require_uuid(n.id),
|
||||
type=n.type,
|
||||
priority=n.priority,
|
||||
from_agent=require_uuid(n.from_agent),
|
||||
to_agents=to_python_uuid_list(n.to_agents),
|
||||
subject=n.subject,
|
||||
body=n.body,
|
||||
requires_ack=n.requires_ack,
|
||||
is_acknowledged=agent_id in n.acked_by,
|
||||
is_fully_acknowledged=all(a in n.acked_by for a in n.to_agents),
|
||||
is_read=agent_id in n.read_by,
|
||||
related_task_id=to_python_uuid(n.related_task_id),
|
||||
timestamp=n.timestamp,
|
||||
expires_at=n.expires_at,
|
||||
)
|
||||
|
||||
+266
-2
@@ -5,12 +5,16 @@ Request/response models for task endpoints.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from roboco.models.base import Complexity, TaskStatus, Team
|
||||
from roboco.utils.converters import require_uuid, to_python_uuid, to_python_uuid_list
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from roboco.db.tables import TaskTable
|
||||
|
||||
# =============================================================================
|
||||
# NESTED RESPONSE MODELS
|
||||
@@ -68,21 +72,104 @@ class TaskPlanResponse(BaseModel):
|
||||
open_questions: list[dict[str, Any]] = []
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# INPUT MODELS (for creating/updating nested data)
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class SubTaskInput(BaseModel):
|
||||
"""Input for creating/updating a sub-task."""
|
||||
|
||||
id: str # Client-generated ID
|
||||
title: str
|
||||
description: str | None = None
|
||||
completed: bool = False
|
||||
order: int
|
||||
estimated_hours: float | None = None
|
||||
notes: str | None = None
|
||||
|
||||
|
||||
class TaskPlanInput(BaseModel):
|
||||
"""Input for creating/updating a task plan."""
|
||||
|
||||
approach: str
|
||||
sub_tasks: list[SubTaskInput] = []
|
||||
technical_considerations: list[str] = []
|
||||
risks: list[dict[str, Any]] = []
|
||||
open_questions: list[dict[str, Any]] = []
|
||||
|
||||
|
||||
class ProgressUpdateInput(BaseModel):
|
||||
"""Input for adding a progress update."""
|
||||
|
||||
timestamp: datetime
|
||||
agent_id: str # Can be agent slug or "CEO"
|
||||
message: str
|
||||
percentage: int | None = None
|
||||
|
||||
|
||||
class CheckpointInput(BaseModel):
|
||||
"""Input for adding a checkpoint."""
|
||||
|
||||
id: str # Client-generated ID
|
||||
timestamp: datetime
|
||||
agent_id: str # Can be agent slug or "CEO"
|
||||
state_summary: str
|
||||
remaining_work: list[str] = []
|
||||
notes: str | None = None
|
||||
|
||||
|
||||
class CommitRefInput(BaseModel):
|
||||
"""Input for linking a commit."""
|
||||
|
||||
hash: str
|
||||
message: str
|
||||
timestamp: datetime
|
||||
author_agent_id: str | None = None # Can be agent slug or "CEO"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# REQUEST MODELS
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TaskUpdate(BaseModel):
|
||||
"""Request to update a task."""
|
||||
"""Request to update a task.
|
||||
|
||||
CEO can update any field. All fields are optional for partial updates.
|
||||
"""
|
||||
|
||||
# Basic info
|
||||
title: str | None = None
|
||||
description: str | None = None
|
||||
acceptance_criteria: list[str] | None = None
|
||||
priority: int | None = Field(default=None, ge=0, le=3)
|
||||
target_date: datetime | None = None
|
||||
estimated_complexity: Complexity | None = None
|
||||
|
||||
# Ownership & assignment
|
||||
team: Team | None = None
|
||||
assigned_to: str | None = None # UUID string or null to unassign
|
||||
|
||||
# Relationships
|
||||
parent_task_id: str | None = None # UUID string or null
|
||||
dependency_ids: list[str] | None = None # List of UUID strings
|
||||
blocker_ids: list[str] | None = None # List of UUID strings
|
||||
|
||||
# Planning
|
||||
plan: TaskPlanInput | None = None
|
||||
|
||||
# Execution tracking
|
||||
progress_updates: list[ProgressUpdateInput] | None = None
|
||||
checkpoints: list[CheckpointInput] | None = None
|
||||
|
||||
# Artifacts
|
||||
commits: list[CommitRefInput] | None = None
|
||||
|
||||
# Notes
|
||||
dev_notes: str | None = None
|
||||
qa_notes: str | None = None
|
||||
auditor_notes: str | None = None
|
||||
quick_context: str | None = None
|
||||
|
||||
|
||||
@@ -222,3 +309,180 @@ class TeamTasksQuery(BaseModel):
|
||||
|
||||
task_status: TaskStatus | None = None
|
||||
limit: int = Field(100, ge=1, le=500)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CONVERTERS (from database/dict to response models)
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def convert_plan(plan_data: dict | None) -> TaskPlanResponse | None:
|
||||
"""Convert plan JSON dict to TaskPlanResponse."""
|
||||
if not plan_data:
|
||||
return None
|
||||
|
||||
sub_tasks = []
|
||||
for st in plan_data.get("sub_tasks", []):
|
||||
sub_tasks.append(
|
||||
SubTaskResponse(
|
||||
id=st.get("id"),
|
||||
title=st.get("title", ""),
|
||||
description=st.get("description"),
|
||||
completed=st.get("completed", False),
|
||||
order=st.get("order", 0),
|
||||
estimated_hours=st.get("estimated_hours"),
|
||||
notes=st.get("notes"),
|
||||
)
|
||||
)
|
||||
|
||||
return TaskPlanResponse(
|
||||
approach=plan_data.get("approach", ""),
|
||||
sub_tasks=sub_tasks,
|
||||
technical_considerations=plan_data.get("technical_considerations", []),
|
||||
risks=plan_data.get("risks", []),
|
||||
open_questions=plan_data.get("open_questions", []),
|
||||
)
|
||||
|
||||
|
||||
def convert_checkpoints(checkpoints_data: list | None) -> list[CheckpointResponse]:
|
||||
"""Convert checkpoints JSON list to CheckpointResponse list."""
|
||||
if not checkpoints_data:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for cp in checkpoints_data:
|
||||
result.append(
|
||||
CheckpointResponse(
|
||||
id=cp.get("id"),
|
||||
timestamp=cp.get("timestamp"),
|
||||
agent_id=cp.get("agent_id"),
|
||||
state_summary=cp.get("state_summary", ""),
|
||||
remaining_work=cp.get("remaining_work", []),
|
||||
notes=cp.get("notes"),
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def convert_progress_updates(
|
||||
updates_data: list | None,
|
||||
) -> list[ProgressUpdateResponse]:
|
||||
"""Convert progress_updates JSON list to ProgressUpdateResponse list."""
|
||||
if not updates_data:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for pu in updates_data:
|
||||
result.append(
|
||||
ProgressUpdateResponse(
|
||||
timestamp=pu.get("timestamp"),
|
||||
agent_id=pu.get("agent_id"),
|
||||
message=pu.get("message", ""),
|
||||
percentage=pu.get("percentage"),
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def convert_commits(commits_data: list | None) -> list[CommitRefResponse]:
|
||||
"""Convert commits JSON list to CommitRefResponse list."""
|
||||
if not commits_data:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for cm in commits_data:
|
||||
result.append(
|
||||
CommitRefResponse(
|
||||
hash=cm.get("hash", ""),
|
||||
message=cm.get("message", ""),
|
||||
timestamp=cm.get("timestamp"),
|
||||
author_agent_id=cm.get("author_agent_id"),
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def task_to_response(task: "TaskTable") -> TaskResponse:
|
||||
"""Convert TaskTable to TaskResponse with proper UUID conversion."""
|
||||
return TaskResponse(
|
||||
id=require_uuid(task.id),
|
||||
title=task.title,
|
||||
description=task.description,
|
||||
acceptance_criteria=task.acceptance_criteria or [],
|
||||
status=task.status,
|
||||
priority=task.priority,
|
||||
team=task.team,
|
||||
created_by=require_uuid(task.created_by),
|
||||
assigned_to=to_python_uuid(task.assigned_to),
|
||||
parent_task_id=to_python_uuid(task.parent_task_id),
|
||||
dependency_ids=to_python_uuid_list(task.dependency_ids),
|
||||
blocker_ids=to_python_uuid_list(task.blocker_ids),
|
||||
created_at=task.created_at,
|
||||
updated_at=task.updated_at,
|
||||
claimed_at=task.claimed_at,
|
||||
started_at=task.started_at,
|
||||
completed_at=task.completed_at,
|
||||
target_date=task.target_date,
|
||||
estimated_complexity=task.estimated_complexity,
|
||||
# Planning
|
||||
plan=convert_plan(task.plan),
|
||||
# Execution
|
||||
checkpoints=convert_checkpoints(task.checkpoints),
|
||||
progress_updates=convert_progress_updates(task.progress_updates),
|
||||
# Artifacts
|
||||
commits=convert_commits(task.commits),
|
||||
# Documentation
|
||||
dev_notes=task.dev_notes,
|
||||
qa_notes=task.qa_notes,
|
||||
auditor_notes=task.auditor_notes,
|
||||
quick_context=task.quick_context,
|
||||
# Review Status
|
||||
self_verified=task.self_verified,
|
||||
qa_verified=task.qa_verified,
|
||||
)
|
||||
|
||||
|
||||
def task_list_to_response(tasks: list["TaskTable"]) -> list[TaskResponse]:
|
||||
"""Convert list of TaskTable to list of TaskResponse."""
|
||||
return [task_to_response(t) for t in tasks]
|
||||
|
||||
|
||||
def parse_uuid_or_none(value: str | None) -> UUID | None:
|
||||
"""Parse a string to UUID, returning None if empty or None."""
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
return UUID(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def _parse_uuid_list(id_strings: list[str] | None) -> list[UUID]:
|
||||
"""Parse a list of UUID strings to UUID objects, filtering empty values."""
|
||||
if not id_strings:
|
||||
return []
|
||||
return [UUID(id_str) for id_str in id_strings if id_str]
|
||||
|
||||
|
||||
# Fields that need UUID parsing (single value)
|
||||
_SINGLE_UUID_FIELDS = ("assigned_to", "parent_task_id")
|
||||
|
||||
# Fields that need UUID list parsing
|
||||
_UUID_LIST_FIELDS = ("dependency_ids", "blocker_ids")
|
||||
|
||||
|
||||
def transform_update_data(data: TaskUpdate) -> dict:
|
||||
"""Transform TaskUpdate input to format suitable for database storage."""
|
||||
updates = data.model_dump(exclude_unset=True)
|
||||
|
||||
# Convert single UUID fields
|
||||
for field in _SINGLE_UUID_FIELDS:
|
||||
if field in updates:
|
||||
updates[field] = parse_uuid_or_none(updates[field])
|
||||
|
||||
# Convert UUID list fields
|
||||
for field in _UUID_LIST_FIELDS:
|
||||
if field in updates:
|
||||
updates[field] = _parse_uuid_list(updates[field])
|
||||
|
||||
return updates
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import structlog
|
||||
import uvicorn
|
||||
|
||||
from roboco.agents import set_reasoning_stream_callback
|
||||
from roboco.api.routes.orchestrator import set_orchestrator
|
||||
from roboco.api.deps import set_orchestrator
|
||||
from roboco.api.websocket import broadcast_agent_chunk
|
||||
from roboco.config import settings
|
||||
from roboco.db import bootstrap_database
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ class Settings(BaseSettings):
|
||||
"""
|
||||
if self.api_url:
|
||||
return f"{self.api_url.rstrip('/')}/api/v1"
|
||||
connect_host = "127.0.0.1" if self.host == "0.0.0.0" else self.host
|
||||
connect_host = "127.0.0.1" if self.host == "0.0.0.0" else self.host # nosec B104
|
||||
return f"http://{connect_host}:{self.port}/api/v1"
|
||||
|
||||
# ==========================================================================
|
||||
|
||||
@@ -222,7 +222,78 @@ async def _handle_channel_list(agent_id: str) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
async def _handle_channel_history( # noqa: PLR0911
|
||||
async def _get_channel_by_slug(
|
||||
client: httpx.AsyncClient,
|
||||
channel_slug: str,
|
||||
headers: dict[str, str],
|
||||
) -> str | dict[str, Any]:
|
||||
"""Get channel ID by slug. Returns channel_id or error dict."""
|
||||
resp = await client.get(
|
||||
f"{settings.internal_api_url}/channels",
|
||||
params={"slug": channel_slug},
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
if resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response("API_ERROR", "Failed to fetch channels")
|
||||
|
||||
data = resp.json()
|
||||
items = data.get("items", data)
|
||||
if not items:
|
||||
return _format_error_response("NOT_FOUND", f"Channel #{channel_slug} not found")
|
||||
|
||||
channel = items[0] if isinstance(items, list) else items
|
||||
return str(channel["id"])
|
||||
|
||||
|
||||
async def _get_sessions_for_group(
|
||||
client: httpx.AsyncClient,
|
||||
group_id: str,
|
||||
headers: dict[str, str],
|
||||
) -> list | dict[str, Any]:
|
||||
"""Get sessions for a group. Returns session list or error dict."""
|
||||
resp = await client.get(
|
||||
f"{settings.internal_api_url}/sessions",
|
||||
params={"group_id": group_id, "limit": 5},
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
if resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response("API_ERROR", "Failed to fetch sessions")
|
||||
|
||||
items: list = resp.json().get("items", [])
|
||||
return items
|
||||
|
||||
|
||||
async def _fetch_messages_from_sessions(
|
||||
client: httpx.AsyncClient,
|
||||
sessions: list,
|
||||
since: datetime,
|
||||
limit: int,
|
||||
headers: dict[str, str],
|
||||
) -> list:
|
||||
"""Fetch messages from multiple sessions."""
|
||||
all_messages: list = []
|
||||
for session in sessions:
|
||||
resp = await client.get(
|
||||
f"{settings.internal_api_url}/messages",
|
||||
params={
|
||||
"session_id": session["id"],
|
||||
"after": since.isoformat(),
|
||||
"limit": limit,
|
||||
},
|
||||
headers=headers,
|
||||
)
|
||||
if resp.status_code == status.HTTP_200_OK:
|
||||
all_messages.extend(resp.json().get("items", []))
|
||||
if len(all_messages) >= limit:
|
||||
break
|
||||
|
||||
all_messages.sort(key=lambda m: m.get("timestamp", ""), reverse=True)
|
||||
return all_messages[:limit]
|
||||
|
||||
|
||||
async def _handle_channel_history(
|
||||
agent_id: str,
|
||||
channel_slug: str,
|
||||
limit: int,
|
||||
@@ -236,48 +307,28 @@ async def _handle_channel_history( # noqa: PLR0911
|
||||
|
||||
limit = min(limit, 100)
|
||||
since = datetime.now(UTC) - timedelta(hours=hours_back)
|
||||
|
||||
headers = _get_agent_headers(agent_id)
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
# Get channel by slug
|
||||
channels_resp = await client.get(
|
||||
f"{settings.internal_api_url}/channels",
|
||||
params={"slug": channel_slug},
|
||||
headers=headers,
|
||||
)
|
||||
# Get channel
|
||||
channel_result = await _get_channel_by_slug(client, channel_slug, headers)
|
||||
if isinstance(channel_result, dict):
|
||||
return channel_result
|
||||
channel_id = channel_result
|
||||
|
||||
if channels_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response("API_ERROR", "Failed to fetch channels")
|
||||
|
||||
channels_data = channels_resp.json()
|
||||
items = channels_data.get("items", channels_data)
|
||||
if not items:
|
||||
return _format_error_response(
|
||||
"NOT_FOUND", f"Channel #{channel_slug} not found"
|
||||
)
|
||||
|
||||
channel = items[0] if isinstance(items, list) else items
|
||||
channel_id = channel["id"]
|
||||
|
||||
# Get groups for this channel
|
||||
# Get group
|
||||
group_result = await _get_default_group(client, channel_id, headers)
|
||||
if isinstance(group_result, dict):
|
||||
return group_result # Error response
|
||||
return group_result
|
||||
group_id = group_result
|
||||
|
||||
# Get sessions for this group
|
||||
sessions_resp = await client.get(
|
||||
f"{settings.internal_api_url}/sessions",
|
||||
params={"group_id": group_id, "limit": 5},
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
if sessions_resp.status_code != status.HTTP_200_OK:
|
||||
return _format_error_response("API_ERROR", "Failed to fetch sessions")
|
||||
|
||||
sessions_data = sessions_resp.json()
|
||||
sessions = sessions_data.get("items", [])
|
||||
# Get sessions
|
||||
sessions_result = await _get_sessions_for_group(client, group_id, headers)
|
||||
if isinstance(sessions_result, dict):
|
||||
return sessions_result
|
||||
sessions = sessions_result
|
||||
|
||||
# Early return for no sessions
|
||||
if not sessions:
|
||||
return {
|
||||
"channel": channel_slug,
|
||||
@@ -287,36 +338,16 @@ async def _handle_channel_history( # noqa: PLR0911
|
||||
"since": since.isoformat(),
|
||||
}
|
||||
|
||||
# Get messages from all recent sessions
|
||||
all_messages = []
|
||||
for session in sessions:
|
||||
session_id = session["id"]
|
||||
messages_resp = await client.get(
|
||||
f"{settings.internal_api_url}/messages",
|
||||
params={
|
||||
"session_id": session_id,
|
||||
"after": since.isoformat(),
|
||||
"limit": limit,
|
||||
},
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
if messages_resp.status_code == status.HTTP_200_OK:
|
||||
msg_data = messages_resp.json()
|
||||
all_messages.extend(msg_data.get("items", []))
|
||||
|
||||
if len(all_messages) >= limit:
|
||||
break
|
||||
|
||||
# Sort by timestamp descending and limit
|
||||
all_messages.sort(key=lambda m: m.get("timestamp", ""), reverse=True)
|
||||
all_messages = all_messages[:limit]
|
||||
# Fetch messages
|
||||
messages = await _fetch_messages_from_sessions(
|
||||
client, sessions, since, limit, headers
|
||||
)
|
||||
|
||||
return {
|
||||
"channel": channel_slug,
|
||||
"messages": all_messages,
|
||||
"total": len(all_messages),
|
||||
"has_more": len(all_messages) >= limit,
|
||||
"messages": messages,
|
||||
"total": len(messages),
|
||||
"has_more": len(messages) >= limit,
|
||||
"since": since.isoformat(),
|
||||
}
|
||||
|
||||
|
||||
@@ -190,7 +190,11 @@ CHANNEL_MEMBERSHIPS = {
|
||||
"doc-all": ["be-doc", "fe-doc", "ux-doc", CEO_AGENT_ID],
|
||||
# Management channels + CEO
|
||||
"main-pm-board": [
|
||||
"main-pm", "product-owner", "head-marketing", "auditor", CEO_AGENT_ID
|
||||
"main-pm",
|
||||
"product-owner",
|
||||
"head-marketing",
|
||||
"auditor",
|
||||
CEO_AGENT_ID,
|
||||
],
|
||||
"board-private": ["product-owner", "head-marketing", "auditor", CEO_AGENT_ID],
|
||||
# Broadcast channels - everyone (CEO included via DEFAULT_AGENTS)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
Health Check Service
|
||||
|
||||
Infrastructure health check functions.
|
||||
"""
|
||||
|
||||
import redis.asyncio as redis
|
||||
from sqlalchemy import text
|
||||
|
||||
from roboco.config import settings
|
||||
from roboco.db.base import get_db_context
|
||||
|
||||
|
||||
async def check_database() -> tuple[str, bool]:
|
||||
"""Check database connectivity."""
|
||||
try:
|
||||
async with get_db_context() as session:
|
||||
await session.execute(text("SELECT 1"))
|
||||
return "ok", True
|
||||
except Exception as e:
|
||||
return str(e), False
|
||||
|
||||
|
||||
async def check_redis() -> tuple[str, bool]:
|
||||
"""Check Redis connectivity."""
|
||||
try:
|
||||
client = redis.from_url(settings.redis_url)
|
||||
await client.ping()
|
||||
await client.close()
|
||||
return "ok", True
|
||||
except Exception as e:
|
||||
return str(e), False
|
||||
@@ -4,12 +4,24 @@ Notification Service
|
||||
Sends notifications through the API with proper enforcement.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import structlog
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy import select
|
||||
|
||||
from roboco.db.base import get_db_context
|
||||
from roboco.db.tables import NotificationTable
|
||||
from roboco.models import NotificationPriority, NotificationType
|
||||
from roboco.models.notification import CreateNotificationParams
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
@@ -189,3 +201,37 @@ class NotificationService:
|
||||
notification_id=str(notification.id),
|
||||
type=params.notification_type.value,
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Route Helpers
|
||||
# =============================================================================
|
||||
|
||||
|
||||
async def get_notification_or_404(
|
||||
db: AsyncSession,
|
||||
notification_id: UUID,
|
||||
) -> NotificationTable:
|
||||
"""Fetch notification by ID or raise 404."""
|
||||
result = await db.execute(
|
||||
select(NotificationTable).where(NotificationTable.id == notification_id)
|
||||
)
|
||||
notification = result.scalar_one_or_none()
|
||||
if not notification:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Notification not found",
|
||||
)
|
||||
return notification
|
||||
|
||||
|
||||
def require_notification_recipient(
|
||||
notification: NotificationTable,
|
||||
agent_id: UUID,
|
||||
) -> None:
|
||||
"""Raise 403 if agent is not a recipient."""
|
||||
if agent_id not in notification.to_agents:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="You are not a recipient of this notification",
|
||||
)
|
||||
|
||||
@@ -838,19 +838,19 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "faker"
|
||||
version = "38.2.0"
|
||||
version = "39.0.0"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
dependencies = [
|
||||
{ name = "tzdata" },
|
||||
]
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/64/27/022d4dbd4c20567b4c294f79a133cc2f05240ea61e0d515ead18c995c249/faker-38.2.0.tar.gz", hash = "sha256:20672803db9c7cb97f9b56c18c54b915b6f1d8991f63d1d673642dc43f5ce7ab", size = 1941469, upload-time = "2025-11-19T16:37:31.892Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/30/b9/0897fb5888ddda099dc0f314a8a9afb5faa7e52eaf6865c00686dfb394db/faker-39.0.0.tar.gz", hash = "sha256:ddae46d3b27e01cea7894651d687b33bcbe19a45ef044042c721ceac6d3da0ff", size = 1941757, upload-time = "2025-12-17T19:19:04.762Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/17/93/00c94d45f55c336434a15f98d906387e87ce28f9918e4444829a8fda432d/faker-38.2.0-py3-none-any.whl", hash = "sha256:35fe4a0a79dee0dc4103a6083ee9224941e7d3594811a50e3969e547b0d2ee65", size = 1980505, upload-time = "2025-11-19T16:37:30.208Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/eb/5a/26cdb1b10a55ac6eb11a738cea14865fa753606c4897d7be0f5dc230df00/faker-39.0.0-py3-none-any.whl", hash = "sha256:c72f1fca8f1a24b8da10fcaa45739135a19772218ddd61b86b7ea1b8c790dce7", size = 1980775, upload-time = "2025-12-17T19:19:02.926Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.124.4"
|
||||
version = "0.125.0"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
dependencies = [
|
||||
{ name = "annotated-doc" },
|
||||
@@ -858,18 +858,18 @@ dependencies = [
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/cd/21/ade3ff6745a82ea8ad88552b4139d27941549e4f19125879f848ac8f3c3d/fastapi-0.124.4.tar.gz", hash = "sha256:0e9422e8d6b797515f33f500309f6e1c98ee4e85563ba0f2debb282df6343763", size = 378460, upload-time = "2025-12-12T15:00:43.891Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/17/71/2df15009fb4bdd522a069d2fbca6007c6c5487fce5cb965be00fc335f1d1/fastapi-0.125.0.tar.gz", hash = "sha256:16b532691a33e2c5dee1dac32feb31dc6eb41a3dd4ff29a95f9487cb21c054c0", size = 370550, upload-time = "2025-12-17T21:41:44.15Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/3e/57/aa70121b5008f44031be645a61a7c4abc24e0e888ad3fc8fda916f4d188e/fastapi-0.124.4-py3-none-any.whl", hash = "sha256:6d1e703698443ccb89e50abe4893f3c84d9d6689c0cf1ca4fad6d3c15cf69f15", size = 113281, upload-time = "2025-12-12T15:00:42.44Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/34/2f/ff2fcc98f500713368d8b650e1bbc4a0b3ebcdd3e050dcdaad5f5a13fd7e/fastapi-0.125.0-py3-none-any.whl", hash = "sha256:2570ec4f3aecf5cca8f0428aed2398b774fcdfee6c2116f86e80513f2f86a7a1", size = 112888, upload-time = "2025-12-17T21:41:41.286Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.20.0"
|
||||
version = "3.20.1"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666, upload-time = "2025-12-15T23:54:26.874Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1295,11 +1295,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "joblib"
|
||||
version = "1.5.2"
|
||||
version = "1.5.3"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1331,19 +1331,19 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "lance-namespace"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
dependencies = [
|
||||
{ name = "lance-namespace-urllib3-client" },
|
||||
]
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/c9/36/1c926adfe4bf5cd43fb488f7b9f61bb0acb6f057f4e22c74809818106f46/lance_namespace-0.3.1.tar.gz", hash = "sha256:ad8408570bd3d8403cfe6558aae1ab99371c892c2c0d8471c2ab8a50a679a3d8", size = 6826, upload-time = "2025-12-11T06:45:41.042Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/4d/44/946ca6033997820623906d84cb9830af89768940bbc9f824aadec6136254/lance_namespace-0.3.2.tar.gz", hash = "sha256:51eb30f8a9f073bba15d1824460bf6e9fa7f867e224e73ee64520ed254f0c140", size = 6833, upload-time = "2025-12-15T18:28:23.012Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/ad/6e/f603cf28c41f79cb3135444ac78e6318759c0365b4257354daa16966a9ee/lance_namespace-0.3.1-py3-none-any.whl", hash = "sha256:2e303f780286a3a80416c140a9c18c8cbdef1f4e0f9a5a2f1ec5292625a65107", size = 8328, upload-time = "2025-12-11T06:45:39.931Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/98/d2/947eedf16c59e1269c9cf7a2dc3c4522a3915cec664a9ffe8a7d1a0e2fcd/lance_namespace-0.3.2-py3-none-any.whl", hash = "sha256:794249bec15fb6e34d2b8d9f9698f11ae191179eccd9cd879743d8fb3c666ca0", size = 8335, upload-time = "2025-12-15T18:28:24.701Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lance-namespace-urllib3-client"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
@@ -1351,14 +1351,14 @@ dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/bd/16/9830da3893f4d5e71072c33fbbee91a950362f9f8f8d1992e64a57d0c424/lance_namespace_urllib3_client-0.3.1.tar.gz", hash = "sha256:4b68684cb9b96b9da5bec895f9d1199784ef925052bea85ae1667d073e104c4d", size = 151787, upload-time = "2025-12-11T06:45:41.823Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e7/17/56d98ad4a969e59d08d6e7157f9a680383f1fe5fd2916b75a42826ad0b52/lance_namespace_urllib3_client-0.3.2.tar.gz", hash = "sha256:1474e8a16a3547faeb5be56270b8903bd2c9ce10ae04d09245f3870ede3a5c4d", size = 151790, upload-time = "2025-12-15T18:28:23.867Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/81/de/cc3f5c5a513913f0dfdfe54bdd5872922ad8949263233f9f6e9d5873abee/lance_namespace_urllib3_client-0.3.1-py3-none-any.whl", hash = "sha256:7f9d2be67a65c68faed3b4771a4665590ac1441451d7f609acb29bf300ba8303", size = 256820, upload-time = "2025-12-11T06:45:43.259Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/1e/8c/40ac725fb6fb7a4a13295fa2bc3b6ff877be1538d0a95ecf939ef0ceb562/lance_namespace_urllib3_client-0.3.2-py3-none-any.whl", hash = "sha256:bc73668b1086ef96c279870b019902bb293d15a6271ea8cf8eb429a57ab6a6ab", size = 256823, upload-time = "2025-12-15T18:28:25.603Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lancedb"
|
||||
version = "0.25.3"
|
||||
version = "0.26.0"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
dependencies = [
|
||||
{ name = "deprecation" },
|
||||
@@ -1370,54 +1370,53 @@ dependencies = [
|
||||
{ name = "tqdm" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/d7/62/a149b47dc4ccf3c569eba722b805cbba1b90566976ff1d459f20f7f00ebc/lancedb-0.25.3-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:1cfa4dd97b33ca8f73288aa4b1baaddc9545ce0d3c8e5d06fba8feb77f42363f", size = 38425074, upload-time = "2025-11-07T05:58:15.763Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/b2/94/ae3e74bb27dcca321ccf1e7a32ccab09b1062ddf54f96376221ca8610e7c/lancedb-0.25.3-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8a7bfe0cb2146f6e78e9f376673ed2f906b93dab84df97dad2ba9fa52f97e152", size = 34506539, upload-time = "2025-11-07T05:14:04.901Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/6a/07/b580d0e002eaaa3d5216699fb9f19186c37861c3fa11ac3be991fa7d6d03/lancedb-0.25.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a395d07d31da1e13e2631fd9911b15e6d4fb903d34358cea0bd450006364e3", size = 36149261, upload-time = "2025-11-07T05:23:13.002Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/c1/95/32ddb779a01cd0d349f391e7d5f4218d045f9848c1d757f5a8ace4c63b09/lancedb-0.25.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500beac161f73e3e6826a711efb1d24397d892d07dfdce2c9fb1da73f8de506c", size = 39145675, upload-time = "2025-11-07T05:24:40.813Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f4/33/fdaff64a111f86dbb99f3ff09136df93b441e350f4953884a9fc21c49283/lancedb-0.25.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2d0fce4187582e48b69430d204665e164002f1b49b03e67747ca8ec2c3083481", size = 36165492, upload-time = "2025-11-07T05:27:13.394Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/ab/15/f0d69acc5e06892d19e09c127cd928cf20f5d2966a069e93693fc389b132/lancedb-0.25.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3035665fb8e4aaff8dff2602747cc77aeba6bc39f1a95345abc3275c97a044cb", size = 39191458, upload-time = "2025-11-07T05:24:38.047Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/bc/dc/3c5785cee0f0abaa5046ff817f3d64275909067500d6a317da0aeb9141b8/lancedb-0.25.3-cp39-abi3-win_amd64.whl", hash = "sha256:8c153d976bec79358d328e4c8a287a7b9c918b35b3912fff6864ced6b2a15943", size = 42080029, upload-time = "2025-11-07T05:46:32.639Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/a8/91/fe585b2181bd61efc65e1da410ae8ab7b29a26f156e4ca7d7d616b1234de/lancedb-0.26.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:3a0d435fff1392f056c173f695f71d495c691c555daa9802c056ea23f6a3900e", size = 41174270, upload-time = "2025-12-16T17:16:30.699Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/ce/fc/e47e092f4fc97a8810b37dbee07996689bca42f0817f3f3c38d7fb51dd9d/lancedb-0.26.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2206320fd0f33c01e264960afd768987646133cf152c4d3a8b7faf81b3017bf", size = 42936720, upload-time = "2025-12-16T17:24:43.527Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/b5/d7/323897d22a7c00ef1dc4f5b76df1a11df549fe887d8e05d689c2224e47b8/lancedb-0.26.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca0322cb4b62d526748f6f29e5b43cce4251c7f693e111897eb1f77e7f1ec2b", size = 45846184, upload-time = "2025-12-16T17:27:33.802Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/3a/0b/7671c94b27a5aa267b9f1d6db759c9e08070cb8f783828ade04da9dc7d79/lancedb-0.26.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7f2b8d69a647265b8753576b501354333c3edfd47d12ec9f47e665e8574c92fe", size = 42954293, upload-time = "2025-12-16T17:24:30.335Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/52/2e/9f720d6ae7bd3a94d096f320a0ec2f277735423af9d16cf5c61c4a70e6ca/lancedb-0.26.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:8e5cc334686a389cf2f28d1c239d13a205098ed98f3914226d3966858e58b957", size = 45896935, upload-time = "2025-12-16T17:27:30.156Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/00/0e/4b292c24a9e25ee2cd081d2da930fcdc672ee0eea531fc453c19c73addb5/lancedb-0.26.0-cp39-abi3-win_amd64.whl", hash = "sha256:2fc9b48a11f526de87388002eb3838329db7279241eefb3166c1c6c3b194a3cf", size = 50615000, upload-time = "2025-12-16T17:53:34.409Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "librt"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/b3/d9/6f3d3fcf5e5543ed8a60cc70fa7d50508ed60b8a10e9af6d2058159ab54e/librt-0.7.3.tar.gz", hash = "sha256:3ec50cf65235ff5c02c5b747748d9222e564ad48597122a361269dd3aa808798", size = 144549, upload-time = "2025-12-06T19:04:45.553Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/93/e4/b59bdf1197fdf9888452ea4d2048cdad61aef85eb83e99dc52551d7fdc04/librt-0.7.4.tar.gz", hash = "sha256:3871af56c59864d5fd21d1ac001eb2fb3b140d52ba0454720f2e4a19812404ba", size = 145862, upload-time = "2025-12-15T16:52:43.862Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/93/7d/e0ce1837dfb452427db556e6d4c5301ba3b22fe8de318379fbd0593759b9/librt-0.7.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56f2a47beda8409061bc1c865bef2d4bd9ff9255219402c0817e68ab5ad89aed", size = 55742, upload-time = "2025-12-06T19:03:52.459Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/be/c0/3564262301e507e1d5cf31c7d84cb12addf0d35e05ba53312494a2eba9a4/librt-0.7.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14569ac5dd38cfccf0a14597a88038fb16811a6fede25c67b79c6d50fc2c8fdc", size = 57163, upload-time = "2025-12-06T19:03:53.516Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/be/ac/245e72b7e443d24a562f6047563c7f59833384053073ef9410476f68505b/librt-0.7.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6038ccbd5968325a5d6fd393cf6e00b622a8de545f0994b89dd0f748dcf3e19e", size = 165840, upload-time = "2025-12-06T19:03:54.918Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/98/af/587e4491f40adba066ba39a450c66bad794c8d92094f936a201bfc7c2b5f/librt-0.7.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d39079379a9a28e74f4d57dc6357fa310a1977b51ff12239d7271ec7e71d67f5", size = 174827, upload-time = "2025-12-06T19:03:56.082Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/78/21/5b8c60ea208bc83dd00421022a3874330685d7e856404128dc3728d5d1af/librt-0.7.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8837d5a52a2d7aa9f4c3220a8484013aed1d8ad75240d9a75ede63709ef89055", size = 189612, upload-time = "2025-12-06T19:03:57.507Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/da/2f/8b819169ef696421fb81cd04c6cdf225f6e96f197366001e9d45180d7e9e/librt-0.7.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:399bbd7bcc1633c3e356ae274a1deb8781c7bf84d9c7962cc1ae0c6e87837292", size = 184584, upload-time = "2025-12-06T19:03:58.686Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/6c/fc/af9d225a9395b77bd7678362cb055d0b8139c2018c37665de110ca388022/librt-0.7.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8d8cf653e798ee4c4e654062b633db36984a1572f68c3aa25e364a0ddfbbb910", size = 178269, upload-time = "2025-12-06T19:03:59.769Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/6c/d8/7b4fa1683b772966749d5683aa3fd605813defffe157833a8fa69cc89207/librt-0.7.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2f03484b54bf4ae80ab2e504a8d99d20d551bfe64a7ec91e218010b467d77093", size = 199852, upload-time = "2025-12-06T19:04:00.901Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/77/e8/4598413aece46ca38d9260ef6c51534bd5f34b5c21474fcf210ce3a02123/librt-0.7.3-cp313-cp313-win32.whl", hash = "sha256:44b3689b040df57f492e02cd4f0bacd1b42c5400e4b8048160c9d5e866de8abe", size = 47936, upload-time = "2025-12-06T19:04:02.054Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/af/80/ac0e92d5ef8c6791b3e2c62373863827a279265e0935acdf807901353b0e/librt-0.7.3-cp313-cp313-win_amd64.whl", hash = "sha256:6b407c23f16ccc36614c136251d6b32bf30de7a57f8e782378f1107be008ddb0", size = 54965, upload-time = "2025-12-06T19:04:03.224Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f1/fd/042f823fcbff25c1449bb4203a29919891ca74141b68d3a5f6612c4ce283/librt-0.7.3-cp313-cp313-win_arm64.whl", hash = "sha256:abfc57cab3c53c4546aee31859ef06753bfc136c9d208129bad23e2eca39155a", size = 48350, upload-time = "2025-12-06T19:04:04.234Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/3e/ae/c6ecc7bb97134a71b5241e8855d39964c0e5f4d96558f0d60593892806d2/librt-0.7.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:120dd21d46ff875e849f1aae19346223cf15656be489242fe884036b23d39e93", size = 55175, upload-time = "2025-12-06T19:04:05.308Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/cf/bc/2cc0cb0ab787b39aa5c7645cd792433c875982bdf12dccca558b89624594/librt-0.7.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1617bea5ab31266e152871208502ee943cb349c224846928a1173c864261375e", size = 56881, upload-time = "2025-12-06T19:04:06.674Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/8e/87/397417a386190b70f5bf26fcedbaa1515f19dce33366e2684c6b7ee83086/librt-0.7.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93b2a1f325fefa1482516ced160c8c7b4b8d53226763fa6c93d151fa25164207", size = 163710, upload-time = "2025-12-06T19:04:08.437Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/c9/37/7338f85b80e8a17525d941211451199845093ca242b32efbf01df8531e72/librt-0.7.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d4801db8354436fd3936531e7f0e4feb411f62433a6b6cb32bb416e20b529f", size = 172471, upload-time = "2025-12-06T19:04:10.124Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/3b/e0/741704edabbfae2c852fedc1b40d9ed5a783c70ed3ed8e4fe98f84b25d13/librt-0.7.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11ad45122bbed42cfc8b0597450660126ef28fd2d9ae1a219bc5af8406f95678", size = 186804, upload-time = "2025-12-06T19:04:11.586Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f4/d1/0a82129d6ba242f3be9af34815be089f35051bc79619f5c27d2c449ecef6/librt-0.7.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6b4e7bff1d76dd2b46443078519dc75df1b5e01562345f0bb740cea5266d8218", size = 181817, upload-time = "2025-12-06T19:04:12.802Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/4f/32/704f80bcf9979c68d4357c46f2af788fbf9d5edda9e7de5786ed2255e911/librt-0.7.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:d86f94743a11873317094326456b23f8a5788bad9161fd2f0e52088c33564620", size = 175602, upload-time = "2025-12-06T19:04:14.004Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f7/6d/4355cfa0fae0c062ba72f541d13db5bc575770125a7ad3d4f46f4109d305/librt-0.7.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:754a0d09997095ad764ccef050dd5bf26cbf457aab9effcba5890dad081d879e", size = 196497, upload-time = "2025-12-06T19:04:15.487Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/2e/eb/ac6d8517d44209e5a712fde46f26d0055e3e8969f24d715f70bd36056230/librt-0.7.3-cp314-cp314-win32.whl", hash = "sha256:fbd7351d43b80d9c64c3cfcb50008f786cc82cba0450e8599fdd64f264320bd3", size = 44678, upload-time = "2025-12-06T19:04:16.688Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e9/93/238f026d141faf9958da588c761a0812a1a21c98cc54a76f3608454e4e59/librt-0.7.3-cp314-cp314-win_amd64.whl", hash = "sha256:d376a35c6561e81d2590506804b428fc1075fcc6298fc5bb49b771534c0ba010", size = 51689, upload-time = "2025-12-06T19:04:17.726Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/52/44/43f462ad9dcf9ed7d3172fe2e30d77b980956250bd90e9889a9cca93df2a/librt-0.7.3-cp314-cp314-win_arm64.whl", hash = "sha256:cbdb3f337c88b43c3b49ca377731912c101178be91cb5071aac48faa898e6f8e", size = 44662, upload-time = "2025-12-06T19:04:18.771Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/1d/35/fed6348915f96b7323241de97f26e2af481e95183b34991df12fd5ce31b1/librt-0.7.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9f0e0927efe87cd42ad600628e595a1a0aa1c64f6d0b55f7e6059079a428641a", size = 57347, upload-time = "2025-12-06T19:04:19.812Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/9a/f2/045383ccc83e3fea4fba1b761796584bc26817b6b2efb6b8a6731431d16f/librt-0.7.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:020c6db391268bcc8ce75105cb572df8cb659a43fd347366aaa407c366e5117a", size = 59223, upload-time = "2025-12-06T19:04:20.862Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/77/3f/c081f8455ab1d7f4a10dbe58463ff97119272ff32494f21839c3b9029c2c/librt-0.7.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7af7785f5edd1f418da09a8cdb9ec84b0213e23d597413e06525340bcce1ea4f", size = 183861, upload-time = "2025-12-06T19:04:21.963Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/1d/f5/73c5093c22c31fbeaebc25168837f05ebfd8bf26ce00855ef97a5308f36f/librt-0.7.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ccadf260bb46a61b9c7e89e2218f6efea9f3eeaaab4e3d1f58571890e54858e", size = 194594, upload-time = "2025-12-06T19:04:23.14Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/78/b8/d5f17d4afe16612a4a94abfded94c16c5a033f183074fb130dfe56fc1a42/librt-0.7.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9883b2d819ce83f87ba82a746c81d14ada78784db431e57cc9719179847376e", size = 206759, upload-time = "2025-12-06T19:04:24.328Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/36/2e/021765c1be85ee23ffd5b5b968bb4cba7526a4db2a0fc27dcafbdfc32da7/librt-0.7.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:59cb0470612d21fa1efddfa0dd710756b50d9c7fb6c1236bbf8ef8529331dc70", size = 203210, upload-time = "2025-12-06T19:04:25.544Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/77/f0/9923656e42da4fd18c594bd08cf6d7e152d4158f8b808e210d967f0dcceb/librt-0.7.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1fe603877e1865b5fd047a5e40379509a4a60204aa7aa0f72b16f7a41c3f0712", size = 196708, upload-time = "2025-12-06T19:04:26.725Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/fc/0b/0708b886ac760e64d6fbe7e16024e4be3ad1a3629d19489a97e9cf4c3431/librt-0.7.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5460d99ed30f043595bbdc888f542bad2caeb6226b01c33cda3ae444e8f82d42", size = 217212, upload-time = "2025-12-06T19:04:27.892Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/5d/7f/12a73ff17bca4351e73d585dd9ebf46723c4a8622c4af7fe11a2e2d011ff/librt-0.7.3-cp314-cp314t-win32.whl", hash = "sha256:d09f677693328503c9e492e33e9601464297c01f9ebd966ea8fc5308f3069bfd", size = 45586, upload-time = "2025-12-06T19:04:29.116Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e2/df/8decd032ac9b995e4f5606cde783711a71094128d88d97a52e397daf2c89/librt-0.7.3-cp314-cp314t-win_amd64.whl", hash = "sha256:25711f364c64cab2c910a0247e90b51421e45dbc8910ceeb4eac97a9e132fc6f", size = 53002, upload-time = "2025-12-06T19:04:30.173Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/de/0c/6605b6199de8178afe7efc77ca1d8e6db00453bc1d3349d27605c0f42104/librt-0.7.3-cp314-cp314t-win_arm64.whl", hash = "sha256:a9f9b661f82693eb56beb0605156c7fca57f535704ab91837405913417d6990b", size = 45647, upload-time = "2025-12-06T19:04:31.302Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/fe/4d/46a53ccfbb39fd0b493fd4496eb76f3ebc15bb3e45d8c2e695a27587edf5/librt-0.7.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d44a1b1ba44cbd2fc3cb77992bef6d6fdb1028849824e1dd5e4d746e1f7f7f0b", size = 55745, upload-time = "2025-12-15T16:51:46.636Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/7f/2b/3ac7f5212b1828bf4f979cf87f547db948d3e28421d7a430d4db23346ce4/librt-0.7.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c9cab4b3de1f55e6c30a84c8cee20e4d3b2476f4d547256694a1b0163da4fe32", size = 57166, upload-time = "2025-12-15T16:51:48.219Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e8/99/6523509097cbe25f363795f0c0d1c6a3746e30c2994e25b5aefdab119b21/librt-0.7.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2857c875f1edd1feef3c371fbf830a61b632fb4d1e57160bb1e6a3206e6abe67", size = 165833, upload-time = "2025-12-15T16:51:49.443Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/fe/35/323611e59f8fe032649b4fb7e77f746f96eb7588fcbb31af26bae9630571/librt-0.7.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b370a77be0a16e1ad0270822c12c21462dc40496e891d3b0caf1617c8cc57e20", size = 174818, upload-time = "2025-12-15T16:51:51.015Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/41/e6/40fb2bb21616c6e06b6a64022802228066e9a31618f493e03f6b9661548a/librt-0.7.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d05acd46b9a52087bfc50c59dfdf96a2c480a601e8898a44821c7fd676598f74", size = 189607, upload-time = "2025-12-15T16:51:52.671Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/32/48/1b47c7d5d28b775941e739ed2bfe564b091c49201b9503514d69e4ed96d7/librt-0.7.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70969229cb23d9c1a80e14225838d56e464dc71fa34c8342c954fc50e7516dee", size = 184585, upload-time = "2025-12-15T16:51:54.027Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/75/a6/ee135dfb5d3b54d5d9001dbe483806229c6beac3ee2ba1092582b7efeb1b/librt-0.7.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4450c354b89dbb266730893862dbff06006c9ed5b06b6016d529b2bf644fc681", size = 178249, upload-time = "2025-12-15T16:51:55.248Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/04/87/d5b84ec997338be26af982bcd6679be0c1db9a32faadab1cf4bb24f9e992/librt-0.7.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:adefe0d48ad35b90b6f361f6ff5a1bd95af80c17d18619c093c60a20e7a5b60c", size = 199851, upload-time = "2025-12-15T16:51:56.933Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/86/63/ba1333bf48306fe398e3392a7427ce527f81b0b79d0d91618c4610ce9d15/librt-0.7.4-cp313-cp313-win32.whl", hash = "sha256:21ea710e96c1e050635700695095962a22ea420d4b3755a25e4909f2172b4ff2", size = 43249, upload-time = "2025-12-15T16:51:58.498Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f9/8a/de2c6df06cdfa9308c080e6b060fe192790b6a48a47320b215e860f0e98c/librt-0.7.4-cp313-cp313-win_amd64.whl", hash = "sha256:772e18696cf5a64afee908662fbcb1f907460ddc851336ee3a848ef7684c8e1e", size = 49417, upload-time = "2025-12-15T16:51:59.618Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/31/66/8ee0949efc389691381ed686185e43536c20e7ad880c122dd1f31e65c658/librt-0.7.4-cp313-cp313-win_arm64.whl", hash = "sha256:52e34c6af84e12921748c8354aa6acf1912ca98ba60cdaa6920e34793f1a0788", size = 42824, upload-time = "2025-12-15T16:52:00.784Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/74/81/6921e65c8708eb6636bbf383aa77e6c7dad33a598ed3b50c313306a2da9d/librt-0.7.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4f1ee004942eaaed6e06c087d93ebc1c67e9a293e5f6b9b5da558df6bf23dc5d", size = 55191, upload-time = "2025-12-15T16:52:01.97Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/0d/d6/3eb864af8a8de8b39cc8dd2e9ded1823979a27795d72c4eea0afa8c26c9f/librt-0.7.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d854c6dc0f689bad7ed452d2a3ecff58029d80612d336a45b62c35e917f42d23", size = 56898, upload-time = "2025-12-15T16:52:03.356Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/49/bc/b1d4c0711fdf79646225d576faee8747b8528a6ec1ceb6accfd89ade7102/librt-0.7.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4f7339d9e445280f23d63dea842c0c77379c4a47471c538fc8feedab9d8d063", size = 163725, upload-time = "2025-12-15T16:52:04.572Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/2c/08/61c41cd8f0a6a41fc99ea78a2205b88187e45ba9800792410ed62f033584/librt-0.7.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39003fc73f925e684f8521b2dbf34f61a5deb8a20a15dcf53e0d823190ce8848", size = 172469, upload-time = "2025-12-15T16:52:05.863Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/8b/c7/4ee18b4d57f01444230bc18cf59103aeab8f8c0f45e84e0e540094df1df1/librt-0.7.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb15ee29d95875ad697d449fe6071b67f730f15a6961913a2b0205015ca0843", size = 186804, upload-time = "2025-12-15T16:52:07.192Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/a1/af/009e8ba3fbf830c936842da048eda1b34b99329f402e49d88fafff6525d1/librt-0.7.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:02a69369862099e37d00765583052a99d6a68af7e19b887e1b78fee0146b755a", size = 181807, upload-time = "2025-12-15T16:52:08.554Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/85/26/51ae25f813656a8b117c27a974f25e8c1e90abcd5a791ac685bf5b489a1b/librt-0.7.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ec72342cc4d62f38b25a94e28b9efefce41839aecdecf5e9627473ed04b7be16", size = 175595, upload-time = "2025-12-15T16:52:10.186Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/48/93/36d6c71f830305f88996b15c8e017aa8d1e03e2e947b40b55bbf1a34cf24/librt-0.7.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:776dbb9bfa0fc5ce64234b446995d8d9f04badf64f544ca036bd6cff6f0732ce", size = 196504, upload-time = "2025-12-15T16:52:11.472Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/08/11/8299e70862bb9d704735bf132c6be09c17b00fbc7cda0429a9df222fdc1b/librt-0.7.4-cp314-cp314-win32.whl", hash = "sha256:0f8cac84196d0ffcadf8469d9ded4d4e3a8b1c666095c2a291e22bf58e1e8a9f", size = 39738, upload-time = "2025-12-15T16:52:12.962Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/54/d5/656b0126e4e0f8e2725cd2d2a1ec40f71f37f6f03f135a26b663c0e1a737/librt-0.7.4-cp314-cp314-win_amd64.whl", hash = "sha256:037f5cb6fe5abe23f1dc058054d50e9699fcc90d0677eee4e4f74a8677636a1a", size = 45976, upload-time = "2025-12-15T16:52:14.441Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/60/86/465ff07b75c1067da8fa7f02913c4ead096ef106cfac97a977f763783bfb/librt-0.7.4-cp314-cp314-win_arm64.whl", hash = "sha256:a5deebb53d7a4d7e2e758a96befcd8edaaca0633ae71857995a0f16033289e44", size = 39073, upload-time = "2025-12-15T16:52:15.621Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/b3/a0/24941f85960774a80d4b3c2aec651d7d980466da8101cae89e8b032a3e21/librt-0.7.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b4c25312c7f4e6ab35ab16211bdf819e6e4eddcba3b2ea632fb51c9a2a97e105", size = 57369, upload-time = "2025-12-15T16:52:16.782Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/77/a0/ddb259cae86ab415786c1547d0fe1b40f04a7b089f564fd5c0242a3fafb2/librt-0.7.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:618b7459bb392bdf373f2327e477597fff8f9e6a1878fffc1b711c013d1b0da4", size = 59230, upload-time = "2025-12-15T16:52:18.259Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/31/11/77823cb530ab8a0c6fac848ac65b745be446f6f301753b8990e8809080c9/librt-0.7.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1437c3f72a30c7047f16fd3e972ea58b90172c3c6ca309645c1c68984f05526a", size = 183869, upload-time = "2025-12-15T16:52:19.457Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/a4/ce/157db3614cf3034b3f702ae5ba4fefda4686f11eea4b7b96542324a7a0e7/librt-0.7.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c96cb76f055b33308f6858b9b594618f1b46e147a4d03a4d7f0c449e304b9b95", size = 194606, upload-time = "2025-12-15T16:52:20.795Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/30/ef/6ec4c7e3d6490f69a4fd2803516fa5334a848a4173eac26d8ee6507bff6e/librt-0.7.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28f990e6821204f516d09dc39966ef8b84556ffd648d5926c9a3f681e8de8906", size = 206776, upload-time = "2025-12-15T16:52:22.229Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/ad/22/750b37bf549f60a4782ab80e9d1e9c44981374ab79a7ea68670159905918/librt-0.7.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc4aebecc79781a1b77d7d4e7d9fe080385a439e198d993b557b60f9117addaf", size = 203205, upload-time = "2025-12-15T16:52:23.603Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/7a/87/2e8a0f584412a93df5faad46c5fa0a6825fdb5eba2ce482074b114877f44/librt-0.7.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:022cc673e69283a42621dd453e2407cf1647e77f8bd857d7ad7499901e62376f", size = 196696, upload-time = "2025-12-15T16:52:24.951Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e5/ca/7bf78fa950e43b564b7de52ceeb477fb211a11f5733227efa1591d05a307/librt-0.7.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2b3ca211ae8ea540569e9c513da052699b7b06928dcda61247cb4f318122bdb5", size = 217191, upload-time = "2025-12-15T16:52:26.194Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/d6/49/3732b0e8424ae35ad5c3166d9dd5bcdae43ce98775e0867a716ff5868064/librt-0.7.4-cp314-cp314t-win32.whl", hash = "sha256:8a461f6456981d8c8e971ff5a55f2e34f4e60871e665d2f5fde23ee74dea4eeb", size = 40276, upload-time = "2025-12-15T16:52:27.54Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/35/d6/d8823e01bd069934525fddb343189c008b39828a429b473fb20d67d5cd36/librt-0.7.4-cp314-cp314t-win_amd64.whl", hash = "sha256:721a7b125a817d60bf4924e1eec2a7867bfcf64cfc333045de1df7a0629e4481", size = 46772, upload-time = "2025-12-15T16:52:28.653Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/36/e9/a0aa60f5322814dd084a89614e9e31139702e342f8459ad8af1984a18168/librt-0.7.4-cp314-cp314t-win_arm64.whl", hash = "sha256:76b2ba71265c0102d11458879b4d53ccd0b32b0164d14deb8d2b598a018e502f", size = 39724, upload-time = "2025-12-15T16:52:29.836Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1692,7 +1691,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "mcp"
|
||||
version = "1.16.0"
|
||||
version = "1.23.3"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@@ -1701,15 +1700,18 @@ dependencies = [
|
||||
{ name = "jsonschema" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "pyjwt", extra = ["crypto"] },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
||||
{ name = "sse-starlette" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
||||
]
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/3d/a1/b1f328da3b153683d2ec34f849b4b6eac2790fb240e3aef06ff2fab3df9d/mcp-1.16.0.tar.gz", hash = "sha256:39b8ca25460c578ee2cdad33feeea122694cfdf73eef58bee76c42f6ef0589df", size = 472918, upload-time = "2025-10-02T16:58:20.631Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/a7/a4/d06a303f45997e266f2c228081abe299bbcba216cb806128e2e49095d25f/mcp-1.23.3.tar.gz", hash = "sha256:b3b0da2cc949950ce1259c7bfc1b081905a51916fcd7c8182125b85e70825201", size = 600697, upload-time = "2025-12-09T16:04:37.351Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/c9/0e/7cebc88e17daf94ebe28c95633af595ccb2864dc2ee7abd75542d98495cc/mcp-1.16.0-py3-none-any.whl", hash = "sha256:ec917be9a5d31b09ba331e1768aa576e0af45470d657a0319996a20a57d7d633", size = 167266, upload-time = "2025-10-02T16:58:19.039Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/32/c6/13c1a26b47b3f3a3b480783001ada4268917c9f42d78a079c336da2e75e5/mcp-1.23.3-py3-none-any.whl", hash = "sha256:32768af4b46a1b4f7df34e2bfdf5c6011e7b63d7f1b0e321d0fdef4cd6082031", size = 231570, upload-time = "2025-12-09T16:04:35.56Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1921,29 +1923,29 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.19.0"
|
||||
version = "1.19.1"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
dependencies = [
|
||||
{ name = "librt" },
|
||||
{ name = "librt", marker = "platform_python_implementation != 'PyPy'" },
|
||||
{ name = "mypy-extensions" },
|
||||
{ name = "pathspec" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f9/b5/b58cdc25fadd424552804bf410855d52324183112aa004f0732c5f6324cf/mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528", size = 3579025, upload-time = "2025-11-28T15:49:01.26Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/cb/0d/a1357e6bb49e37ce26fcf7e3cc55679ce9f4ebee0cd8b6ee3a0e301a9210/mypy-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7686ed65dbabd24d20066f3115018d2dce030d8fa9db01aa9f0a59b6813e9f9e", size = 13191993, upload-time = "2025-11-28T15:47:22.336Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/5d/75/8e5d492a879ec4490e6ba664b5154e48c46c85b5ac9785792a5ec6a4d58f/mypy-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4a985b2e32f23bead72e2fb4bbe5d6aceee176be471243bd831d5b2644672d", size = 12174411, upload-time = "2025-11-28T15:44:55.492Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/71/31/ad5dcee9bfe226e8eaba777e9d9d251c292650130f0450a280aec3485370/mypy-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc51a5b864f73a3a182584b1ac75c404396a17eced54341629d8bdcb644a5bba", size = 12727751, upload-time = "2025-11-28T15:44:14.169Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/77/06/b6b8994ce07405f6039701f4b66e9d23f499d0b41c6dd46ec28f96d57ec3/mypy-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37af5166f9475872034b56c5efdcf65ee25394e9e1d172907b84577120714364", size = 13593323, upload-time = "2025-11-28T15:46:34.699Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/68/b1/126e274484cccdf099a8e328d4fda1c7bdb98a5e888fa6010b00e1bbf330/mypy-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:510c014b722308c9bd377993bcbf9a07d7e0692e5fa8fc70e639c1eb19fc6bee", size = 13818032, upload-time = "2025-11-28T15:46:18.286Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f8/56/53a8f70f562dfc466c766469133a8a4909f6c0012d83993143f2a9d48d2d/mypy-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:cabbee74f29aa9cd3b444ec2f1e4fa5a9d0d746ce7567a6a609e224429781f53", size = 10120644, upload-time = "2025-11-28T15:47:43.99Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/b0/f4/7751f32f56916f7f8c229fe902cbdba3e4dd3f3ea9e8b872be97e7fc546d/mypy-1.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f2e36bed3c6d9b5f35d28b63ca4b727cb0228e480826ffc8953d1892ddc8999d", size = 13185236, upload-time = "2025-11-28T15:45:20.696Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/35/31/871a9531f09e78e8d145032355890384f8a5b38c95a2c7732d226b93242e/mypy-1.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a18d8abdda14035c5718acb748faec09571432811af129bf0d9e7b2d6699bf18", size = 12213902, upload-time = "2025-11-28T15:46:10.117Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/58/b8/af221910dd40eeefa2077a59107e611550167b9994693fc5926a0b0f87c0/mypy-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75e60aca3723a23511948539b0d7ed514dda194bc3755eae0bfc7a6b4887aa7", size = 12738600, upload-time = "2025-11-28T15:44:22.521Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/11/9f/c39e89a3e319c1d9c734dedec1183b2cc3aefbab066ec611619002abb932/mypy-1.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f44f2ae3c58421ee05fe609160343c25f70e3967f6e32792b5a78006a9d850f", size = 13592639, upload-time = "2025-11-28T15:48:08.55Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/97/6d/ffaf5f01f5e284d9033de1267e6c1b8f3783f2cf784465378a86122e884b/mypy-1.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63ea6a00e4bd6822adbfc75b02ab3653a17c02c4347f5bb0cf1d5b9df3a05835", size = 13799132, upload-time = "2025-11-28T15:47:06.032Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/fe/b0/c33921e73aaa0106224e5a34822411bea38046188eb781637f5a5b07e269/mypy-1.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ad925b14a0bb99821ff6f734553294aa6a3440a8cb082fe1f5b84dfb662afb1", size = 10269832, upload-time = "2025-11-28T15:47:29.392Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/09/0e/fe228ed5aeab470c6f4eb82481837fadb642a5aa95cc8215fd2214822c10/mypy-1.19.0-py3-none-any.whl", hash = "sha256:0c01c99d626380752e527d5ce8e69ffbba2046eb8a060db0329690849cf9b6f9", size = 2469714, upload-time = "2025-11-28T15:45:33.22Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2197,7 +2199,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "openai"
|
||||
version = "2.11.0"
|
||||
version = "2.13.0"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@@ -2209,9 +2211,9 @@ dependencies = [
|
||||
{ name = "tqdm" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f4/8c/aa6aea6072f985ace9d6515046b9088ff00c157f9654da0c7b1e129d9506/openai-2.11.0.tar.gz", hash = "sha256:b3da01d92eda31524930b6ec9d7167c535e843918d7ba8a76b1c38f1104f321e", size = 624540, upload-time = "2025-12-11T19:11:58.539Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/0f/39/8e347e9fda125324d253084bb1b82407e5e3c7777a03dc398f79b2d95626/openai-2.13.0.tar.gz", hash = "sha256:9ff633b07a19469ec476b1e2b5b26c5ef700886524a7a72f65e6f0b5203142d5", size = 626583, upload-time = "2025-12-16T18:19:44.387Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e5/f1/d9251b565fce9f8daeb45611e3e0d2f7f248429e40908dcee3b6fe1b5944/openai-2.11.0-py3-none-any.whl", hash = "sha256:21189da44d2e3d027b08c7a920ba4454b8b7d6d30ae7e64d9de11dbe946d4faa", size = 1064131, upload-time = "2025-12-11T19:11:56.816Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/bb/d5/eb52edff49d3d5ea116e225538c118699ddeb7c29fa17ec28af14bc10033/openai-2.13.0-py3-none-any.whl", hash = "sha256:746521065fed68df2f9c2d85613bb50844343ea81f60009b60e6a600c9352c79", size = 1066837, upload-time = "2025-12-16T18:19:43.124Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2904,15 +2906,15 @@ crypto = [
|
||||
|
||||
[[package]]
|
||||
name = "pymdown-extensions"
|
||||
version = "10.19"
|
||||
version = "10.19.1"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
dependencies = [
|
||||
{ name = "markdown" },
|
||||
{ name = "pyyaml" },
|
||||
]
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/1f/4e/e73e88f4f2d0b26cbd2e100074107470984f0a6055869805fc181b847ac7/pymdown_extensions-10.19.tar.gz", hash = "sha256:01bb917ea231f9ce14456fa9092cdb95ac3e5bd32202a3ee61dbd5ad2dd9ef9b", size = 847701, upload-time = "2025-12-11T18:20:46.093Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/72/2d/9f30cee56d4d6d222430d401e85b0a6a1ae229819362f5786943d1a8c03b/pymdown_extensions-10.19.1.tar.gz", hash = "sha256:4969c691009a389fb1f9712dd8e7bd70dcc418d15a0faf70acb5117d022f7de8", size = 847839, upload-time = "2025-12-14T17:25:24.42Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/d4/56/fa9edaceb3805e03ac9faf68ca1ddc660a75b49aee5accb493511005fef5/pymdown_extensions-10.19-py3-none-any.whl", hash = "sha256:dc5f249fc3a1b6d8a6de4634ba8336b88d0942cee75e92b18ac79eaf3503bf7c", size = 266670, upload-time = "2025-12-11T18:20:44.736Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/fb/35/b763e8fbcd51968329b9adc52d188fc97859f85f2ee15fe9f379987d99c5/pymdown_extensions-10.19.1-py3-none-any.whl", hash = "sha256:e8698a66055b1dc0dca2a7f2c9d0ea6f5faa7834a9c432e3535ab96c0c4e509b", size = 266693, upload-time = "2025-12-14T17:25:22.999Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3038,11 +3040,11 @@ cryptography = [
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.20"
|
||||
version = "0.0.21"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/78/96/804520d0850c7db98e5ccb70282e29208723f0964e88ffd9d0da2f52ea09/python_multipart-0.0.21.tar.gz", hash = "sha256:7137ebd4d3bbf70ea1622998f902b97a29434a9e8dc40eb203bbcf7c2a2cba92", size = 37196, upload-time = "2025-12-17T09:24:22.446Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/aa/76/03af049af4dcee5d27442f71b6924f01f3efb5d2bd34f23fcd563f2cc5f5/python_multipart-0.0.21-py3-none-any.whl", hash = "sha256:cf7a6713e01c87aa35387f4774e812c4361150938d20d232800f75ffcf266090", size = 24541, upload-time = "2025-12-17T09:24:21.153Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3697,7 +3699,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "semgrep"
|
||||
version = "1.145.2"
|
||||
version = "1.146.0"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
@@ -3725,13 +3727,13 @@ dependencies = [
|
||||
{ name = "urllib3" },
|
||||
{ name = "wcmatch" },
|
||||
]
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/2a/08/379a699032f63695b0d339f14ba1deac4f7354242b1611d9fdc132ca1ef5/semgrep-1.145.2.tar.gz", hash = "sha256:52e837152fc0d0536a9870e949f73a750aaddec39cd16d5de03382ff820cb23c", size = 42375275, upload-time = "2025-12-13T01:53:47.901Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/e6/7d/5f712a254c0d34acc465876dcea0904cb729d881238fd0f7c9dde32ef916/semgrep-1.146.0.tar.gz", hash = "sha256:a6f665a9ff9c5184bbc89677f7e6953b0f298a3a9bc7d1ab0b75f368c64c9f82", size = 42404719, upload-time = "2025-12-17T20:44:43.159Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/8a/1d/86fea11eb85dd5eb2e6ffb5ab48d5096ce5256876992725d6d41c11c72ae/semgrep-1.145.2-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_10_14_x86_64.whl", hash = "sha256:64bce21fac450e9db64d7ba51e4354160a84efed4c85ca693ba1e5f1cdf98f0a", size = 35051470, upload-time = "2025-12-13T01:53:32.44Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/d9/ff/56d2caa49e41e636f886e2fca0d307f60357bbca2772af6c958eff42eaf0/semgrep-1.145.2-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_11_0_arm64.whl", hash = "sha256:4f212bc7b58dea2f6d99e108b76a3eb6919696dafbb3de66f79035e5020f810f", size = 39953168, upload-time = "2025-12-13T01:53:35.722Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/31/de/fe69e8d5cc797ff7a8f40d657352e8d4fa8875b33c9931b3c896a03322f8/semgrep-1.145.2-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_0_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550f9ca6c5024cd9c92a717d2dfe46123e22c98ff545543c8578235f874f1bb3", size = 54321315, upload-time = "2025-12-13T01:53:38.805Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/b6/e1/86d206f5f97945ccc9b5b2fd9f84ae743a2d035cd485ea809d3c0cd89d56/semgrep-1.145.2-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_0_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5633950d57bb596fef545f694c6131371301c5807740f3b0e46801091c9120a0", size = 50438864, upload-time = "2025-12-13T01:53:41.909Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/f5/1f/e7997d959c115bef967a27ddb8355afc817433a41eb13561f8ccdfbce1b4/semgrep-1.145.2-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-win_amd64.whl", hash = "sha256:57b6433a8620bacaf05c48c8a5ce8f6ae11f0e96e852492c931d877b0534c8a5", size = 42997312, upload-time = "2025-12-13T01:53:44.621Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/88/56/ce2f158819fc7b1fed249930469472ec7a0583a078c12aa956cb36405b8d/semgrep-1.146.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_10_14_x86_64.whl", hash = "sha256:d79328f245bc106ef95b80d2569c804551850a0fd29256a99d734175429c15ae", size = 35070245, upload-time = "2025-12-17T20:44:23.913Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/bf/76/77e79b401d4371a9c9554877d74b7a999ffceeb3679b0b1050650a592ce0/semgrep-1.146.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_11_0_arm64.whl", hash = "sha256:a2d0efba16bafef120031c6bafad40a8288b24f70248e1119881bc4eb26390a2", size = 39967238, upload-time = "2025-12-17T20:44:27.874Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/53/df/67f6f310a88c64e798654766710f0901a7d5f42deadffc5646463c186c4d/semgrep-1.146.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_0_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9335bf8579834aada5663338bf07eb83d9c51ab55e7470d39bdaeb095a0bdab", size = 54436507, upload-time = "2025-12-17T20:44:31.967Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/de/db/c873b2c9f7d2ca49d8a9e536991066a513d2cabde15c587034e03f7ab2ed/semgrep-1.146.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_0_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8eaae8191c3dd5b85c7b40cfaeca6db623fe387c85f8938176d112855cca829", size = 50525908, upload-time = "2025-12-17T20:44:35.947Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/28/e9/4d6f31e21803c571a54cfeeeaaa6c3a7909645a2421e4d247a2ca44543fd/semgrep-1.146.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-win_amd64.whl", hash = "sha256:57e2590ada20fba58376fe541d6babaae9fb95763f0fe132df0729563af4ce4c", size = 43021046, upload-time = "2025-12-17T20:44:39.742Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3856,14 +3858,15 @@ asyncio = [
|
||||
|
||||
[[package]]
|
||||
name = "sse-starlette"
|
||||
version = "3.0.3"
|
||||
version = "3.0.4"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "starlette" },
|
||||
]
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/db/3c/fa6517610dc641262b77cc7bf994ecd17465812c1b0585fe33e11be758ab/sse_starlette-3.0.3.tar.gz", hash = "sha256:88cfb08747e16200ea990c8ca876b03910a23b547ab3bd764c0d8eb81019b971", size = 21943, upload-time = "2025-10-30T18:44:20.117Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/17/8b/54651ad49bce99a50fd61a7f19c2b6a79fbb072e693101fbb1194c362054/sse_starlette-3.0.4.tar.gz", hash = "sha256:5e34286862e96ead0eb70f5ddd0bd21ab1f6473a8f44419dd267f431611383dd", size = 22576, upload-time = "2025-12-14T16:22:52.493Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/23/a0/984525d19ca5c8a6c33911a0c164b11490dd0f90ff7fd689f704f84e9a11/sse_starlette-3.0.3-py3-none-any.whl", hash = "sha256:af5bf5a6f3933df1d9c7f8539633dc8444ca6a97ab2e2a7cd3b6e431ac03a431", size = 11765, upload-time = "2025-10-30T18:44:18.834Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/71/22/8ab1066358601163e1ac732837adba3672f703818f693e179b24e0d3b65c/sse_starlette-3.0.4-py3-none-any.whl", hash = "sha256:32c80ef0d04506ced4b0b6ab8fe300925edc37d26f666afb1874c754895f5dc3", size = 11764, upload-time = "2025-12-14T16:22:51.453Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4279,11 +4282,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.2"
|
||||
version = "2025.3"
|
||||
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
|
||||
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
|
||||
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user