diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index b3d57f0..0000000 --- a/docs/api.md +++ /dev/null @@ -1 +0,0 @@ -[OAD(https://codeboxapi.com/openapi.json)] diff --git a/docs/api/codebox.md b/docs/api/codebox.md new file mode 100644 index 0000000..a5bf21a --- /dev/null +++ b/docs/api/codebox.md @@ -0,0 +1,35 @@ +# CodeBox Class API Reference + +## Constructor + +```python +CodeBox( + session_id: str | None = None, + api_key: str | Literal["local", "docker"] | None = None, + factory_id: str | Literal["default"] | None = None, +) +``` + +## Core Methods + +*Note*: Async methods are available when appened with `a` (e.g. `aexec()`). + +### Code Execution + +- `exec(code: str, kernel: Literal["ipython", "bash"] = "ipython") -> ExecResult` +- `stream_exec(code: str, kernel: Literal["ipython", "bash"] = "ipython") -> AsyncGenerator[str, None]` + +### File Operations + +- `upload(remote_file_path: str, content: Union[BinaryIO, bytes, str]) -> RemoteFile` +- `download(remote_file_path: str) -> RemoteFile` +- `stream_download(remote_file_path: str) -> AsyncGenerator[bytes, None]` +- `list_files() -> list[RemoteFile]` + +### Utility Methods + +- `healthcheck() -> Literal["healthy", "error"]` +- `show_variables() -> dict[str, str]` +- `restart() -> None` +- `keep_alive(minutes: int = 15) -> None` +- `install(*packages: str) -> str` diff --git a/docs/api/types.md b/docs/api/types.md new file mode 100644 index 0000000..2d08f3b --- /dev/null +++ b/docs/api/types.md @@ -0,0 +1,59 @@ +# Types API Reference + +## RemoteFile Class + +Represents a file stored in the remote CodeBox environment. + +```python +RemoteFile( + path: str, + remote: CodeBox, + _size: int | None = None, + _content: bytes | None = None, +) +``` + +### Properties + +- `name: str` - Returns the filename from the path +- `_size: int | None` - Cached file size +- `_content: bytes | None` - Cached file content + +### Methods + +*Note*: Async methods are available when appended with `a` (e.g. `aget_content()`). + +- `get_size() -> int` - Gets file size +- `get_content() -> bytes` - Retrieves and caches file content +- `save(local_path: str) -> None` - Saves file to local path + +## ExecChunk Class + +Represents a single chunk of execution output. + +```python +ExecChunk( + type: Literal["txt", "img", "err"], + content: str +) +``` + +The `type` field can be one of: + +- `txt`: Text output +- `img`: Image output (base64 encoded) +- `err`: Error output + +## ExecResult Class + +Container for execution results composed of multiple chunks. + +```python +ExecResult( + chunks: list[ExecChunk] +) +``` + +- `text: str` - Concatenated text output +- `images: list[str]` - List of base64 encoded images +- `errors: list[str]` - List of error messages diff --git a/docs/concepts/architecture.md b/docs/concepts/architecture.md new file mode 100644 index 0000000..9a9eb1c --- /dev/null +++ b/docs/concepts/architecture.md @@ -0,0 +1,52 @@ +# Architecture Overview + +- [REST API](https://codeboxapi.com/docs) + +## Core Components + +- [Github Repo](https://github.com/shroominic/codebox-api) + +1. **Base Layer** + - Core `CodeBox` class + - Code execution interface + - File management interface + - Helper methods + +2. **Implementation Layer** + - `LocalBox`: Local running sandbox + - `RemoteBox`: REST API adapter for remote execution + - `DockerBox`: Docker adapter for local parallel execution + +3. **CodeBox API Service** + - REST API interface + - Fully isolated and scalable + - File and session management + - Custom factory support + +## Why is Sandboxing Important? + +Security is critical when deploying LLM Agents in production: + +- ๐Ÿ›ก๏ธ **Malicious Code Protection**: Isolated and secure execution +- ๐Ÿ” **Injection Prevention**: Mitigation of prompt injection attacks +- ๐Ÿฐ **System Isolation**: No host system access +- ๐Ÿ“Š **Resource Control**: Memory and CPU limits + +## How It Works + +CodeBox uses a three-tier architecture: + +1. **API Layer** + - REST API for service interaction + - API key authentication + - Sync/Async operation support + +2. **Container Layer** + - Hardened Docker containers + - Complete host system isolation + - Automatic resource management + +3. **Storage Layer** + - Persistent file system + - Dependency management + - Package caching diff --git a/docs/concepts.md b/docs/concepts/components.md similarity index 100% rename from docs/concepts.md rename to docs/concepts/components.md diff --git a/docs/concepts/data_structures.md b/docs/concepts/data_structures.md new file mode 100644 index 0000000..019bc51 --- /dev/null +++ b/docs/concepts/data_structures.md @@ -0,0 +1,66 @@ +# Data Structures + +## Core Types + +### RemoteFile + +Represents a file in the CodeBox environment: + +```python +class RemoteFile: + path: str # File path in CodeBox + remote: CodeBox # Associated CodeBox instance +``` + +### Methods + +- `get_content():` Get file contents +- `aget_content():` Async get contents +- `save(path):` Save to local path +- `asave(path):` Async save to local path + +### ExecChunk + +Represents an execution output chunk: + +```python +class ExecChunk: + type: Literal["txt", "img", "err"] # Output type + content: str # Chunk content +``` + +### Types + +- `txt:` Text output +- `img:` Base64 encoded image +- `err:` Error message + +### ExecResult + +Complete execution result: + +```python +class ExecResult: + chunks: List[ExecChunk] # List of output chunks +``` + +### Properties + +- `text:` Combined text output +- `images:` List of image outputs +- `errors:` List of error messages + +### Usage Examples + +```python +# File handling +codebox = CodeBox() + # Upload and get file + file = codebox.upload("test.txt", "content") + content = file.get_content() + + # Execute code and handle result + result = codebox.exec("print('hello')") + print(result.text) # Text output + print(result.errors) # Any errors +``` diff --git a/docs/concepts/implementations.md b/docs/concepts/implementations.md new file mode 100644 index 0000000..00999b0 --- /dev/null +++ b/docs/concepts/implementations.md @@ -0,0 +1,66 @@ +# CodeBox Implementations + +## Implementation Overview + +### LocalBox + +- **Usage**: Local development and testing +- **Requirements**: jupyter-kernel-gateway, ipython +- **Configuration**: `api_key="local"` +- **Advantages**: + - Rapid development + - No external dependencies + - Direct local environment access + - Fast execution for development +- **Limitations**: + - No isolation + - Development only + - Local system resources + +### DockerBox + +- **Usage**: Isolated testing +- **Requirements**: Docker installation +- **Configuration**: `api_key="docker"` +- **Advantages**: + - Local isolation + - Consistent environment + - No API costs + - Custom image support +- **Limitations**: + - Requires Docker + - Local resource constraints + - Additional setup needed + +### RemoteBox + +- **Usage**: Production, scaling and cloud deployment +- **Requirements**: + - Valid API key + - Internet connection +- **Configuration**: + +```python +codebox = CodeBox(api_key="...") +``` + +- **Key Features**: + - REST API integration + - Automatic session management + - Cloud-based file storage + - Managed package installation + - Resource usage monitoring +- **Best for**: + - Production deployments + - Scalable applications + - Team collaborations + +## Implementation Comparison + +| Feature | RemoteBox | LocalBox | DockerBox | +|---------|-----------|----------|------------| +| Isolation | Full | Minimal | Full | +| Setup Complexity | Low | Medium | High | +| Resource Scaling | Yes | No | Limited | +| Internet Required | Yes | No | No | +| Cost | API Usage | Free | Free | diff --git a/docs/concepts/local_docker_remote.md b/docs/concepts/local_docker_remote.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/examples.md b/docs/examples.md deleted file mode 100644 index 33f0285..0000000 --- a/docs/examples.md +++ /dev/null @@ -1,107 +0,0 @@ -# Examples - -## Basic Usage - -Run code in a new CodeBox: - -```python -from codeboxapi import CodeBox - -with CodeBox() as codebox: - print(codebox.status()) - - codebox.run("print('Hello World!')") -``` - -Run async code: - -```python -import asyncio -from codeboxapi import CodeBox - -async def main(): - async with CodeBox() as codebox: - await codebox.astatus() - await codebox.arun("print('Hello World!')") - -asyncio.run(main()) -``` - -## File IO - -Upload and download files: - -```python -from codeboxapi import CodeBox - -with CodeBox() as codebox: - - # Upload file - codebox.upload("data.csv", b"1,2,3\ -4,5,6") - - # List files - print(codebox.list_files()) - - # Download file - data = codebox.download("data.csv") - print(data.content) -``` - -## Package Installation - -Install packages into the CodeBox: - -```python -from codeboxapi import CodeBox - -with CodeBox() as codebox: - - # Install packages - codebox.install("pandas") - codebox.install("matplotlib") - - # Use them - codebox.run("import pandas as pd") - codebox.run("import matplotlib.pyplot as plt") -``` - -## Restoring Sessions - -Restore a CodeBox session from its ID: - -```python -from codeboxapi import CodeBox - -# Start CodeBox and save ID -codebox = CodeBox() -codebox.start() -session_id = codebox.session_id - -#delete session -del session - -# Restore session -codebox = CodeBox.from_id(session_id) -print(codebox.status()) -``` - -## Parallel Execution - -Run multiple CodeBoxes in parallel: - -```python -import asyncio -from codeboxapi import CodeBox - -async def main(): - await asyncio.gather( - spawn_codebox() for _ in range(10) - ) - -async def spawn_codebox(): - async with CodeBox() as codebox: - print(await codebox.arun("print('Hello World!')")) - -asyncio.run(main()) -``` diff --git a/docs/examples/advanced.md b/docs/examples/advanced.md new file mode 100644 index 0000000..3efe8c0 --- /dev/null +++ b/docs/examples/advanced.md @@ -0,0 +1,52 @@ +# Advanced Examples + +## Custom Kernel Usage + +```python +from codeboxapi import CodeBox +codebox = CodeBox() + # Execute bash commands + result = codebox.exec("ls -la", kernel="bash") + print(result.text) + # Install system packages with timeout + result = codebox.exec( + "apt-get update && apt-get install -y ffmpeg", + kernel="bash", + timeout=300 + ) + print(result.text) +``` + +## File Streaming with Chunks + +```python +from codeboxapi import CodeBox +codebox = CodeBox() +# Upload large file with streaming +with open("large_file.dat", "rb") as f: + file = codebox.upload("remote_file.dat", f) +print(f"Uploaded file size: {file.get_size()} bytes") +# Download with streaming and custom chunk size +for chunk in codebox.stream_download("remote_file.dat"): + process_chunk(chunk) +``` + +## Docker Implementation + +```python +from codeboxapi import CodeBox +# Using DockerBox with custom port and image +codebox = CodeBox(api_key="docker") +# Execute code in container +result = codebox.exec("import sys; print(sys.version)") +print(result.text) +``` + +## Error Handling + +```python +from codeboxapi import CodeBox + +codebox = CodeBox() +codebox.exec("import non_existent_package") +``` diff --git a/docs/examples/async.md b/docs/examples/async.md new file mode 100644 index 0000000..b4bd45f --- /dev/null +++ b/docs/examples/async.md @@ -0,0 +1,57 @@ +# Async CodeBox API + +## Parallel Execution + +Run multiple CodeBoxes in parallel: + +```python +import asyncio +from codeboxapi import CodeBox + +async def process_codebox(id: int): + codebox = CodeBox() + # Execute code + result = await codebox.aexec(f"print('CodeBox {id}')") + print(result.text) + + # Install package + await codebox.ainstall("pandas") + + # Run computation + result = await codebox.aexec( + "import pandas as pd; print(pd.__version__)" + ) + return result.text + +async def main(): + # Run 5 CodeBoxes in parallel + results = await asyncio.gather( + *[process_codebox(i) for i in range(5)] + ) + print(f"Results: {results}") + +asyncio.run(main()) +``` + +## Async File Operations with Progress + +```python +import asyncio +from tqdm import tqdm +from codeboxapi import CodeBox + +async def upload_with_progress(codebox, filename: str): + total_size = os.path.getsize(filename) + with tqdm(total=total_size, desc="Uploading") as pbar: + async with aiofiles.open(filename, "rb") as f: + file = await codebox.aupload(filename, f) + pbar.update(total_size) + return file + +async def main(): + codebox = CodeBox() + file = await upload_with_progress(codebox, "large_file.dat") + print(f"Uploaded: {file.path}, Size: {await file.aget_size()}") + +asyncio.run(main()) +``` diff --git a/docs/examples/basic.md b/docs/examples/basic.md new file mode 100644 index 0000000..8861f89 --- /dev/null +++ b/docs/examples/basic.md @@ -0,0 +1,22 @@ +# Basic Usage + +Run code in a new CodeBox: + +```python +from codeboxapi import CodeBox + +codebox = CodeBox() + +codebox.exec("print('Hello World!')") +``` + +Run async code: + +```python +from codeboxapi import CodeBox + +codebox = CodeBox() + +async def main(): + await codebox.aexec("print('Hello World!')") +``` diff --git a/docs/examples/files.md b/docs/examples/files.md new file mode 100644 index 0000000..63059de --- /dev/null +++ b/docs/examples/files.md @@ -0,0 +1,57 @@ +# File Operations Examples + +## Basic File Operations + +```python +from codeboxapi import CodeBox + +codebox = CodeBox() + +# Upload text file +file = codebox.upload("data.txt", "Hello World!") +print(f"File size: {file.get_size()} bytes") + +# Upload binary data +binary_data = b"Binary content" +file = codebox.upload("data.bin", binary_data) + +# List all files +for file in codebox.list_files(): + print(f"- {file.path}: {file.get_size()} bytes") + +# Download and save locally +remote_file = codebox.download("data.txt") +remote_file.save("local_data.txt") +``` + +## Streaming Operations + +```python +from codeboxapi import CodeBox +import aiofiles + +# Synchronous streaming +codebox = CodeBox() +# Stream upload +with open("large_file.dat", "rb") as f: + codebox.upload("remote_file.dat", f.read()) + +# Stream download with progress +from tqdm import tqdm +total_size = file.get_size() +with tqdm(total=total_size) as pbar: + with open("downloaded.dat", "wb") as f: + for chunk in codebox.stream_download("remote_file.dat"): + f.write(chunk) + pbar.update(len(chunk)) + +# Asynchronous streaming +async def stream_example(): + codebox = CodeBox() + + async with aiofiles.open("large_file.dat", "rb") as f: + file = await codebox.aupload("remote_file.dat", f) + + async for chunk in codebox.astream_download("remote_file.dat"): + await process_chunk(chunk) +``` diff --git a/docs/guides/basic.md b/docs/guides/basic.md new file mode 100644 index 0000000..6cf0868 --- /dev/null +++ b/docs/guides/basic.md @@ -0,0 +1,28 @@ +# Usage + +To use CodeBox, you first need to obtain an API key from [the CodeBox website](https://pay.codeboxapi.com/b/00g3e6dZX2fTg0gaEE). + +You can then start a CodeBox session: + +```python +from codeboxapi import CodeBox + +codebox = CodeBox() +print(codebox.healthcheck()) +result = codebox.exec("print('Hello World!')") +print(result.text) +``` + +The context manager (`with * as *:`) will automatically start and shutdown the CodeBox. + +You can also use CodeBox asynchronously: + +```python +from codeboxapi import CodeBox + +async def main(): + codebox = CodeBox() + await codebox.ahealthcheck() + result = await codebox.aexec("print('Hello World!')") + print(result.text) +``` diff --git a/docs/guides/files.md b/docs/guides/files.md new file mode 100644 index 0000000..b44f869 --- /dev/null +++ b/docs/guides/files.md @@ -0,0 +1,54 @@ +# File Operations + +CodeBox provides a complete API for handling files, including synchronous and asynchronous operations. + +## Basic Operations + +```python +from codeboxapi import CodeBox +codebox = CodeBox() +# Upload a file +codebox.upload("data.txt", "File content") +# List files +files = codebox.list_files() +print(files) +# Download a file +file = codebox.download("data.txt") +print(file.content) +``` + +## Download Streaming + +For large files, CodeBox supports streaming: + +```python +codebox = CodeBox() +# Download streaming +for chunk in codebox.stream_download("large_file.dat"): + process_chunk(chunk) +``` + +## Asynchronous Operations + +```python +codebox = CodeBox() +# Upload a file +await codebox.aupload("data.txt", "File content") +# Asynchronous streaming +async for chunk in codebox.astream_download("large_file.dat"): + await process_chunk(chunk) +``` + +### RemoteFile Methods + +```python +#Get file content +content = file.get_content() # sync +content = await file.aget_content() # async +#Get file size +size = file.get_size() # sync +size = await file.aget_size() # async +#Save file locally +file.save("local_path.txt") # sync +await file.asave("local_path.txt") # async +``` diff --git a/docs/guides/packages.md b/docs/guides/packages.md new file mode 100644 index 0000000..0db1b8b --- /dev/null +++ b/docs/guides/packages.md @@ -0,0 +1,52 @@ +# Package Management + +CodeBox provides simple package management capabilities through the `install` method. + +## Installing Packages + +```python +from codeboxapi import CodeBox + +codebox = CodeBox() +# Install a single package +codebox.install("pandas") + +# Install multiple packages +codebox.install("numpy", "matplotlib") + +# Install specific versions +codebox.install("requests==2.28.1") +``` + +## Async Installation + +```python +codebox = CodeBox() +await codebox.ainstall("pandas", "numpy") +``` + +## Verifying Installations + +```python +codebox = CodeBox() +codebox.install("pandas") +result = codebox.exec("import pandas as pd; print(pd.__version__)") +print(result.text) +``` + +## Error handling during installation + +```python +try: + codebox.install("non-existent-package") +except CodeBoxError as e: + print(f"Installation failed: {e}") +``` + +## Requirements file installation + +```python +with open("requirements.txt") as f: + packages = f.read().splitlines() + codebox.install(packages) +``` diff --git a/docs/index.md b/docs/index.md index 455c493..8b18436 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,3 @@ - # ๐Ÿ“ฆ CodeBox API [![Version](https://badge.fury.io/py/codeboxapi.svg)](https://badge.fury.io/py/codeboxapi) @@ -7,34 +6,49 @@ ![License](https://img.shields.io/pypi/l/codeboxapi) ![PyVersion](https://img.shields.io/pypi/pyversions/codeboxapi) -CodeBox is the simplest cloud infrastructure for running and testing python code in an isolated environment. It allows developers to execute arbitrary python code without worrying about security or dependencies. Some key features include: - -- Securely execute python code in a sandboxed container -- Easily install python packages into the environment -- Built-in file storage for uploading and downloading files -- Support for running code asynchronously using asyncio -- Local testing mode for development without an internet connection - -## Why is SandBoxing important? - -When deploying LLM Agents to production, it is important to ensure -that the code they run is safe and does not contain any malicious code. -This is especially important when considering prompt injection, which -could give an attacker access to the entire system. - -## How does CodeBox work? - -CodeBox uses a cloud hosted system to run hardened containers -that are designed to be secure. These containers are then used to -run the code that is sent to the API. This ensures that the code -is run in a secure environment, and that the code cannot access -the host system. - -## Links & Resources - -- [CodeInterpreterAPI](https://github.com/shroominic/codeinterpreter-api) -- [Documentation](https://shroominic.github.io/codebox-api/) -- [REST API](https://codeboxapi.com/docs) -- [Get API Key](https://pay.codeboxapi.com/b/00g3e6dZX2fTg0gaEE) -- [Github Repo](https://github.com/shroominic/codebox-api) -- [PyPI Package](https://pypi.org/project/codeboxapi/) +## What is CodeBox? + +CodeBox is a cloud infrastructure designed to run and test Python code in an isolated environment. It allows developers to execute arbitrary Python code without worrying about security or dependencies. + +### Key Features + +- ๐Ÿงช **Open Source**: Development without internet connection or API key +- ๐Ÿ”’ **Secure Execution**: Code runs in isolated environments +- ๐Ÿš€ **Easy Scaling**: Scales with your workload +- โฉ๏ธ **Streaming**: Code execution can be streamed +- ๐Ÿ’พ **File System**: Upload/download of files +- โšก **Async Support**: All interfaces support async +- ๐Ÿณ **Docker**: Fully local parallel execution +- ๐Ÿญ **Factories**: Create fully custom environments + +## Quick Start + +```python +from codeboxapi import CodeBox +codebox = CodeBox() +# Execute Python code +result = codebox.exec("print('Hello World!')") +print(result.text) +# Install packages +codebox.install("pandas", "numpy") +# Handle files +codebox.upload("data.csv", "1,2,3\n4,5,6") +files = codebox.list_files() +``` + +## Use Cases + +- ๐Ÿค– **Code Interpreter**: Safe execution of AI-generated code +- ๐Ÿ“š **SWE Agents**: Parallel & secure dev environments +- ๐Ÿงช **Testing**: Isolated testing environment +- ๐Ÿ”ฌ **Security Research**: Safe code execution for analysis +- ๐Ÿ› ๏ธ **Automation**: Automated workflows + +## Resources + +- ๐ŸŒ **CodeBox API**: [codeboxapi.com](https://codeboxapi.com/) +- ๐Ÿ“˜ **Complete Documentation**: [shroominic.github.io/codebox-api/](https://shroominic.github.io/codebox-api/) +- ๐Ÿ”‘ **Get API Key**: [codeboxapi.com/pricing](https://codeboxapi.com/pricing) +- ๐Ÿ‘พ **Code Interpreter API**: [codeinterpreterapi.com](https://codeinterpreterapi.com) +- ๐Ÿ’ป **GitHub Repository**: [github.com/shroominic/codebox-api](https://github.com/shroominic/codebox-api) +- ๐Ÿ“ฆ **PyPI Package**: [pypi.org/project/codeboxapi/](https://pypi.org/project/codeboxapi/) diff --git a/docs/installation.md b/docs/installation.md deleted file mode 100644 index 63019ff..0000000 --- a/docs/installation.md +++ /dev/null @@ -1,15 +0,0 @@ -# Installation - -CodeBox can be installed via pip: - -```bash -pip install codeboxapi -``` - -This will install the `codeboxapi` package and all dependencies. - -For local development without an API key, you will also need to install `jupyter-kernel-gateway`: - -```bash -pip install "codeboxapi[all]" -``` diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..d5e6283 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,91 @@ +# Quick Start + +## Installation +CodeBox can be installed via pip: + +```bash +pip install codeboxapi +``` + +This will install the `codeboxapi` package and all dependencies. + +For local development without an API key, you will also need to install `jupyter-kernel-gateway`: + +```bash +pip install jupyter-kernel-gateway +``` + +## Jupyter Setup for Local Development + +After installing `jupyter-kernel-gateway`, you can start using CodeBox locally without an API key. The LocalBox implementation will automatically manage the Jupyter kernel for you. + +Note: Make sure you have IPython installed in your environment: + +```bash +pip install ipython +``` + +## Local Development + +CodeBox provides a local execution environment using IPython for development and testing: + +```python +from codeboxapi import CodeBox + +# Local execution (no API key needed) +with CodeBox(api_key="local") as codebox: + # Execute Python code + result = codebox.exec("print('Hello World!')") + print(result.text) + # Use matplotlib (automatically handles display) + result = codebox.exec(""" +import matplotlib.pyplot as plt +plt.plot([1, 2, 3], [1, 2, 3]) +plt.show() +""") + # Work with files in local .codebox directory + codebox.upload("data.csv", "1,2,3\n4,5,6") + files = codebox.list_files() + # Install packages locally + codebox.install("pandas") +``` + +You can also specify a custom working directory: + +```python +with CodeBox(api_key="local", codebox_cwd="./my_workspace") as codebox: + codebox.exec("print('Working in custom directory')") +``` + +## API Key Configuration + +For local development, no API key is required. For production use, obtain an API key from [CodeBox website](https://pay.codeboxapi.com/b/00g3e6dZX2fTg0gaEE) and set it: + +```python +codebox = CodeBox(api_key="your-api-key") +``` + +Or via environment variable: + +```python +export CODEBOX_API_KEY="your-api-key" +``` + +## Implementation Options + +CodeBox provides three execution environments: + +1. **LocalBox** (Development): +```python +codebox = CodeBox(api_key="local") +``` + +2. **DockerBox** (Local Isolation): +```python +codebox = CodeBox(api_key="docker") +``` + +3. **RemoteBox** (Production): +```python +codebox = CodeBox(api_key="your-api-key") +``` diff --git a/docs/settings.md b/docs/settings.md deleted file mode 100644 index 4ee927a..0000000 --- a/docs/settings.md +++ /dev/null @@ -1,33 +0,0 @@ -# Settings - -## Settings Class Overview - -The configuration settings are encapsulated within the `CodeBoxSettings` class, which inherits from Pydantic's `BaseSettings` class. - -`codeboxapi/config.py` - -```python -class CodeBoxSettings(BaseSettings): - ... -``` - -## Setting Descriptions - -### Logging Settings - -- `VERBOSE: bool = False` - Determines if verbose logging is enabled or not. - -- `SHOW_INFO: bool = True` - Determines if information-level logging is enabled. - -### CodeBox API Settings - -- `CODEBOX_API_KEY: str | None = None` - The API key for CodeBox. - -- `CODEBOX_BASE_URL: str = "https://codeboxapi.com/api/v1"` - The base URL for the CodeBox API. - -- `CODEBOX_TIMEOUT: int = 20` - Timeout for CodeBox API requests, in seconds. diff --git a/docs/usage.md b/docs/usage.md deleted file mode 100644 index 1b2ac39..0000000 --- a/docs/usage.md +++ /dev/null @@ -1,37 +0,0 @@ -# Usage - -To use CodeBox, you first need to obtain an API key from [the CodeBox website](https://pay.codeboxapi.com/b/00g3e6dZX2fTg0gaEE). - -You can then start a CodeBox session: - -```python -from codeboxapi import CodeBox - -with CodeBox() as codebox: - codebox.run("a = 'Hello World!'") - codebox.run("print(a)") -``` - -The context manager (`with * as *:`) will automatically start and shutdown the CodeBox. - -You can also use CodeBox asynchronously: - -```python -import asyncio -from codeboxapi import CodeBox - -async def main(): - async with CodeBox() as codebox: - await codebox.astatus() - await codebox.arun("print('Hello World!')") - -asyncio.run(main()) -``` - -## CodeBox API Key - -If you want to use remote code execution, you will need to obtain an [API Key](https://pay.codeboxapi.com/b/00g3e6dZX2fTg0gaEE). This is nessesary for deployment when you care about security of your backend system and you want to serve multiple users in parallel. The CodeBox API Cloud service provides auto scaled infrastructure to run your code in a secure sandboxed environment. - -## LocalBox - -If you just want to experiment local with this you dont need an api key and by not inserting one the system will automatically use a LocalBox in the background when you call the CodeBox() class. diff --git a/mkdocs.yml b/mkdocs.yml index b4eeff1..c153243 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,23 +1,58 @@ -site_name: CodeBox API - Docs +site_name: CodeBox API Documentation +site_description: Secure Python Code Execution in Isolated Environments site_author: Shroominic site_url: https://shroominic.github.io/codebox-api/ repo_name: shroominic/codebox-api repo_url: https://github.com/shroominic/codebox-api/ -nav: - - 'Getting Started': - - 'Welcome': 'index.md' - - 'Installation': 'installation.md' - - 'Usage': 'usage.md' - - 'Settings': 'settings.md' - - 'Concepts': 'concepts.md' - - 'Examples': 'examples.md' - - 'API Reference': 'api.md' - theme: name: material palette: scheme: slate + features: + - navigation.tabs + - navigation.sections + - navigation.expand + - navigation.top + - search.suggest + - search.highlight + - content.code.copy + - content.code.annotate + +nav: + - Introduction: + - Overview: index.md + - Quick Start: quickstart.md + - API Reference: + - Settings: api/settings.md + - CodeBox Class: api/codebox.md + - Exceptions: api/exceptions.md + - Core Concepts: + - Architecture: concepts/architecture.md + - Base Components: concepts/components.md + - User Guide: + - Basic Usage: guides/basic.md + - File Operations: guides/files.md + - Package Management: guides/packages.md + + - Examples: + - Basic Examples: examples/basic.md + - File Handling: examples/files.md + - Async Examples: examples/async.md + - Advanced Usage: examples/advanced.md + +markdown_extensions: + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - admonition + - footnotes + - attr_list + - md_in_html + - def_list + - tables plugins: - - neoteroi.mkdocsoad + - search \ No newline at end of file diff --git a/requirements.lock b/requirements.lock index 734fe1b..b71506a 100644 --- a/requirements.lock +++ b/requirements.lock @@ -7,7 +7,6 @@ # all-features: false # with-sources: false # generate-hashes: false -# universal: false -e file:. anyio==4.6.2.post1 @@ -15,8 +14,6 @@ anyio==4.6.2.post1 certifi==2024.8.30 # via httpcore # via httpx -exceptiongroup==1.2.2 - # via anyio h11==0.14.0 # via httpcore httpcore==1.0.6 @@ -29,5 +26,3 @@ idna==3.10 sniffio==1.3.1 # via anyio # via httpx -typing-extensions==4.12.2 - # via anyio diff --git a/src/codeboxapi/api.py b/src/codeboxapi/api.py index 67d704c..ff27b1f 100644 --- a/src/codeboxapi/api.py +++ b/src/codeboxapi/api.py @@ -1,8 +1,8 @@ import asyncio +import typing as t from contextlib import asynccontextmanager from datetime import datetime, timedelta from os import getenv, path -import typing as t from fastapi import Body, Depends, FastAPI, HTTPException, UploadFile from fastapi.responses import FileResponse, StreamingResponse diff --git a/src/codeboxapi/remote.py b/src/codeboxapi/remote.py index 9d8710a..3b5825f 100644 --- a/src/codeboxapi/remote.py +++ b/src/codeboxapi/remote.py @@ -5,6 +5,12 @@ import anyio import httpx +from tenacity import ( + retry, + retry_if_exception, + stop_after_attempt, + wait_exponential, +) from .codebox import CodeBox from .types import ExecChunk, RemoteFile @@ -46,6 +52,14 @@ def __init__( self.client = httpx.Client(base_url=self.url, headers=self.headers) self.aclient = httpx.AsyncClient(base_url=self.url, headers=self.headers) + @retry( + retry=retry_if_exception( + lambda e: isinstance(e, httpx.HTTPStatusError) + and e.response.status_code == 502 + ), + wait=wait_exponential(multiplier=1, min=5, max=150), + stop=stop_after_attempt(3), + ) def stream_exec( self, code: t.Union[str, PathLike], @@ -72,6 +86,14 @@ def stream_exec( yield ExecChunk(type=t, content=c) # type: ignore[arg-type] buffer = buffer[end:] + @retry( + retry=retry_if_exception( + lambda e: isinstance(e, httpx.HTTPStatusError) + and e.response.status_code == 502 + ), + wait=wait_exponential(multiplier=1, min=5, max=150), + stop=stop_after_attempt(3), + ) async def astream_exec( self, code: t.Union[str, PathLike], @@ -105,6 +127,14 @@ async def astream_exec( async for c in self.astream_exec(code, kernel, timeout, cwd): yield c + @retry( + retry=retry_if_exception( + lambda e: isinstance(e, httpx.HTTPStatusError) + and e.response.status_code == 502 + ), + wait=wait_exponential(multiplier=1, min=5, max=150), + stop=stop_after_attempt(3), + ) def upload( self, file_name: str, @@ -122,6 +152,14 @@ def upload( ).raise_for_status() return RemoteFile(path=file_name, remote=self) + @retry( + retry=retry_if_exception( + lambda e: isinstance(e, httpx.HTTPStatusError) + and e.response.status_code == 502 + ), + wait=wait_exponential(multiplier=1, min=5, max=150), + stop=stop_after_attempt(3), + ) async def aupload( self, remote_file_path: str, @@ -140,6 +178,14 @@ async def aupload( response.raise_for_status() return RemoteFile(path=remote_file_path, remote=self) + @retry( + retry=retry_if_exception( + lambda e: isinstance(e, httpx.HTTPStatusError) + and e.response.status_code == 502 + ), + wait=wait_exponential(multiplier=1, min=5, max=150), + stop=stop_after_attempt(3), + ) def stream_download( self, remote_file_path: str, @@ -150,9 +196,18 @@ def stream_download( url=f"/files/download/{remote_file_path}", timeout=timeout, ) as response: + response.raise_for_status() for chunk in response.iter_bytes(): yield chunk + @retry( + retry=retry_if_exception( + lambda e: isinstance(e, httpx.HTTPStatusError) + and e.response.status_code == 502 + ), + wait=wait_exponential(multiplier=1, min=5, max=150), + stop=stop_after_attempt(3), + ) async def astream_download( self, remote_file_path: str, @@ -163,5 +218,6 @@ async def astream_download( url=f"/files/download/{remote_file_path}", timeout=timeout, ) as response: + response.raise_for_status() async for chunk in response.aiter_bytes(): yield chunk