Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ Studio 的持续集成和持续部署工作流。
- 手动触发 (workflow_dispatch)

**测试矩阵**:
- Python 3.10, 3.11
<!-- - Python 3.10, 3.11 -->
- Python 3.11
- 单元测试 + 集成测试

**测试步骤**:
Expand Down
20 changes: 16 additions & 4 deletions .github/workflows/ci-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ jobs:

strategy:
matrix:
python-version: ['3.10', '3.11']
# python-version: ['3.10', '3.11']
python-version: ['3.11']

env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
Expand All @@ -67,16 +68,26 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: 'pyproject.toml'

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential

- name: Install Python dependencies
- name: Install CI test dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
python -m pip install --no-cache-dir -r requirements-ci.txt

- name: Verify key dependencies
run: |
python -c "from jose import JWTError, jwt; print('jose OK')"
python -c "from passlib.context import CryptContext; print('passlib OK')"
python -c "import fastapi; print('fastapi OK')"

- name: Install package (best-effort, private deps may be unavailable)
run: python -m pip install --no-cache-dir -e ".[dev]" || true

- name: Run unit tests
if: ${{ github.event.inputs.test_type == 'unit' || github.event.inputs.test_type == 'all' || github.event.inputs.test_type == '' }}
Expand Down Expand Up @@ -123,11 +134,12 @@ jobs:
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: 'pyproject.toml'

- name: Install dependencies
run: |
pip install --upgrade pip
pip install -e ".[dev]"
pip install --no-cache-dir -e ".[dev]"

- name: Run LLM 集成测试 (CPU backend)
run: |
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ Source: `https://pypi.org/pypi/isage-studio/json` (checked on 2026-02-14, UTC).

### Added
- CI/CD workflow for automated testing (`.github/workflows/ci-test.yml`)
- Unit tests on Python 3.10 and 3.11
<!-- - Unit tests on Python 3.10 and 3.11 -->
- Unit tests on Python 3.11
- Integration tests with CPU backend
- E2E tests for LLM integration
- Code quality checks (Ruff, Mypy)
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ Studio 采用**前后端分离**架构,直接接入 SAGE 核心引擎:

### Environment Requirements

- **Python**: 3.10+ (必需)
<!-- - **Python**: 3.10+ (必需) -->
- **Python**: 3.11+ (必需)
- **Node.js**: 18+ (推荐 LTS)
- **SAGE**: 完整安装 (包括 kernel, middleware, libs)

Expand Down Expand Up @@ -525,7 +526,8 @@ React 18.2 + TypeScript 5.2
#### 后端

```
FastAPI + Python 3.10+
<!-- FastAPI + Python 3.10+ -->
FastAPI + Python 3.11+
├── Pydantic 2.0 # 数据验证
├── Uvicorn # ASGI 服务器
├── sage-kernel # Environment, DataStream API
Expand Down Expand Up @@ -870,7 +872,8 @@ sage studio start --dev

```bash
# 检查 Python 版本
python --version # 需要 3.10+
# python --version # 需要 3.10+
python --version # 需要 3.11+

