Skip to main content

Python SDK for Lumina sandboxes — create, manage, and interact with isolated dev environments

Project description

Lumina Python SDK

The lumina_sandbox Python package provides an async client for the Lumina tool endpoints, plus integration with the bu-agent-sdk for building AI coding agents.

Table of Contents


Installation

The SDK requires Python 3.9+.

# Core SDK
pip install lumina-sandbox

# With agent integration (requires Python >= 3.11)
pip install lumina-sandbox[agent]

Quick Start

import asyncio
from lumina_sandbox import LuminaToolClient

async def main():
    async with LuminaToolClient(
        base_url="http://localhost:8080",
        token="your-api-key-here",
        lumina_name="my-sandbox",
    ) as client:
        # Run a command
        result = await client.bash("echo Hello from Lumina!")
        print(result.output)  # "Hello from Lumina!\n"

        # Read a file
        content = await client.read("/home/user/main.py")
        print(f"File has {content.total_lines} lines")

        # Search for Python files
        files = await client.glob("**/*.py")
        print(f"Found {files.count} Python files")

asyncio.run(main())

LuminaToolClient

Initialization

from lumina_sandbox import LuminaToolClient

client = LuminaToolClient(
    base_url="http://localhost:8080",     # Lumina server URL
    token="a1b2c3d4...",                  # API key (64-char hex) or JWT
    lumina_name="my-sandbox",             # Sandbox name
    tool_session_id=None,                 # Auto-generated UUID if not provided
    timeout=660.0,                        # HTTP request timeout in seconds
)
Parameter Type Required Default Description
base_url str yes Lumina server URL
token str yes API key or JWT token
lumina_name str yes Target sandbox name
tool_session_id str no auto UUID Session ID for state tracking
timeout float no 660.0 HTTP timeout in seconds

The client is an async context manager:

# Recommended: auto-closes on exit
async with LuminaToolClient(...) as client:
    await client.bash("echo hello")

# Manual lifecycle
client = LuminaToolClient(...)
try:
    await client.bash("echo hello")
finally:
    await client.close()

Sandbox Lifecycle

The client provides methods for the full sandbox lifecycle:

# Create a sandbox and use tools
async with LuminaToolClient(
    base_url="http://localhost:8080",
    token="your-api-key",
    lumina_name="my-sandbox",
) as client:
    await client.create_sandbox()          # Create and bootstrap
    result = await client.bash("echo hi")  # Use tools
    await client.destroy_sandbox()         # Cleanup

Or use the convenience class method to create and connect in one call:

async with await LuminaToolClient.create(
    base_url="http://localhost:8080",
    token="your-api-key",
    name="my-sandbox",
) as client:
    result = await client.bash("echo hi")
    await client.destroy_sandbox()

create_sandbox()

async def create_sandbox(eager: bool = True) -> dict

Creates the sandbox on the server. If eager=True (default), immediately acquires a container and bootstraps the user environment so the first tool call has zero cold-start latency. Returns the server response dict with sandbox metadata and lease info.

list_sandboxes()

async def list_sandboxes() -> list[dict]

Returns all sandboxes for the authenticated user.

restart_sandbox()

async def restart_sandbox() -> dict

Restarts the sandbox container with the latest available image. Running processes are terminated but persistent storage is retained.

destroy_sandbox()

async def destroy_sandbox() -> None

Destroys the sandbox and releases its container lease.

File Tools

read()

Read a file from the sandbox. Automatically detects text files, images, and PDFs.

async def read(
    file_path: str,
    offset: Optional[int] = None,    # Starting line (1-based)
    limit: Optional[int] = None,     # Max lines to return
) -> TextFileResult | ImageFileResult | PDFFileResult

Examples:

# Read a text file
result = await client.read("/home/user/main.py")
print(result.content)        # "   1\timport os\n   2\t..."
print(result.total_lines)    # 150
print(result.lines_returned) # 150

# Read with pagination (lines 50-100)
result = await client.read("/home/user/big_file.py", offset=50, limit=50)
print(result.lines_returned)  # 50

# Read an image (returns base64)
result = await client.read("/home/user/screenshot.png")
print(result.mime_type)   # "image/png"
print(result.file_size)   # 24567
# result.image contains base64-encoded data

# Read a PDF
result = await client.read("/home/user/document.pdf")
for page in result.pages:
    print(f"Page {page.page_number}: {page.text[:100]}...")

