280 lines
9.2 KiB
Python
280 lines
9.2 KiB
Python
import base64
|
|
import hashlib
|
|
|
|
import structlog
|
|
from fastapi import Depends, HTTPException, Path, Query
|
|
|
|
from skyvern.exceptions import WorkflowNotFound
|
|
from skyvern.forge import app
|
|
from skyvern.forge.sdk.routes.routers import base_router, legacy_base_router
|
|
from skyvern.forge.sdk.schemas.organizations import Organization
|
|
from skyvern.forge.sdk.services import org_auth_service
|
|
from skyvern.schemas.projects import CreateProjectRequest, CreateProjectResponse, DeployProjectRequest, Project
|
|
from skyvern.services import project_service
|
|
|
|
LOG = structlog.get_logger()
|
|
|
|
|
|
@base_router.post(
|
|
"/projects",
|
|
response_model=CreateProjectResponse,
|
|
summary="Create project",
|
|
description="Create a new project with optional files and metadata",
|
|
tags=["Projects"],
|
|
openapi_extra={
|
|
"x-fern-sdk-method-name": "create_project",
|
|
},
|
|
)
|
|
@base_router.post(
|
|
"/projects/",
|
|
response_model=CreateProjectResponse,
|
|
include_in_schema=False,
|
|
)
|
|
async def create_project(
|
|
data: CreateProjectRequest,
|
|
current_org: Organization = Depends(org_auth_service.get_current_org),
|
|
) -> CreateProjectResponse:
|
|
"""Create a new project with optional files and metadata."""
|
|
organization_id = current_org.organization_id
|
|
LOG.info(
|
|
"Creating project",
|
|
organization_id=organization_id,
|
|
file_count=len(data.files) if data.files else 0,
|
|
)
|
|
# validate workflow_id and run_id
|
|
if data.workflow_id:
|
|
if not await app.DATABASE.get_workflow_by_permanent_id(
|
|
workflow_permanent_id=data.workflow_id, organization_id=organization_id
|
|
):
|
|
raise WorkflowNotFound(workflow_permanent_id=data.workflow_id)
|
|
if data.run_id:
|
|
if not await app.DATABASE.get_run(run_id=data.run_id, organization_id=organization_id):
|
|
raise HTTPException(status_code=404, detail=f"Run_id {data.run_id} not found")
|
|
try:
|
|
# Create the project in the database
|
|
project = await app.DATABASE.create_project(
|
|
organization_id=organization_id,
|
|
workflow_permanent_id=data.workflow_id,
|
|
run_id=data.run_id,
|
|
)
|
|
# Process files if provided
|
|
file_tree = {}
|
|
file_count = 0
|
|
if data.files:
|
|
file_tree = await project_service.build_file_tree(
|
|
data.files,
|
|
organization_id=organization_id,
|
|
project_id=project.project_id,
|
|
project_version=project.version,
|
|
project_revision_id=project.project_revision_id,
|
|
)
|
|
file_count = len(data.files)
|
|
return CreateProjectResponse(
|
|
project_id=project.project_id,
|
|
version=project.version,
|
|
workflow_id=project.workflow_id,
|
|
run_id=project.run_id,
|
|
file_count=file_count,
|
|
created_at=project.created_at,
|
|
file_tree=file_tree,
|
|
)
|
|
except Exception as e:
|
|
LOG.error("Failed to create project", error=str(e), exc_info=True)
|
|
raise HTTPException(status_code=500, detail="Failed to create project")
|
|
|
|
|
|
@legacy_base_router.get("/projects/{project_id}")
|
|
@legacy_base_router.get("/projects/{project_id}/", include_in_schema=False)
|
|
@base_router.get(
|
|
"/projects/{project_id}",
|
|
response_model=Project,
|
|
summary="Get project by ID",
|
|
description="Retrieves a specific project by its ID",
|
|
tags=["Projects"],
|
|
openapi_extra={
|
|
"x-fern-sdk-method-name": "get_project",
|
|
},
|
|
)
|
|
@base_router.get(
|
|
"/projects/{project_id}/",
|
|
response_model=Project,
|
|
include_in_schema=False,
|
|
)
|
|
async def get_project(
|
|
project_id: str = Path(
|
|
...,
|
|
description="The unique identifier of the project",
|
|
examples=["proj_abc123"],
|
|
),
|
|
current_org: Organization = Depends(org_auth_service.get_current_org),
|
|
) -> Project:
|
|
"""Get a project by its ID."""
|
|
LOG.info(
|
|
"Getting project",
|
|
organization_id=current_org.organization_id,
|
|
project_id=project_id,
|
|
)
|
|
|
|
project = await app.DATABASE.get_project(
|
|
project_id=project_id,
|
|
organization_id=current_org.organization_id,
|
|
)
|
|
|
|
if not project:
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
|
|
return project
|
|
|
|
|
|
@legacy_base_router.get("/projects")
|
|
@legacy_base_router.get("/projects/", include_in_schema=False)
|
|
@base_router.get(
|
|
"/projects",
|
|
response_model=list[Project],
|
|
summary="Get all projects",
|
|
description="Retrieves a paginated list of projects for the current organization",
|
|
tags=["Projects"],
|
|
openapi_extra={
|
|
"x-fern-sdk-method-name": "get_projects",
|
|
},
|
|
)
|
|
@base_router.get(
|
|
"/projects/",
|
|
response_model=list[Project],
|
|
include_in_schema=False,
|
|
)
|
|
async def get_projects(
|
|
current_org: Organization = Depends(org_auth_service.get_current_org),
|
|
page: int = Query(
|
|
1,
|
|
ge=1,
|
|
description="Page number for pagination",
|
|
examples=[1],
|
|
),
|
|
page_size: int = Query(
|
|
10,
|
|
ge=1,
|
|
description="Number of items per page",
|
|
examples=[10],
|
|
),
|
|
) -> list[Project]:
|
|
"""Get all projects for the current organization."""
|
|
LOG.info(
|
|
"Getting projects",
|
|
organization_id=current_org.organization_id,
|
|
page=page,
|
|
page_size=page_size,
|
|
)
|
|
|
|
projects = await app.DATABASE.get_projects(
|
|
organization_id=current_org.organization_id,
|
|
page=page,
|
|
page_size=page_size,
|
|
)
|
|
|
|
return projects
|
|
|
|
|
|
@base_router.post(
|
|
"/projects/{project_id}/deploy",
|
|
response_model=CreateProjectResponse,
|
|
summary="Deploy project",
|
|
description="Deploy a project with updated files, creating a new version",
|
|
tags=["Projects"],
|
|
openapi_extra={
|
|
"x-fern-sdk-method-name": "deploy_project",
|
|
},
|
|
)
|
|
@base_router.post(
|
|
"/projects/{project_id}/deploy/",
|
|
response_model=CreateProjectResponse,
|
|
include_in_schema=False,
|
|
)
|
|
async def deploy_project(
|
|
data: DeployProjectRequest,
|
|
project_id: str = Path(
|
|
...,
|
|
description="The unique identifier of the project",
|
|
examples=["proj_abc123"],
|
|
),
|
|
current_org: Organization = Depends(org_auth_service.get_current_org),
|
|
) -> CreateProjectResponse:
|
|
"""Deploy a project with updated files, creating a new version."""
|
|
LOG.info(
|
|
"Deploying project",
|
|
organization_id=current_org.organization_id,
|
|
project_id=project_id,
|
|
file_count=len(data.files) if data.files else 0,
|
|
)
|
|
|
|
try:
|
|
# Get the latest version of the project
|
|
latest_project = await app.DATABASE.get_project(
|
|
project_id=project_id,
|
|
organization_id=current_org.organization_id,
|
|
)
|
|
|
|
if not latest_project:
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
|
|
# Create a new version of the project
|
|
new_version = latest_project.version + 1
|
|
new_project_revision = await app.DATABASE.create_project(
|
|
organization_id=current_org.organization_id,
|
|
workflow_permanent_id=latest_project.workflow_id,
|
|
run_id=latest_project.run_id,
|
|
project_id=project_id, # Use the same project_id for versioning
|
|
version=new_version,
|
|
)
|
|
|
|
# Process files if provided
|
|
file_tree = {}
|
|
file_count = 0
|
|
if data.files:
|
|
file_tree = await project_service.build_file_tree(
|
|
data.files,
|
|
organization_id=current_org.organization_id,
|
|
project_id=new_project_revision.project_id,
|
|
project_version=new_project_revision.version,
|
|
project_revision_id=new_project_revision.project_revision_id,
|
|
)
|
|
file_count = len(data.files)
|
|
|
|
# Create project file records
|
|
for file in data.files:
|
|
content_bytes = base64.b64decode(file.content)
|
|
content_hash = hashlib.sha256(content_bytes).hexdigest()
|
|
file_size = len(content_bytes)
|
|
|
|
# Extract file name from path
|
|
file_name = file.path.split("/")[-1]
|
|
|
|
await app.DATABASE.create_project_file(
|
|
project_revision_id=new_project_revision.project_revision_id,
|
|
project_id=new_project_revision.project_id,
|
|
organization_id=new_project_revision.organization_id,
|
|
file_path=file.path,
|
|
file_name=file_name,
|
|
file_type="file",
|
|
content_hash=f"sha256:{content_hash}",
|
|
file_size=file_size,
|
|
mime_type=file.mime_type,
|
|
encoding=file.encoding,
|
|
)
|
|
|
|
return CreateProjectResponse(
|
|
project_id=new_project_revision.project_id,
|
|
version=new_project_revision.version,
|
|
workflow_id=new_project_revision.workflow_id,
|
|
run_id=new_project_revision.run_id,
|
|
file_count=file_count,
|
|
created_at=new_project_revision.created_at,
|
|
file_tree=file_tree,
|
|
)
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
LOG.error("Failed to deploy project", error=str(e), exc_info=True)
|
|
raise HTTPException(status_code=500, detail="Failed to deploy project")
|