tiny-python 0.2.2
A safe execution environment for a Python-like DSL
Requires Python: >=3.8

# tiny-python (pre-release)

**WARNING: This library is still under development and needs to be thoroughly tested for security. Use at your own risk.**

LLMs are very good at writing python code!

`tiny-python` allows you to take advantage of this without your web app getting hacked.

It is very simple: use `tiny_exec(unsafe_llm_code)` on code generated by an LLM, and a python-like language will be run instead of dangerously running real python.

## Why?

- **More powerful structured outputs**: OpenAI supports [structured outputs](https://platform.openai.com/docs/guides/structured-outputs), but you must define the schema rigorously beforehand, and it is really only designed to handle small numbers of classes. What if you are having the AI create a complicated nested and polymorphic structure like video game level? What if you want the AI to be able to use mathematical functions to lay out positions of where things are? What if you are trying to represent a graph structure with cycles instead of a tree structure?

**Solution**: Have the LLM generate python and evaluate it safely using `tiny-python`. You can control the number of iterations of execution the code is allowed.

## Does tiny-python guarantee security?

**No:**

`tiny-python` allows you to pass your own functions and dataclasses into the execution environment! You must use this feature responsibly. If you want to see a toy example of insecure usage of `tiny-python`:

```python
# BAD: Suppose we define an insecure dataclass
@dataclass
class MyInsecureClass:
  code: str
  def __post_init__(self):
    exec(self.code)  # exec is bad.

# BAD: Then we run on an LLM output
tiny_exec(llm_output, allowed_classes=[MyInsecureClass])
```

To use this library properly, you should avoid providing it access to dataclasses with insecure side effects, or that are used in an insecure way. Care must be taken when you pass any of the following extra arguments to the `tiny_exec` (see more docs on these below):

- `allowed_classes`
- `global_vars`

## Features

- Safe execution without using Python's `exec` or `eval`
- Support for math operations, string operations, and dataclass instantiation
- Control flow structures (if, for, while)
- Configurable execution limits (iterations, recursion depth)

## Installation

```bash
pip install tiny-python
```

## Quick Start

```python
from tiny_python import tiny_exec, tiny_eval_last
from dataclasses import dataclass
from typing import List, Optional

# Define dataclasses that model an organization
# Note: These can have circular references to each other
@dataclass
class Person:
    name: str
    role: str
    team: Optional['Team'] = None

@dataclass
class Team:
    name: str
    description: str
    leader: Optional[Person] = None
    members: List[Person] = None

    def __post_init__(self):
        if self.members is None:
            self.members = []

@dataclass
class Project:
    title: str
    team: Team
    budget: float
    priority: int

# Use tiny_exec to safely execute LLM-generated code that builds complex structures
code = """
# Create team members
alice = Person("Alice", "Engineer")
bob = Person("Bob", "Designer")
charlie = Person("Charlie", "Manager")

# Create a team with a leader
dev_team = Team("Development Team", "Builds awesome products", charlie)
dev_team.members = [alice, bob, charlie]

# Set team references (creating cycles)
alice.team = dev_team
bob.team = dev_team
charlie.team = dev_team

# Create a project for this team
project = Project("New Website", dev_team, 50000.0, 1)

# Calculate some metrics
team_size = len(dev_team.members)
budget_per_person = project.budget / team_size
high_priority = project.priority == 1

# Store results in a summary
summary = {
    "project_name": project.title,
    "team_leader": dev_team.leader.name,
    "team_size": team_size,
    "budget_per_person": budget_per_person,
    "is_high_priority": high_priority
}
"""

# Execute the code safely with the allowed dataclasses
result = tiny_exec(code, allowed_classes=[Person, Team, Project])

# Access the created objects and computed values from the returned locals
print(result["summary"])
# {'project_name': 'New Website', 'team_leader': 'Charlie', 'team_size': 3,
#  'budget_per_person': 16666.666666666668, 'is_high_priority': True}

print(f"Team leader: {result['dev_team'].leader.name}")  # Charlie
print(f"First team member's team: {result['alice'].team.name}")  # Development Team

# Or use tiny_eval_last to get just the last expression value
last_value = tiny_eval_last("2 + 3 * 4")
print(last_value)  # 14
```

You can imagine that the code given in the string above is generated by an LLM that was given a description of the team and its members and projects.

## API

### `tiny_exec(code, **kwargs)`

Execute tiny-python code safely and return the local variables.

Parameters:

- `code`: String containing the code to execute
- `max_iterations`: Maximum number of total operations allowed (default: 1000)
- `max_iterations_per_loop`: Maximum iterations for any single loop (default: 100)
- `allowed_classes`: List of classes that can be instantiated (must be dataclasses)
- `global_vars`: Dictionary of global variables to make available
- `allowed_functions`: List of functions that may be called

Returns a dictionary containing all local variables created during execution. The executor instance is stored in the returned dictionary under the key `"__executor__"`, which provides access to `executor.last_result` for the value of the last expression.

## Supported Operations

- Arithmetic: `+`, `-`, `*`, `/`, `//`, `%`, `**`
- Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in`, `is`, `is not`
- Boolean: `and`, `or`, `not`
- Built-in functions: `len`, `range`, `int`, `float`, `str`, `bool`, `list`, `dict`, `tuple`, `set`, `abs`, `min`, `max`, `sum`, `round`, `sorted`, `reversed`, `enumerate`, `zip`, `all`, `any`, `isinstance`, `type`
- String methods: `upper`, `lower`, `strip`, `lstrip`, `rstrip`, `split`, `join`, `replace`, `startswith`, `endswith`, `find`, `rfind`, `format`, `capitalize`, `title`, `isdigit`, `isalpha`, `isalnum`, `isspace`, and many more string methods
- Control flow: `if`, `elif`, `else`, `for`, `while`, `break`, `continue`, `return`, `pass`
- Variable assignment: `=`, `+=`, `-=`, `*=`, `/=`, `//=`, `%=`, `**=`
- Tuple unpacking in assignments and for loops
- Dataclass instantiation and attribute access (with `allowed_classes`)
- Custom functions (with `allowed_functions`)
- Dictionary and list indexing/slicing

## Security

This package is designed to provide a safe execution environment by:

- Using AST parsing instead of `exec` or `eval`
- Whitelisting allowed operations
- Limiting iteration
- Restricting module imports and file operations
- At this early stage, feel free to open an issue if you find a security vulnerability
- The interpreter disallows access to dangerous python built-ins

## License

MIT