write()

Write content to a file. Creates parent directories automatically.

async def write(
    file_path: str,
    content: str,
) -> WriteResult

Examples:

# Write a new file
result = await client.write("/home/user/hello.txt", "Hello, world!\n")
print(result.bytes_written)  # 14

# Write a Python script
result = await client.write("/home/user/script.py", """#!/usr/bin/env python3
import sys

def main():
    print("Hello from script!")
    return 0

if __name__ == "__main__":
    sys.exit(main())
""")

# Overwrite requires reading first (safety check)
await client.read("/home/user/hello.txt")  # Must read first!
result = await client.write("/home/user/hello.txt", "Updated content\n")

edit()

Edit a file using exact string replacement.

async def edit(
    file_path: str,
    old_string: str,
    new_string: str,
    replace_all: bool = False,
) -> EditResult

Examples:

# Read the file first (required)
await client.read("/home/user/main.py")

# Replace a single occurrence
result = await client.edit(
    "/home/user/main.py",
    old_string="def hello():",
    new_string="def hello(name: str = 'World'):",
)
print(result.replacements)  # 1

# Replace all occurrences
result = await client.edit(
    "/home/user/main.py",
    old_string="print",
    new_string="logging.info",
    replace_all=True,
)
print(result.replacements)  # 5

# If old_string appears multiple times and replace_all=False,
# the server returns a 400 error with the count. Use more context
# to make the match unique, or set replace_all=True.

Search Tools

glob()

Search for files matching a glob pattern.

async def glob(
    pattern: str,
    path: Optional[str] = None,    # Search directory (default: user home)
) -> GlobResult

Examples:

# Find all Python files
result = await client.glob("**/*.py")
print(result.count)     # 12
print(result.matches)   # ["/home/user/main.py", "/home/user/utils.py", ...]

# Find in a specific directory
result = await client.glob("*.ts", path="/home/user/src")
for f in result.matches:
    print(f)

# Find test files
result = await client.glob("**/test_*.py", path="/home/user/project")

Results are sorted by modification time (newest first).

grep()

Search file contents using regular expressions (powered by ripgrep).

async def grep(
    pattern: str,
    path: Optional[str] = None,
    glob: Optional[str] = None,           # File glob filter
    output_mode: str = "files_with_matches",
    line_numbers: bool = False,
    after: int = 0,                       # Context lines after match
    before: int = 0,                      # Context lines before match
    context: int = 0,                     # Context lines both sides
    file_type: Optional[str] = None,      # File type filter (e.g., "py")
    case_insensitive: bool = False,
    multiline: bool = False,
) -> GrepContentResult | GrepFilesResult | GrepCountResult

Examples:

# Find files containing "TODO"
result = await client.grep("TODO")
print(result.count)  # 3
print(result.files)  # ["/home/user/main.py", ...]

# Search with context lines (content mode)
result = await client.grep(
    "def main",
    path="/home/user/project",
    output_mode="content",
    line_numbers=True,
    after=5,
)
for match in result.matches:
    print(f"{match.file}:{match.line_number}")
    print(f"  {match.line}")
    for ctx in match.after_context:
        print(f"  {ctx}")

# Count occurrences per file
result = await client.grep("import", output_mode="count", file_type="py")
for entry in result.counts:
    print(f"{entry.file}: {entry.count} imports")
print(f"Total: {result.total}")

# Case-insensitive search in specific file types
result = await client.grep(
    "error|warning",
    output_mode="content",
    case_insensitive=True,
    glob="*.log",
)

# Multiline regex
result = await client.grep(
    r"class \w+:.*\n\s+def __init__",
    output_mode="content",
    multiline=True,
    file_type="py",
)

Bash Tools

bash()

Execute a bash command in a persistent tmux session.

async def bash(
    command: str,
    timeout: Optional[int] = None,       # Timeout in ms (default: 120000)
    description: Optional[str] = None,    # Human-readable description
    run_in_background: bool = False,
) -> BashResult

Examples:

# Simple command
result = await client.bash("ls -la /home/user")
print(result.output)     # directory listing
print(result.exit_code)  # 0

# Install packages
result = await client.bash("pip install requests flask", timeout=60000)
if result.exit_code != 0:
    print(f"Install failed: {result.output}")

