Skip to content

Commit a5d3488

Browse files
committed
SDK: use SQLite in-memory (off by default)
1 parent 0618542 commit a5d3488

File tree

8 files changed

+102
-57
lines changed

8 files changed

+102
-57
lines changed

skyvern/cli/init_command.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
from rich.progress import Progress, SpinnerColumn, TextColumn
99
from rich.prompt import Confirm, Prompt
1010

11-
from skyvern.forge.forge_app_initializer import start_forge_app
11+
from skyvern.forge.forge_app_initializer import setup_local_organization, start_forge_app
1212
from skyvern.utils import migrate_db
1313
from skyvern.utils.env_paths import resolve_backend_env_path
1414

1515
from .browser import setup_browser_config
1616
from .console import console
1717
from .database import setup_postgresql
1818
from .llm_setup import setup_llm_providers, update_or_add_env_var
19-
from .mcp import setup_local_organization, setup_mcp
19+
from .mcp import setup_mcp
2020

2121

2222
def init_env(

skyvern/cli/mcp.py

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,49 +6,11 @@
66
from rich.panel import Panel
77
from rich.prompt import Confirm, Prompt
88

9-
from skyvern.forge import app
10-
from skyvern.forge.sdk.core import security
11-
from skyvern.forge.sdk.db.enums import OrganizationAuthTokenType
12-
from skyvern.forge.sdk.schemas.organizations import Organization
13-
from skyvern.forge.sdk.services.local_org_auth_token_service import SKYVERN_LOCAL_DOMAIN, SKYVERN_LOCAL_ORG
14-
from skyvern.forge.sdk.services.org_auth_token_service import API_KEY_LIFETIME
159
from skyvern.utils import detect_os, get_windows_appdata_roaming
1610
from skyvern.utils.env_paths import resolve_backend_env_path
1711

1812
from .console import console
1913

20-
21-
async def get_or_create_local_organization() -> Organization:
22-
organization = await app.DATABASE.get_organization_by_domain(SKYVERN_LOCAL_DOMAIN)
23-
if not organization:
24-
organization = await app.DATABASE.create_organization(
25-
organization_name=SKYVERN_LOCAL_ORG,
26-
domain=SKYVERN_LOCAL_DOMAIN,
27-
max_steps_per_run=10,
28-
max_retries_per_step=3,
29-
)
30-
api_key = security.create_access_token(
31-
organization.organization_id,
32-
expires_delta=API_KEY_LIFETIME,
33-
)
34-
# generate OrganizationAutoToken
35-
await app.DATABASE.create_org_auth_token(
36-
organization_id=organization.organization_id,
37-
token=api_key,
38-
token_type=OrganizationAuthTokenType.api,
39-
)
40-
return organization
41-
42-
43-
async def setup_local_organization() -> str:
44-
organization = await get_or_create_local_organization()
45-
org_auth_token = await app.DATABASE.get_valid_org_auth_token(
46-
organization_id=organization.organization_id,
47-
token_type=OrganizationAuthTokenType.api.value,
48-
)
49-
return org_auth_token.token if org_auth_token else ""
50-
51-
5214
# ----- Helper paths and checks -----
5315

5416

skyvern/forge/api_app.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from skyvern.config import settings
1717
from skyvern.exceptions import SkyvernHTTPException
1818
from skyvern.forge import app as forge_app
19+
from skyvern.forge.forge_app import ForgeApp
1920
from skyvern.forge.forge_app_initializer import start_forge_app
2021
from skyvern.forge.request_logging import log_raw_request_middleware
2122
from skyvern.forge.sdk.core import skyvern_context
@@ -79,7 +80,10 @@ def create_api_app() -> FastAPI:
7980
"""
8081

8182
forge_app_instance = start_forge_app()
83+
return create_fast_api_app(forge_app_instance)
8284

85+
86+
def create_fast_api_app(forge_app_instance: ForgeApp) -> FastAPI:
8387
fastapi_app = FastAPI(lifespan=lifespan)
8488

8589
# Add CORS middleware

skyvern/forge/forge_app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,15 @@ class ForgeApp:
8484
agent: ForgeAgent
8585

8686

87-
def create_forge_app() -> ForgeApp:
87+
def create_forge_app(db: AgentDB | None = None) -> ForgeApp:
8888
"""Create and initialize a ForgeApp instance with all services"""
8989
settings: Settings = SettingsManager.get_settings()
9090

9191
app = ForgeApp()
9292

9393
app.SETTINGS_MANAGER = settings
9494

95-
app.DATABASE = AgentDB(settings.DATABASE_STRING, debug_enabled=settings.DEBUG_MODE)
95+
app.DATABASE = db if db else AgentDB(settings.DATABASE_STRING, debug_enabled=settings.DEBUG_MODE)
9696
if settings.SKYVERN_STORAGE_TYPE == "s3":
9797
StorageFactory.set_storage(S3Storage())
9898
app.STORAGE = StorageFactory.get_storage()

skyvern/forge/forge_app_initializer.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import structlog
22

33
from skyvern.config import settings
4-
from skyvern.forge import set_force_app_instance
4+
from skyvern.forge import app, set_force_app_instance
55
from skyvern.forge.forge_app import ForgeApp, create_forge_app
6+
from skyvern.forge.sdk.core import security
7+
from skyvern.forge.sdk.db.client import AgentDB
8+
from skyvern.forge.sdk.db.enums import OrganizationAuthTokenType
9+
from skyvern.forge.sdk.schemas.organizations import Organization
10+
from skyvern.forge.sdk.services.local_org_auth_token_service import SKYVERN_LOCAL_DOMAIN, SKYVERN_LOCAL_ORG
11+
from skyvern.forge.sdk.services.org_auth_token_service import API_KEY_LIFETIME
612

713
LOG = structlog.get_logger()
814

915

10-
def start_forge_app() -> ForgeApp:
11-
force_app_instance = create_forge_app()
16+
def start_forge_app(db: AgentDB | None = None) -> ForgeApp:
17+
force_app_instance = create_forge_app(db)
1218
set_force_app_instance(force_app_instance)
1319

1420
if settings.ADDITIONAL_MODULES:
@@ -26,3 +32,34 @@ def start_forge_app() -> ForgeApp:
2632
)
2733

2834
return force_app_instance
35+
36+
37+
async def get_or_create_local_organization() -> Organization:
38+
organization = await app.DATABASE.get_organization_by_domain(SKYVERN_LOCAL_DOMAIN)
39+
if not organization:
40+
organization = await app.DATABASE.create_organization(
41+
organization_name=SKYVERN_LOCAL_ORG,
42+
domain=SKYVERN_LOCAL_DOMAIN,
43+
max_steps_per_run=10,
44+
max_retries_per_step=3,
45+
)
46+
api_key = security.create_access_token(
47+
organization.organization_id,
48+
expires_delta=API_KEY_LIFETIME,
49+
)
50+
# generate OrganizationAutoToken
51+
await app.DATABASE.create_org_auth_token(
52+
organization_id=organization.organization_id,
53+
token=api_key,
54+
token_type=OrganizationAuthTokenType.api,
55+
)
56+
return organization
57+
58+
59+
async def setup_local_organization() -> str:
60+
organization = await get_or_create_local_organization()
61+
org_auth_token = await app.DATABASE.get_valid_org_auth_token(
62+
organization_id=organization.organization_id,
63+
token_type=OrganizationAuthTokenType.api.value,
64+
)
65+
return org_auth_token.token if org_auth_token else ""

skyvern/forge/sdk/db/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import Any, List, Literal, Sequence, overload
44

55
import structlog
6-
from sqlalchemy import and_, asc, case, delete, distinct, exists, func, or_, pool, select, tuple_, update
6+
from sqlalchemy import and_, asc, case, delete, exists, func, or_, pool, select, update
77
from sqlalchemy.exc import SQLAlchemyError
88
from sqlalchemy.ext.asyncio import AsyncEngine, async_sessionmaker, create_async_engine
99

skyvern/library/embedded_server_factory.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@
22
from typing import Any
33

44
import httpx
5+
import structlog
6+
from dotenv import load_dotenv
57
from httpx import ASGITransport
68

79
from skyvern.config import settings
8-
from skyvern.forge.api_app import create_api_app
10+
from skyvern.forge.api_app import create_api_app, create_fast_api_app
11+
from skyvern.forge.forge_app_initializer import setup_local_organization, start_forge_app
912
from skyvern.forge.sdk.api.llm.config_registry import LLMConfigRegistry
1013
from skyvern.forge.sdk.api.llm.models import LLMConfig, LLMRouterConfig
14+
from skyvern.forge.sdk.db.client import AgentDB
15+
16+
LOG = structlog.get_logger()
1117

1218

1319
def create_embedded_server(
1420
llm_config: LLMRouterConfig | LLMConfig | None = None,
1521
settings_overrides: dict[str, Any] | None = None,
22+
use_in_memory_db: bool = False,
1623
) -> httpx.AsyncClient:
1724
class EmbeddedServerTransport(httpx.AsyncBaseTransport):
1825
def __init__(self) -> None:
@@ -21,6 +28,8 @@ def __init__(self) -> None:
2128

2229
async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
2330
if self._transport is None:
31+
load_dotenv(".env")
32+
2433
settings.BROWSER_LOGS_ENABLED = False
2534

2635
if llm_config:
@@ -38,10 +47,28 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
3847
else:
3948
raise ValueError(f"Invalid setting: {key}")
4049

41-
self._api_key = os.getenv("SKYVERN_API_KEY")
42-
if not self._api_key:
43-
raise ValueError("SKYVERN_API_KEY is not set. Provide api_key or set SKYVERN_API_KEY in .env file.")
44-
api_app = create_api_app()
50+
if use_in_memory_db:
51+
from sqlalchemy.ext.asyncio import create_async_engine # noqa: PLC0415
52+
53+
from skyvern.forge.sdk.db.models import Base # noqa: PLC0415
54+
55+
settings.DATABASE_STRING = "sqlite+aiosqlite:///:memory:"
56+
engine = create_async_engine(settings.DATABASE_STRING)
57+
async with engine.begin() as conn:
58+
await conn.run_sync(Base.metadata.create_all)
59+
60+
db = AgentDB(settings.DATABASE_STRING, debug_enabled=settings.DEBUG_MODE, db_engine=engine)
61+
forge_app_instance = start_forge_app(db)
62+
api_app = create_fast_api_app(forge_app_instance)
63+
self._api_key = await setup_local_organization()
64+
LOG.info("Embedded server initialized with in-memory database")
65+
else:
66+
self._api_key = os.getenv("SKYVERN_API_KEY")
67+
if not self._api_key:
68+
raise ValueError(
69+
"SKYVERN_API_KEY is not set. Provide api_key or set SKYVERN_API_KEY in .env file."
70+
)
71+
api_app = create_api_app()
4572

4673
self._transport = ASGITransport(app=api_app)
4774

skyvern/library/skyvern.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ def local(
128128
*,
129129
llm_config: LLMRouterConfig | LLMConfig | None = None,
130130
settings: dict[str, Any] | None = None,
131+
use_in_memory_db: bool | None = None,
131132
) -> "Skyvern":
132133
"""Local/embedded mode: Run Skyvern locally in-process.
133134
@@ -186,19 +187,32 @@ def local(
186187
settings: Optional dictionary of Skyvern settings to override.
187188
These override the corresponding settings from your .env file.
188189
Example: {"MAX_STEPS_PER_RUN": 100, "BROWSER_TYPE": "chromium-headful"}
190+
use_in_memory_db: Optional flag to use an in-memory database.
189191
190192
Returns:
191193
Skyvern: A Skyvern instance running in local/embedded mode.
192194
"""
193195
from skyvern.library.embedded_server_factory import create_embedded_server # noqa: PLC0415
194196

195-
if not os.path.exists(".env"):
196-
raise ValueError("Please run `skyvern quickstart` to set up your local Skyvern environment")
197-
198197
load_dotenv(".env")
199-
api_key = os.getenv("SKYVERN_API_KEY")
200-
if not api_key:
201-
raise ValueError("SKYVERN_API_KEY is not set. Provide api_key or set SKYVERN_API_KEY in .env file.")
198+
199+
if use_in_memory_db is not None:
200+
do_use_in_memory_db = use_in_memory_db
201+
else:
202+
do_use_in_memory_db = os.environ.get("SKYVERN_USE_IN_MEMORY_DB", "").lower() == "true"
203+
204+
# Validate prerequisites based on configuration mode
205+
if not do_use_in_memory_db and not os.path.exists(".env"):
206+
raise ValueError("Please run `skyvern quickstart` to set up your local Skyvern environment.")
207+
208+
if not llm_config and not os.path.exists(".env"):
209+
raise ValueError(
210+
"LLM configuration is required. Either provide llm_config parameter or "
211+
"run `skyvern quickstart` to configure LLM settings in .env file."
212+
)
213+
214+
if not do_use_in_memory_db and not os.getenv("SKYVERN_API_KEY"):
215+
raise ValueError("Please run `skyvern quickstart` to set up your local Skyvern environment.")
202216

203217
obj = cls.__new__(cls)
204218

@@ -208,6 +222,7 @@ def local(
208222
httpx_client=create_embedded_server(
209223
llm_config=llm_config,
210224
settings_overrides=settings,
225+
use_in_memory_db=do_use_in_memory_db,
211226
),
212227
)
213228

0 commit comments

Comments
 (0)