# 检查 SAGE 安装
pip list | grep isage
Expand Down
34 changes: 22 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ name = "isage-studio"
dynamic = ["version"]
description = "SAGE Studio - Visual workflow builder and LLM playground for SAGE AI pipelines"
readme = "README.md"
requires-python = ">=3.10"
# requires-python = ">=3.10"
requires-python = ">=3.11"
authors = [{ name = "IntelliStream Team", email = "shuhao_zhang@hust.edu.cn" }]
keywords = [
"sage",
Expand Down Expand Up @@ -74,15 +75,8 @@ dependencies = [
# SAGE kernel integration (includes sage.middleware.* pipeline layer)
"isage-kernel>=0.2.5.0",
"isage-vida>=0.1.0",
]

license = { text = "MIT" }

[project.optional-dependencies]
full = [
# Continual-learning / coreset selection for tool-use features
"isage-sias>=0.1.0",
# Backend API / auth / config helpers (optional for lightweight studio installs)
# Backend API / auth / config helpers (required by services layer)
"aiofiles>=23.0.0",
"configparser>=5.3.0",
"markdown>=3.4.4,<4.0.0",
Expand All @@ -92,11 +86,25 @@ full = [
"structlog>=23.0.0",
]

license = { text = "MIT" }

[project.optional-dependencies]
full = [
# Continual-learning / coreset selection for tool-use features
"isage-sias>=0.1.0",
]

dev = [
"pytest>=7.4.0",
"pytest-asyncio>=0.21.0",
"ruff==0.15.4",
"ruff==0.15.8",
"isage-pypi-publisher>=0.2.1.0",
# Auth/backend deps required by unit tests (duplicated from core for CI reliability)
"python-jose[cryptography]>=3.5.0,<4.0.0",
"passlib[argon2]>=1.7.4,<2.0.0",
"aiofiles>=23.0.0",
"pydantic-settings>=2.0.0",
"structlog>=23.0.0",
]

[project.urls]
Expand Down Expand Up @@ -143,7 +151,8 @@ version = { attr = "sage.studio._version.__version__" }
# Development tools configuration
[tool.black]
line-length = 100
target-version = ["py310", "py311", "py312"]
# target-version = ["py310", "py311", "py312"]
target-version = ["py311", "py312"]
include = '\.pyi?$'

[tool.isort]
Expand Down Expand Up @@ -187,7 +196,8 @@ filterwarnings = [

[tool.ruff]
line-length = 100
target-version = "py310"
# target-version = "py310"
target-version = "py311"

[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP"]
Expand Down
9 changes: 6 additions & 3 deletions quickstart.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,13 @@ fi
echo -e "${YELLOW}${BOLD}Step 1/3: Checking Python environment${NC}"
PYTHON_VERSION=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null || echo "unknown")
echo -e " Python version: ${CYAN}${PYTHON_VERSION}${NC}"
if python3 -c "import sys; exit(0 if sys.version_info >= (3,10) else 1)" 2>/dev/null; then
echo -e " ${GREEN}✓ Python ≥ 3.10${NC}"
# if python3 -c "import sys; exit(0 if sys.version_info >= (3,10) else 1)" 2>/dev/null; then
if python3 -c "import sys; exit(0 if sys.version_info >= (3,11) else 1)" 2>/dev/null; then
# echo -e " ${GREEN}✓ Python ≥ 3.10${NC}"
echo -e " ${GREEN}✓ Python ≥ 3.11${NC}"
else
echo -e " ${RED}✗ Python 3.10+ required (found ${PYTHON_VERSION})${NC}"
# echo -e " ${RED}✗ Python 3.10+ required (found ${PYTHON_VERSION})${NC}"
echo -e " ${RED}✗ Python 3.11+ required (found ${PYTHON_VERSION})${NC}"
exit 1
fi
echo ""
Expand Down
31 changes: 31 additions & 0 deletions requirements-ci.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# CI test requirements - public PyPI packages only
# Used by GitHub Actions to bootstrap test environment independently
# of private SAGE packages (isage, isagellm, etc.)

# Test framework
pytest>=7.4.0
pytest-asyncio>=0.21.0

# Auth (required by services/auth_service.py)
python-jose[cryptography]>=3.5.0,<4.0.0
passlib[argon2]>=1.7.4,<2.0.0

# Backend API
pydantic[email]>=2.10.0,<3.0.0
pydantic-settings>=2.0.0
fastapi>=0.115.0,<1.0.0
starlette>=0.40,<0.53
uvicorn[standard]>=0.34.0,<1.0.0
httpx>=0.28.0,<1.0.0
python-multipart>=0.0.6
websockets>=11.0

# Utilities
aiofiles>=23.0.0
configparser>=5.3.0
markdown>=3.4.4,<4.0.0
structlog>=23.0.0
jinja2>=3.1.0,<4.0.0
markupsafe>=2.0.1
PyYAML>=6.0.1
packaging>=24.0,<27.0
2 changes: 1 addition & 1 deletion src/sage/studio/_version.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Version information for sage-studio package."""

# 独立硬编码版本
__version__ = "0.2.4.37"
__version__ = "0.2.4.44"
__author__ = "IntelliStream Team"
__email__ = "shuhao_zhang@hust.edu.cn"
4 changes: 2 additions & 2 deletions src/sage/studio/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from __future__ import annotations

from datetime import datetime, timezone
from datetime import UTC, datetime

from fastapi import APIRouter
from pydantic import BaseModel
Expand All @@ -26,7 +26,7 @@ class TokenResponse(BaseModel):


def _now_iso() -> str:
return datetime.now(timezone.utc).isoformat()
return datetime.now(UTC).isoformat()


def build_auth_router() -> APIRouter:
Expand Down
8 changes: 4 additions & 4 deletions src/sage/studio/api/canvas.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import json
from datetime import datetime, timezone
from datetime import UTC, datetime
from pathlib import Path
from typing import Any

Expand Down Expand Up @@ -144,8 +144,8 @@ async def publish_canvas_graph_route(
"metadata": req.metadata,
"params_schema": {},
"dataset_requirements": {},
"created_at": datetime.now(timezone.utc).isoformat(),
"updated_at": datetime.now(timezone.utc).isoformat(),
"created_at": datetime.now(UTC).isoformat(),
"updated_at": datetime.now(UTC).isoformat(),
},
compiled_payload=compiled_payload,
)
Expand Down Expand Up @@ -184,7 +184,7 @@ async def export_flow(flow_id: str):

export_data = {
"version": "1.0.0",
"exportTime": str(datetime.now(timezone.utc)),
"exportTime": str(datetime.now(UTC)),
"flowId": flow_id,
"flow": flow_data,
}
Expand Down
14 changes: 7 additions & 7 deletions src/sage/studio/contracts/models.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
from __future__ import annotations

from datetime import datetime, timezone
from enum import Enum
from datetime import UTC, datetime
from enum import StrEnum
from typing import Any

from pydantic import BaseModel, ConfigDict, Field

CONTRACT_SCHEMA_VERSION = "v1"


class RunKind(str, Enum):
class RunKind(StrEnum):
CHAT = "chat"
EXPERIMENT = "experiment"
SWARM = "swarm"


class StageEventState(str, Enum):
class StageEventState(StrEnum):
CREATED = "created"
QUEUED = "queued"
RUNNING = "running"
Expand All @@ -24,7 +24,7 @@ class StageEventState(str, Enum):
CANCELLED = "cancelled"


class ArtifactKind(str, Enum):
class ArtifactKind(StrEnum):
INPUT = "input"
OUTPUT = "output"
LOG = "log"
Expand All @@ -39,7 +39,7 @@ class RunRef(BaseModel):
request_id: str
workspace_id: str
kind: RunKind
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))


class StageEvent(BaseModel):
Expand All @@ -50,7 +50,7 @@ class StageEvent(BaseModel):
request_id: str
stage: str
state: StageEventState
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC))
message: str | None = None
metrics: dict[str, Any] | None = None

Expand Down
10 changes: 5 additions & 5 deletions src/sage/studio/runtime/endpoints/contracts.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from datetime import UTC, datetime
from enum import StrEnum


class EndpointProvider(str, Enum):
class EndpointProvider(StrEnum):
ALIBABA_DASHSCOPE = "alibaba_dashscope"
OPENAI = "openai"
ANTHROPIC = "anthropic"
Expand Down Expand Up @@ -39,8 +39,8 @@ class ManagedEndpoint:
is_default: bool = False
extra_headers: tuple[tuple[str, str], ...] = ()
api_key: str | None = None
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
updated_at: datetime = field(default_factory=lambda: datetime.now(UTC))


@dataclass(slots=True, frozen=True)
Expand Down
8 changes: 4 additions & 4 deletions src/sage/studio/runtime/endpoints/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import threading
from dataclasses import replace
from datetime import datetime, timezone
from datetime import UTC, datetime

from sage.studio.runtime.endpoints.contracts import (
PROVIDER_PRESETS,
Expand Down Expand Up @@ -59,7 +59,7 @@ def update_endpoint(self, endpoint_id: str, command: EndpointUpdate) -> ManagedE
if current is None:
raise KeyError(endpoint_id)

now = datetime.now(timezone.utc)
now = datetime.now(UTC)
updated = replace(
current,
display_name=command.display_name
Expand Down Expand Up @@ -92,7 +92,7 @@ def set_enabled(self, endpoint_id: str, enabled: bool) -> ManagedEndpoint:
current = self._records.get(endpoint_id)
if current is None:
raise KeyError(endpoint_id)
updated = replace(current, enabled=enabled, updated_at=datetime.now(timezone.utc))
updated = replace(current, enabled=enabled, updated_at=datetime.now(UTC))
self._records[endpoint_id] = updated
return updated

Expand Down Expand Up @@ -140,7 +140,7 @@ def reset(self) -> None:
self._records.clear()

def _set_default_locked(self, endpoint_id: str) -> None:
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
for record_id, record in list(self._records.items()):
self._records[record_id] = replace(
record,
Expand Down
Loading
Loading