# Run tests
result = await client.bash("cd /home/user/project && python -m pytest -v", timeout=300000)
print(f"Tests {'passed' if result.exit_code == 0 else 'failed'}")

# Environment persists across calls
await client.bash("export DATABASE_URL=postgres://localhost/mydb")
result = await client.bash("echo $DATABASE_URL")
print(result.output)  # "postgres://localhost/mydb\n"

# Working directory persists
await client.bash("cd /home/user/project/src")
result = await client.bash("pwd")
print(result.output)  # "/home/user/project/src\n"

# Background command
result = await client.bash("npm run dev", run_in_background=True)
print(result.shell_id)  # "bash_a1b2c3d4" -- use for polling

# Timeout enforcement
result = await client.bash("sleep 60", timeout=3000)
print(result.killed)     # True
print(result.exit_code)  # 124

bash_output()

Get incremental output from a background bash command.

async def bash_output(
    bash_id: str,
    filter: Optional[str] = None,    # Only lines containing this substring
) -> BashOutputResult

Examples:

# Start a long-running command
result = await client.bash("npm run build 2>&1", run_in_background=True)
shell_id = result.shell_id

# Poll for output
import asyncio
while True:
    status = await client.bash_output(shell_id)
    if status.output:
        print(status.output, end="")
    if status.status in ("completed", "killed", "failed"):
        print(f"\nDone with exit code: {status.exit_code}")
        break
    await asyncio.sleep(2)

# Filter output to only show errors
status = await client.bash_output(shell_id, filter="ERROR")

Status values: "running", "completed", "killed", "failed"

kill_bash()

Terminate a running background command.

async def kill_bash(shell_id: str) -> dict

Example:

# Start a long-running server
result = await client.bash("python -m http.server 8000", run_in_background=True)

# ... do some work ...

# Kill it when done
await client.kill_bash(result.shell_id)

Error Handling

All tool methods raise ToolError on HTTP 4xx/5xx responses:

from lumina_sandbox import ToolError

try:
    result = await client.read("/home/user/nonexistent.txt")
except ToolError as e:
    print(f"Error {e.status_code}: {e}")
    # Error 404: file not found: /home/user/nonexistent.txt

Common error scenarios:

Status Cause Example
400 Invalid request Path not absolute, ambiguous edit match
404 Not found File doesn't exist, sandbox not found
409 Conflict Write without prior Read, session binding mismatch
# Handle write-before-read
try:
    await client.write("/home/user/existing.txt", "new content")
except ToolError as e:
    if e.status_code == 409:
        # Need to read first
        await client.read("/home/user/existing.txt")
        await client.write("/home/user/existing.txt", "new content")

# Handle ambiguous edit
try:
    await client.edit("/home/user/main.py", "x", "y")
except ToolError as e:
    if e.status_code == 400 and "occurrences" in str(e):
        # Use more context or replace_all=True
        await client.edit("/home/user/main.py", "x", "y", replace_all=True)

Result Types

All result types are Python dataclass instances defined in lumina_sandbox.types:

from lumina_sandbox import (
    BashResult,           # output, exit_code, killed, shell_id
    BashOutputResult,     # output, status, exit_code (Optional[int])
    TextFileResult,       # content, total_lines, lines_returned
    ImageFileResult,      # image (base64), mime_type, file_size
    PDFFileResult,        # pages: list[PDFPage], total_pages
    WriteResult,          # message, bytes_written, file_path
    EditResult,           # message, replacements, file_path
    GlobResult,           # matches: list[str], count, search_path
    GrepContentResult,    # matches: list[GrepContentMatch], total_matches
    GrepFilesResult,      # files: list[str], count
    GrepCountResult,      # counts: list[GrepCountEntry], total
    ToolError,            # Exception with status_code: int
)

BashResult

@dataclass
class BashResult:
    output: str        # Command stdout+stderr (combined)
    exit_code: int     # Process exit code (124 if timed out)
    killed: bool       # True if command was killed by timeout
    shell_id: str      # Non-empty only for background commands

BashOutputResult

@dataclass
class BashOutputResult:
    output: str              # Incremental output since last read
    status: str              # "running", "completed", "killed", "failed"
    exit_code: Optional[int] # Set when status != "running"

TextFileResult

@dataclass
class TextFileResult:
    content: str         # File content with line numbers (tab-separated)
    total_lines: int     # Total lines in the file
    lines_returned: int  # Lines returned in this response

