mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
fix(sockets): added throttling, refactor entire socket server, added tests (#534)
* refactor(kb): use chonkie locally (#475) * feat(parsers): text and markdown parsers (#473) * feat: text and markdown parsers * fix: don't readfile on buffer, convert buffer to string instead * fix(knowledge-wh): fixed authentication error on webhook trigger fix(knowledge-wh): fixed authentication error on webhook trigger * feat(tools): add huggingface tools/blcok (#472) * add hugging face tool * docs: add Hugging Face tool documentation * fix: format and lint Hugging Face integration files * docs: add manual intro section to Hugging Face documentation * feat: replace Record<string, any> with proper HuggingFaceRequestBody interface * accidental local files added * restore some docs * make layout full for model field * change huggingface logo * add manual content * fix lint --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> * fix(knowledge-ux): fixed ux for knowledge base (#478) fix(knowledge-ux): fixed ux for knowledge base (#478) * fix(billing): bump better-auth version & fix existing subscription issue when adding seats (#484) * bump better-auth version & fix existing subscription issue Bwhen adding seats * ack PR comments * fix(env): added NEXT_PUBLIC_APP_URL to .env.example (#485) * feat(subworkflows): workflows as a block within workflows (#480) * feat(subworkflows) workflows in workflows * revert sync changes * working output vars * fix greptile comments * add cycle detection * add tests * working tests * works * fix formatting * fix input var handling * add images --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net> * fix(kb): fixed kb race condition resulting in no chunks found (#487) * fix: added all blocks activeExecutionPath (#486) * refactor(chunker): replace chonkie with custom TextChunker (#479) * refactor(chunker): replace chonkie with custom TextChunker implementation and update document processing logic * chore: cleanup unimplemented types * fix: KB tests updated * fix(tab-sync): sync between tabs on change (#489) * fix(tab-sync): sync between tabs on change * refactor: optimize JSON.stringify operations that are redundant * fix(file-upload): upload presigned url to kb for file upload instead of the whole file, circumvents 4.5MB serverless func limit (#491) * feat(folders): folders to manage workflows (#490) * feat(subworkflows) workflows in workflows * revert sync changes * working output vars * fix greptile comments * add cycle detection * add tests * working tests * works * fix formatting * fix input var handling * fix(tab-sync): sync between tabs on change * feat(folders): folders to organize workflows * address comments * change schema types * fix lint error * fix typing error * fix race cond * delete unused files * improved UI * updated naming conventions * revert unrelated changes to db schema * fixed collapsed sidebar subfolders * add logs filters for folders --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net> Co-authored-by: Waleed Latif <walif6@gmail.com> * revert tab sync * improvement(folders): added multi-select for moving folders (#493) * added multi-select for folders * allow drag into root * remove extraneous comments * instantly create worfklow on plus * styling improvements, fixed flicker * small improvement to dragover container * ack PR comments * fix(deployed-chat): made the chat mobile friendly (#494) * improvement(ui/ux): chat deploy (#496) * improvement(ui/ux): chat deploy experience * improvement(ui/ux): chat fontweight * feat(gmail): added option to access raw gmail from gmail polling service (#495) * added option to grab raw gmail from gmail polling service * safe json parse for function block execution to prevent vars in raw email from being resolved as sim studio vars * added tests * remove extraneous comments * fix(ui): fix the UI for folder deletion, huggingface icon, workflow block icon, standardized alert dialog (#498) * fixed folder delete UI * fixed UI for workflow block, huggingface, & added alert dialog for deleting folders * consistently style all alert dialogs * fix(reset-data): remove reset all data button from settings modal along with logic (#499) * fix(airtable): fixed airtable oauth token refresh, added tests (#502) * fixed airtable token refresh, added tests * added helpers for refreshOAuthToken function * feat(registration): disable registration + handle env booleans (#501) * feat: disable registration + handle env booleans * chore: removing pre-process because we need to use util * chore: format * feat(providers): added azure openai (#503) * added azure openai * fix request params being passed through agent block for azure * remove o1 from azure-openai models list * fix: add vscode settings to gitignore * feat(file-upload): generalized storage to support azure blob, enhanced error logging in kb, added xlsx parser (#506) * added blob storage option for azure, refactored storage client to be provider agnostic, tested kb & file upload and s3 is undisrupted, still have to test blob * updated CORS policy for blob, added azure blob-specific headers * remove extraneous comments * add file size limit and timeout * added some extra error handling in kb add documents * grouped envvars * ack PR comments * added sheetjs and xlsx parser * fix(folders): modified folder deletion to delete subfolders & workflows in it instead of moving to root (#508) * modified folder deletion to delete subfolders & workflows in it instead of moving to root * added additional testing utils * ack PR comments * feat: api response block and implementation * improvement(local-storage): remove use of local storage except for oauth and last active workspace id (#497) * remove local storage usage * remove migration for last active workspace id * Update apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/components/jira-issue-selector.tsx Add fallback for required scopes Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * add url builder util * fi * fix lint * lint * modify pre commit hook * fix oauth * get last active workspace working again * new workspace logic works * fetch locks * works now * remove empty useEffect * fix loading issue * skip empty workflow syncs * use isWorkspace in transition flag * add logging * add data initialized flag * fix lint * fix: build error by create a server-side utils * remove migration snapshots * reverse search for workspace based on workflow id * fix lint * improvement: loading check and animation * remove unused utils * remove console logs --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan> * feat(multi-select): simplified chat to always return readable stream, can select multiple outputs and get response streamed back in chat panel & deployed chat (#507) * improvement: all workflow executions return ReadableStream & use sse to support multiple streamed outputs in chats * fixed build * remove extraneous comments * general improvemetns * ack PR comments * fixed built * improvement(workflow-state): split workflow state into separate tables (#511) * new tables to track workflow state * fix lint * refactor into separate tables * fix typing * fix lint * add tests * fix lint * add correct foreign key constraint * add self ref * remove unused checks * fix types * fix type --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net> * feat(models): added new openai models, updated model pricing, added new groq model (#513) * fix(autocomplete): fixed extra closing tag on tag dropdown autocomplete (#514) * chore: enable input format again * fix: process the input made on api calls with proper extraction * feat: add json-object for ai generation for response block and others * chore: add documentation for response block * chore: rollback temp fix and uncomment original input handler * chore: add missing mock for response handler * chore: add missing mock * chore: greptile recommendations * added cost tracking for router & evaluator blocks, consolidated model information into a single file, hosted keys for evaluator & router, parallelized unit tests (#516) * fix(deployState): deploy not persisting bug (#518) * fix(undeploy-bug): fix deployment persistence failing bug * fix lint --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> * fix decimal entry issues * remove unused files * fix(db): decimal position entry issues (#520) * fix decimal entry issues * remove unused files --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net> * fix lint * fix test * improvement(kb): added configurability for chunks, query across multiple knowledge bases (#512) * refactor: consolidate create modal file * fix: identify dead processes * fix: mark failed in DB after processing timeout * improvement: added overlap chunks and fixed modal UI * feat: multiselect logic * fix: biome changes for css ordering warn instead of error * improvement: create chunk ui * fix: removed unused schema columns * fix: removed references to deleted columns * improvement: sped up vector search time * feat: multi-kb search * add bulk endpoint to disable/delete multiple chunks * add bulk endpoint to disable/delete multiple chunks * fix: removed unused schema columns * fix: removed references to deleted columns * made endpoints for knowledge more RESTful, added tests * added batch operations for delete/enable/disable docs, alr have this for chunks * added migrations * added migrations --------- Co-authored-by: Waleed Latif <walif6@gmail.com> * fix(models): remove temp from models that don't support it * feat(sdk): added ts and python SDKs + docs (#524) * added ts & python sdk, renamed cli from simstudio to cli * added docs * ack PR comments * improvements * fixed issue where it goes to random workspace when you click reload fixed lint issue * feat: better response builder + doc update * fix(auth): added preview URLs to list of trusted origins (#525) * trusted origins * lint error * removed localhost * ran lint --------- Co-authored-by: Waleed Latif <walif6@gmail.com> * fix(sdk): remove dev script from SDK * PR: changes for migration * add changes on top of db migration changes * fix: allow removing single input field * improvement(permissions): workspace permissions improvements, added provider and reduced API calls by 85% (#530) * improved permissions UI & access patterns, show outstanding invites * added logger * added provider for workspace permissions, 85% reduction in API calls to get user permissions and improved performance for invitations * ack PR comments * cleanup * fix disabled tooltips * improvement(tests): parallelized tests and build fixes (#531) * added provider for workspace permissions, 85% reduction in API calls to get user permissions and improved performance for invitations * parallelized more tests, fixed test warnings * removed waitlist verification route, use more utils in tests * fixed build * ack PR comments * fix * fix(kb): reduced params in kb block, added advanced mode to starter block, updated docs * feat(realtime): sockets + normalized tables + deprecate sync (#523) * feat: implement real-time collaborative workflow editing with Socket.IO - Add Socket.IO server with room-based architecture for workflow collaboration - Implement socket context for client-side real-time communication - Add collaborative workflow hook for synchronized state management - Update CSP to allow socket connections to localhost:3002 - Add fallback authentication for testing collaborative features - Enable real-time broadcasting of workflow operations between tabs - Support multi-user editing of blocks, edges, and workflow state Key components: - socket-server/: Complete Socket.IO server with authentication and room management - contexts/socket-context.tsx: Client-side socket connection and state management - hooks/use-collaborative-workflow.ts: Hook for collaborative workflow operations - Workflow store integration for real-time state synchronization Status: Basic collaborative features working, authentication bypass enabled for testing * feat: complete collaborative subblock editing implementation ✅ All collaborative features now working perfectly: - Real-time block movement and positioning - Real-time subblock value editing (text fields, inputs) - Real-time edge operations and parent updates - Multi-user workflow rooms with proper broadcasting - Socket.IO server with room-based architecture - Permission bypass system for testing 🔧 Technical improvements: - Modified useSubBlockValue hook to use collaborative event system - All subblock setValue calls now dispatch 'update-subblock-value' events - Collaborative workflow hook handles all real-time operations - Socket server processes and persists all operations to database - Clean separation between local and collaborative state management 🧪 Tested and verified: - Multiple browser tabs with different fallback users - Block dragging and positioning updates in real-time - Subblock text editing reflects immediately across tabs - Workflow room management and user presence - Database persistence of all collaborative operations Status: Full collaborative workflow editing working with fallback authentication * feat: implement proper authentication for collaborative Socket.IO server ✅ **Authentication System Complete**: - Removed all fallback authentication code and bypasses - Socket server now requires valid Better Auth session cookies - Proper session validation using auth.api.getSession() - Authentication errors properly handled and logged - User info extracted from session: userId, userName, email, organizationId 🔧 **Technical Implementation**: - Updated CSP to allow WebSocket connections (ws://localhost:3002) - Socket authentication middleware validates session tokens - Proper error handling for missing/invalid sessions - Permission system enforces workflow access controls - Clean separation between authenticated and unauthenticated states 🧪 **Testing Status**: - Socket server properly rejects unauthenticated connections - Authentication errors logged with clear messages - CSP updated to allow both HTTP and WebSocket protocols - Ready for testing with authenticated users Status: Production-ready collaborative authentication system * feat: complete authentication integration for collaborative Socket.IO system 🎉 **PRODUCTION-READY COLLABORATIVE SYSTEM** ✅ **Authentication Integration Complete**: - Fixed Socket.IO client to send credentials (withCredentials: true) - Updated server CORS to accept credentials with specific origin - Removed all fallback authentication bypasses - Proper Better Auth session validation working 🔧 **Technical Fixes**: - Socket client: Enable withCredentials for cookie transmission - Socket server: Accept credentials with origin 'http://localhost:3000' - Better Auth cookie utility integration for session parsing - Comprehensive authentication middleware with proper error handling 🧪 **Verified Working Features**: - ✅ Real user authentication (Vikhyath Mondreti authenticated) - ✅ Multi-user workflow rooms (2+ users in same workflow) - ✅ Permission system enforcing workflow access controls - ✅ Real-time subblock editing across browser tabs - ✅ Block movement and positioning updates - ✅ Automatic room cleanup and management - ✅ Database persistence of all collaborative operations 🚀 **Status**: Complete enterprise-grade collaborative workflow editing system - No more fallback users - production authentication - Multi-tab collaboration working perfectly - Secure access control with Better Auth integration - Real-time updates for all workflow operations * remove sync system and move to server side * fix lint * delete unused file * added socketio dep * fix subblock persistence bug * working deletion of workflows * fix lint * added railway * add debug logging for railway deployment * improve typing * fix lint * working subflow persistence * fix lint * working cascade deletion * fix lint * working subflow inside subflow * works * fix lint * prevent subflow in subflow * fix lint * add additional logs, add localhost as allowedOrigin * add additional logs, add localhost as allowedOrigin * fix type error * remove unused code * fix lint * fix tests * fix lint * fix build error * workign folder updates * fix typing issue * fix lint * fix typing issues * lib/ * fix tests * added old presence component back, updated to use one-time-token better auth plugin for socket server auth, tested * fix errors * fix bugs * add migration scripts to run * fix lint * fix deploy tests * fix lint * fix minor issues * fix lint * fix migration script * allow comma separateds id file input to migration script * fix lint * fixed * fix lint * fix fallback case * fix type errors * address greptile comments * fix lint * fix script to generate new block ids * fix lint --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan> Co-authored-by: Waleed Latif <walif6@gmail.com> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> * fix(sockets): updated CSP * remove unecessary logs * fix lint * added throttling, refactor entire socket server, added tests * improvements * remove self monitoring func, add block name event * working isWide, isAdvanced toggles with sockets * fix lint * fix duplicate key issue for user avatar * fix lint * fix user presence * working parallel badges / loop badges updates * working connection output persistence * fix lint * fix build errors * fix lint * logs removed * fix cascade var name update bug * works * fix lint * fix parallel blocks * fix placeholder * fix test * fixed tests --------- Co-authored-by: Aditya Tripathi <aditya@climactic.co> Co-authored-by: Adam Gough <77861281+aadamgough@users.noreply.github.com> Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net> Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu> Co-authored-by: Emir Karabeg <78010029+emir-karabeg@users.noreply.github.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan> Co-authored-by: Ajit Kadaveru <ajit.kadaveru@berkeley.edu>
This commit is contained in:
84
packages/python-sdk/.gitignore
vendored
Normal file
84
packages/python-sdk/.gitignore
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
test_env/
|
||||
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
375
packages/python-sdk/README.md
Normal file
375
packages/python-sdk/README.md
Normal file
@@ -0,0 +1,375 @@
|
||||
# Sim Studio Python SDK
|
||||
|
||||
The official Python SDK for [Sim Studio](https://simstudio.ai), allowing you to execute workflows programmatically from your Python applications.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install simstudio-sdk
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
# Initialize the client
|
||||
client = SimStudioClient(
|
||||
api_key=os.getenv("SIMSTUDIO_API_KEY", "your-api-key-here"),
|
||||
base_url="https://simstudio.ai" # optional, defaults to https://simstudio.ai
|
||||
)
|
||||
|
||||
# Execute a workflow
|
||||
try:
|
||||
result = client.execute_workflow("workflow-id")
|
||||
print("Workflow executed successfully:", result)
|
||||
except Exception as error:
|
||||
print("Workflow execution failed:", error)
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### SimStudioClient
|
||||
|
||||
#### Constructor
|
||||
|
||||
```python
|
||||
SimStudioClient(api_key: str, base_url: str = "https://simstudio.ai")
|
||||
```
|
||||
|
||||
- `api_key` (str): Your Sim Studio API key
|
||||
- `base_url` (str, optional): Base URL for the Sim Studio API (defaults to `https://simstudio.ai`)
|
||||
|
||||
#### Methods
|
||||
|
||||
##### execute_workflow(workflow_id, input_data=None, timeout=30.0)
|
||||
|
||||
Execute a workflow with optional input data.
|
||||
|
||||
```python
|
||||
result = client.execute_workflow(
|
||||
"workflow-id",
|
||||
input_data={"message": "Hello, world!"},
|
||||
timeout=30.0 # 30 seconds
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `workflow_id` (str): The ID of the workflow to execute
|
||||
- `input_data` (dict, optional): Input data to pass to the workflow
|
||||
- `timeout` (float): Timeout in seconds (default: 30.0)
|
||||
|
||||
**Returns:** `WorkflowExecutionResult`
|
||||
|
||||
##### get_workflow_status(workflow_id)
|
||||
|
||||
Get the status of a workflow (deployment status, etc.).
|
||||
|
||||
```python
|
||||
status = client.get_workflow_status("workflow-id")
|
||||
print("Is deployed:", status.is_deployed)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `workflow_id` (str): The ID of the workflow
|
||||
|
||||
**Returns:** `WorkflowStatus`
|
||||
|
||||
##### validate_workflow(workflow_id)
|
||||
|
||||
Validate that a workflow is ready for execution.
|
||||
|
||||
```python
|
||||
is_ready = client.validate_workflow("workflow-id")
|
||||
if is_ready:
|
||||
# Workflow is deployed and ready
|
||||
pass
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `workflow_id` (str): The ID of the workflow
|
||||
|
||||
**Returns:** `bool`
|
||||
|
||||
##### execute_workflow_sync(workflow_id, input_data=None, timeout=30.0)
|
||||
|
||||
Execute a workflow and poll for completion (useful for long-running workflows).
|
||||
|
||||
```python
|
||||
result = client.execute_workflow_sync(
|
||||
"workflow-id",
|
||||
input_data={"data": "some input"},
|
||||
timeout=60.0
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `workflow_id` (str): The ID of the workflow to execute
|
||||
- `input_data` (dict, optional): Input data to pass to the workflow
|
||||
- `timeout` (float): Timeout for the initial request in seconds
|
||||
|
||||
**Returns:** `WorkflowExecutionResult`
|
||||
|
||||
##### set_api_key(api_key)
|
||||
|
||||
Update the API key.
|
||||
|
||||
```python
|
||||
client.set_api_key("new-api-key")
|
||||
```
|
||||
|
||||
##### set_base_url(base_url)
|
||||
|
||||
Update the base URL.
|
||||
|
||||
```python
|
||||
client.set_base_url("https://my-custom-domain.com")
|
||||
```
|
||||
|
||||
##### close()
|
||||
|
||||
Close the underlying HTTP session.
|
||||
|
||||
```python
|
||||
client.close()
|
||||
```
|
||||
|
||||
## Data Classes
|
||||
|
||||
### WorkflowExecutionResult
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class WorkflowExecutionResult:
|
||||
success: bool
|
||||
output: Optional[Any] = None
|
||||
error: Optional[str] = None
|
||||
logs: Optional[list] = None
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
trace_spans: Optional[list] = None
|
||||
total_duration: Optional[float] = None
|
||||
```
|
||||
|
||||
### WorkflowStatus
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class WorkflowStatus:
|
||||
is_deployed: bool
|
||||
deployed_at: Optional[str] = None
|
||||
is_published: bool = False
|
||||
needs_redeployment: bool = False
|
||||
```
|
||||
|
||||
### SimStudioError
|
||||
|
||||
```python
|
||||
class SimStudioError(Exception):
|
||||
def __init__(self, message: str, code: Optional[str] = None, status: Optional[int] = None):
|
||||
super().__init__(message)
|
||||
self.code = code
|
||||
self.status = status
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Workflow Execution
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
|
||||
def run_workflow():
|
||||
try:
|
||||
# Check if workflow is ready
|
||||
is_ready = client.validate_workflow("my-workflow-id")
|
||||
if not is_ready:
|
||||
raise Exception("Workflow is not deployed or ready")
|
||||
|
||||
# Execute the workflow
|
||||
result = client.execute_workflow(
|
||||
"my-workflow-id",
|
||||
input_data={
|
||||
"message": "Process this data",
|
||||
"user_id": "12345"
|
||||
}
|
||||
)
|
||||
|
||||
if result.success:
|
||||
print("Output:", result.output)
|
||||
print("Duration:", result.metadata.get("duration") if result.metadata else None)
|
||||
else:
|
||||
print("Workflow failed:", result.error)
|
||||
|
||||
except Exception as error:
|
||||
print("Error:", error)
|
||||
|
||||
run_workflow()
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient, SimStudioError
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
|
||||
def execute_with_error_handling():
|
||||
try:
|
||||
result = client.execute_workflow("workflow-id")
|
||||
return result
|
||||
except SimStudioError as error:
|
||||
if error.code == "UNAUTHORIZED":
|
||||
print("Invalid API key")
|
||||
elif error.code == "TIMEOUT":
|
||||
print("Workflow execution timed out")
|
||||
elif error.code == "USAGE_LIMIT_EXCEEDED":
|
||||
print("Usage limit exceeded")
|
||||
elif error.code == "INVALID_JSON":
|
||||
print("Invalid JSON in request body")
|
||||
else:
|
||||
print(f"Workflow error: {error}")
|
||||
raise
|
||||
except Exception as error:
|
||||
print(f"Unexpected error: {error}")
|
||||
raise
|
||||
```
|
||||
|
||||
### Context Manager Usage
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
# Using context manager to automatically close the session
|
||||
with SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) as client:
|
||||
result = client.execute_workflow("workflow-id")
|
||||
print("Result:", result)
|
||||
# Session is automatically closed here
|
||||
```
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
# Using environment variables
|
||||
client = SimStudioClient(
|
||||
api_key=os.getenv("SIMSTUDIO_API_KEY"),
|
||||
base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://simstudio.ai")
|
||||
)
|
||||
```
|
||||
|
||||
### Batch Workflow Execution
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
|
||||
def execute_workflows_batch(workflow_data_pairs):
|
||||
"""Execute multiple workflows with different input data."""
|
||||
results = []
|
||||
|
||||
for workflow_id, input_data in workflow_data_pairs:
|
||||
try:
|
||||
# Validate workflow before execution
|
||||
if not client.validate_workflow(workflow_id):
|
||||
print(f"Skipping {workflow_id}: not deployed")
|
||||
continue
|
||||
|
||||
result = client.execute_workflow(workflow_id, input_data)
|
||||
results.append({
|
||||
"workflow_id": workflow_id,
|
||||
"success": result.success,
|
||||
"output": result.output,
|
||||
"error": result.error
|
||||
})
|
||||
|
||||
except Exception as error:
|
||||
results.append({
|
||||
"workflow_id": workflow_id,
|
||||
"success": False,
|
||||
"error": str(error)
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
# Example usage
|
||||
workflows = [
|
||||
("workflow-1", {"type": "analysis", "data": "sample1"}),
|
||||
("workflow-2", {"type": "processing", "data": "sample2"}),
|
||||
]
|
||||
|
||||
results = execute_workflows_batch(workflows)
|
||||
for result in results:
|
||||
print(f"Workflow {result['workflow_id']}: {'Success' if result['success'] else 'Failed'}")
|
||||
```
|
||||
|
||||
## Getting Your API Key
|
||||
|
||||
1. Log in to your [Sim Studio](https://simstudio.ai) account
|
||||
2. Navigate to your workflow
|
||||
3. Click on "Deploy" to deploy your workflow
|
||||
4. Select or create an API key during the deployment process
|
||||
5. Copy the API key to use in your application
|
||||
|
||||
## Development
|
||||
|
||||
### Running Tests
|
||||
|
||||
To run the tests locally:
|
||||
|
||||
1. Clone the repository and navigate to the Python SDK directory:
|
||||
```bash
|
||||
cd packages/python-sdk
|
||||
```
|
||||
|
||||
2. Create and activate a virtual environment:
|
||||
```bash
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
```
|
||||
|
||||
3. Install the package in development mode with test dependencies:
|
||||
```bash
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
4. Run the tests:
|
||||
```bash
|
||||
pytest tests/ -v
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
|
||||
Run code quality checks:
|
||||
|
||||
```bash
|
||||
# Code formatting
|
||||
black simstudio/
|
||||
|
||||
# Linting
|
||||
flake8 simstudio/ --max-line-length=100
|
||||
|
||||
# Type checking
|
||||
mypy simstudio/
|
||||
|
||||
# Import sorting
|
||||
isort simstudio/
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.8+
|
||||
- requests >= 2.25.0
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
230
packages/python-sdk/examples/basic_usage.py
Normal file
230
packages/python-sdk/examples/basic_usage.py
Normal file
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Basic usage examples for the Sim Studio Python SDK
|
||||
"""
|
||||
|
||||
import os
|
||||
from simstudio import SimStudioClient, SimStudioError
|
||||
|
||||
|
||||
def basic_example():
|
||||
"""Example 1: Basic workflow execution"""
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
|
||||
try:
|
||||
# Execute a workflow without input
|
||||
result = client.execute_workflow("your-workflow-id")
|
||||
|
||||
if result.success:
|
||||
print("✅ Workflow executed successfully!")
|
||||
print(f"Output: {result.output}")
|
||||
if result.metadata:
|
||||
print(f"Duration: {result.metadata.get('duration')} ms")
|
||||
else:
|
||||
print(f"❌ Workflow failed: {result.error}")
|
||||
|
||||
except SimStudioError as error:
|
||||
print(f"SDK Error: {error} (Code: {error.code})")
|
||||
except Exception as error:
|
||||
print(f"Unexpected error: {error}")
|
||||
|
||||
|
||||
def with_input_example():
|
||||
"""Example 2: Workflow execution with input data"""
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
|
||||
try:
|
||||
result = client.execute_workflow(
|
||||
"your-workflow-id",
|
||||
input_data={
|
||||
"message": "Hello from Python SDK!",
|
||||
"user_id": "12345",
|
||||
"data": {
|
||||
"type": "analysis",
|
||||
"parameters": {
|
||||
"include_metadata": True,
|
||||
"format": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
timeout=60.0 # 60 seconds
|
||||
)
|
||||
|
||||
if result.success:
|
||||
print("✅ Workflow executed successfully!")
|
||||
print(f"Output: {result.output}")
|
||||
if result.metadata:
|
||||
print(f"Duration: {result.metadata.get('duration')} ms")
|
||||
else:
|
||||
print(f"❌ Workflow failed: {result.error}")
|
||||
|
||||
except SimStudioError as error:
|
||||
print(f"SDK Error: {error} (Code: {error.code})")
|
||||
except Exception as error:
|
||||
print(f"Unexpected error: {error}")
|
||||
|
||||
|
||||
def status_example():
|
||||
"""Example 3: Workflow validation and status checking"""
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
|
||||
try:
|
||||
# Check if workflow is ready
|
||||
is_ready = client.validate_workflow("your-workflow-id")
|
||||
print(f"Workflow ready: {is_ready}")
|
||||
|
||||
# Get detailed status
|
||||
status = client.get_workflow_status("your-workflow-id")
|
||||
print(f"Status: {{\n"
|
||||
f" deployed: {status.is_deployed},\n"
|
||||
f" published: {status.is_published},\n"
|
||||
f" needs_redeployment: {status.needs_redeployment},\n"
|
||||
f" deployed_at: {status.deployed_at}\n"
|
||||
f"}}")
|
||||
|
||||
if status.is_deployed:
|
||||
# Execute the workflow
|
||||
result = client.execute_workflow("your-workflow-id")
|
||||
print(f"Result: {result}")
|
||||
|
||||
except Exception as error:
|
||||
print(f"Error: {error}")
|
||||
|
||||
|
||||
def context_manager_example():
|
||||
"""Example 4: Using context manager"""
|
||||
with SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) as client:
|
||||
try:
|
||||
result = client.execute_workflow("your-workflow-id")
|
||||
print(f"Result: {result}")
|
||||
except Exception as error:
|
||||
print(f"Error: {error}")
|
||||
# Session is automatically closed here
|
||||
|
||||
|
||||
def batch_execution_example():
|
||||
"""Example 5: Batch workflow execution"""
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
|
||||
workflows = [
|
||||
("workflow-1", {"type": "analysis", "data": "sample1"}),
|
||||
("workflow-2", {"type": "processing", "data": "sample2"}),
|
||||
("workflow-3", {"type": "validation", "data": "sample3"}),
|
||||
]
|
||||
|
||||
results = []
|
||||
|
||||
for workflow_id, input_data in workflows:
|
||||
try:
|
||||
# Validate workflow before execution
|
||||
if not client.validate_workflow(workflow_id):
|
||||
print(f"⚠️ Skipping {workflow_id}: not deployed")
|
||||
continue
|
||||
|
||||
result = client.execute_workflow(workflow_id, input_data)
|
||||
results.append({
|
||||
"workflow_id": workflow_id,
|
||||
"success": result.success,
|
||||
"output": result.output,
|
||||
"error": result.error
|
||||
})
|
||||
|
||||
status = "✅ Success" if result.success else "❌ Failed"
|
||||
print(f"{status}: {workflow_id}")
|
||||
|
||||
except SimStudioError as error:
|
||||
results.append({
|
||||
"workflow_id": workflow_id,
|
||||
"success": False,
|
||||
"error": str(error)
|
||||
})
|
||||
print(f"❌ SDK Error in {workflow_id}: {error}")
|
||||
except Exception as error:
|
||||
results.append({
|
||||
"workflow_id": workflow_id,
|
||||
"success": False,
|
||||
"error": str(error)
|
||||
})
|
||||
print(f"❌ Unexpected error in {workflow_id}: {error}")
|
||||
|
||||
# Summary
|
||||
successful = sum(1 for r in results if r["success"])
|
||||
total = len(results)
|
||||
print(f"\n📊 Summary: {successful}/{total} workflows completed successfully")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def error_handling_example():
|
||||
"""Example 6: Comprehensive error handling"""
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
|
||||
try:
|
||||
result = client.execute_workflow("your-workflow-id")
|
||||
|
||||
if result.success:
|
||||
print("✅ Workflow executed successfully!")
|
||||
print(f"Output: {result.output}")
|
||||
return result
|
||||
else:
|
||||
print(f"❌ Workflow failed: {result.error}")
|
||||
return result
|
||||
except SimStudioError as error:
|
||||
if error.code == "UNAUTHORIZED":
|
||||
print("❌ Invalid API key")
|
||||
elif error.code == "TIMEOUT":
|
||||
print("⏱️ Workflow execution timed out")
|
||||
elif error.code == "USAGE_LIMIT_EXCEEDED":
|
||||
print("💳 Usage limit exceeded")
|
||||
elif error.code == "INVALID_JSON":
|
||||
print("📝 Invalid JSON in request body")
|
||||
elif error.status == 404:
|
||||
print("🔍 Workflow not found")
|
||||
elif error.status == 403:
|
||||
print("🚫 Workflow is not deployed")
|
||||
else:
|
||||
print(f"⚠️ Workflow error: {error}")
|
||||
raise
|
||||
except Exception as error:
|
||||
print(f"💥 Unexpected error: {error}")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🚀 Running Sim Studio Python SDK Examples\n")
|
||||
|
||||
# Check if API key is set
|
||||
if not os.getenv("SIMSTUDIO_API_KEY"):
|
||||
print("❌ Please set SIMSTUDIO_API_KEY environment variable")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
print("1️⃣ Basic Example:")
|
||||
basic_example()
|
||||
print("\n✅ Basic example completed\n")
|
||||
|
||||
print("2️⃣ Input Example:")
|
||||
with_input_example()
|
||||
print("\n✅ Input example completed\n")
|
||||
|
||||
print("3️⃣ Status Example:")
|
||||
status_example()
|
||||
print("\n✅ Status example completed\n")
|
||||
|
||||
print("4️⃣ Context Manager Example:")
|
||||
context_manager_example()
|
||||
print("\n✅ Context manager example completed\n")
|
||||
|
||||
print("5️⃣ Batch Execution Example:")
|
||||
batch_execution_example()
|
||||
print("\n✅ Batch execution example completed\n")
|
||||
|
||||
print("6️⃣ Error Handling Example:")
|
||||
error_handling_example()
|
||||
print("\n✅ Error handling example completed\n")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n💥 Example failed: {e}")
|
||||
exit(1)
|
||||
|
||||
print("🎉 All examples completed successfully!")
|
||||
84
packages/python-sdk/pyproject.toml
Normal file
84
packages/python-sdk/pyproject.toml
Normal file
@@ -0,0 +1,84 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "simstudio-sdk"
|
||||
version = "0.1.0"
|
||||
authors = [
|
||||
{name = "Sim Studio", email = "support@simstudio.ai"},
|
||||
]
|
||||
description = "Sim Studio SDK - Execute workflows programmatically"
|
||||
readme = "README.md"
|
||||
license = {text = "Apache-2.0"}
|
||||
requires-python = ">=3.8"
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
|
||||
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
||||
]
|
||||
keywords = ["simstudio", "ai", "workflow", "sdk", "api", "automation"]
|
||||
dependencies = [
|
||||
"requests>=2.25.0",
|
||||
"typing-extensions>=4.0.0; python_version<'3.10'",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=6.0.0",
|
||||
"pytest-asyncio>=0.18.0",
|
||||
"black>=22.0.0",
|
||||
"flake8>=4.0.0",
|
||||
"mypy>=0.910",
|
||||
"isort>=5.0.0",
|
||||
"types-requests>=2.25.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://simstudio.ai"
|
||||
Documentation = "https://docs.simstudio.ai"
|
||||
Repository = "https://github.com/simstudioai/sim"
|
||||
"Bug Reports" = "https://github.com/simstudioai/sim/issues"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["simstudio*"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
target-version = ['py38', 'py39', 'py310', 'py311', 'py312']
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 100
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.8"
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
disallow_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
check_untyped_defs = true
|
||||
disallow_untyped_decorators = true
|
||||
no_implicit_optional = true
|
||||
warn_redundant_casts = true
|
||||
warn_unused_ignores = true
|
||||
warn_no_return = true
|
||||
warn_unreachable = true
|
||||
strict_equality = true
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py"]
|
||||
python_classes = ["Test*"]
|
||||
python_functions = ["test_*"]
|
||||
51
packages/python-sdk/setup.py
Normal file
51
packages/python-sdk/setup.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
with open("README.md", "r", encoding="utf-8") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
setup(
|
||||
name="simstudio-sdk",
|
||||
version="0.1.0",
|
||||
author="Sim Studio",
|
||||
author_email="support@simstudio.ai",
|
||||
description="Sim Studio SDK - Execute workflows programmatically",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/simstudioai/sim",
|
||||
packages=find_packages(),
|
||||
classifiers=[
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
],
|
||||
python_requires=">=3.8",
|
||||
install_requires=[
|
||||
"requests>=2.25.0",
|
||||
"typing-extensions>=4.0.0; python_version<'3.10'",
|
||||
],
|
||||
extras_require={
|
||||
"dev": [
|
||||
"pytest>=6.0.0",
|
||||
"pytest-asyncio>=0.18.0",
|
||||
"black>=22.0.0",
|
||||
"flake8>=4.0.0",
|
||||
"mypy>=0.910",
|
||||
],
|
||||
"test": [
|
||||
"pytest>=6.0.0",
|
||||
],
|
||||
},
|
||||
keywords=["simstudio", "ai", "workflow", "sdk", "api", "automation"],
|
||||
project_urls={
|
||||
"Bug Reports": "https://github.com/simstudioai/sim/issues",
|
||||
"Source": "https://github.com/simstudioai/sim",
|
||||
"Documentation": "https://docs.simstudio.ai",
|
||||
},
|
||||
)
|
||||
239
packages/python-sdk/simstudio/__init__.py
Normal file
239
packages/python-sdk/simstudio/__init__.py
Normal file
@@ -0,0 +1,239 @@
|
||||
"""
|
||||
Sim Studio SDK for Python
|
||||
|
||||
Official Python SDK for Sim Studio, allowing you to execute workflows programmatically.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from dataclasses import dataclass
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__all__ = ["SimStudioClient", "SimStudioError", "WorkflowExecutionResult", "WorkflowStatus"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkflowExecutionResult:
|
||||
"""Result of a workflow execution."""
|
||||
success: bool
|
||||
output: Optional[Any] = None
|
||||
error: Optional[str] = None
|
||||
logs: Optional[list] = None
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
trace_spans: Optional[list] = None
|
||||
total_duration: Optional[float] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkflowStatus:
|
||||
"""Status of a workflow."""
|
||||
is_deployed: bool
|
||||
deployed_at: Optional[str] = None
|
||||
is_published: bool = False
|
||||
needs_redeployment: bool = False
|
||||
|
||||
|
||||
class SimStudioError(Exception):
|
||||
"""Exception raised for Sim Studio API errors."""
|
||||
|
||||
def __init__(self, message: str, code: Optional[str] = None, status: Optional[int] = None):
|
||||
super().__init__(message)
|
||||
self.code = code
|
||||
self.status = status
|
||||
|
||||
|
||||
class SimStudioClient:
|
||||
"""
|
||||
Sim Studio API client for executing workflows programmatically.
|
||||
|
||||
Args:
|
||||
api_key: Your Sim Studio API key
|
||||
base_url: Base URL for the Sim Studio API (defaults to https://simstudio.ai)
|
||||
"""
|
||||
|
||||
def __init__(self, api_key: str, base_url: str = "https://simstudio.ai"):
|
||||
self.api_key = api_key
|
||||
self.base_url = base_url.rstrip('/')
|
||||
self._session = requests.Session()
|
||||
self._session.headers.update({
|
||||
'X-API-Key': self.api_key,
|
||||
'Content-Type': 'application/json',
|
||||
})
|
||||
|
||||
def execute_workflow(
|
||||
self,
|
||||
workflow_id: str,
|
||||
input_data: Optional[Dict[str, Any]] = None,
|
||||
timeout: float = 30.0
|
||||
) -> WorkflowExecutionResult:
|
||||
"""
|
||||
Execute a workflow with optional input data.
|
||||
|
||||
Args:
|
||||
workflow_id: The ID of the workflow to execute
|
||||
input_data: Input data to pass to the workflow
|
||||
timeout: Timeout in seconds (default: 30.0)
|
||||
|
||||
Returns:
|
||||
WorkflowExecutionResult object containing the execution result
|
||||
|
||||
Raises:
|
||||
SimStudioError: If the workflow execution fails
|
||||
"""
|
||||
url = f"{self.base_url}/api/workflows/{workflow_id}/execute"
|
||||
|
||||
try:
|
||||
response = self._session.post(
|
||||
url,
|
||||
json=input_data or {},
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
if not response.ok:
|
||||
try:
|
||||
error_data = response.json()
|
||||
error_message = error_data.get('error', f'HTTP {response.status_code}: {response.reason}')
|
||||
error_code = error_data.get('code')
|
||||
except (ValueError, KeyError):
|
||||
error_message = f'HTTP {response.status_code}: {response.reason}'
|
||||
error_code = None
|
||||
|
||||
raise SimStudioError(error_message, error_code, response.status_code)
|
||||
|
||||
result_data = response.json()
|
||||
|
||||
return WorkflowExecutionResult(
|
||||
success=result_data['success'],
|
||||
output=result_data.get('output'),
|
||||
error=result_data.get('error'),
|
||||
logs=result_data.get('logs'),
|
||||
metadata=result_data.get('metadata'),
|
||||
trace_spans=result_data.get('traceSpans'),
|
||||
total_duration=result_data.get('totalDuration')
|
||||
)
|
||||
|
||||
except requests.Timeout:
|
||||
raise SimStudioError(f'Workflow execution timed out after {timeout} seconds', 'TIMEOUT')
|
||||
except requests.RequestException as e:
|
||||
raise SimStudioError(f'Failed to execute workflow: {str(e)}', 'EXECUTION_ERROR')
|
||||
|
||||
def get_workflow_status(self, workflow_id: str) -> WorkflowStatus:
|
||||
"""
|
||||
Get the status of a workflow (deployment status, etc.).
|
||||
|
||||
Args:
|
||||
workflow_id: The ID of the workflow
|
||||
|
||||
Returns:
|
||||
WorkflowStatus object containing the workflow status
|
||||
|
||||
Raises:
|
||||
SimStudioError: If getting the status fails
|
||||
"""
|
||||
url = f"{self.base_url}/api/workflows/{workflow_id}/status"
|
||||
|
||||
try:
|
||||
response = self._session.get(url)
|
||||
|
||||
if not response.ok:
|
||||
try:
|
||||
error_data = response.json()
|
||||
error_message = error_data.get('error', f'HTTP {response.status_code}: {response.reason}')
|
||||
error_code = error_data.get('code')
|
||||
except (ValueError, KeyError):
|
||||
error_message = f'HTTP {response.status_code}: {response.reason}'
|
||||
error_code = None
|
||||
|
||||
raise SimStudioError(error_message, error_code, response.status_code)
|
||||
|
||||
status_data = response.json()
|
||||
|
||||
return WorkflowStatus(
|
||||
is_deployed=status_data.get('isDeployed', False),
|
||||
deployed_at=status_data.get('deployedAt'),
|
||||
is_published=status_data.get('isPublished', False),
|
||||
needs_redeployment=status_data.get('needsRedeployment', False)
|
||||
)
|
||||
|
||||
except requests.RequestException as e:
|
||||
raise SimStudioError(f'Failed to get workflow status: {str(e)}', 'STATUS_ERROR')
|
||||
|
||||
def validate_workflow(self, workflow_id: str) -> bool:
|
||||
"""
|
||||
Validate that a workflow is ready for execution.
|
||||
|
||||
Args:
|
||||
workflow_id: The ID of the workflow
|
||||
|
||||
Returns:
|
||||
True if the workflow is deployed and ready, False otherwise
|
||||
"""
|
||||
try:
|
||||
status = self.get_workflow_status(workflow_id)
|
||||
return status.is_deployed
|
||||
except SimStudioError:
|
||||
return False
|
||||
|
||||
def execute_workflow_sync(
|
||||
self,
|
||||
workflow_id: str,
|
||||
input_data: Optional[Dict[str, Any]] = None,
|
||||
timeout: float = 30.0
|
||||
) -> WorkflowExecutionResult:
|
||||
"""
|
||||
Execute a workflow and poll for completion (useful for long-running workflows).
|
||||
|
||||
Note: Currently, the API is synchronous, so this method just calls execute_workflow.
|
||||
In the future, if async execution is added, this method can be enhanced.
|
||||
|
||||
Args:
|
||||
workflow_id: The ID of the workflow to execute
|
||||
input_data: Input data to pass to the workflow
|
||||
timeout: Timeout for the initial request in seconds
|
||||
|
||||
Returns:
|
||||
WorkflowExecutionResult object containing the execution result
|
||||
|
||||
Raises:
|
||||
SimStudioError: If the workflow execution fails
|
||||
"""
|
||||
# For now, the API is synchronous, so we just execute directly
|
||||
# In the future, if async execution is added, this method can be enhanced
|
||||
return self.execute_workflow(workflow_id, input_data, timeout)
|
||||
|
||||
def set_api_key(self, api_key: str) -> None:
|
||||
"""
|
||||
Update the API key.
|
||||
|
||||
Args:
|
||||
api_key: New API key
|
||||
"""
|
||||
self.api_key = api_key
|
||||
self._session.headers.update({'X-API-Key': api_key})
|
||||
|
||||
def set_base_url(self, base_url: str) -> None:
|
||||
"""
|
||||
Update the base URL.
|
||||
|
||||
Args:
|
||||
base_url: New base URL
|
||||
"""
|
||||
self.base_url = base_url.rstrip('/')
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the underlying HTTP session."""
|
||||
self._session.close()
|
||||
|
||||
def __enter__(self):
|
||||
"""Context manager entry."""
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Context manager exit."""
|
||||
self.close()
|
||||
|
||||
|
||||
# For backward compatibility
|
||||
Client = SimStudioClient
|
||||
0
packages/python-sdk/tests/__init__.py
Normal file
0
packages/python-sdk/tests/__init__.py
Normal file
97
packages/python-sdk/tests/test_client.py
Normal file
97
packages/python-sdk/tests/test_client.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""
|
||||
Tests for the Sim Studio Python SDK
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
from simstudio import SimStudioClient, SimStudioError, WorkflowExecutionResult, WorkflowStatus
|
||||
|
||||
|
||||
def test_simstudio_client_initialization():
|
||||
"""Test SimStudioClient initialization."""
|
||||
client = SimStudioClient(api_key="test-api-key", base_url="https://test.simstudio.ai")
|
||||
assert client.api_key == "test-api-key"
|
||||
assert client.base_url == "https://test.simstudio.ai"
|
||||
|
||||
|
||||
def test_simstudio_client_default_base_url():
|
||||
"""Test SimStudioClient with default base URL."""
|
||||
client = SimStudioClient(api_key="test-api-key")
|
||||
assert client.api_key == "test-api-key"
|
||||
assert client.base_url == "https://simstudio.ai"
|
||||
|
||||
|
||||
def test_set_api_key():
|
||||
"""Test setting a new API key."""
|
||||
client = SimStudioClient(api_key="test-api-key")
|
||||
client.set_api_key("new-api-key")
|
||||
assert client.api_key == "new-api-key"
|
||||
|
||||
|
||||
def test_set_base_url():
|
||||
"""Test setting a new base URL."""
|
||||
client = SimStudioClient(api_key="test-api-key")
|
||||
client.set_base_url("https://new.simstudio.ai/")
|
||||
assert client.base_url == "https://new.simstudio.ai"
|
||||
|
||||
|
||||
def test_set_base_url_strips_trailing_slash():
|
||||
"""Test that base URL strips trailing slash."""
|
||||
client = SimStudioClient(api_key="test-api-key")
|
||||
client.set_base_url("https://test.simstudio.ai/")
|
||||
assert client.base_url == "https://test.simstudio.ai"
|
||||
|
||||
|
||||
@patch('simstudio.requests.Session.get')
|
||||
def test_validate_workflow_returns_false_on_error(mock_get):
|
||||
"""Test that validate_workflow returns False when request fails."""
|
||||
mock_get.side_effect = SimStudioError("Network error")
|
||||
|
||||
client = SimStudioClient(api_key="test-api-key")
|
||||
result = client.validate_workflow("test-workflow-id")
|
||||
|
||||
assert result is False
|
||||
mock_get.assert_called_once_with("https://simstudio.ai/api/workflows/test-workflow-id/status")
|
||||
|
||||
|
||||
def test_simstudio_error():
|
||||
"""Test SimStudioError creation."""
|
||||
error = SimStudioError("Test error", "TEST_CODE", 400)
|
||||
assert str(error) == "Test error"
|
||||
assert error.code == "TEST_CODE"
|
||||
assert error.status == 400
|
||||
|
||||
|
||||
def test_workflow_execution_result():
|
||||
"""Test WorkflowExecutionResult data class."""
|
||||
result = WorkflowExecutionResult(
|
||||
success=True,
|
||||
output={"data": "test"},
|
||||
metadata={"duration": 1000}
|
||||
)
|
||||
assert result.success is True
|
||||
assert result.output == {"data": "test"}
|
||||
assert result.metadata == {"duration": 1000}
|
||||
|
||||
|
||||
def test_workflow_status():
|
||||
"""Test WorkflowStatus data class."""
|
||||
status = WorkflowStatus(
|
||||
is_deployed=True,
|
||||
deployed_at="2023-01-01T00:00:00Z",
|
||||
is_published=False,
|
||||
needs_redeployment=False
|
||||
)
|
||||
assert status.is_deployed is True
|
||||
assert status.deployed_at == "2023-01-01T00:00:00Z"
|
||||
assert status.is_published is False
|
||||
assert status.needs_redeployment is False
|
||||
|
||||
|
||||
@patch('simstudio.requests.Session.close')
|
||||
def test_context_manager(mock_close):
|
||||
"""Test SimStudioClient as context manager."""
|
||||
with SimStudioClient(api_key="test-api-key") as client:
|
||||
assert client.api_key == "test-api-key"
|
||||
# Should close without error
|
||||
mock_close.assert_called_once()
|
||||
Reference in New Issue
Block a user