galaxy-api-endpoint

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Persona: You are a senior Galaxy backend developer specializing in FastAPI and the manager pattern.
Arguments:
  • $ARGUMENTS - Optional resource name (e.g., "credentials", "workflows", "histories") If provided, use this as the resource name throughout the workflow

角色定位:你是一名精通FastAPI和管理器模式的资深Galaxy后端开发者。
参数:
  • $ARGUMENTS - 可选的资源名称(例如"credentials"、"workflows"、"histories") 如果提供了该参数,请在整个流程中以此作为资源名称

Creating a New Galaxy API Endpoint

创建新的Galaxy API端点

This guide walks you through creating a new REST API endpoint following Galaxy's architecture patterns.
本指南将引导你按照Galaxy的架构模式创建新的REST API端点。

Step 0: Understand the Request

步骤0:理解需求

If $ARGUMENTS is empty, ask the user:
  1. What resource are they creating an endpoint for? (e.g., "credentials", "user preferences")
  2. What operation(s) are needed? (create, read, update, delete, list, custom action)
Use their answers to guide the rest of the workflow.

如果$ARGUMENTS为空,请询问用户:
  1. 他们要为哪种资源创建端点?(例如"credentials"、"用户偏好设置")
  2. 需要哪些操作?(创建、读取、更新、删除、列表、自定义操作)
根据用户的回答指导后续流程。

Step 1: Find Similar Endpoint as Reference

步骤1:寻找相似端点作为参考

Before starting, find the most recent similar endpoint to use as a pattern:
bash
undefined
开始之前,找到最新的相似端点作为模板:
bash
undefined

Find recently modified API routers

查找最近修改的API路由文件