ImageFileResult

@dataclass
class ImageFileResult:
    image: str       # Base64-encoded image data
    mime_type: str   # e.g., "image/png", "image/jpeg"
    file_size: int   # File size in bytes

PDFFileResult / PDFPage

@dataclass
class PDFPage:
    page_number: int
    text: str

@dataclass
class PDFFileResult:
    pages: list[PDFPage]
    total_pages: int

WriteResult

@dataclass
class WriteResult:
    message: str       # "ok"
    bytes_written: int
    file_path: str

EditResult

@dataclass
class EditResult:
    message: str       # "ok"
    replacements: int  # Number of replacements made
    file_path: str

GlobResult

@dataclass
class GlobResult:
    matches: list[str]   # Matching file paths (newest first)
    count: int
    search_path: str     # Directory that was searched

GrepContentMatch / GrepContentResult

@dataclass
class GrepContentMatch:
    file: str
    line_number: Optional[int]
    line: str
    before_context: list[str]
    after_context: list[str]

@dataclass
class GrepContentResult:
    matches: list[GrepContentMatch]
    total_matches: int

GrepFilesResult

@dataclass
class GrepFilesResult:
    files: list[str]
    count: int

GrepCountEntry / GrepCountResult

@dataclass
class GrepCountEntry:
    file: str
    count: int

@dataclass
class GrepCountResult:
    counts: list[GrepCountEntry]
    total: int

bu-agent-sdk Integration

The SDK provides two ways to use Lumina tools with bu-agent-sdk:

Using make_tools()

The make_tools() function creates tool-compatible async functions from a LuminaToolClient:

import asyncio
from bu_agent_sdk import Agent
from bu_agent_sdk.llm import ChatAnthropic
from lumina_sandbox import LuminaToolClient
from lumina_sandbox.tools import make_tools

async def main():
    client = LuminaToolClient(
        base_url="http://localhost:8080",
        token="your-api-key",
        lumina_name="my-sandbox",
    )

    tools = make_tools(client)
    # tools = {"Read": fn, "Write": fn, "Edit": fn, "Glob": fn,
    #          "Grep": fn, "Bash": fn, "BashOutput": fn, "KillBash": fn}

    agent = Agent(
        llm=ChatAnthropic(model="claude-sonnet-4-20250514"),
        tools=list(tools.values()),
    )

    result = await agent.query("Read /home/user/main.py and add type hints")
    print(result)

    await client.close()

asyncio.run(main())

The returned tool functions accept the same parameters as the LuminaToolClient methods and return plain dicts (for agent SDK compatibility).

Demo Agent

The demo_agent module provides a complete, ready-to-run agent with 9 tools (8 Lumina tools + done):

from lumina_sandbox.demo_agent import create_agent, run

# Create agent (configures tools, LLM, etc.)
agent = create_agent()

# Run a task
import asyncio
result = asyncio.run(run("Read /home/user/main.py and refactor the main function"))

From the command line:

export LUMINA_URL=http://localhost:8080
export LUMINA_TOKEN=your-api-key
export LUMINA_NAME=my-sandbox
export ANTHROPIC_API_KEY=sk-ant-...

python -m lumina_sandbox.demo_agent "List all Python files and count total lines of code"

Environment variables:

Variable Required Default Description
LUMINA_URL no http://localhost:8080 Lumina server URL
LUMINA_TOKEN yes API key for authentication
LUMINA_NAME yes Sandbox name
ANTHROPIC_API_KEY yes Anthropic API key
AGENT_MODEL no claude-sonnet-4-20250514 Model to use

Building a Custom Agent

import asyncio
import os
from bu_agent_sdk import Agent
from bu_agent_sdk.agent import TaskComplete
from bu_agent_sdk.tools import tool
from bu_agent_sdk.llm import ChatAnthropic
from lumina_sandbox import LuminaToolClient
from lumina_sandbox.tools import make_tools

