From 3a58834f2784ac9c516e9c4bd267c7c1c9e44dbc Mon Sep 17 00:00:00 2001 From: Shuchang Zheng Date: Wed, 30 Jul 2025 15:57:12 -0700 Subject: [PATCH] Add projects table (#3063) --- ...30_2249-f72cf593e1a7_add_projects_table.py | 51 +++++++++++++++++++ skyvern/forge/sdk/db/id.py | 13 +++++ skyvern/forge/sdk/db/models.py | 31 +++++++++++ skyvern/schemas/peojects.py | 16 ++++++ 4 files changed, 111 insertions(+) create mode 100644 alembic/versions/2025_07_30_2249-f72cf593e1a7_add_projects_table.py create mode 100644 skyvern/schemas/peojects.py diff --git a/alembic/versions/2025_07_30_2249-f72cf593e1a7_add_projects_table.py b/alembic/versions/2025_07_30_2249-f72cf593e1a7_add_projects_table.py new file mode 100644 index 00000000..b624387e --- /dev/null +++ b/alembic/versions/2025_07_30_2249-f72cf593e1a7_add_projects_table.py @@ -0,0 +1,51 @@ +"""add projects table + +Revision ID: f72cf593e1a7 +Revises: 1d0a10ae2a13 +Create Date: 2025-07-30 22:49:26.594708+00:00 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "f72cf593e1a7" +down_revision: Union[str, None] = "1d0a10ae2a13" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "projects", + sa.Column("project_revision_id", sa.String(), nullable=False), + sa.Column("project_id", sa.String(), nullable=False), + sa.Column("organization_id", sa.String(), nullable=False), + sa.Column("artifact_id", sa.String(), nullable=True), + sa.Column("workflow_permanent_id", sa.String(), nullable=True), + sa.Column("run_id", sa.String(), nullable=True), + sa.Column("version", sa.Integer(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.Column("modified_at", sa.DateTime(), nullable=False), + sa.Column("deleted_at", sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint("project_revision_id"), + sa.UniqueConstraint("organization_id", "project_id", "version", name="uc_org_project_version"), + ) + op.create_index("project_org_created_at_index", "projects", ["organization_id", "created_at"], unique=False) + op.create_index("project_org_run_id_index", "projects", ["organization_id", "run_id"], unique=False) + op.create_index("project_org_wpid_index", "projects", ["organization_id", "workflow_permanent_id"], unique=False) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index("project_org_wpid_index", table_name="projects") + op.drop_index("project_org_run_id_index", table_name="projects") + op.drop_index("project_org_created_at_index", table_name="projects") + op.drop_table("projects") + # ### end Alembic commands ### diff --git a/skyvern/forge/sdk/db/id.py b/skyvern/forge/sdk/db/id.py index e30b8d87..1b0944d3 100644 --- a/skyvern/forge/sdk/db/id.py +++ b/skyvern/forge/sdk/db/id.py @@ -45,6 +45,8 @@ ORGANIZATION_AUTH_TOKEN_PREFIX = "oat" ORG_PREFIX = "o" OUTPUT_PARAMETER_PREFIX = "op" PERSISTENT_BROWSER_SESSION_ID = "pbs" +PROJECT_REVISION_PREFIX = "pv" +PROJECT_PREFIX = "p" STEP_PREFIX = "stp" TASK_GENERATION_PREFIX = "tg" TASK_PREFIX = "tsk" @@ -203,6 +205,17 @@ def generate_organization_bitwarden_collection_id() -> str: return f"{ORGANIZATION_BITWARDEN_COLLECTION_PREFIX}_{int_id}" +def generate_project_id() -> str: + int_id = generate_id() + return f"{PROJECT_PREFIX}_{int_id}" + + +def generate_project_revision_id() -> str: + int_id = generate_id() + return f"{PROJECT_REVISION_PREFIX}_{int_id}" + + +############# Helper functions below ############## def generate_id() -> int: """ generate a 64-bit int ID diff --git a/skyvern/forge/sdk/db/models.py b/skyvern/forge/sdk/db/models.py index 5f8577c6..bb254e76 100644 --- a/skyvern/forge/sdk/db/models.py +++ b/skyvern/forge/sdk/db/models.py @@ -36,6 +36,8 @@ from skyvern.forge.sdk.db.id import ( generate_organization_bitwarden_collection_id, generate_output_parameter_id, generate_persistent_browser_session_id, + generate_project_id, + generate_project_revision_id, generate_step_id, generate_task_generation_id, generate_task_id, @@ -767,3 +769,32 @@ class DebugSessionModel(Base): user_id = Column(String, nullable=True) # comes from identity vendor (Clerk at time of writing) created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False) modified_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow, nullable=False) + + +class ProjectModel(Base): + __tablename__ = "projects" + __table_args__ = ( + Index("project_org_created_at_index", "organization_id", "created_at"), + Index("project_org_wpid_index", "organization_id", "workflow_permanent_id"), + Index("project_org_run_id_index", "organization_id", "run_id"), + UniqueConstraint("organization_id", "project_id", "version", name="uc_org_project_version"), + ) + + project_revision_id = Column(String, primary_key=True, default=generate_project_revision_id) + project_id = Column(String, default=generate_project_id, nullable=False) # User-facing, consistent across versions + organization_id = Column(String, nullable=False) + # the artifact id for the code + artifact_id = Column(String, nullable=True) + # the wpid that this project is associated with + workflow_permanent_id = Column(String, nullable=True) + # The workflow run or task run id that this project is generated + run_id = Column(String, nullable=True) + version = Column(Integer, default=1, nullable=False) + created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False) + modified_at = Column( + DateTime, + default=datetime.datetime.utcnow, + onupdate=datetime.datetime.utcnow, + nullable=False, + ) + deleted_at = Column(DateTime, nullable=True) diff --git a/skyvern/schemas/peojects.py b/skyvern/schemas/peojects.py new file mode 100644 index 00000000..57844dcb --- /dev/null +++ b/skyvern/schemas/peojects.py @@ -0,0 +1,16 @@ +from datetime import datetime + +from pydantic import BaseModel, ConfigDict + + +class Project(BaseModel): + model_config = ConfigDict(from_attributes=True) + + project_revision_id: str + project_id: str + organization_id: str + artifact_id: str | None = None + version: int | None = None + created_at: datetime + modified_at: datetime + deleted_at: datetime | None = None