ls -t lib/galaxy/webapps/galaxy/api/*.py | head -5

Read one of these files to understand current patterns. Good examples:
- `lib/galaxy/webapps/galaxy/api/job_files.py` - Simple CRUD operations
- `lib/galaxy/webapps/galaxy/api/workflows.py` - Complex resource with many operations
- `lib/galaxy/webapps/galaxy/api/histories.py` - RESTful resource with nested routes

**Key patterns to observe:**
- Router setup: `router = APIRouter(tags=["resource_name"])`
- Dependency injection: `DependsOnTrans`, custom dependency functions
- Request/response models: Pydantic schemas from `galaxy.schema`
- Error handling: Raising appropriate HTTP exceptions
- Documentation: Docstrings and OpenAPI metadata

---
ls -t lib/galaxy/webapps/galaxy/api/*.py | head -5

阅读其中一个文件以了解当前的模式。优秀示例:
- `lib/galaxy/webapps/galaxy/api/job_files.py` - 简单的CRUD操作
- `lib/galaxy/webapps/galaxy/api/workflows.py` - 包含多种操作的复杂资源
- `lib/galaxy/webapps/galaxy/api/histories.py` - 带有嵌套路由的RESTful资源

**需要关注的关键模式:**
- 路由设置:`router = APIRouter(tags=["resource_name"])`
- 依赖注入:`DependsOnTrans`、自定义依赖函数
- 请求/响应模型:来自`galaxy.schema`的Pydantic Schema
- 错误处理:抛出合适的HTTP异常
- 文档:文档字符串和OpenAPI元数据

---

Step 2: Define Pydantic Schemas

步骤2:定义Pydantic Schema

Create request/response models in
lib/galaxy/schema/
(or update existing schema file if one exists for this domain).
Location:
lib/galaxy/schema/schema.py
(or domain-specific file like
lib/galaxy/schema/workflows.py
)
Common imports:
python
from typing import Optional, List
from pydantic import Field
from galaxy.schema.fields import EncodedDatabaseIdField
from galaxy.schema.schema import Model
Example schema definitions:
python
class MyResourceCreateRequest(Model):
    """Request model for creating a new resource."""
    name: str = Field(..., description="Resource name")
    description: Optional[str] = Field(None, description="Optional description")

class MyResourceResponse(Model):
    """Response model for resource operations."""
    id: EncodedDatabaseIdField = Field(..., description="Encoded resource ID")
    name: str
    description: Optional[str]
    create_time: datetime
    update_time: datetime

class MyResourceListResponse(Model):
    """Response model for listing resources."""
    items: List[MyResourceResponse]
    total_count: int
Field types commonly used:
  • EncodedDatabaseIdField
    - For Galaxy's encoded IDs
  • DecodedDatabaseIdField
    - For decoded integer IDs (internal use)
  • str
    ,
    int
    ,
    bool
    ,
    float
    - Standard types
  • Optional[T]
    - For nullable fields
  • List[T]
    - For arrays
  • datetime
    - For timestamps
Best practices:
  • Use descriptive field names matching database column names
  • Add
    description
    to all fields for OpenAPI documentation
  • Use
    ...
    for required fields, defaults for optional
  • Keep request models separate from response models
  • Use
    Field(alias="...")
    if API name differs from Python name

lib/galaxy/schema/
中创建请求/响应模型(如果该领域已有Schema文件,则更新现有文件)。
位置:
lib/galaxy/schema/schema.py
(或特定领域的文件,如
lib/galaxy/schema/workflows.py
常用导入:
python
from typing import Optional, List
from pydantic import Field
from galaxy.schema.fields import EncodedDatabaseIdField
from galaxy.schema.schema import Model
Schema定义示例:
python
class MyResourceCreateRequest(Model):
    """创建新资源的请求模型。"""
    name: str = Field(..., description="资源名称")
    description: Optional[str] = Field(None, description="可选描述")

class MyResourceResponse(Model):
    """资源操作的响应模型。"""
    id: EncodedDatabaseIdField = Field(..., description="编码后的资源ID")
    name: str
    description: Optional[str]
    create_time: datetime
    update_time: datetime

class MyResourceListResponse(Model):
    """列出资源的响应模型。"""
    items: List[MyResourceResponse]
    total_count: int
常用字段类型:
  • EncodedDatabaseIdField
    - 用于Galaxy的编码ID
  • DecodedDatabaseIdField
    - 用于解码后的整数ID(内部使用)
  • str
    ,
    int
    ,
    bool
    ,
    float
    - 标准类型
  • Optional[T]
    - 用于可为空的字段
  • List[T]
    - 用于数组
  • datetime
    - 用于时间戳
最佳实践:
  • 使用与数据库列名匹配的描述性字段名称
  • 为所有字段添加
    description
    以生成OpenAPI文档
  • 必填字段使用
    ...
    ,可选字段设置默认值
  • 将请求模型与响应模型分开
  • 如果API名称与Python名称不同,使用
    Field(alias="...")

Step 3: Add Manager Method

步骤3:添加管理器方法

Business logic belongs in manager classes in
lib/galaxy/managers/
.
Location:
  • If manager exists for this domain: Update
    lib/galaxy/managers/<resource>s.py
  • If new domain: Create
    lib/galaxy/managers/<resource>s.py
Manager pattern structure:
python
from typing import Optional
from galaxy import model
from galaxy.managers.context import ProvidesUserContext
from galaxy.model import Session

class MyResourceManager:
    """Manager for MyResource operations."""

    def __init__(self, app):
        self.app = app
        self.sa_session: Session = app.model.context

    def create(
        self,
        trans: ProvidesUserContext,
        name: str,
        description: Optional[str] = None
    ) -> model.MyResource:
        """Create a new resource."""
        resource = model.MyResource(
            user=trans.user,
            name=name,
            description=description
        )
        self.sa_session.add(resource)
        self.sa_session.flush()
        return resource

    def get(self, trans: ProvidesUserContext, resource_id: int) -> model.MyResource:
        """Get resource by ID."""
        resource = self.sa_session.get(model.MyResource, resource_id)
        if not resource:
            raise exceptions.ObjectNotFound("Resource not found")
        if not self.is_accessible(resource, trans.user):
            raise exceptions.ItemAccessibilityException("Access denied")
        return resource

    def is_accessible(self, resource: model.MyResource, user: Optional[model.User]) -> bool:
        """Check if user can access this resource."""
        if not user:
            return False
        return resource.user_id == user.id

    def list_for_user(self, trans: ProvidesUserContext) -> List[model.MyResource]:
        """List all resources for the current user."""
        stmt = select(model.MyResource).where(
            model.MyResource.user_id == trans.user.id
        )
        return self.sa_session.scalars(stmt).all()
Manager best practices:
  • Constructor takes
    app
    (the Galaxy application object)
  • Methods take
    trans
    (transaction/request context) as first parameter
  • Use
    self.sa_session
    for database operations
  • Raise appropriate exceptions from
    galaxy.exceptions
  • Implement access control checks in separate methods
  • Use SQLAlchemy 2.0
    select()
    syntax for queries

业务逻辑应放在
lib/galaxy/managers/
中的管理器类中。
位置:
  • 如果该领域已有管理器:更新
    lib/galaxy/managers/<resource>s.py
  • 如果是新领域:创建
    lib/galaxy/managers/<resource>s.py
管理器模式结构:
python
from typing import Optional
from galaxy import model
from galaxy.managers.context import ProvidesUserContext
from galaxy.model import Session

class MyResourceManager:
    """MyResource操作的管理器。"""

    def __init__(self, app):
        self.app = app
        self.sa_session: Session = app.model.context

    def create(
        self,
        trans: ProvidesUserContext,
        name: str,
        description: Optional[str] = None
    ) -> model.MyResource:
        """创建新资源。"""
        resource = model.MyResource(
            user=trans.user,
            name=name,
            description=description
        )
        self.sa_session.add(resource)
        self.sa_session.flush()
        return resource

    def get(self, trans: ProvidesUserContext, resource_id: int) -> model.MyResource:
        """通过ID获取资源。"""
        resource = self.sa_session.get(model.MyResource, resource_id)
        if not resource:
            raise exceptions.ObjectNotFound("资源未找到")
        if not self.is_accessible(resource, trans.user):
            raise exceptions.ItemAccessibilityException("访问被拒绝")
        return resource

    def is_accessible(self, resource: model.MyResource, user: Optional[model.User]) -> bool:
        """检查用户是否可以访问该资源。"""
        if not user:
            return False
        return resource.user_id == user.id

    def list_for_user(self, trans: ProvidesUserContext) -> List[model.MyResource]:
        """列出当前用户的所有资源。"""
        stmt = select(model.MyResource).where(
            model.MyResource.user_id == trans.user.id
        )
        return self.sa_session.scalars(stmt).all()
管理器最佳实践:
  • 构造函数接收
    app
    (Galaxy应用对象)
  • 方法将
    trans
    (事务/请求上下文)作为第一个参数
  • 使用
    self.sa_session
    进行数据库操作
  • 抛出
    galaxy.exceptions
    中的合适异常
  • 在单独的方法中实现访问控制检查
  • 使用SQLAlchemy 2.0的
    select()
    语法进行查询

Step 4: Create FastAPI Router

步骤4:创建FastAPI路由

Create or update the API router in
lib/galaxy/webapps/galaxy/api/
.
Location:
lib/galaxy/webapps/galaxy/api/<resource>s.py
Router template:
python
"""
API endpoints for MyResource operations.
"""
import logging
from typing import Optional

from fastapi import (
    APIRouter,
    Depends,
    Path,
    Query,
    status,
)

from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.myresources import MyResourceManager
from galaxy.schema.schema import (
    MyResourceCreateRequest,
    MyResourceResponse,
    MyResourceListResponse,
)
from galaxy.webapps.galaxy.api import (
    DependsOnTrans,
    Router,
)
from galaxy.webapps.galaxy.api.depends import get_app

log = logging.getLogger(__name__)

router = Router(tags=["myresources"])
lib/galaxy/webapps/galaxy/api/
中创建或更新API路由。
位置:
lib/galaxy/webapps/galaxy/api/<resource>s.py
路由模板:
python
"""
MyResource操作的API端点。
"""
import logging
from typing import Optional

from fastapi import (
    APIRouter,
    Depends,
    Path,
    Query,
    status,
)

from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.myresources import MyResourceManager
from galaxy.schema.schema import (
    MyResourceCreateRequest,
    MyResourceResponse,
    MyResourceListResponse,
)
from galaxy.webapps.galaxy.api import (
    DependsOnTrans,
    Router,
)
from galaxy.webapps.galaxy.api.depends import get_app

log = logging.getLogger(__name__)

router = Router(tags=["myresources"])

Dependency for manager

管理器依赖

def get_myresource_manager(app=Depends(get_app)) -> MyResourceManager: return MyResourceManager(app)
@router.cbv class FastAPIMyResources: manager: MyResourceManager = Depends(get_myresource_manager)
@router.get(
    "/api/myresources",
    summary="List all resources for current user",
    response_model=MyResourceListResponse,
)
def index(
    self,
    trans: ProvidesUserContext = DependsOnTrans,
) -> MyResourceListResponse:
    """List all resources owned by the current user."""
    items = self.manager.list_for_user(trans)
    return MyResourceListResponse(
        items=[self._serialize(item) for item in items],
        total_count=len(items),
    )

@router.post(
    "/api/myresources",
    summary="Create a new resource",
    status_code=status.HTTP_201_CREATED,
    response_model=MyResourceResponse,
)
def create(
    self,
    trans: ProvidesUserContext = DependsOnTrans,
    request: MyResourceCreateRequest = ...,
) -> MyResourceResponse:
    """Create a new resource."""
    resource = self.manager.create(
        trans,
        name=request.name,
        description=request.description,
    )
    return self._serialize(resource)

@router.get(
    "/api/myresources/{id}",
    summary="Get resource by ID",
    response_model=MyResourceResponse,
)
def show(
    self,
    trans: ProvidesUserContext = DependsOnTrans,
    id: EncodedDatabaseIdField = Path(..., description="Resource ID"),
) -> MyResourceResponse:
    """Get a specific resource by ID."""
    decoded_id = trans.security.decode_id(id)
    resource = self.manager.get(trans, decoded_id)
    return self._serialize(resource)

def _serialize(self, resource) -> MyResourceResponse:
    """Convert model object to response schema."""
    return MyResourceResponse(
        id=trans.security.encode_id(resource.id),
        name=resource.name,
        description=resource.description,
        create_time=resource.create_time,
        update_time=resource.update_time,
    )

**Router best practices:**
- Use `Router` (capital R) from `galaxy.webapps.galaxy.api` (subclass of FastAPI's APIRouter)
- Use `@router.cbv` class-based views for grouping related endpoints
- Use dependency injection for managers: `manager: Manager = Depends(get_manager)`
- Use `DependsOnTrans` for transaction context
- Path parameters use `Path(...)` with descriptions
- Query parameters use `Query(...)` with defaults
- Set appropriate HTTP status codes (`status_code=status.HTTP_201_CREATED` for creates)
- Add `summary` to all endpoints for OpenAPI docs
- Decode IDs in endpoint, not in manager (manager works with integer IDs)

---
def get_myresource_manager(app=Depends(get_app)) -> MyResourceManager: return MyResourceManager(app)
@router.cbv class FastAPIMyResources: manager: MyResourceManager = Depends(get_myresource_manager)
@router.get(
    "/api/myresources",
    summary="列出当前用户的所有资源",
    response_model=MyResourceListResponse,
)
def index(
    self,
    trans: ProvidesUserContext = DependsOnTrans,
) -> MyResourceListResponse:
    """列出当前用户拥有的所有资源。"""
    items = self.manager.list_for_user(trans)
    return MyResourceListResponse(
        items=[self._serialize(item) for item in items],
        total_count=len(items),
    )

@router.post(
    "/api/myresources",
    summary="创建新资源",
    status_code=status.HTTP_201_CREATED,
    response_model=MyResourceResponse,
)
def create(
    self,
    trans: ProvidesUserContext = DependsOnTrans,
    request: MyResourceCreateRequest = ...,
) -> MyResourceResponse:
    """创建新资源。"""
    resource = self.manager.create(
        trans,
        name=request.name,
        description=request.description,
    )
    return self._serialize(resource)

@router.get(
    "/api/myresources/{id}",
    summary="通过ID获取资源",
    response_model=MyResourceResponse,
)
def show(
    self,
    trans: ProvidesUserContext = DependsOnTrans,
    id: EncodedDatabaseIdField = Path(..., description="资源ID"),
) -> MyResourceResponse:
    """通过ID获取特定资源。"""
    decoded_id = trans.security.decode_id(id)
    resource = self.manager.get(trans, decoded_id)
    return self._serialize(resource)

def _serialize(self, resource) -> MyResourceResponse:
    """将模型对象转换为响应Schema。"""
    return MyResourceResponse(
        id=trans.security.encode_id(resource.id),
        name=resource.name,
        description=resource.description,
        create_time=resource.create_time,
        update_time=resource.update_time,
    )

**路由最佳实践:**
- 使用`galaxy.webapps.galaxy.api`中的`Router`(大写R),它是FastAPI APIRouter的子类
- 使用`@router.cbv`基于类的视图来分组相关端点
- 为管理器使用依赖注入:`manager: Manager = Depends(get_manager)`
- 使用`DependsOnTrans`获取事务上下文
- 路径参数使用`Path(...)`并添加描述
- 查询参数使用`Query(...)`并设置默认值
- 设置合适的HTTP状态码(创建操作使用`status_code=status.HTTP_201_CREATED`)
- 为所有端点添加`summary`以生成OpenAPI文档
- 在端点中解码ID,而不是在管理器中(管理器使用整数ID)

---

Step 5: Register Router

步骤5:注册路由

The router must be registered in the main application builder.
Location:
lib/galaxy/webapps/galaxy/buildapp.py
Add import:
python
from galaxy.webapps.galaxy.api import myresources
Register router in
app_factory()
:
python
app.include_router(myresources.router)
Find the section: Look for other
app.include_router()
calls and add yours in alphabetical order.

必须在主应用构建器中注册路由。
位置:
lib/galaxy/webapps/galaxy/buildapp.py
添加导入:
python
from galaxy.webapps.galaxy.api import myresources
app_factory()
中注册路由:
python
app.include_router(myresources.router)
查找位置: 找到其他
app.include_router()
调用,并按字母顺序添加你的路由。

Step 6: Write API Tests

步骤6:编写API测试

Create tests in
lib/galaxy_test/api/
.
Location:
lib/galaxy_test/api/test_<resource>s.py
Test template:
python
"""
API tests for MyResource endpoints.
"""
from galaxy_test.base.populators import DatasetPopulator
from ._framework import ApiTestCase


class TestMyResourcesApi(ApiTestCase):
    """Tests for /api/myresources endpoints."""

    def setUp(self):
        super().setUp()
        self.dataset_populator = DatasetPopulator(self.galaxy_interactor)

    def test_create_myresource(self):
        """Test creating a new resource."""
        payload = {
            "name": "Test Resource",
            "description": "Test description",
        }
        response = self._post("myresources", data=payload, json=True)
        self._assert_status_code_is(response, 201)
        resource = response.json()
        self._assert_has_keys(resource, "id", "name", "description", "create_time")
        assert resource["name"] == "Test Resource"

    def test_list_myresources(self):
        """Test listing resources."""
        # Create some test data
        self._create_myresource("Resource 1")
        self._create_myresource("Resource 2")

        # List resources
        response = self._get("myresources")
        self._assert_status_code_is_ok(response)
        data = response.json()
        assert data["total_count"] >= 2
        assert len(data["items"]) >= 2

    def test_get_myresource(self):
        """Test getting a specific resource."""
        resource_id = self._create_myresource("Test Resource")
        response = self._get(f"myresources/{resource_id}")
        self._assert_status_code_is_ok(response)
        resource = response.json()
        assert resource["id"] == resource_id
        assert resource["name"] == "Test Resource"

    def test_get_nonexistent_myresource(self):
        """Test getting a resource that doesn't exist."""
        response = self._get("myresources/invalid_id")
        self._assert_status_code_is(response, 404)

    def test_create_myresource_as_different_user(self):
        """Test that users can only see their own resources."""
        # Create as first user
        resource_id = self._create_myresource("User 1 Resource")

        # Switch to different user
        with self._different_user():
            # Should not be able to access
            response = self._get(f"myresources/{resource_id}")
            self._assert_status_code_is(response, 403)

    def _create_myresource(self, name: str) -> str:
        """Helper to create a resource and return its ID."""
        payload = {"name": name, "description": f"Description for {name}"}
        response = self._post("myresources", data=payload, json=True)
        self._assert_status_code_is(response, 201)
        return response.json()["id"]
Test patterns:
  • Extend
    ApiTestCase
    from
    lib/galaxy_test/api/_framework.py
  • Use
    self._get()
    ,
    self._post()
    ,
    self._put()
    ,
    self._delete()
    (paths relative to
    /api/
    )
  • Use
    self._assert_status_code_is(response, 200)
    for status checks
  • Use
    self._assert_status_code_is_ok(response)
    for 2xx status
  • Use
    self._assert_has_keys(obj, "key1", "key2")
    to verify response structure
  • Use
    self._different_user()
    context manager to test as different user
  • Create helper methods like
    _create_myresource()
    for test data setup
  • Test both success and error cases (404, 403, 400, etc.)

lib/galaxy_test/api/
中创建测试。
位置:
lib/galaxy_test/api/test_<resource>s.py
测试模板:
python
"""
MyResource端点的API测试。
"""
from galaxy_test.base.populators import DatasetPopulator
from ._framework import ApiTestCase


class TestMyResourcesApi(ApiTestCase):
    """/api/myresources端点的测试。"""

    def setUp(self):
        super().setUp()
        self.dataset_populator = DatasetPopulator(self.galaxy_interactor)

    def test_create_myresource(self):
        """测试创建新资源。"""
        payload = {
            "name": "测试资源",
            "description": "测试描述",
        }
        response = self._post("myresources", data=payload, json=True)
        self._assert_status_code_is(response, 201)
        resource = response.json()
        self._assert_has_keys(resource, "id", "name", "description", "create_time")
        assert resource["name"] == "测试资源"

    def test_list_myresources(self):
        """测试列出资源。"""
        # 创建一些测试数据
        self._create_myresource("资源1")
        self._create_myresource("资源2")

        # 列出资源
        response = self._get("myresources")
        self._assert_status_code_is_ok(response)
        data = response.json()
        assert data["total_count"] >= 2
        assert len(data["items"]) >= 2

    def test_get_myresource(self):
        """测试获取特定资源。"""
        resource_id = self._create_myresource("测试资源")
        response = self._get(f"myresources/{resource_id}")
        self._assert_status_code_is_ok(response)
        resource = response.json()
        assert resource["id"] == resource_id
        assert resource["name"] == "测试资源"

    def test_get_nonexistent_myresource(self):
        """测试获取不存在的资源。"""
        response = self._get("myresources/invalid_id")
        self._assert_status_code_is(response, 404)

    def test_create_myresource_as_different_user(self):
        """测试用户只能查看自己的资源。"""
        # 以第一个用户创建
        resource_id = self._create_myresource("用户1的资源")

        # 切换到其他用户
        with self._different_user():
            # 应该无法访问
            response = self._get(f"myresources/{resource_id}")
            self._assert_status_code_is(response, 403)

    def _create_myresource(self, name: str) -> str:
        """创建资源并返回其ID的辅助方法。"""
        payload = {"name": name, "description": f"{name}的描述"}
        response = self._post("myresources", data=payload, json=True)
        self._assert_status_code_is(response, 201)
        return response.json()["id"]
测试模式:
  • 继承自
    lib/galaxy_test/api/_framework.py
    中的
    ApiTestCase
  • 使用
    self._get()
    self._post()
    self._put()
    self._delete()
    (路径相对于
    /api/
  • 使用
    self._assert_status_code_is(response, 200)
    检查状态码
  • 使用
    self._assert_status_code_is_ok(response)
    检查2xx状态码
  • 使用
    self._assert_has_keys(obj, "key1", "key2")
    验证响应结构
  • 使用
    self._different_user()
    上下文管理器以其他用户身份测试
  • 创建类似
    _create_myresource()
    的辅助方法来设置测试数据
  • 测试成功和错误场景(404、403、400等)

Step 7: Run Tests

步骤7:运行测试

Run your new tests using the Galaxy test runner:
bash
undefined
使用Galaxy测试运行器运行新测试:
bash
undefined

Run all tests for your new API

运行新API的所有测试

./run_tests.sh -api lib/galaxy_test/api/test_myresources.py
./run_tests.sh -api lib/galaxy_test/api/test_myresources.py

Run a specific test

运行特定测试

./run_tests.sh -api lib/galaxy_test/api/test_myresources.py::TestMyResourcesApi::test_create_myresource
./run_tests.sh -api lib/galaxy_test/api/test_myresources.py::TestMyResourcesApi::test_create_myresource

Run with verbose output

运行并显示详细错误

./run_tests.sh -api lib/galaxy_test/api/test_myresources.py --verbose_errors

**IMPORTANT:** Always use `./run_tests.sh`, not `pytest` directly. The wrapper script sets up the correct environment.

---
./run_tests.sh -api lib/galaxy_test/api/test_myresources.py --verbose_errors

**重要提示:** 请始终使用`./run_tests.sh`,不要直接使用`pytest`。该包装脚本会设置正确的环境。

---

Step 8: Verify and Manual Test

步骤8:验证和手动测试

  1. Start Galaxy dev server:
    bash
    ./run.sh
  2. Check OpenAPI docs: Navigate to
    http://localhost:8080/api/docs
    and verify your endpoints appear
  3. Manual test with curl:
    bash
    # Create
    curl -X POST http://localhost:8080/api/myresources \
      -H "Content-Type: application/json" \
      -d '{"name": "Test", "description": "Test resource"}'
    
    # List
    curl http://localhost:8080/api/myresources
    
    # Get specific
    curl http://localhost:8080/api/myresources/{id}
  4. Check auto-generated TypeScript types: The frontend types in
    client/src/api/schema/schema.ts
    will be auto-generated from your Pydantic schemas next time the schema is rebuilt.

  1. 启动Galaxy开发服务器:
    bash
    ./run.sh
  2. 检查OpenAPI文档: 导航到
    http://localhost:8080/api/docs
    ,验证你的端点是否已显示
  3. 使用curl手动测试:
    bash
    # 创建
    curl -X POST http://localhost:8080/api/myresources \
      -H "Content-Type: application/json" \
      -d '{"name": "测试", "description": "测试资源"}'
    
    # 列出
    curl http://localhost:8080/api/myresources
    
    # 获取特定资源
    curl http://localhost:8080/api/myresources/{id}
  4. 检查自动生成的TypeScript类型: 下次重建Schema时,
    client/src/api/schema/schema.ts
    中的前端类型将从你的Pydantic Schema自动生成。

Reference Files to Check

参考文件

When implementing your endpoint, reference these files:
Recent API examples:
bash
ls -t lib/galaxy/webapps/galaxy/api/*.py | head -5
Schema patterns:
  • lib/galaxy/schema/schema.py
    - Main schema definitions
  • lib/galaxy/schema/fields.py
    - Custom field types
Manager patterns:
bash
ls lib/galaxy/managers/*.py
Test examples:
bash
ls lib/galaxy_test/api/test_*.py

实现端点时,可以参考以下文件:
最新API示例:
bash
ls -t lib/galaxy/webapps/galaxy/api/*.py | head -5
Schema模式:
  • lib/galaxy/schema/schema.py
    - 主Schema定义
  • lib/galaxy/schema/fields.py
    - 自定义字段类型
管理器模式:
bash
ls lib/galaxy/managers/*.py
测试示例:
bash
ls lib/galaxy_test/api/test_*.py

Common Gotchas

常见陷阱

  1. ID encoding: Always encode IDs in API responses (
    trans.security.encode_id()
    ) and decode in endpoints
  2. Transaction context: Manager methods should take
    trans
    as first parameter
  3. Database session: Use
    self.sa_session.flush()
    after adding objects, not
    commit()
  4. Access control: Always check if user can access resource before returning it
  5. Error handling: Raise exceptions from
    galaxy.exceptions
    , not generic ones
  6. Router registration: Don't forget to register your router in
    buildapp.py
  7. Test runner: Use
    ./run_tests.sh -api
    , not plain
    pytest

  1. ID编码: 始终在API响应中编码ID(
    trans.security.encode_id()
    ),在端点中解码ID
  2. 事务上下文: 管理器方法应将
    trans
    作为第一个参数
  3. 数据库会话: 添加对象后使用
    self.sa_session.flush()
    ,而不是
    commit()
  4. 访问控制: 返回资源前始终检查用户是否有权限访问
  5. 错误处理: 抛出
    galaxy.exceptions
    中的异常,而不是通用异常
  6. 路由注册: 不要忘记在
    buildapp.py
    中注册路由
  7. 测试运行器: 使用
    ./run_tests.sh -api
    ,而不是直接使用
    pytest

Next Steps

后续步骤

After creating your endpoint:
  1. Test thoroughly with automated tests
  2. Manual test through browser and curl
  3. Check OpenAPI documentation at
    /api/docs
  4. Consider adding frontend integration (Vue components)
  5. Update any relevant documentation
For more details, see:
  • reference.md
    in this skill directory for concrete code examples
  • Galaxy's CLAUDE.md for architecture overview
  • Existing API implementations for patterns
创建端点后:
  1. 使用自动化测试进行全面测试
  2. 通过浏览器和curl进行手动测试
  3. 检查
    /api/docs
    中的OpenAPI文档
  4. 考虑添加前端集成(Vue组件)
  5. 更新所有相关文档
更多详细信息,请参阅:
  • 本技能目录中的
    reference.md
    ,包含具体代码示例
  • Galaxy的CLAUDE.md,架构概述
  • 现有API实现中的模式