async def main():
    client = LuminaToolClient(
        base_url=os.environ.get("LUMINA_URL", "http://localhost:8080"),
        token=os.environ["LUMINA_TOKEN"],
        lumina_name=os.environ["LUMINA_NAME"],
    )

    # Get the standard Lumina tools
    lumina_tools = make_tools(client)

    # Add custom tools
    @tool("Signal task completion with a summary message.")
    async def done(message: str) -> str:
        raise TaskComplete(message)

    @tool("Search the web for documentation or answers.")
    async def web_search(query: str) -> str:
        # Your custom implementation
        return f"Search results for: {query}"

    # Combine all tools
    all_tools = list(lumina_tools.values()) + [done, web_search]

    agent = Agent(
        llm=ChatAnthropic(model="claude-sonnet-4-20250514"),
        tools=all_tools,
        system_prompt=(
            "You are a coding assistant with access to a Linux sandbox. "
            "You can read, write, and edit files, run bash commands, "
            "and search the web. When done, call the done tool."
        ),
        require_done_tool=True,
    )

    result = await agent.query("Set up a Flask web server with tests")
    print(result)

    await client.close()

asyncio.run(main())

Interactive Chat CLI

The SDK includes a full interactive chat interface that manages the sandbox lifecycle automatically:

# Required environment variables
export LUMINA_HOST=http://localhost:8080   # or LUMINA_URL
export LUMINA_TOKEN=your-api-key
export ANTHROPIC_API_KEY=sk-ant-...

# Launch the chat
python -m lumina_sandbox

The chat CLI will:

  1. Create a temporary sandbox (named chat-<random>)
  2. Present an interactive prompt where you type tasks
  3. Stream the agent's tool calls and responses with colored output
  4. Destroy the sandbox on exit

Reuse an existing sandbox (sandbox will NOT be destroyed on exit):

export LUMINA_SANDBOX_NAME=my-sandbox
python -m lumina_sandbox

Sample session:

╔══════════════════════════════════════════════════╗
║       Lumina Sandbox — Interactive Chat           ║
╚══════════════════════════════════════════════════╝

  Server:  http://localhost:8080
  Sandbox: chat-a1b2c3d4

you > Create a Python project with a calculator module and tests

  ● bash_cmd(command="mkdir -p /home/user/calc && cd /home/user/calc")
    → ...
  ● write_file(file_path="/home/user/calc/calculator.py", content="...")
    → {'message': 'ok', 'bytes_written': 340, ...}
  ● write_file(file_path="/home/user/calc/test_calculator.py", content="...")
    → {'message': 'ok', 'bytes_written': 520, ...}
  ● bash_cmd(command="cd /home/user/calc && python -m pytest -v")
    → test_calculator.py::test_add PASSED\ntest_calculator.py::test_sub...

agent > Created a calculator project at /home/user/calc with add, subtract,
multiply, and divide functions. All 8 tests pass.

you > exit

Environment variables:

Variable Required Default Description
LUMINA_HOST no http://localhost:8080 Lumina server URL
LUMINA_TOKEN yes API key
ANTHROPIC_API_KEY yes Anthropic API key
ANTHROPIC_BASE_URL no Custom Anthropic API base URL (for proxies)
AGENT_MODEL no claude-sonnet-4-20250514 Model to use
LUMINA_SANDBOX_NAME no Reuse existing sandbox (skip create/destroy)

Advanced Usage

Tool Sessions

Every LuminaToolClient instance has a tool_session_id that tracks server-side state:

# Auto-generated session ID
client = LuminaToolClient(base_url="...", token="...", lumina_name="sandbox")
print(client.tool_session_id)  # "550e8400-e29b-41d4-..."

# Explicit session ID (e.g., to resume a previous session)
client = LuminaToolClient(
    base_url="...", token="...", lumina_name="sandbox",
    tool_session_id="my-session-id",
)

Session state includes:

  • Read file tracking: Which files have been read (for write/edit safety checks)
  • Tmux session: Persistent shell with environment variables and working directory
  • Background commands: Running processes and their output buffers

Sessions are bound to a (api_key, sandbox) pair. Using the same session ID with a different API key or sandbox returns an error. Sessions expire after 8 hours.

Read-Before-Write Safety

The server enforces a read-before-write pattern: you cannot overwrite an existing file unless you've read it in the current session. This prevents agents from accidentally destroying file contents they haven't seen.

# New file: Write succeeds without prior Read
await client.write("/home/user/new_file.txt", "content")  # OK

# Existing file: Must Read first
try:
    await client.write("/home/user/new_file.txt", "updated")
except ToolError as e:
    print(e.status_code)  # 409

await client.read("/home/user/new_file.txt")  # Track the read
await client.write("/home/user/new_file.txt", "updated")  # Now OK

