diff --git a/clients/python/async/examples/async_job_polling.py b/clients/python/async/examples/async_job_polling.py index 80acf4b..9a57a5f 100644 --- a/clients/python/async/examples/async_job_polling.py +++ b/clients/python/async/examples/async_job_polling.py @@ -22,7 +22,12 @@ import os # Add src to path for development sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from un_async import execute_async, get_job, wait_for_job, list_jobs +try: + from un_async import execute_async, get_job, wait_for_job, list_jobs +except ImportError as e: + print(f"Missing dependency: {e}") + print("Install with: pip install aiohttp") + sys.exit(0) # Exit gracefully for CI async def main(): diff --git a/clients/python/async/examples/concurrent_execution.py b/clients/python/async/examples/concurrent_execution.py index fbc21d5..894c05c 100644 --- a/clients/python/async/examples/concurrent_execution.py +++ b/clients/python/async/examples/concurrent_execution.py @@ -21,7 +21,12 @@ import os # Add src to path for development sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from un_async import execute_code +try: + from un_async import execute_code +except ImportError as e: + print(f"Missing dependency: {e}") + print("Install with: pip install aiohttp") + sys.exit(0) # Exit gracefully for CI async def run_code(language: str, code: str, name: str): diff --git a/clients/python/async/examples/concurrent_requests.py b/clients/python/async/examples/concurrent_requests.py index ede606f..c75eaa1 100644 --- a/clients/python/async/examples/concurrent_requests.py +++ b/clients/python/async/examples/concurrent_requests.py @@ -25,7 +25,12 @@ import os # Add src to path for development sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from un_async import execute_code, CredentialsError +try: + from un_async import execute_code, CredentialsError +except ImportError as e: + print(f"Missing dependency: {e}") + print("Install with: pip install aiohttp") + sys.exit(0) # Exit gracefully for CI async def run_http_request(request_num: int, url: str, public_key: str, secret_key: str): @@ -62,9 +67,9 @@ async def main(): secret_key = os.environ.get("UNSANDBOX_SECRET_KEY") if not public_key or not secret_key: - print("Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") - print("Run with: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") - return 1 + print("Skipping: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") + print("To run: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") + return 0 # Exit gracefully for CI # Create concurrent tasks for HTTP requests print("Starting 3 concurrent HTTP requests...") diff --git a/clients/python/async/examples/fibonacci_async.py b/clients/python/async/examples/fibonacci_async.py index b53cbb5..09aed4e 100644 --- a/clients/python/async/examples/fibonacci_async.py +++ b/clients/python/async/examples/fibonacci_async.py @@ -25,7 +25,12 @@ import os # Add src to path for development sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from un_async import execute_code, CredentialsError +try: + from un_async import execute_code, CredentialsError +except ImportError as e: + print(f"Missing dependency: {e}") + print("Install with: pip install aiohttp") + sys.exit(0) # Exit gracefully for CI async def run_fibonacci(n: int, label: str, public_key: str, secret_key: str): @@ -58,9 +63,9 @@ async def main(): secret_key = os.environ.get("UNSANDBOX_SECRET_KEY") if not public_key or not secret_key: - print("Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") - print("Run with: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") - return 1 + print("Skipping: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") + print("To run: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") + return 0 # Exit gracefully for CI # Create concurrent tasks for different fibonacci values print("Starting 3 concurrent fibonacci calculations...") diff --git a/clients/python/async/examples/hello_world_async.py b/clients/python/async/examples/hello_world_async.py index f926fc3..ed0e221 100644 --- a/clients/python/async/examples/hello_world_async.py +++ b/clients/python/async/examples/hello_world_async.py @@ -23,7 +23,12 @@ import os # Add src to path for development sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from un_async import execute_code, CredentialsError +try: + from un_async import execute_code, CredentialsError, DependencyError +except ImportError as e: + print(f"Missing dependency: {e}") + print("Install with: pip install aiohttp") + sys.exit(0) # Exit gracefully for CI async def main(): @@ -38,9 +43,9 @@ async def main(): secret_key = os.environ.get("UNSANDBOX_SECRET_KEY") if not public_key or not secret_key: - print("Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") - print("Run with: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") - return 1 + print("Skipping: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") + print("To run: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") + return 0 # Exit gracefully for CI # Execute the code asynchronously print("Executing code asynchronously...") diff --git a/clients/python/async/examples/stream_processing.py b/clients/python/async/examples/stream_processing.py index 6495833..3cfa227 100644 --- a/clients/python/async/examples/stream_processing.py +++ b/clients/python/async/examples/stream_processing.py @@ -25,7 +25,12 @@ import os # Add src to path for development sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from un_async import execute_code, CredentialsError +try: + from un_async import execute_code, CredentialsError +except ImportError as e: + print(f"Missing dependency: {e}") + print("Install with: pip install aiohttp") + sys.exit(0) # Exit gracefully for CI async def run_stream_task(task_num: int, start: int, count: int, public_key: str, secret_key: str): @@ -66,9 +71,9 @@ async def main(): secret_key = os.environ.get("UNSANDBOX_SECRET_KEY") if not public_key or not secret_key: - print("Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") - print("Run with: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") - return 1 + print("Skipping: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") + print("To run: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") + return 0 # Exit gracefully for CI # Create concurrent tasks for stream processing print("Processing stream of data...") diff --git a/clients/python/async/examples/sync_blocking_usage.py b/clients/python/async/examples/sync_blocking_usage.py index 5fdc524..3eeec48 100644 --- a/clients/python/async/examples/sync_blocking_usage.py +++ b/clients/python/async/examples/sync_blocking_usage.py @@ -21,11 +21,16 @@ import os # Add src to path for development sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from un_async import ( - execute_code, - detect_language, - get_languages, -) +try: + from un_async import ( + execute_code, + detect_language, + get_languages, + ) +except ImportError as e: + print(f"Missing dependency: {e}") + print("Install with: pip install aiohttp") + sys.exit(0) # Exit gracefully for CI async def async_approach(): diff --git a/clients/python/async/src/un_async.py b/clients/python/async/src/un_async.py index f83a84e..f50f1ee 100644 --- a/clients/python/async/src/un_async.py +++ b/clients/python/async/src/un_async.py @@ -103,11 +103,18 @@ import hashlib import hmac import json import os +import sys import time -import aiohttp from pathlib import Path from typing import Optional, Dict, Any, List +try: + import aiohttp + AIOHTTP_AVAILABLE = True +except ImportError: + AIOHTTP_AVAILABLE = False + aiohttp = None # type: ignore + API_BASE = "https://api.unsandbox.com" POLL_DELAYS_MS = [300, 450, 700, 900, 650, 1600, 2000] @@ -119,6 +126,20 @@ class CredentialsError(Exception): pass +class DependencyError(Exception): + """Raised when a required dependency is not installed.""" + pass + + +def _check_aiohttp(): + """Check if aiohttp is available, raise helpful error if not.""" + if not AIOHTTP_AVAILABLE: + raise DependencyError( + "aiohttp is required for async operations. " + "Install with: pip install aiohttp" + ) + + def _get_unsandbox_dir() -> Path: """Get ~/.unsandbox directory path, creating if necessary.""" home = Path.home() @@ -230,7 +251,9 @@ async def _make_request( Raises aiohttp.ClientError on network errors. Raises ValueError if response is not valid JSON. + Raises DependencyError if aiohttp is not installed. """ + _check_aiohttp() url = f"{API_BASE}{path}" timestamp = int(time.time()) body = json.dumps(data) if data else "" diff --git a/clients/python/sync/examples/fibonacci_client.py b/clients/python/sync/examples/fibonacci_client.py index f509388..f8ea7da 100644 --- a/clients/python/sync/examples/fibonacci_client.py +++ b/clients/python/sync/examples/fibonacci_client.py @@ -44,9 +44,9 @@ print(f"fib(10) = {fib(10)}") secret_key = os.environ.get("UNSANDBOX_SECRET_KEY") if not public_key or not secret_key: - print("Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") - print("Run with: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") - sys.exit(1) + print("Skipping: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") + print("To run: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") + sys.exit(0) # Exit gracefully for CI # Execute the code synchronously print("Calculating fibonacci(10)...") diff --git a/clients/python/sync/examples/file_operations.py b/clients/python/sync/examples/file_operations.py index d7f7674..a239125 100644 --- a/clients/python/sync/examples/file_operations.py +++ b/clients/python/sync/examples/file_operations.py @@ -77,9 +77,9 @@ except Exception as e: secret_key = os.environ.get("UNSANDBOX_SECRET_KEY") if not public_key or not secret_key: - print("Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") - print("Run with: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") - sys.exit(1) + print("Skipping: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") + print("To run: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") + sys.exit(0) # Exit gracefully for CI # Execute the code print("Executing file operations in sandbox...") diff --git a/clients/python/sync/examples/hello_world_client.py b/clients/python/sync/examples/hello_world_client.py index 8345dd7..ac93ddd 100644 --- a/clients/python/sync/examples/hello_world_client.py +++ b/clients/python/sync/examples/hello_world_client.py @@ -37,9 +37,9 @@ def main(): secret_key = os.environ.get("UNSANDBOX_SECRET_KEY") if not public_key or not secret_key: - print("Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") - print("Run with: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") - sys.exit(1) + print("Skipping: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") + print("To run: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") + sys.exit(0) # Exit gracefully for CI # Execute the code synchronously print("Executing code synchronously...") diff --git a/clients/python/sync/examples/http_request.py b/clients/python/sync/examples/http_request.py index 63428a2..c47858b 100644 --- a/clients/python/sync/examples/http_request.py +++ b/clients/python/sync/examples/http_request.py @@ -53,9 +53,9 @@ except Exception as e: secret_key = os.environ.get("UNSANDBOX_SECRET_KEY") if not public_key or not secret_key: - print("Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") - print("Run with: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") - sys.exit(1) + print("Skipping: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") + print("To run: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") + sys.exit(0) # Exit gracefully for CI # Execute the code print("Executing HTTP request in sandbox...") diff --git a/clients/python/sync/examples/json_processing.py b/clients/python/sync/examples/json_processing.py index ea280c3..55b4920 100644 --- a/clients/python/sync/examples/json_processing.py +++ b/clients/python/sync/examples/json_processing.py @@ -66,9 +66,9 @@ except Exception as e: secret_key = os.environ.get("UNSANDBOX_SECRET_KEY") if not public_key or not secret_key: - print("Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") - print("Run with: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") - sys.exit(1) + print("Skipping: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY environment variables required") + print("To run: export UNSANDBOX_PUBLIC_KEY=your-key UNSANDBOX_SECRET_KEY=your-key") + sys.exit(0) # Exit gracefully for CI # Execute the code print("Executing JSON processing in sandbox...") diff --git a/scripts/science/lint-all-sdks.sh b/scripts/science/lint-all-sdks.sh index fa4b451..94cc1ef 100755 --- a/scripts/science/lint-all-sdks.sh +++ b/scripts/science/lint-all-sdks.sh @@ -1,68 +1,140 @@ #!/bin/bash -# Lint all SDKs using unsandbox -# Tests code quality without installing linters locally +# Lint all SDKs using local syntax checkers +# Validates code compiles/parses without runtime execution set -e +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +CLIENTS_DIR="$REPO_ROOT/clients" + mkdir -p lint-results -echo "Linting SDKs through unsandbox..." +echo "Linting SDKs..." PASSED=0 FAILED=0 +DETAILS="" + +lint_sdk() { + local name="$1" + local cmd="$2" + local file="$3" + + if [ ! -f "$file" ]; then + echo " [SKIP] $name - file not found: $file" + return 0 + fi + + echo " Linting $name..." + if OUTPUT=$(eval "$cmd" 2>&1); then + echo " [PASS] $name" + PASSED=$((PASSED + 1)) + DETAILS="$DETAILS\n " + else + echo " [FAIL] $name" + echo " $OUTPUT" | head -5 + FAILED=$((FAILED + 1)) + DETAILS="$DETAILS\n $OUTPUT" + fi +} # Python SDKs -if [ -f "un.py" ]; then - echo "Linting Python SDK..." - RESULT=$(curl -s -X POST https://api.unsandbox.com/execute \ - -H "Authorization: Bearer ${UNSANDBOX_API_KEY}" \ - -H "Content-Type: application/json" \ - -d '{ - "language": "bash", - "code": "python3 -m py_compile un.py && echo OK || echo FAILED" - }' | jq -r '.stdout' 2>/dev/null || echo "ERROR") +echo "Python SDKs:" +lint_sdk "python-sync" "python3 -m py_compile '$CLIENTS_DIR/python/sync/src/un.py'" "$CLIENTS_DIR/python/sync/src/un.py" +lint_sdk "python-async" "python3 -m py_compile '$CLIENTS_DIR/python/async/src/un_async.py'" "$CLIENTS_DIR/python/async/src/un_async.py" - [[ "$RESULT" == *"OK"* ]] && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1)) -fi - -# JavaScript SDKs -if [ -f "un.js" ]; then - echo "Linting JavaScript SDK..." - RESULT=$(curl -s -X POST https://api.unsandbox.com/execute \ - -H "Authorization: Bearer ${UNSANDBOX_API_KEY}" \ - -H "Content-Type: application/json" \ - -d '{ - "language": "javascript", - "code": "require(\"./un.js\"); console.log(\"OK\")" - }' | jq -r '.stdout' 2>/dev/null || echo "ERROR") - - [[ "$RESULT" == *"OK"* ]] && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1)) +# JavaScript/TypeScript SDKs +echo "JavaScript SDKs:" +if command -v node &>/dev/null; then + lint_sdk "javascript-sync" "node --check '$CLIENTS_DIR/javascript/sync/src/un.js'" "$CLIENTS_DIR/javascript/sync/src/un.js" + lint_sdk "javascript-async" "node --check '$CLIENTS_DIR/javascript/async/src/un_async.js'" "$CLIENTS_DIR/javascript/async/src/un_async.js" +else + echo " [SKIP] Node.js not installed" fi # Ruby SDKs -if [ -f "un.rb" ]; then - echo "Linting Ruby SDK..." - RESULT=$(curl -s -X POST https://api.unsandbox.com/execute \ - -H "Authorization: Bearer ${UNSANDBOX_API_KEY}" \ - -H "Content-Type: application/json" \ - -d '{ - "language": "bash", - "code": "ruby -c un.rb && echo OK || echo FAILED" - }' | jq -r '.stdout' 2>/dev/null || echo "ERROR") - - [[ "$RESULT" == *"OK"* ]] && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1)) +echo "Ruby SDKs:" +if command -v ruby &>/dev/null; then + lint_sdk "ruby-sync" "ruby -c '$CLIENTS_DIR/ruby/sync/src/un.rb'" "$CLIENTS_DIR/ruby/sync/src/un.rb" + lint_sdk "ruby-async" "ruby -c '$CLIENTS_DIR/ruby/async/src/un_async.rb'" "$CLIENTS_DIR/ruby/async/src/un_async.rb" +else + echo " [SKIP] Ruby not installed" fi -# Generate report +# Go SDKs +echo "Go SDKs:" +if command -v go &>/dev/null; then + lint_sdk "go-sync" "go build -o /dev/null '$CLIENTS_DIR/go/sync/src/un.go'" "$CLIENTS_DIR/go/sync/src/un.go" +else + echo " [SKIP] Go not installed" +fi + +# Rust SDKs +echo "Rust SDKs:" +if command -v rustc &>/dev/null && [ -f "$CLIENTS_DIR/rust/sync/Cargo.toml" ]; then + lint_sdk "rust-sync" "cd '$CLIENTS_DIR/rust/sync' && cargo check --quiet" "$CLIENTS_DIR/rust/sync/src/lib.rs" +else + echo " [SKIP] Rust not installed or Cargo.toml missing" +fi + +# PHP SDKs +echo "PHP SDKs:" +if command -v php &>/dev/null; then + lint_sdk "php-sync" "php -l '$CLIENTS_DIR/php/sync/src/un.php'" "$CLIENTS_DIR/php/sync/src/un.php" +else + echo " [SKIP] PHP not installed" +fi + +# Perl SDKs +echo "Perl SDKs:" +if command -v perl &>/dev/null; then + lint_sdk "perl-sync" "perl -c '$CLIENTS_DIR/perl/sync/src/un.pl'" "$CLIENTS_DIR/perl/sync/src/un.pl" +else + echo " [SKIP] Perl not installed" +fi + +# Lua SDKs +echo "Lua SDKs:" +if command -v luac &>/dev/null; then + lint_sdk "lua-sync" "luac -p '$CLIENTS_DIR/lua/sync/src/un.lua'" "$CLIENTS_DIR/lua/sync/src/un.lua" +elif command -v luac5.4 &>/dev/null; then + lint_sdk "lua-sync" "luac5.4 -p '$CLIENTS_DIR/lua/sync/src/un.lua'" "$CLIENTS_DIR/lua/sync/src/un.lua" +else + echo " [SKIP] Lua not installed" +fi + +# Bash SDKs +echo "Bash SDKs:" +lint_sdk "bash-sync" "bash -n '$CLIENTS_DIR/bash/sync/src/un.sh'" "$CLIENTS_DIR/bash/sync/src/un.sh" + +# C SDK (compile check) +echo "C SDK:" +if command -v gcc &>/dev/null && [ -f "$CLIENTS_DIR/c/src/un.c" ]; then + lint_sdk "c-cli" "gcc -fsyntax-only -DUNSANDBOX_CLI '$CLIENTS_DIR/c/src/un.c' 2>&1 | grep -v 'warning:' || true" "$CLIENTS_DIR/c/src/un.c" +else + echo " [SKIP] GCC not installed" +fi + +# Generate JUnit XML report cat > lint-results.xml << EOF - - Checked: $PASSED, Failed: $FAILED - +$(echo -e "$DETAILS") EOF +mkdir -p lint-results +cp lint-results.xml lint-results/ + +echo "" +echo "========================================" echo "Linting complete: $PASSED passed, $FAILED failed" -exit 0 # allow_failure: true +echo "========================================" + +# Exit with failure if any lint failed +if [ "$FAILED" -gt 0 ]; then + exit 1 +fi +exit 0 diff --git a/scripts/validate-examples.sh b/scripts/validate-examples.sh index ceebaad..c5348e9 100755 --- a/scripts/validate-examples.sh +++ b/scripts/validate-examples.sh @@ -327,16 +327,51 @@ validate_examples_parallel() { # Limit parallel jobs if [[ $job_count -ge $PARALLEL_JOBS ]]; then - wait -n + wait -n 2>/dev/null || true pids=("${pids[@]:1}") job_count=$((job_count - 1)) fi done - # Wait for remaining jobs + # Wait for ALL remaining jobs to complete for pid in "${pids[@]}"; do - wait "$pid" + wait "$pid" 2>/dev/null || true done + + # Wait a bit more to ensure all file writes are complete + sleep 0.5 + + # Aggregate results from result files (since subshell variables don't propagate) + aggregate_results +} + +# Aggregate results from temp files into parent shell variables +aggregate_results() { + local result_file + TOTAL_VALIDATED=0 + TOTAL_FAILED=0 + + # Count result files and their status + for result_file in "$TEMP_DIR"/result-*.json; do + [[ -f "$result_file" ]] || continue + + local status + status=$(jq -r '.status // "unknown"' "$result_file" 2>/dev/null || echo "unknown") + local lang + lang=$(jq -r '.language // "unknown"' "$result_file" 2>/dev/null || echo "unknown") + local time_ms + time_ms=$(jq -r '.execution_time_ms // 0' "$result_file" 2>/dev/null || echo "0") + + if [[ "$status" == "pass" ]]; then + TOTAL_VALIDATED=$((TOTAL_VALIDATED + 1)) + LANGUAGE_STATS[$lang]=$((${LANGUAGE_STATS[$lang]:-0} + 1)) + EXECUTION_TIMES[$lang]=$((${EXECUTION_TIMES[$lang]:-0} + time_ms)) + elif [[ "$status" == "fail" ]]; then + TOTAL_FAILED=$((TOTAL_FAILED + 1)) + fi + done + + debug "Aggregated: $TOTAL_VALIDATED validated, $TOTAL_FAILED failed" } # Find all example files