# Different session = different tracking
client2 = LuminaToolClient(base_url="...", token="...", lumina_name="sandbox")
try:
    await client2.write("/home/user/new_file.txt", "from other session")
except ToolError as e:
    print(e.status_code)  # 409 - this session hasn't read the file

Background Commands

Use background execution for long-running processes (servers, builds, watchers):

# Start a dev server in the background
result = await client.bash("cd /home/user/app && npm run dev", run_in_background=True)
server_id = result.shell_id

# Wait for it to start
import asyncio
await asyncio.sleep(3)

# Check if it's running and see output
status = await client.bash_output(server_id)
print(f"Status: {status.status}")    # "running"
print(f"Output: {status.output}")    # "Server started on port 3000\n"

# Do some testing
result = await client.bash("curl -s http://localhost:3000/health")
print(result.output)  # "ok"

# Subsequent bash_output calls return only NEW output (incremental)
status = await client.bash_output(server_id)
print(f"New output: {status.output}")  # Only output since last check

# Filter output to specific lines
status = await client.bash_output(server_id, filter="ERROR")

# Kill when done
await client.kill_bash(server_id)

Environment Persistence

The Bash tool uses tmux sessions under the hood, so shell state persists:

# Set environment variables
await client.bash("export NODE_ENV=production")
await client.bash("export PATH=$HOME/.local/bin:$PATH")

# They persist in subsequent calls
result = await client.bash("echo $NODE_ENV")
print(result.output)  # "production\n"

# Working directory also persists
await client.bash("cd /home/user/project/src")
result = await client.bash("pwd")
print(result.output)  # "/home/user/project/src\n"

# Shell aliases persist too
await client.bash("alias ll='ls -la'")
result = await client.bash("ll")
print(result.output)  # detailed directory listing

API Reference

LuminaToolClient

Method Parameters Returns Description
create (classmethod) base_url, token, name, eager?, ... LuminaToolClient Create sandbox + client
create_sandbox eager? dict Create the sandbox
list_sandboxes list[dict] List all sandboxes
restart_sandbox dict Restart sandbox container
destroy_sandbox None Destroy sandbox
read file_path, offset?, limit? TextFileResult | ImageFileResult | PDFFileResult Read a file
write file_path, content WriteResult Write a file
edit file_path, old_string, new_string, replace_all? EditResult Edit via string replacement
glob pattern, path? GlobResult Search for files by glob
grep pattern, path?, glob?, output_mode?, ... GrepContentResult | GrepFilesResult | GrepCountResult Search file contents
bash command, timeout?, description?, run_in_background? BashResult Execute shell command
bash_output bash_id, filter? BashOutputResult Poll background command
kill_bash shell_id dict Kill background command
close None Close HTTP client

make_tools()

from lumina_sandbox.tools import make_tools

tools = make_tools(client)  # Returns dict[str, Callable]
# Keys: "Read", "Write", "Edit", "Glob", "Grep", "Bash", "BashOutput", "KillBash"

Each tool function returns a plain dict (converted from the dataclass via .__dict__), suitable for direct use with bu-agent-sdk.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lumina_sandbox-0.1.1.tar.gz (277.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lumina_sandbox-0.1.1-py3-none-any.whl (24.2 kB view details)

Uploaded Python 3

File details

Details for the file lumina_sandbox-0.1.1.tar.gz.

File metadata

  • Download URL: lumina_sandbox-0.1.1.tar.gz
  • Upload date:
  • Size: 277.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for lumina_sandbox-0.1.1.tar.gz
Algorithm Hash digest
SHA256 723b7a5f6e71331298dde1df2fa3056b4381fa11106586e0edb3ba5e5d9ad0a2
MD5 39d0cc8f88d6789f09268cb3477ad76f
BLAKE2b-256 a2f9eeed634d119649b1fdffb41f570704d24e7c7e8077e70b720431a590eb31

See more details on using hashes here.

File details

Details for the file lumina_sandbox-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: lumina_sandbox-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 24.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for lumina_sandbox-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a3d7895c0de2eafb270502bdccb092fd6393b75097c32f3770d0be438e9d3325
MD5 844b5ef4fe662a009a9abe5ff81ddf68
BLAKE2b-256 c9675622907410e92653f75fd2c7c194ce791e45eeb3d984033c83b62affa27b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page